@kedataindo/docflow-core 0.0.90 → 0.0.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,6 +36,11 @@ __export(index_exports, {
36
36
  EditorContextExtension: () => EditorContextExtension,
37
37
  FontFamilyExtension: () => FontFamilyExtension,
38
38
  FontSizeExtension: () => FontSizeExtension,
39
+ INDENT_MAX_LEVEL: () => INDENT_MAX_LEVEL,
40
+ INDENT_STEP_PX: () => INDENT_STEP_PX,
41
+ INDENT_STEP_VAR: () => INDENT_STEP_VAR,
42
+ INDENT_STYLE_VAR: () => INDENT_STYLE_VAR,
43
+ IndentExtension: () => IndentExtension,
39
44
  LOCAL_STORAGE_KEY: () => LOCAL_STORAGE_KEY,
40
45
  PAGE_SIZES: () => PAGE_SIZES,
41
46
  PaginationPlus: () => PaginationPlus,
@@ -43,6 +48,7 @@ __export(index_exports, {
43
48
  SearchAndReplaceExtension: () => SearchAndReplaceExtension,
44
49
  SubdocumentProvider: () => SubdocumentProvider,
45
50
  buildAIPrompt: () => buildAIPrompt,
51
+ clampIndentLevel: () => clampIndentLevel,
46
52
  claudeProvider: () => claudeProvider,
47
53
  clearSearch: () => clearSearch,
48
54
  collaborationExtensions: () => collaborationExtensions,
@@ -56,6 +62,7 @@ __export(index_exports, {
56
62
  findMatches: () => findMatches,
57
63
  getSearchState: () => getSearchState,
58
64
  httpKeyStorage: () => httpKeyStorage,
65
+ indentLevelOf: () => indentLevelOf,
59
66
  localStorageKeyStorage: () => localStorageKeyStorage,
60
67
  memoryKeyStorage: () => memoryKeyStorage,
61
68
  normalizeClipboardHTML: () => normalizeClipboardHTML,
@@ -76,7 +83,7 @@ __export(index_exports, {
76
83
  module.exports = __toCommonJS(index_exports);
77
84
 
78
85
  // src/Editor.ts
79
- var import_core6 = require("@tiptap/core");
86
+ var import_core7 = require("@tiptap/core");
80
87
  var import_starter_kit = __toESM(require("@tiptap/starter-kit"), 1);
81
88
  var import_extension_text_style = __toESM(require("@tiptap/extension-text-style"), 1);
82
89
 
@@ -347,10 +354,173 @@ var EditorContextExtension = import_core2.Extension.create({
347
354
  }
348
355
  });
349
356
 
350
- // src/ManualColumnResize.ts
357
+ // src/Indent.ts
351
358
  var import_core3 = require("@tiptap/core");
359
+ var INDENT_STEP_PX = 48;
360
+ var INDENT_MAX_LEVEL = 8;
361
+ var INDENT_ATTR = "indent";
362
+ var INDENT_DATA_ATTR = "data-indent";
363
+ var INDENTABLE_NODES = ["paragraph", "heading"];
364
+ var LIST_ITEM_NODES = ["listItem", "taskItem"];
365
+ var CODE_BLOCK = "codeBlock";
366
+ var ABSOLUTE_UNIT_PX = {
367
+ px: 1,
368
+ in: 96,
369
+ cm: 96 / 2.54,
370
+ mm: 96 / 25.4,
371
+ pt: 96 / 72,
372
+ pc: 16
373
+ };
374
+ function clampIndentLevel(level) {
375
+ if (!Number.isFinite(level)) return 0;
376
+ return Math.min(Math.max(Math.trunc(level), 0), INDENT_MAX_LEVEL);
377
+ }
378
+ function indentLevelOf(node) {
379
+ return clampIndentLevel(Number(node.attrs[INDENT_ATTR] ?? 0));
380
+ }
381
+ function parseIndentLevel(element) {
382
+ const raw = element.getAttribute(INDENT_DATA_ATTR);
383
+ if (raw !== null) return clampIndentLevel(Number.parseInt(raw, 10));
384
+ const px = marginLeftPx(element);
385
+ return Number.isFinite(px) && px > 0 ? clampIndentLevel(Math.round(px / INDENT_STEP_PX)) : 0;
386
+ }
387
+ function marginLeftPx(element) {
388
+ const raw = element.style?.marginLeft?.trim() ?? "";
389
+ if (!raw) return 0;
390
+ const match = /^(-?\d*\.?\d+)\s*([a-z%]*)$/i.exec(raw);
391
+ if (!match) return 0;
392
+ const value = Number.parseFloat(match[1]);
393
+ const unit = (match[2] || "px").toLowerCase();
394
+ const factor = ABSOLUTE_UNIT_PX[unit];
395
+ return factor === void 0 ? 0 : value * factor;
396
+ }
397
+ var INDENT_STYLE_VAR = "--docflow-indent";
398
+ var INDENT_STEP_VAR = "--docflow-indent-step";
399
+ function renderIndentAttributes(level) {
400
+ return {
401
+ [INDENT_DATA_ATTR]: String(level),
402
+ // Three representations on purpose: the custom properties let the
403
+ // stylesheet out-rank its own `!important` block-margin shorthands (see
404
+ // packages/vue/src/styles/index.css) and keep the step size in one place,
405
+ // while the inline `margin-left` still works for hosts that do not ship
406
+ // that stylesheet.
407
+ style: `${INDENT_STYLE_VAR}: ${level}; ${INDENT_STEP_VAR}: ${INDENT_STEP_PX}px; margin-left: ${level * INDENT_STEP_PX}px`
408
+ };
409
+ }
410
+ function shiftBlockIndent(props, delta) {
411
+ const { state, tr } = props;
412
+ const { from, to } = state.selection;
413
+ let changed = false;
414
+ state.doc.nodesBetween(from, to, (node, pos) => {
415
+ if (!INDENTABLE_NODES.includes(node.type.name)) return;
416
+ const current = indentLevelOf(node);
417
+ const next = clampIndentLevel(current + delta);
418
+ if (next === current) return;
419
+ tr.setNodeMarkup(pos, void 0, { ...node.attrs, [INDENT_ATTR]: next });
420
+ changed = true;
421
+ });
422
+ if (changed) props.dispatch?.();
423
+ return changed;
424
+ }
425
+ function coveredCodeLineStarts(state) {
426
+ const { $from, $to } = state.selection;
427
+ if ($from.parent.type.name !== CODE_BLOCK || !$from.sameParent($to)) return null;
428
+ const text = $from.parent.textContent;
429
+ const first = text.lastIndexOf("\n", Math.max($from.parentOffset - 1, 0)) + 1;
430
+ const breakAt = text.indexOf("\n", $to.parentOffset);
431
+ const end = breakAt === -1 ? text.length : breakAt;
432
+ const starts = [];
433
+ for (let at = first; at <= end; at++) {
434
+ if (at === first || text[at - 1] === "\n") starts.push(at);
435
+ }
436
+ return starts;
437
+ }
438
+ var LEADING_INDENT = /^(?:\t| {1,2})/;
439
+ function shiftCodeBlockIndent(props, delta) {
440
+ const starts = coveredCodeLineStarts(props.state);
441
+ if (!starts) return false;
442
+ if (delta > 0 && props.state.selection.empty) {
443
+ props.tr.insertText(" ");
444
+ props.dispatch?.();
445
+ return true;
446
+ }
447
+ const text = props.state.selection.$from.parent.textContent;
448
+ const blockStart = props.state.selection.$from.start();
449
+ const { tr } = props;
450
+ for (let i = starts.length - 1; i >= 0; i--) {
451
+ const at = blockStart + starts[i];
452
+ if (delta > 0) {
453
+ tr.insertText(" ", at, at);
454
+ continue;
455
+ }
456
+ const leading = LEADING_INDENT.exec(text.slice(starts[i]))?.[0];
457
+ if (leading) tr.delete(at, at + leading.length);
458
+ }
459
+ const changed = delta > 0 || tr.docChanged;
460
+ if (changed) props.dispatch?.();
461
+ return changed;
462
+ }
463
+ function runIndent(props, delta) {
464
+ if (props.editor.isActive(CODE_BLOCK)) return shiftCodeBlockIndent(props, delta);
465
+ const listItem = LIST_ITEM_NODES.find((name) => props.editor.isActive(name));
466
+ if (listItem) {
467
+ const nested = delta > 0 ? props.commands.sinkListItem(listItem) : props.commands.liftListItem(listItem);
468
+ if (nested) return true;
469
+ }
470
+ return shiftBlockIndent(props, delta);
471
+ }
472
+ var IndentExtension = import_core3.Extension.create({
473
+ name: "indent",
474
+ priority: 50,
475
+ addGlobalAttributes() {
476
+ return [
477
+ {
478
+ types: [...INDENTABLE_NODES],
479
+ attributes: {
480
+ [INDENT_ATTR]: {
481
+ default: 0,
482
+ parseHTML: parseIndentLevel,
483
+ renderHTML: (attributes) => {
484
+ const level = clampIndentLevel(Number(attributes[INDENT_ATTR] ?? 0));
485
+ return level > 0 ? renderIndentAttributes(level) : {};
486
+ }
487
+ }
488
+ }
489
+ }
490
+ ];
491
+ },
492
+ addCommands() {
493
+ return {
494
+ indent: () => (props) => runIndent(props, 1),
495
+ outdent: () => (props) => runIndent(props, -1),
496
+ setIndent: (level) => ({ state, tr, dispatch }) => {
497
+ const target = clampIndentLevel(level);
498
+ const { from, to } = state.selection;
499
+ let changed = false;
500
+ state.doc.nodesBetween(from, to, (node, pos) => {
501
+ if (!INDENTABLE_NODES.includes(node.type.name)) return;
502
+ if (indentLevelOf(node) === target) return;
503
+ tr.setNodeMarkup(pos, void 0, { ...node.attrs, [INDENT_ATTR]: target });
504
+ changed = true;
505
+ });
506
+ if (changed) dispatch?.();
507
+ return changed;
508
+ },
509
+ unsetIndent: () => ({ commands }) => commands.setIndent(0)
510
+ };
511
+ },
512
+ addKeyboardShortcuts() {
513
+ return {
514
+ Tab: () => this.editor.commands.indent(),
515
+ "Shift-Tab": () => this.editor.commands.outdent()
516
+ };
517
+ }
518
+ });
519
+
520
+ // src/ManualColumnResize.ts
521
+ var import_core4 = require("@tiptap/core");
352
522
  var import_state2 = require("@tiptap/pm/state");
353
- var ManualColumnResize = import_core3.Extension.create({
523
+ var ManualColumnResize = import_core4.Extension.create({
354
524
  name: "manualColumnResize",
355
525
  addProseMirrorPlugins() {
356
526
  const key2 = new import_state2.PluginKey("manualColumnResize");
@@ -673,7 +843,7 @@ var ManualColumnResize = import_core3.Extension.create({
673
843
  });
674
844
 
675
845
  // src/SearchAndReplace.ts
676
- var import_core4 = require("@tiptap/core");
846
+ var import_core5 = require("@tiptap/core");
677
847
  var import_state3 = require("@tiptap/pm/state");
678
848
  var import_view2 = require("@tiptap/pm/view");
679
849
  var searchAndReplaceKey = new import_state3.PluginKey("searchAndReplace");
@@ -733,7 +903,7 @@ var searchPlugin = new import_state3.Plugin({
733
903
  }
734
904
  }
735
905
  });
736
- var SearchAndReplaceExtension = import_core4.Extension.create({
906
+ var SearchAndReplaceExtension = import_core5.Extension.create({
737
907
  name: "searchAndReplace",
738
908
  addProseMirrorPlugins() {
739
909
  return [searchPlugin];
@@ -791,7 +961,7 @@ function replaceAll(editor, replacement) {
791
961
  }
792
962
 
793
963
  // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
794
- var import_core5 = require("@tiptap/core");
964
+ var import_core6 = require("@tiptap/core");
795
965
  var import_state4 = require("@tiptap/pm/state");
796
966
  var import_transform = require("@tiptap/pm/transform");
797
967
  var import_view3 = require("@tiptap/pm/view");
@@ -1074,7 +1244,7 @@ var getPageConfigFromOptions = (_currentOptions) => {
1074
1244
  };
1075
1245
  };
1076
1246
  var paginationKey = new import_state4.PluginKey("pagination");
1077
- var PaginationPlus = import_core5.Extension.create({
1247
+ var PaginationPlus = import_core6.Extension.create({
1078
1248
  name: "PaginationPlus",
1079
1249
  addOptions() {
1080
1250
  return defaultOptions;
@@ -1852,7 +2022,10 @@ var KEEP_ATTRS = {
1852
2022
  td: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1853
2023
  th: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1854
2024
  col: /* @__PURE__ */ new Set(["span"]),
1855
- "*": /* @__PURE__ */ new Set(["style"])
2025
+ // `data-indent` carries the block indent (issue #189). Without it, copying an
2026
+ // indented paragraph inside the editor and pasting it back would silently
2027
+ // lose the level, because normalization runs on every paste.
2028
+ "*": /* @__PURE__ */ new Set(["style", "data-indent"])
1856
2029
  };
1857
2030
  function detectClipboardSource(html) {
1858
2031
  if (/id\s*=\s*["']docs-internal/i.test(html)) return "google-docs";
@@ -2087,13 +2260,17 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
2087
2260
  }),
2088
2261
  // Always present — find & replace decorations + state (core editing infra).
2089
2262
  SearchAndReplaceExtension,
2263
+ // Always present — Tab/Shift-Tab block indent (issue #189). Declares
2264
+ // priority 50 so list nesting, table cell navigation and the AI preview
2265
+ // keep their own Tab bindings.
2266
+ IndentExtension,
2090
2267
  ...pluginExtensions
2091
2268
  ];
2092
2269
  const content = options.collaboration ? void 0 : options.content;
2093
2270
  if (options.collaboration && collaborationSetup) {
2094
2271
  extensions = [...extensions, ...collaborationExtensions(collaborationSetup)];
2095
2272
  }
2096
- return new import_core6.Editor({
2273
+ return new import_core7.Editor({
2097
2274
  element: options.target,
2098
2275
  content,
2099
2276
  extensions,
@@ -2191,7 +2368,7 @@ function sanitizeInlineHTML(html) {
2191
2368
  }
2192
2369
 
2193
2370
  // src/FontSize.ts
2194
- var import_core7 = require("@tiptap/core");
2371
+ var import_core8 = require("@tiptap/core");
2195
2372
  function mergeFontSize(textStyleType, existing, fontSize) {
2196
2373
  if (fontSize === null) {
2197
2374
  const { fontSize: _removed, ...rest } = existing?.attrs ?? {};
@@ -2202,7 +2379,7 @@ function mergeFontSize(textStyleType, existing, fontSize) {
2202
2379
  function hasAttrs(mark) {
2203
2380
  return Object.values(mark.attrs).some((v) => v != null);
2204
2381
  }
2205
- var FontSizeExtension = import_core7.Extension.create({
2382
+ var FontSizeExtension = import_core8.Extension.create({
2206
2383
  name: "fontSize",
2207
2384
  addGlobalAttributes() {
2208
2385
  return [
@@ -2300,7 +2477,7 @@ var FontSizeExtension = import_core7.Extension.create({
2300
2477
  });
2301
2478
 
2302
2479
  // src/FontFamily.ts
2303
- var import_core8 = require("@tiptap/core");
2480
+ var import_core9 = require("@tiptap/core");
2304
2481
  function mergeFontFamily(textStyleType, existing, fontFamily) {
2305
2482
  if (fontFamily === null) {
2306
2483
  const { fontFamily: _removed, ...rest } = existing?.attrs ?? {};
@@ -2311,7 +2488,7 @@ function mergeFontFamily(textStyleType, existing, fontFamily) {
2311
2488
  function hasAttrs2(mark) {
2312
2489
  return Object.values(mark.attrs).some((v) => v != null);
2313
2490
  }
2314
- var FontFamilyExtension = import_core8.Extension.create({
2491
+ var FontFamilyExtension = import_core9.Extension.create({
2315
2492
  name: "fontFamily",
2316
2493
  addGlobalAttributes() {
2317
2494
  return [
@@ -2928,6 +3105,11 @@ function claudeProvider(config) {
2928
3105
  EditorContextExtension,
2929
3106
  FontFamilyExtension,
2930
3107
  FontSizeExtension,
3108
+ INDENT_MAX_LEVEL,
3109
+ INDENT_STEP_PX,
3110
+ INDENT_STEP_VAR,
3111
+ INDENT_STYLE_VAR,
3112
+ IndentExtension,
2931
3113
  LOCAL_STORAGE_KEY,
2932
3114
  PAGE_SIZES,
2933
3115
  PaginationPlus,
@@ -2935,6 +3117,7 @@ function claudeProvider(config) {
2935
3117
  SearchAndReplaceExtension,
2936
3118
  SubdocumentProvider,
2937
3119
  buildAIPrompt,
3120
+ clampIndentLevel,
2938
3121
  claudeProvider,
2939
3122
  clearSearch,
2940
3123
  collaborationExtensions,
@@ -2948,6 +3131,7 @@ function claudeProvider(config) {
2948
3131
  findMatches,
2949
3132
  getSearchState,
2950
3133
  httpKeyStorage,
3134
+ indentLevelOf,
2951
3135
  localStorageKeyStorage,
2952
3136
  memoryKeyStorage,
2953
3137
  normalizeClipboardHTML,
package/dist/index.d.cts CHANGED
@@ -8,8 +8,8 @@ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDra
8
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-DtcV-JK5.cjs';
9
9
  import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
10
  export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
11
- import { PluginKey } from '@tiptap/pm/state';
12
11
  import { Node } from '@tiptap/pm/model';
12
+ import { PluginKey } from '@tiptap/pm/state';
13
13
 
14
14
  interface ToolbarItem {
15
15
  id: string;
@@ -260,6 +260,49 @@ interface BlockAttributesOptions {
260
260
  }
261
261
  declare const BlockAttributesExtension: Extension<BlockAttributesOptions, any>;
262
262
 
263
+ declare module '@tiptap/core' {
264
+ interface Commands<ReturnType> {
265
+ indent: {
266
+ /** Shift the block(s) in the selection one level right (Tab). */
267
+ indent: () => ReturnType;
268
+ /** Shift the block(s) in the selection one level left (Shift-Tab). */
269
+ outdent: () => ReturnType;
270
+ /** Set an absolute block-indent level (clamped to INDENT_MAX_LEVEL). */
271
+ setIndent: (level: number) => ReturnType;
272
+ /** Reset the block indent of every block in the selection. */
273
+ unsetIndent: () => ReturnType;
274
+ };
275
+ }
276
+ }
277
+ /** One indent step — half an inch, matching Google Docs (48px at 96dpi). */
278
+ declare const INDENT_STEP_PX = 48;
279
+ /** Deepest block indent the commands accept (8 × 48px = 384px). */
280
+ declare const INDENT_MAX_LEVEL = 8;
281
+ /** Clamp any input to a valid indent level (0 … INDENT_MAX_LEVEL). */
282
+ declare function clampIndentLevel(level: number): number;
283
+ /** Indent level stored on a block node (`0` when the attribute is absent). */
284
+ declare function indentLevelOf(node: Node): number;
285
+ /** Custom property the editor stylesheet turns into `margin-left`. */
286
+ declare const INDENT_STYLE_VAR = "--docflow-indent";
287
+ /** Custom property carrying the step size, so the stylesheet has no 48px copy. */
288
+ declare const INDENT_STEP_VAR = "--docflow-indent-step";
289
+ /**
290
+ * IndentExtension — keyboard block indentation for the docs editor.
291
+ *
292
+ * Issue #189: Tab used to leave the editor because nothing bound it. The
293
+ * extension stores the level as a node attribute on paragraphs/headings,
294
+ * renders it as `data-indent` + `margin-left`, and exposes
295
+ * `indent` / `outdent` / `setIndent` / `unsetIndent` so the toolbar menu can
296
+ * offer the same commands as the keyboard.
297
+ *
298
+ * Priority is deliberately BELOW the 100 default: Tab is already claimed by
299
+ * richer contexts (`@tiptap/extension-list-item` nesting, table cell
300
+ * navigation, the AI suggestion preview) and those must keep precedence. This
301
+ * extension only runs once they have declined the key — paragraphs/headings,
302
+ * and the first item of a list that cannot nest any deeper.
303
+ */
304
+ declare const IndentExtension: Extension<any, any>;
305
+
263
306
  declare module '@tiptap/core' {
264
307
  interface Commands<ReturnType> {
265
308
  fontSize: {
@@ -434,4 +477,4 @@ declare class SubdocumentProvider {
434
477
  destroy(): void;
435
478
  }
436
479
 
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 };
480
+ 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, INDENT_MAX_LEVEL, INDENT_STEP_PX, INDENT_STEP_VAR, INDENT_STYLE_VAR, ImageUploadHandler, IndentExtension, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clampIndentLevel, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, indentLevelOf, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizeInlineHTML, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
package/dist/index.d.ts CHANGED
@@ -8,8 +8,8 @@ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDra
8
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-DtcV-JK5.js';
9
9
  import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
10
  export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
11
- import { PluginKey } from '@tiptap/pm/state';
12
11
  import { Node } from '@tiptap/pm/model';
12
+ import { PluginKey } from '@tiptap/pm/state';
13
13
 
14
14
  interface ToolbarItem {
15
15
  id: string;
@@ -260,6 +260,49 @@ interface BlockAttributesOptions {
260
260
  }
261
261
  declare const BlockAttributesExtension: Extension<BlockAttributesOptions, any>;
262
262
 
263
+ declare module '@tiptap/core' {
264
+ interface Commands<ReturnType> {
265
+ indent: {
266
+ /** Shift the block(s) in the selection one level right (Tab). */
267
+ indent: () => ReturnType;
268
+ /** Shift the block(s) in the selection one level left (Shift-Tab). */
269
+ outdent: () => ReturnType;
270
+ /** Set an absolute block-indent level (clamped to INDENT_MAX_LEVEL). */
271
+ setIndent: (level: number) => ReturnType;
272
+ /** Reset the block indent of every block in the selection. */
273
+ unsetIndent: () => ReturnType;
274
+ };
275
+ }
276
+ }
277
+ /** One indent step — half an inch, matching Google Docs (48px at 96dpi). */
278
+ declare const INDENT_STEP_PX = 48;
279
+ /** Deepest block indent the commands accept (8 × 48px = 384px). */
280
+ declare const INDENT_MAX_LEVEL = 8;
281
+ /** Clamp any input to a valid indent level (0 … INDENT_MAX_LEVEL). */
282
+ declare function clampIndentLevel(level: number): number;
283
+ /** Indent level stored on a block node (`0` when the attribute is absent). */
284
+ declare function indentLevelOf(node: Node): number;
285
+ /** Custom property the editor stylesheet turns into `margin-left`. */
286
+ declare const INDENT_STYLE_VAR = "--docflow-indent";
287
+ /** Custom property carrying the step size, so the stylesheet has no 48px copy. */
288
+ declare const INDENT_STEP_VAR = "--docflow-indent-step";
289
+ /**
290
+ * IndentExtension — keyboard block indentation for the docs editor.
291
+ *
292
+ * Issue #189: Tab used to leave the editor because nothing bound it. The
293
+ * extension stores the level as a node attribute on paragraphs/headings,
294
+ * renders it as `data-indent` + `margin-left`, and exposes
295
+ * `indent` / `outdent` / `setIndent` / `unsetIndent` so the toolbar menu can
296
+ * offer the same commands as the keyboard.
297
+ *
298
+ * Priority is deliberately BELOW the 100 default: Tab is already claimed by
299
+ * richer contexts (`@tiptap/extension-list-item` nesting, table cell
300
+ * navigation, the AI suggestion preview) and those must keep precedence. This
301
+ * extension only runs once they have declined the key — paragraphs/headings,
302
+ * and the first item of a list that cannot nest any deeper.
303
+ */
304
+ declare const IndentExtension: Extension<any, any>;
305
+
263
306
  declare module '@tiptap/core' {
264
307
  interface Commands<ReturnType> {
265
308
  fontSize: {
@@ -434,4 +477,4 @@ declare class SubdocumentProvider {
434
477
  destroy(): void;
435
478
  }
436
479
 
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 };
480
+ 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, INDENT_MAX_LEVEL, INDENT_STEP_PX, INDENT_STEP_VAR, INDENT_STYLE_VAR, ImageUploadHandler, IndentExtension, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clampIndentLevel, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, indentLevelOf, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizeInlineHTML, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
package/dist/index.js CHANGED
@@ -284,10 +284,173 @@ var EditorContextExtension = Extension2.create({
284
284
  }
285
285
  });
286
286
 
287
- // src/ManualColumnResize.ts
287
+ // src/Indent.ts
288
288
  import { Extension as Extension3 } from "@tiptap/core";
289
+ var INDENT_STEP_PX = 48;
290
+ var INDENT_MAX_LEVEL = 8;
291
+ var INDENT_ATTR = "indent";
292
+ var INDENT_DATA_ATTR = "data-indent";
293
+ var INDENTABLE_NODES = ["paragraph", "heading"];
294
+ var LIST_ITEM_NODES = ["listItem", "taskItem"];
295
+ var CODE_BLOCK = "codeBlock";
296
+ var ABSOLUTE_UNIT_PX = {
297
+ px: 1,
298
+ in: 96,
299
+ cm: 96 / 2.54,
300
+ mm: 96 / 25.4,
301
+ pt: 96 / 72,
302
+ pc: 16
303
+ };
304
+ function clampIndentLevel(level) {
305
+ if (!Number.isFinite(level)) return 0;
306
+ return Math.min(Math.max(Math.trunc(level), 0), INDENT_MAX_LEVEL);
307
+ }
308
+ function indentLevelOf(node) {
309
+ return clampIndentLevel(Number(node.attrs[INDENT_ATTR] ?? 0));
310
+ }
311
+ function parseIndentLevel(element) {
312
+ const raw = element.getAttribute(INDENT_DATA_ATTR);
313
+ if (raw !== null) return clampIndentLevel(Number.parseInt(raw, 10));
314
+ const px = marginLeftPx(element);
315
+ return Number.isFinite(px) && px > 0 ? clampIndentLevel(Math.round(px / INDENT_STEP_PX)) : 0;
316
+ }
317
+ function marginLeftPx(element) {
318
+ const raw = element.style?.marginLeft?.trim() ?? "";
319
+ if (!raw) return 0;
320
+ const match = /^(-?\d*\.?\d+)\s*([a-z%]*)$/i.exec(raw);
321
+ if (!match) return 0;
322
+ const value = Number.parseFloat(match[1]);
323
+ const unit = (match[2] || "px").toLowerCase();
324
+ const factor = ABSOLUTE_UNIT_PX[unit];
325
+ return factor === void 0 ? 0 : value * factor;
326
+ }
327
+ var INDENT_STYLE_VAR = "--docflow-indent";
328
+ var INDENT_STEP_VAR = "--docflow-indent-step";
329
+ function renderIndentAttributes(level) {
330
+ return {
331
+ [INDENT_DATA_ATTR]: String(level),
332
+ // Three representations on purpose: the custom properties let the
333
+ // stylesheet out-rank its own `!important` block-margin shorthands (see
334
+ // packages/vue/src/styles/index.css) and keep the step size in one place,
335
+ // while the inline `margin-left` still works for hosts that do not ship
336
+ // that stylesheet.
337
+ style: `${INDENT_STYLE_VAR}: ${level}; ${INDENT_STEP_VAR}: ${INDENT_STEP_PX}px; margin-left: ${level * INDENT_STEP_PX}px`
338
+ };
339
+ }
340
+ function shiftBlockIndent(props, delta) {
341
+ const { state, tr } = props;
342
+ const { from, to } = state.selection;
343
+ let changed = false;
344
+ state.doc.nodesBetween(from, to, (node, pos) => {
345
+ if (!INDENTABLE_NODES.includes(node.type.name)) return;
346
+ const current = indentLevelOf(node);
347
+ const next = clampIndentLevel(current + delta);
348
+ if (next === current) return;
349
+ tr.setNodeMarkup(pos, void 0, { ...node.attrs, [INDENT_ATTR]: next });
350
+ changed = true;
351
+ });
352
+ if (changed) props.dispatch?.();
353
+ return changed;
354
+ }
355
+ function coveredCodeLineStarts(state) {
356
+ const { $from, $to } = state.selection;
357
+ if ($from.parent.type.name !== CODE_BLOCK || !$from.sameParent($to)) return null;
358
+ const text = $from.parent.textContent;
359
+ const first = text.lastIndexOf("\n", Math.max($from.parentOffset - 1, 0)) + 1;
360
+ const breakAt = text.indexOf("\n", $to.parentOffset);
361
+ const end = breakAt === -1 ? text.length : breakAt;
362
+ const starts = [];
363
+ for (let at = first; at <= end; at++) {
364
+ if (at === first || text[at - 1] === "\n") starts.push(at);
365
+ }
366
+ return starts;
367
+ }
368
+ var LEADING_INDENT = /^(?:\t| {1,2})/;
369
+ function shiftCodeBlockIndent(props, delta) {
370
+ const starts = coveredCodeLineStarts(props.state);
371
+ if (!starts) return false;
372
+ if (delta > 0 && props.state.selection.empty) {
373
+ props.tr.insertText(" ");
374
+ props.dispatch?.();
375
+ return true;
376
+ }
377
+ const text = props.state.selection.$from.parent.textContent;
378
+ const blockStart = props.state.selection.$from.start();
379
+ const { tr } = props;
380
+ for (let i = starts.length - 1; i >= 0; i--) {
381
+ const at = blockStart + starts[i];
382
+ if (delta > 0) {
383
+ tr.insertText(" ", at, at);
384
+ continue;
385
+ }
386
+ const leading = LEADING_INDENT.exec(text.slice(starts[i]))?.[0];
387
+ if (leading) tr.delete(at, at + leading.length);
388
+ }
389
+ const changed = delta > 0 || tr.docChanged;
390
+ if (changed) props.dispatch?.();
391
+ return changed;
392
+ }
393
+ function runIndent(props, delta) {
394
+ if (props.editor.isActive(CODE_BLOCK)) return shiftCodeBlockIndent(props, delta);
395
+ const listItem = LIST_ITEM_NODES.find((name) => props.editor.isActive(name));
396
+ if (listItem) {
397
+ const nested = delta > 0 ? props.commands.sinkListItem(listItem) : props.commands.liftListItem(listItem);
398
+ if (nested) return true;
399
+ }
400
+ return shiftBlockIndent(props, delta);
401
+ }
402
+ var IndentExtension = Extension3.create({
403
+ name: "indent",
404
+ priority: 50,
405
+ addGlobalAttributes() {
406
+ return [
407
+ {
408
+ types: [...INDENTABLE_NODES],
409
+ attributes: {
410
+ [INDENT_ATTR]: {
411
+ default: 0,
412
+ parseHTML: parseIndentLevel,
413
+ renderHTML: (attributes) => {
414
+ const level = clampIndentLevel(Number(attributes[INDENT_ATTR] ?? 0));
415
+ return level > 0 ? renderIndentAttributes(level) : {};
416
+ }
417
+ }
418
+ }
419
+ }
420
+ ];
421
+ },
422
+ addCommands() {
423
+ return {
424
+ indent: () => (props) => runIndent(props, 1),
425
+ outdent: () => (props) => runIndent(props, -1),
426
+ setIndent: (level) => ({ state, tr, dispatch }) => {
427
+ const target = clampIndentLevel(level);
428
+ const { from, to } = state.selection;
429
+ let changed = false;
430
+ state.doc.nodesBetween(from, to, (node, pos) => {
431
+ if (!INDENTABLE_NODES.includes(node.type.name)) return;
432
+ if (indentLevelOf(node) === target) return;
433
+ tr.setNodeMarkup(pos, void 0, { ...node.attrs, [INDENT_ATTR]: target });
434
+ changed = true;
435
+ });
436
+ if (changed) dispatch?.();
437
+ return changed;
438
+ },
439
+ unsetIndent: () => ({ commands }) => commands.setIndent(0)
440
+ };
441
+ },
442
+ addKeyboardShortcuts() {
443
+ return {
444
+ Tab: () => this.editor.commands.indent(),
445
+ "Shift-Tab": () => this.editor.commands.outdent()
446
+ };
447
+ }
448
+ });
449
+
450
+ // src/ManualColumnResize.ts
451
+ import { Extension as Extension4 } from "@tiptap/core";
289
452
  import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
290
- var ManualColumnResize = Extension3.create({
453
+ var ManualColumnResize = Extension4.create({
291
454
  name: "manualColumnResize",
292
455
  addProseMirrorPlugins() {
293
456
  const key2 = new PluginKey2("manualColumnResize");
@@ -610,7 +773,7 @@ var ManualColumnResize = Extension3.create({
610
773
  });
611
774
 
612
775
  // src/SearchAndReplace.ts
613
- import { Extension as Extension4 } from "@tiptap/core";
776
+ import { Extension as Extension5 } from "@tiptap/core";
614
777
  import { Plugin as Plugin3, PluginKey as PluginKey3, TextSelection } from "@tiptap/pm/state";
615
778
  import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
616
779
  var searchAndReplaceKey = new PluginKey3("searchAndReplace");
@@ -670,7 +833,7 @@ var searchPlugin = new Plugin3({
670
833
  }
671
834
  }
672
835
  });
673
- var SearchAndReplaceExtension = Extension4.create({
836
+ var SearchAndReplaceExtension = Extension5.create({
674
837
  name: "searchAndReplace",
675
838
  addProseMirrorPlugins() {
676
839
  return [searchPlugin];
@@ -728,7 +891,7 @@ function replaceAll(editor, replacement) {
728
891
  }
729
892
 
730
893
  // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=00858351959cd6e40e43520c066de1f40e312b64dcfd6be_bddb103a385fcb63b7133ac6ead3d2c8/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
731
- import { Extension as Extension5 } from "@tiptap/core";
894
+ import { Extension as Extension6 } from "@tiptap/core";
732
895
  import { Plugin as Plugin4, PluginKey as PluginKey4 } from "@tiptap/pm/state";
733
896
  import { ReplaceStep, ReplaceAroundStep, AddMarkStep, RemoveMarkStep, RemoveNodeMarkStep, AttrStep } from "@tiptap/pm/transform";
734
897
  import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "@tiptap/pm/view";
@@ -1011,7 +1174,7 @@ var getPageConfigFromOptions = (_currentOptions) => {
1011
1174
  };
1012
1175
  };
1013
1176
  var paginationKey = new PluginKey4("pagination");
1014
- var PaginationPlus = Extension5.create({
1177
+ var PaginationPlus = Extension6.create({
1015
1178
  name: "PaginationPlus",
1016
1179
  addOptions() {
1017
1180
  return defaultOptions;
@@ -1789,7 +1952,10 @@ var KEEP_ATTRS = {
1789
1952
  td: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1790
1953
  th: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1791
1954
  col: /* @__PURE__ */ new Set(["span"]),
1792
- "*": /* @__PURE__ */ new Set(["style"])
1955
+ // `data-indent` carries the block indent (issue #189). Without it, copying an
1956
+ // indented paragraph inside the editor and pasting it back would silently
1957
+ // lose the level, because normalization runs on every paste.
1958
+ "*": /* @__PURE__ */ new Set(["style", "data-indent"])
1793
1959
  };
1794
1960
  function detectClipboardSource(html) {
1795
1961
  if (/id\s*=\s*["']docs-internal/i.test(html)) return "google-docs";
@@ -2024,6 +2190,10 @@ function createTiptapEditor(options, plugins, collaborationSetup) {
2024
2190
  }),
2025
2191
  // Always present — find & replace decorations + state (core editing infra).
2026
2192
  SearchAndReplaceExtension,
2193
+ // Always present — Tab/Shift-Tab block indent (issue #189). Declares
2194
+ // priority 50 so list nesting, table cell navigation and the AI preview
2195
+ // keep their own Tab bindings.
2196
+ IndentExtension,
2027
2197
  ...pluginExtensions
2028
2198
  ];
2029
2199
  const content = options.collaboration ? void 0 : options.content;
@@ -2128,7 +2298,7 @@ function sanitizeInlineHTML(html) {
2128
2298
  }
2129
2299
 
2130
2300
  // src/FontSize.ts
2131
- import { Extension as Extension6 } from "@tiptap/core";
2301
+ import { Extension as Extension7 } from "@tiptap/core";
2132
2302
  function mergeFontSize(textStyleType, existing, fontSize) {
2133
2303
  if (fontSize === null) {
2134
2304
  const { fontSize: _removed, ...rest } = existing?.attrs ?? {};
@@ -2139,7 +2309,7 @@ function mergeFontSize(textStyleType, existing, fontSize) {
2139
2309
  function hasAttrs(mark) {
2140
2310
  return Object.values(mark.attrs).some((v) => v != null);
2141
2311
  }
2142
- var FontSizeExtension = Extension6.create({
2312
+ var FontSizeExtension = Extension7.create({
2143
2313
  name: "fontSize",
2144
2314
  addGlobalAttributes() {
2145
2315
  return [
@@ -2237,7 +2407,7 @@ var FontSizeExtension = Extension6.create({
2237
2407
  });
2238
2408
 
2239
2409
  // src/FontFamily.ts
2240
- import { Extension as Extension7 } from "@tiptap/core";
2410
+ import { Extension as Extension8 } from "@tiptap/core";
2241
2411
  function mergeFontFamily(textStyleType, existing, fontFamily) {
2242
2412
  if (fontFamily === null) {
2243
2413
  const { fontFamily: _removed, ...rest } = existing?.attrs ?? {};
@@ -2248,7 +2418,7 @@ function mergeFontFamily(textStyleType, existing, fontFamily) {
2248
2418
  function hasAttrs2(mark) {
2249
2419
  return Object.values(mark.attrs).some((v) => v != null);
2250
2420
  }
2251
- var FontFamilyExtension = Extension7.create({
2421
+ var FontFamilyExtension = Extension8.create({
2252
2422
  name: "fontFamily",
2253
2423
  addGlobalAttributes() {
2254
2424
  return [
@@ -2457,6 +2627,11 @@ export {
2457
2627
  EditorContextExtension,
2458
2628
  FontFamilyExtension,
2459
2629
  FontSizeExtension,
2630
+ INDENT_MAX_LEVEL,
2631
+ INDENT_STEP_PX,
2632
+ INDENT_STEP_VAR,
2633
+ INDENT_STYLE_VAR,
2634
+ IndentExtension,
2460
2635
  LOCAL_STORAGE_KEY,
2461
2636
  PAGE_SIZES,
2462
2637
  PaginationPlus,
@@ -2464,6 +2639,7 @@ export {
2464
2639
  SearchAndReplaceExtension,
2465
2640
  SubdocumentProvider,
2466
2641
  buildAIPrompt,
2642
+ clampIndentLevel,
2467
2643
  claudeProvider,
2468
2644
  clearSearch,
2469
2645
  collaborationExtensions,
@@ -2477,6 +2653,7 @@ export {
2477
2653
  findMatches,
2478
2654
  getSearchState,
2479
2655
  httpKeyStorage,
2656
+ indentLevelOf,
2480
2657
  localStorageKeyStorage,
2481
2658
  memoryKeyStorage,
2482
2659
  normalizeClipboardHTML,
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.90",
4
+ "version": "0.0.91",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",