diff --git a/src/App.vue b/src/App.vue index 2136a52..06e644b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -74,63 +74,93 @@ function onChange(markdownValue) { .theme-toggle__track { position: relative; - width: 78px; - height: 40px; + width: 72px; + height: 36px; display: block; border-radius: 999px; - border: 1px solid var(--panel-border); + border: 2px solid var(--panel-border); background: linear-gradient(135deg, var(--toggle-bg-start), var(--toggle-bg-end)); - box-shadow: var(--panel-shadow); + box-shadow: var(--panel-shadow), inset 0 2px 4px rgba(255, 255, 255, 0.1), inset 0 -2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: background 400ms ease, border-color 400ms ease, box-shadow 400ms ease; +} + +.theme-toggle__track::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgba(255,255,255,0.15) 0%, transparent 50%, rgba(0,0,0,0.1) 100%); + border-radius: 999px; + pointer-events: none; + transition: opacity 400ms ease; } .theme-toggle__thumb { position: absolute; - top: 3px; - left: 3px; - width: 32px; - height: 32px; + top: 2px; + left: 2px; + width: 28px; + height: 28px; border-radius: 50%; - background: var(--toggle-thumb-bg); - box-shadow: 0 5px 14px rgba(0, 0, 0, 0.2); - transition: transform 220ms ease; + background: linear-gradient(135deg, var(--toggle-thumb-bg) 0%, var(--toggle-thumb-bg) 100%); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.4); + transition: transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1); + z-index: 2; } .theme-toggle__sun, .theme-toggle__moon { position: absolute; top: 50%; - width: 16px; - height: 16px; + width: 18px; + height: 18px; transform: translateY(-50%); - transition: opacity 200ms ease, transform 220ms ease; + transition: opacity 300ms ease, transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1); } .theme-toggle__sun { - left: 12px; + left: 10px; color: var(--toggle-sun); opacity: 1; - transform: translateY(-50%) rotate(0deg); + transform: translateY(-50%) scale(1); +} + +.theme-toggle__sun svg { + width: 18px; + height: 18px; + filter: drop-shadow(0 0 3px var(--toggle-sun)); } .theme-toggle__moon { - right: 12px; + right: 10px; color: var(--toggle-moon); - opacity: 0.55; - transform: translateY(-50%) rotate(-16deg); + opacity: 0.5; + transform: translateY(-50%) scale(0.85); +} + +.theme-toggle__moon svg { + width: 18px; + height: 18px; } .theme-toggle.is-dark .theme-toggle__thumb { - transform: translateX(38px); + transform: translateX(36px); + background: linear-gradient(135deg, var(--toggle-thumb-bg) 0%, #c8d4ec 100%); } .theme-toggle.is-dark .theme-toggle__sun { - opacity: 0.55; - transform: translateY(-50%) rotate(16deg); + opacity: 0.5; + transform: translateY(-50%) scale(0.85); } .theme-toggle.is-dark .theme-toggle__moon { opacity: 1; - transform: translateY(-50%) rotate(0deg); + transform: translateY(-50%) scale(1); + color: #c8d4ec; +} + +.theme-toggle.is-dark .theme-toggle__moon svg { + filter: drop-shadow(0 0 4px rgba(200, 212, 236, 0.6)); } .theme-toggle:focus-visible { diff --git a/src/components/MilkdownEditor.vue b/src/components/MilkdownEditor.vue index 0e2b71a..655c728 100644 --- a/src/components/MilkdownEditor.vue +++ b/src/components/MilkdownEditor.vue @@ -38,8 +38,8 @@
- - + +
@@ -65,9 +65,14 @@ :aria-label="aiButtonLabel" :title="aiButtonLabel" > - - - + + + + + + + + {{ aiButtonLabel }} @@ -79,16 +84,16 @@
-

Insert Image from URL

+

通过 URL 插入图片

- - + +
diff --git a/src/plugins/copilotPlugin.ts b/src/plugins/copilotPlugin.ts index 9f3687d..8b14d75 100644 --- a/src/plugins/copilotPlugin.ts +++ b/src/plugins/copilotPlugin.ts @@ -3,7 +3,6 @@ import { $prose, $ctx, $markSchema } from '@milkdown/kit/utils' import { parserCtx, serializerCtx } from '@milkdown/kit/core' import { Node as ProseNode, DOMParser, DOMSerializer } from '@milkdown/prose/model' import type { Ctx } from '@milkdown/kit/core' -import { Decoration, DecorationSet } from '@milkdown/prose/view' import type { EditorView } from '@milkdown/prose/view' import { getOcrCache, OCR_SIZE_LIMIT, extractTextFromOCR } from '../utils/ocrCache' @@ -119,26 +118,6 @@ function clearGhostText(view: EditorView): boolean { return true } -function buildGhostBlockDecorations(state: any): DecorationSet | null { - const pluginState = COPILOT_PLUGIN_KEY.getState(state) as CopilotState | undefined - if (!pluginState || !pluginState.suggestion || pluginState.from >= pluginState.to) { - return null - } - - const from = Math.max(0, Math.min(pluginState.from, state.doc.content.size)) - const to = Math.max(from, Math.min(pluginState.to, state.doc.content.size)) - const decorations: Decoration[] = [] - - state.doc.nodesBetween(from, to, (node: any, pos: number) => { - if (!node.isBlock || node.nodeSize <= 0) return true - decorations.push(Decoration.node(pos, pos + node.nodeSize, { class: 'copilot-ghost-block' })) - return true - }) - - if (decorations.length === 0) return null - return DecorationSet.create(state.doc, decorations) -} - function getCursorBeforeGhostInsert(tr: any, from: number): number { const mapped = tr.mapping.map(from, -1) return Math.max(0, Math.min(mapped, tr.doc.content.size)) @@ -167,9 +146,13 @@ function addGhostMarksToTextNodes(tr: any, from: number, to: number, markType: a tr.doc.nodesBetween(from, to, (node: any, pos: number) => { if (!node.isText || node.nodeSize <= 0) return true - const $pos = tr.doc.resolve(pos) + const start = Math.max(pos, from) + const end = Math.min(pos + node.nodeSize, to) + if (end <= start) return true + + const $pos = tr.doc.resolve(start) if ($pos.parent.type.allowsMarkType?.(markType)) { - tr.addMark(pos, pos + node.nodeSize, markType.create()) + tr.addMark(start, end, markType.create()) } return true }) @@ -454,7 +437,6 @@ export const copilotPlugin = $prose((ctx) => new Plugin({ } }, props: { - decorations: (state) => buildGhostBlockDecorations(state), handleKeyDown: (view, event) => { const hasGhost = hasGhostText(view)