@templatical/core 0.26.3 → 0.27.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { SdkError, createBlock, createDefaultTemplateContent, generateId, safeClone } from "@templatical/types";
2
2
  import { computed, reactive, readonly, ref, watch } from "@vue/reactivity";
3
- import { computed as computed$1, ref as ref$1 } from "vue";
3
+ import { computed as computed$1, onScopeDispose, ref as ref$1, watch as watch$1 } from "vue";
4
4
  //#region src/editor.ts
5
5
  function getColumnCount(layout) {
6
6
  if (layout === "1") return 1;
@@ -9,19 +9,33 @@ function getColumnCount(layout) {
9
9
  }
10
10
  function useEditor(options) {
11
11
  const state = reactive({
12
+ template: null,
12
13
  content: options.content ?? createDefaultTemplateContent(options.defaultFontFamily, options.templateDefaults),
13
14
  selectedBlockId: null,
14
15
  viewport: "desktop",
15
16
  darkMode: false,
16
17
  previewMode: false,
17
18
  isDirty: false,
19
+ isSaving: false,
20
+ isLoading: false,
18
21
  uiTheme: "auto"
19
22
  });
23
+ /**
24
+ * Bumped by every content mutation. `save()` captures it before awaiting the
25
+ * provider and only clears `isDirty` if it is unchanged afterwards, so an edit
26
+ * made while a save is in flight is not reported as persisted — which would
27
+ * also make autosave skip it, since that decides dirtiness at debounce time.
28
+ */
29
+ let revision = 0;
30
+ function touch() {
31
+ state.isDirty = true;
32
+ revision++;
33
+ }
20
34
  const content = computed({
21
35
  get: () => state.content,
22
36
  set: (value) => {
23
37
  state.content = value;
24
- state.isDirty = true;
38
+ touch();
25
39
  }
26
40
  });
27
41
  const selectedBlock = computed(() => {
@@ -73,7 +87,7 @@ function useEditor(options) {
73
87
  }
74
88
  function setContent(newContent, markDirty = true) {
75
89
  state.content = newContent;
76
- if (markDirty) state.isDirty = true;
90
+ if (markDirty) touch();
77
91
  }
78
92
  function selectBlock(blockId) {
79
93
  if (blockId && isBlockLocked(blockId)) return;
@@ -97,7 +111,7 @@ function useEditor(options) {
97
111
  const block = findBlockById(state.content.blocks, blockId);
98
112
  if (block) {
99
113
  Object.assign(block, updates);
100
- state.isDirty = true;
114
+ touch();
101
115
  }
102
116
  }
103
117
  function updateSettings(updates) {
@@ -105,7 +119,7 @@ function useEditor(options) {
105
119
  ...state.content.settings,
106
120
  ...updates
107
121
  };
108
- state.isDirty = true;
122
+ touch();
109
123
  }
110
124
  function addBlock(block, targetSectionId, columnIndex = 0, index) {
111
125
  if (targetSectionId && block.type === "section") return;
@@ -121,7 +135,7 @@ function useEditor(options) {
121
135
  }
122
136
  } else if (index !== void 0 && index < state.content.blocks.length) state.content.blocks.splice(index, 0, block);
123
137
  else state.content.blocks.push(block);
124
- state.isDirty = true;
138
+ touch();
125
139
  }
126
140
  function removeBlock(blockId) {
127
141
  if (isBlockLocked(blockId)) return;
@@ -135,7 +149,7 @@ function useEditor(options) {
135
149
  collectBlockIds(removed, removedIds);
136
150
  if (removedIds.has(state.selectedBlockId)) state.selectedBlockId = null;
137
151
  }
138
- state.isDirty = true;
152
+ touch();
139
153
  }
140
154
  }
141
155
  }
@@ -157,10 +171,105 @@ function useEditor(options) {
157
171
  } else targetArray = state.content.blocks;
158
172
  const [block] = parent.blocks.splice(oldIndex, 1);
159
173
  targetArray.splice(newIndex, 0, block);
160
- state.isDirty = true;
174
+ touch();
161
175
  }
162
176
  function markDirty() {
163
- state.isDirty = true;
177
+ touch();
178
+ }
179
+ /**
180
+ * A refused call throws rather than resolving, because a resolved promise
181
+ * would read as "saved" to whoever awaited it.
182
+ *
183
+ * The message names the provider key the consumer set, not an internal
184
+ * capability flag: `EditorCapabilities` is type-only and its injection key is
185
+ * unexported, so neither a core nor an editor consumer can read one. Note the
186
+ * editor hides its own save controls when `save` is withheld but has no
187
+ * create affordance to hide at all, so `create()` is reachable by definition.
188
+ */
189
+ function refuse(action, reason) {
190
+ throw new SdkError(`[Templatical] Templates: ${action} ${reason}`);
191
+ }
192
+ function requireProvider(action) {
193
+ const { templates } = options;
194
+ if (!templates) refuse(action, "needs a templates provider. Pass one as `init({ templates })` to enable saving and loading.");
195
+ return templates;
196
+ }
197
+ function setName(name) {
198
+ if (!state.template) return;
199
+ state.template = {
200
+ ...state.template,
201
+ name
202
+ };
203
+ touch();
204
+ }
205
+ async function create(input) {
206
+ const { create: providerCreate } = requireProvider("create()");
207
+ if (typeof providerCreate !== "function") refuse("create()", "is disabled by the provider — its `create` is `false`.");
208
+ state.isLoading = true;
209
+ try {
210
+ if (input?.content) state.content = input.content;
211
+ const revisionAtRequest = revision;
212
+ const template = await providerCreate(input?.name !== void 0 ? {
213
+ name: input.name,
214
+ content: state.content
215
+ } : { content: state.content });
216
+ state.template = template;
217
+ if (revision === revisionAtRequest) state.isDirty = false;
218
+ return template;
219
+ } catch (error) {
220
+ options.onError?.(error);
221
+ throw error;
222
+ } finally {
223
+ state.isLoading = false;
224
+ }
225
+ }
226
+ async function load(templateId) {
227
+ const provider = requireProvider("load()");
228
+ state.isLoading = true;
229
+ try {
230
+ const template = await provider.load(templateId);
231
+ state.template = template;
232
+ state.content = template.content;
233
+ state.isDirty = false;
234
+ return template;
235
+ } catch (error) {
236
+ options.onError?.(error);
237
+ throw error;
238
+ } finally {
239
+ state.isLoading = false;
240
+ }
241
+ }
242
+ async function save() {
243
+ const { save: providerSave } = requireProvider("save()");
244
+ if (typeof providerSave !== "function") refuse("save()", "is disabled by the provider — its `save` is `false`.");
245
+ const current = state.template;
246
+ if (!current) refuse("save()", "has no template loaded. Call create() or load() first.");
247
+ const patch = current.name !== void 0 ? {
248
+ name: current.name,
249
+ content: state.content
250
+ } : { content: state.content };
251
+ state.isSaving = true;
252
+ try {
253
+ const revisionAtRequest = revision;
254
+ const template = await providerSave(current.id, patch);
255
+ if (state.template?.id === current.id) {
256
+ const localName = state.template.name;
257
+ state.template = localName !== current.name ? {
258
+ ...template,
259
+ name: localName
260
+ } : template;
261
+ }
262
+ if (revision === revisionAtRequest) state.isDirty = false;
263
+ return template;
264
+ } catch (error) {
265
+ options.onError?.(error);
266
+ throw error;
267
+ } finally {
268
+ state.isSaving = false;
269
+ }
270
+ }
271
+ function hasTemplate() {
272
+ return state.template?.id !== void 0;
164
273
  }
165
274
  return {
166
275
  state: readonly(state),
@@ -179,7 +288,12 @@ function useEditor(options) {
179
288
  removeBlock,
180
289
  moveBlock,
181
290
  markDirty,
182
- findBlockLocation
291
+ findBlockLocation,
292
+ setName,
293
+ create,
294
+ load,
295
+ save,
296
+ hasTemplate
183
297
  };
184
298
  }
185
299
  //#endregion
@@ -344,8 +458,23 @@ function useBlockActions(options) {
344
458
  }
345
459
  //#endregion
346
460
  //#region src/auto-save.ts
461
+ /**
462
+ * Trailing debounce, in ms, measured from the *last* content mutation.
463
+ *
464
+ * Typing is not debounced upstream — TipTap's `onUpdate` calls `updateBlock` per
465
+ * keystroke — so this is the only thing between a keypress and a whole-document
466
+ * write. 1000 was too eager: ordinary prose pauses for a second constantly
467
+ * (word choice, re-reading, reaching for the mouse), so a single paragraph could
468
+ * produce dozens of full-content saves. 2000 roughly halves that while still
469
+ * landing well before a user wonders whether their work was kept.
470
+ *
471
+ * **The single default for both entry points.** Cloud imports this constant
472
+ * rather than declaring its own — a second copy for one setting drifts silently,
473
+ * since nothing links the two.
474
+ */
475
+ const DEFAULT_AUTO_SAVE_DEBOUNCE_MS = 2e3;
347
476
  function useAutoSave(options) {
348
- const { content, isDirty, onChange, debounce = 1e3, enabled = true } = options;
477
+ const { content, isDirty, onChange, debounce = DEFAULT_AUTO_SAVE_DEBOUNCE_MS, enabled = true } = options;
349
478
  let timeoutId = null;
350
479
  let paused = false;
351
480
  function isEnabled() {
@@ -685,6 +814,389 @@ function createLocalStorageSavedBlocksProvider(options = {}) {
685
814
  };
686
815
  }
687
816
  //#endregion
688
- export { createLocalStorageSavedBlocksProvider, useAutoSave, useBlockActions, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor, useSavedBlocks };
817
+ //#region src/version-history.ts
818
+ /**
819
+ * Reactive state over a {@link VersionHistoryProvider}.
820
+ *
821
+ * Owns the list, the loading flags and the per-version content cache that keeps
822
+ * scrubbing synchronous. Errors are reported through `onError` and re-thrown,
823
+ * leaving the list untouched on failure.
824
+ *
825
+ * It deliberately does **not** create versions of its own accord. The editor
826
+ * never records a version on a save — whoever implements `TemplatesProvider.save`
827
+ * decides that, because they are the side that pays for the storage.
828
+ */
829
+ function useVersionHistory(options) {
830
+ const { provider, getTemplateId } = options;
831
+ const versions = ref$1([]);
832
+ const isLoading = ref$1(false);
833
+ const nextCursor = ref$1(void 0);
834
+ const isRestoring = ref$1(false);
835
+ /**
836
+ * Content fetched through `get`, keyed by version id. A version that carried
837
+ * a `content` hint never lands here — the hint is read straight off the entry,
838
+ * so a provider that eager-loads pays no second copy.
839
+ */
840
+ const fetched = /* @__PURE__ */ new Map();
841
+ const canCreate = computed$1(() => typeof provider.create === "function");
842
+ const canRestore = computed$1(() => typeof provider.restore === "function");
843
+ /**
844
+ * The UI hides disabled actions and renders nothing before a template exists,
845
+ * so reaching one of these means a programmatic caller went around it. Fail
846
+ * loudly rather than silently no-op: a resolved promise reads as "done".
847
+ */
848
+ function requireTemplateId(action) {
849
+ const templateId = getTemplateId();
850
+ if (!templateId) throw new SdkError(`[Templatical] Version history: ${action} needs a template. Call create() or load() first.`);
851
+ return templateId;
852
+ }
853
+ function refuse(action) {
854
+ throw new SdkError(`[Templatical] Version history: ${action} is disabled by the provider. Check the capability before calling — the editor's own UI hides the action.`);
855
+ }
856
+ async function load(params) {
857
+ const templateId = requireTemplateId("list");
858
+ isLoading.value = true;
859
+ try {
860
+ const page = await provider.list(templateId, params);
861
+ versions.value = page.versions;
862
+ nextCursor.value = page.nextCursor;
863
+ } catch (error) {
864
+ options.onError?.(error);
865
+ throw error;
866
+ } finally {
867
+ isLoading.value = false;
868
+ }
869
+ }
870
+ function peekContent(version) {
871
+ return version.content ?? fetched.get(version.id) ?? null;
872
+ }
873
+ async function resolveContent(version) {
874
+ const known = peekContent(version);
875
+ if (known) return known;
876
+ const templateId = requireTemplateId("get");
877
+ try {
878
+ const content = await provider.get(templateId, version.id);
879
+ fetched.set(version.id, content);
880
+ return content;
881
+ } catch (error) {
882
+ options.onError?.(error);
883
+ throw error;
884
+ }
885
+ }
886
+ async function create(content, meta) {
887
+ const { create: providerCreate } = provider;
888
+ if (typeof providerCreate !== "function") refuse("create");
889
+ const templateId = requireTemplateId("create");
890
+ try {
891
+ const created = await providerCreate(templateId, content, meta);
892
+ versions.value = [created, ...versions.value];
893
+ return created;
894
+ } catch (error) {
895
+ options.onError?.(error);
896
+ throw error;
897
+ }
898
+ }
899
+ async function restore(versionId) {
900
+ const { restore: providerRestore } = provider;
901
+ if (typeof providerRestore !== "function") refuse("restore");
902
+ const templateId = requireTemplateId("restore");
903
+ isRestoring.value = true;
904
+ try {
905
+ return await providerRestore(templateId, versionId);
906
+ } catch (error) {
907
+ options.onError?.(error);
908
+ throw error;
909
+ } finally {
910
+ isRestoring.value = false;
911
+ }
912
+ }
913
+ return {
914
+ nextCursor,
915
+ versions,
916
+ isLoading,
917
+ isRestoring,
918
+ canCreate,
919
+ canRestore,
920
+ load,
921
+ peekContent,
922
+ resolveContent,
923
+ create,
924
+ restore
925
+ };
926
+ }
927
+ //#endregion
928
+ //#region src/comments.ts
929
+ /**
930
+ * Reactive state over a {@link CommentsProvider}.
931
+ *
932
+ * Owns the thread list, the loading flags, the local mutations that keep the list
933
+ * consistent, and the derived counts the chrome renders. Errors are reported
934
+ * through `onError` and **re-thrown**, leaving the list untouched on failure —
935
+ * the same discipline as `useSavedBlocks` and `useVersionHistory`.
936
+ *
937
+ * Mutations **reject** when the provider withheld them rather than resolving to
938
+ * `null`: a resolved promise reads as "saved" to whoever awaited it. Ask through
939
+ * `canCreate` / `canUpdate` / `canDelete` / `canResolve` first — which is what the
940
+ * editor's own UI does, hiding each action rather than disabling it.
941
+ */
942
+ function useComments(options) {
943
+ const { provider, getTemplateId, getUser } = options;
944
+ const comments = ref$1([]);
945
+ const isLoading = ref$1(false);
946
+ const isSubmitting = ref$1(false);
947
+ const canCreate = computed$1(() => typeof provider.create === "function");
948
+ const canUpdate = computed$1(() => typeof provider.update === "function");
949
+ const canDelete = computed$1(() => typeof provider.delete === "function");
950
+ const canResolve = computed$1(() => typeof provider.setResolved === "function");
951
+ const totalCount = computed$1(() => comments.value.reduce((sum, thread) => sum + 1 + (thread.replies?.length ?? 0), 0));
952
+ const unresolvedCount = computed$1(() => comments.value.filter((thread) => !thread.resolvedAt).length);
953
+ const commentCountByBlock = computed$1(() => {
954
+ const map = /* @__PURE__ */ new Map();
955
+ for (const thread of comments.value) {
956
+ if (!thread.blockId) continue;
957
+ map.set(thread.blockId, (map.get(thread.blockId) ?? 0) + 1 + (thread.replies?.length ?? 0));
958
+ }
959
+ return map;
960
+ });
961
+ /**
962
+ * The UI hides disabled actions and renders nothing before a template exists,
963
+ * so reaching one of these means a programmatic caller went around it. Fail
964
+ * loudly rather than silently no-op.
965
+ */
966
+ function requireTemplateId(action) {
967
+ const templateId = getTemplateId();
968
+ if (!templateId) throw new SdkError(`[Templatical] Comments: ${action} needs a template. Call create() or load() first.`);
969
+ return templateId;
970
+ }
971
+ function requireUser(action) {
972
+ const user = getUser();
973
+ if (!user) throw new SdkError(`[Templatical] Comments: ${action} needs to know who is commenting. Pass init({ user: { id, name } }) — an unattributed comment is not written.`);
974
+ return user;
975
+ }
976
+ function refuse(action) {
977
+ throw new SdkError(`[Templatical] Comments: ${action} is disabled by the provider. Check the capability before calling — the editor's own UI hides the action.`);
978
+ }
979
+ function find(commentId) {
980
+ for (const thread of comments.value) {
981
+ if (thread.id === commentId) return thread;
982
+ for (const reply of thread.replies ?? []) if (reply.id === commentId) return reply;
983
+ }
984
+ return null;
985
+ }
986
+ function isOwn(comment) {
987
+ const user = getUser();
988
+ return user !== null && comment.author.id === user.id;
989
+ }
990
+ function emit(type, comment) {
991
+ options.onComment?.({
992
+ type,
993
+ comment
994
+ });
995
+ }
996
+ function report(error) {
997
+ const wrapped = error instanceof Error ? error : new Error(String(error), { cause: error });
998
+ options.onError?.(wrapped);
999
+ throw wrapped;
1000
+ }
1001
+ /**
1002
+ * Insert a comment, replacing an entry with the same id rather than duplicating
1003
+ * it. `create` and a subscribe echo of that same create both land here, which is
1004
+ * why a provider does not have to de-duplicate its own broadcasts.
1005
+ */
1006
+ function replaceAt(list, index, next) {
1007
+ return [
1008
+ ...list.slice(0, index),
1009
+ next,
1010
+ ...list.slice(index + 1)
1011
+ ];
1012
+ }
1013
+ function upsert(comment) {
1014
+ if (comment.parentId) {
1015
+ comments.value = comments.value.map((thread) => {
1016
+ if (thread.id !== comment.parentId) return thread;
1017
+ const replies = thread.replies ?? [];
1018
+ const at = replies.findIndex((reply) => reply.id === comment.id);
1019
+ return {
1020
+ ...thread,
1021
+ replies: at === -1 ? [...replies, comment] : replaceAt(replies, at, comment)
1022
+ };
1023
+ });
1024
+ return;
1025
+ }
1026
+ const at = comments.value.findIndex((thread) => thread.id === comment.id);
1027
+ if (at === -1) {
1028
+ comments.value = [...comments.value, comment];
1029
+ return;
1030
+ }
1031
+ comments.value = replaceAt(comments.value, at, {
1032
+ ...comment,
1033
+ replies: comment.replies ?? comments.value[at].replies
1034
+ });
1035
+ }
1036
+ function drop(commentId, parentId) {
1037
+ const existing = find(commentId);
1038
+ const parent = parentId ?? existing?.parentId ?? null;
1039
+ if (parent) comments.value = comments.value.map((thread) => thread.id === parent ? {
1040
+ ...thread,
1041
+ replies: (thread.replies ?? []).filter((r) => r.id !== commentId)
1042
+ } : thread);
1043
+ else comments.value = comments.value.filter((c) => c.id !== commentId);
1044
+ return existing;
1045
+ }
1046
+ async function load(params) {
1047
+ const templateId = requireTemplateId("list");
1048
+ isLoading.value = true;
1049
+ try {
1050
+ comments.value = await provider.list(templateId, params);
1051
+ } catch (error) {
1052
+ report(error);
1053
+ } finally {
1054
+ isLoading.value = false;
1055
+ }
1056
+ }
1057
+ async function create(input) {
1058
+ const { create: providerCreate } = provider;
1059
+ if (typeof providerCreate !== "function") refuse("create");
1060
+ const templateId = requireTemplateId("create");
1061
+ requireUser("create");
1062
+ isSubmitting.value = true;
1063
+ try {
1064
+ const created = await providerCreate(templateId, input);
1065
+ upsert(created);
1066
+ emit("created", created);
1067
+ return created;
1068
+ } catch (error) {
1069
+ report(error);
1070
+ } finally {
1071
+ isSubmitting.value = false;
1072
+ }
1073
+ }
1074
+ async function update(commentId, patch) {
1075
+ const { update: providerUpdate } = provider;
1076
+ if (typeof providerUpdate !== "function") refuse("update");
1077
+ const templateId = requireTemplateId("update");
1078
+ requireUser("update");
1079
+ isSubmitting.value = true;
1080
+ try {
1081
+ const updated = await providerUpdate(templateId, commentId, patch);
1082
+ upsert(updated);
1083
+ emit("updated", updated);
1084
+ return updated;
1085
+ } catch (error) {
1086
+ report(error);
1087
+ } finally {
1088
+ isSubmitting.value = false;
1089
+ }
1090
+ }
1091
+ async function remove(commentId) {
1092
+ const { delete: providerDelete } = provider;
1093
+ if (typeof providerDelete !== "function") refuse("delete");
1094
+ const templateId = requireTemplateId("delete");
1095
+ requireUser("delete");
1096
+ const existing = find(commentId);
1097
+ isSubmitting.value = true;
1098
+ try {
1099
+ await providerDelete(templateId, commentId);
1100
+ drop(commentId);
1101
+ if (existing) emit("deleted", existing);
1102
+ } catch (error) {
1103
+ report(error);
1104
+ } finally {
1105
+ isSubmitting.value = false;
1106
+ }
1107
+ }
1108
+ async function setResolved(commentId, resolved) {
1109
+ const { setResolved: providerSetResolved } = provider;
1110
+ if (typeof providerSetResolved !== "function") refuse("setResolved");
1111
+ const templateId = requireTemplateId("setResolved");
1112
+ requireUser("setResolved");
1113
+ isSubmitting.value = true;
1114
+ try {
1115
+ const updated = await providerSetResolved(templateId, commentId, resolved);
1116
+ upsert(updated);
1117
+ emit(updated.resolvedAt ? "resolved" : "unresolved", updated);
1118
+ return updated;
1119
+ } catch (error) {
1120
+ report(error);
1121
+ } finally {
1122
+ isSubmitting.value = false;
1123
+ }
1124
+ }
1125
+ function applyRemoteCreate(comment) {
1126
+ upsert(comment);
1127
+ emit("created", comment);
1128
+ }
1129
+ function applyRemoteUpdate(comment) {
1130
+ upsert(comment);
1131
+ emit(comment.resolvedAt ? "resolved" : "updated", comment);
1132
+ }
1133
+ function applyRemoteDelete(commentId, parentId) {
1134
+ const existing = drop(commentId, parentId);
1135
+ if (existing) emit("deleted", existing);
1136
+ }
1137
+ return {
1138
+ comments,
1139
+ isLoading,
1140
+ isSubmitting,
1141
+ canCreate,
1142
+ canUpdate,
1143
+ canDelete,
1144
+ canResolve,
1145
+ totalCount,
1146
+ unresolvedCount,
1147
+ commentCountByBlock,
1148
+ load,
1149
+ create,
1150
+ update,
1151
+ remove,
1152
+ setResolved,
1153
+ find,
1154
+ isOwn,
1155
+ applyRemoteCreate,
1156
+ applyRemoteUpdate,
1157
+ applyRemoteDelete
1158
+ };
1159
+ }
1160
+ /**
1161
+ * Wire {@link CommentsProvider.subscribe} into the three `applyRemote*` paths.
1162
+ *
1163
+ * A no-op when the provider has no `subscribe` — realtime is optional, and
1164
+ * comments without it work exactly the same except that a colleague's comment
1165
+ * only appears on the next read. Nothing here knows about a transport: Cloud's
1166
+ * Pusher channel lives inside `createCloudCommentsProvider`, and an SSE or
1167
+ * long-poll implementation is the same three lines on the consumer's side.
1168
+ *
1169
+ * Re-subscribes when the template id changes, and unsubscribes on scope dispose —
1170
+ * so a session that loads a second template does not keep receiving the first
1171
+ * one's comments.
1172
+ */
1173
+ function useCommentListener(options) {
1174
+ const { comments, provider, getTemplateId } = options;
1175
+ const { subscribe } = provider;
1176
+ if (typeof subscribe !== "function") return;
1177
+ let unsubscribe = null;
1178
+ function stop() {
1179
+ unsubscribe?.();
1180
+ unsubscribe = null;
1181
+ }
1182
+ watch$1(() => getTemplateId(), (templateId) => {
1183
+ stop();
1184
+ if (!templateId) return;
1185
+ unsubscribe = subscribe(templateId, (change) => {
1186
+ switch (change.type) {
1187
+ case "created":
1188
+ comments.applyRemoteCreate(change.comment);
1189
+ break;
1190
+ case "updated":
1191
+ comments.applyRemoteUpdate(change.comment);
1192
+ break;
1193
+ case "deleted": comments.applyRemoteDelete(change.commentId, change.parentId);
1194
+ }
1195
+ });
1196
+ }, { immediate: true });
1197
+ onScopeDispose(stop);
1198
+ }
1199
+ //#endregion
1200
+ export { DEFAULT_AUTO_SAVE_DEBOUNCE_MS, createLocalStorageSavedBlocksProvider, useAutoSave, useBlockActions, useCommentListener, useComments, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor, useSavedBlocks, useVersionHistory };
689
1201
 
690
1202
  //# sourceMappingURL=index.js.map