From 5a2ef02ce7071badf3b95713b4cdc40e22e9d1b4 Mon Sep 17 00:00:00 2001 From: WangHe <51102@163.com> Date: Wed, 6 Aug 2025 14:29:10 +0800 Subject: [PATCH 01/17] fix: add missing highlight.js --- frontend/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/package.json b/frontend/package.json index 372208730..0f3e926cb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "core-js": "^3.8.3", "cropperjs": "^1.6.2", "echarts": "^5.6.0", + "highlight.js": "^11.11.1", "ldrs": "^1.1.7", "markdown-it": "^14.1.0", "vditor": "^3.11.1", From 393e60c6e9200e132624e1315a53fcc96b01c2fd Mon Sep 17 00:00:00 2001 From: WangHe <51102@163.com> Date: Wed, 6 Aug 2025 15:20:33 +0800 Subject: [PATCH 02/17] fix: npm ci "highlight.js" build fail --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 0f3e926cb..b67b5739d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,7 @@ "core-js": "^3.8.3", "cropperjs": "^1.6.2", "echarts": "^5.6.0", - "highlight.js": "^11.11.1", + "highlight.js": "^10.7.1", "ldrs": "^1.1.7", "markdown-it": "^14.1.0", "vditor": "^3.11.1", From 597bc09c57a1d126c526420b761cae5fc5a47a41 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:15:22 +0800 Subject: [PATCH 03/17] fix: offset vditor toolbar when pinned --- frontend/src/assets/global.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/assets/global.css b/frontend/src/assets/global.css index 1eaff1765..88432945e 100644 --- a/frontend/src/assets/global.css +++ b/frontend/src/assets/global.css @@ -65,7 +65,11 @@ body { *************************/ .vditor { min-height: 200px; -} +} +.vditor-toolbar--pin { + top: var(--header-height) !important; + z-index: 900 !important; +} /* .vditor { --textarea-background-color: transparent; border: none !important; From 2241cfc9da5958c447dd29c004b25a097b83d43c Mon Sep 17 00:00:00 2001 From: WangHe <51102@163.com> Date: Wed, 6 Aug 2025 17:10:36 +0800 Subject: [PATCH 04/17] feat: vditor add loading --- frontend/src/components/PostEditor.vue | 10 ++++++++-- frontend/src/views/NewPostPageView.vue | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/PostEditor.vue b/frontend/src/components/PostEditor.vue index 645f5ff4e..85b1ca9c5 100644 --- a/frontend/src/components/PostEditor.vue +++ b/frontend/src/components/PostEditor.vue @@ -19,10 +19,9 @@ import { clearVditorStorage } from '../utils/clearVditorStorage' import { hatch } from 'ldrs' hatch.register() - export default { name: 'PostEditor', - emits: ['update:modelValue'], + emits: ['update:modelValue', 'update:loading'], props: { modelValue: { type: String, @@ -43,6 +42,8 @@ export default { }, setup(props, { emit }) { const vditorInstance = ref(null) + let vditorRender = false + const getEditorTheme = getEditorThemeUtil const getPreviewTheme = getPreviewThemeUtil const applyTheme = () => { @@ -54,6 +55,7 @@ export default { watch( () => props.loading, val => { + if (!vditorRender) return if (val) { vditorInstance.value.disabled() } else { @@ -91,12 +93,15 @@ export default { ) onMounted(() => { + emit('update:loading', true) vditorInstance.value = createVditor(props.editorId, { placeholder: '请输入正文...', input(value) { emit('update:modelValue', value) }, after() { + vditorRender = true + emit('update:loading', false) vditorInstance.value.setValue(props.modelValue) if (props.loading || props.disabled) { vditorInstance.value.disabled() @@ -120,6 +125,7 @@ export default { .post-editor-container { border: 1px solid var(--normal-border-color); position: relative; + min-height: 200px; } .editor-loading-overlay { diff --git a/frontend/src/views/NewPostPageView.vue b/frontend/src/views/NewPostPageView.vue index 1038df159..a784ed202 100644 --- a/frontend/src/views/NewPostPageView.vue +++ b/frontend/src/views/NewPostPageView.vue @@ -3,7 +3,7 @@
- +
From f5a3206f36fe947f7a8627c76ef806de97cb6894 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 6 Aug 2025 17:23:19 +0800 Subject: [PATCH 05/17] =?UTF-8?q?feat:=20sticky=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/global.css | 3 ++- frontend/src/views/AboutPageView.vue | 4 +--- frontend/src/views/HomePageView.vue | 2 +- frontend/src/views/MessagePageView.vue | 2 +- frontend/src/views/NewPostPageView.vue | 2 -- frontend/src/views/ProfileView.vue | 5 +---- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/frontend/src/assets/global.css b/frontend/src/assets/global.css index 88432945e..77be29063 100644 --- a/frontend/src/assets/global.css +++ b/frontend/src/assets/global.css @@ -66,10 +66,11 @@ body { .vditor { min-height: 200px; } + .vditor-toolbar--pin { top: var(--header-height) !important; - z-index: 900 !important; } + /* .vditor { --textarea-background-color: transparent; border: none !important; diff --git a/frontend/src/views/AboutPageView.vue b/frontend/src/views/AboutPageView.vue index f36ba3489..669218787 100644 --- a/frontend/src/views/AboutPageView.vue +++ b/frontend/src/views/AboutPageView.vue @@ -75,13 +75,11 @@ export default { max-width: var(--page-max-width); background-color: var(--background-color); margin: 0 auto; - height: 100%; - overflow-y: auto; } .about-tabs { position: sticky; - top: 1px; + top: calc(var(--header-height) + 1px); z-index: 200; background-color: var(--background-color-blur); display: flex; diff --git a/frontend/src/views/HomePageView.vue b/frontend/src/views/HomePageView.vue index bbae20c0b..bd5a3ab13 100644 --- a/frontend/src/views/HomePageView.vue +++ b/frontend/src/views/HomePageView.vue @@ -433,7 +433,7 @@ export default { .topic-container { position: sticky; - top: 1px; + top: calc(var(--header-height) + 1px); z-index: 10; background-color: var(--background-color-blur); display: flex; diff --git a/frontend/src/views/MessagePageView.vue b/frontend/src/views/MessagePageView.vue index 03b79d59f..3eda7cf51 100644 --- a/frontend/src/views/MessagePageView.vue +++ b/frontend/src/views/MessagePageView.vue @@ -632,7 +632,7 @@ export default { .message-page-header { position: sticky; - top: 1px; + top: calc(var(--header-height) + 1px); z-index: 200; background-color: var(--background-color-blur); display: flex; diff --git a/frontend/src/views/NewPostPageView.vue b/frontend/src/views/NewPostPageView.vue index 1038df159..190c85409 100644 --- a/frontend/src/views/NewPostPageView.vue +++ b/frontend/src/views/NewPostPageView.vue @@ -261,10 +261,8 @@ export default { display: flex; justify-content: center; background-color: var(--background-color); - height: 100%; padding-right: 20px; padding-left: 20px; - overflow-y: auto; } .new-post-form { diff --git a/frontend/src/views/ProfileView.vue b/frontend/src/views/ProfileView.vue index 4470100dd..672e8d463 100644 --- a/frontend/src/views/ProfileView.vue +++ b/frontend/src/views/ProfileView.vue @@ -497,9 +497,6 @@ export default { .profile-page { background-color: var(--background-color); - height: 100%; - overflow-y: auto; - overflow-x: hidden; } .profile-page-header { @@ -639,7 +636,7 @@ export default { .profile-tabs { position: sticky; - top: 1px; + top: calc(var(--header-height) + 1px); z-index: 200; background-color: var(--background-color-blur); display: flex; From 597f682b755a2a55830fd5779069b779e9c343cb Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 6 Aug 2025 18:37:51 +0800 Subject: [PATCH 06/17] fix: message page layout fix --- frontend/src/views/MessagePageView.vue | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/views/MessagePageView.vue b/frontend/src/views/MessagePageView.vue index 3eda7cf51..ca15bfb14 100644 --- a/frontend/src/views/MessagePageView.vue +++ b/frontend/src/views/MessagePageView.vue @@ -628,11 +628,12 @@ export default { .message-page { background-color: var(--background-color); + overflow-x: hidden; } .message-page-header { position: sticky; - top: calc(var(--header-height) + 1px); + top: 1px; z-index: 200; background-color: var(--background-color-blur); display: flex; @@ -769,9 +770,5 @@ export default { .has_read_button { display: none; } - - .message-page { - overflow-x: hidden; - } } From c838caf9e13bab9e80f3d1cb40ce8ea171d23332 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:52:02 +0800 Subject: [PATCH 07/17] feat: update notification read UI instantly --- frontend/src/views/MessagePageView.vue | 40 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/frontend/src/views/MessagePageView.vue b/frontend/src/views/MessagePageView.vue index ca15bfb14..c04db1f43 100644 --- a/frontend/src/views/MessagePageView.vue +++ b/frontend/src/views/MessagePageView.vue @@ -299,7 +299,7 @@ import BaseTimeline from '../components/BaseTimeline.vue' import BasePlaceholder from '../components/BasePlaceholder.vue' import NotificationContainer from '../components/NotificationContainer.vue' import { getToken, authState } from '../utils/auth' -import { markNotificationsRead, fetchUnreadCount } from '../utils/notification' +import { markNotificationsRead, fetchUnreadCount, notificationState } from '../utils/notification' import { toast } from '../main' import { stripMarkdownLength } from '../utils/markdown' import TimeManager from '../utils/time' @@ -322,28 +322,42 @@ export default { const markRead = async id => { if (!id) return + const n = notifications.value.find(n => n.id === id) + if (!n || n.read) return + n.read = true + if (notificationState.unreadCount > 0) notificationState.unreadCount-- const ok = await markNotificationsRead([id]) - if (ok) { - const n = notifications.value.find(n => n.id === id) - if (n) n.read = true - await fetchUnreadCount() + if (!ok) { + n.read = false + notificationState.unreadCount++ + } else { + fetchUnreadCount() } } const markAllRead = async () => { // 除了 REGISTER_REQUEST 类型消息 - const idsToMark = notifications.value.filter(n => n.type !== 'REGISTER_REQUEST').map(n => n.id) + const idsToMark = notifications.value + .filter(n => n.type !== 'REGISTER_REQUEST' && !n.read) + .map(n => n.id) + if (idsToMark.length === 0) return + notifications.value.forEach(n => { + if (n.type !== 'REGISTER_REQUEST') n.read = true + }) + notificationState.unreadCount = notifications.value.filter(n => !n.read).length const ok = await markNotificationsRead(idsToMark) - if (ok) { + if (!ok) { notifications.value.forEach(n => { - if (n.type !== 'REGISTER_REQUEST') n.read = true + if (idsToMark.includes(n.id)) n.read = false }) await fetchUnreadCount() - if (authState.role === 'ADMIN') { - toast.success('已读所有消息(注册请求除外)') - } else { - toast.success('已读所有消息') - } + return + } + fetchUnreadCount() + if (authState.role === 'ADMIN') { + toast.success('已读所有消息(注册请求除外)') + } else { + toast.success('已读所有消息') } } From 5709b0d6fdaca0efde24a4b9939dd9951a494c6e Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:58:01 +0800 Subject: [PATCH 08/17] refactor: reuse shared reaction emoji map --- frontend/src/components/ReactionsGroup.vue | 33 +++------------------- frontend/src/utils/reactions.js | 25 ++++++++++++++++ frontend/src/views/MessagePageView.vue | 27 +----------------- 3 files changed, 30 insertions(+), 55 deletions(-) create mode 100644 frontend/src/utils/reactions.js diff --git a/frontend/src/components/ReactionsGroup.vue b/frontend/src/components/ReactionsGroup.vue index be996796d..2398c22be 100644 --- a/frontend/src/components/ReactionsGroup.vue +++ b/frontend/src/components/ReactionsGroup.vue @@ -4,7 +4,7 @@
@@ -24,7 +24,7 @@
- {{ iconMap[t] }}{{ counts[t] }} + {{ reactionEmojiMap[t] }}{{ counts[t] }}
@@ -34,6 +34,7 @@ import { ref, computed, watch, onMounted } from 'vue' import { API_BASE_URL, toast } from '../main' import { getToken, authState } from '../utils/auth' +import { reactionEmojiMap } from '../utils/reactions' let cachedTypes = null const fetchTypes = async () => { @@ -54,32 +55,6 @@ const fetchTypes = async () => { return cachedTypes } -const iconMap = { - LIKE: '❤️', - DISLIKE: '👎', - RECOMMEND: '👏', - ANGRY: '😡', - FLUSHED: '😳', - STAR_STRUCK: '🤩', - ROFL: '🤣', - HOLDING_BACK_TEARS: '🥹', - MIND_BLOWN: '🤯', - POOP: '💩', - CLOWN: '🤡', - SKULL: '☠️', - FIRE: '🔥', - EYES: '👀', - FROWN: '☹️', - HOT: '🥵', - EAGLE: '🦅', - SPIDER: '🕷️', - BAT: '🦇', - CHINA: '🇨🇳', - USA: '🇺🇸', - JAPAN: '🇯🇵', - KOREA: '🇰🇷' -} - export default { name: 'ReactionsGroup', props: { @@ -202,7 +177,7 @@ export default { } return { - iconMap, + reactionEmojiMap, counts, totalCount, likeCount, diff --git a/frontend/src/utils/reactions.js b/frontend/src/utils/reactions.js new file mode 100644 index 000000000..7fa967de7 --- /dev/null +++ b/frontend/src/utils/reactions.js @@ -0,0 +1,25 @@ +export const reactionEmojiMap = { + LIKE: '❤️', + DISLIKE: '👎', + RECOMMEND: '👏', + ANGRY: '😡', + FLUSHED: '😳', + STAR_STRUCK: '🤩', + ROFL: '🤣', + HOLDING_BACK_TEARS: '🥹', + MIND_BLOWN: '🤯', + POOP: '💩', + CLOWN: '🤡', + SKULL: '☠️', + FIRE: '🔥', + EYES: '👀', + FROWN: '☹️', + HOT: '🥵', + EAGLE: '🦅', + SPIDER: '🕷️', + BAT: '🦇', + CHINA: '🇨🇳', + USA: '🇺🇸', + JAPAN: '🇯🇵', + KOREA: '🇰🇷' +} diff --git a/frontend/src/views/MessagePageView.vue b/frontend/src/views/MessagePageView.vue index ca15bfb14..4d4672a00 100644 --- a/frontend/src/views/MessagePageView.vue +++ b/frontend/src/views/MessagePageView.vue @@ -304,6 +304,7 @@ import { toast } from '../main' import { stripMarkdownLength } from '../utils/markdown' import TimeManager from '../utils/time' import { hatch } from 'ldrs' +import { reactionEmojiMap } from '../utils/reactions' hatch.register() export default { @@ -364,32 +365,6 @@ export default { MENTION: 'fas fa-at' } - const reactionEmojiMap = { - LIKE: '❤️', - DISLIKE: '👎', - RECOMMEND: '👏', - ANGRY: '😡', - FLUSHED: '😳', - STAR_STRUCK: '🤩', - ROFL: '🤣', - HOLDING_BACK_TEARS: '🥹', - MIND_BLOWN: '🤯', - POOP: '💩', - CLOWN: '🤡', - SKULL: '☠️', - FIRE: '🔥', - EYES: '👀', - FROWN: '☹️', - HOT: '🥵', - EAGLE: '🦅', - SPIDER: '🕷️', - BAT: '🦇', - CHINA: '🇨🇳', - USA: '🇺🇸', - JAPAN: '🇯🇵', - KOREA: '🇰🇷' - } - const fetchNotifications = async () => { try { const token = getToken() From 6a1b71de0fdd0b52b84457425e979f831086dbaf Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:59:08 +0800 Subject: [PATCH 09/17] feat: add tieba emoji support --- frontend/src/utils/markdown.js | 24 ++++++++++++++++++++++++ frontend/src/utils/tiebaEmoji.js | 10 ++++++++++ frontend/src/utils/vditor.js | 9 ++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 frontend/src/utils/tiebaEmoji.js diff --git a/frontend/src/utils/markdown.js b/frontend/src/utils/markdown.js index a3ed32ec7..579a6749c 100644 --- a/frontend/src/utils/markdown.js +++ b/frontend/src/utils/markdown.js @@ -2,6 +2,7 @@ import MarkdownIt from 'markdown-it' import hljs from 'highlight.js' import 'highlight.js/styles/github.css' import { toast } from '../main' +import { tiebaEmoji, TIEBA_EMOJI_CDN } from './tiebaEmoji' function mentionPlugin(md) { const mentionReg = /^@\[([^\]]+)\]/ @@ -27,6 +28,28 @@ function mentionPlugin(md) { md.inline.ruler.before('emphasis', 'mention', mention) } +function tiebaEmojiPlugin(md) { + md.renderer.rules['tieba-emoji'] = (tokens, idx) => { + const name = tokens[idx].content + const file = tiebaEmoji[name] + return `${name}` + } + md.inline.ruler.before('emphasis', 'tieba-emoji', (state, silent) => { + const pos = state.pos + if (state.src.charCodeAt(pos) !== 0x3a) return false + const match = state.src.slice(pos).match(/^:tieba(\d+):/) + if (!match) return false + const key = `tieba${match[1]}` + if (!tiebaEmoji[key]) return false + if (!silent) { + const token = state.push('tieba-emoji', '', 0) + token.content = key + } + state.pos += match[0].length + return true + }) +} + const md = new MarkdownIt({ html: false, linkify: true, @@ -43,6 +66,7 @@ const md = new MarkdownIt({ }) md.use(mentionPlugin) +md.use(tiebaEmojiPlugin) export function renderMarkdown(text) { return md.render(text || '') diff --git a/frontend/src/utils/tiebaEmoji.js b/frontend/src/utils/tiebaEmoji.js new file mode 100644 index 000000000..da413d0e9 --- /dev/null +++ b/frontend/src/utils/tiebaEmoji.js @@ -0,0 +1,10 @@ +export const TIEBA_EMOJI_CDN = 'https://cdn.jsdelivr.net/gh/microlong666/tieba_mobile_emotions@master/' + +export const tiebaEmoji = (() => { + const map = { tieba1: 'image_emoticon.png' } + for (let i = 2; i <= 124; i++) { + if (i > 50 && i < 62) continue + map[`tieba${i}`] = `image_emoticon${i}.png` + } + return map +})() diff --git a/frontend/src/utils/vditor.js b/frontend/src/utils/vditor.js index 2e9a2628c..a7fabe346 100644 --- a/frontend/src/utils/vditor.js +++ b/frontend/src/utils/vditor.js @@ -3,6 +3,7 @@ import 'vditor/dist/index.css' import { API_BASE_URL } from '../main' import { getToken, authState } from './auth' import { searchUsers, fetchFollowings, fetchAdmins } from './user' +import { tiebaEmoji, TIEBA_EMOJI_CDN } from './tiebaEmoji' export function getEditorTheme() { return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'classic' @@ -42,8 +43,14 @@ export function createVditor(editorId, options = {}) { placeholder, height: 'auto', theme: getEditorTheme(), - preview: Object.assign({ theme: { current: getPreviewTheme() } }, preview), + preview: Object.assign({ + theme: { current: getPreviewTheme() }, + customEmoji: tiebaEmoji, + emojiPath: TIEBA_EMOJI_CDN + }, preview), hint: { + emoji: tiebaEmoji, + emojiPath: TIEBA_EMOJI_CDN, extend: [ { key: '@', From 2235612070ef3c00a9c3c920a2dc3c02268d1f22 Mon Sep 17 00:00:00 2001 From: WangHe <51102@163.com> Date: Wed, 6 Aug 2025 19:14:19 +0800 Subject: [PATCH 10/17] fix: Click outside the drop-down box to not hide --- frontend/src/components/Dropdown.vue | 162 ++++++++++++++++-------- frontend/src/directives/clickOutside.js | 159 +++++++++++++++++++++++ frontend/src/main.js | 10 +- 3 files changed, 274 insertions(+), 57 deletions(-) create mode 100644 frontend/src/directives/clickOutside.js diff --git a/frontend/src/components/Dropdown.vue b/frontend/src/components/Dropdown.vue index 27f5a641c..062521262 100644 --- a/frontend/src/components/Dropdown.vue +++ b/frontend/src/components/Dropdown.vue @@ -1,13 +1,24 @@