From 70cf9b24aeaa231a86708e44a4809fd2dd71963a Mon Sep 17 00:00:00 2001 From: MuRong Date: Tue, 28 Jul 2026 19:37:27 +0800 Subject: [PATCH] feat(editor): add task list and today slash commands Refs #584 --- .../CodeMirrorPaperSurface.test.tsx | 36 +++++++- .../src/components/CodeMirrorPaperSurface.tsx | 7 ++ packages/editor/src/codemirror/blocks.test.ts | 22 +++++ packages/editor/src/codemirror/blocks.ts | 53 +++++++++-- packages/editor/src/codemirror/index.ts | 6 ++ .../editor/src/codemirror/insertions.test.ts | 79 ++++++++++++++++ packages/editor/src/codemirror/insertions.ts | 91 +++++++++++++++++++ packages/shared/src/i18n/locales/de.ts | 4 +- packages/shared/src/i18n/locales/en.ts | 4 +- packages/shared/src/i18n/locales/es.ts | 4 +- packages/shared/src/i18n/locales/fr.ts | 4 +- packages/shared/src/i18n/locales/it.ts | 4 +- packages/shared/src/i18n/locales/ja.ts | 4 +- packages/shared/src/i18n/locales/ko.ts | 4 +- packages/shared/src/i18n/locales/pt-BR.ts | 4 +- packages/shared/src/i18n/locales/ru.ts | 4 +- packages/shared/src/i18n/locales/types.ts | 4 +- packages/shared/src/i18n/locales/zh-CN.ts | 4 +- packages/shared/src/i18n/locales/zh-TW.ts | 4 +- 19 files changed, 322 insertions(+), 20 deletions(-) create mode 100644 packages/editor/src/codemirror/insertions.test.ts create mode 100644 packages/editor/src/codemirror/insertions.ts diff --git a/packages/app/src/components/CodeMirrorPaperSurface.test.tsx b/packages/app/src/components/CodeMirrorPaperSurface.test.tsx index bfdf4e4e..1e1d9958 100644 --- a/packages/app/src/components/CodeMirrorPaperSurface.test.tsx +++ b/packages/app/src/components/CodeMirrorPaperSurface.test.tsx @@ -520,10 +520,44 @@ describe("CodeMirrorPaperSurface", () => { expect( getMarkraSlashMenuState(view).actions.map((action) => action.command), ).toEqual( - expect.arrayContaining(["block.callout", "block.table"]), + expect.arrayContaining([ + "block.callout", + "block.table", + "block.task-list", + "insert.today", + ]), ); }); + it("inserts today's local date from the slash command", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2031, 4, 6, 10, 30)); + const onEditorReady = vi.fn(); + + try { + render( + {}} + />, + ); + const view = onEditorReady.mock.calls[0]?.[0] as EditorView; + act(() => { + view.dispatch({ + selection: EditorSelection.cursor(view.state.doc.length), + }); + }); + + expect(runMarkraCommand(view, "insert.today")).toBe(true); + expect(view.state.doc.toString()).toBe("Published: 2031-05-06"); + expect(view.state.selection.main.head).toBe(view.state.doc.length); + } finally { + vi.useRealTimers(); + } + }); + it("accepts host plugins and renders their toolbar actions", () => { const onEditorReady = vi.fn(); const plugin = defineMarkraPlugin({ diff --git a/packages/app/src/components/CodeMirrorPaperSurface.tsx b/packages/app/src/components/CodeMirrorPaperSurface.tsx index d738b69d..fbdddaf7 100644 --- a/packages/app/src/components/CodeMirrorPaperSurface.tsx +++ b/packages/app/src/components/CodeMirrorPaperSurface.tsx @@ -18,6 +18,7 @@ import { getActiveCodeMirrorSpellcheckMatch, horizontalRulePlugin, imagePreviewPlugin, + insertionsPlugin, liveMarkdown, linksPlugin, markdownEditingPlugin, @@ -204,6 +205,7 @@ function markdownExtension({ "block.paragraph": t(language, "menu.paragraph"), "block.quote": t(language, "menu.quote"), "block.table": t(language, "menu.table"), + "block.task-list": t(language, "menu.taskList"), }, }), codeMirrorBlockDragPlugin({ @@ -253,6 +255,11 @@ function markdownExtension({ frontmatterPreviewPlugin(), horizontalRulePlugin(), imagePreviewPlugin(imageOptions), + insertionsPlugin({ + labels: { + "insert.today": t(language, "menu.today"), + }, + }), ...(linkOptions ? [linksPlugin(linkOptions)] : []), mathPreviewPlugin(), markdownEditingPlugin(), diff --git a/packages/editor/src/codemirror/blocks.test.ts b/packages/editor/src/codemirror/blocks.test.ts index 76f5ae17..a8e8a82a 100644 --- a/packages/editor/src/codemirror/blocks.test.ts +++ b/packages/editor/src/codemirror/blocks.test.ts @@ -69,6 +69,11 @@ describe("blocksPlugin", () => { icon: "list", label: "Bullet list", }, + { + command: "block.task-list", + icon: "list-checks", + label: "Task list", + }, { command: "block.ordered-list", icon: "list-ordered", @@ -181,6 +186,12 @@ describe("blocksPlugin", () => { expect(runMarkraCommand(bullet, "block.bullet-list")).toBe(true); expect(bullet.state.doc.toString()).toBe("Alpha\nBeta"); + const task = createView(); + expect(runMarkraCommand(task, "block.task-list")).toBe(true); + expect(task.state.doc.toString()).toBe("- [ ] Alpha\n- [ ] Beta"); + expect(runMarkraCommand(task, "block.task-list")).toBe(true); + expect(task.state.doc.toString()).toBe("Alpha\nBeta"); + const ordered = createView(); expect(runMarkraCommand(ordered, "block.ordered-list")).toBe(true); expect(ordered.state.doc.toString()).toBe("1. Alpha\n2. Beta"); @@ -188,6 +199,17 @@ describe("blocksPlugin", () => { expect(ordered.state.doc.toString()).toBe("Alpha\nBeta"); }); + it("converts existing list markers without leaving task syntax behind", () => { + const view = createView({ + doc: "- [x] Alpha\n* Beta", + from: 0, + to: "- [x] Alpha\n* Beta".length, + }); + + expect(runMarkraCommand(view, "block.ordered-list")).toBe(true); + expect(view.state.doc.toString()).toBe("1. Alpha\n2. Beta"); + }); + it("wraps and unwraps a selected block in a fenced code block", () => { const view = createView(); diff --git a/packages/editor/src/codemirror/blocks.ts b/packages/editor/src/codemirror/blocks.ts index aae24883..b5de4d97 100644 --- a/packages/editor/src/codemirror/blocks.ts +++ b/packages/editor/src/codemirror/blocks.ts @@ -23,6 +23,7 @@ export type BlockCommandId = | "block.heading.5" | "block.heading.6" | "block.bullet-list" + | "block.task-list" | "block.ordered-list" | "block.quote" | "block.callout" @@ -62,6 +63,7 @@ const defaultBlockLabels: BlockLabels = { "block.heading.5": "Heading 5", "block.heading.6": "Heading 6", "block.bullet-list": "Bullet list", + "block.task-list": "Task list", "block.ordered-list": "Numbered list", "block.quote": "Quote", "block.callout": "Callout", @@ -126,19 +128,33 @@ const blockSpecs: readonly BlockSpec[] = [ keywords: ["bullet", "unordered", "list", "项目符号", "列表"], order: 80, }, + { + id: "block.task-list", + icon: "list-checks", + keywords: [ + "task", + "todo", + "checkbox", + "checklist", + "任务", + "待办", + "复选框", + ], + order: 90, + }, { id: "block.ordered-list", icon: "list-ordered", key: "Mod-Shift-7", keywords: ["numbered", "ordered", "list", "编号", "有序列表"], - order: 90, + order: 100, }, { id: "block.quote", icon: "text-quote", key: "Mod-Shift-b", keywords: ["quote", "blockquote", "引用"], - order: 100, + order: 110, }, { id: "block.callout", @@ -155,27 +171,30 @@ const blockSpecs: readonly BlockSpec[] = [ "提示", "警告", ], - order: 105, + order: 115, }, { id: "block.code", icon: "square-code", key: "Mod-Alt-c", keywords: ["code", "fence", "代码", "代码块"], - order: 110, + order: 120, }, { id: "block.table", icon: "table-2", keywords: ["table", "grid", "表格"], - order: 120, + order: 130, }, ]; const headingPattern = /^([\t ]{0,3})#{1,6}[\t ]+/; -const bulletPattern = /^([\t ]*)[-+*][\t ]+/; +const taskPattern = /^([\t ]*)[-+*][\t ]+\[[ xX]\](?:[\t ]+|$)/; +const bulletPattern = + /^([\t ]*)[-+*][\t ]+(?!\[[ xX]\](?:[\t ]+|$))/; const orderedPattern = /^([\t ]*)\d+[.)][\t ]+/; -const listPattern = /^([\t ]*)(?:[-+*][\t ]+|\d+[.)][\t ]+)/; +const listPattern = + /^([\t ]*)(?:[-+*][\t ]+\[[ xX]\](?:[\t ]+|$)|[-+*][\t ]+|\d+[.)][\t ]+)/; const quotePattern = /^([\t ]*)>[\t ]?/; const openingFencePattern = /^[\t ]*```[^`]*$/; const closingFencePattern = /^[\t ]*```[\t ]*$/; @@ -312,6 +331,20 @@ function toggleList(view: EditorView, ordered: boolean) { return dispatchLineChanges(view, changes); } +function toggleTaskList(view: EditorView) { + if (!isEditable(view)) return false; + const lines = selectedLines(view.state); + const remove = lines.every((line) => taskPattern.test(line.text)); + const changes = lines.map((line) => + markerChange( + line, + remove ? taskPattern : listPattern, + remove ? "" : "- [ ] ", + ), + ); + return dispatchLineChanges(view, changes); +} + function codeBlockBounds(view: EditorView) { if (view.state.selection.ranges.length !== 1) return undefined; const range = view.state.selection.main; @@ -471,6 +504,12 @@ function blockCommand( isActive: (view) => everyLineMatches(view, bulletPattern), run: (view) => toggleList(view, false), }; + case "block.task-list": + return { + ...shared, + isActive: (view) => everyLineMatches(view, taskPattern), + run: toggleTaskList, + }; case "block.ordered-list": return { ...shared, diff --git a/packages/editor/src/codemirror/index.ts b/packages/editor/src/codemirror/index.ts index 39ff263d..ee2cfd61 100644 --- a/packages/editor/src/codemirror/index.ts +++ b/packages/editor/src/codemirror/index.ts @@ -139,6 +139,12 @@ export { markraHighlight, } from "./highlight.ts"; export { horizontalRulePlugin } from "./horizontal-rule.ts"; +export type { + InsertionCommandId, + InsertionLabels, + InsertionsPluginOptions, +} from "./insertions.ts"; +export { insertionsPlugin } from "./insertions.ts"; export { markdownEditingPlugin } from "./markdown-editing.ts"; export type { MarkdownShortcutsPluginOptions } from "./markdown-shortcuts.ts"; export { markdownShortcutsPlugin } from "./markdown-shortcuts.ts"; diff --git a/packages/editor/src/codemirror/insertions.test.ts b/packages/editor/src/codemirror/insertions.test.ts new file mode 100644 index 00000000..ceddfbcc --- /dev/null +++ b/packages/editor/src/codemirror/insertions.test.ts @@ -0,0 +1,79 @@ +import { EditorSelection, EditorState } from "@codemirror/state"; +import { EditorView } from "@codemirror/view"; +import { afterEach, describe, expect, it } from "vitest"; +import { + insertionsPlugin, + listMarkraUi, + liveMarkdown, + runMarkraCommand, +} from "./index.ts"; + +import "./dom.test-support.ts"; + +const views: EditorView[] = []; + +function createView(readOnly = false) { + const doc = "Published: placeholder"; + const parent = document.createElement("div"); + document.body.append(parent); + const view = new EditorView({ + parent, + state: EditorState.create({ + doc, + extensions: [ + EditorState.readOnly.of(readOnly), + liveMarkdown({ + plugins: [ + insertionsPlugin({ + labels: { "insert.today": "Current date" }, + now: () => new Date(2031, 4, 6, 10, 30), + }), + ], + }), + ], + selection: EditorSelection.range( + doc.indexOf("placeholder"), + doc.length, + ), + }), + }); + views.push(view); + return view; +} + +afterEach(() => { + for (const view of views.splice(0)) view.destroy(); + document.body.replaceChildren(); +}); + +describe("insertionsPlugin", () => { + it("publishes a Today slash command that replaces the selection", () => { + const view = createView(); + + expect( + listMarkraUi(view, "slash-menu").map((action) => ({ + command: action.command, + icon: action.icon, + label: action.label, + })), + ).toEqual([ + { + command: "insert.today", + icon: "calendar-days", + label: "Current date", + }, + ]); + + expect(runMarkraCommand(view, "insert.today")).toBe(true); + expect(view.state.doc.toString()).toBe("Published: 2031-05-06"); + expect(view.state.selection.main.head).toBe(view.state.doc.length); + }); + + it("disables date insertion in a read-only editor", () => { + const view = createView(true); + + expect(listMarkraUi(view, "slash-menu")[0]?.enabled).toBe(false); + expect(runMarkraCommand(view, "insert.today")).toBe(false); + expect(view.state.doc.toString()).toBe("Published: placeholder"); + }); +}); diff --git a/packages/editor/src/codemirror/insertions.ts b/packages/editor/src/codemirror/insertions.ts new file mode 100644 index 00000000..1842522f --- /dev/null +++ b/packages/editor/src/codemirror/insertions.ts @@ -0,0 +1,91 @@ +import { + EditorSelection, + EditorState, + type ChangeSpec, +} from "@codemirror/state"; +import type { EditorView } from "@codemirror/view"; +import { defineMarkraPlugin } from "./plugin.ts"; + +export type InsertionCommandId = "insert.today"; + +export type InsertionLabels = Record; + +export interface InsertionsPluginOptions { + labels?: Partial; + now?: () => Date; +} + +const defaultInsertionLabels: InsertionLabels = { + "insert.today": "Today", +}; + +function isEditable(view: EditorView) { + return !view.state.facet(EditorState.readOnly); +} + +function twoDigits(value: number) { + return value.toString().padStart(2, "0"); +} + +function localDate(date: Date) { + // Keep the user's calendar day at UTC boundaries; toISOString() can produce + // yesterday or tomorrow for users outside UTC. + return [ + date.getFullYear(), + twoDigits(date.getMonth() + 1), + twoDigits(date.getDate()), + ].join("-"); +} + +function insertText(view: EditorView, text: string) { + if (!isEditable(view)) return false; + const { state } = view; + const changes: ChangeSpec[] = state.selection.ranges.map((range) => ({ + from: range.from, + insert: text, + to: range.to, + })); + const changeSet = state.changes(changes); + const selection = EditorSelection.create( + state.selection.ranges.map((range) => + EditorSelection.cursor(changeSet.mapPos(range.to, 1)), + ), + state.selection.mainIndex, + ); + + view.dispatch({ + changes: changeSet, + scrollIntoView: true, + selection, + userEvent: "input", + }); + view.focus(); + return true; +} + +export function insertionsPlugin(options: InsertionsPluginOptions = {}) { + const labels = { ...defaultInsertionLabels, ...options.labels }; + const now = options.now ?? (() => new Date()); + + return defineMarkraPlugin({ + id: "markra.insertions", + commands: [ + { + id: "insert.today", + isEnabled: isEditable, + label: labels["insert.today"], + run: (view) => insertText(view, localDate(now())), + }, + ], + ui: [ + { + command: "insert.today", + group: "insert", + icon: "calendar-days", + keywords: ["today", "date", "current date", "今天", "日期"], + order: 140, + placement: "slash-menu", + }, + ], + }); +} diff --git a/packages/shared/src/i18n/locales/de.ts b/packages/shared/src/i18n/locales/de.ts index a67d09c8..91d6f833 100644 --- a/packages/shared/src/i18n/locales/de.ts +++ b/packages/shared/src/i18n/locales/de.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "Überschrift 2", "menu.heading3": "Überschrift 3", "menu.bulletList": "Aufzählung", + "menu.taskList": "Aufgabenliste", "menu.orderedList": "Nummerierte Liste", "menu.quote": "Zitat", "menu.callout": "Hinweisblock", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "Bild", "menu.importLocalImages": "Lokale Bilder importieren...", "menu.importLocalFiles": "Lokale Dateien importieren...", - "menu.table": "Tabelle" + "menu.table": "Tabelle", + "menu.today": "Heute" }; export default messages; diff --git a/packages/shared/src/i18n/locales/en.ts b/packages/shared/src/i18n/locales/en.ts index be7fa8a6..77a60a8d 100644 --- a/packages/shared/src/i18n/locales/en.ts +++ b/packages/shared/src/i18n/locales/en.ts @@ -923,6 +923,7 @@ const messages: BaseLocaleMessages = { "menu.heading2": "Heading 2", "menu.heading3": "Heading 3", "menu.bulletList": "Bullet List", + "menu.taskList": "Task List", "menu.orderedList": "Ordered List", "menu.quote": "Quote", "menu.callout": "Callout", @@ -931,7 +932,8 @@ const messages: BaseLocaleMessages = { "menu.image": "Image", "menu.importLocalImages": "Import Local Images...", "menu.importLocalFiles": "Import Local Files...", - "menu.table": "Table" + "menu.table": "Table", + "menu.today": "Today" }; export default messages; diff --git a/packages/shared/src/i18n/locales/es.ts b/packages/shared/src/i18n/locales/es.ts index d504a7d9..89bc3268 100644 --- a/packages/shared/src/i18n/locales/es.ts +++ b/packages/shared/src/i18n/locales/es.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "Encabezado 2", "menu.heading3": "Encabezado 3", "menu.bulletList": "Lista con viñetas", + "menu.taskList": "Lista de tareas", "menu.orderedList": "Lista numerada", "menu.quote": "Cita", "menu.callout": "Aviso", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "Imagen", "menu.importLocalImages": "Importar imágenes locales...", "menu.importLocalFiles": "Importar archivos locales...", - "menu.table": "Tabla" + "menu.table": "Tabla", + "menu.today": "Hoy" }; export default messages; diff --git a/packages/shared/src/i18n/locales/fr.ts b/packages/shared/src/i18n/locales/fr.ts index b8eda0f0..7e753742 100644 --- a/packages/shared/src/i18n/locales/fr.ts +++ b/packages/shared/src/i18n/locales/fr.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "Titre 2", "menu.heading3": "Titre 3", "menu.bulletList": "Liste à puces", + "menu.taskList": "Liste de tâches", "menu.orderedList": "Liste numérotée", "menu.quote": "Citation", "menu.callout": "Encadré", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "Image", "menu.importLocalImages": "Importer des images locales...", "menu.importLocalFiles": "Importer des fichiers locaux...", - "menu.table": "Tableau" + "menu.table": "Tableau", + "menu.today": "Aujourd’hui" }; export default messages; diff --git a/packages/shared/src/i18n/locales/it.ts b/packages/shared/src/i18n/locales/it.ts index 653a1cfd..3744977d 100644 --- a/packages/shared/src/i18n/locales/it.ts +++ b/packages/shared/src/i18n/locales/it.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "Titolo 2", "menu.heading3": "Titolo 3", "menu.bulletList": "Elenco puntato", + "menu.taskList": "Elenco attività", "menu.orderedList": "Elenco numerato", "menu.quote": "Citazione", "menu.callout": "Avviso", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "Immagine", "menu.importLocalImages": "Importa immagini locali...", "menu.importLocalFiles": "Importa file locali...", - "menu.table": "Tabella" + "menu.table": "Tabella", + "menu.today": "Oggi" }; export default messages; diff --git a/packages/shared/src/i18n/locales/ja.ts b/packages/shared/src/i18n/locales/ja.ts index 6c9955c8..0bad2225 100644 --- a/packages/shared/src/i18n/locales/ja.ts +++ b/packages/shared/src/i18n/locales/ja.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "見出し 2", "menu.heading3": "見出し 3", "menu.bulletList": "箇条書き", + "menu.taskList": "タスクリスト", "menu.orderedList": "番号付きリスト", "menu.quote": "引用", "menu.callout": "コールアウト", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "画像", "menu.importLocalImages": "ローカル画像を読み込む...", "menu.importLocalFiles": "ローカルファイルを読み込む...", - "menu.table": "表" + "menu.table": "表", + "menu.today": "今日" }; export default messages; diff --git a/packages/shared/src/i18n/locales/ko.ts b/packages/shared/src/i18n/locales/ko.ts index 8ef7cd16..87d29ad7 100644 --- a/packages/shared/src/i18n/locales/ko.ts +++ b/packages/shared/src/i18n/locales/ko.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "제목 2", "menu.heading3": "제목 3", "menu.bulletList": "글머리 기호 목록", + "menu.taskList": "작업 목록", "menu.orderedList": "번호 매기기 목록", "menu.quote": "인용", "menu.callout": "콜아웃", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "이미지", "menu.importLocalImages": "로컬 이미지 가져오기...", "menu.importLocalFiles": "로컬 파일 가져오기...", - "menu.table": "표" + "menu.table": "표", + "menu.today": "오늘" }; export default messages; diff --git a/packages/shared/src/i18n/locales/pt-BR.ts b/packages/shared/src/i18n/locales/pt-BR.ts index 139e078b..8c1d5f8d 100644 --- a/packages/shared/src/i18n/locales/pt-BR.ts +++ b/packages/shared/src/i18n/locales/pt-BR.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "Título 2", "menu.heading3": "Título 3", "menu.bulletList": "Lista com marcadores", + "menu.taskList": "Lista de tarefas", "menu.orderedList": "Lista numerada", "menu.quote": "Citação", "menu.callout": "Aviso", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "Imagem", "menu.importLocalImages": "Importar imagens locais...", "menu.importLocalFiles": "Importar arquivos locais...", - "menu.table": "Tabela" + "menu.table": "Tabela", + "menu.today": "Hoje" }; export default messages; diff --git a/packages/shared/src/i18n/locales/ru.ts b/packages/shared/src/i18n/locales/ru.ts index 32d82405..434b9971 100644 --- a/packages/shared/src/i18n/locales/ru.ts +++ b/packages/shared/src/i18n/locales/ru.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "Заголовок 2", "menu.heading3": "Заголовок 3", "menu.bulletList": "Маркированный список", + "menu.taskList": "Список задач", "menu.orderedList": "Нумерованный список", "menu.quote": "Цитата", "menu.callout": "Выноска", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "Изображение", "menu.importLocalImages": "Импорт локальных изображений...", "menu.importLocalFiles": "Импорт локальных файлов...", - "menu.table": "Таблица" + "menu.table": "Таблица", + "menu.today": "Сегодня" }; export default messages; diff --git a/packages/shared/src/i18n/locales/types.ts b/packages/shared/src/i18n/locales/types.ts index e10f0b4e..8a702aae 100644 --- a/packages/shared/src/i18n/locales/types.ts +++ b/packages/shared/src/i18n/locales/types.ts @@ -934,6 +934,7 @@ export type I18nKey = | "menu.heading2" | "menu.heading3" | "menu.bulletList" + | "menu.taskList" | "menu.orderedList" | "menu.quote" | "menu.callout" @@ -942,7 +943,8 @@ export type I18nKey = | "menu.image" | "menu.importLocalImages" | "menu.importLocalFiles" - | "menu.table"; + | "menu.table" + | "menu.today"; export type BaseLocaleMessages = Record; export type LocaleMessages = Partial>; diff --git a/packages/shared/src/i18n/locales/zh-CN.ts b/packages/shared/src/i18n/locales/zh-CN.ts index 022bc25d..edc4c975 100644 --- a/packages/shared/src/i18n/locales/zh-CN.ts +++ b/packages/shared/src/i18n/locales/zh-CN.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "标题 2", "menu.heading3": "标题 3", "menu.bulletList": "无序列表", + "menu.taskList": "任务列表", "menu.orderedList": "有序列表", "menu.quote": "引用", "menu.callout": "提示块", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "图片", "menu.importLocalImages": "导入本地图片...", "menu.importLocalFiles": "导入本地文件...", - "menu.table": "表格" + "menu.table": "表格", + "menu.today": "今天" }; export default messages; diff --git a/packages/shared/src/i18n/locales/zh-TW.ts b/packages/shared/src/i18n/locales/zh-TW.ts index 9b659d88..7b4d99be 100644 --- a/packages/shared/src/i18n/locales/zh-TW.ts +++ b/packages/shared/src/i18n/locales/zh-TW.ts @@ -923,6 +923,7 @@ const messages: LocaleMessages = { "menu.heading2": "標題 2", "menu.heading3": "標題 3", "menu.bulletList": "項目符號清單", + "menu.taskList": "任務清單", "menu.orderedList": "編號清單", "menu.quote": "引用", "menu.callout": "提示區塊", @@ -931,7 +932,8 @@ const messages: LocaleMessages = { "menu.image": "圖片", "menu.importLocalImages": "匯入本機圖片...", "menu.importLocalFiles": "匯入本機檔案...", - "menu.table": "表格" + "menu.table": "表格", + "menu.today": "今天" }; export default messages;