@comitor/ui 3.11.1 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/dist/chat/chat-composer.d.ts +36 -0
- package/dist/chat/chat-composer.js +85 -0
- package/dist/chat/chat-composer.js.map +1 -0
- package/dist/chat/chat-constants.d.ts +63 -0
- package/dist/chat/chat-constants.js +1 -0
- package/dist/chat/chat-constants.js.map +1 -0
- package/dist/chat/chat-message.d.ts +51 -0
- package/dist/chat/chat-message.js +137 -0
- package/dist/chat/chat-message.js.map +1 -0
- package/dist/chat/chat-sources.d.ts +25 -0
- package/dist/chat/chat-sources.js +97 -0
- package/dist/chat/chat-sources.js.map +1 -0
- package/dist/chat/chat-steps.d.ts +20 -0
- package/dist/chat/chat-steps.js +84 -0
- package/dist/chat/chat-steps.js.map +1 -0
- package/dist/chat/chat-thread.d.ts +21 -0
- package/dist/chat/chat-thread.js +87 -0
- package/dist/chat/chat-thread.js.map +1 -0
- package/dist/chat/index.d.ts +12 -0
- package/dist/chat/index.js +14 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/components/composite/gantt-constants.d.ts +127 -0
- package/dist/components/composite/gantt-constants.js +5 -0
- package/dist/components/composite/gantt-constants.js.map +1 -0
- package/dist/components/composite/gantt-utils.d.ts +106 -0
- package/dist/components/composite/gantt-utils.js +196 -0
- package/dist/components/composite/gantt-utils.js.map +1 -0
- package/dist/components/composite/gantt.d.ts +93 -0
- package/dist/components/composite/gantt.js +1137 -0
- package/dist/components/composite/gantt.js.map +1 -0
- package/dist/components/composite/stat-card.d.ts +1 -1
- package/dist/components/composite/tree-view-constants.d.ts +96 -0
- package/dist/components/composite/tree-view-constants.js +85 -0
- package/dist/components/composite/tree-view-constants.js.map +1 -0
- package/dist/components/composite/tree-view.d.ts +58 -0
- package/dist/components/composite/tree-view.js +385 -0
- package/dist/components/composite/tree-view.js.map +1 -0
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/editor/bubble-toolbar.d.ts +1 -1
- package/dist/editor/editor-core.d.ts +2 -2
- package/dist/editor/editor-core.js +6 -0
- package/dist/editor/editor-core.js.map +1 -1
- package/dist/editor/editor-types.d.ts +1 -1
- package/dist/editor/fixed-toolbar.d.ts +1 -1
- package/dist/editor/image-upload.d.ts +1 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/markdown-editor.d.ts +1 -1
- package/dist/editor/markdown-extensions.d.ts +3 -1
- package/dist/editor/markdown-extensions.js +3 -1
- package/dist/editor/markdown-extensions.js.map +1 -1
- package/dist/editor/rich-text-editor.d.ts +1 -1
- package/dist/editor/slash-commands.d.ts +1 -1
- package/dist/editor/toolbar-actions.d.ts +1 -1
- package/dist/{editor-types-CSrMeuSw.d.ts → editor-types-BacXD2VN.d.ts} +7 -1
- package/dist/i18n/locale-provider.d.ts +3 -0
- package/dist/i18n/messages.d.ts +6 -0
- package/dist/i18n/messages.js +11 -2
- package/dist/i18n/messages.js.map +1 -1
- package/dist/i18n/namespaces/chat.d.ts +8 -0
- package/dist/i18n/namespaces/chat.js +47 -0
- package/dist/i18n/namespaces/chat.js.map +1 -0
- package/dist/i18n/namespaces/gantt.d.ts +10 -0
- package/dist/i18n/namespaces/gantt.js +57 -0
- package/dist/i18n/namespaces/gantt.js.map +1 -0
- package/dist/i18n/namespaces/tree-view.d.ts +8 -0
- package/dist/i18n/namespaces/tree-view.js +21 -0
- package/dist/i18n/namespaces/tree-view.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/editor/editor-core.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Lõi CHUNG của hai trình soạn — `MarkdownEditor` (lưu markdown) và `RichTextEditor` (lưu HTML).\n *\n * Hai component công khai chỉ là hai lớp mỏng: chọn định dạng, thanh công cụ mặc định, và tên của cặp\n * đọc/ghi trên handle (`getMarkdown` / `getHTML`). Mọi thứ còn lại — menu `/` và `@`, ảnh, liên kết,\n * phím tắt, trợ năng, đồng bộ `value` — nằm ở đây, MỘT bản: hai bản là hai nơi để sửa một lỗi mà\n * quên nơi kia.\n *\n * Không xuất khỏi entry: app chọn định dạng bằng cách chọn COMPONENT, không bằng prop. Định dạng quyết\n * định cả lược đồ (`createSchemaExtensions`), mà lược đồ chỉ dựng một lần lúc mount — một prop đổi được\n * lúc chạy sẽ là một prop nói dối.\n */\n\nimport type { AnyExtension, Editor } from \"@tiptap/core\";\nimport { PluginKey } from \"@tiptap/pm/state\";\nimport { EditorContent, useEditor } from \"@tiptap/react\";\nimport * as React from \"react\";\nimport { LetterAvatar } from \"../components/composite/letter-avatar.js\";\nimport { toast } from \"../components/composite/toast.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { matchesSearch } from \"../lib/text-search.js\";\nimport { useAsRef } from \"../lib/use-as-ref.js\";\nimport { useLazyRef } from \"../lib/use-lazy-ref.js\";\nimport { BubbleToolbar } from \"./bubble-toolbar.js\";\nimport type {\n EditorBaseProps,\n EditorFormat,\n EditorHandleBase,\n EditorToolbarMode,\n MarkdownEditorMentionItem\n} from \"./editor-types.js\";\nimport { FixedToolbar } from \"./fixed-toolbar.js\";\nimport {\n createImageUploadExtension,\n type ImageUploadHandlers,\n insertImageFiles,\n type PendingUploads\n} from \"./image-upload.js\";\nimport { MARKDOWN_EDITOR_CONTENT_CLASS } from \"./markdown-editor-classes.js\";\nimport {\n createEditorKeymap,\n createSchemaExtensions,\n type EditorKeymapHandlers,\n type MarkdownEditorRuntime\n} from \"./markdown-extensions.js\";\nimport { trimMarkdown } from \"./markdown-syntax.js\";\nimport {\n createSlashCommandExtension,\n filterSlashCommands,\n SLASH_PLUGIN_KEY,\n type SlashMenuItem\n} from \"./slash-commands.js\";\nimport { SuggestionMenu } from \"./suggestion-menu.js\";\nimport { SuggestionStore } from \"./suggestion-store.js\";\n\n/** Handle chung, đọc/ghi theo định dạng của trình soạn — mỗi lớp ngoài đặt tên lại cho cặp này. */\nexport interface EditorCoreHandle extends EditorHandleBase {\n getValue: () => string;\n setValue: (value: string) => void;\n}\n\nexport interface EditorCoreProps<THandle> extends EditorBaseProps {\n format: EditorFormat;\n toolbar: EditorToolbarMode;\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /** `data-slot` của khung ngoài — mỗi trình soạn một tên, để CSS/test của app nhắm đúng. */\n slot: string;\n handleRef?: React.Ref<THandle>;\n /**\n * Dựng handle công khai từ handle chung. Hàm ở cấp MODULE (định danh ổn định), và KHÔNG trải `core`\n * ra (`{ ...core }`): trải là đọc getter `editor` đúng một lần — lúc nó còn `null`.\n */\n createHandle: (core: EditorCoreHandle) => THandle;\n}\n\ntype MentionSelection = { id: string; label: string; kind: string | null };\n\nconst MENTION_PLUGIN_KEY = new PluginKey(\"comitorMentionMenu\");\n\n/** Trần số người hiện khi nguồn là MẢNG mà chưa gõ gì — gõ thêm chữ là lọc; 500 dòng DOM thì không. */\nconst MENTION_LIST_LIMIT = 50;\n\nfunction filterMentionItems(items: readonly MarkdownEditorMentionItem[], query: string): MarkdownEditorMentionItem[] {\n const term = query.trim();\n const matches = term\n ? items.filter(\n (item) =>\n matchesSearch(item.label, term) ||\n // Đi QUA từ đầu của tên: \"@nguyenvana\" vẫn tới \"Nguyễn Văn An\" — cùng luật `Mention`.\n matchesSearch(item.label.replace(/\\s+/g, \"\"), term.replace(/\\s+/g, \"\")) ||\n (item.description ? matchesSearch(item.description, term) : false)\n )\n : items;\n return matches.slice(0, MENTION_LIST_LIMIT);\n}\n\n/**\n * Giá trị hiện tại theo định dạng. HTML của tài liệu rỗng là `<p></p>` — trả `\"\"` để \"rỗng\" là MỘT\n * giá trị ở cả hai trình soạn: biểu mẫu kiểm trường bắt buộc bằng `value === \"\"` mà không phải biết\n * trình soạn nào đứng sau.\n */\nfunction readValue(editor: Editor, format: EditorFormat): string {\n if (format === \"markdown\") return trimMarkdown(editor.getMarkdown());\n return editor.isEmpty ? \"\" : editor.getHTML();\n}\n\nconst toBoolean = (value: boolean | \"true\" | \"false\" | undefined) => value === true || value === \"true\";\n\nexport function EditorCore<THandle>({\n format,\n toolbar,\n value,\n defaultValue,\n onChange,\n onSubmit,\n onFocus,\n onBlur,\n placeholder,\n editable = true,\n autoFocus = false,\n variant = \"outline\",\n mentions,\n onImageUpload,\n onImageUploadError,\n resolveImageSrc,\n extensions: extraExtensions,\n labels: labelsProp,\n name,\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n \"aria-required\": ariaRequired,\n className,\n contentClassName,\n slot,\n handleRef,\n createHandle\n}: EditorCoreProps<THandle>) {\n const labels = useComitorLabels(\"markdownEditor\", labelsProp);\n\n /*\n * Mọi giá trị đổi được SAU KHI dựng đi qua ref: danh sách extension chỉ dựng MỘT lần (đổi nó là\n * dựng lại trình soạn — mất lịch sử hoàn tác, mất con trỏ), nên extension đọc giá trị mới nhất lúc\n * cần chứ không chép lúc dựng.\n */\n const runtime = useAsRef<MarkdownEditorRuntime>({\n placeholder: placeholder ?? labels.placeholder,\n taskCheckbox: labels.taskCheckbox,\n resolveImageSrc,\n resolveMentionLabel: mentions?.resolveLabel,\n resolveMentionHref: mentions?.getHref\n });\n const latest = useAsRef({ onChange, onFocus, onBlur, mentions, onImageUpload, labels, name });\n const imageHandlers = useAsRef<ImageUploadHandlers>({\n upload: onImageUpload,\n onError: (error, file) => {\n if (onImageUploadError) onImageUploadError(error, file);\n else toast(labels.imageUploadFailed, { variant: \"destructive\", description: file.name || undefined });\n },\n resolveSrc: resolveImageSrc,\n uploadingLabel: labels.imageUploading,\n format\n });\n\n const [linkRequest, setLinkRequest] = React.useState(0);\n const requestLink = React.useCallback(() => setLinkRequest((count) => count + 1), []);\n const keymapHandlers = useAsRef<EditorKeymapHandlers>({\n onLinkShortcut: () => {\n if (!editable) return false;\n requestLink();\n return true;\n },\n onSubmitShortcut: () => {\n if (!onSubmit) return false;\n onSubmit();\n return true;\n }\n });\n\n const initialValue = value ?? defaultValue ?? \"\";\n const lastValue = React.useRef(initialValue);\n const [hiddenValue, setHiddenValue] = React.useState(initialValue);\n const pendingUploads = useLazyRef<PendingUploads>(() => new Set()).current;\n const fileInputRef = React.useRef<HTMLInputElement>(null);\n const pickImage = React.useCallback(() => fileInputRef.current?.click(), []);\n\n const slashStore = useLazyRef(\n () => new SuggestionStore<SlashMenuItem, SlashMenuItem>(SLASH_PLUGIN_KEY, (item) => item)\n ).current;\n const mentionStore = useLazyRef(\n () =>\n new SuggestionStore<MarkdownEditorMentionItem, MentionSelection>(MENTION_PLUGIN_KEY, (item) => ({\n id: item.id,\n label: item.label,\n kind: item.kind ?? null\n }))\n ).current;\n\n const [extensions] = React.useState<AnyExtension[]>(() => [\n ...createSchemaExtensions({\n format,\n runtime,\n mentionSuggestion: {\n pluginKey: MENTION_PLUGIN_KEY,\n char: \"@\",\n // Tên người Việt có dấu cách. Khi chữ gõ có dấu cách mà không còn ai khớp, menu tự thoát\n // (`exitWhenEmpty`) — người dùng đang viết câu, không còn tìm người nữa.\n allowSpaces: true,\n debounce: typeof mentions?.items === \"function\" ? 150 : 0,\n items: async ({ query, signal }) => {\n const source = latest.current.mentions?.items;\n if (!source) return [];\n if (typeof source === \"function\") return [...(await source(query, { signal }))];\n return filterMentionItems(source, query);\n },\n allow: ({ editor: current }) =>\n Boolean(latest.current.mentions) &&\n current.isEditable &&\n !current.isActive(\"codeBlock\") &&\n !current.isActive(\"code\"),\n render: mentionStore.render\n }\n }),\n createSlashCommandExtension({\n items: ({ editor: current, query }) =>\n filterSlashCommands(current, query, latest.current.labels, {\n format,\n pickImage: latest.current.onImageUpload ? () => fileInputRef.current?.click() : undefined\n }),\n render: slashStore.render\n }),\n createImageUploadExtension(imageHandlers, pendingUploads),\n createEditorKeymap(keymapHandlers, format),\n ...(extraExtensions ?? [])\n ]);\n\n /*\n * `<label for={id}>` không trỏ được vào một `div contenteditable` (chỉ `input`/`textarea`/… là\n * \"labelable\") — nên `FormField` dựng nhãn đúng mà vùng soạn vẫn không có tên, và bấm nhãn không\n * đưa con trỏ vào. Effect bên dưới tìm nhãn theo `for` và ghi id của nó vào đây; tên trợ năng đi\n * qua `aria-labelledby` trong bộ thuộc tính — nhờ vậy tên dự phòng (`editorLabel`) được BỎ hẳn\n * chứ không nằm song song với nhãn thật.\n */\n const [labelElementId, setLabelElementId] = React.useState<string | null>(null);\n\n const contentPadding = variant === \"outline\" ? \"min-h-16 px-3 py-2\" : \"min-h-6\";\n\n /*\n * Thuộc tính của vùng soạn. Ổn định theo giá trị: `useEditor` so tuỳ chọn mỗi lượt render và gửi\n * lại `editorProps` mỗi khi định danh đổi — tức mỗi lần FormField bật/tắt `aria-invalid` là một\n * lần cập nhật đúng, chứ không phải mỗi phím gõ.\n *\n * Tên trợ năng: chỉ đặt khi SOẠN được (`role=\"textbox\"`). Ở chế độ đọc, vùng nội dung là văn bản\n * thường; `aria-label` trên một `div` không vai trò là thứ ARIA 1.2 cấm và trình đọc màn hình bỏ qua.\n * ⚠ Chế độ đọc phải GHI ĐÈ `role` chứ không bỏ trống: Tiptap luôn trộn `role=\"textbox\"` mặc định\n * vào TRƯỚC thuộc tính của app (`getEditorViewAttributes`), nên thiếu khoá là danh sách bình luận\n * đọc lên thành một dãy \"ô nhập chỉ đọc\" — đã gặp ở harness.\n */\n const editorProps = React.useMemo(() => {\n const attributes: Record<string, string> = {\n class: cn(MARKDOWN_EDITOR_CONTENT_CLASS, contentPadding, contentClassName)\n };\n if (id) attributes.id = id;\n if (!editable) attributes.role = \"none\";\n if (editable) {\n attributes.role = \"textbox\";\n attributes[\"aria-multiline\"] = \"true\";\n const labelledBy = ariaLabelledBy ?? labelElementId;\n if (ariaLabel || !labelledBy) attributes[\"aria-label\"] = ariaLabel ?? labels.editorLabel;\n if (labelledBy) attributes[\"aria-labelledby\"] = labelledBy;\n if (toBoolean(ariaRequired)) attributes[\"aria-required\"] = \"true\";\n }\n if (ariaDescribedBy) attributes[\"aria-describedby\"] = ariaDescribedBy;\n if (toBoolean(ariaInvalid)) attributes[\"aria-invalid\"] = \"true\";\n return { attributes };\n }, [\n contentPadding,\n contentClassName,\n id,\n editable,\n ariaLabel,\n ariaLabelledBy,\n labelElementId,\n ariaDescribedBy,\n ariaInvalid,\n ariaRequired,\n labels.editorLabel\n ]);\n\n const editor = useEditor({\n extensions,\n content: initialValue,\n contentType: format,\n editable,\n autofocus: autoFocus ? \"end\" : false,\n // Next App Router render ở máy chủ: dựng trình soạn ở client sau lần hydrate đầu, không thì HTML\n // hai phía lệch nhau. Khung rỗng giữ chỗ trong khoảng đó — xem phần render.\n immediatelyRender: false,\n editorProps,\n onUpdate: ({ editor: current }) => {\n const next = readValue(current, format);\n lastValue.current = next;\n latest.current.onChange?.(next);\n if (latest.current.name) setHiddenValue(next);\n },\n onFocus: () => latest.current.onFocus?.(),\n onBlur: () => latest.current.onBlur?.()\n });\n\n // Giá trị đổi từ NGOÀI (xoá sau khi gửi, nạp bản nháp…). Giá trị chính mình vừa phát thì bỏ qua —\n // không thì mỗi phím gõ là một lần nạp lại, con trỏ nhảy về đầu.\n React.useEffect(() => {\n if (!editor || value === undefined || value === lastValue.current) return;\n editor.commands.setContent(value, { contentType: format, emitUpdate: false });\n lastValue.current = value;\n setHiddenValue(value);\n }, [editor, value, format]);\n\n // `useEditor` cố ý giữ nguyên `editable` khi cập nhật tuỳ chọn, nên phải tự đặt. Không phát `update`:\n // đổi chế độ không phải đổi nội dung.\n React.useEffect(() => {\n if (editor && editor.isEditable !== editable) editor.setEditable(editable, false);\n }, [editor, editable]);\n\n // Nối `<label for={id}>` — lý do ở chỗ khai `labelElementId`. Bấm nhãn thì tự đưa con trỏ vào.\n React.useEffect(() => {\n if (!editor || !id || !editable || ariaLabel || ariaLabelledBy) return;\n const label = document.querySelector<HTMLLabelElement>(`label[for=\"${CSS.escape(id)}\"]`);\n if (!label) return;\n if (!label.id) label.id = `${id}-label`;\n setLabelElementId(label.id);\n const focusEditor = (event: MouseEvent) => {\n event.preventDefault();\n editor.commands.focus();\n };\n label.addEventListener(\"click\", focusEditor);\n return () => {\n label.removeEventListener(\"click\", focusEditor);\n setLabelElementId(null);\n };\n }, [editor, id, editable, ariaLabel, ariaLabelledBy]);\n\n /*\n * Handle ỔN ĐỊNH suốt đời component, đọc trình soạn MỚI NHẤT qua ref. Dựng lại theo `editor` thì ai\n * giữ đối tượng cũ — một closure, một biến ngoài React — thấy `editor: null` mãi, vì trình soạn chỉ\n * có SAU lượt render đầu (`immediatelyRender: false`). Đã gặp ở harness.\n */\n const editorRef = useAsRef(editor);\n const core = React.useMemo<EditorCoreHandle>(\n () => ({\n focus: (position = \"end\") => {\n editorRef.current?.commands.focus(position);\n },\n getValue: () => (editorRef.current ? readValue(editorRef.current, format) : lastValue.current),\n setValue: (next) => {\n editorRef.current?.commands.setContent(next, { contentType: format, emitUpdate: true });\n },\n clear: () => {\n editorRef.current?.commands.clearContent(true);\n },\n isEmpty: () => (editorRef.current ? editorRef.current.isEmpty : lastValue.current.trim() === \"\"),\n isUploading: () => pendingUploads.size > 0,\n get editor() {\n return editorRef.current;\n }\n }),\n [editorRef, pendingUploads, format]\n );\n React.useImperativeHandle(handleRef, () => createHandle(core), [createHandle, core]);\n\n return (\n <div\n data-slot={slot}\n data-variant={variant}\n data-toolbar={toolbar}\n data-readonly={!editable || undefined}\n className={cn(\n \"relative min-w-0\",\n variant === \"outline\" &&\n // Khuôn khung của `Textarea`: viền `border-input`, nền trong suốt (dark: `--input-fill`),\n // focus `--primary-ink` + vòng `ring/50`; lỗi đọc từ `aria-invalid` của CHÍNH vùng soạn.\n \"rounded-md border border-input bg-transparent shadow-xs transition-colors duration-150 ease-out focus-within:border-primary-ink focus-within:ring-2 focus-within:ring-ring/50 has-[[aria-invalid=true]]:border-destructive-ink dark:bg-input-fill\",\n className\n )}\n >\n {toolbar === \"fixed\" && editable && (\n <FixedToolbar\n editor={editor}\n labels={labels}\n format={format}\n onLink={requestLink}\n onPickImage={onImageUpload ? pickImage : undefined}\n // Khung `outline` bo góc mà KHÔNG `overflow-hidden` (nó sẽ giết `sticky`) — nền thanh tự bo\n // theo góc của khung. `ghost` không có khung: chừa khoảng cho chữ khỏi dính vào viền dưới.\n className={variant === \"outline\" ? \"rounded-t-[inherit]\" : \"mb-3\"}\n />\n )}\n\n {editor ? (\n <EditorContent editor={editor} />\n ) : (\n // Giữ chỗ tới khi trình soạn dựng xong ở client (`immediatelyRender: false`) — cùng chiều cao tối thiểu.\n <div aria-hidden=\"true\" className={cn(contentPadding, contentClassName)} />\n )}\n\n {editor && editable && (\n <BubbleToolbar\n editor={editor}\n labels={labels}\n format={format}\n formatting={toolbar === \"bubble\"}\n linkRequest={linkRequest}\n />\n )}\n\n {editor && (\n <SuggestionMenu\n editor={editor}\n store={slashStore}\n label={labels.slashMenu}\n emptyLabel={labels.slashEmpty}\n getKey={(item) => item.id}\n renderItem={(item) => (\n <>\n <item.icon aria-hidden=\"true\" />\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.hint && (\n // Cú pháp gõ tắt — mờ, chữ đều, như phím tắt trong `CommandShortcut`.\n <span className=\"ml-auto font-mono text-xs text-muted-foreground\">{item.hint}</span>\n )}\n </>\n )}\n />\n )}\n\n {editor && mentions && (\n <SuggestionMenu\n editor={editor}\n store={mentionStore}\n label={labels.mentionMenu}\n emptyLabel={labels.mentionEmpty}\n loadingLabel={labels.mentionLoading}\n // Id của hai loại có thể trùng nhau (người `u1` và việc `u1`) — khoá phải mang cả loại.\n getKey={(item) => `${item.kind ?? \"\"}:${item.id}`}\n exitWhenEmpty={(query) => /\\s/.test(query)}\n renderItem={(item) => (\n <>\n {item.icon ? (\n <span aria-hidden=\"true\" className=\"flex size-6 shrink-0 items-center justify-center\">\n <item.icon className=\"size-4 text-muted-foreground\" />\n </span>\n ) : (\n <LetterAvatar name={item.label} src={item.avatarUrl} size=\"xs\" aria-hidden=\"true\" />\n )}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.description && (\n <span className=\"max-w-[45%] truncate text-xs text-muted-foreground\">{item.description}</span>\n )}\n </>\n )}\n />\n )}\n\n {onImageUpload && (\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n tabIndex={-1}\n onChange={(event) => {\n const files = Array.from(event.target.files ?? []).filter((file) => file.type.startsWith(\"image/\"));\n // Xoá giá trị để chọn LẠI đúng tệp đó lần sau vẫn phát `change`.\n event.target.value = \"\";\n if (editor && files.length > 0)\n insertImageFiles(editor, files, undefined, imageHandlers.current, pendingUploads);\n }}\n />\n )}\n\n {name && <input type=\"hidden\" name={name} value={hiddenValue} />}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/editor/editor-core.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Lõi CHUNG của hai trình soạn — `MarkdownEditor` (lưu markdown) và `RichTextEditor` (lưu HTML).\n *\n * Hai component công khai chỉ là hai lớp mỏng: chọn định dạng, thanh công cụ mặc định, và tên của cặp\n * đọc/ghi trên handle (`getMarkdown` / `getHTML`). Mọi thứ còn lại — menu `/` và `@`, ảnh, liên kết,\n * phím tắt, trợ năng, đồng bộ `value` — nằm ở đây, MỘT bản: hai bản là hai nơi để sửa một lỗi mà\n * quên nơi kia.\n *\n * Không xuất khỏi entry: app chọn định dạng bằng cách chọn COMPONENT, không bằng prop. Định dạng quyết\n * định cả lược đồ (`createSchemaExtensions`), mà lược đồ chỉ dựng một lần lúc mount — một prop đổi được\n * lúc chạy sẽ là một prop nói dối.\n */\n\nimport type { AnyExtension, Editor } from \"@tiptap/core\";\nimport { PluginKey } from \"@tiptap/pm/state\";\nimport { EditorContent, useEditor } from \"@tiptap/react\";\nimport * as React from \"react\";\nimport { LetterAvatar } from \"../components/composite/letter-avatar.js\";\nimport { toast } from \"../components/composite/toast.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { matchesSearch } from \"../lib/text-search.js\";\nimport { useAsRef } from \"../lib/use-as-ref.js\";\nimport { useLazyRef } from \"../lib/use-lazy-ref.js\";\nimport { BubbleToolbar } from \"./bubble-toolbar.js\";\nimport type {\n EditorBaseProps,\n EditorFormat,\n EditorHandleBase,\n EditorToolbarMode,\n MarkdownEditorMentionItem\n} from \"./editor-types.js\";\nimport { FixedToolbar } from \"./fixed-toolbar.js\";\nimport {\n createImageUploadExtension,\n type ImageUploadHandlers,\n insertImageFiles,\n type PendingUploads\n} from \"./image-upload.js\";\nimport { MARKDOWN_EDITOR_CONTENT_CLASS } from \"./markdown-editor-classes.js\";\nimport {\n createEditorKeymap,\n createSchemaExtensions,\n type EditorKeymapHandlers,\n type MarkdownEditorRuntime\n} from \"./markdown-extensions.js\";\nimport { trimMarkdown } from \"./markdown-syntax.js\";\nimport {\n createSlashCommandExtension,\n filterSlashCommands,\n SLASH_PLUGIN_KEY,\n type SlashMenuItem\n} from \"./slash-commands.js\";\nimport { SuggestionMenu } from \"./suggestion-menu.js\";\nimport { SuggestionStore } from \"./suggestion-store.js\";\n\n/** Handle chung, đọc/ghi theo định dạng của trình soạn — mỗi lớp ngoài đặt tên lại cho cặp này. */\nexport interface EditorCoreHandle extends EditorHandleBase {\n getValue: () => string;\n setValue: (value: string) => void;\n}\n\nexport interface EditorCoreProps<THandle> extends EditorBaseProps {\n format: EditorFormat;\n toolbar: EditorToolbarMode;\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /** `data-slot` của khung ngoài — mỗi trình soạn một tên, để CSS/test của app nhắm đúng. */\n slot: string;\n handleRef?: React.Ref<THandle>;\n /**\n * Dựng handle công khai từ handle chung. Hàm ở cấp MODULE (định danh ổn định), và KHÔNG trải `core`\n * ra (`{ ...core }`): trải là đọc getter `editor` đúng một lần — lúc nó còn `null`.\n */\n createHandle: (core: EditorCoreHandle) => THandle;\n}\n\ntype MentionSelection = { id: string; label: string; kind: string | null };\n\nconst MENTION_PLUGIN_KEY = new PluginKey(\"comitorMentionMenu\");\n\n/** Trần số người hiện khi nguồn là MẢNG mà chưa gõ gì — gõ thêm chữ là lọc; 500 dòng DOM thì không. */\nconst MENTION_LIST_LIMIT = 50;\n\nfunction filterMentionItems(items: readonly MarkdownEditorMentionItem[], query: string): MarkdownEditorMentionItem[] {\n const term = query.trim();\n const matches = term\n ? items.filter(\n (item) =>\n matchesSearch(item.label, term) ||\n // Đi QUA từ đầu của tên: \"@nguyenvana\" vẫn tới \"Nguyễn Văn An\" — cùng luật `Mention`.\n matchesSearch(item.label.replace(/\\s+/g, \"\"), term.replace(/\\s+/g, \"\")) ||\n (item.description ? matchesSearch(item.description, term) : false)\n )\n : items;\n return matches.slice(0, MENTION_LIST_LIMIT);\n}\n\n/**\n * Giá trị hiện tại theo định dạng. HTML của tài liệu rỗng là `<p></p>` — trả `\"\"` để \"rỗng\" là MỘT\n * giá trị ở cả hai trình soạn: biểu mẫu kiểm trường bắt buộc bằng `value === \"\"` mà không phải biết\n * trình soạn nào đứng sau.\n */\nfunction readValue(editor: Editor, format: EditorFormat): string {\n if (format === \"markdown\") return trimMarkdown(editor.getMarkdown());\n return editor.isEmpty ? \"\" : editor.getHTML();\n}\n\nconst toBoolean = (value: boolean | \"true\" | \"false\" | undefined) => value === true || value === \"true\";\n\nexport function EditorCore<THandle>({\n format,\n toolbar,\n value,\n defaultValue,\n onChange,\n onSubmit,\n submitOnEnter = false,\n onFocus,\n onBlur,\n placeholder,\n editable = true,\n autoFocus = false,\n variant = \"outline\",\n mentions,\n onImageUpload,\n onImageUploadError,\n resolveImageSrc,\n extensions: extraExtensions,\n labels: labelsProp,\n name,\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n \"aria-required\": ariaRequired,\n className,\n contentClassName,\n slot,\n handleRef,\n createHandle\n}: EditorCoreProps<THandle>) {\n const labels = useComitorLabels(\"markdownEditor\", labelsProp);\n\n /*\n * Mọi giá trị đổi được SAU KHI dựng đi qua ref: danh sách extension chỉ dựng MỘT lần (đổi nó là\n * dựng lại trình soạn — mất lịch sử hoàn tác, mất con trỏ), nên extension đọc giá trị mới nhất lúc\n * cần chứ không chép lúc dựng.\n */\n const runtime = useAsRef<MarkdownEditorRuntime>({\n placeholder: placeholder ?? labels.placeholder,\n taskCheckbox: labels.taskCheckbox,\n resolveImageSrc,\n resolveMentionLabel: mentions?.resolveLabel,\n resolveMentionHref: mentions?.getHref\n });\n const latest = useAsRef({ onChange, onFocus, onBlur, mentions, onImageUpload, labels, name });\n const imageHandlers = useAsRef<ImageUploadHandlers>({\n upload: onImageUpload,\n onError: (error, file) => {\n if (onImageUploadError) onImageUploadError(error, file);\n else toast(labels.imageUploadFailed, { variant: \"destructive\", description: file.name || undefined });\n },\n resolveSrc: resolveImageSrc,\n uploadingLabel: labels.imageUploading,\n format\n });\n\n const [linkRequest, setLinkRequest] = React.useState(0);\n const requestLink = React.useCallback(() => setLinkRequest((count) => count + 1), []);\n const keymapHandlers = useAsRef<EditorKeymapHandlers>({\n onLinkShortcut: () => {\n if (!editable) return false;\n requestLink();\n return true;\n },\n onSubmitShortcut: () => {\n if (!onSubmit) return false;\n onSubmit();\n return true;\n },\n onEnterShortcut: () => {\n // Menu `@`/`/` đang mở: Enter là CHỌN mục — plugin gợi ý đứng sau keymap này (ưu tiên 1000).\n if (!submitOnEnter || !onSubmit || slashStore.getSnapshot() || mentionStore.getSnapshot()) return false;\n onSubmit();\n return true;\n }\n });\n\n const initialValue = value ?? defaultValue ?? \"\";\n const lastValue = React.useRef(initialValue);\n const [hiddenValue, setHiddenValue] = React.useState(initialValue);\n const pendingUploads = useLazyRef<PendingUploads>(() => new Set()).current;\n const fileInputRef = React.useRef<HTMLInputElement>(null);\n const pickImage = React.useCallback(() => fileInputRef.current?.click(), []);\n\n const slashStore = useLazyRef(\n () => new SuggestionStore<SlashMenuItem, SlashMenuItem>(SLASH_PLUGIN_KEY, (item) => item)\n ).current;\n const mentionStore = useLazyRef(\n () =>\n new SuggestionStore<MarkdownEditorMentionItem, MentionSelection>(MENTION_PLUGIN_KEY, (item) => ({\n id: item.id,\n label: item.label,\n kind: item.kind ?? null\n }))\n ).current;\n\n const [extensions] = React.useState<AnyExtension[]>(() => [\n ...createSchemaExtensions({\n format,\n runtime,\n mentionSuggestion: {\n pluginKey: MENTION_PLUGIN_KEY,\n char: \"@\",\n // Tên người Việt có dấu cách. Khi chữ gõ có dấu cách mà không còn ai khớp, menu tự thoát\n // (`exitWhenEmpty`) — người dùng đang viết câu, không còn tìm người nữa.\n allowSpaces: true,\n debounce: typeof mentions?.items === \"function\" ? 150 : 0,\n items: async ({ query, signal }) => {\n const source = latest.current.mentions?.items;\n if (!source) return [];\n if (typeof source === \"function\") return [...(await source(query, { signal }))];\n return filterMentionItems(source, query);\n },\n allow: ({ editor: current }) =>\n Boolean(latest.current.mentions) &&\n current.isEditable &&\n !current.isActive(\"codeBlock\") &&\n !current.isActive(\"code\"),\n render: mentionStore.render\n }\n }),\n createSlashCommandExtension({\n items: ({ editor: current, query }) =>\n filterSlashCommands(current, query, latest.current.labels, {\n format,\n pickImage: latest.current.onImageUpload ? () => fileInputRef.current?.click() : undefined\n }),\n render: slashStore.render\n }),\n createImageUploadExtension(imageHandlers, pendingUploads),\n createEditorKeymap(keymapHandlers, format),\n ...(extraExtensions ?? [])\n ]);\n\n /*\n * `<label for={id}>` không trỏ được vào một `div contenteditable` (chỉ `input`/`textarea`/… là\n * \"labelable\") — nên `FormField` dựng nhãn đúng mà vùng soạn vẫn không có tên, và bấm nhãn không\n * đưa con trỏ vào. Effect bên dưới tìm nhãn theo `for` và ghi id của nó vào đây; tên trợ năng đi\n * qua `aria-labelledby` trong bộ thuộc tính — nhờ vậy tên dự phòng (`editorLabel`) được BỎ hẳn\n * chứ không nằm song song với nhãn thật.\n */\n const [labelElementId, setLabelElementId] = React.useState<string | null>(null);\n\n const contentPadding = variant === \"outline\" ? \"min-h-16 px-3 py-2\" : \"min-h-6\";\n\n /*\n * Thuộc tính của vùng soạn. Ổn định theo giá trị: `useEditor` so tuỳ chọn mỗi lượt render và gửi\n * lại `editorProps` mỗi khi định danh đổi — tức mỗi lần FormField bật/tắt `aria-invalid` là một\n * lần cập nhật đúng, chứ không phải mỗi phím gõ.\n *\n * Tên trợ năng: chỉ đặt khi SOẠN được (`role=\"textbox\"`). Ở chế độ đọc, vùng nội dung là văn bản\n * thường; `aria-label` trên một `div` không vai trò là thứ ARIA 1.2 cấm và trình đọc màn hình bỏ qua.\n * ⚠ Chế độ đọc phải GHI ĐÈ `role` chứ không bỏ trống: Tiptap luôn trộn `role=\"textbox\"` mặc định\n * vào TRƯỚC thuộc tính của app (`getEditorViewAttributes`), nên thiếu khoá là danh sách bình luận\n * đọc lên thành một dãy \"ô nhập chỉ đọc\" — đã gặp ở harness.\n */\n const editorProps = React.useMemo(() => {\n const attributes: Record<string, string> = {\n class: cn(MARKDOWN_EDITOR_CONTENT_CLASS, contentPadding, contentClassName)\n };\n if (id) attributes.id = id;\n if (!editable) attributes.role = \"none\";\n if (editable) {\n attributes.role = \"textbox\";\n attributes[\"aria-multiline\"] = \"true\";\n const labelledBy = ariaLabelledBy ?? labelElementId;\n if (ariaLabel || !labelledBy) attributes[\"aria-label\"] = ariaLabel ?? labels.editorLabel;\n if (labelledBy) attributes[\"aria-labelledby\"] = labelledBy;\n if (toBoolean(ariaRequired)) attributes[\"aria-required\"] = \"true\";\n }\n if (ariaDescribedBy) attributes[\"aria-describedby\"] = ariaDescribedBy;\n if (toBoolean(ariaInvalid)) attributes[\"aria-invalid\"] = \"true\";\n return { attributes };\n }, [\n contentPadding,\n contentClassName,\n id,\n editable,\n ariaLabel,\n ariaLabelledBy,\n labelElementId,\n ariaDescribedBy,\n ariaInvalid,\n ariaRequired,\n labels.editorLabel\n ]);\n\n const editor = useEditor({\n extensions,\n content: initialValue,\n contentType: format,\n editable,\n autofocus: autoFocus ? \"end\" : false,\n // Next App Router render ở máy chủ: dựng trình soạn ở client sau lần hydrate đầu, không thì HTML\n // hai phía lệch nhau. Khung rỗng giữ chỗ trong khoảng đó — xem phần render.\n immediatelyRender: false,\n editorProps,\n onUpdate: ({ editor: current }) => {\n const next = readValue(current, format);\n lastValue.current = next;\n latest.current.onChange?.(next);\n if (latest.current.name) setHiddenValue(next);\n },\n onFocus: () => latest.current.onFocus?.(),\n onBlur: () => latest.current.onBlur?.()\n });\n\n // Giá trị đổi từ NGOÀI (xoá sau khi gửi, nạp bản nháp…). Giá trị chính mình vừa phát thì bỏ qua —\n // không thì mỗi phím gõ là một lần nạp lại, con trỏ nhảy về đầu.\n React.useEffect(() => {\n if (!editor || value === undefined || value === lastValue.current) return;\n editor.commands.setContent(value, { contentType: format, emitUpdate: false });\n lastValue.current = value;\n setHiddenValue(value);\n }, [editor, value, format]);\n\n // `useEditor` cố ý giữ nguyên `editable` khi cập nhật tuỳ chọn, nên phải tự đặt. Không phát `update`:\n // đổi chế độ không phải đổi nội dung.\n React.useEffect(() => {\n if (editor && editor.isEditable !== editable) editor.setEditable(editable, false);\n }, [editor, editable]);\n\n // Nối `<label for={id}>` — lý do ở chỗ khai `labelElementId`. Bấm nhãn thì tự đưa con trỏ vào.\n React.useEffect(() => {\n if (!editor || !id || !editable || ariaLabel || ariaLabelledBy) return;\n const label = document.querySelector<HTMLLabelElement>(`label[for=\"${CSS.escape(id)}\"]`);\n if (!label) return;\n if (!label.id) label.id = `${id}-label`;\n setLabelElementId(label.id);\n const focusEditor = (event: MouseEvent) => {\n event.preventDefault();\n editor.commands.focus();\n };\n label.addEventListener(\"click\", focusEditor);\n return () => {\n label.removeEventListener(\"click\", focusEditor);\n setLabelElementId(null);\n };\n }, [editor, id, editable, ariaLabel, ariaLabelledBy]);\n\n /*\n * Handle ỔN ĐỊNH suốt đời component, đọc trình soạn MỚI NHẤT qua ref. Dựng lại theo `editor` thì ai\n * giữ đối tượng cũ — một closure, một biến ngoài React — thấy `editor: null` mãi, vì trình soạn chỉ\n * có SAU lượt render đầu (`immediatelyRender: false`). Đã gặp ở harness.\n */\n const editorRef = useAsRef(editor);\n const core = React.useMemo<EditorCoreHandle>(\n () => ({\n focus: (position = \"end\") => {\n editorRef.current?.commands.focus(position);\n },\n getValue: () => (editorRef.current ? readValue(editorRef.current, format) : lastValue.current),\n setValue: (next) => {\n editorRef.current?.commands.setContent(next, { contentType: format, emitUpdate: true });\n },\n clear: () => {\n editorRef.current?.commands.clearContent(true);\n },\n isEmpty: () => (editorRef.current ? editorRef.current.isEmpty : lastValue.current.trim() === \"\"),\n isUploading: () => pendingUploads.size > 0,\n get editor() {\n return editorRef.current;\n }\n }),\n [editorRef, pendingUploads, format]\n );\n React.useImperativeHandle(handleRef, () => createHandle(core), [createHandle, core]);\n\n return (\n <div\n data-slot={slot}\n data-variant={variant}\n data-toolbar={toolbar}\n data-readonly={!editable || undefined}\n className={cn(\n \"relative min-w-0\",\n variant === \"outline\" &&\n // Khuôn khung của `Textarea`: viền `border-input`, nền trong suốt (dark: `--input-fill`),\n // focus `--primary-ink` + vòng `ring/50`; lỗi đọc từ `aria-invalid` của CHÍNH vùng soạn.\n \"rounded-md border border-input bg-transparent shadow-xs transition-colors duration-150 ease-out focus-within:border-primary-ink focus-within:ring-2 focus-within:ring-ring/50 has-[[aria-invalid=true]]:border-destructive-ink dark:bg-input-fill\",\n className\n )}\n >\n {toolbar === \"fixed\" && editable && (\n <FixedToolbar\n editor={editor}\n labels={labels}\n format={format}\n onLink={requestLink}\n onPickImage={onImageUpload ? pickImage : undefined}\n // Khung `outline` bo góc mà KHÔNG `overflow-hidden` (nó sẽ giết `sticky`) — nền thanh tự bo\n // theo góc của khung. `ghost` không có khung: chừa khoảng cho chữ khỏi dính vào viền dưới.\n className={variant === \"outline\" ? \"rounded-t-[inherit]\" : \"mb-3\"}\n />\n )}\n\n {editor ? (\n <EditorContent editor={editor} />\n ) : (\n // Giữ chỗ tới khi trình soạn dựng xong ở client (`immediatelyRender: false`) — cùng chiều cao tối thiểu.\n <div aria-hidden=\"true\" className={cn(contentPadding, contentClassName)} />\n )}\n\n {editor && editable && (\n <BubbleToolbar\n editor={editor}\n labels={labels}\n format={format}\n formatting={toolbar === \"bubble\"}\n linkRequest={linkRequest}\n />\n )}\n\n {editor && (\n <SuggestionMenu\n editor={editor}\n store={slashStore}\n label={labels.slashMenu}\n emptyLabel={labels.slashEmpty}\n getKey={(item) => item.id}\n renderItem={(item) => (\n <>\n <item.icon aria-hidden=\"true\" />\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.hint && (\n // Cú pháp gõ tắt — mờ, chữ đều, như phím tắt trong `CommandShortcut`.\n <span className=\"ml-auto font-mono text-xs text-muted-foreground\">{item.hint}</span>\n )}\n </>\n )}\n />\n )}\n\n {editor && mentions && (\n <SuggestionMenu\n editor={editor}\n store={mentionStore}\n label={labels.mentionMenu}\n emptyLabel={labels.mentionEmpty}\n loadingLabel={labels.mentionLoading}\n // Id của hai loại có thể trùng nhau (người `u1` và việc `u1`) — khoá phải mang cả loại.\n getKey={(item) => `${item.kind ?? \"\"}:${item.id}`}\n exitWhenEmpty={(query) => /\\s/.test(query)}\n renderItem={(item) => (\n <>\n {item.icon ? (\n <span aria-hidden=\"true\" className=\"flex size-6 shrink-0 items-center justify-center\">\n <item.icon className=\"size-4 text-muted-foreground\" />\n </span>\n ) : (\n <LetterAvatar name={item.label} src={item.avatarUrl} size=\"xs\" aria-hidden=\"true\" />\n )}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.description && (\n <span className=\"max-w-[45%] truncate text-xs text-muted-foreground\">{item.description}</span>\n )}\n </>\n )}\n />\n )}\n\n {onImageUpload && (\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n tabIndex={-1}\n onChange={(event) => {\n const files = Array.from(event.target.files ?? []).filter((file) => file.type.startsWith(\"image/\"));\n // Xoá giá trị để chọn LẠI đúng tệp đó lần sau vẫn phát `change`.\n event.target.value = \"\";\n if (editor && files.length > 0)\n insertImageFiles(editor, files, undefined, imageHandlers.current, pendingUploads);\n }}\n />\n )}\n\n {name && <input type=\"hidden\" name={name} value={hiddenValue} />}\n </div>\n );\n}\n"],"mappings":";AAgZQ,SAqCI,UArCJ,KAqCI,YArCJ;AAhYR,SAAS,iBAAiB;AAC1B,SAAS,eAAe,iBAAiB;AACzC,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,UAAU;AACnB,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAQ9B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EAEA;AAAA,OAEK;AACP,SAAS,qCAAqC;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AA0BhC,MAAM,qBAAqB,IAAI,UAAU,oBAAoB;AAG7D,MAAM,qBAAqB;AAE3B,SAAS,mBAAmB,OAA6C,OAA4C;AACnH,QAAM,OAAO,MAAM,KAAK;AACxB,QAAM,UAAU,OACZ,MAAM;AAAA,IACJ,CAAC,SACC,cAAc,KAAK,OAAO,IAAI;AAAA,IAE9B,cAAc,KAAK,MAAM,QAAQ,QAAQ,EAAE,GAAG,KAAK,QAAQ,QAAQ,EAAE,CAAC,MACrE,KAAK,cAAc,cAAc,KAAK,aAAa,IAAI,IAAI;AAAA,EAChE,IACA;AACJ,SAAO,QAAQ,MAAM,GAAG,kBAAkB;AAC5C;AAOA,SAAS,UAAU,QAAgB,QAA8B;AAC/D,MAAI,WAAW,WAAY,QAAO,aAAa,OAAO,YAAY,CAAC;AACnE,SAAO,OAAO,UAAU,KAAK,OAAO,QAAQ;AAC9C;AAEA,MAAM,YAAY,CAAC,UAAkD,UAAU,QAAQ,UAAU;AAE1F,SAAS,WAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,SAAS,iBAAiB,kBAAkB,UAAU;AAO5D,QAAM,UAAU,SAAgC;AAAA,IAC9C,aAAa,eAAe,OAAO;AAAA,IACnC,cAAc,OAAO;AAAA,IACrB;AAAA,IACA,qBAAqB,UAAU;AAAA,IAC/B,oBAAoB,UAAU;AAAA,EAChC,CAAC;AACD,QAAM,SAAS,SAAS,EAAE,UAAU,SAAS,QAAQ,UAAU,eAAe,QAAQ,KAAK,CAAC;AAC5F,QAAM,gBAAgB,SAA8B;AAAA,IAClD,QAAQ;AAAA,IACR,SAAS,CAAC,OAAO,SAAS;AACxB,UAAI,mBAAoB,oBAAmB,OAAO,IAAI;AAAA,UACjD,OAAM,OAAO,mBAAmB,EAAE,SAAS,eAAe,aAAa,KAAK,QAAQ,OAAU,CAAC;AAAA,IACtG;AAAA,IACA,YAAY;AAAA,IACZ,gBAAgB,OAAO;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,cAAc,MAAM,YAAY,MAAM,eAAe,CAAC,UAAU,QAAQ,CAAC,GAAG,CAAC,CAAC;AACpF,QAAM,iBAAiB,SAA+B;AAAA,IACpD,gBAAgB,MAAM;AACpB,UAAI,CAAC,SAAU,QAAO;AACtB,kBAAY;AACZ,aAAO;AAAA,IACT;AAAA,IACA,kBAAkB,MAAM;AACtB,UAAI,CAAC,SAAU,QAAO;AACtB,eAAS;AACT,aAAO;AAAA,IACT;AAAA,IACA,iBAAiB,MAAM;AAErB,UAAI,CAAC,iBAAiB,CAAC,YAAY,WAAW,YAAY,KAAK,aAAa,YAAY,EAAG,QAAO;AAClG,eAAS;AACT,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,QAAM,eAAe,SAAS,gBAAgB;AAC9C,QAAM,YAAY,MAAM,OAAO,YAAY;AAC3C,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,YAAY;AACjE,QAAM,iBAAiB,WAA2B,MAAM,oBAAI,IAAI,CAAC,EAAE;AACnE,QAAM,eAAe,MAAM,OAAyB,IAAI;AACxD,QAAM,YAAY,MAAM,YAAY,MAAM,aAAa,SAAS,MAAM,GAAG,CAAC,CAAC;AAE3E,QAAM,aAAa;AAAA,IACjB,MAAM,IAAI,gBAA8C,kBAAkB,CAAC,SAAS,IAAI;AAAA,EAC1F,EAAE;AACF,QAAM,eAAe;AAAA,IACnB,MACE,IAAI,gBAA6D,oBAAoB,CAAC,UAAU;AAAA,MAC9F,IAAI,KAAK;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK,QAAQ;AAAA,IACrB,EAAE;AAAA,EACN,EAAE;AAEF,QAAM,CAAC,UAAU,IAAI,MAAM,SAAyB,MAAM;AAAA,IACxD,GAAG,uBAAuB;AAAA,MACxB;AAAA,MACA;AAAA,MACA,mBAAmB;AAAA,QACjB,WAAW;AAAA,QACX,MAAM;AAAA;AAAA;AAAA,QAGN,aAAa;AAAA,QACb,UAAU,OAAO,UAAU,UAAU,aAAa,MAAM;AAAA,QACxD,OAAO,OAAO,EAAE,OAAO,OAAO,MAAM;AAClC,gBAAM,SAAS,OAAO,QAAQ,UAAU;AACxC,cAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,cAAI,OAAO,WAAW,WAAY,QAAO,CAAC,GAAI,MAAM,OAAO,OAAO,EAAE,OAAO,CAAC,CAAE;AAC9E,iBAAO,mBAAmB,QAAQ,KAAK;AAAA,QACzC;AAAA,QACA,OAAO,CAAC,EAAE,QAAQ,QAAQ,MACxB,QAAQ,OAAO,QAAQ,QAAQ,KAC/B,QAAQ,cACR,CAAC,QAAQ,SAAS,WAAW,KAC7B,CAAC,QAAQ,SAAS,MAAM;AAAA,QAC1B,QAAQ,aAAa;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,IACD,4BAA4B;AAAA,MAC1B,OAAO,CAAC,EAAE,QAAQ,SAAS,MAAM,MAC/B,oBAAoB,SAAS,OAAO,OAAO,QAAQ,QAAQ;AAAA,QACzD;AAAA,QACA,WAAW,OAAO,QAAQ,gBAAgB,MAAM,aAAa,SAAS,MAAM,IAAI;AAAA,MAClF,CAAC;AAAA,MACH,QAAQ,WAAW;AAAA,IACrB,CAAC;AAAA,IACD,2BAA2B,eAAe,cAAc;AAAA,IACxD,mBAAmB,gBAAgB,MAAM;AAAA,IACzC,GAAI,mBAAmB,CAAC;AAAA,EAC1B,CAAC;AASD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAwB,IAAI;AAE9E,QAAM,iBAAiB,YAAY,YAAY,uBAAuB;AAatE,QAAM,cAAc,MAAM,QAAQ,MAAM;AACtC,UAAM,aAAqC;AAAA,MACzC,OAAO,GAAG,+BAA+B,gBAAgB,gBAAgB;AAAA,IAC3E;AACA,QAAI,GAAI,YAAW,KAAK;AACxB,QAAI,CAAC,SAAU,YAAW,OAAO;AACjC,QAAI,UAAU;AACZ,iBAAW,OAAO;AAClB,iBAAW,gBAAgB,IAAI;AAC/B,YAAM,aAAa,kBAAkB;AACrC,UAAI,aAAa,CAAC,WAAY,YAAW,YAAY,IAAI,aAAa,OAAO;AAC7E,UAAI,WAAY,YAAW,iBAAiB,IAAI;AAChD,UAAI,UAAU,YAAY,EAAG,YAAW,eAAe,IAAI;AAAA,IAC7D;AACA,QAAI,gBAAiB,YAAW,kBAAkB,IAAI;AACtD,QAAI,UAAU,WAAW,EAAG,YAAW,cAAc,IAAI;AACzD,WAAO,EAAE,WAAW;AAAA,EACtB,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT,CAAC;AAED,QAAM,SAAS,UAAU;AAAA,IACvB;AAAA,IACA,SAAS;AAAA,IACT,aAAa;AAAA,IACb;AAAA,IACA,WAAW,YAAY,QAAQ;AAAA;AAAA;AAAA,IAG/B,mBAAmB;AAAA,IACnB;AAAA,IACA,UAAU,CAAC,EAAE,QAAQ,QAAQ,MAAM;AACjC,YAAM,OAAO,UAAU,SAAS,MAAM;AACtC,gBAAU,UAAU;AACpB,aAAO,QAAQ,WAAW,IAAI;AAC9B,UAAI,OAAO,QAAQ,KAAM,gBAAe,IAAI;AAAA,IAC9C;AAAA,IACA,SAAS,MAAM,OAAO,QAAQ,UAAU;AAAA,IACxC,QAAQ,MAAM,OAAO,QAAQ,SAAS;AAAA,EACxC,CAAC;AAID,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,UAAU,UAAU,UAAa,UAAU,UAAU,QAAS;AACnE,WAAO,SAAS,WAAW,OAAO,EAAE,aAAa,QAAQ,YAAY,MAAM,CAAC;AAC5E,cAAU,UAAU;AACpB,mBAAe,KAAK;AAAA,EACtB,GAAG,CAAC,QAAQ,OAAO,MAAM,CAAC;AAI1B,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU,OAAO,eAAe,SAAU,QAAO,YAAY,UAAU,KAAK;AAAA,EAClF,GAAG,CAAC,QAAQ,QAAQ,CAAC;AAGrB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,UAAU,CAAC,MAAM,CAAC,YAAY,aAAa,eAAgB;AAChE,UAAM,QAAQ,SAAS,cAAgC,cAAc,IAAI,OAAO,EAAE,CAAC,IAAI;AACvF,QAAI,CAAC,MAAO;AACZ,QAAI,CAAC,MAAM,GAAI,OAAM,KAAK,GAAG,EAAE;AAC/B,sBAAkB,MAAM,EAAE;AAC1B,UAAM,cAAc,CAAC,UAAsB;AACzC,YAAM,eAAe;AACrB,aAAO,SAAS,MAAM;AAAA,IACxB;AACA,UAAM,iBAAiB,SAAS,WAAW;AAC3C,WAAO,MAAM;AACX,YAAM,oBAAoB,SAAS,WAAW;AAC9C,wBAAkB,IAAI;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,QAAQ,IAAI,UAAU,WAAW,cAAc,CAAC;AAOpD,QAAM,YAAY,SAAS,MAAM;AACjC,QAAM,OAAO,MAAM;AAAA,IACjB,OAAO;AAAA,MACL,OAAO,CAAC,WAAW,UAAU;AAC3B,kBAAU,SAAS,SAAS,MAAM,QAAQ;AAAA,MAC5C;AAAA,MACA,UAAU,MAAO,UAAU,UAAU,UAAU,UAAU,SAAS,MAAM,IAAI,UAAU;AAAA,MACtF,UAAU,CAAC,SAAS;AAClB,kBAAU,SAAS,SAAS,WAAW,MAAM,EAAE,aAAa,QAAQ,YAAY,KAAK,CAAC;AAAA,MACxF;AAAA,MACA,OAAO,MAAM;AACX,kBAAU,SAAS,SAAS,aAAa,IAAI;AAAA,MAC/C;AAAA,MACA,SAAS,MAAO,UAAU,UAAU,UAAU,QAAQ,UAAU,UAAU,QAAQ,KAAK,MAAM;AAAA,MAC7F,aAAa,MAAM,eAAe,OAAO;AAAA,MACzC,IAAI,SAAS;AACX,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAAA,IACA,CAAC,WAAW,gBAAgB,MAAM;AAAA,EACpC;AACA,QAAM,oBAAoB,WAAW,MAAM,aAAa,IAAI,GAAG,CAAC,cAAc,IAAI,CAAC;AAEnF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAW;AAAA,MACX,gBAAc;AAAA,MACd,gBAAc;AAAA,MACd,iBAAe,CAAC,YAAY;AAAA,MAC5B,WAAW;AAAA,QACT;AAAA,QACA,YAAY;AAAA;AAAA,QAGV;AAAA,QACF;AAAA,MACF;AAAA,MAEC;AAAA,oBAAY,WAAW,YACtB;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,QAAQ;AAAA,YACR,aAAa,gBAAgB,YAAY;AAAA,YAGzC,WAAW,YAAY,YAAY,wBAAwB;AAAA;AAAA,QAC7D;AAAA,QAGD,SACC,oBAAC,iBAAc,QAAgB;AAAA;AAAA,UAG/B,oBAAC,SAAI,eAAY,QAAO,WAAW,GAAG,gBAAgB,gBAAgB,GAAG;AAAA;AAAA,QAG1E,UAAU,YACT;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY,YAAY;AAAA,YACxB;AAAA;AAAA,QACF;AAAA,QAGD,UACC;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAO;AAAA,YACP,OAAO,OAAO;AAAA,YACd,YAAY,OAAO;AAAA,YACnB,QAAQ,CAAC,SAAS,KAAK;AAAA,YACvB,YAAY,CAAC,SACX,iCACE;AAAA,kCAAC,KAAK,MAAL,EAAU,eAAY,QAAO;AAAA,cAC9B,oBAAC,UAAK,WAAU,2BAA2B,eAAK,OAAM;AAAA,cACrD,KAAK;AAAA,cAEJ,oBAAC,UAAK,WAAU,mDAAmD,eAAK,MAAK;AAAA,eAEjF;AAAA;AAAA,QAEJ;AAAA,QAGD,UAAU,YACT;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAO;AAAA,YACP,OAAO,OAAO;AAAA,YACd,YAAY,OAAO;AAAA,YACnB,cAAc,OAAO;AAAA,YAErB,QAAQ,CAAC,SAAS,GAAG,KAAK,QAAQ,EAAE,IAAI,KAAK,EAAE;AAAA,YAC/C,eAAe,CAAC,UAAU,KAAK,KAAK,KAAK;AAAA,YACzC,YAAY,CAAC,SACX,iCACG;AAAA,mBAAK,OACJ,oBAAC,UAAK,eAAY,QAAO,WAAU,oDACjC,8BAAC,KAAK,MAAL,EAAU,WAAU,gCAA+B,GACtD,IAEA,oBAAC,gBAAa,MAAM,KAAK,OAAO,KAAK,KAAK,WAAW,MAAK,MAAK,eAAY,QAAO;AAAA,cAEpF,oBAAC,UAAK,WAAU,2BAA2B,eAAK,OAAM;AAAA,cACrD,KAAK,eACJ,oBAAC,UAAK,WAAU,sDAAsD,eAAK,aAAY;AAAA,eAE3F;AAAA;AAAA,QAEJ;AAAA,QAGD,iBACC;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,QAAO;AAAA,YACP,UAAQ;AAAA,YACR,QAAM;AAAA,YACN,UAAU;AAAA,YACV,UAAU,CAAC,UAAU;AACnB,oBAAM,QAAQ,MAAM,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,KAAK,KAAK,WAAW,QAAQ,CAAC;AAElG,oBAAM,OAAO,QAAQ;AACrB,kBAAI,UAAU,MAAM,SAAS;AAC3B,iCAAiB,QAAQ,OAAO,QAAW,cAAc,SAAS,cAAc;AAAA,YACpF;AAAA;AAAA,QACF;AAAA,QAGD,QAAQ,oBAAC,WAAM,MAAK,UAAS,MAAY,OAAO,aAAa;AAAA;AAAA;AAAA,EAChE;AAEJ;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '@tiptap/core';
|
|
2
2
|
import '../components/composite/empty-state.js';
|
|
3
|
-
export {
|
|
3
|
+
export { f as EditorBaseProps, g as EditorFormat, E as EditorHandleBase, a as EditorToolbarMode, b as MarkdownEditorMentionItem, c as MarkdownEditorMentionSource, d as MarkdownEditorMentions, e as MarkdownEditorVariant } from '../editor-types-BacXD2VN.js';
|
|
4
4
|
import './markdown-editor-constants.js';
|
|
5
5
|
import 'react';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Editor } from '@tiptap/core';
|
|
3
|
-
import {
|
|
3
|
+
import { g as EditorFormat } from '../editor-types-BacXD2VN.js';
|
|
4
4
|
import { MarkdownEditorLabels } from './markdown-editor-constants.js';
|
|
5
5
|
import '../components/composite/empty-state.js';
|
|
6
6
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '@tiptap/core';
|
|
2
|
-
export { I as ImageUploadHandlers, M as MarkdownEditorImageUploadResult, P as PendingUploads, h as createImageUploadExtension, i as imageFilesFrom, j as insertImageFiles } from '../editor-types-
|
|
2
|
+
export { I as ImageUploadHandlers, M as MarkdownEditorImageUploadResult, P as PendingUploads, h as createImageUploadExtension, i as imageFilesFrom, j as insertImageFiles } from '../editor-types-BacXD2VN.js';
|
|
3
3
|
import '../components/composite/empty-state.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
import './markdown-editor-constants.js';
|
package/dist/editor/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { E as EditorHandleBase, a as EditorToolbarMode, M as MarkdownEditorImageUploadResult, b as MarkdownEditorMentionItem, c as MarkdownEditorMentionSource, d as MarkdownEditorMentions, e as MarkdownEditorVariant } from '../editor-types-
|
|
1
|
+
export { E as EditorHandleBase, a as EditorToolbarMode, M as MarkdownEditorImageUploadResult, b as MarkdownEditorMentionItem, c as MarkdownEditorMentionSource, d as MarkdownEditorMentions, e as MarkdownEditorVariant } from '../editor-types-BacXD2VN.js';
|
|
2
2
|
export { MarkdownEditor, MarkdownEditorHandle, MarkdownEditorProps } from './markdown-editor.js';
|
|
3
3
|
export { DEFAULT_MARKDOWN_EDITOR_LABELS, MarkdownEditorBlockId, MarkdownEditorLabels } from './markdown-editor-constants.js';
|
|
4
4
|
export { RichTextEditor, RichTextEditorHandle, RichTextEditorProps } from './rich-text-editor.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { f as EditorBaseProps, a as EditorToolbarMode, E as EditorHandleBase } from '../editor-types-BacXD2VN.js';
|
|
3
3
|
import '@tiptap/core';
|
|
4
4
|
import '../components/composite/empty-state.js';
|
|
5
5
|
import './markdown-editor-constants.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Extension, AnyExtension } from '@tiptap/core';
|
|
2
2
|
import { MentionOptions } from '@tiptap/extension-mention';
|
|
3
3
|
import { Slice } from '@tiptap/pm/model';
|
|
4
|
-
import {
|
|
4
|
+
import { g as EditorFormat } from '../editor-types-BacXD2VN.js';
|
|
5
5
|
import '../components/composite/empty-state.js';
|
|
6
6
|
import 'react';
|
|
7
7
|
import './markdown-editor-constants.js';
|
|
@@ -84,6 +84,8 @@ interface EditorKeymapHandlers {
|
|
|
84
84
|
onLinkShortcut: () => boolean;
|
|
85
85
|
/** ⌘/Ctrl+Enter ngoài danh sách việc. Trả `false` để phím đi tiếp (xuống dòng cứng của Tiptap). */
|
|
86
86
|
onSubmitShortcut: () => boolean;
|
|
87
|
+
/** Enter trần (chỉ khi `submitOnEnter`). Trả `false` để phím đi tiếp (đoạn mới, mục danh sách mới…). */
|
|
88
|
+
onEnterShortcut: () => boolean;
|
|
87
89
|
}
|
|
88
90
|
/**
|
|
89
91
|
* Phím tắt của RIÊNG gói, ưu tiên cao hơn mặc định của Tiptap.
|
|
@@ -283,7 +283,9 @@ function createEditorKeymap(handlers, format = "markdown") {
|
|
|
283
283
|
if (toggled || handlers.current.onSubmitShortcut()) return true;
|
|
284
284
|
return swallowInCell(editor);
|
|
285
285
|
},
|
|
286
|
-
"Shift-Enter": ({ editor }) => swallowInCell(editor)
|
|
286
|
+
"Shift-Enter": ({ editor }) => swallowInCell(editor),
|
|
287
|
+
// `view.composing`: Enter đang chốt chữ của bộ gõ (Telex/VNI qua IME hệ thống) không được gửi tin.
|
|
288
|
+
Enter: ({ editor }) => !editor.view.composing && handlers.current.onEnterShortcut()
|
|
287
289
|
};
|
|
288
290
|
}
|
|
289
291
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/editor/markdown-extensions.ts"],"sourcesContent":["/**\n * LƯỢC ĐỒ của hai trình soạn — danh sách extension Tiptap, không React, không DOM.\n *\n * Mọi thứ quyết định \"tài liệu này lưu xuống thành chuỗi nào, và đọc lên lại thành tài liệu nào\"\n * nằm ở đây, và CHỈ ở đây: trình soạn dùng nó, cổng `test:editor-markdown` dựng `MarkdownManager`\n * từ chính hàm này trên `dist/` rồi đo vòng tài liệu → markdown → tài liệu. Hai nơi mà dùng hai danh\n * sách là hai lược đồ, và cổng xanh trên một lược đồ không ai dùng.\n *\n * Hai định dạng LƯU, hai lược đồ khác nhau ở đúng những chỗ định dạng ấy biểu diễn được hay không\n * (`createSchemaExtensions`): markdown (`MarkdownEditor`) hẹp hơn — không gạch chân, ô bảng một đoạn —\n * vì thứ gì không ghi được thành markdown thì sẽ mất ở lần lưu; HTML (`RichTextEditor`) giữ được cả hai.\n *\n * Phần GIAO DIỆN (menu `/`, menu `@`, tải ảnh, phím tắt) gắn vào qua tham số — xem\n * `MarkdownSchemaOptions`. Gọi không tham số là ra đúng lược đồ, đủ để đọc/ghi.\n */\n\nimport { type AnyExtension, Extension, InputRule, type JSONContent, mergeAttributes } from \"@tiptap/core\";\nimport { CodeBlockLowlight } from \"@tiptap/extension-code-block-lowlight\";\nimport { Image } from \"@tiptap/extension-image\";\nimport { TaskItem, TaskList } from \"@tiptap/extension-list\";\nimport { Mention, type MentionOptions } from \"@tiptap/extension-mention\";\nimport { Paragraph } from \"@tiptap/extension-paragraph\";\nimport { renderTableToMarkdown, Table, TableCell, TableHeader, TableKit } from \"@tiptap/extension-table\";\nimport { Placeholder } from \"@tiptap/extensions\";\nimport { Markdown } from \"@tiptap/markdown\";\nimport { Fragment, type Node as ProseMirrorNode, type ResolvedPos, Slice } from \"@tiptap/pm/model\";\nimport { Plugin } from \"@tiptap/pm/state\";\nimport { StarterKit } from \"@tiptap/starter-kit\";\nimport { common, createLowlight } from \"lowlight\";\nimport { Marked, type marked } from \"marked\";\nimport type { EditorFormat } from \"./editor-types.js\";\nimport {\n codeFence,\n escapeParagraphMarkdown,\n imageMarkdown,\n parseMentionMarkdown,\n renderMentionMarkdown\n} from \"./markdown-syntax.js\";\n\n/**\n * Giá trị đổi được SAU KHI trình soạn đã dựng. Extension đọc chúng lúc CẦN (lúc vẽ chữ mờ, lúc vẽ một\n * lần nhắc tên…), không chép lúc dựng — vì đổi danh sách extension là dựng lại cả trình soạn: mất\n * lịch sử hoàn tác, mất con trỏ. Component giữ object này trong một ref và ghi đè từng lượt render.\n */\nexport interface MarkdownEditorRuntime {\n placeholder: string;\n taskCheckbox: (text: string) => string;\n /** Đổi `src` ĐÃ LƯU thành địa chỉ HIỂN THỊ được (vd URL đã ký). Markdown vẫn giữ `src` gốc. */\n resolveImageSrc?: (src: string) => string;\n /**\n * Tên SỐNG theo id (và loại — `undefined` là người). `undefined` thì rơi về tên chụp lúc nhắc.\n */\n resolveMentionLabel?: (id: string, kind?: string) => string | undefined;\n /** Địa chỉ mở khi bấm một lần nhắc (vd `/tasks/<id>`). `undefined` = không phải liên kết. */\n resolveMentionHref?: (id: string, kind?: string) => string | undefined;\n}\n\nconst DEFAULT_RUNTIME: MarkdownEditorRuntime = {\n placeholder: \"\",\n taskCheckbox: (text) => text\n};\n\nexport interface MarkdownSchemaOptions {\n /** Định dạng LƯU — quyết định lược đồ. Mặc định `markdown`. */\n format?: EditorFormat;\n /** Đọc lười — xem `MarkdownEditorRuntime`. Bỏ trống ở nơi chỉ cần đọc/ghi (cổng, máy chủ). */\n runtime?: { readonly current: MarkdownEditorRuntime };\n /**\n * Cấu hình gợi ý `@` của `Mention` (danh sách, cách vẽ, `allow`). Bỏ trống = node `mention` vẫn có\n * trong lược đồ — để tài liệu CÓ nhắc tên vẫn đọc ra đúng ở chế độ chỉ đọc — nhưng gõ `@` không\n * mở gì.\n */\n mentionSuggestion?: Partial<MentionOptions[\"suggestion\"]>;\n}\n\n/*\n * Ngôn ngữ tô màu: bộ `common` của highlight.js (37 ngôn ngữ, ~53KB gzip — đo bằng esbuild trên\n * lowlight 3.3.0). Dựng MỘT lần cho cả module: bảng ngôn ngữ chỉ đọc, chia được giữa các trình soạn.\n */\nconst lowlight = createLowlight(common);\n\n/**\n * Đoạn văn — thêm phép thoát cú pháp khối (ca 1 ở đầu `markdown-syntax.ts`). Mọi hành vi khác giữ\n * nguyên bản gốc, kể cả ` ` cho đoạn rỗng: đó là cách CỐ Ý của Tiptap để giữ dòng trống người\n * dùng gõ — bỏ nó đi là nội dung \"co\" lại sau mỗi lần lưu.\n */\nconst ComitorParagraph = Paragraph.extend({\n renderMarkdown(node, helpers, context) {\n return escapeParagraphMarkdown(this.parent?.(node, helpers, context) ?? \"\");\n }\n});\n\n/** Khối mã — hàng rào dài hơn mọi dãy backtick bên trong (ca 2 ở đầu `markdown-syntax.ts`). */\nconst ComitorCodeBlock = CodeBlockLowlight.extend({\n renderMarkdown(node: JSONContent) {\n const language = typeof node.attrs?.language === \"string\" ? node.attrs.language : \"\";\n const text = (node.content ?? []).map((child) => child.text ?? \"\").join(\"\");\n const fence = codeFence(text);\n return `${fence}${language}\\n${text}\\n${fence}`;\n }\n});\n\n/** Bảng — bỏ dòng trống bản gốc kẹp hai đầu (xem `trimMarkdown`). */\nconst ComitorTable = Table.extend({\n renderMarkdown(node, helpers) {\n return renderTableToMarkdown(node, helpers).replace(/^\\n+|\\n+$/g, \"\");\n }\n});\n\n/*\n * Ô bảng chứa ĐÚNG MỘT đoạn văn — bản gốc cho `block+` (danh sách, khối mã, nhiều đoạn trong một ô).\n *\n * Ô bảng GFM là một dòng chữ, không chứa khối được. Đã đo trên 3.31.4: ô có hai đoạn ghi thành\n * `dòng 1<br>dòng 2`, ô có danh sách thành `- a<br>- b`, khối mã thành ```` ```<br>code<br>``` ````;\n * mở lại thì `<br>` là CHỮ THƯỜNG, và lần lưu kế tiếp nó thành `<br>` — mỗi lần lưu hỏng thêm\n * một lớp. Thu hẹp lược đồ là cách duy nhất chặn từ gốc: luật nhập (`- `, `# `, ```` ``` ````) và\n * lệnh khối tự thất bại trong ô vì lược đồ không cho, không cần chặn từng chỗ. Xuống dòng cứng trong ô\n * thì `createEditorKeymap` nuốt — `hardBreak` là node NỘI DÒNG nên lược đồ không chặn được nó.\n */\nconst ComitorTableCell = TableCell.extend({ content: \"paragraph\" });\nconst ComitorTableHeader = TableHeader.extend({ content: \"paragraph\" });\n\n/** Mẫu luật nhập của `HorizontalRule` trong StarterKit (3.31.4) — xem `MarkdownTableCellGuard`. */\nconst HORIZONTAL_RULE_INPUT = /^(?:---|—-|___\\s|\\*\\*\\*\\s)$/;\n\n/**\n * Ô bảng MARKDOWN — chặn nốt những đường CHÈN KHỐI mà lược đồ không chặn được.\n *\n * `ComitorTableCell` làm luật nhập BỌC (`- `, `# `, `> `) và lệnh đổi khối tự thất bại trong ô. Nhưng\n * phép THAY của ProseMirror không thất bại: khối không vừa chỗ thì nó TÁCH các node cha cho tới khi\n * vừa — tức tách CẢ BẢNG. Đã đo trên 3.31.4 (harness của `3.6.0`; cả ba có từ `3.5.0`):\n * · gõ `---` trong ô → bảng gãy làm hai, một đường kẻ ở giữa (luật nhập của `HorizontalRule`);\n * · dán một danh sách vào ô → bảng gãy làm hai, danh sách ở giữa;\n * · dán hai dòng chữ vào ô → hai dòng GHI ĐÈ lên ô đang đứng và ô kế bên — chữ cũ của cả hai mất.\n * Ba lớp, từ cụ thể tới chung:\n * 1. dán/thả từ ngoài: mọi khối của đoạn dán gộp thành MỘT dòng (`flattenSliceToLine`) — ô GFM là\n * một dòng chữ;\n * 2. `---` gõ trong ô ở lại là CHỮ — \"---\" là cách người ta hay ghi \"không có\" trong một ô bảng;\n * 3. lưới cuối: giao dịch nào làm SỐ BẢNG tăng khi con trỏ đang ở trong ô thì bị bỏ. Nó bắt mọi\n * đường còn lại (luật nhập ảnh ``, kéo thả một khối trong tài liệu, `insertContent` của app),\n * với cái giá là phím hay cú thả vừa rồi không có tác dụng — mất một phím còn hơn gãy một bảng.\n * Hoàn tác/làm lại và thay CẢ tài liệu (`setContent`, `value` đổi từ ngoài) đi thẳng.\n *\n * Ô bảng của `RichTextEditor` chứa được khối nên không cần lớp nào trong ba.\n */\nconst MarkdownTableCellGuard = Extension.create({\n name: \"comitorMarkdownTableCellGuard\",\n // Cao hơn StarterKit (100): mỗi extension một plugin luật nhập, xếp theo `priority` — luật dưới đây\n // phải được hỏi TRƯỚC luật đường kẻ, không thì đường kẻ đã chèn xong.\n priority: 1000,\n addInputRules() {\n return [\n new InputRule({\n find: HORIZONTAL_RULE_INPUT,\n undoable: false,\n handler: ({ state, range, match }) => {\n if (!isInTableCell(state.doc.resolve(range.from))) return null;\n state.tr.insertText(match[0], range.from, range.to);\n }\n })\n ];\n },\n addProseMirrorPlugins() {\n /*\n * Đoạn dán có thật là BẢNG không — phải hỏi ở chuỗi GỐC. `normalizeSiblings` của ProseMirror bọc\n * sẵn các đoạn văn dán vào ô thành Ô BẢNG (để chúng \"vừa\" chỗ) trước khi `transformPasted` thấy\n * chúng, nên nhìn slice thì \"dán hai dòng chữ\" và \"dán hai ô\" là một — và `prosemirror-tables` dán\n * cả hai theo kiểu ô, đè lên ô kế bên. Đã đo: đó chính là ca \"hai dòng ghi đè hai ô\" ở trên.\n */\n let pastedTable = false;\n return [\n new Plugin({\n props: {\n transformPastedHTML: (html) => {\n pastedTable = /<(?:table|tr|td|th)[\\s>/]/i.test(html);\n return html;\n },\n transformPastedText: (text) => {\n pastedTable = false;\n return text;\n },\n transformPasted: (slice, view) => {\n const keepTables = pastedTable;\n pastedTable = false;\n return isInTableCell(view.state.selection.$from) ? flattenSliceToLine(slice, keepTables) : slice;\n }\n },\n filterTransaction: (tr, state) => {\n if (!tr.docChanged || tr.getMeta(\"history$\") || !isInTableCell(state.selection.$from)) return true;\n if (replacesWholeDocument(tr.steps, state.doc.content.size)) return true;\n return countTables(tr.doc) <= countTables(state.doc);\n }\n })\n ];\n }\n});\n\n/**\n * Gộp một đoạn dán thành MỘT dòng nội dòng: các khối chữ nối nhau bằng một dấu cách, xuống dòng (cả\n * `hardBreak` lẫn `\\n` trong khối mã) thành dấu cách, khối không phải chữ (ảnh, đường kẻ) bỏ đi.\n * Nội dung nội dòng sẵn thì để nguyên; `keepTables` (đoạn dán GỐC có bảng) thì đoạn dán mang ô bảng\n * cũng để nguyên — dán theo ô là việc của `prosemirror-tables`.\n * Xuất ra cho cổng `test:editor-markdown`; không nằm trong entry.\n */\nexport function flattenSliceToLine(slice: Slice, keepTables = false): Slice {\n const first = slice.content.firstChild;\n if (!first || first.isInline) return slice;\n if (keepTables) {\n let tabular = false;\n slice.content.descendants((node) => {\n if (node.type.spec.tableRole) tabular = true;\n return !tabular;\n });\n if (tabular) return slice;\n }\n\n const schema = first.type.schema;\n const line: ProseMirrorNode[] = [];\n slice.content.descendants((node) => {\n if (!node.isTextblock) return true;\n if (node.content.size === 0) return false;\n if (line.length > 0) line.push(schema.text(\" \"));\n node.content.forEach((child) => {\n if (child.type.name === \"hardBreak\") line.push(schema.text(\" \"));\n else if (child.isText && child.text?.includes(\"\\n\"))\n line.push(schema.text(child.text.replace(/\\s*\\n\\s*/g, \" \"), child.marks));\n else line.push(child);\n });\n return false;\n });\n return new Slice(Fragment.from(line), 0, 0);\n}\n\nfunction countTables(doc: ProseMirrorNode): number {\n let count = 0;\n doc.descendants((node) => {\n if (node.type.name === \"table\") count++;\n return node.type.name !== \"table\";\n });\n return count;\n}\n\n/** Có bước nào thay TOÀN BỘ tài liệu không — `setContent` làm đúng một bước như vậy. */\nfunction replacesWholeDocument(steps: readonly unknown[], size: number): boolean {\n return steps.some((step) => {\n const { from, to } = step as { from?: unknown; to?: unknown };\n return from === 0 && to === size;\n });\n}\n\n/**\n * Bảng chữ markdown RIÊNG cho từng trình soạn.\n *\n * ⚠ Không truyền thì `@tiptap/markdown` dùng `marked` TOÀN CỤC, và mỗi lần trình soạn mount nó gọi\n * `marked.use()` đăng ký lại bộ tách từ (bảng, nhắc tên…) — không bao giờ gỡ. Đã đo trên 3.31.4:\n * mỗi `MarkdownManager` thêm 4 bộ tách; sau 200 lần dựng, parse cùng một tài liệu 200 đoạn chậm từ\n * 18,6ms lên 282ms, và còn tăng tiếp. Trong một SPA, 200 lần là một buổi làm việc mở việc này sang\n * việc khác. Một bản `Marked` mỗi trình soạn: 4 bộ tách, 5,7ms, mất cùng trình soạn.\n *\n * Ép kiểu vì option khai `typeof marked` (hàm `marked` toàn cục, gọi được) mà thứ đúng là một bản\n * `Marked`: `MarkdownManager` chỉ dùng `.use`, `.Lexer`, `.defaults`, `.lexer`, `.setOptions` — bản\n * `Marked` có đủ cả năm, chỉ thiếu chữ ký gọi-như-hàm mà không chỗ nào gọi tới.\n */\nfunction createMarkedInstance(): typeof marked {\n return new Marked() as unknown as typeof marked;\n}\n\n/**\n * Danh sách extension tạo nên lược đồ của trình soạn, theo định dạng LƯU.\n *\n * Markdown (`format: \"markdown\"`, mặc định) hẹp hơn HTML ở hai chỗ, cả hai vì cùng một lý do — thứ gì\n * không ghi được thành markdown thì MẤT ở lần lưu, mà người dùng không được báo:\n * · `underline` tắt: markdown không có cú pháp gạch chân. `@tiptap/markdown` ghi nó thành\n * `++chữ++` — cú pháp riêng, nên email thông báo, tác tử đọc qua MCP và mọi trình xem markdown\n * khác in nguyên hai dấu cộng;\n * · ô bảng chỉ chứa MỘT đoạn văn (`ComitorTableCell`).\n * Hai bản vá markdown (`ComitorParagraph`, `Markdown` với `Marked` riêng) cũng chỉ nạp ở định dạng đó.\n *\n * HTML (`format: \"html\"`) giữ cả gạch chân lẫn ô bảng nhiều khối — `<u>`, `<td><ul>…</ul></td>` là HTML\n * hợp lệ và đọc lên lại đúng.\n */\nexport function createSchemaExtensions(options: MarkdownSchemaOptions = {}): AnyExtension[] {\n const runtime = options.runtime ?? { current: DEFAULT_RUNTIME };\n const markdown = (options.format ?? \"markdown\") === \"markdown\";\n\n return [\n StarterKit.configure({\n paragraph: false,\n codeBlock: false,\n ...(markdown ? { underline: false as const } : {}),\n link: {\n // Đang sửa thì bấm vào liên kết là đặt con trỏ, không phải mở tab mới (thanh định dạng có nút\n // Mở). Chỉ đọc thì Tiptap không chặn cú bấm — thẻ `<a target=\"_blank\">` tự mở như thường.\n openOnClick: false,\n autolink: true,\n linkOnPaste: true,\n // Gõ/dán `[chữ](https://…)` là thành liên kết — người quen markdown gõ vậy theo phản xạ.\n markdownLinks: true,\n defaultProtocol: \"https\",\n HTMLAttributes: { target: \"_blank\", rel: \"noopener noreferrer nofollow\" }\n }\n }),\n markdown ? ComitorParagraph : Paragraph,\n ComitorCodeBlock.configure({\n lowlight,\n defaultLanguage: null,\n // Tab trong khối mã là thụt lề — đúng điều người viết mã chờ đợi. Thoát khối: ↓ ở dòng cuối,\n // hoặc Enter ba lần (mặc định của Tiptap).\n enableTabIndentation: true,\n tabSize: 2\n }),\n TaskList,\n TaskItem.configure({\n nested: true,\n // Chữ của ĐOẠN ĐẦU, không phải `node.textContent`: mục cha có danh sách con thì `textContent` dính\n // liền chữ của mọi mục con (\"Gửi mã qua emailGiới hạn 5 lần/giờ\") — đã gặp.\n a11y: { checkboxLabel: (node) => runtime.current.taskCheckbox(node.firstChild?.textContent ?? \"\") }\n }),\n ...(markdown\n ? [\n TableKit.configure({ table: false, tableCell: false, tableHeader: false }),\n ComitorTable.configure({ resizable: false }),\n ComitorTableCell,\n ComitorTableHeader,\n MarkdownTableCellGuard\n ]\n : [TableKit.configure({ table: { resizable: false } })]),\n createImageExtension(runtime),\n createMentionExtension(runtime, options.mentionSuggestion),\n Placeholder.configure({ placeholder: () => runtime.current.placeholder }),\n ...(markdown\n ? [Markdown.configure({ marked: createMarkedInstance(), indentation: { style: \"space\", size: 2 } })]\n : [])\n ];\n}\n\n/** Lược đồ của `MarkdownEditor` — tên có từ `3.5.0`, cổng `test:editor-markdown` gọi thẳng nó. */\nexport function createMarkdownSchemaExtensions(options: Omit<MarkdownSchemaOptions, \"format\"> = {}): AnyExtension[] {\n return createSchemaExtensions({ ...options, format: \"markdown\" });\n}\n\n/**\n * Ảnh — tách địa chỉ LƯU khỏi địa chỉ HIỂN THỊ, và thoát cú pháp khi ghi markdown.\n *\n * Kho ảnh của module Comitor là bucket RIÊNG TƯ: thứ hiện được là URL đã ký, có hạn. Lưu URL đó vào\n * nội dung là lưu một giá trị sẽ chết ngay trong database — nên nội dung giữ `src` app trả về (khoá,\n * hoặc một route ổn định của app), còn `resolveImageSrc` đổi nó thành địa chỉ vẽ được lúc vẽ.\n *\n * ⚠ Đổi địa chỉ ở NODE VIEW, không ở `renderHTML`. `renderHTML` vừa vẽ ảnh trong trình soạn vừa là\n * thứ `editor.getHTML()` dùng để GHI — đổi `src` ở đó (cách của `3.5.0`, khi giá trị còn là markdown)\n * là `RichTextEditor` ghi URL đã ký vào HTML lưu xuống. Node view chỉ là DOM của màn hình; HTML lưu\n * vẫn đi `renderHTML` gốc với `src` gốc.\n *\n * Ảnh ĐANG tải lên không phải node — xem đầu `image-upload.ts`.\n */\nfunction createImageExtension(runtime: { readonly current: MarkdownEditorRuntime }) {\n return Image.extend({\n addNodeView() {\n return ({ node, HTMLAttributes }) => {\n const image = document.createElement(\"img\");\n setAttributes(image, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), [\"src\"]);\n const src = typeof node.attrs.src === \"string\" ? node.attrs.src : \"\";\n // `src=\"\"` thì trình duyệt tải lại CHÍNH trang làm ảnh — thiếu địa chỉ thì bỏ hẳn thuộc tính.\n if (src) image.setAttribute(\"src\", runtime.current.resolveImageSrc?.(src) ?? src);\n return { dom: image, update: (updated) => sameNode(updated, node) };\n };\n },\n renderMarkdown(node: JSONContent) {\n return imageMarkdown(String(node.attrs?.src ?? \"\"), node.attrs?.alt, node.attrs?.title);\n }\n }).configure({ allowBase64: false });\n}\n\n/**\n * Nhắc tên — định dạng lưu `[@Tên](mention:<id>)` (lý do ở `markdown-syntax.ts`) và tên SỐNG lúc vẽ.\n *\n * Tên sống chỉ đi vào thứ NGƯỜI XEM thấy: node view và `renderText` (chữ khi chép ra ngoài).\n * `renderHTML` — thứ `getHTML()` ghi xuống — giữ TÊN CHỤP lúc nhắc, cùng luật với markdown: nội dung\n * lưu không tự đổi theo danh bạ chỉ vì có người mở và lưu lại.\n */\nfunction createMentionExtension(\n runtime: { readonly current: MarkdownEditorRuntime },\n suggestion: MarkdownSchemaOptions[\"mentionSuggestion\"]\n) {\n const storedLabelOf = (attrs: Record<string, unknown>) => {\n const stored = typeof attrs.label === \"string\" ? attrs.label : \"\";\n return stored || (typeof attrs.id === \"string\" ? attrs.id : \"\");\n };\n const kindOf = (attrs: Record<string, unknown>) =>\n typeof attrs.kind === \"string\" && attrs.kind ? attrs.kind : undefined;\n const labelOf = (attrs: Record<string, unknown>) =>\n runtime.current.resolveMentionLabel?.(typeof attrs.id === \"string\" ? attrs.id : \"\", kindOf(attrs)) ??\n storedLabelOf(attrs);\n\n return Mention.extend({\n /*\n * `kind` (từ 3.7.0): loại thực thể — `null` là người. Ra HTML thành `data-kind` (lưu được ở định dạng\n * HTML và để app tô khác theo loại), ra markdown thành tiền tố `kind/` của địa chỉ.\n */\n addAttributes() {\n return {\n ...this.parent?.(),\n kind: {\n default: null,\n parseHTML: (element: HTMLElement) => element.getAttribute(\"data-kind\") || null,\n renderHTML: (attributes: Record<string, unknown>) =>\n typeof attributes.kind === \"string\" && attributes.kind ? { \"data-kind\": attributes.kind } : {}\n }\n };\n },\n markdownTokenName: \"mention\",\n markdownTokenizer: {\n name: \"mention\",\n level: \"inline\",\n start: (source: string) => source.indexOf(\"[@\"),\n tokenize: (source: string) => {\n const parsed = parseMentionMarkdown(source);\n return parsed\n ? { type: \"mention\", raw: parsed.raw, id: parsed.id, label: parsed.label, kind: parsed.kind ?? null }\n : undefined;\n }\n },\n parseMarkdown: (token, helpers) =>\n helpers.createNode(\"mention\", { id: token.id, label: token.label, kind: token.kind ?? null }),\n renderMarkdown: (node: JSONContent) =>\n renderMentionMarkdown(\n String(node.attrs?.id ?? \"\"),\n node.attrs?.label as string | null | undefined,\n node.attrs?.kind as string | null | undefined\n ),\n addNodeView() {\n return ({ node, HTMLAttributes }) => {\n /*\n * Có địa chỉ (`mentions.getHref`) thì vẽ `<a>`: nhắc VIỆC mở được việc đó. Chỉ nhận đường dẫn tương đối\n * cùng gốc hoặc `http(s):` — địa chỉ do app dựng, nhưng một `javascript:` lọt vào đây là chạy được.\n * Chỉ ở node VIEW: thứ GHI xuống (`renderHTML`, markdown) không mang địa chỉ nào.\n */\n const id = typeof node.attrs.id === \"string\" ? node.attrs.id : \"\";\n const href = runtime.current.resolveMentionHref?.(id, kindOf(node.attrs));\n const safeHref = href && /^(?:\\/(?![/\\\\])|https?:\\/\\/)/i.test(href) ? href : undefined;\n const dom = document.createElement(safeHref ? \"a\" : \"span\");\n setAttributes(dom, mergeAttributes({ \"data-type\": this.name }, this.options.HTMLAttributes, HTMLAttributes));\n if (safeHref) dom.setAttribute(\"href\", safeHref);\n dom.textContent = `@${labelOf(node.attrs)}`;\n return { dom, update: (updated) => sameNode(updated, node) };\n };\n }\n }).configure({\n renderText: ({ node }) => `@${labelOf(node.attrs)}`,\n // `options.HTMLAttributes` ở đây là bản Tiptap đã trộn sẵn (`data-type`, `data-id`, `data-label`).\n renderHTML: ({ options, node }) => [\"span\", options.HTMLAttributes, `@${storedLabelOf(node.attrs)}`],\n suggestion: { char: \"@\", allow: () => false, ...suggestion }\n });\n}\n\n/** Phím tắt riêng của trình soạn — xem `createEditorKeymap`. */\nexport interface EditorKeymapHandlers {\n /** ⌘/Ctrl+K — mở ô liên kết. Trả `true` khi đã xử lý. */\n onLinkShortcut: () => boolean;\n /** ⌘/Ctrl+Enter ngoài danh sách việc. Trả `false` để phím đi tiếp (xuống dòng cứng của Tiptap). */\n onSubmitShortcut: () => boolean;\n}\n\n/**\n * Phím tắt của RIÊNG gói, ưu tiên cao hơn mặc định của Tiptap.\n *\n * ⌘/Ctrl+Enter: trong danh sách việc thì tick/bỏ tick mục đang đứng (như Linear); ngoài đó thì gửi\n * (`onSubmit`) — đúng phím mà ô bình luận nào cũng dùng. Không có `onSubmit` thì trả phím về cho\n * Tiptap (bản gốc gán nó cho xuống dòng cứng).\n *\n * ⌘/Ctrl+K: ô liên kết. Không đụng bảng lệnh của shell — listener của shell tự bỏ qua phím khi\n * đích là vùng `contenteditable` (`shell-context.tsx`, nhánh `isTyping`).\n */\nexport function createEditorKeymap(\n handlers: { readonly current: EditorKeymapHandlers },\n format: EditorFormat = \"markdown\"\n): Extension {\n // Chỉ ô bảng MARKDOWN cấm xuống dòng cứng (`ComitorTableCell`); ô bảng HTML chứa được cả khối.\n const swallowInCell = (editor: { state: { selection: { $from: ResolvedPos } } }) =>\n format === \"markdown\" && isInTableCell(editor.state.selection.$from);\n return Extension.create({\n name: \"comitorEditorKeymap\",\n priority: 1000,\n addKeyboardShortcuts() {\n return {\n \"Mod-k\": () => handlers.current.onLinkShortcut(),\n \"Mod-Enter\": ({ editor }) => {\n const toggled = editor.commands.command(({ tr, state }) => {\n const { $from } = state.selection;\n for (let depth = $from.depth; depth > 0; depth--) {\n const node = $from.node(depth);\n if (node.type.name !== \"taskItem\") continue;\n tr.setNodeMarkup($from.before(depth), undefined, { ...node.attrs, checked: !node.attrs.checked });\n return true;\n }\n return false;\n });\n if (toggled || handlers.current.onSubmitShortcut()) return true;\n // Phím đi tiếp tới Tiptap là xuống dòng cứng — trong ô bảng markdown thì nuốt (xem `ComitorTableCell`).\n return swallowInCell(editor);\n },\n \"Shift-Enter\": ({ editor }) => swallowInCell(editor)\n };\n }\n });\n}\n\n/** Con trỏ có đang đứng trong một ô bảng (thường hay tiêu đề) không. */\nfunction isInTableCell($pos: ResolvedPos): boolean {\n for (let depth = $pos.depth; depth > 0; depth--) {\n const name = $pos.node(depth).type.name;\n if (name === \"tableCell\" || name === \"tableHeader\") return true;\n }\n return false;\n}\n\n/** Ghi thuộc tính lên phần tử của node view — bỏ giá trị rỗng, như `DOMSerializer` làm với `renderHTML`. */\nfunction setAttributes(element: HTMLElement, attributes: Record<string, unknown>, skip: readonly string[] = []): void {\n for (const [key, value] of Object.entries(attributes)) {\n if (skip.includes(key) || value === null || value === undefined || value === false) continue;\n element.setAttribute(key, String(value));\n }\n}\n\n/**\n * Node view của nút LÁ (ảnh, nhắc tên): thuộc tính không đổi thì giữ DOM; đổi thì trả `false` để\n * ProseMirror dựng lại — rẻ, và không có trạng thái nào trong DOM đáng giữ.\n */\nfunction sameNode(updated: ProseMirrorNode, current: ProseMirrorNode): boolean {\n if (updated.type !== current.type) return false;\n return Object.keys(current.attrs).every((key) => updated.attrs[key] === current.attrs[key]);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/editor/markdown-extensions.ts"],"sourcesContent":["/**\n * LƯỢC ĐỒ của hai trình soạn — danh sách extension Tiptap, không React, không DOM.\n *\n * Mọi thứ quyết định \"tài liệu này lưu xuống thành chuỗi nào, và đọc lên lại thành tài liệu nào\"\n * nằm ở đây, và CHỈ ở đây: trình soạn dùng nó, cổng `test:editor-markdown` dựng `MarkdownManager`\n * từ chính hàm này trên `dist/` rồi đo vòng tài liệu → markdown → tài liệu. Hai nơi mà dùng hai danh\n * sách là hai lược đồ, và cổng xanh trên một lược đồ không ai dùng.\n *\n * Hai định dạng LƯU, hai lược đồ khác nhau ở đúng những chỗ định dạng ấy biểu diễn được hay không\n * (`createSchemaExtensions`): markdown (`MarkdownEditor`) hẹp hơn — không gạch chân, ô bảng một đoạn —\n * vì thứ gì không ghi được thành markdown thì sẽ mất ở lần lưu; HTML (`RichTextEditor`) giữ được cả hai.\n *\n * Phần GIAO DIỆN (menu `/`, menu `@`, tải ảnh, phím tắt) gắn vào qua tham số — xem\n * `MarkdownSchemaOptions`. Gọi không tham số là ra đúng lược đồ, đủ để đọc/ghi.\n */\n\nimport { type AnyExtension, Extension, InputRule, type JSONContent, mergeAttributes } from \"@tiptap/core\";\nimport { CodeBlockLowlight } from \"@tiptap/extension-code-block-lowlight\";\nimport { Image } from \"@tiptap/extension-image\";\nimport { TaskItem, TaskList } from \"@tiptap/extension-list\";\nimport { Mention, type MentionOptions } from \"@tiptap/extension-mention\";\nimport { Paragraph } from \"@tiptap/extension-paragraph\";\nimport { renderTableToMarkdown, Table, TableCell, TableHeader, TableKit } from \"@tiptap/extension-table\";\nimport { Placeholder } from \"@tiptap/extensions\";\nimport { Markdown } from \"@tiptap/markdown\";\nimport { Fragment, type Node as ProseMirrorNode, type ResolvedPos, Slice } from \"@tiptap/pm/model\";\nimport { Plugin } from \"@tiptap/pm/state\";\nimport { StarterKit } from \"@tiptap/starter-kit\";\nimport { common, createLowlight } from \"lowlight\";\nimport { Marked, type marked } from \"marked\";\nimport type { EditorFormat } from \"./editor-types.js\";\nimport {\n codeFence,\n escapeParagraphMarkdown,\n imageMarkdown,\n parseMentionMarkdown,\n renderMentionMarkdown\n} from \"./markdown-syntax.js\";\n\n/**\n * Giá trị đổi được SAU KHI trình soạn đã dựng. Extension đọc chúng lúc CẦN (lúc vẽ chữ mờ, lúc vẽ một\n * lần nhắc tên…), không chép lúc dựng — vì đổi danh sách extension là dựng lại cả trình soạn: mất\n * lịch sử hoàn tác, mất con trỏ. Component giữ object này trong một ref và ghi đè từng lượt render.\n */\nexport interface MarkdownEditorRuntime {\n placeholder: string;\n taskCheckbox: (text: string) => string;\n /** Đổi `src` ĐÃ LƯU thành địa chỉ HIỂN THỊ được (vd URL đã ký). Markdown vẫn giữ `src` gốc. */\n resolveImageSrc?: (src: string) => string;\n /**\n * Tên SỐNG theo id (và loại — `undefined` là người). `undefined` thì rơi về tên chụp lúc nhắc.\n */\n resolveMentionLabel?: (id: string, kind?: string) => string | undefined;\n /** Địa chỉ mở khi bấm một lần nhắc (vd `/tasks/<id>`). `undefined` = không phải liên kết. */\n resolveMentionHref?: (id: string, kind?: string) => string | undefined;\n}\n\nconst DEFAULT_RUNTIME: MarkdownEditorRuntime = {\n placeholder: \"\",\n taskCheckbox: (text) => text\n};\n\nexport interface MarkdownSchemaOptions {\n /** Định dạng LƯU — quyết định lược đồ. Mặc định `markdown`. */\n format?: EditorFormat;\n /** Đọc lười — xem `MarkdownEditorRuntime`. Bỏ trống ở nơi chỉ cần đọc/ghi (cổng, máy chủ). */\n runtime?: { readonly current: MarkdownEditorRuntime };\n /**\n * Cấu hình gợi ý `@` của `Mention` (danh sách, cách vẽ, `allow`). Bỏ trống = node `mention` vẫn có\n * trong lược đồ — để tài liệu CÓ nhắc tên vẫn đọc ra đúng ở chế độ chỉ đọc — nhưng gõ `@` không\n * mở gì.\n */\n mentionSuggestion?: Partial<MentionOptions[\"suggestion\"]>;\n}\n\n/*\n * Ngôn ngữ tô màu: bộ `common` của highlight.js (37 ngôn ngữ, ~53KB gzip — đo bằng esbuild trên\n * lowlight 3.3.0). Dựng MỘT lần cho cả module: bảng ngôn ngữ chỉ đọc, chia được giữa các trình soạn.\n */\nconst lowlight = createLowlight(common);\n\n/**\n * Đoạn văn — thêm phép thoát cú pháp khối (ca 1 ở đầu `markdown-syntax.ts`). Mọi hành vi khác giữ\n * nguyên bản gốc, kể cả ` ` cho đoạn rỗng: đó là cách CỐ Ý của Tiptap để giữ dòng trống người\n * dùng gõ — bỏ nó đi là nội dung \"co\" lại sau mỗi lần lưu.\n */\nconst ComitorParagraph = Paragraph.extend({\n renderMarkdown(node, helpers, context) {\n return escapeParagraphMarkdown(this.parent?.(node, helpers, context) ?? \"\");\n }\n});\n\n/** Khối mã — hàng rào dài hơn mọi dãy backtick bên trong (ca 2 ở đầu `markdown-syntax.ts`). */\nconst ComitorCodeBlock = CodeBlockLowlight.extend({\n renderMarkdown(node: JSONContent) {\n const language = typeof node.attrs?.language === \"string\" ? node.attrs.language : \"\";\n const text = (node.content ?? []).map((child) => child.text ?? \"\").join(\"\");\n const fence = codeFence(text);\n return `${fence}${language}\\n${text}\\n${fence}`;\n }\n});\n\n/** Bảng — bỏ dòng trống bản gốc kẹp hai đầu (xem `trimMarkdown`). */\nconst ComitorTable = Table.extend({\n renderMarkdown(node, helpers) {\n return renderTableToMarkdown(node, helpers).replace(/^\\n+|\\n+$/g, \"\");\n }\n});\n\n/*\n * Ô bảng chứa ĐÚNG MỘT đoạn văn — bản gốc cho `block+` (danh sách, khối mã, nhiều đoạn trong một ô).\n *\n * Ô bảng GFM là một dòng chữ, không chứa khối được. Đã đo trên 3.31.4: ô có hai đoạn ghi thành\n * `dòng 1<br>dòng 2`, ô có danh sách thành `- a<br>- b`, khối mã thành ```` ```<br>code<br>``` ````;\n * mở lại thì `<br>` là CHỮ THƯỜNG, và lần lưu kế tiếp nó thành `<br>` — mỗi lần lưu hỏng thêm\n * một lớp. Thu hẹp lược đồ là cách duy nhất chặn từ gốc: luật nhập (`- `, `# `, ```` ``` ````) và\n * lệnh khối tự thất bại trong ô vì lược đồ không cho, không cần chặn từng chỗ. Xuống dòng cứng trong ô\n * thì `createEditorKeymap` nuốt — `hardBreak` là node NỘI DÒNG nên lược đồ không chặn được nó.\n */\nconst ComitorTableCell = TableCell.extend({ content: \"paragraph\" });\nconst ComitorTableHeader = TableHeader.extend({ content: \"paragraph\" });\n\n/** Mẫu luật nhập của `HorizontalRule` trong StarterKit (3.31.4) — xem `MarkdownTableCellGuard`. */\nconst HORIZONTAL_RULE_INPUT = /^(?:---|—-|___\\s|\\*\\*\\*\\s)$/;\n\n/**\n * Ô bảng MARKDOWN — chặn nốt những đường CHÈN KHỐI mà lược đồ không chặn được.\n *\n * `ComitorTableCell` làm luật nhập BỌC (`- `, `# `, `> `) và lệnh đổi khối tự thất bại trong ô. Nhưng\n * phép THAY của ProseMirror không thất bại: khối không vừa chỗ thì nó TÁCH các node cha cho tới khi\n * vừa — tức tách CẢ BẢNG. Đã đo trên 3.31.4 (harness của `3.6.0`; cả ba có từ `3.5.0`):\n * · gõ `---` trong ô → bảng gãy làm hai, một đường kẻ ở giữa (luật nhập của `HorizontalRule`);\n * · dán một danh sách vào ô → bảng gãy làm hai, danh sách ở giữa;\n * · dán hai dòng chữ vào ô → hai dòng GHI ĐÈ lên ô đang đứng và ô kế bên — chữ cũ của cả hai mất.\n * Ba lớp, từ cụ thể tới chung:\n * 1. dán/thả từ ngoài: mọi khối của đoạn dán gộp thành MỘT dòng (`flattenSliceToLine`) — ô GFM là\n * một dòng chữ;\n * 2. `---` gõ trong ô ở lại là CHỮ — \"---\" là cách người ta hay ghi \"không có\" trong một ô bảng;\n * 3. lưới cuối: giao dịch nào làm SỐ BẢNG tăng khi con trỏ đang ở trong ô thì bị bỏ. Nó bắt mọi\n * đường còn lại (luật nhập ảnh ``, kéo thả một khối trong tài liệu, `insertContent` của app),\n * với cái giá là phím hay cú thả vừa rồi không có tác dụng — mất một phím còn hơn gãy một bảng.\n * Hoàn tác/làm lại và thay CẢ tài liệu (`setContent`, `value` đổi từ ngoài) đi thẳng.\n *\n * Ô bảng của `RichTextEditor` chứa được khối nên không cần lớp nào trong ba.\n */\nconst MarkdownTableCellGuard = Extension.create({\n name: \"comitorMarkdownTableCellGuard\",\n // Cao hơn StarterKit (100): mỗi extension một plugin luật nhập, xếp theo `priority` — luật dưới đây\n // phải được hỏi TRƯỚC luật đường kẻ, không thì đường kẻ đã chèn xong.\n priority: 1000,\n addInputRules() {\n return [\n new InputRule({\n find: HORIZONTAL_RULE_INPUT,\n undoable: false,\n handler: ({ state, range, match }) => {\n if (!isInTableCell(state.doc.resolve(range.from))) return null;\n state.tr.insertText(match[0], range.from, range.to);\n }\n })\n ];\n },\n addProseMirrorPlugins() {\n /*\n * Đoạn dán có thật là BẢNG không — phải hỏi ở chuỗi GỐC. `normalizeSiblings` của ProseMirror bọc\n * sẵn các đoạn văn dán vào ô thành Ô BẢNG (để chúng \"vừa\" chỗ) trước khi `transformPasted` thấy\n * chúng, nên nhìn slice thì \"dán hai dòng chữ\" và \"dán hai ô\" là một — và `prosemirror-tables` dán\n * cả hai theo kiểu ô, đè lên ô kế bên. Đã đo: đó chính là ca \"hai dòng ghi đè hai ô\" ở trên.\n */\n let pastedTable = false;\n return [\n new Plugin({\n props: {\n transformPastedHTML: (html) => {\n pastedTable = /<(?:table|tr|td|th)[\\s>/]/i.test(html);\n return html;\n },\n transformPastedText: (text) => {\n pastedTable = false;\n return text;\n },\n transformPasted: (slice, view) => {\n const keepTables = pastedTable;\n pastedTable = false;\n return isInTableCell(view.state.selection.$from) ? flattenSliceToLine(slice, keepTables) : slice;\n }\n },\n filterTransaction: (tr, state) => {\n if (!tr.docChanged || tr.getMeta(\"history$\") || !isInTableCell(state.selection.$from)) return true;\n if (replacesWholeDocument(tr.steps, state.doc.content.size)) return true;\n return countTables(tr.doc) <= countTables(state.doc);\n }\n })\n ];\n }\n});\n\n/**\n * Gộp một đoạn dán thành MỘT dòng nội dòng: các khối chữ nối nhau bằng một dấu cách, xuống dòng (cả\n * `hardBreak` lẫn `\\n` trong khối mã) thành dấu cách, khối không phải chữ (ảnh, đường kẻ) bỏ đi.\n * Nội dung nội dòng sẵn thì để nguyên; `keepTables` (đoạn dán GỐC có bảng) thì đoạn dán mang ô bảng\n * cũng để nguyên — dán theo ô là việc của `prosemirror-tables`.\n * Xuất ra cho cổng `test:editor-markdown`; không nằm trong entry.\n */\nexport function flattenSliceToLine(slice: Slice, keepTables = false): Slice {\n const first = slice.content.firstChild;\n if (!first || first.isInline) return slice;\n if (keepTables) {\n let tabular = false;\n slice.content.descendants((node) => {\n if (node.type.spec.tableRole) tabular = true;\n return !tabular;\n });\n if (tabular) return slice;\n }\n\n const schema = first.type.schema;\n const line: ProseMirrorNode[] = [];\n slice.content.descendants((node) => {\n if (!node.isTextblock) return true;\n if (node.content.size === 0) return false;\n if (line.length > 0) line.push(schema.text(\" \"));\n node.content.forEach((child) => {\n if (child.type.name === \"hardBreak\") line.push(schema.text(\" \"));\n else if (child.isText && child.text?.includes(\"\\n\"))\n line.push(schema.text(child.text.replace(/\\s*\\n\\s*/g, \" \"), child.marks));\n else line.push(child);\n });\n return false;\n });\n return new Slice(Fragment.from(line), 0, 0);\n}\n\nfunction countTables(doc: ProseMirrorNode): number {\n let count = 0;\n doc.descendants((node) => {\n if (node.type.name === \"table\") count++;\n return node.type.name !== \"table\";\n });\n return count;\n}\n\n/** Có bước nào thay TOÀN BỘ tài liệu không — `setContent` làm đúng một bước như vậy. */\nfunction replacesWholeDocument(steps: readonly unknown[], size: number): boolean {\n return steps.some((step) => {\n const { from, to } = step as { from?: unknown; to?: unknown };\n return from === 0 && to === size;\n });\n}\n\n/**\n * Bảng chữ markdown RIÊNG cho từng trình soạn.\n *\n * ⚠ Không truyền thì `@tiptap/markdown` dùng `marked` TOÀN CỤC, và mỗi lần trình soạn mount nó gọi\n * `marked.use()` đăng ký lại bộ tách từ (bảng, nhắc tên…) — không bao giờ gỡ. Đã đo trên 3.31.4:\n * mỗi `MarkdownManager` thêm 4 bộ tách; sau 200 lần dựng, parse cùng một tài liệu 200 đoạn chậm từ\n * 18,6ms lên 282ms, và còn tăng tiếp. Trong một SPA, 200 lần là một buổi làm việc mở việc này sang\n * việc khác. Một bản `Marked` mỗi trình soạn: 4 bộ tách, 5,7ms, mất cùng trình soạn.\n *\n * Ép kiểu vì option khai `typeof marked` (hàm `marked` toàn cục, gọi được) mà thứ đúng là một bản\n * `Marked`: `MarkdownManager` chỉ dùng `.use`, `.Lexer`, `.defaults`, `.lexer`, `.setOptions` — bản\n * `Marked` có đủ cả năm, chỉ thiếu chữ ký gọi-như-hàm mà không chỗ nào gọi tới.\n */\nfunction createMarkedInstance(): typeof marked {\n return new Marked() as unknown as typeof marked;\n}\n\n/**\n * Danh sách extension tạo nên lược đồ của trình soạn, theo định dạng LƯU.\n *\n * Markdown (`format: \"markdown\"`, mặc định) hẹp hơn HTML ở hai chỗ, cả hai vì cùng một lý do — thứ gì\n * không ghi được thành markdown thì MẤT ở lần lưu, mà người dùng không được báo:\n * · `underline` tắt: markdown không có cú pháp gạch chân. `@tiptap/markdown` ghi nó thành\n * `++chữ++` — cú pháp riêng, nên email thông báo, tác tử đọc qua MCP và mọi trình xem markdown\n * khác in nguyên hai dấu cộng;\n * · ô bảng chỉ chứa MỘT đoạn văn (`ComitorTableCell`).\n * Hai bản vá markdown (`ComitorParagraph`, `Markdown` với `Marked` riêng) cũng chỉ nạp ở định dạng đó.\n *\n * HTML (`format: \"html\"`) giữ cả gạch chân lẫn ô bảng nhiều khối — `<u>`, `<td><ul>…</ul></td>` là HTML\n * hợp lệ và đọc lên lại đúng.\n */\nexport function createSchemaExtensions(options: MarkdownSchemaOptions = {}): AnyExtension[] {\n const runtime = options.runtime ?? { current: DEFAULT_RUNTIME };\n const markdown = (options.format ?? \"markdown\") === \"markdown\";\n\n return [\n StarterKit.configure({\n paragraph: false,\n codeBlock: false,\n ...(markdown ? { underline: false as const } : {}),\n link: {\n // Đang sửa thì bấm vào liên kết là đặt con trỏ, không phải mở tab mới (thanh định dạng có nút\n // Mở). Chỉ đọc thì Tiptap không chặn cú bấm — thẻ `<a target=\"_blank\">` tự mở như thường.\n openOnClick: false,\n autolink: true,\n linkOnPaste: true,\n // Gõ/dán `[chữ](https://…)` là thành liên kết — người quen markdown gõ vậy theo phản xạ.\n markdownLinks: true,\n defaultProtocol: \"https\",\n HTMLAttributes: { target: \"_blank\", rel: \"noopener noreferrer nofollow\" }\n }\n }),\n markdown ? ComitorParagraph : Paragraph,\n ComitorCodeBlock.configure({\n lowlight,\n defaultLanguage: null,\n // Tab trong khối mã là thụt lề — đúng điều người viết mã chờ đợi. Thoát khối: ↓ ở dòng cuối,\n // hoặc Enter ba lần (mặc định của Tiptap).\n enableTabIndentation: true,\n tabSize: 2\n }),\n TaskList,\n TaskItem.configure({\n nested: true,\n // Chữ của ĐOẠN ĐẦU, không phải `node.textContent`: mục cha có danh sách con thì `textContent` dính\n // liền chữ của mọi mục con (\"Gửi mã qua emailGiới hạn 5 lần/giờ\") — đã gặp.\n a11y: { checkboxLabel: (node) => runtime.current.taskCheckbox(node.firstChild?.textContent ?? \"\") }\n }),\n ...(markdown\n ? [\n TableKit.configure({ table: false, tableCell: false, tableHeader: false }),\n ComitorTable.configure({ resizable: false }),\n ComitorTableCell,\n ComitorTableHeader,\n MarkdownTableCellGuard\n ]\n : [TableKit.configure({ table: { resizable: false } })]),\n createImageExtension(runtime),\n createMentionExtension(runtime, options.mentionSuggestion),\n Placeholder.configure({ placeholder: () => runtime.current.placeholder }),\n ...(markdown\n ? [Markdown.configure({ marked: createMarkedInstance(), indentation: { style: \"space\", size: 2 } })]\n : [])\n ];\n}\n\n/** Lược đồ của `MarkdownEditor` — tên có từ `3.5.0`, cổng `test:editor-markdown` gọi thẳng nó. */\nexport function createMarkdownSchemaExtensions(options: Omit<MarkdownSchemaOptions, \"format\"> = {}): AnyExtension[] {\n return createSchemaExtensions({ ...options, format: \"markdown\" });\n}\n\n/**\n * Ảnh — tách địa chỉ LƯU khỏi địa chỉ HIỂN THỊ, và thoát cú pháp khi ghi markdown.\n *\n * Kho ảnh của module Comitor là bucket RIÊNG TƯ: thứ hiện được là URL đã ký, có hạn. Lưu URL đó vào\n * nội dung là lưu một giá trị sẽ chết ngay trong database — nên nội dung giữ `src` app trả về (khoá,\n * hoặc một route ổn định của app), còn `resolveImageSrc` đổi nó thành địa chỉ vẽ được lúc vẽ.\n *\n * ⚠ Đổi địa chỉ ở NODE VIEW, không ở `renderHTML`. `renderHTML` vừa vẽ ảnh trong trình soạn vừa là\n * thứ `editor.getHTML()` dùng để GHI — đổi `src` ở đó (cách của `3.5.0`, khi giá trị còn là markdown)\n * là `RichTextEditor` ghi URL đã ký vào HTML lưu xuống. Node view chỉ là DOM của màn hình; HTML lưu\n * vẫn đi `renderHTML` gốc với `src` gốc.\n *\n * Ảnh ĐANG tải lên không phải node — xem đầu `image-upload.ts`.\n */\nfunction createImageExtension(runtime: { readonly current: MarkdownEditorRuntime }) {\n return Image.extend({\n addNodeView() {\n return ({ node, HTMLAttributes }) => {\n const image = document.createElement(\"img\");\n setAttributes(image, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), [\"src\"]);\n const src = typeof node.attrs.src === \"string\" ? node.attrs.src : \"\";\n // `src=\"\"` thì trình duyệt tải lại CHÍNH trang làm ảnh — thiếu địa chỉ thì bỏ hẳn thuộc tính.\n if (src) image.setAttribute(\"src\", runtime.current.resolveImageSrc?.(src) ?? src);\n return { dom: image, update: (updated) => sameNode(updated, node) };\n };\n },\n renderMarkdown(node: JSONContent) {\n return imageMarkdown(String(node.attrs?.src ?? \"\"), node.attrs?.alt, node.attrs?.title);\n }\n }).configure({ allowBase64: false });\n}\n\n/**\n * Nhắc tên — định dạng lưu `[@Tên](mention:<id>)` (lý do ở `markdown-syntax.ts`) và tên SỐNG lúc vẽ.\n *\n * Tên sống chỉ đi vào thứ NGƯỜI XEM thấy: node view và `renderText` (chữ khi chép ra ngoài).\n * `renderHTML` — thứ `getHTML()` ghi xuống — giữ TÊN CHỤP lúc nhắc, cùng luật với markdown: nội dung\n * lưu không tự đổi theo danh bạ chỉ vì có người mở và lưu lại.\n */\nfunction createMentionExtension(\n runtime: { readonly current: MarkdownEditorRuntime },\n suggestion: MarkdownSchemaOptions[\"mentionSuggestion\"]\n) {\n const storedLabelOf = (attrs: Record<string, unknown>) => {\n const stored = typeof attrs.label === \"string\" ? attrs.label : \"\";\n return stored || (typeof attrs.id === \"string\" ? attrs.id : \"\");\n };\n const kindOf = (attrs: Record<string, unknown>) =>\n typeof attrs.kind === \"string\" && attrs.kind ? attrs.kind : undefined;\n const labelOf = (attrs: Record<string, unknown>) =>\n runtime.current.resolveMentionLabel?.(typeof attrs.id === \"string\" ? attrs.id : \"\", kindOf(attrs)) ??\n storedLabelOf(attrs);\n\n return Mention.extend({\n /*\n * `kind` (từ 3.7.0): loại thực thể — `null` là người. Ra HTML thành `data-kind` (lưu được ở định dạng\n * HTML và để app tô khác theo loại), ra markdown thành tiền tố `kind/` của địa chỉ.\n */\n addAttributes() {\n return {\n ...this.parent?.(),\n kind: {\n default: null,\n parseHTML: (element: HTMLElement) => element.getAttribute(\"data-kind\") || null,\n renderHTML: (attributes: Record<string, unknown>) =>\n typeof attributes.kind === \"string\" && attributes.kind ? { \"data-kind\": attributes.kind } : {}\n }\n };\n },\n markdownTokenName: \"mention\",\n markdownTokenizer: {\n name: \"mention\",\n level: \"inline\",\n start: (source: string) => source.indexOf(\"[@\"),\n tokenize: (source: string) => {\n const parsed = parseMentionMarkdown(source);\n return parsed\n ? { type: \"mention\", raw: parsed.raw, id: parsed.id, label: parsed.label, kind: parsed.kind ?? null }\n : undefined;\n }\n },\n parseMarkdown: (token, helpers) =>\n helpers.createNode(\"mention\", { id: token.id, label: token.label, kind: token.kind ?? null }),\n renderMarkdown: (node: JSONContent) =>\n renderMentionMarkdown(\n String(node.attrs?.id ?? \"\"),\n node.attrs?.label as string | null | undefined,\n node.attrs?.kind as string | null | undefined\n ),\n addNodeView() {\n return ({ node, HTMLAttributes }) => {\n /*\n * Có địa chỉ (`mentions.getHref`) thì vẽ `<a>`: nhắc VIỆC mở được việc đó. Chỉ nhận đường dẫn tương đối\n * cùng gốc hoặc `http(s):` — địa chỉ do app dựng, nhưng một `javascript:` lọt vào đây là chạy được.\n * Chỉ ở node VIEW: thứ GHI xuống (`renderHTML`, markdown) không mang địa chỉ nào.\n */\n const id = typeof node.attrs.id === \"string\" ? node.attrs.id : \"\";\n const href = runtime.current.resolveMentionHref?.(id, kindOf(node.attrs));\n const safeHref = href && /^(?:\\/(?![/\\\\])|https?:\\/\\/)/i.test(href) ? href : undefined;\n const dom = document.createElement(safeHref ? \"a\" : \"span\");\n setAttributes(dom, mergeAttributes({ \"data-type\": this.name }, this.options.HTMLAttributes, HTMLAttributes));\n if (safeHref) dom.setAttribute(\"href\", safeHref);\n dom.textContent = `@${labelOf(node.attrs)}`;\n return { dom, update: (updated) => sameNode(updated, node) };\n };\n }\n }).configure({\n renderText: ({ node }) => `@${labelOf(node.attrs)}`,\n // `options.HTMLAttributes` ở đây là bản Tiptap đã trộn sẵn (`data-type`, `data-id`, `data-label`).\n renderHTML: ({ options, node }) => [\"span\", options.HTMLAttributes, `@${storedLabelOf(node.attrs)}`],\n suggestion: { char: \"@\", allow: () => false, ...suggestion }\n });\n}\n\n/** Phím tắt riêng của trình soạn — xem `createEditorKeymap`. */\nexport interface EditorKeymapHandlers {\n /** ⌘/Ctrl+K — mở ô liên kết. Trả `true` khi đã xử lý. */\n onLinkShortcut: () => boolean;\n /** ⌘/Ctrl+Enter ngoài danh sách việc. Trả `false` để phím đi tiếp (xuống dòng cứng của Tiptap). */\n onSubmitShortcut: () => boolean;\n /** Enter trần (chỉ khi `submitOnEnter`). Trả `false` để phím đi tiếp (đoạn mới, mục danh sách mới…). */\n onEnterShortcut: () => boolean;\n}\n\n/**\n * Phím tắt của RIÊNG gói, ưu tiên cao hơn mặc định của Tiptap.\n *\n * ⌘/Ctrl+Enter: trong danh sách việc thì tick/bỏ tick mục đang đứng (như Linear); ngoài đó thì gửi\n * (`onSubmit`) — đúng phím mà ô bình luận nào cũng dùng. Không có `onSubmit` thì trả phím về cho\n * Tiptap (bản gốc gán nó cho xuống dòng cứng).\n *\n * ⌘/Ctrl+K: ô liên kết. Không đụng bảng lệnh của shell — listener của shell tự bỏ qua phím khi\n * đích là vùng `contenteditable` (`shell-context.tsx`, nhánh `isTyping`).\n */\nexport function createEditorKeymap(\n handlers: { readonly current: EditorKeymapHandlers },\n format: EditorFormat = \"markdown\"\n): Extension {\n // Chỉ ô bảng MARKDOWN cấm xuống dòng cứng (`ComitorTableCell`); ô bảng HTML chứa được cả khối.\n const swallowInCell = (editor: { state: { selection: { $from: ResolvedPos } } }) =>\n format === \"markdown\" && isInTableCell(editor.state.selection.$from);\n return Extension.create({\n name: \"comitorEditorKeymap\",\n priority: 1000,\n addKeyboardShortcuts() {\n return {\n \"Mod-k\": () => handlers.current.onLinkShortcut(),\n \"Mod-Enter\": ({ editor }) => {\n const toggled = editor.commands.command(({ tr, state }) => {\n const { $from } = state.selection;\n for (let depth = $from.depth; depth > 0; depth--) {\n const node = $from.node(depth);\n if (node.type.name !== \"taskItem\") continue;\n tr.setNodeMarkup($from.before(depth), undefined, { ...node.attrs, checked: !node.attrs.checked });\n return true;\n }\n return false;\n });\n if (toggled || handlers.current.onSubmitShortcut()) return true;\n // Phím đi tiếp tới Tiptap là xuống dòng cứng — trong ô bảng markdown thì nuốt (xem `ComitorTableCell`).\n return swallowInCell(editor);\n },\n \"Shift-Enter\": ({ editor }) => swallowInCell(editor),\n // `view.composing`: Enter đang chốt chữ của bộ gõ (Telex/VNI qua IME hệ thống) không được gửi tin.\n Enter: ({ editor }) => !editor.view.composing && handlers.current.onEnterShortcut()\n };\n }\n });\n}\n\n/** Con trỏ có đang đứng trong một ô bảng (thường hay tiêu đề) không. */\nfunction isInTableCell($pos: ResolvedPos): boolean {\n for (let depth = $pos.depth; depth > 0; depth--) {\n const name = $pos.node(depth).type.name;\n if (name === \"tableCell\" || name === \"tableHeader\") return true;\n }\n return false;\n}\n\n/** Ghi thuộc tính lên phần tử của node view — bỏ giá trị rỗng, như `DOMSerializer` làm với `renderHTML`. */\nfunction setAttributes(element: HTMLElement, attributes: Record<string, unknown>, skip: readonly string[] = []): void {\n for (const [key, value] of Object.entries(attributes)) {\n if (skip.includes(key) || value === null || value === undefined || value === false) continue;\n element.setAttribute(key, String(value));\n }\n}\n\n/**\n * Node view của nút LÁ (ảnh, nhắc tên): thuộc tính không đổi thì giữ DOM; đổi thì trả `false` để\n * ProseMirror dựng lại — rẻ, và không có trạng thái nào trong DOM đáng giữ.\n */\nfunction sameNode(updated: ProseMirrorNode, current: ProseMirrorNode): boolean {\n if (updated.type !== current.type) return false;\n return Object.keys(current.attrs).every((key) => updated.attrs[key] === current.attrs[key]);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { f as EditorBaseProps, a as EditorToolbarMode, E as EditorHandleBase } from '../editor-types-BacXD2VN.js';
|
|
3
3
|
import '@tiptap/core';
|
|
4
4
|
import '../components/composite/empty-state.js';
|
|
5
5
|
import './markdown-editor-constants.js';
|
|
@@ -2,7 +2,7 @@ import { Editor, Range, Extension } from '@tiptap/core';
|
|
|
2
2
|
import { PluginKey } from '@tiptap/pm/state';
|
|
3
3
|
import { SuggestionOptions } from '@tiptap/suggestion';
|
|
4
4
|
import { LucideIcon } from 'lucide-react';
|
|
5
|
-
import {
|
|
5
|
+
import { g as EditorFormat } from '../editor-types-BacXD2VN.js';
|
|
6
6
|
import { MarkdownEditorBlockId, MarkdownEditorLabels } from './markdown-editor-constants.js';
|
|
7
7
|
import '../components/composite/empty-state.js';
|
|
8
8
|
import 'react';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Editor } from '@tiptap/core';
|
|
2
2
|
import { LucideIcon } from 'lucide-react';
|
|
3
|
-
import {
|
|
3
|
+
import { g as EditorFormat } from '../editor-types-BacXD2VN.js';
|
|
4
4
|
import { MarkdownEditorLabels } from './markdown-editor-constants.js';
|
|
5
5
|
import '../components/composite/empty-state.js';
|
|
6
6
|
import 'react';
|
|
@@ -140,6 +140,12 @@ interface EditorBaseProps {
|
|
|
140
140
|
* phím giữ nghĩa mặc định.
|
|
141
141
|
*/
|
|
142
142
|
onSubmit?: () => void;
|
|
143
|
+
/**
|
|
144
|
+
* Enter trần cũng gửi (`onSubmit`) — cho ô nhập của khung hội thoại. Shift+Enter xuống dòng; Enter
|
|
145
|
+
* khi menu `@`/`/` đang mở vẫn chọn mục, và Enter đang gõ dở bộ gõ tiếng Việt (IME) vẫn chốt chữ.
|
|
146
|
+
* Không có `onSubmit` thì phím giữ nghĩa mặc định.
|
|
147
|
+
*/
|
|
148
|
+
submitOnEnter?: boolean;
|
|
143
149
|
onFocus?: () => void;
|
|
144
150
|
onBlur?: () => void;
|
|
145
151
|
/** Chữ mờ khi rỗng. Mặc định theo `ComitorLocaleProvider`. */
|
|
@@ -185,4 +191,4 @@ interface EditorBaseProps {
|
|
|
185
191
|
contentClassName?: string;
|
|
186
192
|
}
|
|
187
193
|
|
|
188
|
-
export { type EditorHandleBase as E, type ImageUploadHandlers as I, type MarkdownEditorImageUploadResult as M, type PendingUploads as P, type EditorToolbarMode as a, type MarkdownEditorMentionItem as b, type MarkdownEditorMentionSource as c, type MarkdownEditorMentions as d, type MarkdownEditorVariant as e, type
|
|
194
|
+
export { type EditorHandleBase as E, type ImageUploadHandlers as I, type MarkdownEditorImageUploadResult as M, type PendingUploads as P, type EditorToolbarMode as a, type MarkdownEditorMentionItem as b, type MarkdownEditorMentionSource as c, type MarkdownEditorMentions as d, type MarkdownEditorVariant as e, type EditorBaseProps as f, type EditorFormat as g, createImageUploadExtension as h, imageFilesFrom as i, insertImageFiles as j };
|
|
@@ -30,6 +30,7 @@ import './namespaces/command-palette.js';
|
|
|
30
30
|
import '../components/composite/command-palette-constants.js';
|
|
31
31
|
import './namespaces/command.js';
|
|
32
32
|
import './namespaces/combobox.js';
|
|
33
|
+
import '../chat/chat-constants.js';
|
|
33
34
|
import './namespaces/carousel.js';
|
|
34
35
|
import './namespaces/calendar.js';
|
|
35
36
|
import './namespaces/breadcrumb.js';
|
|
@@ -37,8 +38,10 @@ import './namespaces/banner.js';
|
|
|
37
38
|
import './namespaces/badge-overflow.js';
|
|
38
39
|
import './namespaces/avatar-group.js';
|
|
39
40
|
import '../components/composite/data-table-constants.js';
|
|
41
|
+
import '../components/composite/gantt-constants.js';
|
|
40
42
|
import '../components/composite/search-filter-bar-constants.js';
|
|
41
43
|
import '../components/composite/table-pagination-constants.js';
|
|
44
|
+
import '../components/composite/tree-view-constants.js';
|
|
42
45
|
import '../components/ui/action-bar-constants.js';
|
|
43
46
|
import '../components/ui/cropper-constants.js';
|
|
44
47
|
import '../components/ui/kanban-constants.js';
|
package/dist/i18n/messages.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { ConfirmDialogMessages } from './namespaces/confirm-dialog.js';
|
|
|
22
22
|
import { CommandPaletteMessages } from './namespaces/command-palette.js';
|
|
23
23
|
import { CommandMessages } from './namespaces/command.js';
|
|
24
24
|
import { ComboboxMessages } from './namespaces/combobox.js';
|
|
25
|
+
import { ChatLabels } from '../chat/chat-constants.js';
|
|
25
26
|
import { CarouselMessages } from './namespaces/carousel.js';
|
|
26
27
|
import { CalendarMessages } from './namespaces/calendar.js';
|
|
27
28
|
import { BreadcrumbMessages } from './namespaces/breadcrumb.js';
|
|
@@ -29,8 +30,10 @@ import { BannerMessages } from './namespaces/banner.js';
|
|
|
29
30
|
import { BadgeOverflowMessages } from './namespaces/badge-overflow.js';
|
|
30
31
|
import { AvatarGroupMessages } from './namespaces/avatar-group.js';
|
|
31
32
|
import { DataTableLabels } from '../components/composite/data-table-constants.js';
|
|
33
|
+
import { GanttLabels } from '../components/composite/gantt-constants.js';
|
|
32
34
|
import { SearchFilterBarLabels } from '../components/composite/search-filter-bar-constants.js';
|
|
33
35
|
import { TablePaginationLabels } from '../components/composite/table-pagination-constants.js';
|
|
36
|
+
import { TreeViewLabels } from '../components/composite/tree-view-constants.js';
|
|
34
37
|
import { ActionBarLabels } from '../components/ui/action-bar-constants.js';
|
|
35
38
|
import { CropperLabels } from '../components/ui/cropper-constants.js';
|
|
36
39
|
import { KanbanLabels } from '../components/ui/kanban-constants.js';
|
|
@@ -57,6 +60,7 @@ declare const comitorMessagesEn: {
|
|
|
57
60
|
breadcrumb: BreadcrumbMessages;
|
|
58
61
|
calendar: CalendarMessages;
|
|
59
62
|
carousel: CarouselMessages;
|
|
63
|
+
chat: ChatLabels;
|
|
60
64
|
combobox: ComboboxMessages;
|
|
61
65
|
command: CommandMessages;
|
|
62
66
|
commandPalette: CommandPaletteMessages;
|
|
@@ -70,6 +74,7 @@ declare const comitorMessagesEn: {
|
|
|
70
74
|
displayAxis: DisplayAxisMessages;
|
|
71
75
|
fileUpload: FileUploadLabels;
|
|
72
76
|
filterChips: FilterChipsMessages;
|
|
77
|
+
gantt: GanttLabels;
|
|
73
78
|
imageUploadField: ImageUploadFieldLabels;
|
|
74
79
|
inlineEdit: InlineEditMessages;
|
|
75
80
|
keyboardShortcuts: KeyboardShortcutsMessages;
|
|
@@ -92,6 +97,7 @@ declare const comitorMessagesEn: {
|
|
|
92
97
|
timePicker: TimePickerLabels;
|
|
93
98
|
toast: ToastMessages;
|
|
94
99
|
tour: TourLabels;
|
|
100
|
+
treeView: TreeViewLabels;
|
|
95
101
|
};
|
|
96
102
|
/** Hình dạng của một bảng chữ đầy đủ. Suy từ bảng tiếng Anh, không viết tay lần hai. */
|
|
97
103
|
type ComitorMessages = typeof comitorMessagesEn;
|
package/dist/i18n/messages.js
CHANGED
|
@@ -6,6 +6,7 @@ import { bannerEn, bannerVi } from "./namespaces/banner.js";
|
|
|
6
6
|
import { breadcrumbEn, breadcrumbVi } from "./namespaces/breadcrumb.js";
|
|
7
7
|
import { calendarEn, calendarVi } from "./namespaces/calendar.js";
|
|
8
8
|
import { carouselEn, carouselVi } from "./namespaces/carousel.js";
|
|
9
|
+
import { chatEn, chatVi } from "./namespaces/chat.js";
|
|
9
10
|
import { comboboxEn, comboboxVi } from "./namespaces/combobox.js";
|
|
10
11
|
import { commandEn, commandVi } from "./namespaces/command.js";
|
|
11
12
|
import { commandPaletteEn, commandPaletteVi } from "./namespaces/command-palette.js";
|
|
@@ -19,6 +20,7 @@ import { dialogEn, dialogVi } from "./namespaces/dialog.js";
|
|
|
19
20
|
import { displayAxisEn, displayAxisVi } from "./namespaces/display-axis.js";
|
|
20
21
|
import { fileUploadEn, fileUploadVi } from "./namespaces/file-upload.js";
|
|
21
22
|
import { filterChipsEn, filterChipsVi } from "./namespaces/filter-chips.js";
|
|
23
|
+
import { ganttEn, ganttVi } from "./namespaces/gantt.js";
|
|
22
24
|
import { imageUploadFieldEn, imageUploadFieldVi } from "./namespaces/image-upload-field.js";
|
|
23
25
|
import { inlineEditEn, inlineEditVi } from "./namespaces/inline-edit.js";
|
|
24
26
|
import { kanbanEn, kanbanVi } from "./namespaces/kanban.js";
|
|
@@ -41,6 +43,7 @@ import { tagsInputEn, tagsInputVi } from "./namespaces/tags-input.js";
|
|
|
41
43
|
import { timePickerEn, timePickerVi } from "./namespaces/time-picker.js";
|
|
42
44
|
import { toastEn, toastVi } from "./namespaces/toast.js";
|
|
43
45
|
import { tourEn, tourVi } from "./namespaces/tour.js";
|
|
46
|
+
import { treeViewEn, treeViewVi } from "./namespaces/tree-view.js";
|
|
44
47
|
const comitorMessagesEn = {
|
|
45
48
|
actionBar: actionBarEn,
|
|
46
49
|
avatarGroup: avatarGroupEn,
|
|
@@ -49,6 +52,7 @@ const comitorMessagesEn = {
|
|
|
49
52
|
breadcrumb: breadcrumbEn,
|
|
50
53
|
calendar: calendarEn,
|
|
51
54
|
carousel: carouselEn,
|
|
55
|
+
chat: chatEn,
|
|
52
56
|
combobox: comboboxEn,
|
|
53
57
|
command: commandEn,
|
|
54
58
|
commandPalette: commandPaletteEn,
|
|
@@ -62,6 +66,7 @@ const comitorMessagesEn = {
|
|
|
62
66
|
displayAxis: displayAxisEn,
|
|
63
67
|
fileUpload: fileUploadEn,
|
|
64
68
|
filterChips: filterChipsEn,
|
|
69
|
+
gantt: ganttEn,
|
|
65
70
|
imageUploadField: imageUploadFieldEn,
|
|
66
71
|
inlineEdit: inlineEditEn,
|
|
67
72
|
keyboardShortcuts: keyboardShortcutsEn,
|
|
@@ -83,7 +88,8 @@ const comitorMessagesEn = {
|
|
|
83
88
|
tagsInput: tagsInputEn,
|
|
84
89
|
timePicker: timePickerEn,
|
|
85
90
|
toast: toastEn,
|
|
86
|
-
tour: tourEn
|
|
91
|
+
tour: tourEn,
|
|
92
|
+
treeView: treeViewEn
|
|
87
93
|
};
|
|
88
94
|
const comitorMessagesVi = {
|
|
89
95
|
actionBar: actionBarVi,
|
|
@@ -93,6 +99,7 @@ const comitorMessagesVi = {
|
|
|
93
99
|
breadcrumb: breadcrumbVi,
|
|
94
100
|
calendar: calendarVi,
|
|
95
101
|
carousel: carouselVi,
|
|
102
|
+
chat: chatVi,
|
|
96
103
|
combobox: comboboxVi,
|
|
97
104
|
command: commandVi,
|
|
98
105
|
commandPalette: commandPaletteVi,
|
|
@@ -106,6 +113,7 @@ const comitorMessagesVi = {
|
|
|
106
113
|
displayAxis: displayAxisVi,
|
|
107
114
|
fileUpload: fileUploadVi,
|
|
108
115
|
filterChips: filterChipsVi,
|
|
116
|
+
gantt: ganttVi,
|
|
109
117
|
imageUploadField: imageUploadFieldVi,
|
|
110
118
|
inlineEdit: inlineEditVi,
|
|
111
119
|
keyboardShortcuts: keyboardShortcutsVi,
|
|
@@ -127,7 +135,8 @@ const comitorMessagesVi = {
|
|
|
127
135
|
tagsInput: tagsInputVi,
|
|
128
136
|
timePicker: timePickerVi,
|
|
129
137
|
toast: toastVi,
|
|
130
|
-
tour: tourVi
|
|
138
|
+
tour: tourVi,
|
|
139
|
+
treeView: treeViewVi
|
|
131
140
|
};
|
|
132
141
|
const COMITOR_MESSAGES = {
|
|
133
142
|
en: comitorMessagesEn,
|