From 7495f3c5c63979e6510656bc1bbad03ce540e17b Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Mon, 27 Apr 2026 19:21:15 +0800 Subject: [PATCH] feat(editor): add editor readiness handling and visibility checks - Introduced a new optional callback for editor readiness in the markdown editor configuration, allowing for better integration with the component lifecycle. - Implemented a fallback timer to manage the editor's readiness state, ensuring it reflects visibility accurately. - Enhanced the component to check for the editor's visibility before marking it as ready, improving user experience during initialization. These changes enhance the responsiveness and reliability of the markdown editor, providing clearer feedback on its loading state. --- app/components/PostBodyMarkdownEditor.vue | 47 ++++++++++++++++++++- .../post-body-markdown-editor-vditor-config.ts | 4 ++ packages/drizzle-pkg/db.sqlite | Bin 188416 -> 188416 bytes 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/components/PostBodyMarkdownEditor.vue b/app/components/PostBodyMarkdownEditor.vue index 500b0c5..aa74cf0 100644 --- a/app/components/PostBodyMarkdownEditor.vue +++ b/app/components/PostBodyMarkdownEditor.vue @@ -17,6 +17,43 @@ const toast = useToast() const mountEl = ref(null) const isMobileViewport = ref(false) const isEditorReady = ref(false) +let editorReadyFallbackTimer: ReturnType | null = null +let readinessCheckToken = 0 + +function clearEditorReadyFallbackTimer() { + if (editorReadyFallbackTimer) { + clearTimeout(editorReadyFallbackTimer) + editorReadyFallbackTimer = null + } +} + +function startEditorReadyFallbackTimer() { + clearEditorReadyFallbackTimer() + editorReadyFallbackTimer = setTimeout(() => { + isEditorReady.value = true + editorReadyFallbackTimer = null + }, 3000) +} + +async function markEditorReadyWhenVisible() { + const currentToken = ++readinessCheckToken + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))) + const deadline = Date.now() + 2000 + while (Date.now() < deadline) { + if (currentToken !== readinessCheckToken) { + return + } + const host = mountEl.value + const vditorRoot = host?.querySelector('.vditor') as HTMLElement | null + const hasVisibleEditor = Boolean(vditorRoot && vditorRoot.offsetHeight > 0 && vditorRoot.offsetWidth > 0) + if (hasVisibleEditor) { + isEditorReady.value = true + clearEditorReadyFallbackTimer() + return + } + await new Promise(resolve => setTimeout(resolve, 50)) + } +} const editorContainerStyle = computed(() => { if (isMobileViewport.value) { return { @@ -44,6 +81,9 @@ const bridge = createPostBodyMarkdownEditorBridge({ value, isMobile: isMobileViewport.value, onInput, + onEditorReady: () => { + void markEditorReadyWhenVisible() + }, onUploadError: () => { toast.add({ title: '图片上传失败', color: 'warning' }) }, @@ -74,6 +114,8 @@ onMounted(async () => { if (!mountEl.value) { return } + isEditorReady.value = false + startEditorReadyFallbackTimer() bridge.unmount() bridge.mount(mountEl.value) }, @@ -89,8 +131,9 @@ onMounted(async () => { onReady: (ctor) => { vditorCtor.value = ctor if (mountEl.value) { + isEditorReady.value = false + startEditorReadyFallbackTimer() bridge.mount(mountEl.value) - isEditorReady.value = true } }, onError: (error) => { @@ -105,7 +148,9 @@ watch(() => props.modelValue, () => { onBeforeUnmount(() => { unmounted = true + readinessCheckToken += 1 isEditorReady.value = false + clearEditorReadyFallbackTimer() if (viewportMql && onViewportChange) { viewportMql.removeEventListener('change', onViewportChange) } diff --git a/app/components/post-body-markdown-editor-vditor-config.ts b/app/components/post-body-markdown-editor-vditor-config.ts index e500575..a7eecff 100644 --- a/app/components/post-body-markdown-editor-vditor-config.ts +++ b/app/components/post-body-markdown-editor-vditor-config.ts @@ -3,6 +3,7 @@ interface BuildVditorOptionsInput { isMobile: boolean onInput: (value: string) => void onUploadError: () => void + onEditorReady?: () => void } const DESKTOP_TOOLBAR: ReadonlyArray = [ @@ -114,6 +115,9 @@ export function buildPostBodyMarkdownEditorVditorOptions(input: BuildVditorOptio input(value: string) { input.onInput(value) }, + after() { + input.onEditorReady?.() + }, } } diff --git a/packages/drizzle-pkg/db.sqlite b/packages/drizzle-pkg/db.sqlite index b1eb4fac3b6e5affc887eccf20f31938229db35c..180e03b7142dcd5c2412b2f8097ad99e556dcf06 100644 GIT binary patch delta 737 zcmZoTz};|wdxA8h^+XwGM(d3U%l@;}F!0xGR#ZsipI*Sv$iv;p$jHUOndlzM$}l}K zl~H~AB7Q~>mUD}*7j6H=&&cJ#!^U@>fuD)rls|*-{C2(o#{GQLnc^7@IE;+gj956+ z8K(>SFzIeLiD$gpd7brVHdUvbbgBm!%fzreqeSCYNO9=jlIP*7RiW)F*rQKcBL~IXL)f$C78$ z*1p`c;Ms!qC%gNePG0q7$Es)3c0Qdj@x}hxPg|EhZEb$OWx@0YXGW{(3tSmFIgQNY z4b6>>OpT^_GRn%DC@Cc7l-Mc(je-~g(FHMK`d&Xq+j@{K8(W{ZYkp85F3HE zKkL~3d{zrY*RvTwslF#Wx?av*1T^8<+@(ON_j`I??4JRX>PC}#+PdxOx_Qt0HazW{ z|9nE*%enKO?%45k`|_vDW&$-n*}ebC-q}z4XS_68JLTD&^-p#*yy#d1lz29$^J(jr z7uyyCEr2)(V&9WpZ7-HD1}b~9qkFo8AEWb-^QZ^u9(~-<#5nhyxA=;JqRg_yl2jmP O@%6^-f4!NQrvLyG6Op9= delta 117 zcmV-*0E+*BzzcxD3y>QDIguPg0XeZ?rvC(R01t4pAs}WCmv9dO43}bM0VbEO4*^IJ z%>+@}W@U6^Xdq@`X>4U=xB3qO3P1`44*~!W0uMD0Xtxkh0lyBDFc3AD&t3sAw=!P= X(%=RG3;h5K{j(tu^9#5AO9BIws2L^w