@kedataindo/docflow-core 0.0.71 → 0.0.73

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/dist/index.js CHANGED
@@ -10,10 +10,14 @@ import {
10
10
  trimContextAfter,
11
11
  trimContextBefore
12
12
  } from "./chunk-HMOWKFPE.js";
13
+ import {
14
+ collaborationExtensions,
15
+ createCollaboration
16
+ } from "./chunk-XJG72F7M.js";
13
17
 
14
18
  // src/Editor.ts
15
- import { Extension as Extension5, Editor as TiptapEditor } from "@tiptap/core";
16
- import { Plugin as Plugin4, PluginKey as PluginKey4 } from "@tiptap/pm/state";
19
+ import { Extension as Extension6, Editor as TiptapEditor } from "@tiptap/core";
20
+ import { Plugin as Plugin5, PluginKey as PluginKey5 } from "@tiptap/pm/state";
17
21
  import StarterKit from "@tiptap/starter-kit";
18
22
  import TextStyle from "@tiptap/extension-text-style";
19
23
 
@@ -64,143 +68,6 @@ function createActionMap(editor, plugins) {
64
68
  return map;
65
69
  }
66
70
 
67
- // src/Collaboration.ts
68
- import { Collaboration } from "@tiptap/extension-collaboration";
69
- import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
70
- import { Awareness } from "y-protocols/awareness";
71
- import { IndexeddbPersistence } from "y-indexeddb";
72
- import { WebrtcProvider } from "y-webrtc";
73
- import { WebsocketProvider } from "y-websocket";
74
- import * as Y from "yjs";
75
- var LOCAL_SIGNALING = ["ws://localhost:4444"];
76
- var warnedLocalSignaling = false;
77
- function resolveSignalingUrls(signaling) {
78
- if (signaling && signaling.length > 0) return signaling;
79
- if (!warnedLocalSignaling) {
80
- warnedLocalSignaling = true;
81
- console.warn(
82
- '[docflow] webrtc provider has no `signaling` configured \u2014 falling back to localhost-only sync (ws://localhost:4444 + same-browser tabs). For production self-host, run your own signaling server and pass `signaling`, or use `provider: "websocket"` with your own websocketUrl.'
83
- );
84
- }
85
- return LOCAL_SIGNALING;
86
- }
87
- var LOCAL_PRESENT_DEFAULT = true;
88
- function createAwarenessStates(awareness) {
89
- const states = [];
90
- awareness.getStates().forEach((state, clientId) => {
91
- const raw = state;
92
- states.push({
93
- clientId,
94
- user: raw.user ?? { name: "", color: "" },
95
- cursor: raw.cursor ?? null,
96
- // `present` defaults to true for backwards compat (legacy states
97
- // don't set it) and for the local user (always considered present
98
- // until PR2 toggles it off).
99
- present: raw.present ?? LOCAL_PRESENT_DEFAULT
100
- });
101
- });
102
- return states;
103
- }
104
- function createCollaboration(options) {
105
- const ydoc = new Y.Doc();
106
- if (options.initialStorageState) {
107
- Y.applyUpdate(ydoc, options.initialStorageState);
108
- }
109
- let persistence;
110
- if (options.offline) {
111
- persistence = new IndexeddbPersistence(`docflow-${options.room}`, ydoc);
112
- }
113
- let provider = null;
114
- if (options.provider === "webrtc") {
115
- provider = new WebrtcProvider(options.room, ydoc, {
116
- signaling: resolveSignalingUrls(options.signaling)
117
- });
118
- } else if (options.provider === "websocket") {
119
- if (!options.websocketUrl) {
120
- throw new Error('[Collaboration] websocketUrl is required when provider is "websocket"');
121
- }
122
- provider = new WebsocketProvider(options.websocketUrl, options.room, ydoc);
123
- }
124
- const awareness = provider?.awareness ?? new Awareness(ydoc);
125
- awareness.setLocalStateField("user", options.user);
126
- let emitCursor = options.emitCursor ?? true;
127
- const setLocalCursorEnabled = (enabled) => {
128
- if (emitCursor === enabled) return;
129
- emitCursor = enabled;
130
- if (!enabled) {
131
- awareness.setLocalStateField("cursor", null);
132
- }
133
- awareness.setLocalStateField("emitCursor", enabled);
134
- };
135
- awareness.setLocalStateField("present", true);
136
- awareness.setLocalStateField("emitCursor", emitCursor);
137
- let awarenessHandler;
138
- if (options.onAwarenessChange) {
139
- const notify = () => {
140
- options.onAwarenessChange?.(createAwarenessStates(awareness));
141
- };
142
- awarenessHandler = notify;
143
- awareness.on("change", notify);
144
- notify();
145
- }
146
- const readyParts = [];
147
- if (persistence) {
148
- readyParts.push(
149
- persistence.whenSynced.then(
150
- () => void 0,
151
- () => void 0
152
- )
153
- );
154
- }
155
- if (provider && options.provider === "websocket") {
156
- const ws = provider;
157
- readyParts.push(
158
- new Promise((resolve) => {
159
- let settled = false;
160
- const done = () => {
161
- if (settled) return;
162
- settled = true;
163
- resolve();
164
- };
165
- if (ws.synced) {
166
- done();
167
- return;
168
- }
169
- ws.once("sync", done);
170
- ws.once("synced", done);
171
- setTimeout(done, 5e3);
172
- })
173
- );
174
- }
175
- const whenReady = readyParts.length === 0 ? Promise.resolve() : Promise.all(readyParts).then(() => void 0);
176
- const destroy = () => {
177
- if (awarenessHandler) {
178
- awareness.off("change", awarenessHandler);
179
- }
180
- awareness.setLocalState(null);
181
- provider?.destroy?.();
182
- awareness.destroy?.();
183
- void persistence?.destroy();
184
- ydoc.destroy();
185
- };
186
- return { ydoc, provider, awareness, persistence, whenReady, destroy, setLocalCursorEnabled };
187
- }
188
- function collaborationExtensions(options) {
189
- const setup = "ydoc" in options ? options : createCollaboration(options);
190
- const extensions = [
191
- Collaboration.configure({ document: setup.ydoc })
192
- ];
193
- if (setup.provider) {
194
- extensions.push(
195
- CollaborationCursor.configure({
196
- provider: setup.provider,
197
- user: setup.awareness.getLocalState()?.user ?? { name: "", color: "" }
198
- })
199
- );
200
- }
201
- return extensions;
202
- }
203
-
204
71
  // src/BlockAttributes.ts
205
72
  import { Extension } from "@tiptap/core";
206
73
  import { Plugin, PluginKey } from "@tiptap/pm/state";
@@ -262,6 +129,42 @@ var BlockAttributesExtension = Extension.create({
262
129
  }
263
130
  });
264
131
 
132
+ // src/collab/seedEmptyFragment.ts
133
+ import * as Y from "yjs";
134
+ var PROVISIONAL_SEED_ORIGIN = "docflow:provisional-seed";
135
+ function isPristineEmptyParagraph(element) {
136
+ return element.length === 0 && Object.keys(element.getAttributes()).length === 0;
137
+ }
138
+ function seedEmptyFragment(doc, field = "default") {
139
+ const fragment = doc.getXmlFragment(field);
140
+ const paragraph = new Y.XmlElement("paragraph");
141
+ const observer = (_events, transaction) => {
142
+ if (transaction.origin === PROVISIONAL_SEED_ORIGIN) return;
143
+ if (paragraph.parent !== fragment) return;
144
+ if (!isPristineEmptyParagraph(paragraph)) return;
145
+ if (fragment.length < 2) return;
146
+ const index = fragment.toArray().indexOf(paragraph);
147
+ if (index === -1) return;
148
+ doc.transact(() => {
149
+ fragment.delete(index);
150
+ }, PROVISIONAL_SEED_ORIGIN);
151
+ };
152
+ let disposed = false;
153
+ const dispose = () => {
154
+ if (disposed) return;
155
+ disposed = true;
156
+ fragment.unobserveDeep(observer);
157
+ };
158
+ if (fragment.length > 0) {
159
+ return { seeded: false, dispose };
160
+ }
161
+ fragment.observeDeep(observer);
162
+ doc.transact(() => {
163
+ fragment.insert(0, [paragraph]);
164
+ }, PROVISIONAL_SEED_ORIGIN);
165
+ return { seeded: true, dispose };
166
+ }
167
+
265
168
  // src/EditorContext.ts
266
169
  import { Extension as Extension2 } from "@tiptap/core";
267
170
  var EditorContextExtension = Extension2.create({
@@ -1212,6 +1115,94 @@ var PaginationPlus = Extension4.create({
1212
1115
  }
1213
1116
  });
1214
1117
 
1118
+ // src/pagination/PaginationCaretFix.ts
1119
+ import { Extension as Extension5 } from "@tiptap/core";
1120
+ import { Plugin as Plugin4, PluginKey as PluginKey4, TextSelection as TextSelection2 } from "@tiptap/pm/state";
1121
+ var PAGINATION_CLASS = "rm-with-pagination";
1122
+ var PAGINATION_DISABLED_ATTR = "rm-pagination-disabled";
1123
+ var dragStates = /* @__PURE__ */ new WeakMap();
1124
+ var isPaginationActive = (view) => {
1125
+ const dom = view.dom;
1126
+ return dom.classList.contains(PAGINATION_CLASS) && dom.getAttribute(PAGINATION_DISABLED_ATTR) === null;
1127
+ };
1128
+ var isEditable = (view) => view.dom.getAttribute("contenteditable") !== "false";
1129
+ var isPlainSingleLeftClick = (event) => event.button === 0 && event.detail === 1 && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey;
1130
+ function posFromTextOffset(view, textNode, offset) {
1131
+ if (textNode.nodeType !== Node.TEXT_NODE || !view.dom.contains(textNode)) return null;
1132
+ const text = textNode;
1133
+ const clamped = Math.min(Math.max(0, offset), text.length);
1134
+ const parent = text.parentElement;
1135
+ if (parent?.closest?.('[contenteditable="false"]')) return null;
1136
+ let pos = view.posAtDOM(text, clamped, -1);
1137
+ if (pos < 0) pos = view.posAtDOM(text, clamped, 1);
1138
+ if (pos < 0) return null;
1139
+ return view.state.doc.resolve(pos).parent.isTextblock ? pos : null;
1140
+ }
1141
+ function resolvePosFromNativePoint(view, clientX, clientY) {
1142
+ const doc = view.dom.ownerDocument;
1143
+ if (typeof doc.caretRangeFromPoint !== "function") return null;
1144
+ const range = doc.caretRangeFromPoint(clientX, clientY);
1145
+ if (!range) return null;
1146
+ return posFromTextOffset(view, range.startContainer, range.startOffset);
1147
+ }
1148
+ function stopDrag(view) {
1149
+ const drag = dragStates.get(view);
1150
+ if (!drag) return;
1151
+ dragStates.delete(view);
1152
+ const doc = view.dom.ownerDocument;
1153
+ doc.removeEventListener("mousemove", drag.onMouseMove);
1154
+ doc.removeEventListener("mouseup", drag.onMouseUp);
1155
+ }
1156
+ function beginDrag(view, anchorPos) {
1157
+ stopDrag(view);
1158
+ const doc = view.dom.ownerDocument;
1159
+ const onMouseMove = (event) => {
1160
+ const drag = dragStates.get(view);
1161
+ if (!drag) return;
1162
+ const pos = resolvePosFromNativePoint(view, event.clientX, event.clientY);
1163
+ if (pos === null) return;
1164
+ const from = Math.min(drag.anchorPos, pos);
1165
+ const to = Math.max(drag.anchorPos, pos);
1166
+ view.dispatch(view.state.tr.setSelection(TextSelection2.create(view.state.doc, from, to)));
1167
+ };
1168
+ const onMouseUp = () => {
1169
+ stopDrag(view);
1170
+ };
1171
+ dragStates.set(view, { anchorPos, onMouseMove, onMouseUp });
1172
+ doc.addEventListener("mousemove", onMouseMove);
1173
+ doc.addEventListener("mouseup", onMouseUp);
1174
+ }
1175
+ function interceptPaginationMouseDown(view, event) {
1176
+ if (!isPaginationActive(view) || !isPlainSingleLeftClick(event) || !isEditable(view)) return false;
1177
+ const pos = resolvePosFromNativePoint(view, event.clientX, event.clientY);
1178
+ if (pos === null) return false;
1179
+ view.focus();
1180
+ view.dispatch(view.state.tr.setSelection(TextSelection2.create(view.state.doc, pos)));
1181
+ beginDrag(view, pos);
1182
+ return true;
1183
+ }
1184
+ var PaginationCaretFix = Extension5.create({
1185
+ name: "paginationCaretFix",
1186
+ addProseMirrorPlugins() {
1187
+ return [
1188
+ new Plugin4({
1189
+ key: new PluginKey4("paginationCaretFix"),
1190
+ props: {
1191
+ handleDOMEvents: {
1192
+ mousedown: (view, event) => {
1193
+ try {
1194
+ return interceptPaginationMouseDown(view, event);
1195
+ } catch {
1196
+ return false;
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+ })
1202
+ ];
1203
+ }
1204
+ });
1205
+
1215
1206
  // src/PerformanceMonitor.ts
1216
1207
  var TARGET_FRAME_MS = 1e3 / 60;
1217
1208
  var formatMb = (bytes) => `${Math.round(bytes / 1024 / 1024)}MB`;
@@ -1579,7 +1570,7 @@ function createEditor(options = {}) {
1579
1570
  content: options.content ? migrateContent(options.content) : options.content
1580
1571
  };
1581
1572
  const plugins = migratedOptions.plugins ?? [];
1582
- const collaborationSetup = !migratedOptions.collaboration ? void 0 : "ydoc" in migratedOptions.collaboration ? migratedOptions.collaboration : createCollaboration(migratedOptions.collaboration);
1573
+ const collaborationSetup = migratedOptions.collaboration;
1583
1574
  const performanceMonitor = migratedOptions.debug ? createPerformanceMonitor() : void 0;
1584
1575
  let tiptapEditor = createTiptapEditor(migratedOptions, plugins, collaborationSetup);
1585
1576
  let pluginActions = createActionMap(tiptapEditor, plugins);
@@ -1640,11 +1631,11 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
1640
1631
  const pluginExtensions = collectExtensions(plugins);
1641
1632
  const blockAttrs = options.getPageMap ? BlockAttributesExtension.configure({ pageMap: options.getPageMap }) : BlockAttributesExtension;
1642
1633
  const paginationExt = options.paginationOptions ? PaginationPlus.configure(options.paginationOptions) : PaginationPlus;
1643
- const ManualColumnResize = Extension5.create({
1634
+ const ManualColumnResize = Extension6.create({
1644
1635
  name: "manualColumnResize",
1645
1636
  addProseMirrorPlugins() {
1646
- const key2 = new PluginKey4("manualColumnResize");
1647
- return [new Plugin4({
1637
+ const key2 = new PluginKey5("manualColumnResize");
1638
+ return [new Plugin5({
1648
1639
  key: key2,
1649
1640
  view(view) {
1650
1641
  function pauseObserver() {
@@ -1966,6 +1957,10 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
1966
1957
  TextStyle,
1967
1958
  blockAttrs,
1968
1959
  paginationExt,
1960
+ // Always present — fixes caret-on-click misplacement caused by the
1961
+ // pagination DOM (header/footer/gap widgets breaking linear coordinates).
1962
+ // No-op (gated at runtime) when pagination is off or disabled.
1963
+ PaginationCaretFix,
1969
1964
  // Always present — carries host-injected ports (onImageUpload, citation, …)
1970
1965
  // in storage so plugin commands can reach them through the editor instance.
1971
1966
  EditorContextExtension.configure({
@@ -1980,9 +1975,18 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
1980
1975
  ];
1981
1976
  const content = options.collaboration ? void 0 : options.content;
1982
1977
  if (options.collaboration && collaborationSetup) {
1978
+ const seed = seedEmptyFragment(collaborationSetup.ydoc);
1979
+ if (seed.seeded) {
1980
+ extensions.push(
1981
+ Extension6.create({
1982
+ name: "provisionalSeedCleanup",
1983
+ onDestroy: () => seed.dispose()
1984
+ })
1985
+ );
1986
+ }
1983
1987
  extensions = [...extensions, ...collaborationExtensions(collaborationSetup)];
1984
1988
  }
1985
- return new TiptapEditor({
1989
+ const tiptapEditor = new TiptapEditor({
1986
1990
  element: options.target,
1987
1991
  content,
1988
1992
  extensions,
@@ -1997,10 +2001,11 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
1997
2001
  transformPastedHTML: sanitizePastedHTML
1998
2002
  }
1999
2003
  });
2004
+ return tiptapEditor;
2000
2005
  }
2001
2006
 
2002
2007
  // src/FontSize.ts
2003
- import { Extension as Extension6 } from "@tiptap/core";
2008
+ import { Extension as Extension7 } from "@tiptap/core";
2004
2009
  function mergeFontSize(textStyleType, existing, fontSize) {
2005
2010
  if (fontSize === null) {
2006
2011
  const { fontSize: _removed, ...rest } = existing?.attrs ?? {};
@@ -2011,7 +2016,7 @@ function mergeFontSize(textStyleType, existing, fontSize) {
2011
2016
  function hasAttrs(mark) {
2012
2017
  return Object.values(mark.attrs).some((v) => v != null);
2013
2018
  }
2014
- var FontSizeExtension = Extension6.create({
2019
+ var FontSizeExtension = Extension7.create({
2015
2020
  name: "fontSize",
2016
2021
  addGlobalAttributes() {
2017
2022
  return [
@@ -2108,113 +2113,6 @@ var FontSizeExtension = Extension6.create({
2108
2113
  };
2109
2114
  }
2110
2115
  });
2111
-
2112
- // src/SubdocumentProvider.ts
2113
- import { Awareness as AwarenessClass } from "y-protocols/awareness";
2114
- import { WebsocketProvider as WebsocketProvider2 } from "y-websocket";
2115
- import * as Y2 from "yjs";
2116
- var SubdocumentProvider = class {
2117
- parentDoc;
2118
- subdocs;
2119
- awareness;
2120
- options;
2121
- states = /* @__PURE__ */ new Map();
2122
- activeQueue = [];
2123
- parentProvider = null;
2124
- constructor(options) {
2125
- this.options = {
2126
- maxActive: options.maxActive ?? 5,
2127
- onStateChange: options.onStateChange ?? (() => {
2128
- }),
2129
- ...options
2130
- };
2131
- this.parentDoc = new Y2.Doc();
2132
- this.subdocs = this.parentDoc.getMap("subdocs");
2133
- this.awareness = new AwarenessClass(this.parentDoc);
2134
- this.awareness.setLocalStateField("user", this.options.user);
2135
- this.parentProvider = new WebsocketProvider2(
2136
- this.options.websocketUrl,
2137
- `${this.options.roomPrefix}/_parent`,
2138
- this.parentDoc
2139
- );
2140
- }
2141
- /** Create a new subdocument. Does NOT activate it. */
2142
- createSubdoc(id, initialState) {
2143
- if (this.states.has(id)) return this.states.get(id).doc;
2144
- const doc = new Y2.Doc();
2145
- if (initialState) Y2.applyUpdate(doc, initialState);
2146
- doc.getXmlFragment("default");
2147
- this.subdocs.set(id, doc);
2148
- this.states.set(id, {
2149
- id,
2150
- doc,
2151
- provider: null,
2152
- active: false
2153
- });
2154
- this.options.onStateChange(this.getAllStates());
2155
- return doc;
2156
- }
2157
- /** Activate a subdocument — connects it to the network for live sync. */
2158
- activate(id) {
2159
- const state = this.states.get(id);
2160
- if (!state || state.active) return;
2161
- if (this.activeQueue.length >= this.options.maxActive) {
2162
- const oldest = this.activeQueue.shift();
2163
- if (oldest) this.deactivate(oldest);
2164
- }
2165
- const doc = state.doc;
2166
- const roomName = `${this.options.roomPrefix}/${id}`;
2167
- const provider = new WebsocketProvider2(
2168
- this.options.websocketUrl,
2169
- roomName,
2170
- doc
2171
- );
2172
- provider.awareness.setLocalStateField("user", this.options.user);
2173
- state.provider = provider;
2174
- state.active = true;
2175
- this.activeQueue.push(id);
2176
- this.options.onStateChange(this.getAllStates());
2177
- }
2178
- /** Deactivate a subdocument — disconnects it from the network. */
2179
- deactivate(id) {
2180
- const state = this.states.get(id);
2181
- if (!state || !state.active) return;
2182
- state.provider?.destroy();
2183
- state.provider = null;
2184
- state.active = false;
2185
- const idx = this.activeQueue.indexOf(id);
2186
- if (idx >= 0) this.activeQueue.splice(idx, 1);
2187
- this.options.onStateChange(this.getAllStates());
2188
- }
2189
- /** Get the Y.XmlFragment for a subdocument. */
2190
- getFragment(id) {
2191
- const doc = this.states.get(id)?.doc ?? this.subdocs.get(id);
2192
- if (!doc) return null;
2193
- return doc.getXmlFragment("default");
2194
- }
2195
- /** Check if a subdocument is currently active (syncing). */
2196
- isActive(id) {
2197
- return this.states.get(id)?.active ?? false;
2198
- }
2199
- /** Get all subdocument states. */
2200
- getAllStates() {
2201
- return Array.from(this.states.values());
2202
- }
2203
- /** Get IDs of all active subdocuments. */
2204
- getActiveIds() {
2205
- return [...this.activeQueue];
2206
- }
2207
- /** Destroy all subdocuments and providers. */
2208
- destroy() {
2209
- for (const [id] of this.states) {
2210
- this.deactivate(id);
2211
- }
2212
- this.parentProvider?.destroy();
2213
- this.parentDoc.destroy();
2214
- this.states.clear();
2215
- this.activeQueue.length = 0;
2216
- }
2217
- };
2218
2116
  export {
2219
2117
  BlockAttributesExtension,
2220
2118
  CONTEXT_CHAR_CAP,
@@ -2225,7 +2123,6 @@ export {
2225
2123
  PaginationPlus,
2226
2124
  PerformanceMonitor,
2227
2125
  SearchAndReplaceExtension,
2228
- SubdocumentProvider,
2229
2126
  buildAIPrompt,
2230
2127
  clearSearch,
2231
2128
  collaborationExtensions,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kedataindo/docflow-core",
3
3
  "license": "UNLICENSED",
4
- "version": "0.0.71",
4
+ "version": "0.0.73",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -16,6 +16,11 @@
16
16
  "types": "./dist/ai/index.d.ts",
17
17
  "import": "./dist/ai/index.js",
18
18
  "require": "./dist/ai/index.cjs"
19
+ },
20
+ "./collab": {
21
+ "types": "./dist/collab/index.d.ts",
22
+ "import": "./dist/collab/index.js",
23
+ "require": "./dist/collab/index.cjs"
19
24
  }
20
25
  },
21
26
  "publishConfig": {