refactor(announcements): Move duplicate markdown code into utils/markdown.js
This commit is contained in:
@@ -130,9 +130,8 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { XCircle, AlertTriangle, Info, CheckCircle, Circle, ChevronDown } from 'lucide-vue-next'
|
||||
import { formatAnnouncementMessage } from '@/utils/markdown'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@@ -237,55 +236,6 @@ const getTypeClasses = (type) => {
|
||||
return typeConfigs[type] || typeConfigs.none
|
||||
}
|
||||
|
||||
const escapeHtml = (value) => {
|
||||
if (value === null || value === undefined) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
||||
renderer.link = (tokenOrHref, title, text) => {
|
||||
const tokenObject = typeof tokenOrHref === 'object' && tokenOrHref !== null
|
||||
? tokenOrHref
|
||||
: null
|
||||
|
||||
const href = tokenObject ? tokenObject.href : tokenOrHref
|
||||
const resolvedTitle = tokenObject ? tokenObject.title : title
|
||||
const resolvedText = tokenObject ? tokenObject.text : text
|
||||
|
||||
const url = escapeHtml(href || '')
|
||||
const titleAttribute = resolvedTitle ? ` title="${escapeHtml(resolvedTitle)}"` : ''
|
||||
const linkText = resolvedText || ''
|
||||
|
||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer"${titleAttribute}>${linkText}</a>`
|
||||
}
|
||||
|
||||
marked.use({
|
||||
renderer,
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
headerIds: false,
|
||||
mangle: false
|
||||
})
|
||||
|
||||
const formatAnnouncementMessage = (message) => {
|
||||
if (!message) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const markdown = String(message)
|
||||
const html = marked.parse(markdown)
|
||||
return DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'rel'] })
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
const today = new Date()
|
||||
@@ -349,22 +299,4 @@ const formatFullTimestamp = (timestamp) => {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.announcement-content :deep(a) {
|
||||
color: #1d4ed8;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.announcement-content :deep(a:hover) {
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.dark .announcement-content :deep(a) {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.dark .announcement-content :deep(a:hover) {
|
||||
color: #93c5fd;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -70,9 +70,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { XCircle, AlertTriangle, Info, CheckCircle, Circle, ChevronDown } from 'lucide-vue-next'
|
||||
import { formatAnnouncementMessage } from '@/utils/markdown'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@@ -181,55 +180,6 @@ const getTypeClasses = (type) => {
|
||||
return typeConfigs[type] || typeConfigs.none
|
||||
}
|
||||
|
||||
const escapeHtml = (value) => {
|
||||
if (value === null || value === undefined) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
||||
renderer.link = (tokenOrHref, title, text) => {
|
||||
const tokenObject = typeof tokenOrHref === 'object' && tokenOrHref !== null
|
||||
? tokenOrHref
|
||||
: null
|
||||
|
||||
const href = tokenObject ? tokenObject.href : tokenOrHref
|
||||
const resolvedTitle = tokenObject ? tokenObject.title : title
|
||||
const resolvedText = tokenObject ? tokenObject.text : text
|
||||
|
||||
const url = escapeHtml(href || '')
|
||||
const titleAttribute = resolvedTitle ? ` title="${escapeHtml(resolvedTitle)}"` : ''
|
||||
const linkText = resolvedText || ''
|
||||
|
||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer"${titleAttribute}>${linkText}</a>`
|
||||
}
|
||||
|
||||
marked.use({
|
||||
renderer,
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
headerIds: false,
|
||||
mangle: false
|
||||
})
|
||||
|
||||
const formatAnnouncementMessage = (message) => {
|
||||
if (!message) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const markdown = String(message)
|
||||
const html = marked.parse(markdown)
|
||||
return DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'rel'] })
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
@@ -260,23 +210,3 @@ const formatFullTimestamp = (timestamp) => {
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.past-announcements :deep(a) {
|
||||
color: #1d4ed8;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.past-announcements :deep(a:hover) {
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.dark .past-announcements :deep(a) {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.dark .past-announcements :deep(a:hover) {
|
||||
color: #93c5fd;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<script setup>
|
||||
/* eslint-disable no-undef */
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { combineClasses } from '@/lib/utils'
|
||||
import { combineClasses } from '@/utils/misc'
|
||||
|
||||
defineProps({
|
||||
variant: {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<script setup>
|
||||
/* eslint-disable no-undef */
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { combineClasses } from '@/lib/utils'
|
||||
import { combineClasses } from '@/utils/misc'
|
||||
|
||||
defineProps({
|
||||
variant: {
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { combineClasses } from '@/lib/utils'
|
||||
import { combineClasses } from '@/utils/misc'
|
||||
</script>
|
||||
@@ -5,5 +5,5 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { combineClasses } from '@/lib/utils'
|
||||
import { combineClasses } from '@/utils/misc'
|
||||
</script>
|
||||
@@ -5,5 +5,5 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { combineClasses } from '@/lib/utils'
|
||||
import { combineClasses } from '@/utils/misc'
|
||||
</script>
|
||||
@@ -5,5 +5,5 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { combineClasses } from '@/lib/utils'
|
||||
import { combineClasses } from '@/utils/misc'
|
||||
</script>
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<script setup>
|
||||
/* eslint-disable no-undef */
|
||||
import { combineClasses } from '@/lib/utils'
|
||||
import { combineClasses } from '@/utils/misc'
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
|
||||
46
web/app/src/utils/markdown.js
Normal file
46
web/app/src/utils/markdown.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { marked } from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
const escapeHtml = (value) => {
|
||||
if (value === null || value === undefined) {
|
||||
return ''
|
||||
}
|
||||
return String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
||||
renderer.link = (tokenOrHref, title, text) => {
|
||||
const tokenObject = typeof tokenOrHref === 'object' && tokenOrHref !== null
|
||||
? tokenOrHref
|
||||
: null
|
||||
const href = tokenObject ? tokenObject.href : tokenOrHref
|
||||
const resolvedTitle = tokenObject ? tokenObject.title : title
|
||||
const resolvedText = tokenObject ? tokenObject.text : text
|
||||
const url = escapeHtml(href || '')
|
||||
const titleAttribute = resolvedTitle ? ` title="${escapeHtml(resolvedTitle)}"` : ''
|
||||
const linkText = resolvedText || ''
|
||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="text-blue-700 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline font-medium"${titleAttribute}>${linkText}</a>`
|
||||
}
|
||||
|
||||
marked.use({
|
||||
renderer,
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
headerIds: false,
|
||||
mangle: false
|
||||
})
|
||||
|
||||
export const formatAnnouncementMessage = (message) => {
|
||||
if (!message) {
|
||||
return ''
|
||||
}
|
||||
const markdown = String(message)
|
||||
const html = marked.parse(markdown)
|
||||
return DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'rel'] })
|
||||
}
|
||||
Reference in New Issue
Block a user