From 304286847fe20cddf1a3f987624f719005ff6dc3 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Sat, 21 Mar 2026 16:50:58 +0800 Subject: [PATCH 1/3] Fix MEditor inline AI prompt modes --- .../meditor/components/TiptapEditor.tsx | 55 ++++-- .../editor/meditor/utils/inlineAi.test.ts | 32 ++- .../tools/editor/meditor/utils/inlineAi.ts | 186 +++++++++++++----- .../editor/meditor/utils/tiptapMarkdown.ts | 14 ++ 4 files changed, 208 insertions(+), 79 deletions(-) diff --git a/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.tsx b/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.tsx index fea06ace..11db75db 100644 --- a/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.tsx +++ b/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.tsx @@ -36,11 +36,13 @@ import { import { getBlockIndexForLine } from '../utils/markdownBlocks'; import { buildInlineContinuePrompt, + buildInlineSummaryPrompt, + buildInlineTodoPrompt, sanitizeInlineAiMarkdownResponse, } from '../utils/inlineAi'; import { getCachedLocalImageDataUrl, loadLocalImages } from '../utils/loadLocalImages'; import { isLocalPath, resolveImagePath } from '../utils/rehype-local-images'; -import { markdownToTiptapDoc, tiptapDocToMarkdown } from '../utils/tiptapMarkdown'; +import { markdownToTiptapDoc, tiptapDocToMarkdown, tiptapDocToTopLevelMarkdownBlocks } from '../utils/tiptapMarkdown'; import './TiptapEditor.scss'; const log = createLogger('TiptapEditor'); @@ -195,12 +197,12 @@ async function resolveEditorLocalImages( await loadLocalImages(container); } -type InlineAiMode = 'ask' | 'continue'; type InlineAiStatus = 'idle' | 'submitting' | 'streaming' | 'ready' | 'error'; +type InlineAiPromptKind = 'continue' | 'summary' | 'todo'; type InlineAiState = { isOpen: boolean; - mode: InlineAiMode; + promptKind: InlineAiPromptKind; query: string; status: InlineAiStatus; response: string; @@ -262,7 +264,7 @@ function getTopLevelBlockPositionById( function getCurrentEmptyParagraphContext( instance: TiptapEditorInstance, root: HTMLDivElement | null -): Omit | null { +): Omit | null { const { selection } = instance.state; if (!selection.empty || selection.$from.depth !== 1) { return null; @@ -392,7 +394,7 @@ export const TiptapEditor = React.forwardRef { + const handleContinueWriting = useCallback(async ( + options?: { userInputOverride?: string; promptKindOverride?: InlineAiPromptKind } + ) => { if (!inlineAiState || inlineAiState.status === 'submitting' || inlineAiState.status === 'streaming') { return; } @@ -856,18 +860,27 @@ export const TiptapEditor = React.forwardRef { + const handleInlineAiQuickAction = useCallback((promptKind: InlineAiPromptKind, query: string) => { setInlineAiState(current => current ? { ...current, + promptKind, query, } : current); - void handleContinueWriting(query); + void handleContinueWriting({ + userInputOverride: query, + promptKindOverride: promptKind, + }); }, [handleContinueWriting]); useEffect(() => { @@ -927,7 +944,6 @@ export const TiptapEditor = React.forwardRef { - handleInlineAiQuickAction(''); + handleInlineAiQuickAction('continue', ''); }} > @@ -1127,7 +1142,7 @@ export const TiptapEditor = React.forwardRef { - handleInlineAiQuickAction(t('editor.meditor.inlineAi.summaryDirection')); + handleInlineAiQuickAction('summary', t('editor.meditor.inlineAi.summaryDirection')); }} > @@ -1139,7 +1154,7 @@ export const TiptapEditor = React.forwardRef { - handleInlineAiQuickAction(t('editor.meditor.inlineAi.todoDirection')); + handleInlineAiQuickAction('todo', t('editor.meditor.inlineAi.todoDirection')); }} > diff --git a/src/web-ui/src/tools/editor/meditor/utils/inlineAi.test.ts b/src/web-ui/src/tools/editor/meditor/utils/inlineAi.test.ts index 2741654f..fb995114 100644 --- a/src/web-ui/src/tools/editor/meditor/utils/inlineAi.test.ts +++ b/src/web-ui/src/tools/editor/meditor/utils/inlineAi.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { buildInlineContinuePrompt } from './inlineAi'; +import { + buildInlineContinuePrompt, + buildInlineSummaryPrompt, +} from './inlineAi'; function createBlock(index: number, repeat = 220): string { const heading = `## Section ${String(index).padStart(2, '0')}`; @@ -26,10 +29,10 @@ describe('inlineAi prompt shaping', () => { filePath: '/tmp/doc.md', }); - expect(prompt).toContain('[[before_1]]\n# Intro\nAAA'); - expect(prompt).toContain('[[after_1]]\n## Middle\nBBB'); - expect(prompt).toContain('[[after_2]]\n## Tail\nCCC'); - expect(prompt).not.toContain('[[before_2]]\n## Middle\nBBB'); + expect(prompt).toContain('[[before_block_1]]\n# Intro\nAAA'); + expect(prompt).toContain('[[after_block_2]]\n## Middle\nBBB'); + expect(prompt).toContain('[[after_block_3]]\n## Tail\nCCC'); + expect(prompt).not.toContain('[[before_block_2]]\n## Middle\nBBB'); }); it('prioritizes blocks near the insertion point when the document is long', () => { @@ -51,4 +54,23 @@ describe('inlineAi prompt shaping', () => { expect(prompt).toContain('- [#1] ## Section 01'); expect(prompt).toContain('- [#24] ## Section 24'); }); + + it('uses summary-specific instructions without the continuation anti-summary rule', () => { + const markdown = [ + '# Intro\nAAA', + '## Middle\nBBB', + ].join('\n\n'); + + const prompt = buildInlineSummaryPrompt({ + userInput: '在当前位置插入一段简短摘要,概括上文的关键内容,并保持当前文档语言和风格。', + markdown, + blockIndex: 1, + filePath: '/tmp/doc.md', + }); + + expect(prompt).toContain('You are inserting a short summary into an in-editor Markdown document at one exact insertion point.'); + expect(prompt).toContain('Summarize the key points from the content before the insertion point in a compact way.'); + expect(prompt).not.toContain('Do not rewrite, summarize, paraphrase, or restate any existing block in the document.'); + expect(prompt).toContain('User direction for the summary: 在当前位置插入一段简短摘要'); + }); }); diff --git a/src/web-ui/src/tools/editor/meditor/utils/inlineAi.ts b/src/web-ui/src/tools/editor/meditor/utils/inlineAi.ts index 6a429b6f..10958d3e 100644 --- a/src/web-ui/src/tools/editor/meditor/utils/inlineAi.ts +++ b/src/web-ui/src/tools/editor/meditor/utils/inlineAi.ts @@ -1,10 +1,15 @@ +import type { TiptapTopLevelMarkdownBlock } from './tiptapMarkdown'; + type InlineAiPromptParams = { userInput: string; markdown: string; blockIndex: number; filePath?: string; + topLevelBlocks?: TiptapTopLevelMarkdownBlock[]; }; +type InlineAiPromptKind = 'continue' | 'summary' | 'todo'; + const MAX_CONTEXT_CHARS = 12000; const SURROUNDING_BLOCK_WINDOW = 2; const MAX_FOCUSED_CONTEXT_CHARS = 8000; @@ -12,8 +17,17 @@ const MAX_BLOCK_SNIPPET_CHARS = 1600; const MAX_STRUCTURE_SUMMARY_CHARS = 2400; const MAX_RANGE_SUMMARY_ENTRIES = 6; -function formatDocumentContext(markdown: string, blockIndex: number): string { - const content = buildPromptDocumentContext(markdown, blockIndex).trim(); +function formatDocumentContext(markdown: string, blocks: string[], blockIndex: number): string { + const normalized = markdown.trim(); + if (!normalized) { + return '(Document is currently empty)'; + } + + if (normalized.length <= MAX_CONTEXT_CHARS) { + return '(Omitted because the full document is short and already shown below.)'; + } + + const content = buildPromptDocumentContext(markdown, blocks, blockIndex).trim(); if (!content) { return '(Document is currently empty)'; } @@ -21,13 +35,21 @@ function formatDocumentContext(markdown: string, blockIndex: number): string { return content; } -function parseTopLevelBlocks(markdown: string): string[] { - const normalized = markdown.replace(/\r\n/g, '\n').trim(); - if (!normalized) { +function getPromptBlocks(markdown: string, topLevelBlocks?: TiptapTopLevelMarkdownBlock[]): string[] { + const normalizedMarkdown = markdown.replace(/\r\n/g, '\n').trim(); + const normalizedTopLevelBlocks = (topLevelBlocks ?? []) + .map(block => block.markdown.replace(/\r\n/g, '\n').trim()) + .filter(Boolean); + + if (normalizedTopLevelBlocks.length > 0) { + return normalizedTopLevelBlocks; + } + + if (!normalizedMarkdown) { return []; } - return normalized + return normalizedMarkdown .split(/\n{2,}/) .map(block => block.trim()) .filter(Boolean); @@ -121,7 +143,7 @@ function buildOmittedRangeSummary( ].join('\n'); } -function buildPromptDocumentContext(markdown: string, blockIndex: number): string { +function buildPromptDocumentContext(markdown: string, blocks: string[], blockIndex: number): string { const normalized = markdown.trim(); if (!normalized) { return ''; @@ -131,7 +153,6 @@ function buildPromptDocumentContext(markdown: string, blockIndex: number): strin return normalized; } - const blocks = parseTopLevelBlocks(markdown); if (blocks.length === 0) { return normalized.slice(0, MAX_CONTEXT_CHARS); } @@ -214,8 +235,7 @@ function buildPromptDocumentContext(markdown: string, blockIndex: number): strin ].join('\n'); } -function formatInsertionContext(markdown: string, blockIndex: number): string { - const blocks = parseTopLevelBlocks(markdown); +function formatInsertionContext(blocks: string[], blockIndex: number): string { if (blocks.length === 0) { return '(No surrounding blocks yet)'; } @@ -228,76 +248,134 @@ function formatInsertionContext(markdown: string, blockIndex: number): string { const nextBlocks = blocks.slice(anchorIndex, anchorIndex + SURROUNDING_BLOCK_WINDOW); return [ + `Insertion anchor: after top-level block #${blockIndex} and before top-level block #${blockIndex + 1}.`, + `Resolved anchor index in current prompt blocks: ${anchorIndex}.`, + '', 'Blocks immediately before the insertion point:', previousBlocks.length > 0 - ? previousBlocks.map((block, index) => `[[before_${index + 1}]]\n${block}`).join('\n\n') + ? previousBlocks.map((block, index) => { + const actualIndex = anchorIndex - previousBlocks.length + index; + return `[[before_block_${actualIndex + 1}]]\n${block}`; + }).join('\n\n') : '(No previous blocks)', '', 'Blocks immediately after the insertion point:', nextBlocks.length > 0 - ? nextBlocks.map((block, index) => `[[after_${index + 1}]]\n${block}`).join('\n\n') + ? nextBlocks.map((block, index) => `[[after_block_${anchorIndex + index + 1}]]\n${block}`).join('\n\n') : '(Insertion point is at the end of the document)', ].join('\n'); } -export function buildInlineAskAiPrompt(params: InlineAiPromptParams): string { - const { userInput, markdown, blockIndex, filePath } = params; - const locationLine = filePath - ? `Current file path: ${filePath}` - : 'Current file path: (not available)'; +function formatFullDocument(markdown: string): string { + const normalized = markdown.trim(); + return normalized || '(Document is currently empty)'; +} + +function buildPromptDocumentSection(markdown: string, blocks: string[], blockIndex: number): string[] { + const normalizedMarkdown = markdown.trim(); + const shouldIncludeFullDocument = normalizedMarkdown.length > 0 && normalizedMarkdown.length <= MAX_CONTEXT_CHARS; return [ - 'You are helping with an in-editor Markdown document.', - 'The document may contain unsaved local edits, so treat the content below as the source of truth.', - 'You may answer the user, suggest edits, or decide which workspace actions to take.', - 'If you reference edits to the current document, remember that the unsaved buffer may differ from the file on disk.', - '', - locationLine, - `Cursor is on an empty paragraph after top-level block #${blockIndex + 1}.`, - '', - formatInsertionContext(markdown, blockIndex), - '', - 'Current markdown document or focused document context:', + shouldIncludeFullDocument ? 'Current markdown document:' : 'Focused document context beyond the immediate neighbors:', '```md', - formatDocumentContext(markdown, blockIndex), + shouldIncludeFullDocument ? formatFullDocument(markdown) : formatDocumentContext(markdown, blocks, blockIndex), '```', - '', - 'User request:', - userInput.trim(), - ].join('\n'); + ]; } -export function buildInlineContinuePrompt(params: InlineAiPromptParams): string { - const { userInput, markdown, blockIndex, filePath } = params; - const instruction = userInput.trim() - ? `User direction for the continuation: ${userInput.trim()}` - : 'User direction for the continuation: continue naturally from the current context.'; +function buildInlineInsertPrompt( + params: InlineAiPromptParams, + kind: InlineAiPromptKind, +): string { + const { userInput, markdown, blockIndex, filePath, topLevelBlocks } = params; + const blocks = getPromptBlocks(markdown, topLevelBlocks); const locationLine = filePath ? `Current file path: ${filePath}` : 'Current file path: (not available)'; + const trimmedUserInput = userInput.trim(); + + const promptByKind: Record = { + continue: [ + 'You are continuing an in-editor Markdown document at one exact insertion point.', + 'This is continuation only, not rewriting.', + 'Return only the new Markdown content that should be inserted at that location.', + 'Do not add explanations, analysis, XML tags, or wrapper text.', + 'Do not rewrite, summarize, paraphrase, or restate any existing block in the document.', + 'Do not copy sentences, headings, list items, or bullet points from the surrounding blocks.', + 'Keep the writing consistent with the existing language, tone, structure, heading depth, and list style.', + 'The user is actively editing at the insertion anchor described below, so generate content for that exact location only.', + 'If there are later blocks after the insertion point, make the continuation lead into them naturally without reusing their text.', + 'If no new content is needed, return an empty string.', + 'Prefer a concise continuation unless the user explicitly asks for something longer.', + '', + locationLine, + `Insertion point: the empty paragraph after top-level block #${blockIndex}.`, + 'The generated content will replace that empty paragraph only.', + 'Do not modify or regenerate the blocks before or after the insertion point.', + trimmedUserInput + ? `User direction for the continuation: ${trimmedUserInput}` + : 'User direction for the continuation: continue naturally from the current context.', + ], + summary: [ + 'You are inserting a short summary into an in-editor Markdown document at one exact insertion point.', + 'Return only the new Markdown content that should be inserted at that location.', + 'Do not add explanations, analysis, XML tags, or wrapper text.', + 'Summarize the key points from the content before the insertion point in a compact way.', + 'Do not copy sentences or headings verbatim unless a short phrase is necessary.', + 'Do not modify or regenerate the surrounding blocks.', + 'If there are later blocks after the insertion point, make sure the summary does not conflict with them.', + 'Keep the writing consistent with the existing language, tone, structure, heading depth, and list style.', + 'Prefer a single short paragraph or a very short bullet list unless the user asks for a different shape.', + '', + locationLine, + `Insertion point: the empty paragraph after top-level block #${blockIndex}.`, + 'The generated content will replace that empty paragraph only.', + trimmedUserInput + ? `User direction for the summary: ${trimmedUserInput}` + : 'User direction for the summary: insert a short summary at the current position.', + ], + todo: [ + 'You are inserting a concise Markdown todo list into an in-editor Markdown document at one exact insertion point.', + 'Return only the new Markdown content that should be inserted at that location.', + 'Do not add explanations, analysis, XML tags, or wrapper text.', + 'Extract the next concrete action items implied by the content before the insertion point.', + 'Prefer Markdown task list items (`- [ ]`) unless the user explicitly asks for another format.', + 'Do not copy sentences or bullet points verbatim from the surrounding blocks.', + 'Do not modify or regenerate the surrounding blocks.', + 'If there are later blocks after the insertion point, make sure the todo list does not conflict with them.', + 'Keep the writing consistent with the existing language and style.', + 'Prefer a short list focused on the most actionable next steps.', + '', + locationLine, + `Insertion point: the empty paragraph after top-level block #${blockIndex}.`, + 'The generated content will replace that empty paragraph only.', + trimmedUserInput + ? `User direction for the todo list: ${trimmedUserInput}` + : 'User direction for the todo list: insert a concise Markdown todo list at the current position.', + ], + }; return [ - 'You are completing an in-editor Markdown document at a specific insertion point.', - 'Return only the Markdown content that should be inserted there.', - 'Do not add explanations, analysis, XML tags, or wrapper text.', - 'Keep the writing consistent with the existing language, tone, structure, heading depth, and list style.', - 'Do not repeat surrounding content that is already in the document.', - 'If there are later blocks after the insertion point, make the continuation flow into them naturally.', - 'Prefer a concise continuation unless the user explicitly asks for something longer.', + ...promptByKind[kind], '', - locationLine, - `Insertion point: empty paragraph after top-level block #${blockIndex + 1}.`, - instruction, + formatInsertionContext(blocks, blockIndex), '', - formatInsertionContext(markdown, blockIndex), - '', - 'Current markdown document or focused document context:', - '```md', - formatDocumentContext(markdown, blockIndex), - '```', + ...buildPromptDocumentSection(markdown, blocks, blockIndex), ].join('\n'); } +export function buildInlineContinuePrompt(params: InlineAiPromptParams): string { + return buildInlineInsertPrompt(params, 'continue'); +} + +export function buildInlineSummaryPrompt(params: InlineAiPromptParams): string { + return buildInlineInsertPrompt(params, 'summary'); +} + +export function buildInlineTodoPrompt(params: InlineAiPromptParams): string { + return buildInlineInsertPrompt(params, 'todo'); +} + export function sanitizeInlineAiMarkdownResponse(value: string): string { const trimmed = value.trim(); if (!trimmed) { diff --git a/src/web-ui/src/tools/editor/meditor/utils/tiptapMarkdown.ts b/src/web-ui/src/tools/editor/meditor/utils/tiptapMarkdown.ts index 5c082bbd..98422f7e 100644 --- a/src/web-ui/src/tools/editor/meditor/utils/tiptapMarkdown.ts +++ b/src/web-ui/src/tools/editor/meditor/utils/tiptapMarkdown.ts @@ -28,6 +28,11 @@ type TiptapMarkdownOptions = { preserveTrailingNewline?: boolean; }; +export interface TiptapTopLevelMarkdownBlock { + blockId?: string; + markdown: string; +} + type AlignmentStackEntry = { align: string | null; groupId: number | null; @@ -680,3 +685,12 @@ export function tiptapDocToMarkdown( return options.preserveTrailingNewline ? `${markdown}\n` : markdown; } + +export function tiptapDocToTopLevelMarkdownBlocks( + doc: JSONContent | null | undefined, +): TiptapTopLevelMarkdownBlock[] { + return (doc?.content ?? []).map((node: JSONContent) => ({ + blockId: typeof node.attrs?.blockId === 'string' ? node.attrs.blockId : undefined, + markdown: renderBlock(node).trim(), + })); +} From 4e03062671fc70b988e7b2e07a39f7037e13ee51 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Sat, 21 Mar 2026 17:02:10 +0800 Subject: [PATCH 2/3] Fix markdown editor dirty state and inline preview clicks --- .../tools/editor/components/MarkdownEditor.tsx | 13 +++++++++++++ .../meditor/components/InlineAiPreviewBlock.tsx | 17 ++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/web-ui/src/tools/editor/components/MarkdownEditor.tsx b/src/web-ui/src/tools/editor/components/MarkdownEditor.tsx index 291e488a..64435b3d 100644 --- a/src/web-ui/src/tools/editor/components/MarkdownEditor.tsx +++ b/src/web-ui/src/tools/editor/components/MarkdownEditor.tsx @@ -62,7 +62,10 @@ const MarkdownEditor: React.FC = ({ const lastModifiedTimeRef = useRef(0); const lastJumpPositionRef = useRef<{ filePath: string; line: number } | null>(null); const onContentChangeRef = useRef(onContentChange); + const contentRef = useRef(content); + const lastReportedDirtyRef = useRef(null); onContentChangeRef.current = onContentChange; + contentRef.current = content; const basePath = React.useMemo(() => { if (!filePath) return undefined; @@ -106,6 +109,7 @@ const MarkdownEditor: React.FC = ({ if (!isUnmountedRef.current) { setContent(fileContent); setHasChanges(false); + lastReportedDirtyRef.current = false; setTimeout(() => { editorRef.current?.setInitialContent?.(fileContent); }, 0); @@ -148,6 +152,7 @@ const MarkdownEditor: React.FC = ({ } else if (initialContent !== undefined) { setContent(initialContent); setHasChanges(false); + lastReportedDirtyRef.current = false; setTimeout(() => { editorRef.current?.setInitialContent?.(initialContent); }, 0); @@ -181,6 +186,7 @@ const MarkdownEditor: React.FC = ({ if (!isUnmountedRef.current) { editorRef.current?.markSaved?.(); setHasChanges(false); + lastReportedDirtyRef.current = false; if (onContentChangeRef.current) { onContentChangeRef.current(content, false); } @@ -200,11 +206,18 @@ const MarkdownEditor: React.FC = ({ }, [content, filePath, workspacePath, hasChanges, onSave, t]); const handleContentChange = useCallback((newContent: string) => { + contentRef.current = newContent; setContent(newContent); }, []); const handleDirtyChange = useCallback((isDirty: boolean) => { setHasChanges(isDirty); + if (lastReportedDirtyRef.current === isDirty) { + return; + } + + lastReportedDirtyRef.current = isDirty; + onContentChangeRef.current?.(contentRef.current, isDirty); }, []); const handleSave = useCallback((_value: string) => { diff --git a/src/web-ui/src/tools/editor/meditor/components/InlineAiPreviewBlock.tsx b/src/web-ui/src/tools/editor/meditor/components/InlineAiPreviewBlock.tsx index 7c92ef43..65c2d235 100644 --- a/src/web-ui/src/tools/editor/meditor/components/InlineAiPreviewBlock.tsx +++ b/src/web-ui/src/tools/editor/meditor/components/InlineAiPreviewBlock.tsx @@ -38,6 +38,17 @@ export const InlineAiPreviewBlock: React.FC = ({ onReject, onRetry, }) => { + const handlePointerDown: React.PointerEventHandler = (event) => { + // Keep ProseMirror from stealing focus and remounting the widget before click fires. + event.preventDefault(); + event.stopPropagation(); + }; + + const handleMouseDown: React.MouseEventHandler = (event) => { + event.preventDefault(); + event.stopPropagation(); + }; + const statusText = status === 'submitting' || status === 'streaming' ? labels.streaming @@ -47,7 +58,11 @@ export const InlineAiPreviewBlock: React.FC = ({ const previewStateClass = `m-editor-inline-ai-preview--${status}`; return ( -
+
{labels.title} {statusText} From a60b0dc8f23663e6f1a21bad6fd9c7befa4804bb Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Sat, 21 Mar 2026 17:11:06 +0800 Subject: [PATCH 3/3] Adapt MEditor styles to app theme --- .../editor/components/MarkdownEditor.scss | 28 +++++++++---------- .../editor/meditor/components/Preview.scss | 5 ++-- .../meditor/components/TiptapEditor.scss | 14 +++++----- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/web-ui/src/tools/editor/components/MarkdownEditor.scss b/src/web-ui/src/tools/editor/components/MarkdownEditor.scss index ce3e3680..672c2097 100644 --- a/src/web-ui/src/tools/editor/components/MarkdownEditor.scss +++ b/src/web-ui/src/tools/editor/components/MarkdownEditor.scss @@ -107,8 +107,8 @@ h1 { font-size: $font-size-3xl; - color: #111111; - border-bottom: 1px solid rgba(17, 17, 17, 0.12); + color: var(--color-text-primary); + border-bottom: 1px solid $border-base; padding-bottom: $size-gap-3; text-align: center; letter-spacing: 0.5px; @@ -116,7 +116,7 @@ h2 { font-size: $font-size-2xl; - border-bottom: 1px solid rgba(255, 255, 255, 0.04); + border-bottom: 1px solid $border-base; padding-bottom: $size-gap-2; } @@ -145,7 +145,7 @@ padding: 0.2em 0.4em; margin: 0; font-size: 100%; - background-color: rgba(255, 255, 255, 0.08); + background-color: $element-bg-subtle; border-radius: $size-radius-sm; font-family: $font-family-mono; color: var(--color-accent-500); @@ -157,7 +157,7 @@ font-size: 100%; line-height: 1.45; background-color: $element-bg-subtle; - border: 1px solid rgba(255, 255, 255, 0.03); + border: 1px solid $border-base; border-radius: $size-radius-base; margin-bottom: $size-gap-4; @@ -175,7 +175,7 @@ blockquote { padding: $size-gap-3 $size-gap-4; color: var(--color-text-muted); - border-left: 3px solid rgba(255, 255, 255, 0.15); + border-left: 3px solid $border-base; margin: 0 0 $size-gap-4 0; background: $element-bg-subtle; border-radius: 0 $size-radius-base $size-radius-base 0; @@ -263,28 +263,28 @@ margin-bottom: $size-gap-4; border-radius: $size-radius-base; overflow: hidden; - border: 1px solid rgba(255, 255, 255, 0.04); + border: 1px solid $border-base; th { font-weight: $font-weight-semibold; padding: $size-gap-2 $size-gap-3; - border: 1px solid rgba(255, 255, 255, 0.04); + border: 1px solid $border-base; background: $element-bg-soft; color: var(--color-text-primary); } td { padding: $size-gap-2 $size-gap-3; - border: 1px solid rgba(255, 255, 255, 0.03); + border: 1px solid $border-base; } tr { background-color: transparent; - border-top: 1px solid rgba(255, 255, 255, 0.03); + border-top: 1px solid $border-base; transition: background-color $motion-fast $easing-standard; &:nth-child(2n) { - background-color: rgba(255, 255, 255, 0.01); + background-color: color-mix(in srgb, var(--color-text-primary) 2%, transparent); } &:hover { @@ -306,8 +306,8 @@ margin: $size-gap-6 0; background: linear-gradient(90deg, transparent 0%, - rgba(255, 255, 255, 0.1) 20%, - rgba(255, 255, 255, 0.1) 80%, + color-mix(in srgb, var(--color-text-muted) 30%, transparent) 20%, + color-mix(in srgb, var(--color-text-muted) 30%, transparent) 80%, transparent 100%); border: 0; border-radius: 1px; @@ -358,7 +358,7 @@ margin: $size-gap-4 0; padding: $size-gap-4; background: $element-bg-subtle; - border: 1px solid rgba(255, 255, 255, 0.03); + border: 1px solid $border-base; border-radius: $size-radius-base; overflow-x: auto; diff --git a/src/web-ui/src/tools/editor/meditor/components/Preview.scss b/src/web-ui/src/tools/editor/meditor/components/Preview.scss index ce0728fc..5ad51898 100644 --- a/src/web-ui/src/tools/editor/meditor/components/Preview.scss +++ b/src/web-ui/src/tools/editor/meditor/components/Preview.scss @@ -33,7 +33,7 @@ margin: $size-gap-4 0; padding: $size-gap-4; background: $element-bg-subtle; - border: 1px solid rgba(255, 255, 255, 0.03); + border: 1px solid $border-base; border-radius: $size-radius-base; overflow-x: auto; @@ -70,7 +70,7 @@ &-divider { border: none; - border-top: 1px dashed rgba(255, 255, 255, 0.08); + border-top: 1px dashed $border-base; margin: $size-gap-1 0; } @@ -80,4 +80,3 @@ word-break: break-word; } } - diff --git a/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss b/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss index de709205..6cd6cad0 100644 --- a/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss +++ b/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss @@ -85,7 +85,7 @@ h1 { font-size: $font-size-3xl; - color: #111111; + color: var(--color-text-primary); text-align: center; } @@ -114,7 +114,7 @@ blockquote { margin: 0; padding-left: $size-gap-3; - border-left: 3px solid rgba(255, 255, 255, 0.15); + border-left: 3px solid $border-base; } pre { @@ -309,8 +309,8 @@ height: 3.5rem; padding-right: $size-gap-2; border-radius: 999px; - background: rgba(255, 255, 255, 0.03); - border-color: rgba(255, 255, 255, 0.1); + background: $element-bg-subtle; + border-color: $border-base; } .bitfun-input { @@ -330,7 +330,7 @@ padding: 0 $size-gap-2; height: 2rem; border-radius: 999px; - background: rgba(255, 255, 255, 0.05); + background: $element-bg-subtle; color: var(--color-text-muted); font-size: $font-size-xs; white-space: nowrap; @@ -389,8 +389,8 @@ transition: background $motion-fast $easing-standard, border-color $motion-fast $easing-standard, color $motion-fast $easing-standard; &:hover { - background: rgba(255, 255, 255, 0.04); - border-color: rgba(255, 255, 255, 0.08); + background: $element-bg-subtle; + border-color: $border-base; } }