@phuong-tran-redoc/document-engine-core 0.0.41 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -115
- package/THIRD-PARTY-NOTICES.txt +67 -0
- package/package.json +42 -31
- package/src/extensions/restricted-editing.extension.js +2 -2
- package/src/extensions/table-resizing.extension.d.ts +1 -1
- package/src/extensions/table-resizing.extension.js +28 -28
- package/src/extensions/table-style.extension.d.ts +3 -1
- package/src/extensions/table-style.extension.js +21 -21
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/kit/default-extensions.d.ts +22 -0
- package/src/kit/default-extensions.js +86 -0
- package/src/kit/generate-html.d.ts +23 -0
- package/src/kit/generate-html.js +40 -0
- package/src/kit/index.d.ts +2 -0
- package/src/kit/index.js +2 -0
- package/src/migrations/doc-migrations.d.ts +27 -0
- package/src/migrations/doc-migrations.js +24 -0
- package/src/migrations/index.d.ts +3 -0
- package/src/migrations/index.js +3 -0
- package/src/migrations/migrate-doc.d.ts +16 -0
- package/src/migrations/migrate-doc.js +32 -0
- package/src/migrations/types.d.ts +25 -0
- package/src/migrations/types.js +4 -0
- package/src/nodes/image-ref.node.d.ts +40 -0
- package/src/nodes/image-ref.node.js +138 -0
- package/src/nodes/index.d.ts +1 -0
- package/src/nodes/index.js +1 -0
- package/src/types/tiptap-html-server.d.js +10 -0
- package/src/__tests__/helpers/editor-factory.d.ts +0 -13
- package/src/__tests__/helpers/editor-factory.js +0 -25
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Node } from '@tiptap/core';
|
|
2
|
+
/**
|
|
3
|
+
* Where the caption sits relative to the image.
|
|
4
|
+
*/
|
|
5
|
+
export type ImageRefCaptionPosition = 'top' | 'bottom';
|
|
6
|
+
/**
|
|
7
|
+
* Attributes carried by an `image-ref` node.
|
|
8
|
+
*
|
|
9
|
+
* Only `imageId` is required. The node is intentionally **presentation-free**:
|
|
10
|
+
* it stores an opaque media id, never a URL or width/style. The real responsive
|
|
11
|
+
* image is resolved downstream (consumer side) from `imageId`.
|
|
12
|
+
*/
|
|
13
|
+
export interface ImageRefAttributes {
|
|
14
|
+
imageId: string;
|
|
15
|
+
/** Plain text only — serialized as `<figcaption>` text content; any nested markup is flattened on round-trip. */
|
|
16
|
+
caption?: string | null;
|
|
17
|
+
captionPosition?: ImageRefCaptionPosition | null;
|
|
18
|
+
}
|
|
19
|
+
export interface ImageRefOptions {
|
|
20
|
+
HTMLAttributes: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
declare module '@tiptap/core' {
|
|
23
|
+
interface Commands<ReturnType> {
|
|
24
|
+
imageRef: {
|
|
25
|
+
/** Insert an `image-ref` block. `imageId` is required. */
|
|
26
|
+
insertImageRef: (attributes: ImageRefAttributes) => ReturnType;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `image-ref` — a semantic, URL-free reference to a media asset.
|
|
32
|
+
*
|
|
33
|
+
* Renders a `<figure data-block="image-ref" data-image-id="…">` (with an optional
|
|
34
|
+
* `<figcaption>`), decoupling document content from the resolved image URL. The
|
|
35
|
+
* consumer hydrates the figure by attribute (`data-image-id`) at render time —
|
|
36
|
+
* the document itself never embeds a URL or any presentation. In the editor the
|
|
37
|
+
* node shows a placeholder (it has no URL to display); resolving and showing the
|
|
38
|
+
* real image is the consumer's / media-picker's job.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ImageRef: Node<ImageRefOptions, any>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from '@tiptap/core';
|
|
2
|
+
/**
|
|
3
|
+
* `image-ref` — a semantic, URL-free reference to a media asset.
|
|
4
|
+
*
|
|
5
|
+
* Renders a `<figure data-block="image-ref" data-image-id="…">` (with an optional
|
|
6
|
+
* `<figcaption>`), decoupling document content from the resolved image URL. The
|
|
7
|
+
* consumer hydrates the figure by attribute (`data-image-id`) at render time —
|
|
8
|
+
* the document itself never embeds a URL or any presentation. In the editor the
|
|
9
|
+
* node shows a placeholder (it has no URL to display); resolving and showing the
|
|
10
|
+
* real image is the consumer's / media-picker's job.
|
|
11
|
+
*/ export const ImageRef = Node.create({
|
|
12
|
+
name: 'imageRef',
|
|
13
|
+
group: 'block',
|
|
14
|
+
atom: true,
|
|
15
|
+
selectable: true,
|
|
16
|
+
draggable: true,
|
|
17
|
+
addOptions () {
|
|
18
|
+
return {
|
|
19
|
+
HTMLAttributes: {}
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
addAttributes () {
|
|
23
|
+
return {
|
|
24
|
+
imageId: {
|
|
25
|
+
default: null,
|
|
26
|
+
parseHTML: (element)=>element.getAttribute('data-image-id'),
|
|
27
|
+
renderHTML: (attributes)=>{
|
|
28
|
+
if (!attributes['imageId']) return {};
|
|
29
|
+
return {
|
|
30
|
+
'data-image-id': attributes['imageId']
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
caption: {
|
|
35
|
+
default: null,
|
|
36
|
+
// Caption is rendered as <figcaption> text content (below), not a data
|
|
37
|
+
// attribute — so parse it back from the figcaption child for round-trip.
|
|
38
|
+
parseHTML: (element)=>{
|
|
39
|
+
const figcaption = element.querySelector(':scope > figcaption');
|
|
40
|
+
return figcaption?.textContent?.trim() || null;
|
|
41
|
+
},
|
|
42
|
+
renderHTML: ()=>({})
|
|
43
|
+
},
|
|
44
|
+
captionPosition: {
|
|
45
|
+
default: null,
|
|
46
|
+
// Normalize to the typed union: absent attribute stays `null` (preserves
|
|
47
|
+
// the byte-identical round-trip), any present-but-unknown value coerces to
|
|
48
|
+
// `'bottom'` so a non-`'top' | 'bottom'` string can never leak through.
|
|
49
|
+
parseHTML: (element)=>{
|
|
50
|
+
const raw = element.getAttribute('data-caption-position');
|
|
51
|
+
if (raw === null) return null;
|
|
52
|
+
return raw === 'top' ? 'top' : 'bottom';
|
|
53
|
+
},
|
|
54
|
+
renderHTML: (attributes)=>{
|
|
55
|
+
if (!attributes['captionPosition']) return {};
|
|
56
|
+
return {
|
|
57
|
+
'data-caption-position': attributes['captionPosition']
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
parseHTML () {
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
tag: 'figure[data-block="image-ref"]'
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
},
|
|
70
|
+
renderHTML ({ HTMLAttributes, node }) {
|
|
71
|
+
const { caption } = node.attrs;
|
|
72
|
+
const figure = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
73
|
+
'data-block': 'image-ref'
|
|
74
|
+
});
|
|
75
|
+
// No <img> is emitted — the asset is URL-free. The caption order relative to
|
|
76
|
+
// the (not-yet-resolved) image is carried by `data-caption-position` for the
|
|
77
|
+
// consumer to honor at hydration time; the figure itself just holds the text.
|
|
78
|
+
if (!caption) {
|
|
79
|
+
return [
|
|
80
|
+
'figure',
|
|
81
|
+
figure
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
const figcaption = [
|
|
85
|
+
'figcaption',
|
|
86
|
+
{},
|
|
87
|
+
caption
|
|
88
|
+
];
|
|
89
|
+
return [
|
|
90
|
+
'figure',
|
|
91
|
+
figure,
|
|
92
|
+
figcaption
|
|
93
|
+
];
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Editor-only placeholder. The DOM here is chrome (never serialized — that is
|
|
97
|
+
* `renderHTML`'s job); it just shows which media the block references.
|
|
98
|
+
*/ addNodeView () {
|
|
99
|
+
return ({ node })=>{
|
|
100
|
+
const { imageId, caption, captionPosition } = node.attrs;
|
|
101
|
+
const dom = document.createElement('figure');
|
|
102
|
+
dom.classList.add('image-ref-placeholder');
|
|
103
|
+
dom.setAttribute('data-block', 'image-ref');
|
|
104
|
+
if (imageId) dom.setAttribute('data-image-id', imageId);
|
|
105
|
+
if (captionPosition) dom.setAttribute('data-caption-position', captionPosition);
|
|
106
|
+
dom.style.cssText = 'margin:1rem 0;display:flex;flex-direction:column;gap:.5rem;';
|
|
107
|
+
const box = document.createElement('div');
|
|
108
|
+
box.style.cssText = 'display:flex;align-items:center;gap:.5rem;padding:1rem;border:1px dashed currentColor;' + 'border-radius:.5rem;opacity:.65;font-size:.875rem;justify-content:center;';
|
|
109
|
+
box.textContent = `🖼 image-ref · id: ${imageId || '—'}`;
|
|
110
|
+
const figcaption = document.createElement('figcaption');
|
|
111
|
+
figcaption.style.cssText = 'font-size:.8125rem;opacity:.7;text-align:center;';
|
|
112
|
+
if (caption) figcaption.textContent = caption;
|
|
113
|
+
if (caption && captionPosition === 'top') {
|
|
114
|
+
dom.append(figcaption, box);
|
|
115
|
+
} else {
|
|
116
|
+
dom.append(box);
|
|
117
|
+
if (caption) dom.append(figcaption);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
dom
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
addCommands () {
|
|
125
|
+
return {
|
|
126
|
+
insertImageRef: (attributes)=>({ chain })=>{
|
|
127
|
+
if (!attributes.imageId) {
|
|
128
|
+
console.error('[ImageRef] imageId is required.');
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return chain().insertContent({
|
|
132
|
+
type: this.name,
|
|
133
|
+
attrs: attributes
|
|
134
|
+
}).run();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
});
|
package/src/nodes/index.d.ts
CHANGED
package/src/nodes/index.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type shim for the `@tiptap/html/server` subpath.
|
|
3
|
+
*
|
|
4
|
+
* `@tiptap/html` only exposes its Node-safe (`/server`) entry through the
|
|
5
|
+
* package `exports` map. This library compiles under `moduleResolution: node10`,
|
|
6
|
+
* which ignores `exports` for type resolution, so TypeScript cannot find the
|
|
7
|
+
* subpath's declarations on its own. The runtime (Jest/Node) resolves `/server`
|
|
8
|
+
* via the exports map fine — this declaration only satisfies the type checker by
|
|
9
|
+
* pointing it at the same public types as the bare package.
|
|
10
|
+
*/
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Editor } from '@tiptap/core';
|
|
2
|
-
import type { Extensions } from '@tiptap/core';
|
|
3
|
-
/**
|
|
4
|
-
* Create a test editor instance with minimal required extensions
|
|
5
|
-
* @param extensions Additional extensions to include
|
|
6
|
-
* @param content Initial content (HTML string or JSON)
|
|
7
|
-
* @returns Editor instance
|
|
8
|
-
*/
|
|
9
|
-
export declare function createTestEditor(extensions?: Extensions, content?: string | Record<string, unknown>): Editor;
|
|
10
|
-
/**
|
|
11
|
-
* Destroy editor and cleanup
|
|
12
|
-
*/
|
|
13
|
-
export declare function destroyEditor(editor: Editor): void;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Editor } from '@tiptap/core';
|
|
2
|
-
import { Document } from '@tiptap/extension-document';
|
|
3
|
-
import { Paragraph } from '@tiptap/extension-paragraph';
|
|
4
|
-
import { Text } from '@tiptap/extension-text';
|
|
5
|
-
/**
|
|
6
|
-
* Create a test editor instance with minimal required extensions
|
|
7
|
-
* @param extensions Additional extensions to include
|
|
8
|
-
* @param content Initial content (HTML string or JSON)
|
|
9
|
-
* @returns Editor instance
|
|
10
|
-
*/ export function createTestEditor(extensions = [], content = '') {
|
|
11
|
-
return new Editor({
|
|
12
|
-
extensions: [
|
|
13
|
-
Document,
|
|
14
|
-
Paragraph,
|
|
15
|
-
Text,
|
|
16
|
-
...extensions
|
|
17
|
-
],
|
|
18
|
-
content
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Destroy editor and cleanup
|
|
23
|
-
*/ export function destroyEditor(editor) {
|
|
24
|
-
editor.destroy();
|
|
25
|
-
}
|