feat: add task lists across clients
This commit is contained in:
1 parent
0e3ebf7771
commit
a257e53220
28 files changed
+1124
-810
No files matched your search
@@ -98,8 +98,8 @@ enum BuiltInMemoTemplates {
|
||||
titleEN: "Weekly Review & Status",
|
||||
descriptionZH: "梳理本周核心产出、风险卡点与下周关键优先级。",
|
||||
descriptionEN: "Summarize weekly highlights, blockers, and next week's key priorities.",
|
||||
contentMarkdownZH: "# 🗓️ 工作周报\n\n## 🌟 本周核心进展 (Highlights)\n\n- [x] **[项目/功能]** 完成情况与成果说明\n- [x] **[项目/功能]** 完成情况与成果说明\n\n## 🚧 卡点与风险 (Blockers & Risks)\n\n- ⚠️ **阻塞项**:原因及所需支持\n\n## 🎯 下周优先级 (Next Week Priorities)\n\n1. [ ] \n2. [ ] \n3. [ ] \n\n## 💡 总结与思考\n\n- \n",
|
||||
contentMarkdownEN: "# 🗓️ Weekly Status Report\n\n## 🌟 Highlights\n\n- [x] **[Project/Feature]** Accomplishment details\n- [x] **[Project/Feature]** Accomplishment details\n\n## 🚧 Blockers & Risks\n\n- ⚠️ **Blocker**: Reason and required support\n\n## 🎯 Next Week Priorities\n\n1. [ ] \n2. [ ] \n3. [ ] \n\n## 💡 Reflection & Insights\n\n- \n"
|
||||
contentMarkdownZH: "# 🗓️ 工作周报\n\n## 🌟 本周核心进展 (Highlights)\n\n- [x] **[项目/功能]** 完成情况与成果说明\n- [x] **[项目/功能]** 完成情况与成果说明\n\n## 🚧 卡点与风险 (Blockers & Risks)\n\n- ⚠️ **阻塞项**:原因及所需支持\n\n## 🎯 下周优先级 (Next Week Priorities)\n\n- [ ] \n- [ ] \n- [ ] \n\n## 💡 总结与思考\n\n- \n",
|
||||
contentMarkdownEN: "# 🗓️ Weekly Status Report\n\n## 🌟 Highlights\n\n- [x] **[Project/Feature]** Accomplishment details\n- [x] **[Project/Feature]** Accomplishment details\n\n## 🚧 Blockers & Risks\n\n- ⚠️ **Blocker**: Reason and required support\n\n## 🎯 Next Week Priorities\n\n- [ ] \n- [ ] \n- [ ] \n\n## 💡 Reflection & Insights\n\n- \n"
|
||||
),
|
||||
Item(
|
||||
id: "reading",
|
||||
|
||||
@@ -121,12 +121,19 @@ enum EditorContentCodec {
|
||||
let src = attrs["src"] as? String ?? ""
|
||||
let alt = attrs["alt"] as? String ?? ""
|
||||
if !src.isEmpty { lines.append(")") }
|
||||
case "bulletList", "orderedList":
|
||||
case "bulletList", "orderedList", "taskList":
|
||||
if let items = dict["content"] as? [Any] {
|
||||
for item in items {
|
||||
guard let itemDict = item as? [String: Any] else { continue }
|
||||
let text = blockText(itemDict)
|
||||
if !text.isEmpty { lines.append("- \(text)") }
|
||||
if !text.isEmpty {
|
||||
if type == "taskList" {
|
||||
let checked = (itemDict["attrs"] as? [String: Any])?["checked"] as? Bool ?? false
|
||||
lines.append("- [\(checked ? "x" : " ")] \(text)")
|
||||
} else {
|
||||
lines.append("- \(text)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "blockquote":
|
||||
|
||||
@@ -66,6 +66,16 @@ enum TipTapContentSource: Sendable {
|
||||
return true
|
||||
}
|
||||
|
||||
let markdownHasTaskList = markdown.range(
|
||||
of: #"(?m)^\s*[-*+]\s+\[[ xX]\]\s*"#,
|
||||
options: .regularExpression
|
||||
) != nil
|
||||
let jsonHasTaskList = json.contains(#""type":"taskList""#)
|
||||
|| json.contains(#""type": "taskList""#)
|
||||
if markdownHasTaskList && !jsonHasTaskList {
|
||||
return true
|
||||
}
|
||||
|
||||
let md = structureScore(markdown: markdown)
|
||||
let js = structureScore(json: json)
|
||||
return md >= 2 && md > js
|
||||
@@ -90,7 +100,7 @@ enum TipTapContentSource: Sendable {
|
||||
if json.contains("\"heading\"") { score += 3 }
|
||||
if json.contains("\"table\"") || json.contains("\"tableRow\"") { score += 2 }
|
||||
if json.contains("\"codeBlock\"") { score += 3 }
|
||||
if json.contains("\"bulletList\"") || json.contains("\"orderedList\"") { score += 2 }
|
||||
if json.contains("\"bulletList\"") || json.contains("\"orderedList\"") || json.contains("\"taskList\"") { score += 2 }
|
||||
if json.contains("\"blockquote\"") { score += 1 }
|
||||
if json.contains("\"bold\"") || json.contains("\"italic\"") { score += 1 }
|
||||
if json.contains("\"type\":\"image\"") || json.contains("\"type\": \"image\"") { score += 1 }
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+406
-395
File diff suppressed because it is too large.
Load diff
@@ -8,6 +8,7 @@
|
||||
"@tiptap/core": "3.27.1",
|
||||
"@tiptap/extension-code-block": "3.27.1",
|
||||
"@tiptap/extension-image": "3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-mathematics": "3.27.1",
|
||||
"@tiptap/extension-placeholder": "3.27.1",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
@@ -171,7 +172,7 @@
|
||||
|
||||
"@tiptap/extension-link": ["@tiptap/extension-link@3.29.2", "", { "dependencies": { "linkifyjs": "^4.3.3" }, "peerDependencies": { "@tiptap/core": "3.29.2", "@tiptap/pm": "3.29.2" } }, "sha512-DcVer5SqrexKCEP6Ip1UPxJUMvcRCCItSv0wxoGytanrimBh2smvcg6X0DWnjlsi5H0updhyl+atYCmmQXUIXA=="],
|
||||
|
||||
"@tiptap/extension-list": ["@tiptap/extension-list@3.29.2", "", { "peerDependencies": { "@tiptap/core": "3.29.2", "@tiptap/pm": "3.29.2" } }, "sha512-WPZ9BHAPT6QeIm1vdVkuoOWvy9a8/EZeJwV2VhU8LXyTAttvzyj4rsbbHyJWvYWlUSTt/QF2AZ2zhKo7u1w3/A=="],
|
||||
"@tiptap/extension-list": ["@tiptap/extension-list@3.27.1", "", { "peerDependencies": { "@tiptap/core": "3.27.1", "@tiptap/pm": "3.27.1" } }, "sha512-c2Upru7lj0/ZV/Ibww6cNz6sUS8m6Dp/9uygFhYcZOd3X8M0xBIEk42c6m6SQehkPziVA8QOgNJz7sMqsbz1OQ=="],
|
||||
|
||||
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.29.2", "", { "peerDependencies": { "@tiptap/extension-list": "3.29.2" } }, "sha512-s8vBVHHFT0Qpu7CzAZ7S1kYmSiVaDvUNvMNcZUWnxj6VPfiwmx0eXd9FsjePrRCMoM5tFmPnhFTHDxY3D/eZeQ=="],
|
||||
|
||||
@@ -469,6 +470,8 @@
|
||||
|
||||
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
||||
|
||||
"@tiptap/starter-kit/@tiptap/extension-list": ["@tiptap/extension-list@3.29.2", "", { "peerDependencies": { "@tiptap/core": "3.29.2", "@tiptap/pm": "3.29.2" } }, "sha512-WPZ9BHAPT6QeIm1vdVkuoOWvy9a8/EZeJwV2VhU8LXyTAttvzyj4rsbbHyJWvYWlUSTt/QF2AZ2zhKo7u1w3/A=="],
|
||||
|
||||
"cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="],
|
||||
|
||||
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@tiptap/core": "3.27.1",
|
||||
"@tiptap/extension-code-block": "3.27.1",
|
||||
"@tiptap/extension-image": "3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-mathematics": "3.27.1",
|
||||
"@tiptap/extension-placeholder": "3.27.1",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
|
||||
@@ -3,6 +3,7 @@ import "katex/dist/katex.min.css";
|
||||
import { Editor, mergeAttributes, Node } from "@tiptap/core";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import CodeBlock from "@tiptap/extension-code-block";
|
||||
import { TableKit } from "@tiptap/extension-table";
|
||||
@@ -608,6 +609,8 @@ function buildExtensions(placeholder: string) {
|
||||
StarterKit.configure({
|
||||
codeBlock: false,
|
||||
}),
|
||||
TaskList,
|
||||
TaskItem.configure({ nested: true }),
|
||||
MergeDivider,
|
||||
...createEdgeEverMathematics(),
|
||||
CodeBlock.configure({
|
||||
@@ -847,6 +850,8 @@ function getEditorSearchMatches(ed: Editor, query: string): EditorSearchMatch[]
|
||||
return matches;
|
||||
}
|
||||
|
||||
const activeListItemType = () => editor.isActive("taskItem") ? "taskItem" : "listItem";
|
||||
|
||||
function setToolbarVisible(visible: boolean) {
|
||||
toolbarEl.classList.toggle("editor-mode", visible);
|
||||
toolbarEl.innerHTML = "";
|
||||
@@ -863,15 +868,20 @@ function setToolbarVisible(visible: boolean) {
|
||||
label: "•",
|
||||
run: () => editor.chain().focus().toggleBulletList().run(),
|
||||
},
|
||||
{
|
||||
id: "task",
|
||||
label: "☑",
|
||||
run: () => editor.chain().focus().toggleTaskList().run(),
|
||||
},
|
||||
{
|
||||
id: "indent",
|
||||
label: "⇥",
|
||||
run: () => editor.chain().focus().sinkListItem("listItem").run(),
|
||||
run: () => editor.chain().focus().sinkListItem(activeListItemType()).run(),
|
||||
},
|
||||
{
|
||||
id: "outdent",
|
||||
label: "⇤",
|
||||
run: () => editor.chain().focus().liftListItem("listItem").run(),
|
||||
run: () => editor.chain().focus().liftListItem(activeListItemType()).run(),
|
||||
},
|
||||
{
|
||||
id: "quote",
|
||||
@@ -893,6 +903,7 @@ function setToolbarVisible(visible: boolean) {
|
||||
image: ["插入图片", "Insert image"],
|
||||
bold: ["粗体", "Bold"],
|
||||
bullet: ["项目符号列表", "Bullet list"],
|
||||
task: ["任务清单", "Task list"],
|
||||
indent: ["增加列表缩进", "Increase list indent"],
|
||||
outdent: ["减少列表缩进", "Decrease list indent"],
|
||||
quote: ["引用", "Block quote"],
|
||||
@@ -913,6 +924,7 @@ function refreshToolbarState() {
|
||||
const active: Record<string, boolean> = {
|
||||
bold: editor.isActive("bold"),
|
||||
bullet: editor.isActive("bulletList"),
|
||||
task: editor.isActive("taskList"),
|
||||
quote: editor.isActive("blockquote"),
|
||||
};
|
||||
toolbarEl.querySelectorAll<HTMLButtonElement>("button[data-action]").forEach((button) => {
|
||||
@@ -1059,6 +1071,7 @@ const api: EdgeEverEditorAPI = {
|
||||
const map: Record<string, () => void> = {
|
||||
bold: () => editor.chain().focus().toggleBold().run(),
|
||||
bulletList: () => editor.chain().focus().toggleBulletList().run(),
|
||||
taskList: () => editor.chain().focus().toggleTaskList().run(),
|
||||
blockquote: () => editor.chain().focus().toggleBlockquote().run(),
|
||||
horizontalRule: () => editor.chain().focus().setHorizontalRule().run(),
|
||||
heading2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||
|
||||
@@ -177,6 +177,48 @@ html[data-theme="dark"] body {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] {
|
||||
margin: 0 0 1rem;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 9px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > label {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
margin-top: 3px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > label input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
accent-color: #16a06e;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > div {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > div > p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
margin: 4px 0 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.edgeever-prose img,
|
||||
.ProseMirror img {
|
||||
max-width: 100%;
|
||||
|
||||
@@ -117,6 +117,24 @@ final class TipTapContentSourceTests: XCTestCase {
|
||||
XCTAssertFalse(TipTapContentSource.resolve(mode: .viewer, documentJSON: flatJSON, markdown: markdown).useJSON)
|
||||
}
|
||||
|
||||
func testEditorRecoversTaskListMissingFromLegacyJSON() {
|
||||
let markdown = "- [ ] Pending\n- [x] Complete\n"
|
||||
let legacyJSON = #"{"type":"doc","content":[{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Pending"}]}]}]}]}"#
|
||||
|
||||
let decision = TipTapContentSource.resolve(mode: .editor, documentJSON: legacyJSON, markdown: markdown)
|
||||
XCTAssertFalse(decision.useJSON)
|
||||
XCTAssertEqual(decision.payload, markdown)
|
||||
}
|
||||
|
||||
func testNativeMarkdownFallbackPreservesTaskState() {
|
||||
let json = #"{"type":"doc","content":[{"type":"taskList","content":[{"type":"taskItem","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"type":"text","text":"Pending"}]}]},{"type":"taskItem","attrs":{"checked":true},"content":[{"type":"paragraph","content":[{"type":"text","text":"Complete"}]}]}]}]}"#
|
||||
|
||||
XCTAssertEqual(
|
||||
EditorContentCodec.markdownFromTipTapJSON(json),
|
||||
"- [ ] Pending\n\n- [x] Complete\n"
|
||||
)
|
||||
}
|
||||
|
||||
/// Live WKWebView: packaged TipTap must turn Markdown into real DOM structure.
|
||||
@MainActor
|
||||
func testPackagedEditorSetMarkdownRendersHeadingsListsAndBold() async throws {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@tanstack/react-query-persist-client": "^5.101.2",
|
||||
"@tiptap/extension-code-block": "3.27.1",
|
||||
"@tiptap/extension-image": "3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-placeholder": "3.27.1",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
"@tiptap/react": "3.27.1",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { renderMermaidSVG, THEMES } from "beautiful-mermaid";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import CodeBlock from "@tiptap/extension-code-block";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
||||
import { TableKit } from "@tiptap/extension-table";
|
||||
import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
@@ -404,6 +405,8 @@ function LocalTiptapEditorImpl(props: LocalTiptapEditorProps) {
|
||||
autofocus: autoFocus ? "end" : false,
|
||||
extensions: [
|
||||
StarterKit.configure({ codeBlock: false, link: { openOnClick: false } }),
|
||||
TaskList,
|
||||
TaskItem.configure({ nested: true }),
|
||||
MergeDivider,
|
||||
...createEdgeEverMathematics(),
|
||||
mermaidCodeBlockExtension,
|
||||
@@ -703,6 +706,7 @@ function LocalTiptapEditorImpl(props: LocalTiptapEditorProps) {
|
||||
selector: ({ editor: activeEditor }) =>
|
||||
(activeEditor?.isActive("bold") ? MOBILE_EDITOR_ACTIVE_FLAGS.bold : 0) |
|
||||
(activeEditor?.isActive("bulletList") ? MOBILE_EDITOR_ACTIVE_FLAGS.bulletList : 0) |
|
||||
(activeEditor?.isActive("taskList") ? MOBILE_EDITOR_ACTIVE_FLAGS.taskList : 0) |
|
||||
(activeEditor?.isActive("blockquote") ? MOBILE_EDITOR_ACTIVE_FLAGS.blockquote : 0),
|
||||
});
|
||||
|
||||
@@ -729,17 +733,20 @@ function LocalTiptapEditorImpl(props: LocalTiptapEditorProps) {
|
||||
image: <ImagePlusIcon />,
|
||||
bold: <BoldIcon />,
|
||||
bulletList: <ListIcon />,
|
||||
taskList: <ListTodoIcon />,
|
||||
increaseListIndent: <ListIndentIncreaseIcon />,
|
||||
decreaseListIndent: <ListIndentDecreaseIcon />,
|
||||
blockquote: <QuoteIcon />,
|
||||
horizontalRule: <MinusIcon />,
|
||||
};
|
||||
const activeListItemType = editor?.isActive("taskItem") ? "taskItem" : "listItem";
|
||||
const toolbarHandlers: Record<MobileEditorToolbarActionId, () => void> = {
|
||||
image: () => void insertImage(),
|
||||
bold: () => editor?.chain().focus().toggleBold().run(),
|
||||
bulletList: () => editor?.chain().focus().toggleBulletList().run(),
|
||||
increaseListIndent: () => editor?.chain().focus().sinkListItem("listItem").run(),
|
||||
decreaseListIndent: () => editor?.chain().focus().liftListItem("listItem").run(),
|
||||
taskList: () => editor?.chain().focus().toggleTaskList().run(),
|
||||
increaseListIndent: () => editor?.chain().focus().sinkListItem(activeListItemType).run(),
|
||||
decreaseListIndent: () => editor?.chain().focus().liftListItem(activeListItemType).run(),
|
||||
blockquote: () => editor?.chain().focus().toggleBlockquote().run(),
|
||||
horizontalRule: () => editor?.chain().focus().setHorizontalRule().run(),
|
||||
};
|
||||
@@ -754,9 +761,9 @@ function LocalTiptapEditorImpl(props: LocalTiptapEditorProps) {
|
||||
key={action.id}
|
||||
active={action.activeFlag > 0 && Boolean(toolbarState & action.activeFlag)}
|
||||
disabled={(action.id === "increaseListIndent"
|
||||
&& !Boolean(editor?.can().chain().focus().sinkListItem("listItem").run()))
|
||||
&& !Boolean(editor?.can().chain().focus().sinkListItem(activeListItemType).run()))
|
||||
|| (action.id === "decreaseListIndent"
|
||||
&& !Boolean(editor?.can().chain().focus().liftListItem("listItem").run()))}
|
||||
&& !Boolean(editor?.can().chain().focus().liftListItem(activeListItemType).run()))}
|
||||
icon={toolbarIcons[action.id]}
|
||||
label={getMobileEditorToolbarActionLabel(action.id, props.locale)}
|
||||
onRun={toolbarHandlers[action.id]}
|
||||
@@ -850,6 +857,15 @@ const ListIcon = () => (
|
||||
</EditorIcon>
|
||||
);
|
||||
|
||||
const ListTodoIcon = () => (
|
||||
<EditorIcon size={18} strokeWidth={2.1}>
|
||||
<rect height="6" rx="1" width="6" x="3" y="3" />
|
||||
<path d="m4.5 6 1 1 2-2M13 6h8" />
|
||||
<rect height="6" rx="1" width="6" x="3" y="15" />
|
||||
<path d="M13 18h8" />
|
||||
</EditorIcon>
|
||||
);
|
||||
|
||||
const ListIndentIncreaseIcon = () => (
|
||||
<EditorIcon size={18} strokeWidth={2.1}>
|
||||
<path d="M4 5h16M4 12h10M4 19h16" />
|
||||
@@ -1790,6 +1806,13 @@ const getEditorStyles = (theme: "light" | "dark", options?: { viewer?: boolean }
|
||||
.edgeever-editor-content h1 { margin: 0.7em 0 0.4em; font-size: 1.6rem; }
|
||||
.edgeever-editor-content h2 { margin: 0.85em 0 0.35em; font-size: 1.35rem; }
|
||||
.edgeever-editor-content h3 { margin: 0.75em 0 0.3em; font-size: 1.15rem; }
|
||||
.edgeever-editor-content ul[data-type="taskList"] { margin: 0 0 var(--editor-paragraph-spacing); padding-left: 0; list-style: none; }
|
||||
.edgeever-editor-content ul[data-type="taskList"] li[data-type="taskItem"] { display: flex; align-items: flex-start; gap: 9px; margin: 4px 0; }
|
||||
.edgeever-editor-content ul[data-type="taskList"] li[data-type="taskItem"] > label { display: inline-flex; flex: 0 0 auto; align-items: center; margin-top: 3px; user-select: none; }
|
||||
.edgeever-editor-content ul[data-type="taskList"] li[data-type="taskItem"] > label input { width: 18px; height: 18px; margin: 0; accent-color: #16a06e; }
|
||||
.edgeever-editor-content ul[data-type="taskList"] li[data-type="taskItem"] > div { min-width: 0; flex: 1 1 auto; }
|
||||
.edgeever-editor-content ul[data-type="taskList"] li[data-type="taskItem"] > div > p { margin-bottom: 0; }
|
||||
.edgeever-editor-content ul[data-type="taskList"] ul[data-type="taskList"] { margin: 4px 0 0; padding-left: 24px; }
|
||||
.edgeever-editor-content blockquote { margin-left: 0; max-width: 100%; padding-left: 14px; border-left: 3px solid #5eead4; color: ${theme === "dark" ? "#cbd5e1" : "#475569"}; }
|
||||
.edgeever-editor-content pre { max-width: 100%; overflow-x: auto; border-radius: 10px; padding: 14px 90px 14px 14px; background: #0f172a; color: #e2e8f0; font-size: 0.9rem; }
|
||||
.edgeever-editor-content code { border-radius: 4px; padding: 2px 4px; background: ${theme === "dark" ? "#1e293b" : "#f1f5f9"}; font-size: 0.9em; }
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
|
||||
import { useEditor, EditorContent, type Editor } from "@tiptap/react";
|
||||
import type { Mark } from "@tiptap/pm/model";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { TableKit } from "@tiptap/extension-table";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -1029,6 +1030,8 @@ const RichEditorPane = ({
|
||||
codeBlock: false,
|
||||
link: { openOnClick: false },
|
||||
}),
|
||||
TaskList,
|
||||
TaskItem.configure({ nested: true }),
|
||||
EdgeEverCodeBlock.configure({ lowlight: codeBlockLowlight, defaultLanguage: "plaintext" }),
|
||||
MergeDivider,
|
||||
...createEdgeEverMathematics(),
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Strikethrough,
|
||||
Code2,
|
||||
List,
|
||||
ListTodo,
|
||||
ListOrdered,
|
||||
Quote,
|
||||
SquareCode,
|
||||
@@ -354,6 +355,14 @@ export const EditorToolbar = ({
|
||||
>
|
||||
<List className="h-4 w-4" />
|
||||
</EditorToolbarButton>
|
||||
<EditorToolbarButton
|
||||
title={`${t("editorToolbar.taskList")} · ${t("editorToolbar.listIndentHint")}`}
|
||||
active={isActive("taskList")}
|
||||
disabled={!canRun((current) => current.can().chain().focus().toggleTaskList().run())}
|
||||
onClick={() => run((current) => current.chain().focus().toggleTaskList().run())}
|
||||
>
|
||||
<ListTodo className="h-4 w-4" />
|
||||
</EditorToolbarButton>
|
||||
<EditorToolbarButton
|
||||
title={`${t("editorToolbar.orderedList")} · ${t("editorToolbar.listIndentHint")}`}
|
||||
active={isActive("orderedList")}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bold, Check, ChevronDown, ImagePlus, List, ListIndentDecrease, ListIndentIncrease, Minus, Quote } from "lucide-react";
|
||||
import { Bold, Check, ChevronDown, ImagePlus, List, ListIndentDecrease, ListIndentIncrease, ListTodo, Minus, Quote } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
MOBILE_EDITOR_TOOLBAR_ACTIONS,
|
||||
@@ -56,6 +56,7 @@ export const MobileEditorToolbar = ({
|
||||
disabled,
|
||||
boldActive,
|
||||
bulletListActive,
|
||||
taskListActive,
|
||||
increaseListIndentAvailable,
|
||||
decreaseListIndentAvailable,
|
||||
blockquoteActive,
|
||||
@@ -63,6 +64,7 @@ export const MobileEditorToolbar = ({
|
||||
onPickImage,
|
||||
onToggleBold,
|
||||
onToggleBulletList,
|
||||
onToggleTaskList,
|
||||
onIncreaseListIndent,
|
||||
onDecreaseListIndent,
|
||||
onToggleBlockquote,
|
||||
@@ -71,6 +73,7 @@ export const MobileEditorToolbar = ({
|
||||
disabled: boolean;
|
||||
boldActive: boolean;
|
||||
bulletListActive: boolean;
|
||||
taskListActive: boolean;
|
||||
increaseListIndentAvailable: boolean;
|
||||
decreaseListIndentAvailable: boolean;
|
||||
blockquoteActive: boolean;
|
||||
@@ -78,6 +81,7 @@ export const MobileEditorToolbar = ({
|
||||
onPickImage: () => void;
|
||||
onToggleBold: () => void;
|
||||
onToggleBulletList: () => void;
|
||||
onToggleTaskList: () => void;
|
||||
onIncreaseListIndent: () => void;
|
||||
onDecreaseListIndent: () => void;
|
||||
onToggleBlockquote: () => void;
|
||||
@@ -87,6 +91,7 @@ export const MobileEditorToolbar = ({
|
||||
image: <ImagePlus aria-hidden="true" size={18} strokeWidth={2} />,
|
||||
bold: <Bold aria-hidden="true" size={17} strokeWidth={2.4} />,
|
||||
bulletList: <List aria-hidden="true" size={18} strokeWidth={2.2} />,
|
||||
taskList: <ListTodo aria-hidden="true" size={18} strokeWidth={2.1} />,
|
||||
increaseListIndent: <ListIndentIncrease aria-hidden="true" size={18} strokeWidth={2.1} />,
|
||||
decreaseListIndent: <ListIndentDecrease aria-hidden="true" size={18} strokeWidth={2.1} />,
|
||||
blockquote: <Quote aria-hidden="true" size={17} strokeWidth={2.2} />,
|
||||
@@ -96,6 +101,7 @@ export const MobileEditorToolbar = ({
|
||||
image: onPickImage,
|
||||
bold: onToggleBold,
|
||||
bulletList: onToggleBulletList,
|
||||
taskList: onToggleTaskList,
|
||||
increaseListIndent: onIncreaseListIndent,
|
||||
decreaseListIndent: onDecreaseListIndent,
|
||||
blockquote: onToggleBlockquote,
|
||||
@@ -104,6 +110,7 @@ export const MobileEditorToolbar = ({
|
||||
const activeStates: Partial<Record<MobileEditorToolbarActionId, boolean>> = {
|
||||
bold: boldActive,
|
||||
bulletList: bulletListActive,
|
||||
taskList: taskListActive,
|
||||
blockquote: blockquoteActive,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
||||
import { mergeAttributes } from "@tiptap/core";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { TableKit } from "@tiptap/extension-table";
|
||||
@@ -187,6 +188,8 @@ export const MobileStandaloneTiptapEditor = ({
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({ codeBlock: false }),
|
||||
TaskList,
|
||||
TaskItem.configure({ nested: true }),
|
||||
EdgeEverCodeBlock.configure({ lowlight: codeBlockLowlight, defaultLanguage: "plaintext" }),
|
||||
MergeDivider,
|
||||
...createEdgeEverMathematics(),
|
||||
@@ -878,6 +881,7 @@ export const MobileStandaloneTiptapEditor = ({
|
||||
!memo || !editor || saveState === "loading" || saveState === "compressing" || saveState === "uploading" || saveState === "leaving";
|
||||
const currentNotebookLabel =
|
||||
notebookOptions.find((notebook) => notebook.id === memo?.notebookId)?.name ?? t("editor.notebookFallback");
|
||||
const activeListItemType = editor?.isActive("taskItem") ? "taskItem" : "listItem";
|
||||
|
||||
const fallbackMarkdown = memo ? docToMarkdown(contentJsonRef.current) : "";
|
||||
const runEditorCommand = (command: () => boolean) => {
|
||||
@@ -925,15 +929,17 @@ export const MobileStandaloneTiptapEditor = ({
|
||||
disabled={editorActionDisabled}
|
||||
boldActive={Boolean(editor?.isActive("bold"))}
|
||||
bulletListActive={Boolean(editor?.isActive("bulletList"))}
|
||||
increaseListIndentAvailable={Boolean(editor?.can().chain().focus().sinkListItem("listItem").run())}
|
||||
decreaseListIndentAvailable={Boolean(editor?.can().chain().focus().liftListItem("listItem").run())}
|
||||
taskListActive={Boolean(editor?.isActive("taskList"))}
|
||||
increaseListIndentAvailable={Boolean(editor?.can().chain().focus().sinkListItem(activeListItemType).run())}
|
||||
decreaseListIndentAvailable={Boolean(editor?.can().chain().focus().liftListItem(activeListItemType).run())}
|
||||
blockquoteActive={Boolean(editor?.isActive("blockquote"))}
|
||||
locale={locale}
|
||||
onPickImage={() => imageInputRef.current?.click()}
|
||||
onToggleBold={() => runEditorCommand(() => editor?.chain().focus().toggleBold().run() ?? false)}
|
||||
onToggleBulletList={() => runEditorCommand(() => editor?.chain().focus().toggleBulletList().run() ?? false)}
|
||||
onIncreaseListIndent={() => runEditorCommand(() => editor?.chain().focus().sinkListItem("listItem").run() ?? false)}
|
||||
onDecreaseListIndent={() => runEditorCommand(() => editor?.chain().focus().liftListItem("listItem").run() ?? false)}
|
||||
onToggleTaskList={() => runEditorCommand(() => editor?.chain().focus().toggleTaskList().run() ?? false)}
|
||||
onIncreaseListIndent={() => runEditorCommand(() => editor?.chain().focus().sinkListItem(activeListItemType).run() ?? false)}
|
||||
onDecreaseListIndent={() => runEditorCommand(() => editor?.chain().focus().liftListItem(activeListItemType).run() ?? false)}
|
||||
onToggleBlockquote={() => runEditorCommand(() => editor?.chain().focus().toggleBlockquote().run() ?? false)}
|
||||
onSetHorizontalRule={() => runEditorCommand(() => editor?.chain().focus().setHorizontalRule().run() ?? false)}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "katex/dist/katex.min.css";
|
||||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
||||
import { TableKit } from "@tiptap/extension-table";
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
@@ -79,6 +80,8 @@ const SharedDocument = ({ share, token }: { share: PublicMemoShare; token: strin
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({ codeBlock: false, link: { openOnClick: true } }),
|
||||
TaskList,
|
||||
TaskItem.configure({ nested: true }),
|
||||
EdgeEverCodeBlock.configure({ lowlight: codeBlockLowlight, defaultLanguage: "plaintext" }),
|
||||
MergeDivider,
|
||||
...createEdgeEverMathematics(),
|
||||
|
||||
@@ -472,6 +472,47 @@ textarea {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > label {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
margin-top: 0.2rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > label input {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0;
|
||||
accent-color: var(--brand-green);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > div {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] li[data-type="taskItem"] > div > p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ProseMirror ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
margin: 0.25rem 0 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.ProseMirror blockquote {
|
||||
margin: 0 0 1rem;
|
||||
border-left: 3px solid var(--brand-green);
|
||||
|
||||
@@ -96,6 +96,46 @@
|
||||
margin: 1mm 0;
|
||||
}
|
||||
|
||||
.edgeever-print-content ul[data-type="taskList"] {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.edgeever-print-content li[data-type="taskItem"] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 2mm;
|
||||
break-inside: avoid-page;
|
||||
}
|
||||
|
||||
.edgeever-print-content li[data-type="taskItem"] > label {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
margin-top: 0.8mm;
|
||||
}
|
||||
|
||||
.edgeever-print-content li[data-type="taskItem"] > label input {
|
||||
width: 4mm;
|
||||
height: 4mm;
|
||||
margin: 0;
|
||||
accent-color: #16a06e;
|
||||
}
|
||||
|
||||
.edgeever-print-content li[data-type="taskItem"] > div {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.edgeever-print-content li[data-type="taskItem"] > div > p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.edgeever-print-content ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
margin: 1mm 0 0;
|
||||
padding-left: 6mm;
|
||||
}
|
||||
|
||||
.edgeever-print-content blockquote {
|
||||
break-inside: avoid-page;
|
||||
margin: 4mm 0;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@tanstack/react-query": "latest",
|
||||
"@tiptap/core": "3.27.1",
|
||||
"@tiptap/extension-image": "^3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-placeholder": "latest",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
"@tiptap/react": "latest",
|
||||
@@ -116,6 +117,7 @@
|
||||
"@tanstack/react-query-persist-client": "^5.101.2",
|
||||
"@tiptap/extension-code-block": "3.27.1",
|
||||
"@tiptap/extension-image": "3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-placeholder": "3.27.1",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
"@tiptap/react": "3.27.1",
|
||||
@@ -215,6 +217,7 @@
|
||||
"dependencies": {
|
||||
"@tiptap/core": "3.27.1",
|
||||
"@tiptap/extension-image": "3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-mathematics": "3.27.1",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
"@tiptap/markdown": "3.27.1",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"@tanstack/react-query": "latest",
|
||||
"@tiptap/core": "3.27.1",
|
||||
"@tiptap/extension-image": "^3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-placeholder": "latest",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
"@tiptap/react": "latest",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@tiptap/core": "3.27.1",
|
||||
"@tiptap/extension-image": "3.27.1",
|
||||
"@tiptap/extension-list": "3.27.1",
|
||||
"@tiptap/extension-mathematics": "3.27.1",
|
||||
"@tiptap/extension-table": "3.27.1",
|
||||
"@tiptap/markdown": "3.27.1",
|
||||
|
||||
@@ -127,6 +127,51 @@ describe("Nested list Markdown conversion", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Markdown task list conversion", () => {
|
||||
const markdown = [
|
||||
"- [ ] Pending task",
|
||||
"- [x] Completed task",
|
||||
" - [ ] Nested task",
|
||||
].join("\n");
|
||||
|
||||
test("preserves checked state and nesting through a Markdown round trip", () => {
|
||||
const doc = markdownToDoc(markdown);
|
||||
const taskList = doc.content[0];
|
||||
|
||||
expect(taskList?.type).toBe("taskList");
|
||||
expect(taskList?.content?.[0]).toMatchObject({
|
||||
type: "taskItem",
|
||||
attrs: { checked: false },
|
||||
});
|
||||
expect(taskList?.content?.[1]).toMatchObject({
|
||||
type: "taskItem",
|
||||
attrs: { checked: true },
|
||||
});
|
||||
expect(taskList?.content?.[1]?.content?.[1]?.type).toBe("taskList");
|
||||
expect(docToMarkdown(doc)).toBe(markdown);
|
||||
});
|
||||
|
||||
test("recovers task semantics retained only in the Markdown compatibility copy", () => {
|
||||
const legacyDoc = {
|
||||
type: "doc",
|
||||
content: [{
|
||||
type: "bulletList",
|
||||
content: [{
|
||||
type: "listItem",
|
||||
content: [{ type: "paragraph", content: [{ type: "text", text: "Pending task" }] }],
|
||||
}],
|
||||
}],
|
||||
};
|
||||
|
||||
const resolved = resolveMemoContentDoc(legacyDoc, "- [ ] Pending task");
|
||||
expect(resolved.content[0]?.type).toBe("taskList");
|
||||
expect(resolved.content[0]?.content?.[0]).toMatchObject({
|
||||
type: "taskItem",
|
||||
attrs: { checked: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Mermaid Markdown conversion", () => {
|
||||
const markdown = "```mermaid\nflowchart LR\n A --> B\n```";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Image from "@tiptap/extension-image";
|
||||
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
||||
import { TableKit } from "@tiptap/extension-table";
|
||||
import { Markdown, MarkdownManager } from "@tiptap/markdown";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
@@ -71,6 +72,8 @@ export const emptyDoc = (): TiptapDoc => ({
|
||||
const markdownManager = new MarkdownManager({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
TaskList,
|
||||
TaskItem.configure({ nested: true }),
|
||||
TableKit,
|
||||
Image,
|
||||
MergeDivider,
|
||||
@@ -113,6 +116,7 @@ export const resolveMemoContentDoc = (
|
||||
if (
|
||||
!contentMarkdown?.trim() ||
|
||||
docContainsNodeType(currentDoc, "table") ||
|
||||
docContainsNodeType(currentDoc, "taskList") ||
|
||||
docContainsNodeType(currentDoc, "edgeeverThemeBlock") ||
|
||||
docContainsNodeType(currentDoc, MERGE_DIVIDER_NODE_TYPE) ||
|
||||
docContainsNodeType(currentDoc, BLOCK_MATH_NODE_TYPE) ||
|
||||
@@ -125,8 +129,10 @@ export const resolveMemoContentDoc = (
|
||||
// Some older saves left an empty JSON document behind while retaining the
|
||||
// real body in Markdown. Treat that as a compatibility case too; otherwise
|
||||
// the editor can show the Markdown body while list excerpts see an empty
|
||||
// JSON document. Also recover merge dividers when only Markdown still has them.
|
||||
// JSON document. Also recover task lists and merge dividers when only Markdown
|
||||
// still retains their semantics.
|
||||
return docContainsNodeType(markdownDoc, "table")
|
||||
|| docContainsNodeType(markdownDoc, "taskList")
|
||||
|| docContainsNodeType(markdownDoc, MERGE_DIVIDER_NODE_TYPE)
|
||||
|| docContainsNodeType(markdownDoc, BLOCK_MATH_NODE_TYPE)
|
||||
|| docContainsNodeType(markdownDoc, INLINE_MATH_NODE_TYPE)
|
||||
|
||||
@@ -808,6 +808,7 @@ export const enUS = {
|
||||
strike: "Strikethrough",
|
||||
inlineCode: "Inline code",
|
||||
bulletList: "Bulleted list",
|
||||
taskList: "Task list",
|
||||
orderedList: "Numbered list",
|
||||
increaseListIndent: "Increase list level (Tab)",
|
||||
decreaseListIndent: "Decrease list level (Shift + Tab)",
|
||||
@@ -1117,7 +1118,7 @@ export const enUS = {
|
||||
weeklyReview: {
|
||||
title: "Weekly Review & Status",
|
||||
description: "Summarize weekly highlights, blockers, and next week's key priorities.",
|
||||
contentMarkdown: "# 🗓️ Weekly Status Report\n\n## 🌟 Highlights\n\n- [x] **[Project/Feature]** Accomplishment details\n- [x] **[Project/Feature]** Accomplishment details\n\n## 🚧 Blockers & Risks\n\n- ⚠️ **Blocker**: Reason and required support\n\n## 🎯 Next Week Priorities\n\n1. [ ] \n2. [ ] \n3. [ ] \n\n## 💡 Reflection & Insights\n\n- \n",
|
||||
contentMarkdown: "# 🗓️ Weekly Status Report\n\n## 🌟 Highlights\n\n- [x] **[Project/Feature]** Accomplishment details\n- [x] **[Project/Feature]** Accomplishment details\n\n## 🚧 Blockers & Risks\n\n- ⚠️ **Blocker**: Reason and required support\n\n## 🎯 Next Week Priorities\n\n- [ ] \n- [ ] \n- [ ] \n\n## 💡 Reflection & Insights\n\n- \n",
|
||||
},
|
||||
reading: {
|
||||
title: "Reading Note Card",
|
||||
|
||||
@@ -808,6 +808,7 @@ export const zhCN = {
|
||||
strike: "删除线",
|
||||
inlineCode: "行内代码",
|
||||
bulletList: "无序列表",
|
||||
taskList: "任务清单",
|
||||
orderedList: "有序列表",
|
||||
increaseListIndent: "增加列表层级(Tab)",
|
||||
decreaseListIndent: "减少列表层级(Shift + Tab)",
|
||||
@@ -1117,7 +1118,7 @@ export const zhCN = {
|
||||
weeklyReview: {
|
||||
title: "周报与进展复盘",
|
||||
description: "梳理本周核心产出、风险卡点与下周关键优先级。",
|
||||
contentMarkdown: "# 🗓️ 工作周报\n\n## 🌟 本周核心进展 (Highlights)\n\n- [x] **[项目/功能]** 完成情况与成果说明\n- [x] **[项目/功能]** 完成情况与成果说明\n\n## 🚧 卡点与风险 (Blockers & Risks)\n\n- ⚠️ **阻塞项**:原因及所需支持\n\n## 🎯 下周优先级 (Next Week Priorities)\n\n1. [ ] \n2. [ ] \n3. [ ] \n\n## 💡 总结与思考\n\n- \n",
|
||||
contentMarkdown: "# 🗓️ 工作周报\n\n## 🌟 本周核心进展 (Highlights)\n\n- [x] **[项目/功能]** 完成情况与成果说明\n- [x] **[项目/功能]** 完成情况与成果说明\n\n## 🚧 卡点与风险 (Blockers & Risks)\n\n- ⚠️ **阻塞项**:原因及所需支持\n\n## 🎯 下周优先级 (Next Week Priorities)\n\n- [ ] \n- [ ] \n- [ ] \n\n## 💡 总结与思考\n\n- \n",
|
||||
},
|
||||
reading: {
|
||||
title: "深度阅读卡片",
|
||||
|
||||
@@ -16,6 +16,7 @@ describe("mobile editor contract", () => {
|
||||
"image",
|
||||
"bold",
|
||||
"bulletList",
|
||||
"taskList",
|
||||
"increaseListIndent",
|
||||
"decreaseListIndent",
|
||||
"blockquote",
|
||||
@@ -31,6 +32,7 @@ describe("mobile editor contract", () => {
|
||||
expect(getMobileEditorPlaceholder("en-US")).toBe("Start writing...");
|
||||
expect(getMobileEditorToolbarLabel("zh-CN")).toBe("编辑器工具栏");
|
||||
expect(getMobileEditorToolbarActionLabel("bulletList", "en-US")).toBe("Bullet list");
|
||||
expect(getMobileEditorToolbarActionLabel("taskList", "zh-CN")).toBe("任务清单");
|
||||
expect(getMobileEditorToolbarActionLabel("increaseListIndent", "zh-CN")).toBe("增加列表层级(Tab)");
|
||||
expect(getMobileEditorToolbarActionLabel("decreaseListIndent", "en-US")).toBe("Decrease list level (Shift + Tab)");
|
||||
expect(getMobileEditorImageScaleLabel("zh-CN")).toBe("图片显示尺寸");
|
||||
|
||||
@@ -6,6 +6,7 @@ export type MobileEditorToolbarActionId =
|
||||
| "image"
|
||||
| "bold"
|
||||
| "bulletList"
|
||||
| "taskList"
|
||||
| "increaseListIndent"
|
||||
| "decreaseListIndent"
|
||||
| "blockquote"
|
||||
@@ -13,6 +14,7 @@ export type MobileEditorToolbarActionId =
|
||||
|
||||
export const MOBILE_EDITOR_ACTIVE_FLAGS = {
|
||||
bold: 1,
|
||||
taskList: 2,
|
||||
bulletList: 8,
|
||||
blockquote: 16,
|
||||
} as const;
|
||||
@@ -21,6 +23,7 @@ export const MOBILE_EDITOR_TOOLBAR_ACTIONS = [
|
||||
{ id: "image", activeFlag: 0 },
|
||||
{ id: "bold", activeFlag: MOBILE_EDITOR_ACTIVE_FLAGS.bold },
|
||||
{ id: "bulletList", activeFlag: MOBILE_EDITOR_ACTIVE_FLAGS.bulletList },
|
||||
{ id: "taskList", activeFlag: MOBILE_EDITOR_ACTIVE_FLAGS.taskList },
|
||||
{ id: "increaseListIndent", activeFlag: 0 },
|
||||
{ id: "decreaseListIndent", activeFlag: 0 },
|
||||
{ id: "blockquote", activeFlag: MOBILE_EDITOR_ACTIVE_FLAGS.blockquote },
|
||||
@@ -38,6 +41,7 @@ const MOBILE_EDITOR_COPY = {
|
||||
image: "上传图片",
|
||||
bold: "加粗",
|
||||
bulletList: "无序列表",
|
||||
taskList: "任务清单",
|
||||
increaseListIndent: "增加列表层级(Tab)",
|
||||
decreaseListIndent: "减少列表层级(Shift + Tab)",
|
||||
blockquote: "引用",
|
||||
@@ -58,6 +62,7 @@ const MOBILE_EDITOR_COPY = {
|
||||
image: "Upload image",
|
||||
bold: "Bold",
|
||||
bulletList: "Bullet list",
|
||||
taskList: "Task list",
|
||||
increaseListIndent: "Increase list level (Tab)",
|
||||
decreaseListIndent: "Decrease list level (Shift + Tab)",
|
||||
blockquote: "Quote",
|
||||
|
||||
Reference in new issue
Block a user