feat: add theme management with light and dark modes

- Implemented a new composable `useTheme` for managing theme state.
- Added functions to read and write theme preference to local storage.
- Applied theme styles to the DOM based on user preference.
- Introduced a toggle function to switch between light and dark themes.

refactor: enhance copilot plugin functionality

- Improved request handling with sequence and document versioning.
- Refactored ghost text handling to improve clarity and efficiency.
- Updated markdown insertion logic to handle parsed content more robustly.
- Enhanced error handling and logging for better debugging.

style: update global styles for light and dark themes

- Defined CSS variables for light and dark themes to streamline styling.
- Improved overall styling consistency and responsiveness.
- Added transitions for smoother theme changes and interactions.
This commit is contained in:
2026-02-15 15:44:09 +08:00
parent 03bb21d5c6
commit 838eec30a8
205 changed files with 1868 additions and 344 deletions

View File

@@ -100,7 +100,8 @@ import { onMounted, onUnmounted, ref, computed } from 'vue'
import { replaceAll } from '@milkdown/kit/utils'
import { Crepe } from '@milkdown/crepe'
import { editorViewCtx, serializerCtx } from '@milkdown/kit/core'
import { copilotPlugin, copilotConfigCtx, copilotGhostMark, setCopilotEnabled, COPILOT_PLUGIN_KEY, SIZE_LIMIT, checkSizeLimit } from '../plugins/copilotPlugin'
import { Selection } from '@milkdown/prose/state'
import { copilotPlugin, copilotConfigCtx, copilotGhostMark, setCopilotEnabled, COPILOT_PLUGIN_KEY, SIZE_LIMIT, checkSizeLimit, clearGhostSuggestion } from '../plugins/copilotPlugin'
import { fetchSuggestion } from '../utils/api.js'
import { DEBUG, OCR_URL } from '../utils/config.js'
import { setOcrCache, clearOcrCache, clearAllOcrCache } from '../utils/ocrCache.js'
@@ -252,13 +253,7 @@ const logDebugInfo = async () => {
}
const clearCurrentSuggestion = (view) => {
const state = COPILOT_PLUGIN_KEY.getState(view.state)
if (state?.suggestion && state.from < state.to) {
const tr = view.state.tr
.delete(state.from, state.to)
.setMeta(COPILOT_PLUGIN_KEY, { from: 0, to: 0, suggestion: '' })
view.dispatch(tr)
}
clearGhostSuggestion(view)
}
const performOCR = async (file, cacheKey) => {
@@ -289,11 +284,6 @@ const performOCR = async (file, cacheKey) => {
if (data.text) {
setOcrCache(cacheKey, data.text)
setOcrCache(file.name, data.text)
if (crepe?.editor) {
crepe.editor.action((ctx) => {
refreshSizeAndLimit(ctx)
})
}
}
} catch (e) {
console.error('[OCR] Error:', e)
@@ -438,13 +428,16 @@ const insertImageAtCursor = (src) => {
const view = ctx.get(editorViewCtx)
const { state } = view
const { schema } = state
const { from, to } = state.selection
const imageType = schema.nodes.image
if (!imageType) return
const imageNode = imageType.create({ src })
const tr = state.tr.replaceSelectionWith(imageNode)
view.dispatch(tr)
const tr = state.tr.replaceRangeWith(from, to, imageNode)
const cursorPos = Math.min(from + imageNode.nodeSize, tr.doc.content.size)
tr.setSelection(Selection.near(tr.doc.resolve(cursorPos), 1))
view.dispatch(tr.scrollIntoView())
})
}
@@ -513,61 +506,61 @@ onUnmounted(() => {
width: 44px;
height: 44px;
padding: 10px;
background-color: #fff;
color: #666;
border: 1px solid #ddd;
background-color: var(--btn-bg);
color: var(--btn-fg);
border: 1px solid var(--panel-border);
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
box-shadow: var(--panel-shadow);
opacity: 0.5;
}
.action-btn:hover {
background-color: #4a90d9;
color: white;
border-color: #4a90d9;
background-color: var(--btn-hover-bg);
color: var(--btn-hover-fg);
border-color: var(--btn-hover-bg);
opacity: 1;
}
.action-btn.ai-disabled {
background-color: #333;
color: #fff;
border-color: #333;
background-color: var(--crepe-color-surface-low);
color: var(--crepe-color-on-background);
border-color: var(--panel-border);
}
.action-btn.ai-disabled:hover {
background-color: #4a90d9;
color: white;
border-color: #4a90d9;
background-color: var(--btn-hover-bg);
color: var(--btn-hover-fg);
border-color: var(--btn-hover-bg);
}
.action-btn.force-disabled {
background-color: #ccc;
color: #999;
border-color: #ccc;
background-color: var(--btn-disabled-bg);
color: var(--btn-disabled-fg);
border-color: var(--btn-disabled-bg);
cursor: not-allowed;
opacity: 0.6;
}
.action-btn.force-disabled:hover {
background-color: #ccc;
color: #999;
border-color: #ccc;
background-color: var(--btn-disabled-bg);
color: var(--btn-disabled-fg);
border-color: var(--btn-disabled-bg);
opacity: 0.6;
}
.size-indicator {
font-size: 10px;
color: #999;
color: var(--muted-text);
text-align: center;
margin-top: 4px;
}
.size-indicator.over-limit {
color: #e74c3c;
color: var(--danger-text);
}
.action-btn {
@@ -580,8 +573,8 @@ onUnmounted(() => {
right: 100%;
transform: translateY(-50%);
margin-right: 8px;
background: #333;
color: #fff;
background: var(--tooltip-bg);
color: var(--tooltip-fg);
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
@@ -608,10 +601,10 @@ onUnmounted(() => {
bottom: 100%;
right: 0;
margin-bottom: 8px;
background: #fff;
border: 1px solid #ddd;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
box-shadow: var(--panel-shadow);
overflow: hidden;
z-index: 10000;
min-width: 160px;
@@ -626,11 +619,11 @@ onUnmounted(() => {
text-align: left;
cursor: pointer;
font-size: 14px;
color: #333;
color: var(--app-text);
}
.image-dropdown button:hover {
background: #f5f5f5;
background: var(--crepe-color-hover);
}
.url-dialog-overlay {
@@ -639,7 +632,7 @@ onUnmounted(() => {
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
background: var(--overlay-bg);
display: flex;
align-items: center;
justify-content: center;
@@ -647,32 +640,35 @@ onUnmounted(() => {
}
.url-dialog {
background: #fff;
background: var(--panel-bg);
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
border: 1px solid var(--panel-border);
box-shadow: var(--panel-shadow);
min-width: 320px;
}
.url-dialog h3 {
margin: 0 0 12px 0;
font-size: 16px;
color: #333;
color: var(--app-text);
}
.url-dialog input {
width: 100%;
box-sizing: border-box;
padding: 10px 12px;
border: 1px solid #ddd;
border: 1px solid var(--panel-border);
border-radius: 4px;
font-size: 14px;
margin-bottom: 16px;
color: var(--app-text);
background: var(--crepe-color-background);
}
.url-dialog input:focus {
outline: none;
border-color: #4a90d9;
border-color: var(--focus-ring);
}
.url-dialog-buttons {
@@ -683,32 +679,32 @@ onUnmounted(() => {
.dialog-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border: 1px solid var(--panel-border);
border-radius: 4px;
cursor: pointer;
font-size: 14px;
background: #fff;
color: #333;
background: var(--btn-bg);
color: var(--btn-fg);
}
.dialog-btn:hover {
background: #f5f5f5;
background: var(--crepe-color-hover);
}
.dialog-btn.primary {
background: #4a90d9;
color: #fff;
border-color: #4a90d9;
background: var(--btn-hover-bg);
color: var(--btn-hover-fg);
border-color: var(--btn-hover-bg);
}
.dialog-btn.primary:hover {
background: #3a80c9;
filter: brightness(0.92);
}
.milkdown-editor {
width: 100%;
height: 100%;
background-color: #ffffff;
background-color: var(--crepe-color-background);
overflow-y: auto;
}
@@ -770,10 +766,14 @@ onUnmounted(() => {
}
.milkdown-editor::-webkit-scrollbar-thumb {
background-color: #ddd;
background-color: var(--scrollbar-thumb);
border-radius: 4px;
}
.milkdown-editor::-webkit-scrollbar-thumb:hover {
background-color: var(--scrollbar-thumb-hover);
}
.milkdown-editor :deep(.milkdown__toolbar),
.milkdown-editor :deep(.milkdown__menu),
.milkdown-editor :deep(.milkdown__statusbar),
@@ -799,8 +799,8 @@ onUnmounted(() => {
<style>
.copilot-ghost-text {
color: #999;
opacity: 0.6;
color: var(--ghost-text);
opacity: 0.72;
pointer-events: auto;
}
@@ -817,7 +817,7 @@ onUnmounted(() => {
}
.copilot-ghost-text code {
background-color: rgba(0, 0, 0, 0.05);
background-color: var(--ghost-code-bg);
padding: 0.2em 0.4em;
border-radius: 3px;
}
@@ -825,4 +825,16 @@ onUnmounted(() => {
.copilot-ghost-text a {
text-decoration: underline;
}
.copilot-ghost-block {
color: var(--ghost-text);
opacity: 0.72;
}
.copilot-ghost-block code,
.copilot-ghost-block pre,
.copilot-ghost-block a {
color: inherit;
opacity: inherit;
}
</style>