@kedataindo/docflow-core 0.0.85 → 0.0.87

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,7 +32,9 @@ 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,
37
+ FontFamilyExtension: () => FontFamilyExtension,
36
38
  FontSizeExtension: () => FontSizeExtension,
37
39
  LOCAL_STORAGE_KEY: () => LOCAL_STORAGE_KEY,
38
40
  PAGE_SIZES: () => PAGE_SIZES,
@@ -41,6 +43,7 @@ __export(index_exports, {
41
43
  SearchAndReplaceExtension: () => SearchAndReplaceExtension,
42
44
  SubdocumentProvider: () => SubdocumentProvider,
43
45
  buildAIPrompt: () => buildAIPrompt,
46
+ claudeProvider: () => claudeProvider,
44
47
  clearSearch: () => clearSearch,
45
48
  collaborationExtensions: () => collaborationExtensions,
46
49
  collectExtensions: () => collectExtensions,
@@ -787,13 +790,13 @@ function replaceAll(editor, replacement) {
787
790
  return s.matches.length;
788
791
  }
789
792
 
790
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
793
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
791
794
  var import_core5 = require("@tiptap/core");
792
795
  var import_state4 = require("@tiptap/pm/state");
793
796
  var import_transform = require("@tiptap/pm/transform");
794
797
  var import_view3 = require("@tiptap/pm/view");
795
798
 
796
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/utils.js
799
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/utils.js
797
800
  var updateCssVariables = (targetNode, config) => {
798
801
  const cssVariables = {
799
802
  "rm-page-height": `${config.pageHeight}px`,
@@ -967,7 +970,7 @@ var footerClickEvent = (pageNumber, onFooterClick) => {
967
970
  };
968
971
  };
969
972
 
970
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
973
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
971
974
  var page_count_meta_key = "PAGE_COUNT_META_KEY";
972
975
  var key = new import_state4.PluginKey("brDecoration");
973
976
  function buildDecorations(doc) {
@@ -1474,12 +1477,6 @@ var calculatePageCount = (view, pageOptions, headerHeight = 0, footerHeight = 0)
1474
1477
  const lastPageGap = lastElementRect.bottom - lastPageBreakRect.bottom;
1475
1478
  if (lastPageGap > 0) {
1476
1479
  const addPage = Math.ceil(lastPageGap / pageContentAreaHeight);
1477
- if (lastElementRect.height > pageContentAreaHeight) {
1478
- const isSplittableTable = (lastElementOfEditor === null || lastElementOfEditor === void 0 ? void 0 : lastElementOfEditor.tagName) === "TABLE" && lastElementOfEditor.hasAttribute("data-tps-splittable");
1479
- if (!isSplittableTable) {
1480
- return currentPageCount;
1481
- }
1482
- }
1483
1480
  const prev = pageGapTracker.get(editorDom);
1484
1481
  if (prev && prev.pages < currentPageCount && lastPageGap > prev.gap - 2) {
1485
1482
  return currentPageCount;
@@ -1630,7 +1627,7 @@ function createDecoration(pageOptions, headerHeightMap, footerHeightMap) {
1630
1627
  return [pageWidget, firstHeaderWidget];
1631
1628
  }
1632
1629
 
1633
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/constants.js
1630
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/constants.js
1634
1631
  var A4_PAGE_SIZE = getPageSize(1123, 794, 95, 95, 76, 76);
1635
1632
  var A3_PAGE_SIZE = getPageSize(1591, 1123, 95, 95, 76, 76);
1636
1633
  var A5_PAGE_SIZE = getPageSize(794, 419, 76, 76, 57, 57);
@@ -2227,7 +2224,6 @@ var FontSizeExtension = import_core7.Extension.create({
2227
2224
  addCommands() {
2228
2225
  return {
2229
2226
  setFontSize: (fontSize) => ({ tr, state, dispatch }) => {
2230
- console.log("[FontSizeExt] setFontSize called, fontSize:", fontSize, "empty:", state.selection.empty);
2231
2227
  const textStyleType = state.schema.marks.textStyle;
2232
2228
  const { from, to, empty } = state.selection;
2233
2229
  if (empty) {
@@ -2303,6 +2299,114 @@ var FontSizeExtension = import_core7.Extension.create({
2303
2299
  }
2304
2300
  });
2305
2301
 
2302
+ // src/FontFamily.ts
2303
+ var import_core8 = require("@tiptap/core");
2304
+ function mergeFontFamily(textStyleType, existing, fontFamily) {
2305
+ if (fontFamily === null) {
2306
+ const { fontFamily: _removed, ...rest } = existing?.attrs ?? {};
2307
+ return textStyleType.create(rest);
2308
+ }
2309
+ return textStyleType.create({ ...existing?.attrs, fontFamily });
2310
+ }
2311
+ function hasAttrs2(mark) {
2312
+ return Object.values(mark.attrs).some((v) => v != null);
2313
+ }
2314
+ var FontFamilyExtension = import_core8.Extension.create({
2315
+ name: "fontFamily",
2316
+ addGlobalAttributes() {
2317
+ return [
2318
+ {
2319
+ types: ["textStyle"],
2320
+ attributes: {
2321
+ fontFamily: {
2322
+ default: null,
2323
+ parseHTML: (el) => el.style.fontFamily || null,
2324
+ renderHTML: (attrs) => {
2325
+ if (!attrs.fontFamily) return {};
2326
+ return { style: `font-family: ${attrs.fontFamily}` };
2327
+ }
2328
+ }
2329
+ }
2330
+ }
2331
+ ];
2332
+ },
2333
+ addCommands() {
2334
+ return {
2335
+ setFontFamily: (fontFamily) => ({ tr, state, dispatch }) => {
2336
+ const textStyleType = state.schema.marks.textStyle;
2337
+ const { from, to, empty } = state.selection;
2338
+ if (empty) {
2339
+ const stored = tr.storedMarks ? [...tr.storedMarks] : [...state.storedMarks ?? []];
2340
+ const idx = stored.findIndex((m) => m.type === textStyleType);
2341
+ const existing = idx >= 0 ? stored[idx] : void 0;
2342
+ const merged = mergeFontFamily(textStyleType, existing, fontFamily);
2343
+ if (idx >= 0) {
2344
+ if (hasAttrs2(merged)) {
2345
+ stored[idx] = merged;
2346
+ } else {
2347
+ stored.splice(idx, 1);
2348
+ }
2349
+ } else {
2350
+ stored.push(merged);
2351
+ }
2352
+ tr.setStoredMarks(stored);
2353
+ } else {
2354
+ state.doc.nodesBetween(from, to, (node, pos) => {
2355
+ if (!node.isText) return;
2356
+ const fromPos = Math.max(pos, from);
2357
+ const toPos = Math.min(pos + node.nodeSize, to);
2358
+ const existingTextStyle = node.marks.find((m) => m.type === textStyleType);
2359
+ const merged = mergeFontFamily(textStyleType, existingTextStyle, fontFamily);
2360
+ if (hasAttrs2(merged)) {
2361
+ tr.addMark(fromPos, toPos, merged);
2362
+ } else if (existingTextStyle) {
2363
+ tr.removeMark(fromPos, toPos, existingTextStyle);
2364
+ }
2365
+ });
2366
+ }
2367
+ if (dispatch) dispatch(tr);
2368
+ return true;
2369
+ },
2370
+ unsetFontFamily: () => ({ tr, state, dispatch }) => {
2371
+ const textStyleType = state.schema.marks.textStyle;
2372
+ const { from, to, empty } = state.selection;
2373
+ if (empty) {
2374
+ const stored = tr.storedMarks ? [...tr.storedMarks] : [...state.storedMarks ?? []];
2375
+ const idx = stored.findIndex((m) => m.type === textStyleType);
2376
+ if (idx >= 0) {
2377
+ const merged = mergeFontFamily(textStyleType, stored[idx], null);
2378
+ if (hasAttrs2(merged)) {
2379
+ stored[idx] = merged;
2380
+ } else {
2381
+ stored.splice(idx, 1);
2382
+ }
2383
+ tr.setStoredMarks(stored);
2384
+ }
2385
+ } else {
2386
+ state.doc.nodesBetween(from, to, (node, pos) => {
2387
+ if (!node.isText) return;
2388
+ const fromPos = Math.max(pos, from);
2389
+ const toPos = Math.min(pos + node.nodeSize, to);
2390
+ const existingTextStyle = node.marks.find((m) => m.type === textStyleType);
2391
+ if (!existingTextStyle) return;
2392
+ const merged = mergeFontFamily(textStyleType, existingTextStyle, null);
2393
+ if (hasAttrs2(merged)) {
2394
+ tr.addMark(fromPos, toPos, merged);
2395
+ } else {
2396
+ tr.removeMark(fromPos, toPos, existingTextStyle);
2397
+ }
2398
+ });
2399
+ }
2400
+ if (dispatch) dispatch(tr);
2401
+ return true;
2402
+ }
2403
+ };
2404
+ }
2405
+ });
2406
+
2407
+ // src/modes.ts
2408
+ var DOCUMENT_MODES = ["editing", "suggesting", "viewing"];
2409
+
2306
2410
  // src/SubdocumentProvider.ts
2307
2411
  var import_awareness2 = require("y-protocols/awareness");
2308
2412
  var import_y_websocket2 = require("y-websocket");
@@ -2672,11 +2776,157 @@ function openaiCompatibleProvider(config) {
2672
2776
  }
2673
2777
  };
2674
2778
  }
2779
+
2780
+ // src/ai/claudeProvider.ts
2781
+ var DEFAULT_BASE_URL = "https://api.anthropic.com";
2782
+ var ANTHROPIC_VERSION = "2023-06-01";
2783
+ var DEFAULT_MAX_TOKENS = 4096;
2784
+ function authHeaders2(auth) {
2785
+ switch (auth.type) {
2786
+ case "bearer":
2787
+ return { "x-api-key": auth.apiKey };
2788
+ case "header":
2789
+ return { [auth.name]: auth.value };
2790
+ case "none":
2791
+ return {};
2792
+ }
2793
+ }
2794
+ function toError2(err) {
2795
+ return err instanceof Error ? err : new Error(String(err));
2796
+ }
2797
+ function claudeProvider(config) {
2798
+ const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
2799
+ const maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
2800
+ return {
2801
+ async *complete(req) {
2802
+ const controller = new AbortController();
2803
+ const onAbort = () => controller.abort();
2804
+ const callerSignal = req.signal;
2805
+ if (callerSignal) {
2806
+ if (callerSignal.aborted) controller.abort();
2807
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
2808
+ }
2809
+ try {
2810
+ const system = [config.systemPrompt, req.system].filter(Boolean).join("\n\n");
2811
+ const body = {
2812
+ model: config.model,
2813
+ max_tokens: maxTokens,
2814
+ messages: [{ role: "user", content: req.prompt }],
2815
+ stream: true
2816
+ };
2817
+ if (system) body.system = system;
2818
+ let res;
2819
+ try {
2820
+ res = await fetch(`${baseUrl}/v1/messages`, {
2821
+ method: "POST",
2822
+ headers: {
2823
+ "Content-Type": "application/json",
2824
+ "anthropic-version": ANTHROPIC_VERSION,
2825
+ // Anthropic only allows browser calls with this explicit opt-in.
2826
+ "anthropic-dangerous-direct-browser-access": "true",
2827
+ ...authHeaders2(config.auth)
2828
+ },
2829
+ body: JSON.stringify(body),
2830
+ signal: controller.signal
2831
+ });
2832
+ } catch (err) {
2833
+ if (controller.signal.aborted) return;
2834
+ yield { type: "error", error: toError2(err) };
2835
+ return;
2836
+ }
2837
+ if (!res.ok) {
2838
+ const detail = await res.text().catch(() => "");
2839
+ yield {
2840
+ type: "error",
2841
+ error: new Error(
2842
+ `Claude provider responded ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
2843
+ )
2844
+ };
2845
+ return;
2846
+ }
2847
+ if (!res.body) {
2848
+ yield { type: "error", error: new Error("Claude provider response has no body") };
2849
+ return;
2850
+ }
2851
+ const reader = res.body.getReader();
2852
+ const decoder = new TextDecoder();
2853
+ let buffer = "";
2854
+ let sawDone = false;
2855
+ let stopReason;
2856
+ try {
2857
+ for (; ; ) {
2858
+ const { done, value } = await reader.read();
2859
+ if (done) break;
2860
+ buffer += decoder.decode(value, { stream: true });
2861
+ let sep;
2862
+ while ((sep = buffer.indexOf("\n\n")) >= 0) {
2863
+ const block = buffer.slice(0, sep);
2864
+ buffer = buffer.slice(sep + 2);
2865
+ for (const line of block.split("\n")) {
2866
+ if (!line.startsWith("data:")) continue;
2867
+ const data = line.slice("data:".length).trim();
2868
+ if (!data) continue;
2869
+ let json;
2870
+ try {
2871
+ json = JSON.parse(data);
2872
+ } catch {
2873
+ continue;
2874
+ }
2875
+ switch (json.type) {
2876
+ case "content_block_delta": {
2877
+ const text = json.delta?.text;
2878
+ if (text) yield { type: "delta", text };
2879
+ break;
2880
+ }
2881
+ case "message_delta": {
2882
+ if (json.delta?.stop_reason) stopReason = json.delta.stop_reason;
2883
+ break;
2884
+ }
2885
+ case "message_stop": {
2886
+ sawDone = true;
2887
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
2888
+ return;
2889
+ }
2890
+ case "error": {
2891
+ yield {
2892
+ type: "error",
2893
+ error: new Error(
2894
+ json.error?.message ? `Claude provider error: ${json.error.message}` : "Claude provider stream error"
2895
+ )
2896
+ };
2897
+ return;
2898
+ }
2899
+ default:
2900
+ break;
2901
+ }
2902
+ }
2903
+ }
2904
+ }
2905
+ } catch (err) {
2906
+ if (!controller.signal.aborted) {
2907
+ yield { type: "error", error: toError2(err) };
2908
+ return;
2909
+ }
2910
+ } finally {
2911
+ reader.releaseLock();
2912
+ }
2913
+ if (!sawDone && !controller.signal.aborted) {
2914
+ yield stopReason ? { type: "done", stopReason } : { type: "done" };
2915
+ }
2916
+ } finally {
2917
+ callerSignal?.removeEventListener("abort", onAbort);
2918
+ controller.abort();
2919
+ }
2920
+ }
2921
+ };
2922
+ }
2675
2923
  // Annotate the CommonJS export names for ESM import in node:
2676
2924
  0 && (module.exports = {
2677
2925
  BlockAttributesExtension,
2678
2926
  CONTEXT_CHAR_CAP,
2927
+ DOCUMENT_MODES,
2679
2928
  EditorContextExtension,
2929
+ FontFamilyExtension,
2680
2930
  FontSizeExtension,
2681
2931
  LOCAL_STORAGE_KEY,
2682
2932
  PAGE_SIZES,
@@ -2685,6 +2935,7 @@ function openaiCompatibleProvider(config) {
2685
2935
  SearchAndReplaceExtension,
2686
2936
  SubdocumentProvider,
2687
2937
  buildAIPrompt,
2938
+ claudeProvider,
2688
2939
  clearSearch,
2689
2940
  collaborationExtensions,
2690
2941
  collectExtensions,
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';
@@ -280,6 +280,28 @@ declare module '@tiptap/core' {
280
280
  */
281
281
  declare const FontSizeExtension: Extension<any, any>;
282
282
 
283
+ declare module '@tiptap/core' {
284
+ interface Commands<ReturnType> {
285
+ fontFamily: {
286
+ /** Set font family on the current selection, e.g. 'Georgia, serif'. */
287
+ setFontFamily: (fontFamily: string) => ReturnType;
288
+ /** Remove font family from the current selection. */
289
+ unsetFontFamily: () => ReturnType;
290
+ };
291
+ }
292
+ }
293
+ /**
294
+ * FontFamilyExtension — adds a `fontFamily` attribute onto TextStyle marks.
295
+ * Requires TextStyle to be registered separately (done in Editor.ts).
296
+ *
297
+ * Mirrors FontSizeExtension: it manipulates the transaction directly
298
+ * (tr.setStoredMarks for a collapsed caret / tr.addMark over a selection) so it
299
+ * merges cleanly with the other TextStyle attributes (fontSize, color) instead
300
+ * of clobbering them. The attribute round-trips through HTML, so the chosen
301
+ * font is stored with the document.
302
+ */
303
+ declare const FontFamilyExtension: Extension<any, any>;
304
+
283
305
  interface EditorContextOptions {
284
306
  onImageUpload?: ImageUploadHandler;
285
307
  citation?: CitationPort;
@@ -335,6 +357,21 @@ declare function replaceCurrent(editor: Editor, replacement: string): boolean;
335
357
  /** Replace every match in one transaction (applied back-to-front). */
336
358
  declare function replaceAll(editor: Editor, replacement: string): number;
337
359
 
360
+ /**
361
+ * Document editing modes (issues #27 / #28 — suggesting mode + track changes).
362
+ *
363
+ * - `editing` — normal editing (default).
364
+ * - `suggesting` — edits are recorded as suggestion marks instead of applied.
365
+ * - `viewing` — read-only; the editor is not editable.
366
+ *
367
+ * Hosts own *authorisation* (who may suggest / accept). The library only
368
+ * provides the mode behaviour and the commands; see
369
+ * `docs/plans/suggesting-mode-and-track-changes.md`.
370
+ */
371
+ type DocumentMode = 'editing' | 'suggesting' | 'viewing';
372
+ /** Every document mode, in menu order. */
373
+ declare const DOCUMENT_MODES: readonly DocumentMode[];
374
+
338
375
  interface SubdocState {
339
376
  id: string;
340
377
  doc: Y.Doc;
@@ -397,4 +434,4 @@ declare class SubdocumentProvider {
397
434
  destroy(): void;
398
435
  }
399
436
 
400
- 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, sanitizeInlineHTML, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
437
+ 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, FontFamilyExtension, 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';
@@ -280,6 +280,28 @@ declare module '@tiptap/core' {
280
280
  */
281
281
  declare const FontSizeExtension: Extension<any, any>;
282
282
 
283
+ declare module '@tiptap/core' {
284
+ interface Commands<ReturnType> {
285
+ fontFamily: {
286
+ /** Set font family on the current selection, e.g. 'Georgia, serif'. */
287
+ setFontFamily: (fontFamily: string) => ReturnType;
288
+ /** Remove font family from the current selection. */
289
+ unsetFontFamily: () => ReturnType;
290
+ };
291
+ }
292
+ }
293
+ /**
294
+ * FontFamilyExtension — adds a `fontFamily` attribute onto TextStyle marks.
295
+ * Requires TextStyle to be registered separately (done in Editor.ts).
296
+ *
297
+ * Mirrors FontSizeExtension: it manipulates the transaction directly
298
+ * (tr.setStoredMarks for a collapsed caret / tr.addMark over a selection) so it
299
+ * merges cleanly with the other TextStyle attributes (fontSize, color) instead
300
+ * of clobbering them. The attribute round-trips through HTML, so the chosen
301
+ * font is stored with the document.
302
+ */
303
+ declare const FontFamilyExtension: Extension<any, any>;
304
+
283
305
  interface EditorContextOptions {
284
306
  onImageUpload?: ImageUploadHandler;
285
307
  citation?: CitationPort;
@@ -335,6 +357,21 @@ declare function replaceCurrent(editor: Editor, replacement: string): boolean;
335
357
  /** Replace every match in one transaction (applied back-to-front). */
336
358
  declare function replaceAll(editor: Editor, replacement: string): number;
337
359
 
360
+ /**
361
+ * Document editing modes (issues #27 / #28 — suggesting mode + track changes).
362
+ *
363
+ * - `editing` — normal editing (default).
364
+ * - `suggesting` — edits are recorded as suggestion marks instead of applied.
365
+ * - `viewing` — read-only; the editor is not editable.
366
+ *
367
+ * Hosts own *authorisation* (who may suggest / accept). The library only
368
+ * provides the mode behaviour and the commands; see
369
+ * `docs/plans/suggesting-mode-and-track-changes.md`.
370
+ */
371
+ type DocumentMode = 'editing' | 'suggesting' | 'viewing';
372
+ /** Every document mode, in menu order. */
373
+ declare const DOCUMENT_MODES: readonly DocumentMode[];
374
+
338
375
  interface SubdocState {
339
376
  id: string;
340
377
  doc: Y.Doc;
@@ -397,4 +434,4 @@ declare class SubdocumentProvider {
397
434
  destroy(): void;
398
435
  }
399
436
 
400
- 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, sanitizeInlineHTML, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
437
+ 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, FontFamilyExtension, 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);
@@ -2166,7 +2161,6 @@ var FontSizeExtension = Extension6.create({
2166
2161
  addCommands() {
2167
2162
  return {
2168
2163
  setFontSize: (fontSize) => ({ tr, state, dispatch }) => {
2169
- console.log("[FontSizeExt] setFontSize called, fontSize:", fontSize, "empty:", state.selection.empty);
2170
2164
  const textStyleType = state.schema.marks.textStyle;
2171
2165
  const { from, to, empty } = state.selection;
2172
2166
  if (empty) {
@@ -2242,6 +2236,114 @@ var FontSizeExtension = Extension6.create({
2242
2236
  }
2243
2237
  });
2244
2238
 
2239
+ // src/FontFamily.ts
2240
+ import { Extension as Extension7 } from "@tiptap/core";
2241
+ function mergeFontFamily(textStyleType, existing, fontFamily) {
2242
+ if (fontFamily === null) {
2243
+ const { fontFamily: _removed, ...rest } = existing?.attrs ?? {};
2244
+ return textStyleType.create(rest);
2245
+ }
2246
+ return textStyleType.create({ ...existing?.attrs, fontFamily });
2247
+ }
2248
+ function hasAttrs2(mark) {
2249
+ return Object.values(mark.attrs).some((v) => v != null);
2250
+ }
2251
+ var FontFamilyExtension = Extension7.create({
2252
+ name: "fontFamily",
2253
+ addGlobalAttributes() {
2254
+ return [
2255
+ {
2256
+ types: ["textStyle"],
2257
+ attributes: {
2258
+ fontFamily: {
2259
+ default: null,
2260
+ parseHTML: (el) => el.style.fontFamily || null,
2261
+ renderHTML: (attrs) => {
2262
+ if (!attrs.fontFamily) return {};
2263
+ return { style: `font-family: ${attrs.fontFamily}` };
2264
+ }
2265
+ }
2266
+ }
2267
+ }
2268
+ ];
2269
+ },
2270
+ addCommands() {
2271
+ return {
2272
+ setFontFamily: (fontFamily) => ({ tr, state, dispatch }) => {
2273
+ const textStyleType = state.schema.marks.textStyle;
2274
+ const { from, to, empty } = state.selection;
2275
+ if (empty) {
2276
+ const stored = tr.storedMarks ? [...tr.storedMarks] : [...state.storedMarks ?? []];
2277
+ const idx = stored.findIndex((m) => m.type === textStyleType);
2278
+ const existing = idx >= 0 ? stored[idx] : void 0;
2279
+ const merged = mergeFontFamily(textStyleType, existing, fontFamily);
2280
+ if (idx >= 0) {
2281
+ if (hasAttrs2(merged)) {
2282
+ stored[idx] = merged;
2283
+ } else {
2284
+ stored.splice(idx, 1);
2285
+ }
2286
+ } else {
2287
+ stored.push(merged);
2288
+ }
2289
+ tr.setStoredMarks(stored);
2290
+ } else {
2291
+ state.doc.nodesBetween(from, to, (node, pos) => {
2292
+ if (!node.isText) return;
2293
+ const fromPos = Math.max(pos, from);
2294
+ const toPos = Math.min(pos + node.nodeSize, to);
2295
+ const existingTextStyle = node.marks.find((m) => m.type === textStyleType);
2296
+ const merged = mergeFontFamily(textStyleType, existingTextStyle, fontFamily);
2297
+ if (hasAttrs2(merged)) {
2298
+ tr.addMark(fromPos, toPos, merged);
2299
+ } else if (existingTextStyle) {
2300
+ tr.removeMark(fromPos, toPos, existingTextStyle);
2301
+ }
2302
+ });
2303
+ }
2304
+ if (dispatch) dispatch(tr);
2305
+ return true;
2306
+ },
2307
+ unsetFontFamily: () => ({ tr, state, dispatch }) => {
2308
+ const textStyleType = state.schema.marks.textStyle;
2309
+ const { from, to, empty } = state.selection;
2310
+ if (empty) {
2311
+ const stored = tr.storedMarks ? [...tr.storedMarks] : [...state.storedMarks ?? []];
2312
+ const idx = stored.findIndex((m) => m.type === textStyleType);
2313
+ if (idx >= 0) {
2314
+ const merged = mergeFontFamily(textStyleType, stored[idx], null);
2315
+ if (hasAttrs2(merged)) {
2316
+ stored[idx] = merged;
2317
+ } else {
2318
+ stored.splice(idx, 1);
2319
+ }
2320
+ tr.setStoredMarks(stored);
2321
+ }
2322
+ } else {
2323
+ state.doc.nodesBetween(from, to, (node, pos) => {
2324
+ if (!node.isText) return;
2325
+ const fromPos = Math.max(pos, from);
2326
+ const toPos = Math.min(pos + node.nodeSize, to);
2327
+ const existingTextStyle = node.marks.find((m) => m.type === textStyleType);
2328
+ if (!existingTextStyle) return;
2329
+ const merged = mergeFontFamily(textStyleType, existingTextStyle, null);
2330
+ if (hasAttrs2(merged)) {
2331
+ tr.addMark(fromPos, toPos, merged);
2332
+ } else {
2333
+ tr.removeMark(fromPos, toPos, existingTextStyle);
2334
+ }
2335
+ });
2336
+ }
2337
+ if (dispatch) dispatch(tr);
2338
+ return true;
2339
+ }
2340
+ };
2341
+ }
2342
+ });
2343
+
2344
+ // src/modes.ts
2345
+ var DOCUMENT_MODES = ["editing", "suggesting", "viewing"];
2346
+
2245
2347
  // src/SubdocumentProvider.ts
2246
2348
  import { Awareness as AwarenessClass } from "y-protocols/awareness";
2247
2349
  import { WebsocketProvider as WebsocketProvider2 } from "y-websocket";
@@ -2351,7 +2453,9 @@ var SubdocumentProvider = class {
2351
2453
  export {
2352
2454
  BlockAttributesExtension,
2353
2455
  CONTEXT_CHAR_CAP,
2456
+ DOCUMENT_MODES,
2354
2457
  EditorContextExtension,
2458
+ FontFamilyExtension,
2355
2459
  FontSizeExtension,
2356
2460
  LOCAL_STORAGE_KEY,
2357
2461
  PAGE_SIZES,
@@ -2360,6 +2464,7 @@ export {
2360
2464
  SearchAndReplaceExtension,
2361
2465
  SubdocumentProvider,
2362
2466
  buildAIPrompt,
2467
+ claudeProvider,
2363
2468
  clearSearch,
2364
2469
  collaborationExtensions,
2365
2470
  collectExtensions,
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.85",
4
+ "version": "0.0.87",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",