@kedataindo/docflow-core 0.0.84 → 0.0.86

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/ai/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(ai_exports, {
23
23
  CONTEXT_CHAR_CAP: () => CONTEXT_CHAR_CAP,
24
24
  LOCAL_STORAGE_KEY: () => LOCAL_STORAGE_KEY,
25
25
  buildAIPrompt: () => buildAIPrompt,
26
+ claudeProvider: () => claudeProvider,
26
27
  httpKeyStorage: () => httpKeyStorage,
27
28
  localStorageKeyStorage: () => localStorageKeyStorage,
28
29
  memoryKeyStorage: () => memoryKeyStorage,
@@ -295,11 +296,156 @@ function openaiCompatibleProvider(config) {
295
296
  }
296
297
  };
297
298
  }
299
+
300
+ // src/ai/claudeProvider.ts
301
+ var DEFAULT_BASE_URL = "https://api.anthropic.com";
302
+ var ANTHROPIC_VERSION = "2023-06-01";
303
+ var DEFAULT_MAX_TOKENS = 4096;
304
+ function authHeaders2(auth) {
305
+ switch (auth.type) {
306
+ case "bearer":
307
+ return { "x-api-key": auth.apiKey };
308
+ case "header":
309
+ return { [auth.name]: auth.value };
310
+ case "none":
311
+ return {};
312
+ }
313
+ }
314
+ function toError2(err) {
315
+ return err instanceof Error ? err : new Error(String(err));
316
+ }
317
+ function claudeProvider(config) {
318
+ const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
319
+ const maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
320
+ return {
321
+ async *complete(req) {
322
+ const controller = new AbortController();
323
+ const onAbort = () => controller.abort();
324
+ const callerSignal = req.signal;
325
+ if (callerSignal) {
326
+ if (callerSignal.aborted) controller.abort();
327
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
328
+ }
329
+ try {
330
+ const system = [config.systemPrompt, req.system].filter(Boolean).join("\n\n");
331
+ const body = {
332
+ model: config.model,
333
+ max_tokens: maxTokens,
334
+ messages: [{ role: "user", content: req.prompt }],
335
+ stream: true
336
+ };
337
+ if (system) body.system = system;
338
+ let res;
339
+ try {
340
+ res = await fetch(`${baseUrl}/v1/messages`, {
341
+ method: "POST",
342
+ headers: {
343
+ "Content-Type": "application/json",
344
+ "anthropic-version": ANTHROPIC_VERSION,
345
+ // Anthropic only allows browser calls with this explicit opt-in.
346
+ "anthropic-dangerous-direct-browser-access": "true",
347
+ ...authHeaders2(config.auth)
348
+ },
349
+ body: JSON.stringify(body),
350
+ signal: controller.signal
351
+ });
352
+ } catch (err) {
353
+ if (controller.signal.aborted) return;
354
+ yield { type: "error", error: toError2(err) };
355
+ return;
356
+ }
357
+ if (!res.ok) {
358
+ const detail = await res.text().catch(() => "");
359
+ yield {
360
+ type: "error",
361
+ error: new Error(
362
+ `Claude provider responded ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
363
+ )
364
+ };
365
+ return;
366
+ }
367
+ if (!res.body) {
368
+ yield { type: "error", error: new Error("Claude provider response has no body") };
369
+ return;
370
+ }
371
+ const reader = res.body.getReader();
372
+ const decoder = new TextDecoder();
373
+ let buffer = "";
374
+ let sawDone = false;
375
+ let stopReason;
376
+ try {
377
+ for (; ; ) {
378
+ const { done, value } = await reader.read();
379
+ if (done) break;
380
+ buffer += decoder.decode(value, { stream: true });
381
+ let sep;
382
+ while ((sep = buffer.indexOf("\n\n")) >= 0) {
383
+ const block = buffer.slice(0, sep);
384
+ buffer = buffer.slice(sep + 2);
385
+ for (const line of block.split("\n")) {
386
+ if (!line.startsWith("data:")) continue;
387
+ const data = line.slice("data:".length).trim();
388
+ if (!data) continue;
389
+ let json;
390
+ try {
391
+ json = JSON.parse(data);
392
+ } catch {
393
+ continue;
394
+ }
395
+ switch (json.type) {
396
+ case "content_block_delta": {
397
+ const text = json.delta?.text;
398
+ if (text) yield { type: "delta", text };
399
+ break;
400
+ }
401
+ case "message_delta": {
402
+ if (json.delta?.stop_reason) stopReason = json.delta.stop_reason;
403
+ break;
404
+ }
405
+ case "message_stop": {
406
+ sawDone = true;
407
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
408
+ return;
409
+ }
410
+ case "error": {
411
+ yield {
412
+ type: "error",
413
+ error: new Error(
414
+ json.error?.message ? `Claude provider error: ${json.error.message}` : "Claude provider stream error"
415
+ )
416
+ };
417
+ return;
418
+ }
419
+ default:
420
+ break;
421
+ }
422
+ }
423
+ }
424
+ }
425
+ } catch (err) {
426
+ if (!controller.signal.aborted) {
427
+ yield { type: "error", error: toError2(err) };
428
+ return;
429
+ }
430
+ } finally {
431
+ reader.releaseLock();
432
+ }
433
+ if (!sawDone && !controller.signal.aborted) {
434
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
435
+ }
436
+ } finally {
437
+ callerSignal?.removeEventListener("abort", onAbort);
438
+ controller.abort();
439
+ }
440
+ }
441
+ };
442
+ }
298
443
  // Annotate the CommonJS export names for ESM import in node:
299
444
  0 && (module.exports = {
300
445
  CONTEXT_CHAR_CAP,
301
446
  LOCAL_STORAGE_KEY,
302
447
  buildAIPrompt,
448
+ claudeProvider,
303
449
  httpKeyStorage,
304
450
  localStorageKeyStorage,
305
451
  memoryKeyStorage,
@@ -1 +1 @@
1
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, k as CONTEXT_CHAR_CAP, l as CslDate, m as CslItemData, n as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, p as buildAIPrompt, q as httpKeyStorage, r as localStorageKeyStorage, s as memoryKeyStorage, t as openaiCompatibleProvider, u as toAIStreamFn, v as trimContextAfter, w as trimContextBefore } from '../index-BsIFJXPk.cjs';
1
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from '../index-3B5ckTRa.cjs';
@@ -1 +1 @@
1
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, k as CONTEXT_CHAR_CAP, l as CslDate, m as CslItemData, n as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, p as buildAIPrompt, q as httpKeyStorage, r as localStorageKeyStorage, s as memoryKeyStorage, t as openaiCompatibleProvider, u as toAIStreamFn, v as trimContextAfter, w as trimContextBefore } from '../index-BsIFJXPk.js';
1
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from '../index-3B5ckTRa.js';
package/dist/ai/index.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  CONTEXT_CHAR_CAP,
3
3
  LOCAL_STORAGE_KEY,
4
4
  buildAIPrompt,
5
+ claudeProvider,
5
6
  httpKeyStorage,
6
7
  localStorageKeyStorage,
7
8
  memoryKeyStorage,
@@ -9,11 +10,12 @@ import {
9
10
  toAIStreamFn,
10
11
  trimContextAfter,
11
12
  trimContextBefore
12
- } from "../chunk-HMOWKFPE.js";
13
+ } from "../chunk-6RIBBYIP.js";
13
14
  export {
14
15
  CONTEXT_CHAR_CAP,
15
16
  LOCAL_STORAGE_KEY,
16
17
  buildAIPrompt,
18
+ claudeProvider,
17
19
  httpKeyStorage,
18
20
  localStorageKeyStorage,
19
21
  memoryKeyStorage,
@@ -261,6 +261,150 @@ function openaiCompatibleProvider(config) {
261
261
  };
262
262
  }
263
263
 
264
+ // src/ai/claudeProvider.ts
265
+ var DEFAULT_BASE_URL = "https://api.anthropic.com";
266
+ var ANTHROPIC_VERSION = "2023-06-01";
267
+ var DEFAULT_MAX_TOKENS = 4096;
268
+ function authHeaders2(auth) {
269
+ switch (auth.type) {
270
+ case "bearer":
271
+ return { "x-api-key": auth.apiKey };
272
+ case "header":
273
+ return { [auth.name]: auth.value };
274
+ case "none":
275
+ return {};
276
+ }
277
+ }
278
+ function toError2(err) {
279
+ return err instanceof Error ? err : new Error(String(err));
280
+ }
281
+ function claudeProvider(config) {
282
+ const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
283
+ const maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
284
+ return {
285
+ async *complete(req) {
286
+ const controller = new AbortController();
287
+ const onAbort = () => controller.abort();
288
+ const callerSignal = req.signal;
289
+ if (callerSignal) {
290
+ if (callerSignal.aborted) controller.abort();
291
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
292
+ }
293
+ try {
294
+ const system = [config.systemPrompt, req.system].filter(Boolean).join("\n\n");
295
+ const body = {
296
+ model: config.model,
297
+ max_tokens: maxTokens,
298
+ messages: [{ role: "user", content: req.prompt }],
299
+ stream: true
300
+ };
301
+ if (system) body.system = system;
302
+ let res;
303
+ try {
304
+ res = await fetch(`${baseUrl}/v1/messages`, {
305
+ method: "POST",
306
+ headers: {
307
+ "Content-Type": "application/json",
308
+ "anthropic-version": ANTHROPIC_VERSION,
309
+ // Anthropic only allows browser calls with this explicit opt-in.
310
+ "anthropic-dangerous-direct-browser-access": "true",
311
+ ...authHeaders2(config.auth)
312
+ },
313
+ body: JSON.stringify(body),
314
+ signal: controller.signal
315
+ });
316
+ } catch (err) {
317
+ if (controller.signal.aborted) return;
318
+ yield { type: "error", error: toError2(err) };
319
+ return;
320
+ }
321
+ if (!res.ok) {
322
+ const detail = await res.text().catch(() => "");
323
+ yield {
324
+ type: "error",
325
+ error: new Error(
326
+ `Claude provider responded ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
327
+ )
328
+ };
329
+ return;
330
+ }
331
+ if (!res.body) {
332
+ yield { type: "error", error: new Error("Claude provider response has no body") };
333
+ return;
334
+ }
335
+ const reader = res.body.getReader();
336
+ const decoder = new TextDecoder();
337
+ let buffer = "";
338
+ let sawDone = false;
339
+ let stopReason;
340
+ try {
341
+ for (; ; ) {
342
+ const { done, value } = await reader.read();
343
+ if (done) break;
344
+ buffer += decoder.decode(value, { stream: true });
345
+ let sep;
346
+ while ((sep = buffer.indexOf("\n\n")) >= 0) {
347
+ const block = buffer.slice(0, sep);
348
+ buffer = buffer.slice(sep + 2);
349
+ for (const line of block.split("\n")) {
350
+ if (!line.startsWith("data:")) continue;
351
+ const data = line.slice("data:".length).trim();
352
+ if (!data) continue;
353
+ let json;
354
+ try {
355
+ json = JSON.parse(data);
356
+ } catch {
357
+ continue;
358
+ }
359
+ switch (json.type) {
360
+ case "content_block_delta": {
361
+ const text = json.delta?.text;
362
+ if (text) yield { type: "delta", text };
363
+ break;
364
+ }
365
+ case "message_delta": {
366
+ if (json.delta?.stop_reason) stopReason = json.delta.stop_reason;
367
+ break;
368
+ }
369
+ case "message_stop": {
370
+ sawDone = true;
371
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
372
+ return;
373
+ }
374
+ case "error": {
375
+ yield {
376
+ type: "error",
377
+ error: new Error(
378
+ json.error?.message ? `Claude provider error: ${json.error.message}` : "Claude provider stream error"
379
+ )
380
+ };
381
+ return;
382
+ }
383
+ default:
384
+ break;
385
+ }
386
+ }
387
+ }
388
+ }
389
+ } catch (err) {
390
+ if (!controller.signal.aborted) {
391
+ yield { type: "error", error: toError2(err) };
392
+ return;
393
+ }
394
+ } finally {
395
+ reader.releaseLock();
396
+ }
397
+ if (!sawDone && !controller.signal.aborted) {
398
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
399
+ }
400
+ } finally {
401
+ callerSignal?.removeEventListener("abort", onAbort);
402
+ controller.abort();
403
+ }
404
+ }
405
+ };
406
+ }
407
+
264
408
  export {
265
409
  CONTEXT_CHAR_CAP,
266
410
  trimContextBefore,
@@ -271,5 +415,6 @@ export {
271
415
  LOCAL_STORAGE_KEY,
272
416
  localStorageKeyStorage,
273
417
  httpKeyStorage,
274
- openaiCompatibleProvider
418
+ openaiCompatibleProvider,
419
+ claudeProvider
275
420
  };
@@ -310,4 +310,35 @@ interface OpenAICompatibleConfig {
310
310
  }
311
311
  declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
312
312
 
313
- export { type AIStreamFn as A, type CitationPort as C, type HttpKeyStorageUrls as H, type ImageUploadHandler as I, type KeyStorage as K, LOCAL_STORAGE_KEY as L, type OpenAICompatibleConfig as O, type StreamEvent as S, type AIDraftFn as a, type AIAction as b, type AIActionRequest as c, type AICompleteRequest as d, type AIConfig as e, type AIDraftCitation as f, type AIDraftEvent as g, type AIProvider as h, type AIProviderFactory as i, type Auth as j, CONTEXT_CHAR_CAP as k, type CslDate as l, type CslItemData as m, type CslName as n, type ImageUploadResult as o, buildAIPrompt as p, httpKeyStorage as q, localStorageKeyStorage as r, memoryKeyStorage as s, openaiCompatibleProvider as t, toAIStreamFn as u, trimContextAfter as v, trimContextBefore as w };
313
+ /**
314
+ * Native Anthropic Claude `AIProvider` (per-user BYOK — issue #118,
315
+ * `docs/plans/PER_USER_BYOK.md` §3.4).
316
+ *
317
+ * Anthropic's Messages API is not OpenAI-shaped: different endpoint
318
+ * (`/v1/messages`), different auth (`x-api-key`), a **required** `max_tokens`, a
319
+ * top-level `system` parameter (not a system message), and its own SSE event
320
+ * names. This provider maps all of that onto the same `AIProvider` contract, so
321
+ * hosts can swap it for `openaiCompatibleProvider` without changing call sites.
322
+ *
323
+ * Cancellation contract matches `openaiCompatibleProvider`:
324
+ * - a non-2xx response yields `{ type: 'error' }` (never throws out of the iterator);
325
+ * - an aborted `req.signal` ends the stream **silently**;
326
+ * - closing the iterator early aborts the in-flight `fetch` so no tokens are wasted.
327
+ */
328
+
329
+ interface ClaudeConfig {
330
+ /** Base URL of the Anthropic (or compatible) endpoint. Defaults to `https://api.anthropic.com`. */
331
+ baseUrl?: string;
332
+ auth: Auth;
333
+ model: string;
334
+ /**
335
+ * Required by the Messages API. Defaults to 4096 — omit it and every call
336
+ * would fail with a 400.
337
+ */
338
+ maxTokens?: number;
339
+ /** Prepended to every call's system prompt so hosts don't repeat it. */
340
+ systemPrompt?: string;
341
+ }
342
+ declare function claudeProvider(config: ClaudeConfig): AIProvider;
343
+
344
+ export { type AIStreamFn as A, type CitationPort as C, type HttpKeyStorageUrls as H, type ImageUploadHandler as I, type KeyStorage as K, LOCAL_STORAGE_KEY as L, type OpenAICompatibleConfig as O, type StreamEvent as S, type AIDraftFn as a, type AIAction as b, type AIActionRequest as c, type AICompleteRequest as d, type AIConfig as e, type AIDraftCitation as f, type AIDraftEvent as g, type AIProvider as h, type AIProviderFactory as i, type Auth as j, CONTEXT_CHAR_CAP as k, type ClaudeConfig as l, type CslDate as m, type CslItemData as n, type CslName as o, type ImageUploadResult as p, buildAIPrompt as q, claudeProvider as r, httpKeyStorage as s, localStorageKeyStorage as t, memoryKeyStorage as u, openaiCompatibleProvider as v, toAIStreamFn as w, trimContextAfter as x, trimContextBefore as y };
@@ -310,4 +310,35 @@ interface OpenAICompatibleConfig {
310
310
  }
311
311
  declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
312
312
 
313
- export { type AIStreamFn as A, type CitationPort as C, type HttpKeyStorageUrls as H, type ImageUploadHandler as I, type KeyStorage as K, LOCAL_STORAGE_KEY as L, type OpenAICompatibleConfig as O, type StreamEvent as S, type AIDraftFn as a, type AIAction as b, type AIActionRequest as c, type AICompleteRequest as d, type AIConfig as e, type AIDraftCitation as f, type AIDraftEvent as g, type AIProvider as h, type AIProviderFactory as i, type Auth as j, CONTEXT_CHAR_CAP as k, type CslDate as l, type CslItemData as m, type CslName as n, type ImageUploadResult as o, buildAIPrompt as p, httpKeyStorage as q, localStorageKeyStorage as r, memoryKeyStorage as s, openaiCompatibleProvider as t, toAIStreamFn as u, trimContextAfter as v, trimContextBefore as w };
313
+ /**
314
+ * Native Anthropic Claude `AIProvider` (per-user BYOK — issue #118,
315
+ * `docs/plans/PER_USER_BYOK.md` §3.4).
316
+ *
317
+ * Anthropic's Messages API is not OpenAI-shaped: different endpoint
318
+ * (`/v1/messages`), different auth (`x-api-key`), a **required** `max_tokens`, a
319
+ * top-level `system` parameter (not a system message), and its own SSE event
320
+ * names. This provider maps all of that onto the same `AIProvider` contract, so
321
+ * hosts can swap it for `openaiCompatibleProvider` without changing call sites.
322
+ *
323
+ * Cancellation contract matches `openaiCompatibleProvider`:
324
+ * - a non-2xx response yields `{ type: 'error' }` (never throws out of the iterator);
325
+ * - an aborted `req.signal` ends the stream **silently**;
326
+ * - closing the iterator early aborts the in-flight `fetch` so no tokens are wasted.
327
+ */
328
+
329
+ interface ClaudeConfig {
330
+ /** Base URL of the Anthropic (or compatible) endpoint. Defaults to `https://api.anthropic.com`. */
331
+ baseUrl?: string;
332
+ auth: Auth;
333
+ model: string;
334
+ /**
335
+ * Required by the Messages API. Defaults to 4096 — omit it and every call
336
+ * would fail with a 400.
337
+ */
338
+ maxTokens?: number;
339
+ /** Prepended to every call's system prompt so hosts don't repeat it. */
340
+ systemPrompt?: string;
341
+ }
342
+ declare function claudeProvider(config: ClaudeConfig): AIProvider;
343
+
344
+ export { type AIStreamFn as A, type CitationPort as C, type HttpKeyStorageUrls as H, type ImageUploadHandler as I, type KeyStorage as K, LOCAL_STORAGE_KEY as L, type OpenAICompatibleConfig as O, type StreamEvent as S, type AIDraftFn as a, type AIAction as b, type AIActionRequest as c, type AICompleteRequest as d, type AIConfig as e, type AIDraftCitation as f, type AIDraftEvent as g, type AIProvider as h, type AIProviderFactory as i, type Auth as j, CONTEXT_CHAR_CAP as k, type ClaudeConfig as l, type CslDate as m, type CslItemData as n, type CslName as o, type ImageUploadResult as p, buildAIPrompt as q, claudeProvider as r, httpKeyStorage as s, localStorageKeyStorage as t, memoryKeyStorage as u, openaiCompatibleProvider as v, toAIStreamFn as w, trimContextAfter as x, trimContextBefore as y };
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BlockAttributesExtension: () => BlockAttributesExtension,
34
34
  CONTEXT_CHAR_CAP: () => CONTEXT_CHAR_CAP,
35
+ DOCUMENT_MODES: () => DOCUMENT_MODES,
35
36
  EditorContextExtension: () => EditorContextExtension,
36
37
  FontSizeExtension: () => FontSizeExtension,
37
38
  LOCAL_STORAGE_KEY: () => LOCAL_STORAGE_KEY,
@@ -41,6 +42,7 @@ __export(index_exports, {
41
42
  SearchAndReplaceExtension: () => SearchAndReplaceExtension,
42
43
  SubdocumentProvider: () => SubdocumentProvider,
43
44
  buildAIPrompt: () => buildAIPrompt,
45
+ claudeProvider: () => claudeProvider,
44
46
  clearSearch: () => clearSearch,
45
47
  collaborationExtensions: () => collaborationExtensions,
46
48
  collectExtensions: () => collectExtensions,
@@ -60,6 +62,7 @@ __export(index_exports, {
60
62
  replaceAll: () => replaceAll,
61
63
  replaceCurrent: () => replaceCurrent,
62
64
  resolveAction: () => resolveAction,
65
+ sanitizeInlineHTML: () => sanitizeInlineHTML,
63
66
  sanitizePastedHTML: () => sanitizePastedHTML,
64
67
  searchAndReplaceKey: () => searchAndReplaceKey,
65
68
  searchNext: () => searchNext,
@@ -786,13 +789,13 @@ function replaceAll(editor, replacement) {
786
789
  return s.matches.length;
787
790
  }
788
791
 
789
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
792
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
790
793
  var import_core5 = require("@tiptap/core");
791
794
  var import_state4 = require("@tiptap/pm/state");
792
795
  var import_transform = require("@tiptap/pm/transform");
793
796
  var import_view3 = require("@tiptap/pm/view");
794
797
 
795
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/utils.js
798
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/utils.js
796
799
  var updateCssVariables = (targetNode, config) => {
797
800
  const cssVariables = {
798
801
  "rm-page-height": `${config.pageHeight}px`,
@@ -966,7 +969,7 @@ var footerClickEvent = (pageNumber, onFooterClick) => {
966
969
  };
967
970
  };
968
971
 
969
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
972
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
970
973
  var page_count_meta_key = "PAGE_COUNT_META_KEY";
971
974
  var key = new import_state4.PluginKey("brDecoration");
972
975
  function buildDecorations(doc) {
@@ -1473,12 +1476,6 @@ var calculatePageCount = (view, pageOptions, headerHeight = 0, footerHeight = 0)
1473
1476
  const lastPageGap = lastElementRect.bottom - lastPageBreakRect.bottom;
1474
1477
  if (lastPageGap > 0) {
1475
1478
  const addPage = Math.ceil(lastPageGap / pageContentAreaHeight);
1476
- if (lastElementRect.height > pageContentAreaHeight) {
1477
- const isSplittableTable = (lastElementOfEditor === null || lastElementOfEditor === void 0 ? void 0 : lastElementOfEditor.tagName) === "TABLE" && lastElementOfEditor.hasAttribute("data-tps-splittable");
1478
- if (!isSplittableTable) {
1479
- return currentPageCount;
1480
- }
1481
- }
1482
1479
  const prev = pageGapTracker.get(editorDom);
1483
1480
  if (prev && prev.pages < currentPageCount && lastPageGap > prev.gap - 2) {
1484
1481
  return currentPageCount;
@@ -1629,7 +1626,7 @@ function createDecoration(pageOptions, headerHeightMap, footerHeightMap) {
1629
1626
  return [pageWidget, firstHeaderWidget];
1630
1627
  }
1631
1628
 
1632
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/constants.js
1629
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/constants.js
1633
1630
  var A4_PAGE_SIZE = getPageSize(1123, 794, 95, 95, 76, 76);
1634
1631
  var A3_PAGE_SIZE = getPageSize(1591, 1123, 95, 95, 76, 76);
1635
1632
  var A5_PAGE_SIZE = getPageSize(794, 419, 76, 76, 57, 57);
@@ -2112,6 +2109,86 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
2112
2109
  });
2113
2110
  }
2114
2111
 
2112
+ // src/sanitizeInlineHTML.ts
2113
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set(["b", "strong", "i", "em", "u", "br"]);
2114
+ var DROP_TAGS = /* @__PURE__ */ new Set([
2115
+ "script",
2116
+ "style",
2117
+ "template",
2118
+ "noscript",
2119
+ "iframe",
2120
+ "frame",
2121
+ "frameset",
2122
+ "object",
2123
+ "embed",
2124
+ "applet",
2125
+ "svg",
2126
+ "math",
2127
+ "link",
2128
+ "meta",
2129
+ "base",
2130
+ "title",
2131
+ "form",
2132
+ "input",
2133
+ "textarea",
2134
+ "select",
2135
+ "button",
2136
+ "canvas",
2137
+ "audio",
2138
+ "video",
2139
+ "source",
2140
+ "track"
2141
+ ]);
2142
+ function unwrap2(el) {
2143
+ const parent = el.parentNode;
2144
+ if (!parent) return;
2145
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
2146
+ parent.removeChild(el);
2147
+ }
2148
+ function stripAttributes(el) {
2149
+ for (const attr of Array.from(el.attributes)) el.removeAttribute(attr.name);
2150
+ }
2151
+ function removeComments2(root) {
2152
+ const doc = root.ownerDocument;
2153
+ if (!doc) return;
2154
+ const walker = doc.createTreeWalker(
2155
+ root,
2156
+ 128
2157
+ /* NodeFilter.SHOW_COMMENT */
2158
+ );
2159
+ const comments = [];
2160
+ let node = walker.nextNode();
2161
+ while (node) {
2162
+ comments.push(node);
2163
+ node = walker.nextNode();
2164
+ }
2165
+ for (const comment of comments) comment.parentNode?.removeChild(comment);
2166
+ }
2167
+ function sanitizeInlineHTML(html) {
2168
+ if (!html || typeof html !== "string") return "";
2169
+ if (!html.includes("<")) return html;
2170
+ try {
2171
+ const doc = new DOMParser().parseFromString(html, "text/html");
2172
+ removeComments2(doc.body);
2173
+ for (const tag of DROP_TAGS) {
2174
+ for (const el of Array.from(doc.body.getElementsByTagName(tag))) {
2175
+ el.parentNode?.removeChild(el);
2176
+ }
2177
+ }
2178
+ const walk = (node) => {
2179
+ if (node.nodeType !== 1) return;
2180
+ for (const child of Array.from(node.childNodes)) walk(child);
2181
+ const el = node;
2182
+ if (ALLOWED_TAGS.has(el.tagName.toLowerCase())) stripAttributes(el);
2183
+ else unwrap2(el);
2184
+ };
2185
+ for (const child of Array.from(doc.body.childNodes)) walk(child);
2186
+ return doc.body.innerHTML;
2187
+ } catch {
2188
+ return "";
2189
+ }
2190
+ }
2191
+
2115
2192
  // src/FontSize.ts
2116
2193
  var import_core7 = require("@tiptap/core");
2117
2194
  function mergeFontSize(textStyleType, existing, fontSize) {
@@ -2222,6 +2299,9 @@ var FontSizeExtension = import_core7.Extension.create({
2222
2299
  }
2223
2300
  });
2224
2301
 
2302
+ // src/modes.ts
2303
+ var DOCUMENT_MODES = ["editing", "suggesting", "viewing"];
2304
+
2225
2305
  // src/SubdocumentProvider.ts
2226
2306
  var import_awareness2 = require("y-protocols/awareness");
2227
2307
  var import_y_websocket2 = require("y-websocket");
@@ -2591,10 +2671,155 @@ function openaiCompatibleProvider(config) {
2591
2671
  }
2592
2672
  };
2593
2673
  }
2674
+
2675
+ // src/ai/claudeProvider.ts
2676
+ var DEFAULT_BASE_URL = "https://api.anthropic.com";
2677
+ var ANTHROPIC_VERSION = "2023-06-01";
2678
+ var DEFAULT_MAX_TOKENS = 4096;
2679
+ function authHeaders2(auth) {
2680
+ switch (auth.type) {
2681
+ case "bearer":
2682
+ return { "x-api-key": auth.apiKey };
2683
+ case "header":
2684
+ return { [auth.name]: auth.value };
2685
+ case "none":
2686
+ return {};
2687
+ }
2688
+ }
2689
+ function toError2(err) {
2690
+ return err instanceof Error ? err : new Error(String(err));
2691
+ }
2692
+ function claudeProvider(config) {
2693
+ const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
2694
+ const maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
2695
+ return {
2696
+ async *complete(req) {
2697
+ const controller = new AbortController();
2698
+ const onAbort = () => controller.abort();
2699
+ const callerSignal = req.signal;
2700
+ if (callerSignal) {
2701
+ if (callerSignal.aborted) controller.abort();
2702
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
2703
+ }
2704
+ try {
2705
+ const system = [config.systemPrompt, req.system].filter(Boolean).join("\n\n");
2706
+ const body = {
2707
+ model: config.model,
2708
+ max_tokens: maxTokens,
2709
+ messages: [{ role: "user", content: req.prompt }],
2710
+ stream: true
2711
+ };
2712
+ if (system) body.system = system;
2713
+ let res;
2714
+ try {
2715
+ res = await fetch(`${baseUrl}/v1/messages`, {
2716
+ method: "POST",
2717
+ headers: {
2718
+ "Content-Type": "application/json",
2719
+ "anthropic-version": ANTHROPIC_VERSION,
2720
+ // Anthropic only allows browser calls with this explicit opt-in.
2721
+ "anthropic-dangerous-direct-browser-access": "true",
2722
+ ...authHeaders2(config.auth)
2723
+ },
2724
+ body: JSON.stringify(body),
2725
+ signal: controller.signal
2726
+ });
2727
+ } catch (err) {
2728
+ if (controller.signal.aborted) return;
2729
+ yield { type: "error", error: toError2(err) };
2730
+ return;
2731
+ }
2732
+ if (!res.ok) {
2733
+ const detail = await res.text().catch(() => "");
2734
+ yield {
2735
+ type: "error",
2736
+ error: new Error(
2737
+ `Claude provider responded ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
2738
+ )
2739
+ };
2740
+ return;
2741
+ }
2742
+ if (!res.body) {
2743
+ yield { type: "error", error: new Error("Claude provider response has no body") };
2744
+ return;
2745
+ }
2746
+ const reader = res.body.getReader();
2747
+ const decoder = new TextDecoder();
2748
+ let buffer = "";
2749
+ let sawDone = false;
2750
+ let stopReason;
2751
+ try {
2752
+ for (; ; ) {
2753
+ const { done, value } = await reader.read();
2754
+ if (done) break;
2755
+ buffer += decoder.decode(value, { stream: true });
2756
+ let sep;
2757
+ while ((sep = buffer.indexOf("\n\n")) >= 0) {
2758
+ const block = buffer.slice(0, sep);
2759
+ buffer = buffer.slice(sep + 2);
2760
+ for (const line of block.split("\n")) {
2761
+ if (!line.startsWith("data:")) continue;
2762
+ const data = line.slice("data:".length).trim();
2763
+ if (!data) continue;
2764
+ let json;
2765
+ try {
2766
+ json = JSON.parse(data);
2767
+ } catch {
2768
+ continue;
2769
+ }
2770
+ switch (json.type) {
2771
+ case "content_block_delta": {
2772
+ const text = json.delta?.text;
2773
+ if (text) yield { type: "delta", text };
2774
+ break;
2775
+ }
2776
+ case "message_delta": {
2777
+ if (json.delta?.stop_reason) stopReason = json.delta.stop_reason;
2778
+ break;
2779
+ }
2780
+ case "message_stop": {
2781
+ sawDone = true;
2782
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
2783
+ return;
2784
+ }
2785
+ case "error": {
2786
+ yield {
2787
+ type: "error",
2788
+ error: new Error(
2789
+ json.error?.message ? `Claude provider error: ${json.error.message}` : "Claude provider stream error"
2790
+ )
2791
+ };
2792
+ return;
2793
+ }
2794
+ default:
2795
+ break;
2796
+ }
2797
+ }
2798
+ }
2799
+ }
2800
+ } catch (err) {
2801
+ if (!controller.signal.aborted) {
2802
+ yield { type: "error", error: toError2(err) };
2803
+ return;
2804
+ }
2805
+ } finally {
2806
+ reader.releaseLock();
2807
+ }
2808
+ if (!sawDone && !controller.signal.aborted) {
2809
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
2810
+ }
2811
+ } finally {
2812
+ callerSignal?.removeEventListener("abort", onAbort);
2813
+ controller.abort();
2814
+ }
2815
+ }
2816
+ };
2817
+ }
2594
2818
  // Annotate the CommonJS export names for ESM import in node:
2595
2819
  0 && (module.exports = {
2596
2820
  BlockAttributesExtension,
2597
2821
  CONTEXT_CHAR_CAP,
2822
+ DOCUMENT_MODES,
2598
2823
  EditorContextExtension,
2599
2824
  FontSizeExtension,
2600
2825
  LOCAL_STORAGE_KEY,
@@ -2604,6 +2829,7 @@ function openaiCompatibleProvider(config) {
2604
2829
  SearchAndReplaceExtension,
2605
2830
  SubdocumentProvider,
2606
2831
  buildAIPrompt,
2832
+ claudeProvider,
2607
2833
  clearSearch,
2608
2834
  collaborationExtensions,
2609
2835
  collectExtensions,
@@ -2623,6 +2849,7 @@ function openaiCompatibleProvider(config) {
2623
2849
  replaceAll,
2624
2850
  replaceCurrent,
2625
2851
  resolveAction,
2852
+ sanitizeInlineHTML,
2626
2853
  sanitizePastedHTML,
2627
2854
  searchAndReplaceKey,
2628
2855
  searchNext,
package/dist/index.d.cts CHANGED
@@ -4,8 +4,8 @@ import { IndexeddbPersistence } from 'y-indexeddb';
4
4
  import { WebrtcProvider } from 'y-webrtc';
5
5
  import { WebsocketProvider } from 'y-websocket';
6
6
  import * as Y from 'yjs';
7
- import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-BsIFJXPk.cjs';
8
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as CslDate, m as CslItemData, n as CslName, H as HttpKeyStorageUrls, o as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, p as buildAIPrompt, q as httpKeyStorage, r as localStorageKeyStorage, s as memoryKeyStorage, t as openaiCompatibleProvider, u as toAIStreamFn, v as trimContextAfter, w as trimContextBefore } from './index-BsIFJXPk.cjs';
7
+ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-3B5ckTRa.cjs';
8
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, p as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from './index-3B5ckTRa.cjs';
9
9
  import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
10
  export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
11
11
  import { PluginKey } from '@tiptap/pm/state';
@@ -250,6 +250,8 @@ interface DocsEditor {
250
250
  }
251
251
  declare function createEditor(options?: EditorOptions): DocsEditor;
252
252
 
253
+ declare function sanitizeInlineHTML(html: string): string;
254
+
253
255
  interface BlockAttributesOptions {
254
256
  pageMap?: () => Map<number, {
255
257
  page: number;
@@ -333,6 +335,21 @@ declare function replaceCurrent(editor: Editor, replacement: string): boolean;
333
335
  /** Replace every match in one transaction (applied back-to-front). */
334
336
  declare function replaceAll(editor: Editor, replacement: string): number;
335
337
 
338
+ /**
339
+ * Document editing modes (issues #27 / #28 — suggesting mode + track changes).
340
+ *
341
+ * - `editing` — normal editing (default).
342
+ * - `suggesting` — edits are recorded as suggestion marks instead of applied.
343
+ * - `viewing` — read-only; the editor is not editable.
344
+ *
345
+ * Hosts own *authorisation* (who may suggest / accept). The library only
346
+ * provides the mode behaviour and the commands; see
347
+ * `docs/plans/suggesting-mode-and-track-changes.md`.
348
+ */
349
+ type DocumentMode = 'editing' | 'suggesting' | 'viewing';
350
+ /** Every document mode, in menu order. */
351
+ declare const DOCUMENT_MODES: readonly DocumentMode[];
352
+
336
353
  interface SubdocState {
337
354
  id: string;
338
355
  doc: Y.Doc;
@@ -395,4 +412,4 @@ declare class SubdocumentProvider {
395
412
  destroy(): void;
396
413
  }
397
414
 
398
- export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
415
+ export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, DOCUMENT_MODES, type DocsEditor, type DocsEditorPlugin, type DocumentMode, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizeInlineHTML, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ import { IndexeddbPersistence } from 'y-indexeddb';
4
4
  import { WebrtcProvider } from 'y-webrtc';
5
5
  import { WebsocketProvider } from 'y-websocket';
6
6
  import * as Y from 'yjs';
7
- import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-BsIFJXPk.js';
8
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as CslDate, m as CslItemData, n as CslName, H as HttpKeyStorageUrls, o as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, p as buildAIPrompt, q as httpKeyStorage, r as localStorageKeyStorage, s as memoryKeyStorage, t as openaiCompatibleProvider, u as toAIStreamFn, v as trimContextAfter, w as trimContextBefore } from './index-BsIFJXPk.js';
7
+ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-3B5ckTRa.js';
8
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, p as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from './index-3B5ckTRa.js';
9
9
  import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
10
  export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
11
11
  import { PluginKey } from '@tiptap/pm/state';
@@ -250,6 +250,8 @@ interface DocsEditor {
250
250
  }
251
251
  declare function createEditor(options?: EditorOptions): DocsEditor;
252
252
 
253
+ declare function sanitizeInlineHTML(html: string): string;
254
+
253
255
  interface BlockAttributesOptions {
254
256
  pageMap?: () => Map<number, {
255
257
  page: number;
@@ -333,6 +335,21 @@ declare function replaceCurrent(editor: Editor, replacement: string): boolean;
333
335
  /** Replace every match in one transaction (applied back-to-front). */
334
336
  declare function replaceAll(editor: Editor, replacement: string): number;
335
337
 
338
+ /**
339
+ * Document editing modes (issues #27 / #28 — suggesting mode + track changes).
340
+ *
341
+ * - `editing` — normal editing (default).
342
+ * - `suggesting` — edits are recorded as suggestion marks instead of applied.
343
+ * - `viewing` — read-only; the editor is not editable.
344
+ *
345
+ * Hosts own *authorisation* (who may suggest / accept). The library only
346
+ * provides the mode behaviour and the commands; see
347
+ * `docs/plans/suggesting-mode-and-track-changes.md`.
348
+ */
349
+ type DocumentMode = 'editing' | 'suggesting' | 'viewing';
350
+ /** Every document mode, in menu order. */
351
+ declare const DOCUMENT_MODES: readonly DocumentMode[];
352
+
336
353
  interface SubdocState {
337
354
  id: string;
338
355
  doc: Y.Doc;
@@ -395,4 +412,4 @@ declare class SubdocumentProvider {
395
412
  destroy(): void;
396
413
  }
397
414
 
398
- export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
415
+ export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, DOCUMENT_MODES, type DocsEditor, type DocsEditorPlugin, type DocumentMode, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizeInlineHTML, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  CONTEXT_CHAR_CAP,
3
3
  LOCAL_STORAGE_KEY,
4
4
  buildAIPrompt,
5
+ claudeProvider,
5
6
  httpKeyStorage,
6
7
  localStorageKeyStorage,
7
8
  memoryKeyStorage,
@@ -9,7 +10,7 @@ import {
9
10
  toAIStreamFn,
10
11
  trimContextAfter,
11
12
  trimContextBefore
12
- } from "./chunk-HMOWKFPE.js";
13
+ } from "./chunk-6RIBBYIP.js";
13
14
 
14
15
  // src/Editor.ts
15
16
  import { Editor as TiptapEditor } from "@tiptap/core";
@@ -726,13 +727,13 @@ function replaceAll(editor, replacement) {
726
727
  return s.matches.length;
727
728
  }
728
729
 
729
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
730
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
730
731
  import { Extension as Extension5 } from "@tiptap/core";
731
732
  import { Plugin as Plugin4, PluginKey as PluginKey4 } from "@tiptap/pm/state";
732
733
  import { ReplaceStep, ReplaceAroundStep, AddMarkStep, RemoveMarkStep, RemoveNodeMarkStep, AttrStep } from "@tiptap/pm/transform";
733
734
  import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "@tiptap/pm/view";
734
735
 
735
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/utils.js
736
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/utils.js
736
737
  var updateCssVariables = (targetNode, config) => {
737
738
  const cssVariables = {
738
739
  "rm-page-height": `${config.pageHeight}px`,
@@ -906,7 +907,7 @@ var footerClickEvent = (pageNumber, onFooterClick) => {
906
907
  };
907
908
  };
908
909
 
909
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
910
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
910
911
  var page_count_meta_key = "PAGE_COUNT_META_KEY";
911
912
  var key = new PluginKey4("brDecoration");
912
913
  function buildDecorations(doc) {
@@ -1413,12 +1414,6 @@ var calculatePageCount = (view, pageOptions, headerHeight = 0, footerHeight = 0)
1413
1414
  const lastPageGap = lastElementRect.bottom - lastPageBreakRect.bottom;
1414
1415
  if (lastPageGap > 0) {
1415
1416
  const addPage = Math.ceil(lastPageGap / pageContentAreaHeight);
1416
- if (lastElementRect.height > pageContentAreaHeight) {
1417
- const isSplittableTable = (lastElementOfEditor === null || lastElementOfEditor === void 0 ? void 0 : lastElementOfEditor.tagName) === "TABLE" && lastElementOfEditor.hasAttribute("data-tps-splittable");
1418
- if (!isSplittableTable) {
1419
- return currentPageCount;
1420
- }
1421
- }
1422
1417
  const prev = pageGapTracker.get(editorDom);
1423
1418
  if (prev && prev.pages < currentPageCount && lastPageGap > prev.gap - 2) {
1424
1419
  return currentPageCount;
@@ -1569,7 +1564,7 @@ function createDecoration(pageOptions, headerHeightMap, footerHeightMap) {
1569
1564
  return [pageWidget, firstHeaderWidget];
1570
1565
  }
1571
1566
 
1572
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/constants.js
1567
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/constants.js
1573
1568
  var A4_PAGE_SIZE = getPageSize(1123, 794, 95, 95, 76, 76);
1574
1569
  var A3_PAGE_SIZE = getPageSize(1591, 1123, 95, 95, 76, 76);
1575
1570
  var A5_PAGE_SIZE = getPageSize(794, 419, 76, 76, 57, 57);
@@ -2052,6 +2047,86 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
2052
2047
  });
2053
2048
  }
2054
2049
 
2050
+ // src/sanitizeInlineHTML.ts
2051
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set(["b", "strong", "i", "em", "u", "br"]);
2052
+ var DROP_TAGS = /* @__PURE__ */ new Set([
2053
+ "script",
2054
+ "style",
2055
+ "template",
2056
+ "noscript",
2057
+ "iframe",
2058
+ "frame",
2059
+ "frameset",
2060
+ "object",
2061
+ "embed",
2062
+ "applet",
2063
+ "svg",
2064
+ "math",
2065
+ "link",
2066
+ "meta",
2067
+ "base",
2068
+ "title",
2069
+ "form",
2070
+ "input",
2071
+ "textarea",
2072
+ "select",
2073
+ "button",
2074
+ "canvas",
2075
+ "audio",
2076
+ "video",
2077
+ "source",
2078
+ "track"
2079
+ ]);
2080
+ function unwrap2(el) {
2081
+ const parent = el.parentNode;
2082
+ if (!parent) return;
2083
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
2084
+ parent.removeChild(el);
2085
+ }
2086
+ function stripAttributes(el) {
2087
+ for (const attr of Array.from(el.attributes)) el.removeAttribute(attr.name);
2088
+ }
2089
+ function removeComments2(root) {
2090
+ const doc = root.ownerDocument;
2091
+ if (!doc) return;
2092
+ const walker = doc.createTreeWalker(
2093
+ root,
2094
+ 128
2095
+ /* NodeFilter.SHOW_COMMENT */
2096
+ );
2097
+ const comments = [];
2098
+ let node = walker.nextNode();
2099
+ while (node) {
2100
+ comments.push(node);
2101
+ node = walker.nextNode();
2102
+ }
2103
+ for (const comment of comments) comment.parentNode?.removeChild(comment);
2104
+ }
2105
+ function sanitizeInlineHTML(html) {
2106
+ if (!html || typeof html !== "string") return "";
2107
+ if (!html.includes("<")) return html;
2108
+ try {
2109
+ const doc = new DOMParser().parseFromString(html, "text/html");
2110
+ removeComments2(doc.body);
2111
+ for (const tag of DROP_TAGS) {
2112
+ for (const el of Array.from(doc.body.getElementsByTagName(tag))) {
2113
+ el.parentNode?.removeChild(el);
2114
+ }
2115
+ }
2116
+ const walk = (node) => {
2117
+ if (node.nodeType !== 1) return;
2118
+ for (const child of Array.from(node.childNodes)) walk(child);
2119
+ const el = node;
2120
+ if (ALLOWED_TAGS.has(el.tagName.toLowerCase())) stripAttributes(el);
2121
+ else unwrap2(el);
2122
+ };
2123
+ for (const child of Array.from(doc.body.childNodes)) walk(child);
2124
+ return doc.body.innerHTML;
2125
+ } catch {
2126
+ return "";
2127
+ }
2128
+ }
2129
+
2055
2130
  // src/FontSize.ts
2056
2131
  import { Extension as Extension6 } from "@tiptap/core";
2057
2132
  function mergeFontSize(textStyleType, existing, fontSize) {
@@ -2162,6 +2237,9 @@ var FontSizeExtension = Extension6.create({
2162
2237
  }
2163
2238
  });
2164
2239
 
2240
+ // src/modes.ts
2241
+ var DOCUMENT_MODES = ["editing", "suggesting", "viewing"];
2242
+
2165
2243
  // src/SubdocumentProvider.ts
2166
2244
  import { Awareness as AwarenessClass } from "y-protocols/awareness";
2167
2245
  import { WebsocketProvider as WebsocketProvider2 } from "y-websocket";
@@ -2271,6 +2349,7 @@ var SubdocumentProvider = class {
2271
2349
  export {
2272
2350
  BlockAttributesExtension,
2273
2351
  CONTEXT_CHAR_CAP,
2352
+ DOCUMENT_MODES,
2274
2353
  EditorContextExtension,
2275
2354
  FontSizeExtension,
2276
2355
  LOCAL_STORAGE_KEY,
@@ -2280,6 +2359,7 @@ export {
2280
2359
  SearchAndReplaceExtension,
2281
2360
  SubdocumentProvider,
2282
2361
  buildAIPrompt,
2362
+ claudeProvider,
2283
2363
  clearSearch,
2284
2364
  collaborationExtensions,
2285
2365
  collectExtensions,
@@ -2299,6 +2379,7 @@ export {
2299
2379
  replaceAll,
2300
2380
  replaceCurrent,
2301
2381
  resolveAction,
2382
+ sanitizeInlineHTML,
2302
2383
  sanitizePastedHTML,
2303
2384
  searchAndReplaceKey,
2304
2385
  searchNext,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kedataindo/docflow-core",
3
3
  "license": "Apache-2.0",
4
- "version": "0.0.84",
4
+ "version": "0.0.86",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",