feat(i18n): add English and Simplified Chinese
This commit is contained in:
1 parent
38c4c8b781
commit
1e9e0678ee
149 files changed
+5356
-1144
No files matched your search
@@ -7,6 +7,7 @@ import { StateField } from '@codemirror/state'
|
||||
import { Decoration, EditorView } from '@codemirror/view'
|
||||
import { codeContentRange, fenceLanguage } from './codeblocks'
|
||||
import { editorFocused, setEditorFocus } from './focus'
|
||||
import { editorLocale } from './locale'
|
||||
import { CodeRendererWidget, MathWidget } from './widgets'
|
||||
|
||||
// 公式块与 Mermaid 图表:未编辑时整块替换为渲染结果;光标在块内时显示源码,并在下方实时预览。
|
||||
@@ -50,7 +51,8 @@ export const renderedBlocks = StateField.define<DecorationSet>({
|
||||
create: buildBlocks,
|
||||
update(value, tr) {
|
||||
if (
|
||||
tr.docChanged
|
||||
tr.startState.facet(editorLocale) !== tr.state.facet(editorLocale)
|
||||
|| tr.docChanged
|
||||
|| tr.selection
|
||||
|| tr.effects.some(e => e.is(setEditorFocus) || e.is(pluginRefresh))
|
||||
|| syntaxTree(tr.startState) !== syntaxTree(tr.state)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t as translate } from '@@/i18n/runtime'
|
||||
/* eslint-disable no-control-regex -- 粘贴链接必须识别并移除协议中的控制字符。 */
|
||||
import TurndownService from 'turndown'
|
||||
|
||||
@@ -14,7 +15,7 @@ export async function htmlToMarkdown(html: string, importImage: (url: string) =>
|
||||
try {
|
||||
node.setAttribute(attribute, new URL(node.getAttribute(attribute)!, base).href)
|
||||
} catch (error) {
|
||||
onError(new Error('来源基址无效,已保留原链接', { cause: error }))
|
||||
onError(new Error(translate('workspace.invalid_source_base_url_original_f0ec05'), { cause: error }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Extension } from '@codemirror/state'
|
||||
import type { KeyBinding, ViewUpdate } from '@codemirror/view'
|
||||
import type { MarkdownParser } from '@lezer/markdown'
|
||||
import { locale } from '@@/i18n/runtime'
|
||||
import { typoraExtensions } from '@@/markdown/extensions'
|
||||
import { editorExtensions, markdownSyntax, reportEditorFailure } from '@@/plugins/registry'
|
||||
import { defaultKeymap, history, indentLess, indentMore, selectAll, selectParentSyntax, toggleComment } from '@codemirror/commands'
|
||||
@@ -12,6 +13,7 @@ import { renderedBlocks } from './blocks'
|
||||
import { codeBlockHighlight } from './codeblocks'
|
||||
import { editorFocus } from './focus'
|
||||
import { livePreview, openLink, resolveImageUrl } from './livepreview'
|
||||
import { editorLocale, localeCompartment } from './locale'
|
||||
import { defaultEditorKeys } from './shortcuts'
|
||||
import { tableEditing } from './table'
|
||||
import { markdownTheme } from './theme'
|
||||
@@ -56,6 +58,7 @@ export interface EditorStateOptions {
|
||||
export function createEditorState(opts: EditorStateOptions): EditorState {
|
||||
const extensions: Extension[] = [
|
||||
history(),
|
||||
localeCompartment.of(editorLocale.of(locale.value)),
|
||||
drawSelection(),
|
||||
EditorView.lineWrapping,
|
||||
// 不用 lang-markdown 的 markdown():它会静态引入 lang-html/css/javascript 用于嵌套 HTML 高亮,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { syntaxTree } from '@codemirror/language'
|
||||
import { Facet, StateEffect } from '@codemirror/state'
|
||||
import { Decoration, ViewPlugin } from '@codemirror/view'
|
||||
import { codeContentRange, fencedCodeAt, fenceLanguage } from './codeblocks'
|
||||
import { editorLocale } from './locale'
|
||||
import {
|
||||
BulletWidget,
|
||||
CheckboxWidget,
|
||||
@@ -363,6 +364,7 @@ const livePreviewPlugin = ViewPlugin.fromClass(
|
||||
if (
|
||||
this.stale
|
||||
|| u.transactions.some(tr => tr.effects.some(effect => effect.is(refreshImages)))
|
||||
|| u.startState.facet(editorLocale) !== u.state.facet(editorLocale)
|
||||
|| u.docChanged
|
||||
|| u.viewportChanged
|
||||
|| u.selectionSet
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { applyLocale } from '@@/i18n/runtime'
|
||||
import { undo, undoDepth } from '@codemirror/commands'
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import { createEditorState, createEditorView } from './index'
|
||||
import { editorLocale, localeCompartment } from './locale'
|
||||
|
||||
const views: ReturnType<typeof createEditorView>[] = []
|
||||
afterEach(() => {
|
||||
views.splice(0).forEach(view => view.destroy())
|
||||
document.body.replaceChildren()
|
||||
applyLocale('en')
|
||||
})
|
||||
|
||||
it('切换语言更新表格和代码控件,保留正文、选区、撤销历史及缓存状态', () => {
|
||||
applyLocale('en')
|
||||
const doc = '| a | b |\n| --- | --- |\n| 1 | 2 |\n\n```js\nlet x = 1\n```\n\nEnd'
|
||||
const host = document.createElement('div')
|
||||
document.body.append(host)
|
||||
const view = createEditorView(host, createEditorState({ doc }))
|
||||
views.push(view)
|
||||
view.dispatch({ changes: { from: doc.length, insert: '!' }, selection: { anchor: doc.length + 1 } })
|
||||
const content = view.state.doc.toString()
|
||||
const selection = view.state.selection
|
||||
const history = undoDepth(view.state)
|
||||
expect(view.dom.querySelector('.cm-rm-code-copy')?.getAttribute('title')).toBe('Copy code')
|
||||
expect(view.dom.querySelector('.cm-rm-table-toolbar')?.textContent).toContain('Source')
|
||||
let cached = createEditorState({ doc: 'Background note' })
|
||||
applyLocale('zh-CN')
|
||||
const effects = localeCompartment.reconfigure(editorLocale.of('zh-CN'))
|
||||
view.dispatch({ effects })
|
||||
cached = cached.update({ effects }).state
|
||||
expect(view.state.doc.toString()).toBe(content)
|
||||
expect(view.state.selection.eq(selection)).toBe(true)
|
||||
expect(undoDepth(view.state)).toBe(history)
|
||||
expect(cached.doc.toString()).toBe('Background note')
|
||||
expect(cached.facet(editorLocale)).toBe('zh-CN')
|
||||
expect(view.dom.querySelector('.cm-rm-code-copy')?.getAttribute('title')).toBe('复制代码')
|
||||
expect(view.dom.querySelector('.cm-rm-table-toolbar')?.textContent).toContain('源码')
|
||||
expect(undo(view)).toBe(true)
|
||||
expect(view.state.doc.toString()).toBe(doc)
|
||||
})
|
||||
|
||||
it('空 Mermaid 提示随语言双向更新,不改正文或撤销历史', async () => {
|
||||
applyLocale('en')
|
||||
const doc = '```mermaid\n\n```\n\nEnd'
|
||||
const host = document.createElement('div')
|
||||
document.body.append(host)
|
||||
const view = createEditorView(host, createEditorState({ doc }))
|
||||
views.push(view)
|
||||
view.dispatch({ changes: { from: doc.length, insert: '!' } })
|
||||
const content = view.state.doc.toString()
|
||||
const history = undoDepth(view.state)
|
||||
await vi.waitFor(() => expect(view.dom.querySelector('.cm-rm-mermaid')?.textContent).toBe('Empty diagram'))
|
||||
for (const [language, label] of [['zh-CN', '空图表'], ['en', 'Empty diagram']] as const) {
|
||||
applyLocale(language)
|
||||
view.dispatch({ effects: localeCompartment.reconfigure(editorLocale.of(language)) })
|
||||
await vi.waitFor(() => expect(view.dom.querySelector('.cm-rm-mermaid')?.textContent).toBe(label))
|
||||
expect(view.state.doc.toString()).toBe(content)
|
||||
expect(undoDepth(view.state)).toBe(history)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
import { locale } from '@@/i18n/runtime'
|
||||
import { Compartment, Facet } from '@codemirror/state'
|
||||
|
||||
export const editorLocale = Facet.define<string, string>({ combine: values => values[0] ?? locale.value })
|
||||
export const localeCompartment = new Compartment()
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { EditorState, Extension, TransactionSpec } from '@codemirror/state'
|
||||
import { t as translate } from '@@/i18n/runtime'
|
||||
import { isolateHistory } from '@codemirror/commands'
|
||||
import { Facet, StateEffect, StateField } from '@codemirror/state'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
@@ -78,7 +79,7 @@ function insertHTML(view: EditorView, html: string, text: string, opts: PasteIma
|
||||
})
|
||||
}
|
||||
void import('./html-paste').then(({ htmlToMarkdown }) => htmlToMarkdown(html, opts.importImage, opts.onError)).catch((error) => {
|
||||
opts.onError(new Error('网页转换失败,已保留剪贴板纯文本', { cause: error }))
|
||||
opts.onError(new Error(translate('workspace.could_not_convert_the_web_4f0e6a'), { cause: error }))
|
||||
return text
|
||||
}).then(insert).catch(opts.onError).finally(() => opts.onPending(-1))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Command, KeyBinding } from '@codemirror/view'
|
||||
import { t as translate } from '@@/i18n/runtime'
|
||||
import { typoraKeys } from '@@/utils/typora-keys'
|
||||
import { indentLess, indentMore, redo, selectAll, undo } from '@codemirror/commands'
|
||||
import { selectCodeBlock } from './codeblocks'
|
||||
@@ -6,20 +7,56 @@ import { insertLink, toggleBlock, toggleBold, toggleCode, toggleHeading, toggleI
|
||||
import { toggleSelectionMark, toggleUnderline } from './inline-styles'
|
||||
|
||||
export const editorCommands: { id: string, title: string, keys: string[], run: Command }[] = [
|
||||
{ id: 'editor.bold', title: '粗体', keys: typoraKeys('editor.bold'), run: toggleBold },
|
||||
{ id: 'editor.italic', title: '斜体', keys: typoraKeys('editor.italic'), run: toggleItalic },
|
||||
{ id: 'editor.underline', title: '下划线', keys: typoraKeys('editor.underline'), run: toggleUnderline },
|
||||
{ id: 'editor.strike', title: '删除线', keys: typoraKeys('editor.strike'), run: toggleSelectionMark('Strikethrough') },
|
||||
{ id: 'editor.highlight', title: '高亮', keys: [], run: toggleSelectionMark('Highlight') },
|
||||
{ id: 'editor.code', title: '行内代码', keys: typoraKeys('editor.code'), run: toggleCode },
|
||||
...(['task', 'bullet', 'ordered', 'quote'] as const).map(kind => ({ id: `editor.${kind}`, title: { task: '任务列表', bullet: '无序列表', ordered: '有序列表', quote: '引用' }[kind], keys: typoraKeys(`editor.${kind}`), run: toggleBlock(kind) })),
|
||||
{ id: 'editor.link', title: '插入链接', keys: typoraKeys('editor.link'), run: insertLink },
|
||||
{ id: 'editor.undo', title: '撤销', keys: typoraKeys('editor.undo'), run: undo },
|
||||
{ id: 'editor.redo', title: '重做', keys: typoraKeys('editor.redo'), run: redo },
|
||||
{ id: 'editor.indent', title: '增加缩进', keys: typoraKeys('editor.indent'), run: indentMore },
|
||||
{ id: 'editor.outdent', title: '减少缩进', keys: typoraKeys('editor.outdent'), run: indentLess },
|
||||
{ id: 'editor.selectAll', title: '全选(代码块内优先选中代码块)', keys: typoraKeys('editor.selectAll'), run: view => selectCodeBlock(view) || selectAll(view) },
|
||||
...Array.from({ length: 6 }, (_, i) => ({ id: `editor.heading${i + 1}`, title: `设置 ${i + 1} 级标题`, keys: typoraKeys(`editor.heading${i + 1}`), run: toggleHeading(i + 1) })),
|
||||
{ id: 'editor.bold', get title() {
|
||||
return translate('workspace.bold_f6da93')
|
||||
}, keys: typoraKeys('editor.bold'), run: toggleBold },
|
||||
{ id: 'editor.italic', get title() {
|
||||
return translate('workspace.italic_217901')
|
||||
}, keys: typoraKeys('editor.italic'), run: toggleItalic },
|
||||
{ id: 'editor.underline', get title() {
|
||||
return translate('workspace.underline_d3b4a2')
|
||||
}, keys: typoraKeys('editor.underline'), run: toggleUnderline },
|
||||
{ id: 'editor.strike', get title() {
|
||||
return translate('workspace.strikethrough_5b0508')
|
||||
}, keys: typoraKeys('editor.strike'), run: toggleSelectionMark('Strikethrough') },
|
||||
{ id: 'editor.highlight', get title() {
|
||||
return translate('workspace.highlight_05f954')
|
||||
}, keys: [], run: toggleSelectionMark('Highlight') },
|
||||
{ id: 'editor.code', get title() {
|
||||
return translate('workspace.inline_code_6711d9')
|
||||
}, keys: typoraKeys('editor.code'), run: toggleCode },
|
||||
...(['task', 'bullet', 'ordered', 'quote'] as const).map(kind => ({ id: `editor.${kind}`, get title() {
|
||||
return { get task() {
|
||||
return translate('workspace.task_list_c5ced4')
|
||||
}, get bullet() {
|
||||
return translate('workspace.bulleted_list_9ee577')
|
||||
}, get ordered() {
|
||||
return translate('workspace.numbered_list_5d0bb2')
|
||||
}, get quote() {
|
||||
return translate('workspace.block_quote')
|
||||
} }[kind]
|
||||
}, keys: typoraKeys(`editor.${kind}`), run: toggleBlock(kind) })),
|
||||
{ id: 'editor.link', get title() {
|
||||
return translate('workspace.insert_link_1ad3ca')
|
||||
}, keys: typoraKeys('editor.link'), run: insertLink },
|
||||
{ id: 'editor.undo', get title() {
|
||||
return translate('workspace.undo_926a50')
|
||||
}, keys: typoraKeys('editor.undo'), run: undo },
|
||||
{ id: 'editor.redo', get title() {
|
||||
return translate('workspace.redo_03717b')
|
||||
}, keys: typoraKeys('editor.redo'), run: redo },
|
||||
{ id: 'editor.indent', get title() {
|
||||
return translate('workspace.increase_indent_cfd42c')
|
||||
}, keys: typoraKeys('editor.indent'), run: indentMore },
|
||||
{ id: 'editor.outdent', get title() {
|
||||
return translate('workspace.decrease_indent_efd955')
|
||||
}, keys: typoraKeys('editor.outdent'), run: indentLess },
|
||||
{ id: 'editor.selectAll', get title() {
|
||||
return translate('workspace.select_all_select_code_inside_8dfb27')
|
||||
}, keys: typoraKeys('editor.selectAll'), run: view => selectCodeBlock(view) || selectAll(view) },
|
||||
...Array.from({ length: 6 }, (_, i) => ({ id: `editor.heading${i + 1}`, get title() {
|
||||
return translate('workspace.heading_a4f510', { p0: i + 1 })
|
||||
}, keys: typoraKeys(`editor.heading${i + 1}`), run: toggleHeading(i + 1) })),
|
||||
]
|
||||
|
||||
export const defaultEditorKeys: KeyBinding[] = editorCommands.flatMap(command => command.keys.map(key => ({ key: key.replaceAll('+', '-').replace(/-[A-Z]$/, letter => letter.toLowerCase()), run: command.run, preventDefault: true })))
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { EditorState, Range } from '@codemirror/state'
|
||||
import type { DecorationSet } from '@codemirror/view'
|
||||
import type { SyntaxNode } from '@lezer/common'
|
||||
import { locale, t as translate } from '@@/i18n/runtime'
|
||||
import { renderInlineMarkdown } from '@@/markdown/html'
|
||||
import { hydrate } from '@@/markdown/render'
|
||||
import { isolateHistory, redo, undo } from '@codemirror/commands'
|
||||
@@ -8,6 +9,7 @@ import { syntaxTree } from '@codemirror/language'
|
||||
import { StateField } from '@codemirror/state'
|
||||
import { Decoration, EditorView, WidgetType } from '@codemirror/view'
|
||||
import { editorFocused, setEditorFocus } from './focus'
|
||||
import { editorLocale } from './locale'
|
||||
|
||||
type Align = '' | 'left' | 'center' | 'right'
|
||||
|
||||
@@ -116,6 +118,7 @@ interface WidgetState {
|
||||
const widgetStates = new WeakMap<HTMLElement, WidgetState>()
|
||||
|
||||
class TableWidget extends WidgetType {
|
||||
readonly locale = locale.value
|
||||
readonly key: string
|
||||
|
||||
constructor(readonly model: TableModel) {
|
||||
@@ -124,7 +127,7 @@ class TableWidget extends WidgetType {
|
||||
}
|
||||
|
||||
eq(other: TableWidget) {
|
||||
return other.key === this.key
|
||||
return other.locale === this.locale && other.key === this.key
|
||||
}
|
||||
|
||||
get estimatedHeight() {
|
||||
@@ -134,6 +137,7 @@ class TableWidget extends WidgetType {
|
||||
toDOM(view: EditorView) {
|
||||
const wrap = document.createElement('div')
|
||||
wrap.className = 'cm-rm-table-widget'
|
||||
wrap.lang = this.locale
|
||||
widgetStates.set(wrap, { model: this.model, current: { r: 0, c: 0 }, pendingFocus: null })
|
||||
// 横向滚动放在内层,避免绝对定位的工具栏被裁剪
|
||||
const scroller = document.createElement('div')
|
||||
@@ -146,6 +150,10 @@ class TableWidget extends WidgetType {
|
||||
updateDOM(dom: HTMLElement, view: EditorView) {
|
||||
const st = widgetStates.get(dom)
|
||||
if (!st) return false
|
||||
if (dom.lang !== this.locale) {
|
||||
dom.querySelector('.cm-rm-table-toolbar')!.replaceWith(this.toolbar(view, dom))
|
||||
dom.lang = this.locale
|
||||
}
|
||||
const old = st.model
|
||||
st.model = this.model
|
||||
const sameShape
|
||||
@@ -230,13 +238,13 @@ class TableWidget extends WidgetType {
|
||||
sep.className = 'cm-rm-sep'
|
||||
tools.append(sep)
|
||||
}
|
||||
button(ICONS.addRow, '行', '在下方插入行', () => insertRow(view, wrap))
|
||||
button(ICONS.addColumn, '列', '在右侧插入列', () => insertColumn(view, wrap))
|
||||
button(ICONS.addRow, translate('workspace.line_4cc7b0'), translate('workspace.insert_row_below_7c131f'), () => insertRow(view, wrap))
|
||||
button(ICONS.addColumn, translate('workspace.column_c06d21'), translate('workspace.insert_column_to_the_right_8ae98f'), () => insertColumn(view, wrap))
|
||||
separator()
|
||||
button(ICONS.deleteRow, '行', '删除当前行', () => deleteRow(view, wrap), true)
|
||||
button(ICONS.deleteColumn, '列', '删除当前列', () => deleteColumn(view, wrap), true)
|
||||
button(ICONS.deleteRow, translate('workspace.line_4cc7b0'), translate('workspace.delete_current_row_3bc4de'), () => deleteRow(view, wrap), true)
|
||||
button(ICONS.deleteColumn, translate('workspace.column_c06d21'), translate('workspace.delete_current_column_435bde'), () => deleteColumn(view, wrap), true)
|
||||
separator()
|
||||
button(ICONS.source, '源码', '以 Markdown 源码编辑', () => {
|
||||
button(ICONS.source, translate('workspace.source_41b50a'), translate('workspace.edit_as_markdown_source_9375e8'), () => {
|
||||
const model = currentModel(view, wrap)
|
||||
if (!model) return
|
||||
view.dispatch({ selection: { anchor: model.from } })
|
||||
@@ -439,7 +447,8 @@ export const tableField = StateField.define<DecorationSet>({
|
||||
create: buildTables,
|
||||
update(value, tr) {
|
||||
if (
|
||||
tr.docChanged
|
||||
tr.startState.facet(editorLocale) !== tr.state.facet(editorLocale)
|
||||
|| tr.docChanged
|
||||
|| tr.selection
|
||||
|| tr.effects.some(e => e.is(setEditorFocus))
|
||||
|| syntaxTree(tr.startState) !== syntaxTree(tr.state)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OwnedRenderer } from '@@/plugins/registry'
|
||||
import { locale, t as translate } from '@@/i18n/runtime'
|
||||
import { emojiFor, renderMath } from '@@/markdown/render'
|
||||
import { mountContent } from '@@/plugins/mount'
|
||||
import { copyText } from '@@/utils/clipboard'
|
||||
@@ -66,6 +67,7 @@ const CHECK_ICON
|
||||
|
||||
// 代码块右上角:语言标签(未激活时)+ 复制按钮
|
||||
export class CodeHeaderWidget extends WidgetType {
|
||||
readonly locale = locale.value
|
||||
constructor(
|
||||
readonly lang: string,
|
||||
readonly showLang: boolean,
|
||||
@@ -76,7 +78,7 @@ export class CodeHeaderWidget extends WidgetType {
|
||||
}
|
||||
|
||||
eq(other: CodeHeaderWidget) {
|
||||
return other.lang === this.lang && other.showLang === this.showLang
|
||||
return other.locale === this.locale && other.lang === this.lang && other.showLang === this.showLang
|
||||
}
|
||||
|
||||
toDOM(view: EditorView) {
|
||||
@@ -91,7 +93,7 @@ export class CodeHeaderWidget extends WidgetType {
|
||||
const btn = document.createElement('button')
|
||||
btn.type = 'button'
|
||||
btn.className = 'cm-rm-code-copy'
|
||||
btn.title = '复制代码'
|
||||
btn.title = translate('workspace.copy_code_420c20')
|
||||
btn.innerHTML = COPY_ICON
|
||||
btn.addEventListener('mousedown', e => e.preventDefault())
|
||||
btn.addEventListener('click', async () => {
|
||||
@@ -175,6 +177,7 @@ function showRenderError(el: HTMLElement, err: unknown) {
|
||||
* 否则是替换源码的渲染结果,点击后光标移到源码 editOffset 处
|
||||
*/
|
||||
export class MathWidget extends WidgetType {
|
||||
readonly locale = locale.value
|
||||
constructor(
|
||||
readonly tex: string,
|
||||
readonly display: boolean,
|
||||
@@ -184,7 +187,7 @@ export class MathWidget extends WidgetType {
|
||||
}
|
||||
|
||||
eq(other: MathWidget) {
|
||||
return other.tex === this.tex && other.display === this.display && other.editOffset === this.editOffset
|
||||
return other.locale === this.locale && other.tex === this.tex && other.display === this.display && other.editOffset === this.editOffset
|
||||
}
|
||||
|
||||
toDOM(view: EditorView) {
|
||||
@@ -205,7 +208,7 @@ export class MathWidget extends WidgetType {
|
||||
|
||||
private render(el: HTMLElement, view: EditorView) {
|
||||
if (!this.tex.trim()) {
|
||||
el.textContent = '空公式'
|
||||
el.textContent = translate('workspace.empty_formula_fd803b')
|
||||
el.classList.add('cm-rm-placeholder')
|
||||
return
|
||||
}
|
||||
@@ -224,6 +227,7 @@ const rendererMounts = new WeakMap<HTMLElement, () => void>()
|
||||
|
||||
/** 通用代码块预览;editOffset 与公式组件使用相同的点击编辑约定。 */
|
||||
export class CodeRendererWidget extends WidgetType {
|
||||
readonly locale = locale.value
|
||||
constructor(
|
||||
readonly code: string,
|
||||
readonly editOffset: number | null,
|
||||
@@ -235,7 +239,7 @@ export class CodeRendererWidget extends WidgetType {
|
||||
}
|
||||
|
||||
eq(other: CodeRendererWidget) {
|
||||
return other.code === this.code && other.editOffset === this.editOffset && other.language === this.language && other.path === this.path && other.renderer === this.renderer
|
||||
return other.locale === this.locale && other.code === this.code && other.editOffset === this.editOffset && other.language === this.language && other.path === this.path && other.renderer === this.renderer
|
||||
}
|
||||
|
||||
toDOM(view: EditorView) {
|
||||
@@ -313,6 +317,7 @@ export class EmojiWidget extends WidgetType {
|
||||
}
|
||||
|
||||
export class FootnoteRefWidget extends WidgetType {
|
||||
readonly locale = locale.value
|
||||
constructor(
|
||||
readonly label: string,
|
||||
readonly definition: string | undefined,
|
||||
@@ -321,14 +326,14 @@ export class FootnoteRefWidget extends WidgetType {
|
||||
}
|
||||
|
||||
eq(other: FootnoteRefWidget) {
|
||||
return other.label === this.label && other.definition === this.definition
|
||||
return other.locale === this.locale && other.label === this.label && other.definition === this.definition
|
||||
}
|
||||
|
||||
toDOM(view: EditorView) {
|
||||
const el = document.createElement('sup')
|
||||
el.className = 'cm-rm-footnote-ref'
|
||||
el.textContent = this.label
|
||||
el.title = this.definition ?? '未找到脚注定义'
|
||||
el.title = this.definition ?? translate('workspace.footnote_definition_not_found_1c0993')
|
||||
editOnMousedown(el, view, 2)
|
||||
return el
|
||||
}
|
||||
@@ -341,19 +346,20 @@ export interface TocEntry {
|
||||
}
|
||||
|
||||
export class TocWidget extends WidgetType {
|
||||
readonly locale = locale.value
|
||||
constructor(readonly entries: TocEntry[]) {
|
||||
super()
|
||||
}
|
||||
|
||||
eq(other: TocWidget) {
|
||||
return JSON.stringify(other.entries) === JSON.stringify(this.entries)
|
||||
return other.locale === this.locale && JSON.stringify(other.entries) === JSON.stringify(this.entries)
|
||||
}
|
||||
|
||||
toDOM(view: EditorView) {
|
||||
const el = document.createElement('div')
|
||||
el.className = 'cm-rm-toc'
|
||||
if (!this.entries.length) {
|
||||
el.textContent = '目录(还没有标题)'
|
||||
el.textContent = translate('workspace.table_of_contents_no_headings_c24b5d')
|
||||
el.classList.add('cm-rm-placeholder')
|
||||
}
|
||||
const minLevel = Math.min(...this.entries.map(e => e.level))
|
||||
|
||||
Reference in new issue
Block a user