docs: 将仓库代码注释统一为中文
This commit is contained in:
@@ -52,7 +52,7 @@ describe('EditorPane file switching', () => {
|
||||
|
||||
expect(store.currentFilePath).toBe('/数据结构/红黑树.md')
|
||||
expect(wrapper.text()).not.toContain('祝你写作愉快')
|
||||
}, 15000) // Real Milkdown is now imported lazily; cold module transforms count toward this integration test.
|
||||
}, 15000) // 真正的Milkdown现在被延迟导入;冷模块将计数转换为此集成测试。
|
||||
|
||||
it('applies the saved spell-check and language settings to source mode', async () => {
|
||||
const editor = useEditorStore()
|
||||
@@ -68,5 +68,5 @@ describe('EditorPane file switching', () => {
|
||||
expect(textarea.attributes('spellcheck')).toBe('true')
|
||||
expect(textarea.attributes('lang')).toBe('en')
|
||||
expect(textarea.attributes('aria-label')).toBe('Markdown source editor')
|
||||
}, 15000) // Lazy source-editor module transforms need the same cold-start budget.
|
||||
}, 15000) // 惰性源编辑器模块转换需要相同的冷启动预算。
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
// The application has a doctype; happy-dom otherwise reports quirks mode to KaTeX.
|
||||
// 应用程序有一个文档类型; happy-dom 否则会向 KaTeX 报告怪癖模式。
|
||||
vi.hoisted(() => { Object.defineProperty(document, 'compatMode', {value:'CSS1Compat',configurable:true}) })
|
||||
import { mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
@@ -29,7 +29,7 @@ async function waitForEditor(wrapper: VueWrapper): Promise<Editor> {
|
||||
try {
|
||||
editor.action(getMarkdown())
|
||||
return editor
|
||||
} catch { /* editor is still creating */ }
|
||||
} catch { /* 编辑器仍在创建 */ }
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||
}
|
||||
@@ -263,7 +263,7 @@ describe('VisualMarkdownEditor formatting toolbars', () => {
|
||||
const wrapper = mount(VisualMarkdownEditor, {props:{initialContent:''},attachTo:document.body})
|
||||
mounted.push(wrapper)
|
||||
const editor = await waitForEditor(wrapper)
|
||||
// Chromium/IME can omit data and commit its DOM change after the input event.
|
||||
// Chromium/IME 可以省略数据并在输入事件后提交其 DOM 更改。
|
||||
await wrapper.get('.ProseMirror').trigger('input', {inputType, data:null})
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
editor.action(ctx => ctx.get(editorViewCtx).dispatch(ctx.get(editorViewCtx).state.tr.insertText('`s`')))
|
||||
@@ -292,8 +292,7 @@ describe('VisualMarkdownEditor formatting toolbars', () => {
|
||||
view.dispatch(view.state.tr.insertText('``'))
|
||||
await wrapper.get('.ProseMirror').trigger('input', {inputType:'insertText', data:'`'})
|
||||
await new Promise(resolve => setTimeout(resolve, 60))
|
||||
// Empty pairs are serialized as escaped literal text, but that must not
|
||||
// prevent recognition after the user moves back and fills in the content.
|
||||
// 空对被序列化为转义文字文本,但这不得妨碍用户向后移动并填写内容后的识别。
|
||||
expect(editor.action(getMarkdown())).toContain('\\`')
|
||||
view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, 2)).insertText('s'))
|
||||
await wrapper.get('.ProseMirror').trigger('input', {inputType:'insertText', data:'s'})
|
||||
|
||||
@@ -350,8 +350,8 @@ onMounted(async () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
// Crepe's defaultsDeep merges language arrays and theme extension internals.
|
||||
// Replace both AFTER feature configuration to avoid default grammar collisions.
|
||||
// Crepe 的 defaultsDeep 会合并语言数组与主题扩展内部配置。
|
||||
// 必须在功能配置完成后同时替换两者,以免默认语法发生冲突。
|
||||
crepe.editor.config(ctx => ctx.update(codeBlockConfig.key, config => ({
|
||||
...config,
|
||||
languages: shikiLanguages(themeStore.resolvedCodeBlockTheme),
|
||||
@@ -391,7 +391,7 @@ onMounted(async () => {
|
||||
const sections = headingSections(current.state.doc)
|
||||
const folded = headingFoldKey.getState(current.state)
|
||||
hasFoldableHeadings.value = sections.length > 0
|
||||
// Hidden descendants retain their own state but are not visible expanded sections.
|
||||
// 被隐藏的后代节点保留自身状态,但不算作可见的展开章节。
|
||||
let hiddenUntil = -1
|
||||
allHeadingsFolded.value = sections.length > 0 && sections.every(section => {
|
||||
if (section.from < hiddenUntil) return true
|
||||
@@ -585,8 +585,7 @@ defineExpose({ getEditor: () => crepe?.editor })
|
||||
.milkdown-host :deep(.ProseMirror) { box-sizing: border-box; width: min(100%, var(--editor-line-width, 80ch)); min-height: 100%; margin: 0 auto; padding: var(--space-3xl) var(--space-xl); outline: none; font-family: var(--font-editor-sans); font-size: var(--font-editor-size); line-height: var(--font-editor-line-height); caret-color: var(--color-accent-primary); }
|
||||
.milkdown-host :deep(.ProseMirror-selectednode) { outline-color: var(--color-accent-primary); }
|
||||
.milkdown-host :deep(.ProseMirror p) { font-weight: 400; }
|
||||
/* Mermaid measures HTML labels outside the editor. Crepe's paragraph padding
|
||||
must not enlarge them after insertion into fixed-size SVG foreignObjects. */
|
||||
/* Mermaid 在编辑器外部测量 HTML 标签。 Crepe 的段落填充在插入固定大小的 SVGforeignObjects 后不得放大它们。 */
|
||||
.milkdown-host :deep(.editor-mermaid-preview svg foreignObject p) { margin: 0; padding: 0; line-height: inherit; font-weight: inherit; }
|
||||
.milkdown-host :deep(.ProseMirror h1), .milkdown-host :deep(.ProseMirror h2), .milkdown-host :deep(.ProseMirror h3), .milkdown-host :deep(.ProseMirror h4), .milkdown-host :deep(.ProseMirror h5), .milkdown-host :deep(.ProseMirror h6) { font-weight: 700; }
|
||||
.milkdown-host :deep(.font-size-marker) { display: none; }
|
||||
|
||||
@@ -16,7 +16,7 @@ export const configureCalloutSerialization: Parameters<Editor['config']>[0] = ct
|
||||
tracker.shift(2)
|
||||
const result = state.indentLines(state.containerFlow(node, tracker.current()), (line, _index, blank) => `>${blank ? '' : ' '}${line}`)
|
||||
exit()
|
||||
// Only remove escaping from a leading callout marker, never body literals.
|
||||
// 仅删除前导标注标记的转义,绝不删除正文文字。
|
||||
return result.replace(/^(> )\\\[!([\w-]+)\\?\]/, '$1[!$2]')
|
||||
} },
|
||||
}))
|
||||
@@ -36,8 +36,7 @@ function calloutMarkers(doc: ProseNode) {
|
||||
return markers
|
||||
}
|
||||
|
||||
// Keep native blockquotes in the document: typing, undo and Markdown serialization
|
||||
// remain Milkdown transactions; the view never rewrites a user's callout source.
|
||||
// 在文档中保留本机块引用:键入、撤消和 Markdown 序列化保留 Milkdown 事务;该视图永远不会重写用户的标注源。
|
||||
export const calloutPlugin = $prose(() => new Plugin({
|
||||
props: {
|
||||
decorations(state) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Mirror changed language labels without rescanning every code block on each DOM mutation. */
|
||||
/** 镜像更改的语言标签,无需重新扫描每个 DOM 突变上的每个代码块。 */
|
||||
export function installCodeBlockLabels(root: HTMLElement): () => void {
|
||||
const sync = (block: HTMLElement) => {
|
||||
const label = block.querySelector('.language-button')?.textContent?.trim() || 'Plain text'
|
||||
@@ -13,7 +13,7 @@ export function installCodeBlockLabels(root: HTMLElement): () => void {
|
||||
const changed = new Set<HTMLElement>()
|
||||
for (const record of records) {
|
||||
const element = record.target instanceof Element ? record.target : record.target.parentElement
|
||||
// CodeMirror viewport/text changes do not change the footer's language.
|
||||
// CodeMirror 视口/文本更改不会更改页脚的语言。
|
||||
const label = element?.closest('.language-button')
|
||||
const block = label?.closest<HTMLElement>('.milkdown-code-block')
|
||||
if (block) changed.add(block)
|
||||
|
||||
@@ -8,7 +8,7 @@ export const headingFoldKey = new PluginKey<Set<number>>('heading-folding')
|
||||
type Section = { from: number; body: number; end: number; level: number }
|
||||
const sectionCache = new WeakMap<Node, Section[]>()
|
||||
const decorationCache = new WeakMap<Node, WeakMap<Set<number>, DecorationSet>>()
|
||||
/** A section ends at the next sibling heading of the same or a higher rank. */
|
||||
/** 节以相同或更高级别的下一个同级标题结束。 */
|
||||
export function headingSections(doc: Node): Section[] {
|
||||
const cached = sectionCache.get(doc)
|
||||
if (cached) return cached
|
||||
@@ -79,7 +79,7 @@ export const headingFoldingPlugin = $prose(() => new Plugin<Set<number>>({
|
||||
const result = tr.mapping.mapResult(old, 1)
|
||||
if (!result.deleted && positions.has(result.pos)) mapped.add(result.pos)
|
||||
}
|
||||
// Outline jumps, find and keyboard navigation must never leave a hidden caret.
|
||||
// 轮廓跳转、查找和键盘导航绝不能留下隐藏的插入符号。
|
||||
if (tr.selectionSet || tr.docChanged) {
|
||||
for (const section of sections) if (tr.selection.to >= section.body && tr.selection.from < section.end) mapped.delete(section.from)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ function reconcile(view: EditorView) {
|
||||
const { $from } = view.state.selection
|
||||
if (!$from.parent.isTextblock || $from.parent.type.spec.code) return
|
||||
const text = $from.parent.textBetween(0, $from.parent.content.size, '\n', '\ufffc')
|
||||
// Also inspect the closing delimiter AFTER the caret: users commonly type
|
||||
// a pair of backticks first, move left, and then fill in the code.
|
||||
// 还要检查结束分隔符 AFTER 插入符号:用户通常首先键入一对反引号,向左移动,然后填写代码。
|
||||
const spans = /(^|[^\\`])`([^`\n\ufffc]+)`(?!`)/g
|
||||
let candidate: { start: number; end: number } | undefined
|
||||
for (const match of text.matchAll(spans)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Generated by scripts/generate-language-icons.mjs. VSCode Icons (MIT); see language-icons-LICENSE.txt. */
|
||||
/* 由 scripts/generate-language-icons.mjs 自动生成。VSCode Icons 采用 MIT 许可证;详情见 language-icons-LICENSE.txt。 */
|
||||
.milkdown-host .language-list-item[data-language] { display: flex; align-items: center; gap: 8px; }
|
||||
.milkdown-host .language-list-item[data-language]::before { content: ''; flex: 0 0 20px; width: 20px; height: 20px; background: center / contain no-repeat url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23c5c5c5%22%20d%3D%22M20.414%202H5v28h22V8.586ZM7%2028V4h12v6h6v18Z%22%2F%3E%3C%2Fsvg%3E"); }
|
||||
.milkdown-host .language-list-item[data-language][data-language="actionscript-3"]::before { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23c41718%22%20d%3D%22M2%2015.281c1.918%200%202.11-1.055%202.11-1.918a17%2017%200%200%200-.192-2.205a19%2019%200%200%201-.192-2.205c0-2.4%201.63-3.452%203.836-3.452h.575v1.437h-.479c-1.534%200-2.11.767-2.11%202.205a14%2014%200%200%200%20.192%201.918a14%2014%200%200%201%20.192%202.014c0%201.726-.671%202.493-1.918%202.877v.1c1.151.288%201.918%201.151%201.918%202.877a14%2014%200%200%201-.192%202.014a13%2013%200%200%200-.192%201.918c0%201.438.575%202.3%202.11%202.3h.479V26.6h-.575c-2.205%200-3.836-.959-3.836-3.644a19%2019%200%200%201%20.192-2.205a16%2016%200%200%200%20.192-2.11c0-.863-.288-1.918-2.11-1.918Z%22%2F%3E%3Cpath%20fill%3D%22%23c41718%22%20d%3D%22M9.479%2018.062L8.233%2021.8H6.6l4.03-11.889h1.822L16.479%2021.8h-1.534L13.7%2018.062Zm3.932-1.151l-1.151-3.452a9.4%209.4%200%200%201-.575-2.205c-.192.671-.384%201.438-.575%202.11l-1.151%203.451h3.452Zm4.507%203.068a5.94%205.94%200%200%200%202.781.767c1.534%200%202.493-.863%202.493-2.014s-.671-1.726-2.205-2.4c-1.918-.671-3.164-1.726-3.164-3.356c0-1.822%201.534-3.26%203.836-3.26a5.14%205.14%200%200%201%202.589.575l-.384%201.247a5.5%205.5%200%200%200-2.3-.479c-1.63%200-2.205.959-2.205%201.822c0%201.151.767%201.63%202.4%202.3c2.014.767%203.068%201.726%203.068%203.452c0%201.822-1.342%203.452-4.123%203.452a5.8%205.8%200%200%201-3.068-.767Z%22%2F%3E%3Cpath%20fill%3D%22%23c41718%22%20d%3D%22M30%2016.623c-1.918%200-2.11%201.151-2.11%201.918a16%2016%200%200%200%20.192%202.11a16%2016%200%200%201%20.192%202.205c0%202.685-1.63%203.644-3.836%203.644h-.575v-1.438h.479c1.438%200%202.11-.863%202.11-2.3a13%2013%200%200%200-.192-1.918a14%2014%200%200%201-.192-2.014c0-1.726.767-2.589%201.918-2.877v-.1c-1.151-.288-1.918-1.151-1.918-2.877a14%2014%200%200%201%20.192-2.014a13%2013%200%200%200%20.192-1.918c0-1.438-.575-2.205-2.11-2.3h-.479V5.4h.575c2.205%200%203.836%201.055%203.836%203.452a17%2017%200%200%201-.192%202.205a17%2017%200%200%200-.192%202.205c0%20.959.288%201.918%202.11%201.918Z%22%2F%3E%3C%2Fsvg%3E"); }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Promote menus to the top layer; only open menus need scroll measurements. */
|
||||
/** 将菜单提升到顶层;只有打开的菜单才需要滚动测量。 */
|
||||
export function installLanguagePickerPopover(root: HTMLElement): () => void {
|
||||
const menus = new Set<HTMLElement>()
|
||||
const openMenus = new Set<HTMLElement>()
|
||||
@@ -55,7 +55,7 @@ export function installLanguagePickerPopover(root: HTMLElement): () => void {
|
||||
}
|
||||
root.querySelectorAll<HTMLElement>('.language-picker').forEach(sync)
|
||||
observer.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-expanded'] })
|
||||
// The outer editor viewport is an ancestor of root, so listen in capture on the document.
|
||||
// 外部编辑器视口是根的祖先,因此在文档上侦听捕获。
|
||||
document.addEventListener('scroll', positionOpenMenus, { capture: true, passive: true })
|
||||
window.addEventListener('resize', positionOpenMenus)
|
||||
return () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Editable anchors need explicit navigation; plain clicks keep editing the link. */
|
||||
/** 可编辑锚点需要显式导航;简单的点击即可继续编辑链接。 */
|
||||
export function installLinkNavigation(root: HTMLElement): () => void {
|
||||
const navigate = (event: MouseEvent) => {
|
||||
if (event.button !== 0 || !(event.ctrlKey || event.metaKey) || event.altKey) return
|
||||
@@ -7,7 +7,7 @@ export function installLinkNavigation(root: HTMLElement): () => void {
|
||||
if (!link || !root.contains(link)) return
|
||||
const href = link.getAttribute('href')?.trim()
|
||||
if (!href) return
|
||||
// Consume modified clicks before Milkdown's link editor or native navigation.
|
||||
// 在 Milkdown 的链接编辑器或本机导航之前消耗修改的点击。
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
let url: URL
|
||||
|
||||
@@ -6,9 +6,7 @@ import { appendDiagramControls } from '@/utils/diagramControls'
|
||||
|
||||
let previewId = 0
|
||||
export function createMermaidPreview(source: string, dark: boolean, applyPreview: (value: HTMLElement) => void, kind = 'mermaid', themeId = 'light'): HTMLElement {
|
||||
// Each revision owns its element, so a slow render cannot replace newer content.
|
||||
// Milkdown sanitizes Element input to its inner HTML; retain the revision
|
||||
// marker and controls inside an otherwise disposable envelope.
|
||||
// 每个修订版本都拥有其元素,因此缓慢的渲染无法替换较新的内容。 Milkdown 清理其内部 HTML 的 Element 输入;将修订标记和控件保留在一次性信封内。
|
||||
const envelope = document.createElement('div')
|
||||
const container = document.createElement('div')
|
||||
envelope.append(container)
|
||||
@@ -19,12 +17,10 @@ export function createMermaidPreview(source: string, dark: boolean, applyPreview
|
||||
container.textContent = t('正在渲染图表…', 'Rendering diagram…')
|
||||
const publish = async () => {
|
||||
await nextTick()
|
||||
// Milkdown sanitizes and copies this element. Publish only if its revision
|
||||
// still exists; edits, language changes and unmounts remove the old marker.
|
||||
// Milkdown 清理并复制该元素。仅当其修订版本仍然存在时才发布;编辑、语言更改和卸载会删除旧标记。
|
||||
const visible = document.getElementById(container.id)
|
||||
if (visible) {
|
||||
// PreviewPanel copies HTML instead of retaining the supplied element.
|
||||
// Update the current copy through Milkdown's reactive callback.
|
||||
// PreviewPanel 复制 HTML,而不是保留提供的元素。通过 Milkdown 的反应式回调更新当前副本。
|
||||
applyPreview(envelope.cloneNode(true) as HTMLElement)
|
||||
}
|
||||
}
|
||||
@@ -38,7 +34,7 @@ export function createMermaidPreview(source: string, dark: boolean, applyPreview
|
||||
void publish()
|
||||
return
|
||||
}
|
||||
// Mermaid runs in strict mode; Milkdown sanitizes the preview before insertion.
|
||||
// Mermaid以严格模式运行; Milkdown 在插入之前清理预览。
|
||||
container.innerHTML = result.svg
|
||||
appendDiagramControls(container)
|
||||
if (result.warnings.length) { const warning = document.createElement('p'); warning.textContent = result.warnings.join('\n'); warning.setAttribute('role', 'status'); container.append(warning) }
|
||||
|
||||
@@ -7,8 +7,7 @@ type CodeTheme = 'github-light' | 'github-dark'
|
||||
|
||||
export async function shikiLanguage(language: string, theme: CodeTheme): Promise<LanguageSupport> {
|
||||
const tokenize = await getCodeTokenizer(theme, language)
|
||||
// Milkdown recreates off-screen CodeMirror views. Reuse immutable ranges for
|
||||
// identical code within this language/theme, with a bounded retention budget.
|
||||
// Milkdown 重新创建屏幕外 CodeMirror 视图。在该语言/主题内重复使用相同代码的不可变范围,并保留有限的预算。
|
||||
const cache = new Map<string, DecorationSet>()
|
||||
let cachedCharacters = 0
|
||||
const highlights = ViewPlugin.fromClass(class {
|
||||
@@ -53,7 +52,7 @@ export async function shikiLanguage(language: string, theme: CodeTheme): Promise
|
||||
}
|
||||
}, { decorations: value => value.decorations })
|
||||
|
||||
// CodeMirror still owns selection, input and undo. Shiki owns token colors.
|
||||
// CodeMirror仍然拥有选择、输入和撤消功能。 Shiki拥有令牌颜色。
|
||||
const parser = StreamLanguage.define({ token(stream) { stream.skipToEnd(); return null } })
|
||||
return new LanguageSupport(parser, highlights)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user