@kedataindo/docflow-core 0.0.71 → 0.0.72

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,6 +10,10 @@ 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
19
  import { Extension as Extension5, Editor as TiptapEditor } from "@tiptap/core";
@@ -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({
@@ -1579,7 +1482,7 @@ function createEditor(options = {}) {
1579
1482
  content: options.content ? migrateContent(options.content) : options.content
1580
1483
  };
1581
1484
  const plugins = migratedOptions.plugins ?? [];
1582
- const collaborationSetup = !migratedOptions.collaboration ? void 0 : "ydoc" in migratedOptions.collaboration ? migratedOptions.collaboration : createCollaboration(migratedOptions.collaboration);
1485
+ const collaborationSetup = migratedOptions.collaboration;
1583
1486
  const performanceMonitor = migratedOptions.debug ? createPerformanceMonitor() : void 0;
1584
1487
  let tiptapEditor = createTiptapEditor(migratedOptions, plugins, collaborationSetup);
1585
1488
  let pluginActions = createActionMap(tiptapEditor, plugins);
@@ -1980,9 +1883,18 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
1980
1883
  ];
1981
1884
  const content = options.collaboration ? void 0 : options.content;
1982
1885
  if (options.collaboration && collaborationSetup) {
1886
+ const seed = seedEmptyFragment(collaborationSetup.ydoc);
1887
+ if (seed.seeded) {
1888
+ extensions.push(
1889
+ Extension5.create({
1890
+ name: "provisionalSeedCleanup",
1891
+ onDestroy: () => seed.dispose()
1892
+ })
1893
+ );
1894
+ }
1983
1895
  extensions = [...extensions, ...collaborationExtensions(collaborationSetup)];
1984
1896
  }
1985
- return new TiptapEditor({
1897
+ const tiptapEditor = new TiptapEditor({
1986
1898
  element: options.target,
1987
1899
  content,
1988
1900
  extensions,
@@ -1997,6 +1909,7 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
1997
1909
  transformPastedHTML: sanitizePastedHTML
1998
1910
  }
1999
1911
  });
1912
+ return tiptapEditor;
2000
1913
  }
2001
1914
 
2002
1915
  // src/FontSize.ts
@@ -2108,113 +2021,6 @@ var FontSizeExtension = Extension6.create({
2108
2021
  };
2109
2022
  }
2110
2023
  });
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
2024
  export {
2219
2025
  BlockAttributesExtension,
2220
2026
  CONTEXT_CHAR_CAP,
@@ -2225,7 +2031,6 @@ export {
2225
2031
  PaginationPlus,
2226
2032
  PerformanceMonitor,
2227
2033
  SearchAndReplaceExtension,
2228
- SubdocumentProvider,
2229
2034
  buildAIPrompt,
2230
2035
  clearSearch,
2231
2036
  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.72",
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": {