@phuong-tran-redoc/document-engine-core 0.0.12

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.
Files changed (63) hide show
  1. package/LICENSE.md +19 -0
  2. package/README.md +224 -0
  3. package/package.json +30 -0
  4. package/src/constants/indent.constant.d.ts +1 -0
  5. package/src/constants/indent.constant.js +11 -0
  6. package/src/constants/index.d.ts +1 -0
  7. package/src/constants/index.js +18 -0
  8. package/src/constants/table.constant.d.ts +1 -0
  9. package/src/constants/table.constant.js +11 -0
  10. package/src/extensions/clear-content.extension.d.ts +9 -0
  11. package/src/extensions/clear-content.extension.js +19 -0
  12. package/src/extensions/indent.extension.d.ts +16 -0
  13. package/src/extensions/indent.extension.js +69 -0
  14. package/src/extensions/index.d.ts +8 -0
  15. package/src/extensions/index.js +25 -0
  16. package/src/extensions/ordered-list.extension.d.ts +13 -0
  17. package/src/extensions/ordered-list.extension.js +48 -0
  18. package/src/extensions/reset-format.extension.d.ts +9 -0
  19. package/src/extensions/reset-format.extension.js +19 -0
  20. package/src/extensions/reset-on-enter.extension.d.ts +2 -0
  21. package/src/extensions/reset-on-enter.extension.js +42 -0
  22. package/src/extensions/restricted-editing.extension.d.ts +17 -0
  23. package/src/extensions/restricted-editing.extension.js +384 -0
  24. package/src/extensions/table-resizing.extension.d.ts +6 -0
  25. package/src/extensions/table-resizing.extension.js +171 -0
  26. package/src/extensions/table-style.extension.d.ts +41 -0
  27. package/src/extensions/table-style.extension.js +499 -0
  28. package/src/extensions/text-case.extension.d.ts +13 -0
  29. package/src/extensions/text-case.extension.js +49 -0
  30. package/src/index.d.ts +7 -0
  31. package/src/index.js +24 -0
  32. package/src/models/color.model.d.ts +14 -0
  33. package/src/models/color.model.js +40 -0
  34. package/src/models/index.d.ts +1 -0
  35. package/src/models/index.js +18 -0
  36. package/src/nodes/dynamic-field.node.d.ts +49 -0
  37. package/src/nodes/dynamic-field.node.js +99 -0
  38. package/src/nodes/heading.node.d.ts +8 -0
  39. package/src/nodes/heading.node.js +23 -0
  40. package/src/nodes/index.d.ts +3 -0
  41. package/src/nodes/index.js +20 -0
  42. package/src/nodes/page-break.node.d.ts +9 -0
  43. package/src/nodes/page-break.node.js +50 -0
  44. package/src/types/index.d.ts +2 -0
  45. package/src/types/index.js +19 -0
  46. package/src/types/ordered-list.type.d.ts +1 -0
  47. package/src/types/ordered-list.type.js +4 -0
  48. package/src/types/text-case.type.d.ts +1 -0
  49. package/src/types/text-case.type.js +4 -0
  50. package/src/utils/color.util.d.ts +14 -0
  51. package/src/utils/color.util.js +41 -0
  52. package/src/utils/dom.util.d.ts +2 -0
  53. package/src/utils/dom.util.js +43 -0
  54. package/src/utils/index.d.ts +4 -0
  55. package/src/utils/index.js +21 -0
  56. package/src/utils/table.util.d.ts +36 -0
  57. package/src/utils/table.util.js +103 -0
  58. package/src/utils/text.util.d.ts +9 -0
  59. package/src/utils/text.util.js +59 -0
  60. package/src/views/block-handler.d.ts +44 -0
  61. package/src/views/block-handler.js +310 -0
  62. package/src/views/index.d.ts +1 -0
  63. package/src/views/index.js +18 -0
@@ -0,0 +1,49 @@
1
+ import { Node } from '@tiptap/core';
2
+ /**
3
+ * Interface for Dynamic Field node attributes
4
+ */
5
+ export interface DynamicFieldAttributes {
6
+ fieldId: string;
7
+ label: string;
8
+ }
9
+ /**
10
+ * Interface for Dynamic Field item
11
+ */
12
+ export interface DynamicFieldItem {
13
+ id: string;
14
+ label: string;
15
+ description?: string;
16
+ }
17
+ /**
18
+ * Interface for Dynamic Field category
19
+ */
20
+ export interface DynamicFieldCategory {
21
+ key: string;
22
+ label: string;
23
+ description?: string;
24
+ fields: DynamicFieldItem[];
25
+ }
26
+ /**
27
+ * Interface for Dynamic Field options
28
+ */
29
+ export interface DynamicFieldOptions {
30
+ HTMLAttributes: Record<string, unknown>;
31
+ }
32
+ /**
33
+ * TypeScript declaration for the insertDynamicField command
34
+ */
35
+ declare module '@tiptap/core' {
36
+ interface Commands<ReturnType> {
37
+ dynamicField: {
38
+ insertDynamicField: (attributes: DynamicFieldAttributes) => ReturnType;
39
+ };
40
+ }
41
+ }
42
+ /**
43
+ * Dynamic Field Node Extension
44
+ *
45
+ * This extension creates atomic, non-editable inline nodes that represent
46
+ * dynamic fields (e.g., {{customer_name}}) in document templates.
47
+ * These fields can be programmatically replaced with actual data later.
48
+ */
49
+ export declare const DynamicField: Node<DynamicFieldOptions, any>;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "DynamicField", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return DynamicField;
9
+ }
10
+ });
11
+ const _core = require("@tiptap/core");
12
+ const DynamicField = _core.Node.create({
13
+ name: 'dynamicField',
14
+ /**
15
+ * Node group configuration
16
+ * - 'inline': Makes the node behave as inline content
17
+ * - 'atom': true is critical for non-editable, atomic behavior
18
+ * - 'selectable': true allows users to select the entire node
19
+ * - 'draggable': false prevents users from dragging the node
20
+ */ group: 'inline',
21
+ inline: true,
22
+ atom: true,
23
+ selectable: true,
24
+ draggable: false,
25
+ /**
26
+ * Define the attributes that this node can store
27
+ */ addAttributes () {
28
+ return {
29
+ fieldId: {
30
+ default: null,
31
+ parseHTML: (element)=>element.getAttribute('data-field-id'),
32
+ renderHTML: (attributes)=>{
33
+ if (!attributes['fieldId']) return {};
34
+ return {
35
+ 'data-field-id': attributes['fieldId']
36
+ };
37
+ }
38
+ },
39
+ label: {
40
+ default: null,
41
+ parseHTML: (element)=>element.getAttribute('data-label'),
42
+ renderHTML: (attributes)=>{
43
+ if (!attributes['label']) return {};
44
+ return {
45
+ 'data-label': attributes['label']
46
+ };
47
+ }
48
+ }
49
+ };
50
+ },
51
+ /**
52
+ * Parse HTML to recognize dynamic field spans when loading/pasting content
53
+ */ parseHTML () {
54
+ return [
55
+ {
56
+ tag: 'span[data-field-id]'
57
+ }
58
+ ];
59
+ },
60
+ /**
61
+ * Render the node as HTML
62
+ * Creates a span element with Tailwind classes for styling
63
+ * and stores attributes in data-* attributes for robustness
64
+ */ renderHTML ({ HTMLAttributes, node }) {
65
+ const { fieldId, label } = node.attrs;
66
+ return [
67
+ 'span',
68
+ (0, _core.mergeAttributes)(HTMLAttributes, {
69
+ class: 'dynamic-field',
70
+ 'data-field-id': fieldId,
71
+ 'data-label': label
72
+ }),
73
+ label || ''
74
+ ];
75
+ },
76
+ /**
77
+ * Add custom commands for inserting dynamic fields
78
+ */ addCommands () {
79
+ return {
80
+ insertDynamicField: (attributes)=>({ chain })=>{
81
+ if (!attributes.fieldId || !attributes.label) {
82
+ console.error('[DynamicField] Both fieldId and label are required.');
83
+ return false;
84
+ }
85
+ return chain().insertContent({
86
+ type: this.name,
87
+ attrs: attributes
88
+ }).run();
89
+ }
90
+ };
91
+ },
92
+ /**
93
+ * Default options
94
+ */ addOptions () {
95
+ return {
96
+ HTMLAttributes: {}
97
+ };
98
+ }
99
+ });
@@ -0,0 +1,8 @@
1
+ declare module '@tiptap/core' {
2
+ interface Commands<ReturnType> {
3
+ removeHeading: {
4
+ removeHeading: () => ReturnType;
5
+ };
6
+ }
7
+ }
8
+ export declare const NotumHeading: import("@tiptap/core").Node<import("@tiptap/extension-heading").HeadingOptions, any>;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "NotumHeading", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return NotumHeading;
9
+ }
10
+ });
11
+ const _extensionheading = require("@tiptap/extension-heading");
12
+ require("@tiptap/extension-paragraph");
13
+ const NotumHeading = _extensionheading.Heading.extend({
14
+ addCommands () {
15
+ return {
16
+ ...this.parent?.(),
17
+ removeHeading: ()=>({ editor, chain })=>{
18
+ if (!editor.isActive('heading')) return false;
19
+ return chain().setParagraph().run();
20
+ }
21
+ };
22
+ }
23
+ });
@@ -0,0 +1,3 @@
1
+ export * from './heading.node';
2
+ export * from './dynamic-field.node';
3
+ export * from './page-break.node';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./heading.node"), exports);
6
+ _export_star(require("./dynamic-field.node"), exports);
7
+ _export_star(require("./page-break.node"), exports);
8
+ function _export_star(from, to) {
9
+ Object.keys(from).forEach(function(k) {
10
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
11
+ Object.defineProperty(to, k, {
12
+ enumerable: true,
13
+ get: function() {
14
+ return from[k];
15
+ }
16
+ });
17
+ }
18
+ });
19
+ return from;
20
+ }
@@ -0,0 +1,9 @@
1
+ import { Node } from '@tiptap/core';
2
+ declare module '@tiptap/core' {
3
+ interface Commands<ReturnType> {
4
+ pageBreak: {
5
+ insertPageBreak: () => ReturnType;
6
+ };
7
+ }
8
+ }
9
+ export declare const PageBreak: Node<any, any>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "PageBreak", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return PageBreak;
9
+ }
10
+ });
11
+ const _core = require("@tiptap/core");
12
+ const PageBreak = _core.Node.create({
13
+ name: 'pageBreak',
14
+ group: 'block',
15
+ atom: true,
16
+ selectable: true,
17
+ parseHTML () {
18
+ return [
19
+ {
20
+ tag: 'div[data-page-break]'
21
+ }
22
+ ];
23
+ },
24
+ renderHTML ({ HTMLAttributes }) {
25
+ return [
26
+ 'div',
27
+ (0, _core.mergeAttributes)(HTMLAttributes, {
28
+ 'data-page-break': 'true',
29
+ class: 'document-engine-page-break',
30
+ style: 'break-before: page;'
31
+ }),
32
+ [
33
+ 'span',
34
+ {
35
+ class: 'document-engine-page-break-label'
36
+ },
37
+ 'Page break'
38
+ ]
39
+ ];
40
+ },
41
+ addCommands () {
42
+ return {
43
+ insertPageBreak: ()=>({ chain })=>{
44
+ return chain().insertContent({
45
+ type: this.name
46
+ }).run();
47
+ }
48
+ };
49
+ }
50
+ });
@@ -0,0 +1,2 @@
1
+ export * from './ordered-list.type';
2
+ export * from './text-case.type';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./ordered-list.type"), exports);
6
+ _export_star(require("./text-case.type"), exports);
7
+ function _export_star(from, to) {
8
+ Object.keys(from).forEach(function(k) {
9
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
10
+ Object.defineProperty(to, k, {
11
+ enumerable: true,
12
+ get: function() {
13
+ return from[k];
14
+ }
15
+ });
16
+ }
17
+ });
18
+ return from;
19
+ }
@@ -0,0 +1 @@
1
+ export type ListStyleType = 'decimal' | 'lower-alpha-dot' | 'lower-alpha-parens' | 'lower-roman-parens';
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
@@ -0,0 +1 @@
1
+ export type TextCaseType = 'uppercase' | 'lowercase' | 'capitalize';
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Normalizes a color value to lowercase hex format
3
+ * Supports hex (#RGB, #RRGGBB) and rgb(r, g, b) formats
4
+ *
5
+ * @param color - Color value to normalize
6
+ * @returns Normalized hex color or null if invalid
7
+ *
8
+ * @example
9
+ * normalizeColor('#FF0000') // '#ff0000'
10
+ * normalizeColor('rgb(255, 0, 0)') // '#ff0000'
11
+ * normalizeColor('#F00') // '#f00'
12
+ * normalizeColor('invalid') // null
13
+ */
14
+ export declare function normalizeColor(color: string): string | null;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Normalizes a color value to lowercase hex format
3
+ * Supports hex (#RGB, #RRGGBB) and rgb(r, g, b) formats
4
+ *
5
+ * @param color - Color value to normalize
6
+ * @returns Normalized hex color or null if invalid
7
+ *
8
+ * @example
9
+ * normalizeColor('#FF0000') // '#ff0000'
10
+ * normalizeColor('rgb(255, 0, 0)') // '#ff0000'
11
+ * normalizeColor('#F00') // '#f00'
12
+ * normalizeColor('invalid') // null
13
+ */ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ Object.defineProperty(exports, "normalizeColor", {
18
+ enumerable: true,
19
+ get: function() {
20
+ return normalizeColor;
21
+ }
22
+ });
23
+ function normalizeColor(color) {
24
+ if (!color) return null;
25
+ const trimmed = color.trim().toLowerCase();
26
+ if (trimmed === 'transparent') return trimmed;
27
+ // Check if it's a valid hex color
28
+ if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(trimmed)) {
29
+ return trimmed;
30
+ }
31
+ // Try to parse RGB
32
+ const rgbMatch = trimmed.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
33
+ if (rgbMatch) {
34
+ const r = Math.max(0, Math.min(255, parseInt(rgbMatch[1], 10)));
35
+ const g = Math.max(0, Math.min(255, parseInt(rgbMatch[2], 10)));
36
+ const b = Math.max(0, Math.min(255, parseInt(rgbMatch[3], 10)));
37
+ const toHex = (n)=>n.toString(16).padStart(2, '0');
38
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
39
+ }
40
+ return null;
41
+ }
@@ -0,0 +1,2 @@
1
+ import { Editor } from '@tiptap/core';
2
+ export declare function getClosestDomElement(editor: Editor, selector: string): HTMLElement | null;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "getClosestDomElement", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return getClosestDomElement;
9
+ }
10
+ });
11
+ function getClosestDomElement(editor, selector) {
12
+ const { view, state } = editor;
13
+ const { selection } = state;
14
+ const { from } = selection;
15
+ if (!from) return null;
16
+ let domNodeAtPos = null;
17
+ // Ưu tiên tìm DOM node của 'nearest parent element' của vị trí from
18
+ // Nếu from là trong text, $from.parent.nodeDOM có thể chính xác hơn
19
+ try {
20
+ const resolvedPos = state.doc.resolve(from);
21
+ domNodeAtPos = view.domAtPos(resolvedPos.pos).node; // Bắt đầu từ node ở vị trí from
22
+ // Nếu domNodeAtPos không phải là HTMLElement (ví dụ, là một Text node),
23
+ // thì cố gắng lấy element cha của nó trong DOM
24
+ if (domNodeAtPos && !(domNodeAtPos instanceof HTMLElement)) {
25
+ domNodeAtPos = domNodeAtPos.parentElement;
26
+ }
27
+ } catch (e) {
28
+ // Xử lý trường hợp hiếm gặp khi nodeDOM(from) không trả về gì hợp lệ
29
+ console.warn('Could not find DOM node at cursor position:', e);
30
+ return null;
31
+ }
32
+ if (!domNodeAtPos) return null;
33
+ const boundary = editor.view.dom;
34
+ // Tìm kiếm từ domNodeAtPos lên trên cho đến khi gặp editor container
35
+ let currentElement = domNodeAtPos;
36
+ while(currentElement && currentElement !== boundary){
37
+ if (currentElement.matches(selector)) {
38
+ return currentElement;
39
+ }
40
+ currentElement = currentElement.parentElement;
41
+ }
42
+ return null;
43
+ }
@@ -0,0 +1,4 @@
1
+ export * from './color.util';
2
+ export * from './dom.util';
3
+ export * from './table.util';
4
+ export * from './text.util';
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./color.util"), exports);
6
+ _export_star(require("./dom.util"), exports);
7
+ _export_star(require("./table.util"), exports);
8
+ _export_star(require("./text.util"), exports);
9
+ function _export_star(from, to) {
10
+ Object.keys(from).forEach(function(k) {
11
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
12
+ Object.defineProperty(to, k, {
13
+ enumerable: true,
14
+ get: function() {
15
+ return from[k];
16
+ }
17
+ });
18
+ }
19
+ });
20
+ return from;
21
+ }
@@ -0,0 +1,36 @@
1
+ import { Node as PMNode } from '@tiptap/pm/model';
2
+ import { EditorState } from '@tiptap/pm/state';
3
+ import { TableMap } from '@tiptap/pm/tables';
4
+ import { EditorView } from '@tiptap/pm/view';
5
+ export declare function getCursorCellInfo(state: EditorState): {
6
+ rowIndex: number;
7
+ colIndex: number;
8
+ cellIndex: number;
9
+ tableStart: number;
10
+ tableNode: PMNode;
11
+ map: TableMap;
12
+ } | null;
13
+ /**
14
+ * Lấy tất cả các node cell (tableCell, tableHeader) trong vùng chọn hiện tại.
15
+ */
16
+ interface SelectedCell {
17
+ pos: number;
18
+ node: PMNode;
19
+ }
20
+ export declare function getSelectedCells(state: EditorState): SelectedCell[];
21
+ /**
22
+ * Quyết định giá trị thuộc tính cuối cùng từ một danh sách các cell đã chọn.
23
+ * @param cells Danh sách các cell được chọn.
24
+ * @param attributeName Tên của thuộc tính cần lấy (ví dụ: 'backgroundColor').
25
+ * @returns Giá trị chung, hoặc MIXED_VALUE nếu các giá trị không đồng nhất.
26
+ */
27
+ export declare function getCombinedCellAttributeValue(cells: SelectedCell[], attributeName: string, fallbackValue?: unknown): unknown;
28
+ /**
29
+ * Get the actual table DOM element from a node position
30
+ * Handles both direct table elements and NodeView wrappers
31
+ * Uses :scope selector to avoid selecting nested tables
32
+ *
33
+ * @export For use in other table-related extensions
34
+ */
35
+ export declare function getTableDOMFromView(view: EditorView, tablePos: number): HTMLTableElement | null;
36
+ export {};
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ getCombinedCellAttributeValue: function() {
13
+ return getCombinedCellAttributeValue;
14
+ },
15
+ getCursorCellInfo: function() {
16
+ return getCursorCellInfo;
17
+ },
18
+ getSelectedCells: function() {
19
+ return getSelectedCells;
20
+ },
21
+ getTableDOMFromView: function() {
22
+ return getTableDOMFromView;
23
+ }
24
+ });
25
+ const _tables = require("@tiptap/pm/tables");
26
+ const _lodashes = require("lodash-es");
27
+ function getCursorCellInfo(state) {
28
+ // 1. Dùng selectionCell để tìm vị trí bắt đầu của ô chứa con trỏ
29
+ const cellInfo = (0, _tables.selectionCell)(state);
30
+ // Nếu không tìm thấy (con trỏ không nằm trong ô nào), trả về null
31
+ if (!cellInfo) return null;
32
+ // cellInfo.$pos là một ResolvedPos trỏ tới vị trí bắt đầu của ô
33
+ const table = (0, _tables.findTable)(cellInfo);
34
+ if (!table) return null;
35
+ const map = _tables.TableMap.get(table.node);
36
+ // 2. Lấy vị trí tương đối của ô so với bảng
37
+ const cellOffset = cellInfo.pos - table.start;
38
+ // 3. Tìm chỉ số (index) của ô này trong "bản đồ" của bảng
39
+ const cellIndex = map.map.indexOf(cellOffset);
40
+ if (cellIndex === -1) {
41
+ return null;
42
+ }
43
+ // 4. Từ chỉ số, tính ra rowIndex và colIndex
44
+ const rowIndex = Math.floor(cellIndex / map.width);
45
+ const colIndex = cellIndex % map.width;
46
+ return {
47
+ rowIndex,
48
+ colIndex,
49
+ cellIndex,
50
+ tableStart: table.start,
51
+ tableNode: table.node,
52
+ map
53
+ };
54
+ }
55
+ function getSelectedCells(state) {
56
+ const { selection } = state;
57
+ const cells = [];
58
+ // Trường hợp chọn nhiều ô
59
+ if (selection instanceof _tables.CellSelection) {
60
+ selection.forEachCell((node, pos)=>cells.push({
61
+ pos,
62
+ node
63
+ }));
64
+ return cells;
65
+ }
66
+ // Trường hợp con trỏ nằm trong một ô
67
+ if (selection.empty) {
68
+ // Tìm node cell cha gần nhất
69
+ const cell = selection.$head.node(-1);
70
+ if (!cell || !(cell.type.name === 'tableCell' || cell.type.name === 'tableHeader')) return cells;
71
+ // Lấy vị trí bắt đầu của node cell
72
+ const cellPos = selection.$head.start(-1);
73
+ cells.push({
74
+ pos: cellPos,
75
+ node: cell
76
+ });
77
+ }
78
+ return cells;
79
+ }
80
+ function getCombinedCellAttributeValue(cells, attributeName, fallbackValue) {
81
+ // Không có cell nào được chọn
82
+ if (cells.length === 0) return fallbackValue;
83
+ const firstValue = cells[0].node.attrs[attributeName];
84
+ for(let i = 1; i < cells.length; i++){
85
+ const currentValue = cells[i].node.attrs[attributeName];
86
+ // Dùng isEqual để so sánh object (quan trọng cho thuộc tính 'border')
87
+ if (!(0, _lodashes.isEqual)(currentValue, firstValue)) return undefined;
88
+ }
89
+ return firstValue;
90
+ }
91
+ function getTableDOMFromView(view, tablePos) {
92
+ const nodeDOMEl = view.nodeDOM(tablePos);
93
+ if (!nodeDOMEl) return null;
94
+ // Case 1: nodeDOMEl is the table itself
95
+ if (nodeDOMEl.tagName === 'TABLE') {
96
+ return nodeDOMEl;
97
+ }
98
+ // Case 2: nodeDOMEl is a NodeView wrapper (figure.widget)
99
+ // Structure: figure.widget > table.widget-content
100
+ // Use :scope to only get direct child, avoiding nested tables in cells
101
+ const tableDOM = nodeDOMEl.querySelector(':scope > table.widget-content');
102
+ return tableDOM;
103
+ }
@@ -0,0 +1,9 @@
1
+ import { Editor, MarkRange } from '@tiptap/core';
2
+ export declare function getSelectedText(editor: Editor): string | null;
3
+ /**
4
+ * Tìm instance và range (from, to) của một mark cụ thể tại vị trí con trỏ.
5
+ * @param editor The Tiptap editor instance.
6
+ * @param markName Tên của mark cần tìm (vd: 'link').
7
+ * @returns Object chứa from, to, và mark instance nếu tìm thấy, ngược lại trả về null.
8
+ */
9
+ export declare function getActiveMarkRange(editor: Editor, markName: string): MarkRange | null;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ getActiveMarkRange: function() {
13
+ return getActiveMarkRange;
14
+ },
15
+ getSelectedText: function() {
16
+ return getSelectedText;
17
+ }
18
+ });
19
+ const _state = require("@tiptap/pm/state");
20
+ function getSelectedText(editor) {
21
+ const { state } = editor;
22
+ const { selection } = state;
23
+ // 1. Kiểm tra nếu selection rỗng (chỉ là con trỏ) hoặc không phải là TextSelection
24
+ if (selection.empty || !(selection instanceof _state.TextSelection)) {
25
+ return null;
26
+ }
27
+ // 2. Lấy text giữa điểm đầu và điểm cuối của selection
28
+ const { from, to } = selection;
29
+ return state.doc.textBetween(from, to, ' '); // Trả về text
30
+ }
31
+ function getActiveMarkRange(editor, markName) {
32
+ const { state } = editor;
33
+ const { $from } = state.selection;
34
+ const { doc } = state;
35
+ // 2. Lấy instance của mark tại vị trí con trỏ
36
+ const activeMarks = $from.marks();
37
+ const targetMark = activeMarks.find((mark)=>mark.type.name === markName);
38
+ // Nếu không có mark active, thoát ngay
39
+ if (!targetMark) return null;
40
+ const markType = targetMark.type;
41
+ // 3. Quét ngược về đầu để tìm vị trí `from`
42
+ let from = $from.pos;
43
+ // Giới hạn việc quét trong node cha hiện tại ($from.start())
44
+ while(from > $from.start() && doc.rangeHasMark(from - 1, from, markType)){
45
+ from--;
46
+ }
47
+ // 4. Quét xuôi về cuối để tìm vị trí `to`
48
+ let to = $from.pos;
49
+ // Giới hạn việc quét trong node cha hiện tại ($from.end())
50
+ while(to < $from.end() && doc.rangeHasMark(to, to + 1, markType)){
51
+ to++;
52
+ }
53
+ // 5. Trả về kết quả
54
+ return {
55
+ from,
56
+ to,
57
+ mark: targetMark
58
+ };
59
+ }