diff --git a/.gitignore b/.gitignore index 8f7c7db..5faac87 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ apps/*/.wxt/ apps/*/chrome-mv*/ apps/*/firefox-mv*/ .claude +.superpowers/* diff --git a/apps/admin-web/src/components/ArticleEditorCanvas.vue b/apps/admin-web/src/components/ArticleEditorCanvas.vue index 57d413e..426dad4 100644 --- a/apps/admin-web/src/components/ArticleEditorCanvas.vue +++ b/apps/admin-web/src/components/ArticleEditorCanvas.vue @@ -52,8 +52,16 @@ import { callCommand, replaceAll } from "@milkdown/kit/utils"; import { Crepe, CrepeFeature } from "@milkdown/crepe"; import "@milkdown/crepe/theme/common/style.css"; import "@milkdown/crepe/theme/frame.css"; -import { Milkdown, useEditor, useInstance } from "@milkdown/vue"; -import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"; +import { + computed, + nextTick, + onBeforeUnmount, + onMounted, + ref, + shallowRef, + watch, + type ComponentPublicInstance, +} from "vue"; import { useI18n } from "vue-i18n"; import EditorActionMenu from "@/components/editor-common/EditorActionMenu.vue"; @@ -88,8 +96,11 @@ const emit = defineEmits<{ }>(); const { t } = useI18n(); -const crepeRef = ref(null); +const crepeRef = shallowRef(null); const syncingExternalMarkdown = ref(false); +const editorRootRef = ref(null); +const editorRef = shallowRef(null); +const loading = ref(true); const surfaceRef = ref(null); const tablePickerRef = ref(null); @@ -197,6 +208,8 @@ type SelectedImageSnapshot = { align: RichImageAlign; }; +type MilkdownEditorInstance = Awaited>; + function createDefaultTableContextMenuState(): TableContextMenuState { return { open: false, @@ -248,7 +261,7 @@ function createDefaultAiOptimizePanelState(): AiOptimizePanelState { }; } -const { loading } = useEditor((root) => { +function createCrepe(root: HTMLElement): Crepe { const crepe = new Crepe({ root, defaultValue: props.modelValue || "", @@ -298,10 +311,13 @@ const { loading } = useEditor((root) => { crepeRef.value = crepe; return crepe; -}); +} -const [instanceLoading, getEditor] = useInstance(); -const editorDisabled = computed(() => props.disabled || instanceLoading.value || loading.value); +function getEditor(): MilkdownEditorInstance | null { + return editorRef.value; +} + +const editorDisabled = computed(() => props.disabled || loading.value); const tablePickerOpen = ref(false); const imagePickerOpen = ref(false); const imagePickerAction = ref("insert"); @@ -528,9 +544,9 @@ watch( ); watch( - [() => props.modelValue, loading, instanceLoading], - ([value, editorLoading, currentInstanceLoading]) => { - if (editorLoading || currentInstanceLoading) { + [() => props.modelValue, loading], + ([value, editorLoading]) => { + if (editorLoading) { return; } @@ -539,6 +555,26 @@ watch( { immediate: true }, ); +async function mountEditor(): Promise { + const root = editorRootRef.value; + if (!root || crepeRef.value) { + return; + } + + loading.value = true; + + const crepe = createCrepe(root); + + try { + editorRef.value = await crepe.create(); + } catch (error) { + crepeRef.value = null; + console.error(error); + } finally { + loading.value = false; + } +} + onBeforeUnmount(() => { stopAiOptimizeGeneration(); if (selectedImageSyncFrame) { @@ -549,6 +585,8 @@ onBeforeUnmount(() => { window.removeEventListener("pointerdown", handleWindowPointerDown); window.removeEventListener("resize", handleWindowResize); window.removeEventListener("keydown", handleWindowKeydown); + editorRef.value = null; + void crepeRef.value?.destroy().catch(console.error); crepeRef.value = null; }); @@ -556,6 +594,7 @@ onMounted(() => { window.addEventListener("pointerdown", handleWindowPointerDown); window.addEventListener("resize", handleWindowResize); window.addEventListener("keydown", handleWindowKeydown); + void mountEditor(); }); function runCommand(command: { key: unknown }, payload?: unknown): void { @@ -680,6 +719,18 @@ function closeTablePicker(): void { tablePickerOpen.value = false; } +function assignTablePickerRef(element: Element | ComponentPublicInstance | null): void { + tablePickerRef.value = element instanceof HTMLElement ? element : null; +} + +function assignSurfaceRef(element: Element | ComponentPublicInstance | null): void { + surfaceRef.value = element instanceof HTMLElement ? element : null; +} + +function assignEditorRootRef(element: Element | ComponentPublicInstance | null): void { + editorRootRef.value = element instanceof HTMLElement ? element : null; +} + function closeTableContextMenu(): void { tableContextMenu.value = createDefaultTableContextMenuState(); } @@ -1830,7 +1881,7 @@ function runTableContextAction(action: TableContextMenuAction): void { -
+
- +
{{ t("images.storageUsage") }}: {{ t("images.storageUsageDetail", { used: formatBytes(storageUsage.used_bytes), total: formatBytes(storageUsage.quota_bytes), - percent: storageUsage.used_pct + percent: formatPercentage(storageUsage.used_pct) }) }}
{ - return (variable.options || []).map(opt => ({ label: opt, value: opt })); + return (variable.options || []).map((opt) => ({ label: opt, value: opt })); }; + +function fieldKey(variable: KolVariableDefinition): string { + return variable.key?.trim() || variable.id; +}