mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-21 13:37:58 +00:00
fix(frontend): share announcement rich text styles
This commit is contained in:
@@ -322,6 +322,7 @@ import { useAnnouncementStore } from '@/stores/announcements'
|
||||
import { formatRelativeTime, formatRelativeWithDateTime } from '@/utils/format'
|
||||
import type { UserAnnouncement } from '@/types'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import '@/styles/announcement-markdown.css'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
@@ -471,120 +472,3 @@ watch(
|
||||
background: linear-gradient(to bottom, #6b7280, #4b5563);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* Enhanced Markdown Styles */
|
||||
.markdown-body {
|
||||
@apply text-[15px] leading-[1.75];
|
||||
@apply text-gray-700 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
@apply mb-6 mt-8 border-b border-gray-200 pb-3 text-3xl font-bold text-gray-900 dark:border-dark-600 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
@apply mb-4 mt-7 border-b border-gray-100 pb-2 text-2xl font-bold text-gray-900 dark:border-dark-700 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
@apply mb-3 mt-6 text-xl font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
@apply mb-2 mt-5 text-lg font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
@apply mb-4 leading-relaxed;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
@apply font-medium text-blue-600 underline decoration-blue-600/30 decoration-2 underline-offset-2 transition-all hover:decoration-blue-600 dark:text-blue-400 dark:decoration-blue-400/30 dark:hover:decoration-blue-400;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
@apply mb-4 ml-6 space-y-2;
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
@apply list-disc;
|
||||
}
|
||||
|
||||
.markdown-body ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
@apply leading-relaxed;
|
||||
@apply pl-2;
|
||||
}
|
||||
|
||||
.markdown-body li::marker {
|
||||
@apply text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
@apply relative my-5 border-l-4 border-blue-500 bg-blue-50/50 py-3 pl-5 pr-4 italic text-gray-700 dark:border-blue-400 dark:bg-blue-900/10 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.markdown-body blockquote::before {
|
||||
content: '"';
|
||||
@apply absolute -left-1 top-0 text-5xl font-serif text-blue-500/20 dark:text-blue-400/20;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
@apply rounded-lg bg-gray-100 px-2 py-1 text-[13px] font-mono text-pink-600 dark:bg-dark-700 dark:text-pink-400;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
@apply my-5 overflow-x-auto rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-dark-600 dark:bg-dark-900/50;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
@apply bg-transparent p-0 text-[13px] text-gray-800 dark:text-gray-200;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
@apply my-8 border-0 border-t-2 border-gray-200 dark:border-dark-700;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
@apply mb-5 w-full overflow-hidden rounded-lg border border-gray-200 dark:border-dark-600;
|
||||
}
|
||||
|
||||
.markdown-body th,
|
||||
.markdown-body td {
|
||||
@apply border-r border-b border-gray-200 px-4 py-3 text-left dark:border-dark-600;
|
||||
}
|
||||
|
||||
.markdown-body th:last-child,
|
||||
.markdown-body td:last-child {
|
||||
@apply border-r-0;
|
||||
}
|
||||
|
||||
.markdown-body tr:last-child td {
|
||||
@apply border-b-0;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
@apply bg-gradient-to-br from-blue-50 to-indigo-50 font-semibold text-gray-900 dark:from-blue-900/20 dark:to-indigo-900/10 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body tbody tr {
|
||||
@apply transition-colors hover:bg-gray-50 dark:hover:bg-dark-700/30;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
@apply my-5 max-w-full rounded-xl border border-gray-200 shadow-md dark:border-dark-600;
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
@apply font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body em {
|
||||
@apply italic text-gray-600 dark:text-gray-400;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -90,6 +90,7 @@ import { marked } from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { useAnnouncementStore } from '@/stores/announcements'
|
||||
import { formatRelativeWithDateTime } from '@/utils/format'
|
||||
import '@/styles/announcement-markdown.css'
|
||||
|
||||
const { t } = useI18n()
|
||||
const announcementStore = useAnnouncementStore()
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
import AnnouncementPopup from '../AnnouncementPopup.vue'
|
||||
import { useAnnouncementStore } from '@/stores/announcements'
|
||||
|
||||
const announcementMarkdownStyles = readFileSync(
|
||||
resolve(process.cwd(), 'src/styles/announcement-markdown.css'),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
vi.mock('vue-i18n', async () => {
|
||||
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
describe('AnnouncementPopup', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
document.body.style.overflow = ''
|
||||
})
|
||||
|
||||
it('renders mixed Markdown and HTML inside the shared styled container', async () => {
|
||||
const store = useAnnouncementStore()
|
||||
store.currentPopup = {
|
||||
id: 1,
|
||||
title: 'Mixed content announcement',
|
||||
content: [
|
||||
'## Markdown heading',
|
||||
'',
|
||||
'<div><h3>HTML heading</h3><ul><li>HTML list item</li></ul></div>',
|
||||
'',
|
||||
'<table><thead><tr><th>Status</th></tr></thead><tbody><tr><td>OK</td></tr></tbody></table>',
|
||||
'<script>window.__announcementXss = true</script>',
|
||||
].join('\n'),
|
||||
notify_mode: 'popup',
|
||||
created_at: '2026-07-24T07:30:00Z',
|
||||
updated_at: '2026-07-24T07:30:00Z',
|
||||
}
|
||||
|
||||
const wrapper = mount(AnnouncementPopup)
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const content = document.body.querySelector('.markdown-body')
|
||||
expect(content?.querySelector('h2')?.textContent).toBe('Markdown heading')
|
||||
expect(content?.querySelector('h3')?.textContent).toBe('HTML heading')
|
||||
expect(content?.querySelector('li')?.textContent).toBe('HTML list item')
|
||||
expect(content?.querySelector('table td')?.textContent).toBe('OK')
|
||||
expect(content?.querySelector('script')).toBeNull()
|
||||
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it.each(['h2', 'h3', 'ul', 'li', 'blockquote', 'table', 'th', 'td', 'code'])(
|
||||
'loads a shared style rule for mixed-content <%s> elements',
|
||||
(element) => {
|
||||
expect(announcementMarkdownStyles).toContain(`.markdown-body ${element}`)
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,113 @@
|
||||
/* Shared rich-text styles for announcement popup and bell detail content. */
|
||||
.markdown-body {
|
||||
@apply text-[15px] leading-[1.75];
|
||||
@apply text-gray-700 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
@apply mb-6 mt-8 border-b border-gray-200 pb-3 text-3xl font-bold text-gray-900 dark:border-dark-600 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
@apply mb-4 mt-7 border-b border-gray-100 pb-2 text-2xl font-bold text-gray-900 dark:border-dark-700 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
@apply mb-3 mt-6 text-xl font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
@apply mb-2 mt-5 text-lg font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
@apply mb-4 leading-relaxed;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
@apply font-medium text-blue-600 underline decoration-blue-600/30 decoration-2 underline-offset-2 transition-all hover:decoration-blue-600 dark:text-blue-400 dark:decoration-blue-400/30 dark:hover:decoration-blue-400;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
@apply mb-4 ml-6 space-y-2;
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
@apply list-disc;
|
||||
}
|
||||
|
||||
.markdown-body ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
@apply pl-2 leading-relaxed;
|
||||
}
|
||||
|
||||
.markdown-body li::marker {
|
||||
@apply text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
@apply relative my-5 border-l-4 border-blue-500 bg-blue-50/50 py-3 pl-5 pr-4 italic text-gray-700 dark:border-blue-400 dark:bg-blue-900/10 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.markdown-body blockquote::before {
|
||||
content: '"';
|
||||
@apply absolute -left-1 top-0 font-serif text-5xl text-blue-500/20 dark:text-blue-400/20;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
@apply rounded-lg bg-gray-100 px-2 py-1 font-mono text-[13px] text-pink-600 dark:bg-dark-700 dark:text-pink-400;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
@apply my-5 overflow-x-auto rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-dark-600 dark:bg-dark-900/50;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
@apply bg-transparent p-0 text-[13px] text-gray-800 dark:text-gray-200;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
@apply my-8 border-0 border-t-2 border-gray-200 dark:border-dark-700;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
@apply mb-5 w-full overflow-hidden rounded-lg border border-gray-200 dark:border-dark-600;
|
||||
}
|
||||
|
||||
.markdown-body th,
|
||||
.markdown-body td {
|
||||
@apply border-b border-r border-gray-200 px-4 py-3 text-left dark:border-dark-600;
|
||||
}
|
||||
|
||||
.markdown-body th:last-child,
|
||||
.markdown-body td:last-child {
|
||||
@apply border-r-0;
|
||||
}
|
||||
|
||||
.markdown-body tr:last-child td {
|
||||
@apply border-b-0;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
@apply bg-gradient-to-br from-blue-50 to-indigo-50 font-semibold text-gray-900 dark:from-blue-900/20 dark:to-indigo-900/10 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body tbody tr {
|
||||
@apply transition-colors hover:bg-gray-50 dark:hover:bg-dark-700/30;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
@apply my-5 max-w-full rounded-xl border border-gray-200 shadow-md dark:border-dark-600;
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
@apply font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.markdown-body em {
|
||||
@apply italic text-gray-600 dark:text-gray-400;
|
||||
}
|
||||
Reference in New Issue
Block a user