@ecsplico/editor 2.0.0 → 2.1.1
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 +37 -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 +9 -9
- 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 +6 -6
- 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 +6 -6
- 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,6 +1,8 @@
|
|
|
1
1
|
import { COMMENTS_LAYOUTS } from "../../settings/values.js";
|
|
2
|
-
import { Comments, cancelCommentDraft, commentsAvailable, commentsCanComment, commentsDoc, commentsKey, commentsState, commentsUser, setActiveComment, startCommentDraft } from "./comments-plugin.js";
|
|
3
2
|
import { COMMENTS_FIELD, COMMENT_LIMITS, CommentMark, commentsSchemaExtensions } from "./schema.js";
|
|
3
|
+
import { Comments, cancelCommentDraft, commentsAvailable, commentsCanComment, commentsDoc, commentsKey, commentsState, commentsUser, setActiveComment, startCommentDraft } from "./comments-plugin.js";
|
|
4
4
|
import { commentsModelOf, createCommentsModel, createEditorCommentsModel } from "./model.js";
|
|
5
5
|
import { commentsFeatureCore } from "./feature.js";
|
|
6
|
-
|
|
6
|
+
import { DRAFT_CARD, MARGIN_GAP, layoutMargin, marginCards } from "./margin-layout.js";
|
|
7
|
+
import { createMarginController } from "./margin-controller.js";
|
|
8
|
+
export { COMMENTS_FIELD, COMMENTS_LAYOUTS, COMMENT_LIMITS, CommentMark, Comments, DRAFT_CARD, MARGIN_GAP, cancelCommentDraft, commentsAvailable, commentsCanComment, commentsDoc, commentsFeatureCore, commentsKey, commentsModelOf, commentsSchemaExtensions, commentsState, commentsUser, createCommentsModel, createEditorCommentsModel, createMarginController, layoutMargin, marginCards, setActiveComment, startCommentDraft };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ReadonlyStore } from "../../core/store.js";
|
|
2
|
+
import { MarginCard } from "./margin-layout.js";
|
|
3
|
+
import { Editor } from "@tiptap/core";
|
|
4
|
+
//#region src/features/comments/margin-controller.d.ts
|
|
5
|
+
/** What the margin renders from the measurements. */
|
|
6
|
+
export interface MarginState {
|
|
7
|
+
/** Top of every card by id, relative to the column; empty until the first measurement (cards stay invisible till then). */
|
|
8
|
+
tops: ReadonlyMap<string, number>;
|
|
9
|
+
/** Top of the section below the cards (the resolved threads) and the column's minimum height. */
|
|
10
|
+
resolvedTop: number;
|
|
11
|
+
minHeight: number;
|
|
12
|
+
}
|
|
13
|
+
/** What the controller lays out, read afresh at every measurement. */
|
|
14
|
+
export interface MarginSource {
|
|
15
|
+
/** The cards in document order. */
|
|
16
|
+
cards: () => readonly MarginCard[];
|
|
17
|
+
/** The card that owns its line (the draft composer or the active thread), if any. */
|
|
18
|
+
activeId: () => string | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The measuring and scheduling glue of the comments margin, for both frameworks: where each card's line is
|
|
22
|
+
* (`coordsAtPos`), how tall each card is, one `layoutMargin` per animation frame, re-run after transactions, window and
|
|
23
|
+
* element resizes. Created inert; `connect()` starts it (call it on mount) and `disconnect()` stops it, and both may
|
|
24
|
+
* follow each other again (StrictMode mounts twice). Elements are handed in with the `set*` functions (framework
|
|
25
|
+
* refs), the editor and container with `setEditor` / `setContainer`; none of them needs to exist before `connect()`.
|
|
26
|
+
*/
|
|
27
|
+
export interface MarginController {
|
|
28
|
+
readonly state: ReadonlyStore<MarginState>;
|
|
29
|
+
setEditor: (editor: Editor | undefined) => void;
|
|
30
|
+
/** The editor's scroll container, which the column sits in: its size changes re-run the layout. */
|
|
31
|
+
setContainer: (container: HTMLElement | null) => void;
|
|
32
|
+
/** The column (its size flips between zero and real when the host hides it) and the section of resolved threads; `null` or anything but an element clears. */
|
|
33
|
+
setColumn: (el: unknown) => void;
|
|
34
|
+
setResolvedSection: (el: unknown) => void;
|
|
35
|
+
/** A card's element by id (`null` is ignored: cards that are gone are dropped at the next measurement). */
|
|
36
|
+
setCard: (id: string, el: unknown) => void;
|
|
37
|
+
/** Measures again on the next animation frame; call when the cards or the active card changed. */
|
|
38
|
+
schedule: () => void;
|
|
39
|
+
connect: () => void;
|
|
40
|
+
disconnect: () => void;
|
|
41
|
+
}
|
|
42
|
+
export declare function createMarginController(source: MarginSource): MarginController;
|
|
43
|
+
//#endregion
|
|
44
|
+
//# sourceMappingURL=margin-controller.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createStore } from "../../core/store.js";
|
|
2
|
+
import { layoutMargin } from "./margin-layout.js";
|
|
3
|
+
//#region src/features/comments/margin-controller.ts
|
|
4
|
+
const INITIAL = {
|
|
5
|
+
tops: /* @__PURE__ */ new Map(),
|
|
6
|
+
resolvedTop: 0,
|
|
7
|
+
minHeight: 0
|
|
8
|
+
};
|
|
9
|
+
function sameState(a, b) {
|
|
10
|
+
if (a.resolvedTop !== b.resolvedTop || a.minHeight !== b.minHeight || a.tops.size !== b.tops.size) return false;
|
|
11
|
+
for (const [id, top] of a.tops) if (b.tops.get(id) !== top) return false;
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
const asElement = (el) => el instanceof HTMLElement ? el : null;
|
|
15
|
+
function createMarginController(source) {
|
|
16
|
+
const state = createStore(INITIAL);
|
|
17
|
+
const cards = /* @__PURE__ */ new Map();
|
|
18
|
+
let editor;
|
|
19
|
+
let container = null;
|
|
20
|
+
let column = null;
|
|
21
|
+
let resolvedSection = null;
|
|
22
|
+
let observer = null;
|
|
23
|
+
let connected = false;
|
|
24
|
+
let frame = 0;
|
|
25
|
+
function schedule() {
|
|
26
|
+
if (!connected || frame || typeof requestAnimationFrame === "undefined") return;
|
|
27
|
+
frame = requestAnimationFrame(measure);
|
|
28
|
+
}
|
|
29
|
+
/** Top of the line at document position `pos`, relative to the column; null where the position is not rendered (yet). */
|
|
30
|
+
function lineTop(instance, pos, base) {
|
|
31
|
+
try {
|
|
32
|
+
return instance.view.coordsAtPos(Math.max(0, Math.min(pos, instance.state.doc.content.size))).top - base;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function measure() {
|
|
38
|
+
frame = 0;
|
|
39
|
+
const instance = editor;
|
|
40
|
+
if (!instance || instance.isDestroyed || !column || !column.getClientRects().length) return;
|
|
41
|
+
const list = source.cards();
|
|
42
|
+
const live = new Set(list.map((card) => card.id));
|
|
43
|
+
for (const [id, el] of cards) {
|
|
44
|
+
if (live.has(id)) continue;
|
|
45
|
+
observer?.unobserve(el);
|
|
46
|
+
cards.delete(id);
|
|
47
|
+
}
|
|
48
|
+
const base = column.getBoundingClientRect().top;
|
|
49
|
+
const entries = list.map((card) => ({
|
|
50
|
+
id: card.id,
|
|
51
|
+
top: card.pos === null ? null : lineTop(instance, card.pos, base),
|
|
52
|
+
height: cards.get(card.id)?.offsetHeight ?? 0
|
|
53
|
+
}));
|
|
54
|
+
const { tops, bottom } = layoutMargin(entries, source.activeId());
|
|
55
|
+
const resolvedTop = entries.length ? bottom + 16 : 0;
|
|
56
|
+
const next = {
|
|
57
|
+
tops,
|
|
58
|
+
resolvedTop,
|
|
59
|
+
minHeight: resolvedTop + (resolvedSection?.offsetHeight ?? 0)
|
|
60
|
+
};
|
|
61
|
+
if (!sameState(state.get(), next)) state.set(next);
|
|
62
|
+
}
|
|
63
|
+
/** Replaces the element held in a slot, moving the resize observation along. */
|
|
64
|
+
function swap(previous, next) {
|
|
65
|
+
if (previous === next) return previous;
|
|
66
|
+
if (previous) observer?.unobserve(previous);
|
|
67
|
+
if (next) observer?.observe(next);
|
|
68
|
+
schedule();
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
const onTransaction = () => schedule();
|
|
72
|
+
function connect() {
|
|
73
|
+
if (connected) return;
|
|
74
|
+
connected = true;
|
|
75
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
76
|
+
observer = new ResizeObserver(schedule);
|
|
77
|
+
for (const el of [
|
|
78
|
+
column,
|
|
79
|
+
resolvedSection,
|
|
80
|
+
container,
|
|
81
|
+
...cards.values()
|
|
82
|
+
]) if (el) observer.observe(el);
|
|
83
|
+
}
|
|
84
|
+
editor?.on("transaction", onTransaction);
|
|
85
|
+
window.addEventListener("resize", schedule);
|
|
86
|
+
schedule();
|
|
87
|
+
}
|
|
88
|
+
function disconnect() {
|
|
89
|
+
if (!connected) return;
|
|
90
|
+
connected = false;
|
|
91
|
+
editor?.off("transaction", onTransaction);
|
|
92
|
+
window.removeEventListener("resize", schedule);
|
|
93
|
+
observer?.disconnect();
|
|
94
|
+
observer = null;
|
|
95
|
+
if (frame) cancelAnimationFrame(frame);
|
|
96
|
+
frame = 0;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
state,
|
|
100
|
+
setEditor(next) {
|
|
101
|
+
if (next === editor) return;
|
|
102
|
+
if (connected) editor?.off("transaction", onTransaction);
|
|
103
|
+
editor = next;
|
|
104
|
+
if (connected) next?.on("transaction", onTransaction);
|
|
105
|
+
schedule();
|
|
106
|
+
},
|
|
107
|
+
setContainer(next) {
|
|
108
|
+
container = swap(container, next);
|
|
109
|
+
},
|
|
110
|
+
setColumn(el) {
|
|
111
|
+
column = swap(column, asElement(el));
|
|
112
|
+
},
|
|
113
|
+
setResolvedSection(el) {
|
|
114
|
+
resolvedSection = swap(resolvedSection, asElement(el));
|
|
115
|
+
},
|
|
116
|
+
setCard(id, el) {
|
|
117
|
+
const next = asElement(el);
|
|
118
|
+
if (!next || cards.get(id) === next) return;
|
|
119
|
+
const previous = cards.get(id);
|
|
120
|
+
if (previous) observer?.unobserve(previous);
|
|
121
|
+
cards.set(id, next);
|
|
122
|
+
observer?.observe(next);
|
|
123
|
+
},
|
|
124
|
+
schedule,
|
|
125
|
+
connect,
|
|
126
|
+
disconnect
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { createMarginController };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=margin-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"margin-controller.js","names":[],"sources":["../../../src/features/comments/margin-controller.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport { layoutMargin, type MarginCard, type MarginEntry, MARGIN_GAP } from './margin-layout'\n\n/** What the margin renders from the measurements. */\nexport interface MarginState {\n /** Top of every card by id, relative to the column; empty until the first measurement (cards stay invisible till then). */\n tops: ReadonlyMap<string, number>\n /** Top of the section below the cards (the resolved threads) and the column's minimum height. */\n resolvedTop: number\n minHeight: number\n}\n\n/** What the controller lays out, read afresh at every measurement. */\nexport interface MarginSource {\n /** The cards in document order. */\n cards: () => readonly MarginCard[]\n /** The card that owns its line (the draft composer or the active thread), if any. */\n activeId: () => string | null\n}\n\n/**\n * The measuring and scheduling glue of the comments margin, for both frameworks: where each card's line is\n * (`coordsAtPos`), how tall each card is, one `layoutMargin` per animation frame, re-run after transactions, window and\n * element resizes. Created inert; `connect()` starts it (call it on mount) and `disconnect()` stops it, and both may\n * follow each other again (StrictMode mounts twice). Elements are handed in with the `set*` functions (framework\n * refs), the editor and container with `setEditor` / `setContainer`; none of them needs to exist before `connect()`.\n */\nexport interface MarginController {\n readonly state: ReadonlyStore<MarginState>\n setEditor: (editor: Editor | undefined) => void\n /** The editor's scroll container, which the column sits in: its size changes re-run the layout. */\n setContainer: (container: HTMLElement | null) => void\n /** The column (its size flips between zero and real when the host hides it) and the section of resolved threads; `null` or anything but an element clears. */\n setColumn: (el: unknown) => void\n setResolvedSection: (el: unknown) => void\n /** A card's element by id (`null` is ignored: cards that are gone are dropped at the next measurement). */\n setCard: (id: string, el: unknown) => void\n /** Measures again on the next animation frame; call when the cards or the active card changed. */\n schedule: () => void\n connect: () => void\n disconnect: () => void\n}\n\nconst INITIAL: MarginState = { tops: new Map(), resolvedTop: 0, minHeight: 0 }\n\nfunction sameState(a: MarginState, b: MarginState): boolean {\n if (a.resolvedTop !== b.resolvedTop || a.minHeight !== b.minHeight || a.tops.size !== b.tops.size) return false\n for (const [id, top] of a.tops) {\n if (b.tops.get(id) !== top) return false\n }\n return true\n}\n\nconst asElement = (el: unknown): HTMLElement | null => el instanceof HTMLElement ? el : null\n\nexport function createMarginController(source: MarginSource): MarginController {\n const state = createStore<MarginState>(INITIAL)\n const cards = new Map<string, HTMLElement>()\n let editor: Editor | undefined\n let container: HTMLElement | null = null\n let column: HTMLElement | null = null\n let resolvedSection: HTMLElement | null = null\n let observer: ResizeObserver | null = null\n let connected = false\n let frame = 0\n\n function schedule() {\n if (!connected || frame || typeof requestAnimationFrame === 'undefined') return\n frame = requestAnimationFrame(measure)\n }\n\n /** Top of the line at document position `pos`, relative to the column; null where the position is not rendered (yet). */\n function lineTop(instance: Editor, pos: number, base: number): number | null {\n try {\n return instance.view.coordsAtPos(Math.max(0, Math.min(pos, instance.state.doc.content.size))).top - base\n }\n catch {\n return null\n }\n }\n\n function measure() {\n frame = 0\n const instance = editor\n // Hidden columns have no layout box; the column's own ResizeObserver re-runs this once shown.\n if (!instance || instance.isDestroyed || !column || !column.getClientRects().length) return\n const list = source.cards()\n const live = new Set(list.map(card => card.id))\n for (const [id, el] of cards) {\n if (live.has(id)) continue\n observer?.unobserve(el)\n cards.delete(id)\n }\n const base = column.getBoundingClientRect().top\n const entries: MarginEntry[] = list.map(card => ({\n id: card.id,\n top: card.pos === null ? null : lineTop(instance, card.pos, base),\n height: cards.get(card.id)?.offsetHeight ?? 0,\n }))\n const { tops, bottom } = layoutMargin(entries, source.activeId())\n const resolvedTop = entries.length ? bottom + 2 * MARGIN_GAP : 0\n const next: MarginState = { tops, resolvedTop, minHeight: resolvedTop + (resolvedSection?.offsetHeight ?? 0) }\n if (!sameState(state.get(), next)) state.set(next)\n }\n\n /** Replaces the element held in a slot, moving the resize observation along. */\n function swap(previous: HTMLElement | null, next: HTMLElement | null): HTMLElement | null {\n if (previous === next) return previous\n if (previous) observer?.unobserve(previous)\n if (next) observer?.observe(next)\n schedule()\n return next\n }\n\n const onTransaction = () => schedule()\n\n function connect() {\n if (connected) return\n connected = true\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(schedule)\n for (const el of [column, resolvedSection, container, ...cards.values()]) {\n if (el) observer.observe(el)\n }\n }\n editor?.on('transaction', onTransaction)\n window.addEventListener('resize', schedule)\n schedule()\n }\n\n function disconnect() {\n if (!connected) return\n connected = false\n editor?.off('transaction', onTransaction)\n window.removeEventListener('resize', schedule)\n observer?.disconnect()\n observer = null\n if (frame) cancelAnimationFrame(frame)\n frame = 0\n }\n\n return {\n state,\n setEditor(next) {\n if (next === editor) return\n if (connected) editor?.off('transaction', onTransaction)\n editor = next\n if (connected) next?.on('transaction', onTransaction)\n schedule()\n },\n setContainer(next) {\n container = swap(container, next)\n },\n setColumn(el) {\n column = swap(column, asElement(el))\n },\n setResolvedSection(el) {\n resolvedSection = swap(resolvedSection, asElement(el))\n },\n setCard(id, el) {\n const next = asElement(el)\n if (!next || cards.get(id) === next) return\n const previous = cards.get(id)\n if (previous) observer?.unobserve(previous)\n cards.set(id, next)\n observer?.observe(next)\n },\n schedule,\n connect,\n disconnect,\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CommentThread } from "./schema.js";
|
|
2
|
+
import { DocRange } from "./model.js";
|
|
3
|
+
//#region src/features/comments/margin-layout.d.ts
|
|
4
|
+
/** Vertical gap between two cards of the comments margin, in px. */
|
|
5
|
+
export declare const MARGIN_GAP = 8;
|
|
6
|
+
/** The id of the draft composer's card (a thread id never looks like this). */
|
|
7
|
+
export declare const DRAFT_CARD = "__draft";
|
|
8
|
+
/** One card of the comments margin as the layout rule sees it. */
|
|
9
|
+
export interface MarginEntry {
|
|
10
|
+
id: string;
|
|
11
|
+
/** Top of the line the card belongs to, relative to the column; `null`: the card has no place and follows the others. */
|
|
12
|
+
top: number | null;
|
|
13
|
+
/** Measured height of the card. */
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
export interface MarginLayout {
|
|
17
|
+
/** Top of every entry, relative to the column. */
|
|
18
|
+
tops: ReadonlyMap<string, number>;
|
|
19
|
+
/** Bottom edge of the lowest card (`minTop` without entries). */
|
|
20
|
+
bottom: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The layout rule of the comments margin („Aktiver Faden“). `entries` come in document order. The active card sits
|
|
24
|
+
* exactly at the top of its line; the cards above it are pushed up, the ones below pushed down, each as close to its own
|
|
25
|
+
* line as its neighbour allows, and none overlap (`gap` apart). When pushing up would leave the column (above `minTop`,
|
|
26
|
+
* the column's top offset), the whole stack shifts down just enough. Without an active card (`activeId` null or not among
|
|
27
|
+
* the entries) the cards stack top-down: each at its line, or right below the previous card when that is taken. Entries
|
|
28
|
+
* without a line (`top: null`) come last, stacked below the others, and are never pinned.
|
|
29
|
+
*/
|
|
30
|
+
export declare function layoutMargin(entries: readonly MarginEntry[], activeId: string | null, { gap, minTop }?: {
|
|
31
|
+
gap?: number;
|
|
32
|
+
minTop?: number;
|
|
33
|
+
}): MarginLayout;
|
|
34
|
+
/** A card of the comments margin: the draft composer, or a thread with the document position its line sits at. */
|
|
35
|
+
export interface MarginCard {
|
|
36
|
+
id: string;
|
|
37
|
+
/** Document position of the card's line: the text of an attached thread (its first character), the draft's start, or where an orphan's text was. `null`: unknown, the card goes to the end of the margin. */
|
|
38
|
+
pos: number | null;
|
|
39
|
+
/** The thread; `null` for the draft composer. */
|
|
40
|
+
thread: CommentThread | null;
|
|
41
|
+
/** The thread's text was deleted. */
|
|
42
|
+
orphan: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The cards of the margin in document order: the draft composer, then the open threads, each at its text. An orphaned
|
|
46
|
+
* thread sits at its deletion point (`orphanAt`), one without a known deletion point after all the others.
|
|
47
|
+
*/
|
|
48
|
+
export declare function marginCards(source: {
|
|
49
|
+
draft: DocRange | null;
|
|
50
|
+
open: readonly CommentThread[];
|
|
51
|
+
ranges: ReadonlyMap<string, DocRange>;
|
|
52
|
+
orphanAt: ReadonlyMap<string, number>;
|
|
53
|
+
}): MarginCard[];
|
|
54
|
+
//#endregion
|
|
55
|
+
//# sourceMappingURL=margin-layout.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region src/features/comments/margin-layout.ts
|
|
2
|
+
/** Vertical gap between two cards of the comments margin, in px. */
|
|
3
|
+
const MARGIN_GAP = 8;
|
|
4
|
+
/** The id of the draft composer's card (a thread id never looks like this). */
|
|
5
|
+
const DRAFT_CARD = "__draft";
|
|
6
|
+
/**
|
|
7
|
+
* The layout rule of the comments margin („Aktiver Faden“). `entries` come in document order. The active card sits
|
|
8
|
+
* exactly at the top of its line; the cards above it are pushed up, the ones below pushed down, each as close to its own
|
|
9
|
+
* line as its neighbour allows, and none overlap (`gap` apart). When pushing up would leave the column (above `minTop`,
|
|
10
|
+
* the column's top offset), the whole stack shifts down just enough. Without an active card (`activeId` null or not among
|
|
11
|
+
* the entries) the cards stack top-down: each at its line, or right below the previous card when that is taken. Entries
|
|
12
|
+
* without a line (`top: null`) come last, stacked below the others, and are never pinned.
|
|
13
|
+
*/
|
|
14
|
+
function layoutMargin(entries, activeId, { gap = 8, minTop = 0 } = {}) {
|
|
15
|
+
const placed = entries.flatMap((e) => e.top === null ? [] : [{
|
|
16
|
+
id: e.id,
|
|
17
|
+
want: e.top,
|
|
18
|
+
height: e.height
|
|
19
|
+
}]);
|
|
20
|
+
const floating = entries.filter((e) => e.top === null);
|
|
21
|
+
const pinned = activeId === null ? -1 : placed.findIndex((e) => e.id === activeId);
|
|
22
|
+
let tops = [];
|
|
23
|
+
if (pinned < 0) {
|
|
24
|
+
let bottom = minTop - gap;
|
|
25
|
+
for (const entry of placed) {
|
|
26
|
+
const top = Math.max(entry.want, bottom + gap);
|
|
27
|
+
tops.push(top);
|
|
28
|
+
bottom = top + entry.height;
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
tops[pinned] = placed[pinned].want;
|
|
32
|
+
for (let i = pinned - 1; i >= 0; i--) tops[i] = Math.min(placed[i].want, tops[i + 1] - gap - placed[i].height);
|
|
33
|
+
for (let i = pinned + 1; i < placed.length; i++) tops[i] = Math.max(placed[i].want, tops[i - 1] + placed[i - 1].height + gap);
|
|
34
|
+
const overflow = minTop - Math.min(...tops);
|
|
35
|
+
if (overflow > 0) tops = tops.map((top) => top + overflow);
|
|
36
|
+
}
|
|
37
|
+
const result = /* @__PURE__ */ new Map();
|
|
38
|
+
let bottom = minTop - gap;
|
|
39
|
+
placed.forEach((entry, i) => {
|
|
40
|
+
result.set(entry.id, tops[i]);
|
|
41
|
+
bottom = Math.max(bottom, tops[i] + entry.height);
|
|
42
|
+
});
|
|
43
|
+
for (const entry of floating) {
|
|
44
|
+
const top = bottom + gap;
|
|
45
|
+
result.set(entry.id, top);
|
|
46
|
+
bottom = top + entry.height;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
tops: result,
|
|
50
|
+
bottom: Math.max(bottom, minTop)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The cards of the margin in document order: the draft composer, then the open threads, each at its text. An orphaned
|
|
55
|
+
* thread sits at its deletion point (`orphanAt`), one without a known deletion point after all the others.
|
|
56
|
+
*/
|
|
57
|
+
function marginCards(source) {
|
|
58
|
+
const cards = [];
|
|
59
|
+
if (source.draft) cards.push({
|
|
60
|
+
id: DRAFT_CARD,
|
|
61
|
+
pos: source.draft.from,
|
|
62
|
+
thread: null,
|
|
63
|
+
orphan: false
|
|
64
|
+
});
|
|
65
|
+
for (const thread of source.open) {
|
|
66
|
+
const range = source.ranges.get(thread.id);
|
|
67
|
+
cards.push({
|
|
68
|
+
id: thread.id,
|
|
69
|
+
pos: range ? range.from : source.orphanAt.get(thread.id) ?? null,
|
|
70
|
+
thread,
|
|
71
|
+
orphan: !range
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return cards.sort((a, b) => a.pos === b.pos ? 0 : a.pos === null ? 1 : b.pos === null ? -1 : a.pos - b.pos);
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { DRAFT_CARD, MARGIN_GAP, layoutMargin, marginCards };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=margin-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"margin-layout.js","names":[],"sources":["../../../src/features/comments/margin-layout.ts"],"sourcesContent":["import type { DocRange } from './model'\nimport type { CommentThread } from './schema'\n\n/** Vertical gap between two cards of the comments margin, in px. */\nexport const MARGIN_GAP = 8\n\n/** The id of the draft composer's card (a thread id never looks like this). */\nexport const DRAFT_CARD = '__draft'\n\n/** One card of the comments margin as the layout rule sees it. */\nexport interface MarginEntry {\n id: string\n /** Top of the line the card belongs to, relative to the column; `null`: the card has no place and follows the others. */\n top: number | null\n /** Measured height of the card. */\n height: number\n}\n\nexport interface MarginLayout {\n /** Top of every entry, relative to the column. */\n tops: ReadonlyMap<string, number>\n /** Bottom edge of the lowest card (`minTop` without entries). */\n bottom: number\n}\n\n/**\n * The layout rule of the comments margin („Aktiver Faden“). `entries` come in document order. The active card sits\n * exactly at the top of its line; the cards above it are pushed up, the ones below pushed down, each as close to its own\n * line as its neighbour allows, and none overlap (`gap` apart). When pushing up would leave the column (above `minTop`,\n * the column's top offset), the whole stack shifts down just enough. Without an active card (`activeId` null or not among\n * the entries) the cards stack top-down: each at its line, or right below the previous card when that is taken. Entries\n * without a line (`top: null`) come last, stacked below the others, and are never pinned.\n */\nexport function layoutMargin(\n entries: readonly MarginEntry[],\n activeId: string | null,\n { gap = MARGIN_GAP, minTop = 0 }: { gap?: number, minTop?: number } = {},\n): MarginLayout {\n const placed = entries.flatMap(e => e.top === null ? [] : [{ id: e.id, want: e.top, height: e.height }])\n const floating = entries.filter(e => e.top === null)\n const pinned = activeId === null ? -1 : placed.findIndex(e => e.id === activeId)\n\n let tops: number[] = []\n if (pinned < 0) {\n let bottom = minTop - gap\n for (const entry of placed) {\n const top = Math.max(entry.want, bottom + gap)\n tops.push(top)\n bottom = top + entry.height\n }\n }\n else {\n tops[pinned] = placed[pinned]!.want\n for (let i = pinned - 1; i >= 0; i--) tops[i] = Math.min(placed[i]!.want, tops[i + 1]! - gap - placed[i]!.height)\n for (let i = pinned + 1; i < placed.length; i++) tops[i] = Math.max(placed[i]!.want, tops[i - 1]! + placed[i - 1]!.height + gap)\n const overflow = minTop - Math.min(...tops)\n if (overflow > 0) tops = tops.map(top => top + overflow)\n }\n\n const result = new Map<string, number>()\n let bottom = minTop - gap\n placed.forEach((entry, i) => {\n result.set(entry.id, tops[i]!)\n bottom = Math.max(bottom, tops[i]! + entry.height)\n })\n for (const entry of floating) {\n const top = bottom + gap\n result.set(entry.id, top)\n bottom = top + entry.height\n }\n return { tops: result, bottom: Math.max(bottom, minTop) }\n}\n\n/** A card of the comments margin: the draft composer, or a thread with the document position its line sits at. */\nexport interface MarginCard {\n id: string\n /** Document position of the card's line: the text of an attached thread (its first character), the draft's start, or where an orphan's text was. `null`: unknown, the card goes to the end of the margin. */\n pos: number | null\n /** The thread; `null` for the draft composer. */\n thread: CommentThread | null\n /** The thread's text was deleted. */\n orphan: boolean\n}\n\n/**\n * The cards of the margin in document order: the draft composer, then the open threads, each at its text. An orphaned\n * thread sits at its deletion point (`orphanAt`), one without a known deletion point after all the others.\n */\nexport function marginCards(source: {\n draft: DocRange | null\n open: readonly CommentThread[]\n ranges: ReadonlyMap<string, DocRange>\n orphanAt: ReadonlyMap<string, number>\n}): MarginCard[] {\n const cards: MarginCard[] = []\n if (source.draft) cards.push({ id: DRAFT_CARD, pos: source.draft.from, thread: null, orphan: false })\n for (const thread of source.open) {\n const range = source.ranges.get(thread.id)\n cards.push({ id: thread.id, pos: range ? range.from : source.orphanAt.get(thread.id) ?? null, thread, orphan: !range })\n }\n // Stable: cards at the same place keep their order (draft first, then the threads' order).\n return cards.sort((a, b) => a.pos === b.pos ? 0 : a.pos === null ? 1 : b.pos === null ? -1 : a.pos - b.pos)\n}\n"],"mappings":";;AAIA,MAAa,aAAa;;AAG1B,MAAa,aAAa;;;;;;;;;AA0B1B,SAAgB,aACd,SACA,UACA,EAAE,MAAA,GAAkB,SAAS,MAAyC,CAAC,GACzD;CACd,MAAM,SAAS,QAAQ,SAAQ,MAAK,EAAE,QAAQ,OAAO,CAAC,IAAI,CAAC;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;EAAK,QAAQ,EAAE;CAAO,CAAC,CAAC;CACvG,MAAM,WAAW,QAAQ,QAAO,MAAK,EAAE,QAAQ,IAAI;CACnD,MAAM,SAAS,aAAa,OAAO,KAAK,OAAO,WAAU,MAAK,EAAE,OAAO,QAAQ;CAE/E,IAAI,OAAiB,CAAC;CACtB,IAAI,SAAS,GAAG;EACd,IAAI,SAAS,SAAS;EACtB,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,MAAM,KAAK,IAAI,MAAM,MAAM,SAAS,GAAG;GAC7C,KAAK,KAAK,GAAG;GACb,SAAS,MAAM,MAAM;EACvB;CACF,OACK;EACH,KAAK,UAAU,OAAO,OAAO,CAAE;EAC/B,KAAK,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK,KAAK,KAAK,KAAK,IAAI,OAAO,EAAE,CAAE,MAAM,KAAK,IAAI,KAAM,MAAM,OAAO,EAAE,CAAE,MAAM;EAChH,KAAK,IAAI,IAAI,SAAS,GAAG,IAAI,OAAO,QAAQ,KAAK,KAAK,KAAK,KAAK,IAAI,OAAO,EAAE,CAAE,MAAM,KAAK,IAAI,KAAM,OAAO,IAAI,EAAE,CAAE,SAAS,GAAG;EAC/H,MAAM,WAAW,SAAS,KAAK,IAAI,GAAG,IAAI;EAC1C,IAAI,WAAW,GAAG,OAAO,KAAK,KAAI,QAAO,MAAM,QAAQ;CACzD;CAEA,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,SAAS,SAAS;CACtB,OAAO,SAAS,OAAO,MAAM;EAC3B,OAAO,IAAI,MAAM,IAAI,KAAK,EAAG;EAC7B,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAM,MAAM,MAAM;CACnD,CAAC;CACD,KAAK,MAAM,SAAS,UAAU;EAC5B,MAAM,MAAM,SAAS;EACrB,OAAO,IAAI,MAAM,IAAI,GAAG;EACxB,SAAS,MAAM,MAAM;CACvB;CACA,OAAO;EAAE,MAAM;EAAQ,QAAQ,KAAK,IAAI,QAAQ,MAAM;CAAE;AAC1D;;;;;AAiBA,SAAgB,YAAY,QAKX;CACf,MAAM,QAAsB,CAAC;CAC7B,IAAI,OAAO,OAAO,MAAM,KAAK;EAAE,IAAI;EAAY,KAAK,OAAO,MAAM;EAAM,QAAQ;EAAM,QAAQ;CAAM,CAAC;CACpG,KAAK,MAAM,UAAU,OAAO,MAAM;EAChC,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO,EAAE;EACzC,MAAM,KAAK;GAAE,IAAI,OAAO;GAAI,KAAK,QAAQ,MAAM,OAAO,OAAO,SAAS,IAAI,OAAO,EAAE,KAAK;GAAM;GAAQ,QAAQ,CAAC;EAAM,CAAC;CACxH;CAEA,OAAO,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI,EAAE,QAAQ,OAAO,KAAK,EAAE,MAAM,EAAE,GAAG;AAC5G"}
|
|
@@ -22,6 +22,12 @@ export interface CommentsSnapshot {
|
|
|
22
22
|
threads: readonly CommentThread[];
|
|
23
23
|
/** Current text range per thread id (threads without one are orphans). */
|
|
24
24
|
ranges: ReadonlyMap<string, DocRange>;
|
|
25
|
+
/**
|
|
26
|
+
* Where the text of an orphaned open thread was: the document position of the deletion point, resolved from the
|
|
27
|
+
* thread's stored anchor (so it is the same place for every collaborator and follows edits). Absent for orphans
|
|
28
|
+
* without a usable anchor (threads from before anchors, or whose block is gone for good).
|
|
29
|
+
*/
|
|
30
|
+
orphanAt: ReadonlyMap<string, number>;
|
|
25
31
|
active: string | null;
|
|
26
32
|
/** Current range of the draft a new comment is being written for. */
|
|
27
33
|
draft: DocRange | null;
|
|
@@ -69,6 +75,8 @@ export interface CommentsModel extends CommentsActions {
|
|
|
69
75
|
* active thread and draft, and the mutations. Mutations are no-ops without collaboration or user; editing and
|
|
70
76
|
* deleting are limited to the user's own comments and replies (a client-side rule: the Y.Doc itself has no ACL).
|
|
71
77
|
* `getEditor` names the editor to work on (it may change over time and may return a destroyed or no editor).
|
|
78
|
+
* Each thread also keeps `anchor` (a Yjs relative position of its text's start, see `CommentAnchor`): `orphanAt` resolves
|
|
79
|
+
* it for the threads whose text is gone. Threads from before anchors get theirs written once they are seen with a range.
|
|
72
80
|
*/
|
|
73
81
|
export declare function createCommentsModel(getEditor: () => Editor | undefined): CommentsModel;
|
|
74
82
|
/** Creates the comments model of one editor, as a host supplies it to `commentsFeatureCore`. */
|
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import { newBlockId } from "../../core/schema/block-ids.js";
|
|
2
2
|
import { createStore } from "../../core/store.js";
|
|
3
|
-
import { releaseRanges, resolveRange } from "../../core/doc-anchors.js";
|
|
3
|
+
import { anchorResolverOf, releaseRanges, resolveRange } from "../../core/doc-anchors.js";
|
|
4
4
|
import { editorEntryOf } from "../../core/editor-ui.js";
|
|
5
|
-
import { cancelCommentDraft, commentsDoc, commentsKey, commentsUser, setActiveComment, startCommentDraft } from "./comments-plugin.js";
|
|
6
5
|
import { COMMENTS_FIELD, COMMENT_LIMITS } from "./schema.js";
|
|
6
|
+
import { cancelCommentDraft, commentsDoc, commentsKey, commentsUser, setActiveComment, startCommentDraft } from "./comments-plugin.js";
|
|
7
|
+
import { EMPTY_SNAPSHOT, NO_POSITIONS, NO_RANGES, NO_THREADS, sameOrphanAt, sameRange, sameRanges, sameUser } from "./snapshot-equality.js";
|
|
8
|
+
import { captureThreadAnchor, resolveThreadAnchor } from "./thread-anchor.js";
|
|
7
9
|
import * as Y from "yjs";
|
|
8
10
|
//#region src/features/comments/model.ts
|
|
9
|
-
const NO_RANGES = /* @__PURE__ */ new Map();
|
|
10
|
-
const NO_THREADS = [];
|
|
11
|
-
const EMPTY = {
|
|
12
|
-
available: false,
|
|
13
|
-
canComment: false,
|
|
14
|
-
user: null,
|
|
15
|
-
threads: NO_THREADS,
|
|
16
|
-
ranges: NO_RANGES,
|
|
17
|
-
active: null,
|
|
18
|
-
draft: null,
|
|
19
|
-
draftQuote: "",
|
|
20
|
-
open: NO_THREADS,
|
|
21
|
-
resolved: NO_THREADS
|
|
22
|
-
};
|
|
23
11
|
/** A thread's `Y.Map` as plain data; `replies` converted explicitly so it is always a plain array. */
|
|
24
12
|
function snapshotOf(thread) {
|
|
25
13
|
const replies = thread.get("replies");
|
|
@@ -28,18 +16,6 @@ function snapshotOf(thread) {
|
|
|
28
16
|
replies: replies instanceof Y.Array ? replies.toArray() : []
|
|
29
17
|
};
|
|
30
18
|
}
|
|
31
|
-
function sameRange(a, b) {
|
|
32
|
-
return a === b || !!a && !!b && a.from === b.from && a.to === b.to;
|
|
33
|
-
}
|
|
34
|
-
function sameRanges(a, b) {
|
|
35
|
-
if (a === b) return true;
|
|
36
|
-
if (a.size !== b.size) return false;
|
|
37
|
-
for (const [id, range] of a) if (!sameRange(range, b.get(id) ?? null)) return false;
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
function sameUser(a, b) {
|
|
41
|
-
return a === b || !!a && !!b && a.id === b.id && a.name === b.name && a.color === b.color;
|
|
42
|
-
}
|
|
43
19
|
/**
|
|
44
20
|
* The comment threads of a collaborative editor, stored in its Y.Doc (`COMMENTS_FIELD`, one nested `Y.Map` per thread,
|
|
45
21
|
* replies in a nested `Y.Array`), so they sync live with the document and persist with its Yjs state. Keeps the
|
|
@@ -47,9 +23,11 @@ function sameUser(a, b) {
|
|
|
47
23
|
* active thread and draft, and the mutations. Mutations are no-ops without collaboration or user; editing and
|
|
48
24
|
* deleting are limited to the user's own comments and replies (a client-side rule: the Y.Doc itself has no ACL).
|
|
49
25
|
* `getEditor` names the editor to work on (it may change over time and may return a destroyed or no editor).
|
|
26
|
+
* Each thread also keeps `anchor` (a Yjs relative position of its text's start, see `CommentAnchor`): `orphanAt` resolves
|
|
27
|
+
* it for the threads whose text is gone. Threads from before anchors get theirs written once they are seen with a range.
|
|
50
28
|
*/
|
|
51
29
|
function createCommentsModel(getEditor) {
|
|
52
|
-
const store = createStore(
|
|
30
|
+
const store = createStore(EMPTY_SNAPSHOT);
|
|
53
31
|
const current = () => {
|
|
54
32
|
const instance = getEditor();
|
|
55
33
|
return instance && !instance.isDestroyed ? instance : void 0;
|
|
@@ -59,6 +37,8 @@ function createCommentsModel(getEditor) {
|
|
|
59
37
|
let threads = NO_THREADS;
|
|
60
38
|
let syncQueued = false;
|
|
61
39
|
let told = NO_THREADS;
|
|
40
|
+
const anchorTried = /* @__PURE__ */ new Set();
|
|
41
|
+
let healQueued = false;
|
|
62
42
|
function unobserve() {
|
|
63
43
|
observed?.map.unobserveDeep(onThreadsChanged);
|
|
64
44
|
observed = void 0;
|
|
@@ -81,7 +61,7 @@ function createCommentsModel(getEditor) {
|
|
|
81
61
|
function recompute() {
|
|
82
62
|
const instance = current();
|
|
83
63
|
if (!instance) {
|
|
84
|
-
store.set(
|
|
64
|
+
store.set(EMPTY_SNAPSHOT);
|
|
85
65
|
return;
|
|
86
66
|
}
|
|
87
67
|
const previous = store.get();
|
|
@@ -89,6 +69,7 @@ function createCommentsModel(getEditor) {
|
|
|
89
69
|
const user = commentsUser(instance);
|
|
90
70
|
const plugin = commentsKey.getState(instance.state);
|
|
91
71
|
const ranges = plugin?.ranges ?? NO_RANGES;
|
|
72
|
+
const orphanAt = orphanPositions(instance, ranges);
|
|
92
73
|
const active = plugin?.active ?? null;
|
|
93
74
|
const anchor = plugin?.draft;
|
|
94
75
|
const draft = anchor ? resolveRange(instance.state, anchor) : null;
|
|
@@ -97,7 +78,8 @@ function createCommentsModel(getEditor) {
|
|
|
97
78
|
const canComment = available && instance.isEditable;
|
|
98
79
|
const sameThreads = threads === previous.threads;
|
|
99
80
|
const sameRangeMap = sameRanges(ranges, previous.ranges);
|
|
100
|
-
|
|
81
|
+
const sameOrphanMap = sameOrphanAt(orphanAt, previous.orphanAt);
|
|
82
|
+
if (available === previous.available && canComment === previous.canComment && sameUser(user, previous.user) && sameThreads && sameRangeMap && sameOrphanMap && active === previous.active && sameRange(draft, previous.draft) && draftQuote === previous.draftQuote) return;
|
|
101
83
|
const open = sameThreads && sameRangeMap ? previous.open : threads.filter((t) => !t.resolved).sort((a, b) => (ranges.get(a.id)?.from ?? Infinity) - (ranges.get(b.id)?.from ?? Infinity) || a.createdAt.localeCompare(b.createdAt));
|
|
102
84
|
const resolved = sameThreads ? previous.resolved : threads.filter((t) => t.resolved).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
103
85
|
store.set({
|
|
@@ -106,12 +88,51 @@ function createCommentsModel(getEditor) {
|
|
|
106
88
|
user,
|
|
107
89
|
threads,
|
|
108
90
|
ranges: sameRangeMap ? previous.ranges : ranges,
|
|
91
|
+
orphanAt: sameOrphanMap ? previous.orphanAt : orphanAt,
|
|
109
92
|
active,
|
|
110
93
|
draft: sameRange(draft, previous.draft) ? previous.draft : draft,
|
|
111
94
|
draftQuote,
|
|
112
95
|
open,
|
|
113
96
|
resolved
|
|
114
97
|
});
|
|
98
|
+
if (available) queueHeal();
|
|
99
|
+
}
|
|
100
|
+
/** Where each unresolved thread without a text range was, from its anchor; empty without a Yjs session. */
|
|
101
|
+
function orphanPositions(instance, ranges) {
|
|
102
|
+
const resolver = anchorResolverOf(instance);
|
|
103
|
+
if (!resolver) return NO_POSITIONS;
|
|
104
|
+
const size = instance.state.doc.content.size;
|
|
105
|
+
let found;
|
|
106
|
+
for (const thread of threads) {
|
|
107
|
+
if (thread.resolved || ranges.has(thread.id) || !thread.anchor) continue;
|
|
108
|
+
const position = resolveThreadAnchor(instance.state, resolver, thread.anchor);
|
|
109
|
+
if (position !== null && position <= size) (found ??= /* @__PURE__ */ new Map()).set(thread.id, position);
|
|
110
|
+
}
|
|
111
|
+
return found ?? NO_POSITIONS;
|
|
112
|
+
}
|
|
113
|
+
function queueHeal() {
|
|
114
|
+
if (healQueued || !threads.some((t) => !t.anchor && !anchorTried.has(t.id))) return;
|
|
115
|
+
healQueued = true;
|
|
116
|
+
queueMicrotask(() => {
|
|
117
|
+
healQueued = false;
|
|
118
|
+
healAnchors();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function healAnchors() {
|
|
122
|
+
const ctx = writable();
|
|
123
|
+
const resolver = ctx && anchorResolverOf(ctx.editor);
|
|
124
|
+
if (!ctx || !resolver || ctx.editor !== bound) return;
|
|
125
|
+
const { ranges } = store.get();
|
|
126
|
+
const written = [];
|
|
127
|
+
for (const { id, anchor } of threads) {
|
|
128
|
+
const range = ranges.get(id);
|
|
129
|
+
if (anchor || anchorTried.has(id) || !range) continue;
|
|
130
|
+
anchorTried.add(id);
|
|
131
|
+
const thread = threadMap(ctx.map, id);
|
|
132
|
+
const captured = thread && captureThreadAnchor(ctx.editor.state, resolver, range);
|
|
133
|
+
if (thread && captured) written.push([thread, captured]);
|
|
134
|
+
}
|
|
135
|
+
if (written.length) ctx.ydoc.transact(() => written.forEach(([thread, captured]) => thread.set("anchor", captured)));
|
|
115
136
|
}
|
|
116
137
|
function queueSync() {
|
|
117
138
|
if (syncQueued) return;
|
|
@@ -146,6 +167,7 @@ function createCommentsModel(getEditor) {
|
|
|
146
167
|
bound = instance;
|
|
147
168
|
threads = NO_THREADS;
|
|
148
169
|
told = NO_THREADS;
|
|
170
|
+
anchorTried.clear();
|
|
149
171
|
instance?.on("transaction", onTransaction);
|
|
150
172
|
instance?.on("update", onTransaction);
|
|
151
173
|
queueSync();
|
|
@@ -171,7 +193,8 @@ function createCommentsModel(getEditor) {
|
|
|
171
193
|
unobserve();
|
|
172
194
|
threads = NO_THREADS;
|
|
173
195
|
told = NO_THREADS;
|
|
174
|
-
|
|
196
|
+
anchorTried.clear();
|
|
197
|
+
store.set(EMPTY_SNAPSHOT);
|
|
175
198
|
}
|
|
176
199
|
/** Editor, thread map and user while comments are available; null otherwise. */
|
|
177
200
|
function writable() {
|
|
@@ -209,6 +232,8 @@ function createCommentsModel(getEditor) {
|
|
|
209
232
|
if (!ctx || !anchor || !range || range.from >= range.to || !body || !type) return null;
|
|
210
233
|
const id = newBlockId();
|
|
211
234
|
const { state } = ctx.editor;
|
|
235
|
+
const resolver = anchorResolverOf(ctx.editor);
|
|
236
|
+
const threadAnchor = resolver ? captureThreadAnchor(state, resolver, range) : null;
|
|
212
237
|
const quote = state.doc.textBetween(range.from, range.to, " ").slice(0, COMMENT_LIMITS.quote);
|
|
213
238
|
const tr = state.tr.addMark(range.from, range.to, type.create({ id }));
|
|
214
239
|
if (!tr.docChanged) return null;
|
|
@@ -225,6 +250,7 @@ function createCommentsModel(getEditor) {
|
|
|
225
250
|
thread.set("createdAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
226
251
|
thread.set("quote", quote);
|
|
227
252
|
thread.set("resolved", false);
|
|
253
|
+
if (threadAnchor) thread.set("anchor", threadAnchor);
|
|
228
254
|
thread.set("replies", new Y.Array());
|
|
229
255
|
ctx.map.set(id, thread);
|
|
230
256
|
return id;
|
|
@@ -343,7 +369,7 @@ function commentsModelOf(ctx) {
|
|
|
343
369
|
* consumer of this function, so a host model that must share state across them keeps it outside the factory.
|
|
344
370
|
*/
|
|
345
371
|
function createEditorCommentsModel(getEditor) {
|
|
346
|
-
const store = createStore(
|
|
372
|
+
const store = createStore(EMPTY_SNAPSHOT);
|
|
347
373
|
let inner;
|
|
348
374
|
function release() {
|
|
349
375
|
inner?.stop();
|
|
@@ -371,7 +397,7 @@ function createEditorCommentsModel(getEditor) {
|
|
|
371
397
|
refresh: () => current().refresh(),
|
|
372
398
|
dispose: () => {
|
|
373
399
|
release();
|
|
374
|
-
store.set(
|
|
400
|
+
store.set(EMPTY_SNAPSHOT);
|
|
375
401
|
},
|
|
376
402
|
add: (text) => current().add(text),
|
|
377
403
|
reply: (threadId, text) => current().reply(threadId, text),
|