@ecsplico/editor 2.0.0 → 2.1.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/CHANGELOG.md +18 -0
- package/README.md +4 -3
- package/dist/adapters/anchors.d.ts +80 -0
- package/dist/adapters/anchors.js +270 -0
- package/dist/adapters/anchors.js.map +1 -0
- package/dist/adapters/comments/client.d.ts +64 -0
- package/dist/adapters/comments/client.js +536 -0
- package/dist/adapters/comments/client.js.map +1 -0
- package/dist/adapters/comments/drizzle.d.ts +430 -0
- package/dist/adapters/comments/drizzle.js +236 -0
- package/dist/adapters/comments/drizzle.js.map +1 -0
- package/dist/adapters/comments/errors.d.ts +9 -0
- package/dist/adapters/comments/errors.js +19 -0
- package/dist/adapters/comments/errors.js.map +1 -0
- package/dist/adapters/comments/json.d.ts +19 -0
- package/dist/adapters/comments/json.js +94 -0
- package/dist/adapters/comments/json.js.map +1 -0
- package/dist/adapters/comments/record.js +78 -0
- package/dist/adapters/comments/record.js.map +1 -0
- package/dist/adapters/comments/server.d.ts +23 -0
- package/dist/adapters/comments/server.js +102 -0
- package/dist/adapters/comments/server.js.map +1 -0
- package/dist/adapters/comments/types.d.ts +109 -0
- package/dist/adapters/documents.js +108 -0
- package/dist/adapters/documents.js.map +1 -0
- package/dist/adapters/drizzle.d.ts +293 -0
- package/dist/adapters/drizzle.js +157 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/files.js +30 -0
- package/dist/adapters/files.js.map +1 -0
- package/dist/adapters/forge.d.ts +58 -0
- package/dist/adapters/forge.js +91 -0
- package/dist/adapters/forge.js.map +1 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +0 -0
- package/dist/adapters/json.d.ts +24 -0
- package/dist/adapters/json.js +128 -0
- package/dist/adapters/json.js.map +1 -0
- package/dist/adapters/nitro.d.ts +61 -0
- package/dist/adapters/nitro.js +212 -0
- package/dist/adapters/nitro.js.map +1 -0
- package/dist/adapters/routes.js +16 -0
- package/dist/adapters/routes.js.map +1 -0
- package/dist/adapters/shared.d.ts +37 -0
- package/dist/adapters/shared.js +32 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/vue.d.ts +67 -0
- package/dist/adapters/vue.js +187 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/core/doc-anchors.d.ts +5 -0
- package/dist/core/doc-anchors.js +4 -3
- package/dist/core/doc-anchors.js.map +1 -1
- package/dist/core/editor-ui.js +57 -1
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/surface-session.js +139 -0
- package/dist/core/surface-session.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/agent-op-review.d.ts +2 -2
- package/dist/features/ai/agent-op-review.js.map +1 -1
- package/dist/features/ai/agent-ops.d.ts +1 -1
- package/dist/features/ai/agent-ops.js +9 -6
- package/dist/features/ai/agent-ops.js.map +1 -1
- package/dist/features/ai/ai-apply.d.ts +1 -1
- package/dist/features/ai/ai-review.js +10 -1
- package/dist/features/ai/ai-review.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +78 -252
- package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.js +1 -0
- package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/components/SettingsAiTab.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/document-chat.d.ts +3 -2
- package/dist/features/ai/document-chat.js +16 -19
- package/dist/features/ai/document-chat.js.map +1 -1
- package/dist/features/ai/index.d.ts +5 -3
- package/dist/features/ai/index.js +4 -2
- package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
- package/dist/features/ai/protocol/agent-ops.js +65 -39
- package/dist/features/ai/protocol/agent-ops.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.d.ts +89 -0
- package/dist/features/ai/protocol/block-lines.js +177 -0
- package/dist/features/ai/protocol/block-lines.js.map +1 -0
- package/dist/features/ai/protocol/chat.js +10 -10
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -2
- package/dist/features/ai/protocol/index.js +3 -2
- package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
- package/dist/features/ai/react/AiSelectionMenu.js +64 -255
- package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/react/AiSidebar.js +2 -2
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -0
- package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/react/SettingsAiTab.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
- package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
- package/dist/features/ai/selection-flow.d.ts +133 -0
- package/dist/features/ai/selection-flow.js +283 -0
- package/dist/features/ai/selection-flow.js.map +1 -0
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-document-chat.d.ts +1 -1
- package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
- package/dist/features/authors/feature.js +1 -1
- package/dist/features/authors/index.js +1 -1
- package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
- package/dist/features/comments/components/CommentBubble.js +1 -1
- package/dist/features/comments/components/CommentComposer.js +1 -1
- package/dist/features/comments/components/CommentThreadView.js +5 -2
- package/dist/features/comments/components/CommentThreadView.js.map +1 -1
- package/dist/features/comments/components/CommentsMargin.js +108 -230
- package/dist/features/comments/components/CommentsMargin.js.map +1 -1
- package/dist/features/comments/components/CommentsOverlay.js +1 -1
- package/dist/features/comments/feature.js +1 -1
- package/dist/features/comments/index.d.ts +4 -2
- package/dist/features/comments/index.js +4 -2
- package/dist/features/comments/margin-controller.d.ts +44 -0
- package/dist/features/comments/margin-controller.js +132 -0
- package/dist/features/comments/margin-controller.js.map +1 -0
- package/dist/features/comments/margin-layout.d.ts +55 -0
- package/dist/features/comments/margin-layout.js +79 -0
- package/dist/features/comments/margin-layout.js.map +1 -0
- package/dist/features/comments/model.d.ts +8 -0
- package/dist/features/comments/model.js +60 -34
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentBubble.js +1 -1
- package/dist/features/comments/react/CommentComposer.js +1 -1
- package/dist/features/comments/react/CommentThreadView.js +2 -2
- package/dist/features/comments/react/CommentThreadView.js.map +1 -1
- package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
- package/dist/features/comments/react/CommentsMargin.js +85 -251
- package/dist/features/comments/react/CommentsMargin.js.map +1 -1
- package/dist/features/comments/react/CommentsOverlay.js +1 -1
- package/dist/features/comments/react/use-comments.js +1 -1
- package/dist/features/comments/react/use-margin-layout.js +45 -0
- package/dist/features/comments/react/use-margin-layout.js.map +1 -0
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +23 -0
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js +40 -0
- package/dist/features/comments/snapshot-equality.js.map +1 -0
- package/dist/features/comments/thread-anchor.js +33 -0
- package/dist/features/comments/thread-anchor.js.map +1 -0
- package/dist/features/comments/use-comments.d.ts +3 -1
- package/dist/features/comments/use-comments.js +1 -0
- package/dist/features/comments/use-comments.js.map +1 -1
- package/dist/features/comments/use-margin-layout.js +29 -0
- package/dist/features/comments/use-margin-layout.js.map +1 -0
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +21 -0
- package/dist/features/composites/accepted-children.js +126 -0
- package/dist/features/composites/accepted-children.js.map +1 -0
- package/dist/features/composites/components/CompositeDetails.js +1 -1
- package/dist/features/composites/components/CompositeInspector.js +38 -164
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/feature.d.ts +2 -1
- package/dist/features/composites/feature.js +8 -2
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/index.d.ts +5 -3
- package/dist/features/composites/index.js +8 -6
- package/dist/features/composites/inspector-model.d.ts +97 -0
- package/dist/features/composites/inspector-model.js +255 -0
- package/dist/features/composites/inspector-model.js.map +1 -0
- package/dist/features/composites/kinds.d.ts +11 -0
- package/dist/features/composites/kinds.js +17 -2
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/outline.d.ts +18 -5
- package/dist/features/composites/outline.js +44 -12
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.js +1 -1
- package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
- package/dist/features/composites/react/CompositeInspector.js +21 -180
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
- package/dist/features/composites/react/CompositeView.js +1 -1
- package/dist/features/composites/react/use-composite-inspector.js +29 -0
- package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/schema.d.ts +2 -2
- package/dist/features/composites/schema.js +2 -2
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/use-composite-anchors.d.ts +1 -1
- package/dist/features/composites/use-composite-inspector.js +41 -0
- package/dist/features/composites/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/use-composite-rows.d.ts +1 -1
- package/dist/features/media/index.d.ts +1 -1
- package/dist/features/media/vue.d.ts +1 -1
- package/dist/features/mentions/index.d.ts +3 -3
- package/dist/features/mentions/index.js +3 -3
- package/dist/features/mentions/schema.d.ts +1 -1
- package/dist/features/mentions/schema.js +1 -1
- package/dist/features/themes/index.d.ts +1 -1
- package/dist/features/themes/index.js +1 -1
- package/dist/features/transcript/feature.js +1 -1
- package/dist/features/transcript/index.js +2 -2
- package/dist/i18n/de/ai.js +2 -0
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/composites.js +8 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/tree.js +14 -1
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +22 -0
- package/dist/i18n/en/ai.js +2 -0
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +8 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/tree.js +14 -1
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.js +2 -2
- package/dist/integrations/composite-ai/composite-summary.js +2 -2
- package/dist/integrations/composite-ai/index.js +2 -2
- package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
- package/dist/markdown/MarkdownSurface.d.ts +1 -1
- package/dist/markdown/MarkdownSurface.js +2 -2
- package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
- package/dist/markdown/index.d.ts +2 -2
- package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
- package/dist/react/EditorSurface.d.ts +1 -1
- package/dist/react/EditorSurface.js +43 -149
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.js +3 -40
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/schema/advanced.js +2 -2
- package/dist/server/adapter.d.ts +2 -2
- package/dist/server/collab.js +1 -1
- package/dist/server/handlers/ai.d.ts +4 -2
- package/dist/server/handlers/ai.js +11 -9
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +1 -1
- package/dist/server/handlers/editor.d.ts +1 -1
- package/dist/server/handlers/editor.js +2 -2
- package/dist/server/host.d.ts +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/styles/tokens.css +1 -1
- package/dist/tree/DocumentTree.js +159 -387
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +37 -0
- package/dist/tree/drop-rules.js.map +1 -0
- package/dist/tree/react/DocumentTree.d.ts +2 -2
- package/dist/tree/react/DocumentTree.js +135 -397
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/react/use-tree-model.js +129 -0
- package/dist/tree/react/use-tree-model.js.map +1 -0
- package/dist/tree/react.js +1 -1
- package/dist/tree/tree-host.js +100 -0
- package/dist/tree/tree-host.js.map +1 -0
- package/dist/tree/tree-items.js +22 -43
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +580 -0
- package/dist/tree/tree-model.js.map +1 -0
- package/dist/tree/tree-rows.js +77 -0
- package/dist/tree/tree-rows.js.map +1 -0
- package/dist/tree/use-tree-model.js +94 -0
- package/dist/tree/use-tree-model.js.map +1 -0
- package/dist/tree/vue.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +2 -2
- package/dist/vue/components/EditorSurface.js +27 -87
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/components/EditorToolbar.js +7 -44
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/context.d.ts +1 -1
- package/dist/vue/context.js +1 -1
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/use-editor-ui.d.ts +1 -1
- package/dist/vue/use-notifier.d.ts +1 -1
- package/docs/integration.md +97 -4
- package/docs/llms.md +9 -1
- package/docs/usage.md +10 -5
- package/package.json +67 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","names":[],"sources":["../../../src/features/comments/model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport * as Y from 'yjs'\nimport type { AuthorIdentity, FeatureContext } from '../../core'\nimport { editorEntryOf, releaseRanges, resolveRange } from '../../core'\nimport { newBlockId } from '../../core/schema'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport { cancelCommentDraft, commentsDoc, commentsKey, type CommentsMeta, commentsUser, setActiveComment, startCommentDraft } from './comments-plugin'\nimport { COMMENT_LIMITS, COMMENTS_FIELD, type CommentReply, type CommentThread } from './schema'\n\nexport type DocRange = { from: number, to: number }\n\n/** What a comments UI renders: replaced (a new object) whenever any part of it changes, so a store subscriber re-renders only then. */\nexport interface CommentsSnapshot {\n /** Comments need a collaborative editor whose `Comments` extension has a user (a host model decides for itself). */\n available: boolean\n /**\n * Whether the user may compose a comment now: the default model says `available` and the editor is editable, a host\n * model may allow readers of a read-only document to comment.\n */\n canComment: boolean\n user: AuthorIdentity | null\n /** Plain snapshots of all threads in the Y.Doc, replaced on every change. */\n threads: readonly CommentThread[]\n /** Current text range per thread id (threads without one are orphans). */\n ranges: ReadonlyMap<string, DocRange>\n active: string | null\n /** Current range of the draft a new comment is being written for. */\n draft: DocRange | null\n /** The draft's text as the new thread will keep it (`quote`). */\n draftQuote: string\n /** Unresolved threads in document order, orphans last; resolved ones newest first. */\n open: readonly CommentThread[]\n resolved: readonly CommentThread[]\n}\n\n/** The mutations of the comments of one editor; no-ops without collaboration or a user. */\nexport interface CommentsActions {\n /** Creates a thread on the draft range; its id, or null without a (non-empty) draft or text. */\n add: (text: string) => string | null\n reply: (threadId: string, text: string) => void\n /** Changes the text of the user's own thread (`replyId` null) or reply. */\n edit: (threadId: string, replyId: string | null, text: string) => void\n /** Resolves or reopens a thread (anyone may). */\n setResolved: (threadId: string, resolved: boolean) => void\n /** Deletes the user's own thread with its marks / the user's own reply. */\n remove: (threadId: string) => void\n removeReply: (threadId: string, replyId: string) => void\n /** Activates a thread (`null`: none); `reveal` selects and scrolls to its text. */\n activate: (id: string | null, options?: { reveal?: boolean }) => void\n /** Starts a comment on the current selection (the layout shows the composer while `draft` is set). */\n startDraft: () => void\n cancelDraft: () => void\n}\n\n/**\n * The comments of one editor, framework-free: `state` is the observable snapshot, `refresh()` brings it up to date\n * (the framework hook calls it when the editor it watches changes; the model itself listens to that editor's\n * transactions from then on), `dispose()` detaches the editor and Y.Doc listeners and may be followed by `refresh()` again\n * (StrictMode mounts twice).\n */\nexport interface CommentsModel extends CommentsActions {\n readonly state: ReadonlyStore<CommentsSnapshot>\n refresh: () => void\n dispose: () => void\n}\n\nconst NO_RANGES: ReadonlyMap<string, DocRange> = new Map()\nconst NO_THREADS: readonly CommentThread[] = []\n\nconst EMPTY: CommentsSnapshot = {\n available: false,\n canComment: false,\n user: null,\n threads: NO_THREADS,\n ranges: NO_RANGES,\n active: null,\n draft: null,\n draftQuote: '',\n open: NO_THREADS,\n resolved: NO_THREADS,\n}\n\n/** A thread's `Y.Map` as plain data; `replies` converted explicitly so it is always a plain array. */\nfunction snapshotOf(thread: Y.Map<unknown>): CommentThread {\n const replies = thread.get('replies')\n return { ...(thread.toJSON() as CommentThread), replies: replies instanceof Y.Array ? replies.toArray() as CommentReply[] : [] }\n}\n\nfunction sameRange(a: DocRange | null, b: DocRange | null): boolean {\n return a === b || (!!a && !!b && a.from === b.from && a.to === b.to)\n}\n\nfunction sameRanges(a: ReadonlyMap<string, DocRange>, b: ReadonlyMap<string, DocRange>): boolean {\n if (a === b) return true\n if (a.size !== b.size) return false\n for (const [id, range] of a) {\n if (!sameRange(range, b.get(id) ?? null)) return false\n }\n return true\n}\n\nfunction sameUser(a: AuthorIdentity | null, b: AuthorIdentity | null): boolean {\n return a === b || (!!a && !!b && a.id === b.id && a.name === b.name && a.color === b.color)\n}\n\n/**\n * The comment threads of a collaborative editor, stored in its Y.Doc (`COMMENTS_FIELD`, one nested `Y.Map` per thread,\n * replies in a nested `Y.Array`), so they sync live with the document and persist with its Yjs state. Keeps the\n * `Comments` plugin's `known`/`hidden` thread ids in step with the store (highlights), exposes the plugin's ranges,\n * active thread and draft, and the mutations. Mutations are no-ops without collaboration or user; editing and\n * deleting are limited to the user's own comments and replies (a client-side rule: the Y.Doc itself has no ACL).\n * `getEditor` names the editor to work on (it may change over time and may return a destroyed or no editor).\n */\nexport function createCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY)\n\n const current = () => {\n const instance = getEditor()\n return instance && !instance.isDestroyed ? instance : undefined\n }\n\n // The editor whose transactions are followed, and the Y.Map of threads observed.\n let bound: Editor | undefined\n let observed: { doc: Y.Doc, map: Y.Map<unknown> } | undefined\n let threads: readonly CommentThread[] = NO_THREADS\n let syncQueued = false\n // The thread list the plugin was last told about, for the deactivation rule.\n let told: readonly CommentThread[] = NO_THREADS\n\n function unobserve() {\n observed?.map.unobserveDeep(onThreadsChanged)\n observed = undefined\n }\n\n function onThreadsChanged() {\n if (!observed) return\n threads = [...observed.map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n recompute()\n queueSync()\n }\n\n function onTransaction() {\n recompute()\n }\n\n function unbind() {\n bound?.off('transaction', onTransaction)\n // `setEditable` emits `update` only, and `canComment` follows `isEditable`.\n bound?.off('update', onTransaction)\n bound = undefined\n }\n\n /** The snapshot of the editor's current state; the previous object when nothing changed. */\n function recompute() {\n const instance = current()\n if (!instance) {\n store.set(EMPTY)\n return\n }\n const previous = store.get()\n const ydoc = commentsDoc(instance)\n const user = commentsUser(instance)\n const plugin = commentsKey.getState(instance.state)\n const ranges = plugin?.ranges ?? NO_RANGES\n const active = plugin?.active ?? null\n const anchor = plugin?.draft\n const draft = anchor ? resolveRange(instance.state, anchor) : null\n const draftQuote = draft ? instance.state.doc.textBetween(draft.from, draft.to, ' ').slice(0, COMMENT_LIMITS.quote) : ''\n const available = !!ydoc && !!user\n const canComment = available && instance.isEditable\n\n const sameThreads = threads === previous.threads\n const sameRangeMap = sameRanges(ranges, previous.ranges)\n if (\n available === previous.available && canComment === previous.canComment && sameUser(user, previous.user) && sameThreads && sameRangeMap\n && active === previous.active && sameRange(draft, previous.draft) && draftQuote === previous.draftQuote\n ) return\n\n const kept = sameThreads && sameRangeMap\n const open = kept\n ? previous.open\n : threads\n .filter(t => !t.resolved)\n .sort((a, b) => (ranges.get(a.id)?.from ?? Infinity) - (ranges.get(b.id)?.from ?? Infinity) || a.createdAt.localeCompare(b.createdAt))\n const resolved = sameThreads\n ? previous.resolved\n : threads.filter(t => t.resolved).sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n store.set({\n available,\n canComment,\n user,\n threads,\n ranges: sameRangeMap ? previous.ranges : ranges,\n active,\n draft: sameRange(draft, previous.draft) ? previous.draft : draft,\n draftQuote,\n open,\n resolved,\n })\n }\n\n // Highlights appear and disappear on every client as threads arrive, get resolved or deleted. The active thread\n // stays highlighted even when resolved (a resolved thread activated from its card), so a thread that turns resolved\n // or disappears while active is deactivated here, on every client, not only on the one that resolved or deleted it.\n // Queued (not dispatched from inside the Yjs observer or a transaction listener).\n function queueSync() {\n if (syncQueued) return\n syncQueued = true\n queueMicrotask(() => {\n syncQueued = false\n syncPlugin()\n })\n }\n\n function syncPlugin() {\n const instance = current()\n if (!instance || instance !== bound) return\n const value = commentsKey.getState(instance.state)\n if (!value) return\n const known = new Set(threads.map(t => t.id))\n const hidden = new Set(threads.filter(t => t.resolved).map(t => t.id))\n const meta: CommentsMeta = { known, hidden }\n const before = value.active ? told.find(t => t.id === value.active) : undefined\n if (value.active && before && !before.resolved && (!known.has(value.active) || hidden.has(value.active))) meta.active = null\n told = threads\n instance.view.dispatch(instance.state.tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n }\n\n /** Follows the editor `getEditor` names now, and the Y.Doc of its collaboration session. */\n function refresh() {\n const instance = current()\n if (instance !== bound) {\n unbind()\n unobserve()\n bound = instance\n threads = NO_THREADS\n told = NO_THREADS\n instance?.on('transaction', onTransaction)\n instance?.on('update', onTransaction)\n // The new editor's plugin knows no threads yet.\n queueSync()\n }\n const ydoc = instance ? commentsDoc(instance) : null\n if ((observed?.doc ?? null) !== ydoc) {\n unobserve()\n if (ydoc) {\n const map = ydoc.getMap<unknown>(COMMENTS_FIELD)\n observed = { doc: ydoc, map }\n map.observeDeep(onThreadsChanged)\n threads = [...map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n }\n else {\n threads = NO_THREADS\n }\n queueSync()\n }\n recompute()\n }\n\n function dispose() {\n unbind()\n unobserve()\n threads = NO_THREADS\n told = NO_THREADS\n store.set(EMPTY)\n }\n\n /** Editor, thread map and user while comments are available; null otherwise. */\n function writable() {\n const instance = current()\n const ydoc = instance && commentsDoc(instance)\n const author = instance && commentsUser(instance)\n if (!instance || !ydoc || !author) return null\n return { editor: instance, ydoc, map: ydoc.getMap<unknown>(COMMENTS_FIELD), user: { id: author.id, name: author.name, color: author.color } }\n }\n\n /** Trimmed and cut to the limit; '' when nothing is left. */\n const clean = (text: string) => text.trim().slice(0, COMMENT_LIMITS.text)\n\n function threadMap(map: Y.Map<unknown>, threadId: string): Y.Map<unknown> | null {\n const thread = map.get(threadId)\n return thread instanceof Y.Map ? thread : null\n }\n\n function repliesOf(thread: Y.Map<unknown>): Y.Array<CommentReply> | null {\n const replies = thread.get('replies')\n return replies instanceof Y.Array ? replies as Y.Array<CommentReply> : null\n }\n\n function add(text: string): string | null {\n const ctx = writable()\n const body = clean(text)\n const anchor = ctx && commentsKey.getState(ctx.editor.state)?.draft\n const range = ctx && anchor ? resolveRange(ctx.editor.state, anchor) : null\n const type = ctx?.editor.schema.marks.comment\n if (!ctx || !anchor || !range || range.from >= range.to || !body || !type) return null\n const id = newBlockId()\n const { state } = ctx.editor\n const quote = state.doc.textBetween(range.from, range.to, ' ').slice(0, COMMENT_LIMITS.quote)\n const tr = state.tr.addMark(range.from, range.to, type.create({ id }))\n // Text that takes no marks (code block, inline code): no thread that would start out without a range.\n if (!tr.docChanged) return null\n // Not undoable: the mark is the thread's anchor, not an edit of the text.\n const meta: CommentsMeta = { draft: null, active: id }\n ctx.editor.view.dispatch(tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n releaseRanges(ctx.editor, [anchor])\n const thread = new Y.Map<unknown>()\n thread.set('id', id)\n thread.set('author', ctx.user)\n thread.set('text', body)\n thread.set('createdAt', new Date().toISOString())\n thread.set('quote', quote)\n thread.set('resolved', false)\n thread.set('replies', new Y.Array<CommentReply>())\n ctx.map.set(id, thread)\n return id\n }\n\n function reply(threadId: string, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies || !body) return\n replies.push([{ id: newBlockId(), author: ctx.user, text: body, createdAt: new Date().toISOString() }])\n }\n\n function edit(threadId: string, replyId: string | null, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || !body) return\n const now = new Date().toISOString()\n if (replyId === null) {\n if ((thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id || thread.get('text') === body) return\n ctx.ydoc.transact(() => {\n thread.set('text', body)\n thread.set('editedAt', now)\n })\n return\n }\n const replies = repliesOf(thread)\n const index = replies ? replies.toArray().findIndex(r => r.id === replyId) : -1\n const previous = replies && index >= 0 ? replies.get(index) : undefined\n if (!replies || !previous || previous.author.id !== ctx.user.id || previous.text === body) return\n ctx.ydoc.transact(() => {\n replies.delete(index, 1)\n replies.insert(index, [{ ...previous, text: body, editedAt: now }])\n })\n }\n\n function setResolved(threadId: string, value: boolean): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread) return\n thread.set('resolved', value)\n if (value && commentsKey.getState(ctx.editor.state)?.active === threadId) setActiveComment(ctx.editor, null)\n }\n\n function remove(threadId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || (thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id) return\n const { state } = ctx.editor\n const tr = state.tr\n state.doc.descendants((node, pos) => {\n if (!node.isText) return\n const mark = node.marks.find(m => m.type.name === 'comment' && m.attrs.id === threadId)\n if (mark) tr.removeMark(pos, pos + node.nodeSize, mark)\n })\n if (commentsKey.getState(state)?.active === threadId) tr.setMeta(commentsKey, { active: null } satisfies CommentsMeta)\n if (tr.docChanged || tr.getMeta(commentsKey)) ctx.editor.view.dispatch(tr.setMeta('addToHistory', false))\n ctx.map.delete(threadId)\n }\n\n function removeReply(threadId: string, replyId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies) return\n const index = replies.toArray().findIndex(r => r.id === replyId)\n if (index >= 0 && replies.get(index).author.id === ctx.user.id) replies.delete(index, 1)\n }\n\n function activate(id: string | null, options: { reveal?: boolean } = {}): void {\n const instance = current()\n if (instance) setActiveComment(instance, id, options)\n }\n\n function startDraft(): void {\n const ctx = writable()\n if (ctx) startCommentDraft(ctx.editor)\n }\n\n function cancelDraft(): void {\n const instance = current()\n if (instance) cancelCommentDraft(instance)\n }\n\n return { state: store, refresh, dispose, add, reply, edit, setResolved, remove, removeReply, activate, startDraft, cancelDraft }\n}\n\n/** Creates the comments model of one editor, as a host supplies it to `commentsFeatureCore`. */\nexport type CommentsModelFactory = (getEditor: () => Editor | undefined) => CommentsModel\n\n// The host's model factory per surface context, set by the comments feature while the surface builds its extensions.\nconst modelFactories = new WeakMap<FeatureContext, CommentsModelFactory>()\n\n/** Registers the host model factory of `ctx` (the comments feature does this; hosts pass `model` to the feature). */\nexport function provideCommentsModel(ctx: FeatureContext, factory: CommentsModelFactory): void {\n modelFactories.set(ctx, factory)\n}\n\n/** The host model factory the comments feature of `ctx` was configured with; undefined for the default Y.Doc model. */\nexport function commentsModelOf(ctx: FeatureContext): CommentsModelFactory | undefined {\n return modelFactories.get(ctx)\n}\n\n/**\n * The comments model the framework hooks (`useComments`) use: the model of the surface `getEditor` belongs to\n * (`commentsModelOf` of its feature context: the host's `model` factory) and `createCommentsModel` for every other\n * editor. It follows the editor over time: when the editor changes to one of a surface with another model, the inner\n * model is replaced (disposed, created, refreshed) and `state` carries on with the new one. The factory runs once per\n * consumer of this function, so a host model that must share state across them keeps it outside the factory.\n */\nexport function createEditorCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY)\n let inner: { model: CommentsModel, factory: CommentsModelFactory, stop: () => void } | undefined\n\n function release() {\n inner?.stop()\n inner?.model.dispose()\n inner = undefined\n }\n\n /** The inner model for the editor now; a replacement when its surface brings another factory. */\n function current(): CommentsModel {\n const entry = editorEntryOf(getEditor())\n const factory = (entry && commentsModelOf(entry.ctx)) ?? createCommentsModel\n if (inner?.factory !== factory) {\n release()\n const model = factory(getEditor)\n inner = { model, factory, stop: model.state.subscribe(snapshot => store.set(snapshot)) }\n store.set(model.state.get())\n }\n return inner!.model\n }\n\n return {\n state: store,\n refresh: () => current().refresh(),\n dispose: () => {\n release()\n store.set(EMPTY)\n },\n add: text => current().add(text),\n reply: (threadId, text) => current().reply(threadId, text),\n edit: (threadId, replyId, text) => current().edit(threadId, replyId, text),\n setResolved: (threadId, resolved) => current().setResolved(threadId, resolved),\n remove: threadId => current().remove(threadId),\n removeReply: (threadId, replyId) => current().removeReply(threadId, replyId),\n activate: (id, options) => current().activate(id, options),\n startDraft: () => current().startDraft(),\n cancelDraft: () => current().cancelDraft(),\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"model.js","names":["EMPTY"],"sources":["../../../src/features/comments/model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport * as Y from 'yjs'\nimport type { AuthorIdentity, FeatureContext } from '../../core'\nimport { anchorResolverOf, editorEntryOf, releaseRanges, resolveRange } from '../../core'\nimport { newBlockId } from '../../core/schema'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport { cancelCommentDraft, commentsDoc, commentsKey, type CommentsMeta, commentsUser, setActiveComment, startCommentDraft } from './comments-plugin'\nimport { COMMENT_LIMITS, COMMENTS_FIELD, type CommentAnchor, type CommentReply, type CommentThread } from './schema'\nimport { EMPTY_SNAPSHOT as EMPTY, NO_POSITIONS, NO_RANGES, NO_THREADS, sameOrphanAt, sameRange, sameRanges, sameUser } from './snapshot-equality'\nimport { captureThreadAnchor, resolveThreadAnchor } from './thread-anchor'\n\nexport type DocRange = { from: number, to: number }\n\n/** What a comments UI renders: replaced (a new object) whenever any part of it changes, so a store subscriber re-renders only then. */\nexport interface CommentsSnapshot {\n /** Comments need a collaborative editor whose `Comments` extension has a user (a host model decides for itself). */\n available: boolean\n /**\n * Whether the user may compose a comment now: the default model says `available` and the editor is editable, a host\n * model may allow readers of a read-only document to comment.\n */\n canComment: boolean\n user: AuthorIdentity | null\n /** Plain snapshots of all threads in the Y.Doc, replaced on every change. */\n threads: readonly CommentThread[]\n /** Current text range per thread id (threads without one are orphans). */\n ranges: ReadonlyMap<string, DocRange>\n /**\n * Where the text of an orphaned open thread was: the document position of the deletion point, resolved from the\n * thread's stored anchor (so it is the same place for every collaborator and follows edits). Absent for orphans\n * without a usable anchor (threads from before anchors, or whose block is gone for good).\n */\n orphanAt: ReadonlyMap<string, number>\n active: string | null\n /** Current range of the draft a new comment is being written for. */\n draft: DocRange | null\n /** The draft's text as the new thread will keep it (`quote`). */\n draftQuote: string\n /** Unresolved threads in document order, orphans last; resolved ones newest first. */\n open: readonly CommentThread[]\n resolved: readonly CommentThread[]\n}\n\n/** The mutations of the comments of one editor; no-ops without collaboration or a user. */\nexport interface CommentsActions {\n /** Creates a thread on the draft range; its id, or null without a (non-empty) draft or text. */\n add: (text: string) => string | null\n reply: (threadId: string, text: string) => void\n /** Changes the text of the user's own thread (`replyId` null) or reply. */\n edit: (threadId: string, replyId: string | null, text: string) => void\n /** Resolves or reopens a thread (anyone may). */\n setResolved: (threadId: string, resolved: boolean) => void\n /** Deletes the user's own thread with its marks / the user's own reply. */\n remove: (threadId: string) => void\n removeReply: (threadId: string, replyId: string) => void\n /** Activates a thread (`null`: none); `reveal` selects and scrolls to its text. */\n activate: (id: string | null, options?: { reveal?: boolean }) => void\n /** Starts a comment on the current selection (the layout shows the composer while `draft` is set). */\n startDraft: () => void\n cancelDraft: () => void\n}\n\n/**\n * The comments of one editor, framework-free: `state` is the observable snapshot, `refresh()` brings it up to date\n * (the framework hook calls it when the editor it watches changes; the model itself listens to that editor's\n * transactions from then on), `dispose()` detaches the editor and Y.Doc listeners and may be followed by `refresh()` again\n * (StrictMode mounts twice).\n */\nexport interface CommentsModel extends CommentsActions {\n readonly state: ReadonlyStore<CommentsSnapshot>\n refresh: () => void\n dispose: () => void\n}\n\n/** A thread's `Y.Map` as plain data; `replies` converted explicitly so it is always a plain array. */\nfunction snapshotOf(thread: Y.Map<unknown>): CommentThread {\n const replies = thread.get('replies')\n return { ...(thread.toJSON() as CommentThread), replies: replies instanceof Y.Array ? replies.toArray() as CommentReply[] : [] }\n}\n\n/**\n * The comment threads of a collaborative editor, stored in its Y.Doc (`COMMENTS_FIELD`, one nested `Y.Map` per thread,\n * replies in a nested `Y.Array`), so they sync live with the document and persist with its Yjs state. Keeps the\n * `Comments` plugin's `known`/`hidden` thread ids in step with the store (highlights), exposes the plugin's ranges,\n * active thread and draft, and the mutations. Mutations are no-ops without collaboration or user; editing and\n * deleting are limited to the user's own comments and replies (a client-side rule: the Y.Doc itself has no ACL).\n * `getEditor` names the editor to work on (it may change over time and may return a destroyed or no editor).\n * Each thread also keeps `anchor` (a Yjs relative position of its text's start, see `CommentAnchor`): `orphanAt` resolves\n * it for the threads whose text is gone. Threads from before anchors get theirs written once they are seen with a range.\n */\nexport function createCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY)\n\n const current = () => {\n const instance = getEditor()\n return instance && !instance.isDestroyed ? instance : undefined\n }\n\n // The editor whose transactions are followed, and the Y.Map of threads observed.\n let bound: Editor | undefined\n let observed: { doc: Y.Doc, map: Y.Map<unknown> } | undefined\n let threads: readonly CommentThread[] = NO_THREADS\n let syncQueued = false\n // The thread list the plugin was last told about, for the deactivation rule.\n let told: readonly CommentThread[] = NO_THREADS\n // Threads already tried for an anchor (written, or not capturable), so a failed capture is not repeated on every change.\n const anchorTried = new Set<string>()\n let healQueued = false\n\n function unobserve() {\n observed?.map.unobserveDeep(onThreadsChanged)\n observed = undefined\n }\n\n function onThreadsChanged() {\n if (!observed) return\n threads = [...observed.map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n recompute()\n queueSync()\n }\n\n function onTransaction() {\n recompute()\n }\n\n function unbind() {\n bound?.off('transaction', onTransaction)\n // `setEditable` emits `update` only, and `canComment` follows `isEditable`.\n bound?.off('update', onTransaction)\n bound = undefined\n }\n\n /** The snapshot of the editor's current state; the previous object when nothing changed. */\n function recompute() {\n const instance = current()\n if (!instance) {\n store.set(EMPTY)\n return\n }\n const previous = store.get()\n const ydoc = commentsDoc(instance)\n const user = commentsUser(instance)\n const plugin = commentsKey.getState(instance.state)\n const ranges = plugin?.ranges ?? NO_RANGES\n const orphanAt = orphanPositions(instance, ranges)\n const active = plugin?.active ?? null\n const anchor = plugin?.draft\n const draft = anchor ? resolveRange(instance.state, anchor) : null\n const draftQuote = draft ? instance.state.doc.textBetween(draft.from, draft.to, ' ').slice(0, COMMENT_LIMITS.quote) : ''\n const available = !!ydoc && !!user\n const canComment = available && instance.isEditable\n\n const sameThreads = threads === previous.threads\n const sameRangeMap = sameRanges(ranges, previous.ranges)\n const sameOrphanMap = sameOrphanAt(orphanAt, previous.orphanAt)\n if (\n available === previous.available && canComment === previous.canComment && sameUser(user, previous.user) && sameThreads && sameRangeMap && sameOrphanMap\n && active === previous.active && sameRange(draft, previous.draft) && draftQuote === previous.draftQuote\n ) return\n\n const kept = sameThreads && sameRangeMap\n const open = kept\n ? previous.open\n : threads\n .filter(t => !t.resolved)\n .sort((a, b) => (ranges.get(a.id)?.from ?? Infinity) - (ranges.get(b.id)?.from ?? Infinity) || a.createdAt.localeCompare(b.createdAt))\n const resolved = sameThreads\n ? previous.resolved\n : threads.filter(t => t.resolved).sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n store.set({\n available,\n canComment,\n user,\n threads,\n ranges: sameRangeMap ? previous.ranges : ranges,\n orphanAt: sameOrphanMap ? previous.orphanAt : orphanAt,\n active,\n draft: sameRange(draft, previous.draft) ? previous.draft : draft,\n draftQuote,\n open,\n resolved,\n })\n if (available) queueHeal()\n }\n\n /** Where each unresolved thread without a text range was, from its anchor; empty without a Yjs session. */\n function orphanPositions(instance: Editor, ranges: ReadonlyMap<string, DocRange>): ReadonlyMap<string, number> {\n const resolver = anchorResolverOf(instance)\n if (!resolver) return NO_POSITIONS\n const size = instance.state.doc.content.size\n let found: Map<string, number> | undefined\n for (const thread of threads) {\n if (thread.resolved || ranges.has(thread.id) || !thread.anchor) continue\n const position = resolveThreadAnchor(instance.state, resolver, thread.anchor)\n if (position !== null && position <= size) (found ??= new Map()).set(thread.id, position)\n }\n return found ?? NO_POSITIONS\n }\n\n // Threads from before anchors get theirs once they are seen with their text in the document (any client with a user\n // writes it; the positions are equivalent, so concurrent writes agree). Queued: not written from inside a Yjs\n // observer or a transaction listener.\n function queueHeal() {\n if (healQueued || !threads.some(t => !t.anchor && !anchorTried.has(t.id))) return\n healQueued = true\n queueMicrotask(() => {\n healQueued = false\n healAnchors()\n })\n }\n\n function healAnchors() {\n const ctx = writable()\n const resolver = ctx && anchorResolverOf(ctx.editor)\n if (!ctx || !resolver || ctx.editor !== bound) return\n const { ranges } = store.get()\n const written: [Y.Map<unknown>, CommentAnchor][] = []\n for (const { id, anchor } of threads) {\n const range = ranges.get(id)\n if (anchor || anchorTried.has(id) || !range) continue\n anchorTried.add(id)\n const thread = threadMap(ctx.map, id)\n const captured = thread && captureThreadAnchor(ctx.editor.state, resolver, range)\n if (thread && captured) written.push([thread, captured])\n }\n if (written.length) ctx.ydoc.transact(() => written.forEach(([thread, captured]) => thread.set('anchor', captured)))\n }\n\n // Highlights appear and disappear on every client as threads arrive, get resolved or deleted. The active thread\n // stays highlighted even when resolved (a resolved thread activated from its card), so a thread that turns resolved\n // or disappears while active is deactivated here, on every client, not only on the one that resolved or deleted it.\n // Queued (not dispatched from inside the Yjs observer or a transaction listener).\n function queueSync() {\n if (syncQueued) return\n syncQueued = true\n queueMicrotask(() => {\n syncQueued = false\n syncPlugin()\n })\n }\n\n function syncPlugin() {\n const instance = current()\n if (!instance || instance !== bound) return\n const value = commentsKey.getState(instance.state)\n if (!value) return\n const known = new Set(threads.map(t => t.id))\n const hidden = new Set(threads.filter(t => t.resolved).map(t => t.id))\n const meta: CommentsMeta = { known, hidden }\n const before = value.active ? told.find(t => t.id === value.active) : undefined\n if (value.active && before && !before.resolved && (!known.has(value.active) || hidden.has(value.active))) meta.active = null\n told = threads\n instance.view.dispatch(instance.state.tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n }\n\n /** Follows the editor `getEditor` names now, and the Y.Doc of its collaboration session. */\n function refresh() {\n const instance = current()\n if (instance !== bound) {\n unbind()\n unobserve()\n bound = instance\n threads = NO_THREADS\n told = NO_THREADS\n anchorTried.clear()\n instance?.on('transaction', onTransaction)\n instance?.on('update', onTransaction)\n // The new editor's plugin knows no threads yet.\n queueSync()\n }\n const ydoc = instance ? commentsDoc(instance) : null\n if ((observed?.doc ?? null) !== ydoc) {\n unobserve()\n if (ydoc) {\n const map = ydoc.getMap<unknown>(COMMENTS_FIELD)\n observed = { doc: ydoc, map }\n map.observeDeep(onThreadsChanged)\n threads = [...map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n }\n else {\n threads = NO_THREADS\n }\n queueSync()\n }\n recompute()\n }\n\n function dispose() {\n unbind()\n unobserve()\n threads = NO_THREADS\n told = NO_THREADS\n anchorTried.clear()\n store.set(EMPTY)\n }\n\n /** Editor, thread map and user while comments are available; null otherwise. */\n function writable() {\n const instance = current()\n const ydoc = instance && commentsDoc(instance)\n const author = instance && commentsUser(instance)\n if (!instance || !ydoc || !author) return null\n return { editor: instance, ydoc, map: ydoc.getMap<unknown>(COMMENTS_FIELD), user: { id: author.id, name: author.name, color: author.color } }\n }\n\n /** Trimmed and cut to the limit; '' when nothing is left. */\n const clean = (text: string) => text.trim().slice(0, COMMENT_LIMITS.text)\n\n function threadMap(map: Y.Map<unknown>, threadId: string): Y.Map<unknown> | null {\n const thread = map.get(threadId)\n return thread instanceof Y.Map ? thread : null\n }\n\n function repliesOf(thread: Y.Map<unknown>): Y.Array<CommentReply> | null {\n const replies = thread.get('replies')\n return replies instanceof Y.Array ? replies as Y.Array<CommentReply> : null\n }\n\n function add(text: string): string | null {\n const ctx = writable()\n const body = clean(text)\n const anchor = ctx && commentsKey.getState(ctx.editor.state)?.draft\n const range = ctx && anchor ? resolveRange(ctx.editor.state, anchor) : null\n const type = ctx?.editor.schema.marks.comment\n if (!ctx || !anchor || !range || range.from >= range.to || !body || !type) return null\n const id = newBlockId()\n const { state } = ctx.editor\n const resolver = anchorResolverOf(ctx.editor)\n const threadAnchor = resolver ? captureThreadAnchor(state, resolver, range) : null\n const quote = state.doc.textBetween(range.from, range.to, ' ').slice(0, COMMENT_LIMITS.quote)\n const tr = state.tr.addMark(range.from, range.to, type.create({ id }))\n // Text that takes no marks (code block, inline code): no thread that would start out without a range.\n if (!tr.docChanged) return null\n // Not undoable: the mark is the thread's anchor, not an edit of the text.\n const meta: CommentsMeta = { draft: null, active: id }\n ctx.editor.view.dispatch(tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n releaseRanges(ctx.editor, [anchor])\n const thread = new Y.Map<unknown>()\n thread.set('id', id)\n thread.set('author', ctx.user)\n thread.set('text', body)\n thread.set('createdAt', new Date().toISOString())\n thread.set('quote', quote)\n thread.set('resolved', false)\n if (threadAnchor) thread.set('anchor', threadAnchor)\n thread.set('replies', new Y.Array<CommentReply>())\n ctx.map.set(id, thread)\n return id\n }\n\n function reply(threadId: string, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies || !body) return\n replies.push([{ id: newBlockId(), author: ctx.user, text: body, createdAt: new Date().toISOString() }])\n }\n\n function edit(threadId: string, replyId: string | null, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || !body) return\n const now = new Date().toISOString()\n if (replyId === null) {\n if ((thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id || thread.get('text') === body) return\n ctx.ydoc.transact(() => {\n thread.set('text', body)\n thread.set('editedAt', now)\n })\n return\n }\n const replies = repliesOf(thread)\n const index = replies ? replies.toArray().findIndex(r => r.id === replyId) : -1\n const previous = replies && index >= 0 ? replies.get(index) : undefined\n if (!replies || !previous || previous.author.id !== ctx.user.id || previous.text === body) return\n ctx.ydoc.transact(() => {\n replies.delete(index, 1)\n replies.insert(index, [{ ...previous, text: body, editedAt: now }])\n })\n }\n\n function setResolved(threadId: string, value: boolean): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread) return\n thread.set('resolved', value)\n if (value && commentsKey.getState(ctx.editor.state)?.active === threadId) setActiveComment(ctx.editor, null)\n }\n\n function remove(threadId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || (thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id) return\n const { state } = ctx.editor\n const tr = state.tr\n state.doc.descendants((node, pos) => {\n if (!node.isText) return\n const mark = node.marks.find(m => m.type.name === 'comment' && m.attrs.id === threadId)\n if (mark) tr.removeMark(pos, pos + node.nodeSize, mark)\n })\n if (commentsKey.getState(state)?.active === threadId) tr.setMeta(commentsKey, { active: null } satisfies CommentsMeta)\n if (tr.docChanged || tr.getMeta(commentsKey)) ctx.editor.view.dispatch(tr.setMeta('addToHistory', false))\n ctx.map.delete(threadId)\n }\n\n function removeReply(threadId: string, replyId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies) return\n const index = replies.toArray().findIndex(r => r.id === replyId)\n if (index >= 0 && replies.get(index).author.id === ctx.user.id) replies.delete(index, 1)\n }\n\n function activate(id: string | null, options: { reveal?: boolean } = {}): void {\n const instance = current()\n if (instance) setActiveComment(instance, id, options)\n }\n\n function startDraft(): void {\n const ctx = writable()\n if (ctx) startCommentDraft(ctx.editor)\n }\n\n function cancelDraft(): void {\n const instance = current()\n if (instance) cancelCommentDraft(instance)\n }\n\n return { state: store, refresh, dispose, add, reply, edit, setResolved, remove, removeReply, activate, startDraft, cancelDraft }\n}\n\n/** Creates the comments model of one editor, as a host supplies it to `commentsFeatureCore`. */\nexport type CommentsModelFactory = (getEditor: () => Editor | undefined) => CommentsModel\n\n// The host's model factory per surface context, set by the comments feature while the surface builds its extensions.\nconst modelFactories = new WeakMap<FeatureContext, CommentsModelFactory>()\n\n/** Registers the host model factory of `ctx` (the comments feature does this; hosts pass `model` to the feature). */\nexport function provideCommentsModel(ctx: FeatureContext, factory: CommentsModelFactory): void {\n modelFactories.set(ctx, factory)\n}\n\n/** The host model factory the comments feature of `ctx` was configured with; undefined for the default Y.Doc model. */\nexport function commentsModelOf(ctx: FeatureContext): CommentsModelFactory | undefined {\n return modelFactories.get(ctx)\n}\n\n/**\n * The comments model the framework hooks (`useComments`) use: the model of the surface `getEditor` belongs to\n * (`commentsModelOf` of its feature context: the host's `model` factory) and `createCommentsModel` for every other\n * editor. It follows the editor over time: when the editor changes to one of a surface with another model, the inner\n * model is replaced (disposed, created, refreshed) and `state` carries on with the new one. The factory runs once per\n * consumer of this function, so a host model that must share state across them keeps it outside the factory.\n */\nexport function createEditorCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY)\n let inner: { model: CommentsModel, factory: CommentsModelFactory, stop: () => void } | undefined\n\n function release() {\n inner?.stop()\n inner?.model.dispose()\n inner = undefined\n }\n\n /** The inner model for the editor now; a replacement when its surface brings another factory. */\n function current(): CommentsModel {\n const entry = editorEntryOf(getEditor())\n const factory = (entry && commentsModelOf(entry.ctx)) ?? createCommentsModel\n if (inner?.factory !== factory) {\n release()\n const model = factory(getEditor)\n inner = { model, factory, stop: model.state.subscribe(snapshot => store.set(snapshot)) }\n store.set(model.state.get())\n }\n return inner!.model\n }\n\n return {\n state: store,\n refresh: () => current().refresh(),\n dispose: () => {\n release()\n store.set(EMPTY)\n },\n add: text => current().add(text),\n reply: (threadId, text) => current().reply(threadId, text),\n edit: (threadId, replyId, text) => current().edit(threadId, replyId, text),\n setResolved: (threadId, resolved) => current().setResolved(threadId, resolved),\n remove: threadId => current().remove(threadId),\n removeReply: (threadId, replyId) => current().removeReply(threadId, replyId),\n activate: (id, options) => current().activate(id, options),\n startDraft: () => current().startDraft(),\n cancelDraft: () => current().cancelDraft(),\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { commentsCanComment, commentsState, startCommentDraft } from "../comments-plugin.js";
|
|
1
2
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
2
3
|
import { Icon } from "../../../react/Icon.js";
|
|
3
4
|
import { Button } from "../../../react/ui/button.js";
|
|
4
|
-
import { commentsCanComment, commentsState, startCommentDraft } from "../comments-plugin.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { BubbleMenu } from "@tiptap/react/menus";
|
|
7
7
|
//#region src/features/comments/react/CommentBubble.tsx
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { COMMENT_LIMITS } from "../schema.js";
|
|
1
2
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
2
3
|
import { cn } from "../../../react/ui/utils.js";
|
|
3
4
|
import { Button } from "../../../react/ui/button.js";
|
|
4
5
|
import { Textarea } from "../../../react/ui/textarea.js";
|
|
5
|
-
import { COMMENT_LIMITS } from "../schema.js";
|
|
6
6
|
import { useState } from "react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/features/comments/react/CommentComposer.tsx
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { COMMENT_LIMITS } from "../schema.js";
|
|
1
2
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
2
3
|
import { cn } from "../../../react/ui/utils.js";
|
|
3
4
|
import { Icon } from "../../../react/Icon.js";
|
|
@@ -5,7 +6,6 @@ import { Button } from "../../../react/ui/button.js";
|
|
|
5
6
|
import { Textarea } from "../../../react/ui/textarea.js";
|
|
6
7
|
import { Badge } from "../../../react/ui/badge.js";
|
|
7
8
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../../../react/ui/dropdown-menu.js";
|
|
8
|
-
import { COMMENT_LIMITS } from "../schema.js";
|
|
9
9
|
import { CommentAvatar } from "./CommentAvatar.js";
|
|
10
10
|
import { useMemo, useRef, useState } from "react";
|
|
11
11
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -99,7 +99,7 @@ function CommentThreadView({ thread, user, active, orphan, comments, dense, clas
|
|
|
99
99
|
}
|
|
100
100
|
const time = (entry) => `${timeFormat.format(new Date(entry.createdAt))}${entry.editedAt ? ` ${t("comments.edited")}` : ""}`;
|
|
101
101
|
return /* @__PURE__ */ jsxs("div", {
|
|
102
|
-
className: cn("flex flex-col text-sm", dense ? "gap-1.5" : cn("gap-3 rounded-md border border-border bg-background p-3", active && "ring-2 ring-primary"), className),
|
|
102
|
+
className: cn("flex flex-col text-sm", dense ? "gap-1.5" : cn("gap-3 rounded-md border border-border bg-background p-3", active && "ring-2 ring-primary", orphan && "opacity-70"), className),
|
|
103
103
|
onClick,
|
|
104
104
|
children: [entries.map((entry) => {
|
|
105
105
|
const groups = menu(entry);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadView.js","names":[],"sources":["../../../../src/features/comments/react/CommentThreadView.tsx"],"sourcesContent":["import { type KeyboardEvent, useMemo, useRef, useState } from 'react'\nimport type { AuthorIdentity } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../react/ui/dropdown-menu'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { CommentsActions } from '../model'\nimport { COMMENT_LIMITS, type CommentAuthor, type CommentThread } from '../schema'\nimport { CommentAvatar } from './CommentAvatar'\n\n/** The root comment (`replyId` null) and the replies, rendered with the same row layout. */\ninterface Entry { replyId: string | null, author: CommentAuthor, text: string, createdAt: string, editedAt?: string }\n\n/** Mod-Enter runs `submit`, Esc `cancel` (when given). */\nfunction onKeydown(event: KeyboardEvent<HTMLTextAreaElement>, submit: () => void, cancel?: () => void) {\n if (event.nativeEvent.isComposing) return\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n submit()\n }\n else if (event.key === 'Escape' && cancel) {\n event.preventDefault()\n cancel()\n }\n}\n\n/**\n * One comment thread as a card: root comment (with the commented text, or „Textstelle gelöscht“ for an orphan),\n * replies, a reply field. Everyone may resolve/reopen; editing and deleting are offered on the user's own comments\n * only. Layout-independent: mutations go through the `comments` actions (`useComments()`); a click on the card calls\n * `onClick` (layouts activate and reveal the thread). `dense`: compact, frameless rendering for a popover (name and time\n * on one line, one-line quote, icon-only send button).\n */\nexport function CommentThreadView({ thread, user, active, orphan, comments, dense, className, onClick }: {\n thread: CommentThread\n user: AuthorIdentity | null\n active: boolean\n orphan: boolean\n comments: Pick<CommentsActions, 'edit' | 'reply' | 'remove' | 'removeReply' | 'setResolved'>\n dense?: boolean\n className?: string\n onClick?: () => void\n}) {\n const { t, locale } = useEditorI18n()\n const timeFormat = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short' }), [locale])\n const entries: Entry[] = [\n { replyId: null, author: thread.author, text: thread.text, createdAt: thread.createdAt, editedAt: thread.editedAt },\n ...thread.replies.map(r => ({ replyId: r.id, author: r.author, text: r.text, createdAt: r.createdAt, editedAt: r.editedAt })),\n ]\n\n /** The entry being edited and its new text. */\n const [editing, setEditing] = useState<{ replyId: string | null, text: string } | null>(null)\n const [replyText, setReplyText] = useState('')\n const editStarted = useRef(false)\n\n function saveEdit() {\n if (!editing?.text.trim()) return\n comments.edit(thread.id, editing.replyId, editing.text)\n setEditing(null)\n }\n\n function sendReply() {\n if (!replyText.trim()) return\n comments.reply(thread.id, replyText)\n setReplyText('')\n }\n\n function menu(entry: Entry) {\n const own = !!user && entry.author.id === user.id\n const resolved = thread.resolved\n const groups: { key: string, items: { key: string, label: string, icon: string, destructive?: boolean, onSelect: () => void }[] }[] = []\n if (own) {\n groups.push({\n key: 'own',\n items: [\n {\n key: 'edit',\n label: t('comments.edit'),\n icon: 'i-lucide-pencil',\n onSelect: () => {\n editStarted.current = true\n setEditing({ replyId: entry.replyId, text: entry.text })\n },\n },\n {\n key: 'delete',\n label: t('comments.delete'),\n icon: 'i-lucide-trash-2',\n destructive: true,\n onSelect: () => entry.replyId ? comments.removeReply(thread.id, entry.replyId) : comments.remove(thread.id),\n },\n ],\n })\n }\n if (entry.replyId === null) {\n groups.push({\n key: 'resolve',\n items: [{\n key: 'resolve',\n label: resolved ? t('comments.reopen') : t('comments.resolved'),\n icon: resolved ? 'i-lucide-rotate-ccw' : 'i-lucide-check',\n onSelect: () => comments.setResolved(thread.id, !resolved),\n }],\n })\n }\n return groups\n }\n\n const time = (entry: Entry) => `${timeFormat.format(new Date(entry.createdAt))}${entry.editedAt ? ` ${t('comments.edited')}` : ''}`\n\n return (\n <div\n className={cn('flex flex-col text-sm', dense ? 'gap-1.5' : cn('gap-3 rounded-md border border-border bg-background p-3', active && 'ring-2 ring-primary'), className)}\n onClick={onClick}\n >\n {entries.map((entry) => {\n const groups = menu(entry)\n return (\n <div\n key={entry.replyId ?? 'root'}\n className={cn('flex flex-col', dense ? 'gap-0.5' : 'gap-1', entry.replyId && (dense ? 'ml-2 border-l border-border pl-2' : 'ml-4 border-l border-border pl-3'))}\n >\n <div className={cn('flex items-center', dense ? 'gap-1.5' : 'gap-2')}>\n <CommentAvatar name={entry.author.name} color={entry.author.color} size={dense ? '2xs' : 'xs'} />\n {dense\n ? (\n <div className=\"flex min-w-0 flex-1 items-baseline gap-1.5 text-xs\">\n <span className=\"truncate font-medium\">{entry.author.name}</span>\n <span className=\"shrink-0 text-muted-foreground\">{time(entry)}</span>\n </div>\n )\n : (\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate font-medium\">{entry.author.name}</div>\n <div className=\"text-xs text-muted-foreground\">{time(entry)}</div>\n </div>\n )}\n {groups.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button size=\"icon-xs\" variant=\"ghost\" aria-label={t('comments.actions')}>\n <Icon name=\"i-lucide-ellipsis-vertical\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"w-auto\"\n onCloseAutoFocus={(event) => {\n // „Bearbeiten“ focuses the edit field; the menu must not hand focus back to its trigger.\n if (!editStarted.current) return\n editStarted.current = false\n event.preventDefault()\n }}\n >\n {groups.map((group, index) => (\n <div key={group.key} className=\"contents\">\n {index > 0 && <DropdownMenuSeparator />}\n {group.items.map(item => (\n <DropdownMenuItem key={item.key} variant={item.destructive ? 'destructive' : 'default'} onSelect={item.onSelect}>\n <Icon name={item.icon} />\n {item.label}\n </DropdownMenuItem>\n ))}\n </div>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n\n {entry.replyId === null && (\n orphan\n ? <Badge variant=\"secondary\" className=\"self-start\">{t('comments.orphan')}</Badge>\n : thread.quote && (\n <p\n className={cn('border-l-2 border-(--sk-warning) pl-2 text-muted-foreground italic', dense ? 'truncate text-xs' : 'line-clamp-2')}\n title={dense ? thread.quote : undefined}\n >\n {thread.quote}\n </p>\n )\n )}\n\n {editing && editing.replyId === entry.replyId\n ? (\n <div className={cn('flex flex-col', dense ? 'gap-1.5' : 'gap-2')}>\n <Textarea\n value={editing.text}\n onChange={event => setEditing({ replyId: editing.replyId, text: event.target.value })}\n autoFocus\n rows={1}\n maxLength={COMMENT_LIMITS.text}\n aria-label={t('comments.editLabel')}\n className={cn('max-h-48 min-h-8 w-full resize-none overflow-y-auto', dense ? 'px-2 py-1 text-xs md:text-xs' : 'text-sm md:text-sm')}\n onKeyDown={event => onKeydown(event, saveEdit, () => setEditing(null))}\n />\n <div className={cn('flex justify-end', dense ? 'gap-1' : 'gap-2')}>\n <Button size=\"xs\" variant=\"ghost\" onClick={() => setEditing(null)}>{t('comments.cancel')}</Button>\n <Button size=\"xs\" disabled={!editing.text.trim()} onClick={saveEdit}>{t('comments.save')}</Button>\n </div>\n </div>\n )\n : <p className=\"break-words whitespace-pre-wrap\">{entry.text}</p>}\n </div>\n )\n })}\n\n {dense\n ? (\n <div className=\"flex items-end gap-1\">\n <Textarea\n value={replyText}\n onChange={event => setReplyText(event.target.value)}\n rows={1}\n maxLength={COMMENT_LIMITS.text}\n placeholder={t('comments.reply.placeholder')}\n aria-label={t('comments.reply.label')}\n className=\"max-h-24 min-h-7 min-w-0 flex-1 resize-none overflow-y-auto px-2 py-1 text-xs md:text-xs\"\n onKeyDown={event => onKeydown(event, sendReply)}\n />\n <Button size=\"icon-xs\" variant=\"secondary\" aria-label={t('comments.reply.submit')} disabled={!replyText.trim()} onClick={sendReply}>\n <Icon name=\"i-lucide-send\" />\n </Button>\n </div>\n )\n : (\n <div className=\"flex items-end gap-2\">\n <Textarea\n value={replyText}\n onChange={event => setReplyText(event.target.value)}\n rows={1}\n maxLength={COMMENT_LIMITS.text}\n placeholder={t('comments.reply.placeholder')}\n aria-label={t('comments.reply.label')}\n className=\"max-h-36 min-h-8 min-w-0 flex-1 resize-none overflow-y-auto text-sm md:text-sm\"\n onKeyDown={event => onKeydown(event, sendReply)}\n />\n <Button size=\"sm\" variant=\"secondary\" disabled={!replyText.trim()} onClick={sendReply}>{t('comments.reply.submit')}</Button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentThreadView.js","names":[],"sources":["../../../../src/features/comments/react/CommentThreadView.tsx"],"sourcesContent":["import { type KeyboardEvent, useMemo, useRef, useState } from 'react'\nimport type { AuthorIdentity } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../react/ui/dropdown-menu'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { CommentsActions } from '../model'\nimport { COMMENT_LIMITS, type CommentAuthor, type CommentThread } from '../schema'\nimport { CommentAvatar } from './CommentAvatar'\n\n/** The root comment (`replyId` null) and the replies, rendered with the same row layout. */\ninterface Entry { replyId: string | null, author: CommentAuthor, text: string, createdAt: string, editedAt?: string }\n\n/** Mod-Enter runs `submit`, Esc `cancel` (when given). */\nfunction onKeydown(event: KeyboardEvent<HTMLTextAreaElement>, submit: () => void, cancel?: () => void) {\n if (event.nativeEvent.isComposing) return\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n submit()\n }\n else if (event.key === 'Escape' && cancel) {\n event.preventDefault()\n cancel()\n }\n}\n\n/**\n * One comment thread as a card: root comment (with the commented text, or „Textstelle gelöscht“ for an orphan),\n * replies, a reply field. Everyone may resolve/reopen; editing and deleting are offered on the user's own comments\n * only. Layout-independent: mutations go through the `comments` actions (`useComments()`); a click on the card calls\n * `onClick` (layouts activate and reveal the thread). `dense`: compact, frameless rendering for a popover (name and time\n * on one line, one-line quote, icon-only send button).\n */\nexport function CommentThreadView({ thread, user, active, orphan, comments, dense, className, onClick }: {\n thread: CommentThread\n user: AuthorIdentity | null\n active: boolean\n orphan: boolean\n comments: Pick<CommentsActions, 'edit' | 'reply' | 'remove' | 'removeReply' | 'setResolved'>\n dense?: boolean\n className?: string\n onClick?: () => void\n}) {\n const { t, locale } = useEditorI18n()\n const timeFormat = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short' }), [locale])\n const entries: Entry[] = [\n { replyId: null, author: thread.author, text: thread.text, createdAt: thread.createdAt, editedAt: thread.editedAt },\n ...thread.replies.map(r => ({ replyId: r.id, author: r.author, text: r.text, createdAt: r.createdAt, editedAt: r.editedAt })),\n ]\n\n /** The entry being edited and its new text. */\n const [editing, setEditing] = useState<{ replyId: string | null, text: string } | null>(null)\n const [replyText, setReplyText] = useState('')\n const editStarted = useRef(false)\n\n function saveEdit() {\n if (!editing?.text.trim()) return\n comments.edit(thread.id, editing.replyId, editing.text)\n setEditing(null)\n }\n\n function sendReply() {\n if (!replyText.trim()) return\n comments.reply(thread.id, replyText)\n setReplyText('')\n }\n\n function menu(entry: Entry) {\n const own = !!user && entry.author.id === user.id\n const resolved = thread.resolved\n const groups: { key: string, items: { key: string, label: string, icon: string, destructive?: boolean, onSelect: () => void }[] }[] = []\n if (own) {\n groups.push({\n key: 'own',\n items: [\n {\n key: 'edit',\n label: t('comments.edit'),\n icon: 'i-lucide-pencil',\n onSelect: () => {\n editStarted.current = true\n setEditing({ replyId: entry.replyId, text: entry.text })\n },\n },\n {\n key: 'delete',\n label: t('comments.delete'),\n icon: 'i-lucide-trash-2',\n destructive: true,\n onSelect: () => entry.replyId ? comments.removeReply(thread.id, entry.replyId) : comments.remove(thread.id),\n },\n ],\n })\n }\n if (entry.replyId === null) {\n groups.push({\n key: 'resolve',\n items: [{\n key: 'resolve',\n label: resolved ? t('comments.reopen') : t('comments.resolved'),\n icon: resolved ? 'i-lucide-rotate-ccw' : 'i-lucide-check',\n onSelect: () => comments.setResolved(thread.id, !resolved),\n }],\n })\n }\n return groups\n }\n\n const time = (entry: Entry) => `${timeFormat.format(new Date(entry.createdAt))}${entry.editedAt ? ` ${t('comments.edited')}` : ''}`\n\n return (\n <div\n className={cn('flex flex-col text-sm', dense ? 'gap-1.5' : cn('gap-3 rounded-md border border-border bg-background p-3', active && 'ring-2 ring-primary', orphan && 'opacity-70'), className)}\n onClick={onClick}\n >\n {entries.map((entry) => {\n const groups = menu(entry)\n return (\n <div\n key={entry.replyId ?? 'root'}\n className={cn('flex flex-col', dense ? 'gap-0.5' : 'gap-1', entry.replyId && (dense ? 'ml-2 border-l border-border pl-2' : 'ml-4 border-l border-border pl-3'))}\n >\n <div className={cn('flex items-center', dense ? 'gap-1.5' : 'gap-2')}>\n <CommentAvatar name={entry.author.name} color={entry.author.color} size={dense ? '2xs' : 'xs'} />\n {dense\n ? (\n <div className=\"flex min-w-0 flex-1 items-baseline gap-1.5 text-xs\">\n <span className=\"truncate font-medium\">{entry.author.name}</span>\n <span className=\"shrink-0 text-muted-foreground\">{time(entry)}</span>\n </div>\n )\n : (\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate font-medium\">{entry.author.name}</div>\n <div className=\"text-xs text-muted-foreground\">{time(entry)}</div>\n </div>\n )}\n {groups.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button size=\"icon-xs\" variant=\"ghost\" aria-label={t('comments.actions')}>\n <Icon name=\"i-lucide-ellipsis-vertical\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"w-auto\"\n onCloseAutoFocus={(event) => {\n // „Bearbeiten“ focuses the edit field; the menu must not hand focus back to its trigger.\n if (!editStarted.current) return\n editStarted.current = false\n event.preventDefault()\n }}\n >\n {groups.map((group, index) => (\n <div key={group.key} className=\"contents\">\n {index > 0 && <DropdownMenuSeparator />}\n {group.items.map(item => (\n <DropdownMenuItem key={item.key} variant={item.destructive ? 'destructive' : 'default'} onSelect={item.onSelect}>\n <Icon name={item.icon} />\n {item.label}\n </DropdownMenuItem>\n ))}\n </div>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n\n {entry.replyId === null && (\n orphan\n ? <Badge variant=\"secondary\" className=\"self-start\">{t('comments.orphan')}</Badge>\n : thread.quote && (\n <p\n className={cn('border-l-2 border-(--sk-warning) pl-2 text-muted-foreground italic', dense ? 'truncate text-xs' : 'line-clamp-2')}\n title={dense ? thread.quote : undefined}\n >\n {thread.quote}\n </p>\n )\n )}\n\n {editing && editing.replyId === entry.replyId\n ? (\n <div className={cn('flex flex-col', dense ? 'gap-1.5' : 'gap-2')}>\n <Textarea\n value={editing.text}\n onChange={event => setEditing({ replyId: editing.replyId, text: event.target.value })}\n autoFocus\n rows={1}\n maxLength={COMMENT_LIMITS.text}\n aria-label={t('comments.editLabel')}\n className={cn('max-h-48 min-h-8 w-full resize-none overflow-y-auto', dense ? 'px-2 py-1 text-xs md:text-xs' : 'text-sm md:text-sm')}\n onKeyDown={event => onKeydown(event, saveEdit, () => setEditing(null))}\n />\n <div className={cn('flex justify-end', dense ? 'gap-1' : 'gap-2')}>\n <Button size=\"xs\" variant=\"ghost\" onClick={() => setEditing(null)}>{t('comments.cancel')}</Button>\n <Button size=\"xs\" disabled={!editing.text.trim()} onClick={saveEdit}>{t('comments.save')}</Button>\n </div>\n </div>\n )\n : <p className=\"break-words whitespace-pre-wrap\">{entry.text}</p>}\n </div>\n )\n })}\n\n {dense\n ? (\n <div className=\"flex items-end gap-1\">\n <Textarea\n value={replyText}\n onChange={event => setReplyText(event.target.value)}\n rows={1}\n maxLength={COMMENT_LIMITS.text}\n placeholder={t('comments.reply.placeholder')}\n aria-label={t('comments.reply.label')}\n className=\"max-h-24 min-h-7 min-w-0 flex-1 resize-none overflow-y-auto px-2 py-1 text-xs md:text-xs\"\n onKeyDown={event => onKeydown(event, sendReply)}\n />\n <Button size=\"icon-xs\" variant=\"secondary\" aria-label={t('comments.reply.submit')} disabled={!replyText.trim()} onClick={sendReply}>\n <Icon name=\"i-lucide-send\" />\n </Button>\n </div>\n )\n : (\n <div className=\"flex items-end gap-2\">\n <Textarea\n value={replyText}\n onChange={event => setReplyText(event.target.value)}\n rows={1}\n maxLength={COMMENT_LIMITS.text}\n placeholder={t('comments.reply.placeholder')}\n aria-label={t('comments.reply.label')}\n className=\"max-h-36 min-h-8 min-w-0 flex-1 resize-none overflow-y-auto text-sm md:text-sm\"\n onKeyDown={event => onKeydown(event, sendReply)}\n />\n <Button size=\"sm\" variant=\"secondary\" disabled={!replyText.trim()} onClick={sendReply}>{t('comments.reply.submit')}</Button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { Editor } from "@tiptap/core";
|
|
2
2
|
//#region src/features/comments/react/CommentsMargin.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Comments margin („Randspalte“): a column the host places inside its scroll container right of the editor, so it
|
|
5
|
-
* scrolls with the text. Cards sit at the height of their text, the active thread is expanded; shown only while `open`
|
|
6
|
-
* and the user's comments layout is `margin`.
|
|
7
|
-
*/
|
|
8
3
|
export declare function CommentsMargin({ editor, container, open }: {
|
|
9
4
|
editor: Editor | undefined;
|
|
10
5
|
/** The editor's scroll container, which the column sits in: its size changes re-run the layout. */
|