@moldable-ai/editor 0.1.7 → 0.1.9
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/components/markdown-editor.d.ts +12 -3
- package/dist/components/markdown-editor.d.ts.map +1 -1
- package/dist/components/markdown-editor.js +23 -9
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/lib/lexical/editor-theme.d.ts +2 -0
- package/dist/lib/lexical/editor-theme.d.ts.map +1 -1
- package/dist/lib/lexical/editor-theme.js +2 -0
- package/dist/lib/lexical/file-reference-plugin.d.ts +9 -0
- package/dist/lib/lexical/file-reference-plugin.d.ts.map +1 -0
- package/dist/lib/lexical/file-reference-plugin.js +99 -0
- package/dist/lib/lexical/find-replace-plugin.d.ts +31 -0
- package/dist/lib/lexical/find-replace-plugin.d.ts.map +1 -0
- package/dist/lib/lexical/find-replace-plugin.js +205 -0
- package/dist/lib/lexical/headless-editor.d.ts.map +1 -1
- package/dist/lib/lexical/headless-editor.js +6 -0
- package/dist/lib/lexical/image-component.d.ts +8 -0
- package/dist/lib/lexical/image-component.d.ts.map +1 -0
- package/dist/lib/lexical/image-component.js +99 -0
- package/dist/lib/lexical/image-node.d.ts +36 -0
- package/dist/lib/lexical/image-node.d.ts.map +1 -0
- package/dist/lib/lexical/image-node.js +105 -0
- package/dist/lib/lexical/markdown-transformers.d.ts +4 -1
- package/dist/lib/lexical/markdown-transformers.d.ts.map +1 -1
- package/dist/lib/lexical/markdown-transformers.js +78 -0
- package/dist/lib/lexical/media-context.d.ts +7 -0
- package/dist/lib/lexical/media-context.d.ts.map +1 -0
- package/dist/lib/lexical/media-context.js +7 -0
- package/dist/lib/lexical/media-node.d.ts +32 -0
- package/dist/lib/lexical/media-node.d.ts.map +1 -0
- package/dist/lib/lexical/media-node.js +97 -0
- package/dist/lib/lexical/media-plugin.d.ts +12 -0
- package/dist/lib/lexical/media-plugin.d.ts.map +1 -0
- package/dist/lib/lexical/media-plugin.js +139 -0
- package/dist/lib/lexical/sync-plugin.d.ts +1 -0
- package/dist/lib/lexical/sync-plugin.d.ts.map +1 -1
- package/dist/lib/lexical/sync-plugin.js +13 -5
- package/package.json +19 -18
- package/src/styles/index.css +130 -0
- package/dist/lib/lexical/translation.test.d.ts +0 -2
- package/dist/lib/lexical/translation.test.d.ts.map +0 -1
- package/dist/lib/lexical/translation.test.js +0 -329
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
|
|
5
|
+
import { mergeRegister } from '@lexical/utils';
|
|
6
|
+
import { $getNodeByKey, $getSelection, $isNodeSelection, CLICK_COMMAND, COMMAND_PRIORITY_LOW, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, } from 'lexical';
|
|
7
|
+
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
8
|
+
import { $isImageNode } from './image-node';
|
|
9
|
+
import { useMediaResolver } from './media-context';
|
|
10
|
+
const MIN_IMAGE_WIDTH = 96;
|
|
11
|
+
function clamp(value, min, max) {
|
|
12
|
+
return Math.min(Math.max(value, min), max);
|
|
13
|
+
}
|
|
14
|
+
export function ImageComponent({ altText, nodeKey, src, width, }) {
|
|
15
|
+
const [editor] = useLexicalComposerContext();
|
|
16
|
+
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
|
|
17
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
18
|
+
const imageRef = useRef(null);
|
|
19
|
+
const resizeStartRef = useRef(null);
|
|
20
|
+
const resolveMediaUrl = useMediaResolver();
|
|
21
|
+
const resolvedSrc = resolveMediaUrl?.(src) ?? src;
|
|
22
|
+
const removeSelectedImage = useCallback(() => {
|
|
23
|
+
const selection = $getSelection();
|
|
24
|
+
if (!$isNodeSelection(selection) || !selection.has(nodeKey))
|
|
25
|
+
return false;
|
|
26
|
+
const node = $getNodeByKey(nodeKey);
|
|
27
|
+
if (!$isImageNode(node))
|
|
28
|
+
return false;
|
|
29
|
+
node.remove();
|
|
30
|
+
return true;
|
|
31
|
+
}, [nodeKey]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
return mergeRegister(editor.registerCommand(CLICK_COMMAND, (event) => {
|
|
34
|
+
if (isResizing)
|
|
35
|
+
return true;
|
|
36
|
+
if (event.target !== imageRef.current)
|
|
37
|
+
return false;
|
|
38
|
+
if (!event.shiftKey)
|
|
39
|
+
clearSelection();
|
|
40
|
+
setSelected(!isSelected);
|
|
41
|
+
return true;
|
|
42
|
+
}, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_DELETE_COMMAND, removeSelectedImage, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_BACKSPACE_COMMAND, removeSelectedImage, COMMAND_PRIORITY_LOW));
|
|
43
|
+
}, [
|
|
44
|
+
clearSelection,
|
|
45
|
+
editor,
|
|
46
|
+
isResizing,
|
|
47
|
+
isSelected,
|
|
48
|
+
removeSelectedImage,
|
|
49
|
+
setSelected,
|
|
50
|
+
]);
|
|
51
|
+
const handleResizePointerDown = (event) => {
|
|
52
|
+
const image = imageRef.current;
|
|
53
|
+
const root = editor.getRootElement();
|
|
54
|
+
if (!image || !root)
|
|
55
|
+
return;
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
event.stopPropagation();
|
|
58
|
+
const rootRect = root.getBoundingClientRect();
|
|
59
|
+
const imageRect = image.getBoundingClientRect();
|
|
60
|
+
resizeStartRef.current = {
|
|
61
|
+
maxWidth: Math.max(MIN_IMAGE_WIDTH, rootRect.width - 48),
|
|
62
|
+
startWidth: imageRect.width,
|
|
63
|
+
startX: event.clientX,
|
|
64
|
+
};
|
|
65
|
+
setIsResizing(true);
|
|
66
|
+
const handlePointerMove = (pointerEvent) => {
|
|
67
|
+
const start = resizeStartRef.current;
|
|
68
|
+
if (!start)
|
|
69
|
+
return;
|
|
70
|
+
const nextWidth = clamp(Math.round(start.startWidth + pointerEvent.clientX - start.startX), MIN_IMAGE_WIDTH, start.maxWidth);
|
|
71
|
+
if (imageRef.current)
|
|
72
|
+
imageRef.current.style.width = `${nextWidth}px`;
|
|
73
|
+
};
|
|
74
|
+
const handlePointerUp = () => {
|
|
75
|
+
const nextWidth = imageRef.current?.getBoundingClientRect().width;
|
|
76
|
+
resizeStartRef.current = null;
|
|
77
|
+
setIsResizing(false);
|
|
78
|
+
document.body.style.userSelect = '';
|
|
79
|
+
document.removeEventListener('pointermove', handlePointerMove);
|
|
80
|
+
document.removeEventListener('pointerup', handlePointerUp);
|
|
81
|
+
if (!nextWidth)
|
|
82
|
+
return;
|
|
83
|
+
editor.update(() => {
|
|
84
|
+
const node = $getNodeByKey(nodeKey);
|
|
85
|
+
if ($isImageNode(node))
|
|
86
|
+
node.setWidth(Math.round(nextWidth));
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
document.body.style.userSelect = 'none';
|
|
90
|
+
document.addEventListener('pointermove', handlePointerMove);
|
|
91
|
+
document.addEventListener('pointerup', handlePointerUp);
|
|
92
|
+
};
|
|
93
|
+
return (_jsxs("span", { className: [
|
|
94
|
+
'lexical-image-shell',
|
|
95
|
+
(isSelected || isResizing) && 'lexical-image-shell-selected',
|
|
96
|
+
]
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
.join(' '), draggable: isSelected && !isResizing, children: [_jsx("img", { ref: imageRef, src: resolvedSrc, alt: altText, className: "lexical-image", draggable: false, style: { width: width ? `${width}px` : undefined } }), (isSelected || isResizing) && (_jsx("span", { "aria-hidden": "true", className: "lexical-image-resize-handle", onPointerDown: handleResizePointerDown }))] }));
|
|
99
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DOMConversionMap, DOMExportOutput, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical';
|
|
2
|
+
import type { JSX } from 'react';
|
|
3
|
+
import { DecoratorNode } from 'lexical';
|
|
4
|
+
export type ImagePayload = {
|
|
5
|
+
altText?: string;
|
|
6
|
+
key?: NodeKey;
|
|
7
|
+
src: string;
|
|
8
|
+
width?: number;
|
|
9
|
+
};
|
|
10
|
+
export type SerializedImageNode = SerializedLexicalNode & {
|
|
11
|
+
altText: string;
|
|
12
|
+
src: string;
|
|
13
|
+
width?: number;
|
|
14
|
+
};
|
|
15
|
+
export declare class ImageNode extends DecoratorNode<JSX.Element> {
|
|
16
|
+
__altText: string;
|
|
17
|
+
__src: string;
|
|
18
|
+
__width?: number;
|
|
19
|
+
static getType(): string;
|
|
20
|
+
static clone(node: ImageNode): ImageNode;
|
|
21
|
+
static importJSON(serializedNode: SerializedImageNode): ImageNode;
|
|
22
|
+
static importDOM(): DOMConversionMap | null;
|
|
23
|
+
constructor(src: string, altText?: string, width?: number, key?: NodeKey);
|
|
24
|
+
exportJSON(): SerializedImageNode;
|
|
25
|
+
exportDOM(): DOMExportOutput;
|
|
26
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
27
|
+
updateDOM(): false;
|
|
28
|
+
getSrc(): string;
|
|
29
|
+
getAltText(): string;
|
|
30
|
+
getWidth(): number | undefined;
|
|
31
|
+
setWidth(width: number | undefined): this;
|
|
32
|
+
decorate(): JSX.Element;
|
|
33
|
+
}
|
|
34
|
+
export declare function $createImageNode({ altText, key, src, width, }: ImagePayload): ImageNode;
|
|
35
|
+
export declare function $isImageNode(node: LexicalNode | null | undefined): node is ImageNode;
|
|
36
|
+
//# sourceMappingURL=image-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-node.d.ts","sourceRoot":"","sources":["../../../src/lib/lexical/image-node.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEhB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,OAAO,EACP,qBAAqB,EACtB,MAAM,SAAS,CAAA;AAChB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAyB,aAAa,EAAE,MAAM,SAAS,CAAA;AAG9D,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG;IACxD,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAyBD,qBAAa,SAAU,SAAQ,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;IACvD,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS;IAIxC,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,mBAAmB,GAAG,SAAS;IAQjE,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;gBAS/B,GAAG,EAAE,MAAM,EAAE,OAAO,SAAK,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO;IAOpE,UAAU,IAAI,mBAAmB;IASjC,SAAS,IAAI,eAAe;IAQ5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAO5C,SAAS,IAAI,KAAK;IAIlB,MAAM,IAAI,MAAM;IAIhB,UAAU,IAAI,MAAM;IAIpB,QAAQ,IAAI,MAAM,GAAG,SAAS;IAI9B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAMzC,QAAQ,IAAI,GAAG,CAAC,OAAO;CAUxB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,GAAG,EACH,GAAG,EACH,KAAK,GACN,EAAE,YAAY,GAAG,SAAS,CAE1B;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACnC,IAAI,IAAI,SAAS,CAEnB"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { $applyNodeReplacement, DecoratorNode } from 'lexical';
|
|
4
|
+
import { ImageComponent } from './image-component';
|
|
5
|
+
function $convertImageElement(domNode) {
|
|
6
|
+
const image = domNode;
|
|
7
|
+
const src = image.getAttribute('src');
|
|
8
|
+
if (!src || src.startsWith('file:///'))
|
|
9
|
+
return null;
|
|
10
|
+
const widthAttribute = image.getAttribute('width');
|
|
11
|
+
const styleWidth = image.style.width.endsWith('px')
|
|
12
|
+
? Number.parseInt(image.style.width, 10)
|
|
13
|
+
: undefined;
|
|
14
|
+
const width = widthAttribute
|
|
15
|
+
? Number.parseInt(widthAttribute, 10)
|
|
16
|
+
: styleWidth;
|
|
17
|
+
return {
|
|
18
|
+
node: $createImageNode({
|
|
19
|
+
altText: image.getAttribute('alt') ?? '',
|
|
20
|
+
src,
|
|
21
|
+
width: Number.isFinite(width) ? width : undefined,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export class ImageNode extends DecoratorNode {
|
|
26
|
+
__altText;
|
|
27
|
+
__src;
|
|
28
|
+
__width;
|
|
29
|
+
static getType() {
|
|
30
|
+
return 'image';
|
|
31
|
+
}
|
|
32
|
+
static clone(node) {
|
|
33
|
+
return new ImageNode(node.__src, node.__altText, node.__width, node.__key);
|
|
34
|
+
}
|
|
35
|
+
static importJSON(serializedNode) {
|
|
36
|
+
return $createImageNode({
|
|
37
|
+
altText: serializedNode.altText,
|
|
38
|
+
src: serializedNode.src,
|
|
39
|
+
width: serializedNode.width,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
static importDOM() {
|
|
43
|
+
return {
|
|
44
|
+
img: () => ({
|
|
45
|
+
conversion: $convertImageElement,
|
|
46
|
+
priority: 0,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
constructor(src, altText = '', width, key) {
|
|
51
|
+
super(key);
|
|
52
|
+
this.__src = src;
|
|
53
|
+
this.__altText = altText;
|
|
54
|
+
this.__width = width;
|
|
55
|
+
}
|
|
56
|
+
exportJSON() {
|
|
57
|
+
return {
|
|
58
|
+
...super.exportJSON(),
|
|
59
|
+
altText: this.__altText,
|
|
60
|
+
src: this.__src,
|
|
61
|
+
width: this.__width,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
exportDOM() {
|
|
65
|
+
const element = document.createElement('img');
|
|
66
|
+
element.setAttribute('src', this.__src);
|
|
67
|
+
element.setAttribute('alt', this.__altText);
|
|
68
|
+
if (this.__width)
|
|
69
|
+
element.setAttribute('width', String(this.__width));
|
|
70
|
+
return { element };
|
|
71
|
+
}
|
|
72
|
+
createDOM(config) {
|
|
73
|
+
const element = document.createElement('span');
|
|
74
|
+
const className = config.theme.image;
|
|
75
|
+
if (typeof className === 'string')
|
|
76
|
+
element.className = className;
|
|
77
|
+
return element;
|
|
78
|
+
}
|
|
79
|
+
updateDOM() {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
getSrc() {
|
|
83
|
+
return this.getLatest().__src;
|
|
84
|
+
}
|
|
85
|
+
getAltText() {
|
|
86
|
+
return this.getLatest().__altText;
|
|
87
|
+
}
|
|
88
|
+
getWidth() {
|
|
89
|
+
return this.getLatest().__width;
|
|
90
|
+
}
|
|
91
|
+
setWidth(width) {
|
|
92
|
+
const writable = this.getWritable();
|
|
93
|
+
writable.__width = width;
|
|
94
|
+
return writable;
|
|
95
|
+
}
|
|
96
|
+
decorate() {
|
|
97
|
+
return (_jsx(ImageComponent, { altText: this.__altText, nodeKey: this.getKey(), src: this.__src, width: this.__width }));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function $createImageNode({ altText, key, src, width, }) {
|
|
101
|
+
return $applyNodeReplacement(new ImageNode(src, altText, width, key));
|
|
102
|
+
}
|
|
103
|
+
export function $isImageNode(node) {
|
|
104
|
+
return node instanceof ImageNode;
|
|
105
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { ElementTransformer, Transformer } from '@lexical/markdown';
|
|
1
|
+
import type { ElementTransformer, TextMatchTransformer, Transformer } from '@lexical/markdown';
|
|
2
2
|
import type { ElementNode } from 'lexical';
|
|
3
3
|
/**
|
|
4
4
|
* Horizontal rule transformer - supports ---, ***, and ___
|
|
5
5
|
*/
|
|
6
6
|
export declare const HR: ElementTransformer;
|
|
7
|
+
export declare const IMAGE: TextMatchTransformer;
|
|
8
|
+
export declare const OBSIDIAN_IMAGE_EMBED: TextMatchTransformer;
|
|
9
|
+
export declare const MEDIA: TextMatchTransformer;
|
|
7
10
|
/**
|
|
8
11
|
* All markdown transformers
|
|
9
12
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-transformers.d.ts","sourceRoot":"","sources":["../../../src/lib/lexical/markdown-transformers.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"markdown-transformers.d.ts","sourceRoot":"","sources":["../../../src/lib/lexical/markdown-transformers.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,SAAS,CAAA;AAUvD;;GAEG;AACH,eAAO,MAAM,EAAE,EAAE,kBAgBhB,CAAA;AAUD,eAAO,MAAM,KAAK,EAAE,oBAuBnB,CAAA;AAED,eAAO,MAAM,oBAAoB,EAAE,oBAyBlC,CAAA;AAMD,eAAO,MAAM,KAAK,EAAE,oBAiBnB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,WAAW,CAUnD,CAAA;AAED;;GAEG;AACH,UAAU,0BAA0B;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;IACjC,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,wBAAwB,CAAC,EAAE,OAAO,CAAA;CACnC;AAED;;GAEG;AACH,UAAU,wBAAwB;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;IACjC,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,QAAQ,EACR,YAAmC,EACnC,IAAI,EACJ,sBAA6B,EAC7B,wBAAgC,GACjC,EAAE,0BAA0B,GAAG,IAAI,CAQnC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,YAAmC,EACnC,IAAI,EACJ,sBAA6B,GAC9B,GAAE,wBAA6B,GAAG,MAAM,CAExC"}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { $createHorizontalRuleNode, $isHorizontalRuleNode, HorizontalRuleNode, } from '@lexical/extension';
|
|
6
6
|
import { CHECK_LIST, ELEMENT_TRANSFORMERS, MULTILINE_ELEMENT_TRANSFORMERS, TEXT_FORMAT_TRANSFORMERS, TEXT_MATCH_TRANSFORMERS, $convertFromMarkdownString as lexicalConvertFromMarkdown, $convertToMarkdownString as lexicalConvertToMarkdown, } from '@lexical/markdown';
|
|
7
|
+
import { $createTextNode } from 'lexical';
|
|
8
|
+
import { $createImageNode, $isImageNode, ImageNode } from './image-node';
|
|
9
|
+
import { $createMediaNode, $isMediaNode, MediaNode, } from './media-node';
|
|
7
10
|
/**
|
|
8
11
|
* Horizontal rule transformer - supports ---, ***, and ___
|
|
9
12
|
*/
|
|
@@ -25,6 +28,78 @@ export const HR = {
|
|
|
25
28
|
},
|
|
26
29
|
type: 'element',
|
|
27
30
|
};
|
|
31
|
+
function markdownImageText(altText, src, width) {
|
|
32
|
+
const escapedAlt = altText.replaceAll('[', '\\[').replaceAll(']', '\\]');
|
|
33
|
+
const escapedSrc = src.includes(' ') ? encodeURI(src) : src;
|
|
34
|
+
return width
|
|
35
|
+
? `{width=${width}}`
|
|
36
|
+
: ``;
|
|
37
|
+
}
|
|
38
|
+
export const IMAGE = {
|
|
39
|
+
dependencies: [ImageNode],
|
|
40
|
+
export: (node) => {
|
|
41
|
+
if (!$isImageNode(node))
|
|
42
|
+
return null;
|
|
43
|
+
return markdownImageText(node.getAltText(), node.getSrc(), node.getWidth());
|
|
44
|
+
},
|
|
45
|
+
importRegExp: /!\[([^\]]*)\]\(([^)\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?\)(?:\{width=(\d+)\})?/,
|
|
46
|
+
regExp: /!\[([^\]]*)\]\(([^)\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?\)(?:\{width=(\d+)\})?$/,
|
|
47
|
+
replace: (textNode, match) => {
|
|
48
|
+
const [, altText = '', src = '', , widthMatch] = match;
|
|
49
|
+
const width = widthMatch ? Number.parseInt(widthMatch, 10) : undefined;
|
|
50
|
+
textNode.replace($createImageNode({
|
|
51
|
+
altText,
|
|
52
|
+
src,
|
|
53
|
+
width: Number.isFinite(width) ? width : undefined,
|
|
54
|
+
}));
|
|
55
|
+
},
|
|
56
|
+
trigger: ')',
|
|
57
|
+
type: 'text-match',
|
|
58
|
+
};
|
|
59
|
+
export const OBSIDIAN_IMAGE_EMBED = {
|
|
60
|
+
dependencies: [ImageNode],
|
|
61
|
+
importRegExp: /!\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/,
|
|
62
|
+
regExp: /!\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/,
|
|
63
|
+
replace: (textNode, match) => {
|
|
64
|
+
const [, src = '', labelOrWidth = ''] = match;
|
|
65
|
+
const width = /^\d+$/.test(labelOrWidth)
|
|
66
|
+
? Number.parseInt(labelOrWidth, 10)
|
|
67
|
+
: undefined;
|
|
68
|
+
const altText = width ? src.split('/').pop() : labelOrWidth || src.split('/').pop();
|
|
69
|
+
if (!/\.(avif|gif|jpe?g|png|svg|webp)$/i.test(src)) {
|
|
70
|
+
return $createTextNode(match[0]);
|
|
71
|
+
}
|
|
72
|
+
textNode.replace($createImageNode({
|
|
73
|
+
altText: altText ?? '',
|
|
74
|
+
src,
|
|
75
|
+
width,
|
|
76
|
+
}));
|
|
77
|
+
},
|
|
78
|
+
trigger: ']',
|
|
79
|
+
type: 'text-match',
|
|
80
|
+
};
|
|
81
|
+
function mediaMarkdown(kind, src) {
|
|
82
|
+
return `<${kind} controls src="${src.replaceAll('"', '"')}"></${kind}>`;
|
|
83
|
+
}
|
|
84
|
+
export const MEDIA = {
|
|
85
|
+
dependencies: [MediaNode],
|
|
86
|
+
export: (node) => {
|
|
87
|
+
if (!$isMediaNode(node))
|
|
88
|
+
return null;
|
|
89
|
+
return mediaMarkdown(node.getKind(), node.getSrc());
|
|
90
|
+
},
|
|
91
|
+
importRegExp: /<(video|audio)\b[^>]*\bsrc=["']([^"']+)["'][^>]*>(?:<\/\1>)?/,
|
|
92
|
+
regExp: /<(video|audio)\b[^>]*\bsrc=["']([^"']+)["'][^>]*>(?:<\/\1>)?$/,
|
|
93
|
+
replace: (textNode, match) => {
|
|
94
|
+
const [, kind, src] = match;
|
|
95
|
+
if ((kind !== 'video' && kind !== 'audio') || !src) {
|
|
96
|
+
return $createTextNode(match[0]);
|
|
97
|
+
}
|
|
98
|
+
textNode.replace($createMediaNode({ kind, src }));
|
|
99
|
+
},
|
|
100
|
+
trigger: '>',
|
|
101
|
+
type: 'text-match',
|
|
102
|
+
};
|
|
28
103
|
/**
|
|
29
104
|
* All markdown transformers
|
|
30
105
|
*/
|
|
@@ -34,6 +109,9 @@ export const markdownTransformers = [
|
|
|
34
109
|
...ELEMENT_TRANSFORMERS,
|
|
35
110
|
...MULTILINE_ELEMENT_TRANSFORMERS,
|
|
36
111
|
...TEXT_FORMAT_TRANSFORMERS,
|
|
112
|
+
IMAGE,
|
|
113
|
+
OBSIDIAN_IMAGE_EMBED,
|
|
114
|
+
MEDIA,
|
|
37
115
|
...TEXT_MATCH_TRANSFORMERS,
|
|
38
116
|
];
|
|
39
117
|
/**
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type MediaContextValue = {
|
|
2
|
+
resolveMediaUrl?: (src: string) => string;
|
|
3
|
+
};
|
|
4
|
+
export declare const MediaProvider: import("react").Provider<MediaContextValue>;
|
|
5
|
+
export declare function useMediaResolver(): ((src: string) => string) | undefined;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=media-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-context.d.ts","sourceRoot":"","sources":["../../../src/lib/lexical/media-context.tsx"],"names":[],"mappings":"AAIA,KAAK,iBAAiB,GAAG;IACvB,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C,CAAA;AAID,eAAO,MAAM,aAAa,6CAAwB,CAAA;AAElD,wBAAgB,gBAAgB,WAPN,MAAM,KAAK,MAAM,cAS1C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DOMConversionMap, DOMExportOutput, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical';
|
|
2
|
+
import type { JSX } from 'react';
|
|
3
|
+
import { DecoratorNode } from 'lexical';
|
|
4
|
+
export type MediaKind = 'audio' | 'video';
|
|
5
|
+
export type MediaPayload = {
|
|
6
|
+
kind: MediaKind;
|
|
7
|
+
key?: NodeKey;
|
|
8
|
+
src: string;
|
|
9
|
+
};
|
|
10
|
+
export type SerializedMediaNode = SerializedLexicalNode & {
|
|
11
|
+
kind: MediaKind;
|
|
12
|
+
src: string;
|
|
13
|
+
};
|
|
14
|
+
export declare class MediaNode extends DecoratorNode<JSX.Element> {
|
|
15
|
+
__kind: MediaKind;
|
|
16
|
+
__src: string;
|
|
17
|
+
static getType(): string;
|
|
18
|
+
static clone(node: MediaNode): MediaNode;
|
|
19
|
+
static importJSON(serializedNode: SerializedMediaNode): MediaNode;
|
|
20
|
+
static importDOM(): DOMConversionMap | null;
|
|
21
|
+
constructor(kind: MediaKind, src: string, key?: NodeKey);
|
|
22
|
+
exportJSON(): SerializedMediaNode;
|
|
23
|
+
exportDOM(): DOMExportOutput;
|
|
24
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
25
|
+
updateDOM(): false;
|
|
26
|
+
getKind(): MediaKind;
|
|
27
|
+
getSrc(): string;
|
|
28
|
+
decorate(): JSX.Element;
|
|
29
|
+
}
|
|
30
|
+
export declare function $createMediaNode({ kind, key, src }: MediaPayload): MediaNode;
|
|
31
|
+
export declare function $isMediaNode(node: LexicalNode | null | undefined): node is MediaNode;
|
|
32
|
+
//# sourceMappingURL=media-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-node.d.ts","sourceRoot":"","sources":["../../../src/lib/lexical/media-node.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEhB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,OAAO,EACP,qBAAqB,EACtB,MAAM,SAAS,CAAA;AAChB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAyB,aAAa,EAAE,MAAM,SAAS,CAAA;AAG9D,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;AAEzC,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,SAAS,CAAA;IACf,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG;IACxD,IAAI,EAAE,SAAS,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AA4BD,qBAAa,SAAU,SAAQ,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;IACvD,MAAM,EAAE,SAAS,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IAEb,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS;IAIxC,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,mBAAmB,GAAG,SAAS;IAOjE,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;gBAa/B,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO;IAMvD,UAAU,IAAI,mBAAmB;IAQjC,SAAS,IAAI,eAAe;IAO5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAO5C,SAAS,IAAI,KAAK;IAIlB,OAAO,IAAI,SAAS;IAIpB,MAAM,IAAI,MAAM;IAIhB,QAAQ,IAAI,GAAG,CAAC,OAAO;CAGxB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,YAAY,GAAG,SAAS,CAE5E;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACnC,IAAI,IAAI,SAAS,CAEnB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { RichMediaPlayer } from '@moldable-ai/ui';
|
|
4
|
+
import { $applyNodeReplacement, DecoratorNode } from 'lexical';
|
|
5
|
+
import { useMediaResolver } from './media-context';
|
|
6
|
+
function mediaKindFromElement(element) {
|
|
7
|
+
if (element.tagName.toLowerCase() === 'audio')
|
|
8
|
+
return 'audio';
|
|
9
|
+
if (element.tagName.toLowerCase() === 'video')
|
|
10
|
+
return 'video';
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function $convertMediaElement(domNode) {
|
|
14
|
+
if (!(domNode instanceof HTMLElement))
|
|
15
|
+
return null;
|
|
16
|
+
const kind = mediaKindFromElement(domNode);
|
|
17
|
+
const src = domNode.getAttribute('src');
|
|
18
|
+
if (!kind || !src)
|
|
19
|
+
return null;
|
|
20
|
+
return { node: $createMediaNode({ kind, src }) };
|
|
21
|
+
}
|
|
22
|
+
function MediaComponent({ kind, src }) {
|
|
23
|
+
const resolveMediaUrl = useMediaResolver();
|
|
24
|
+
const resolvedSrc = resolveMediaUrl?.(src) ?? src;
|
|
25
|
+
return (_jsx("span", { className: "lexical-media-shell", children: _jsx(RichMediaPlayer, { kind: kind, src: resolvedSrc }) }));
|
|
26
|
+
}
|
|
27
|
+
export class MediaNode extends DecoratorNode {
|
|
28
|
+
__kind;
|
|
29
|
+
__src;
|
|
30
|
+
static getType() {
|
|
31
|
+
return 'media';
|
|
32
|
+
}
|
|
33
|
+
static clone(node) {
|
|
34
|
+
return new MediaNode(node.__kind, node.__src, node.__key);
|
|
35
|
+
}
|
|
36
|
+
static importJSON(serializedNode) {
|
|
37
|
+
return $createMediaNode({
|
|
38
|
+
kind: serializedNode.kind,
|
|
39
|
+
src: serializedNode.src,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
static importDOM() {
|
|
43
|
+
return {
|
|
44
|
+
audio: () => ({
|
|
45
|
+
conversion: $convertMediaElement,
|
|
46
|
+
priority: 0,
|
|
47
|
+
}),
|
|
48
|
+
video: () => ({
|
|
49
|
+
conversion: $convertMediaElement,
|
|
50
|
+
priority: 0,
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
constructor(kind, src, key) {
|
|
55
|
+
super(key);
|
|
56
|
+
this.__kind = kind;
|
|
57
|
+
this.__src = src;
|
|
58
|
+
}
|
|
59
|
+
exportJSON() {
|
|
60
|
+
return {
|
|
61
|
+
...super.exportJSON(),
|
|
62
|
+
kind: this.__kind,
|
|
63
|
+
src: this.__src,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
exportDOM() {
|
|
67
|
+
const element = document.createElement(this.__kind);
|
|
68
|
+
element.setAttribute('controls', '');
|
|
69
|
+
element.setAttribute('src', this.__src);
|
|
70
|
+
return { element };
|
|
71
|
+
}
|
|
72
|
+
createDOM(config) {
|
|
73
|
+
const element = document.createElement('span');
|
|
74
|
+
const className = config.theme.media;
|
|
75
|
+
if (typeof className === 'string')
|
|
76
|
+
element.className = className;
|
|
77
|
+
return element;
|
|
78
|
+
}
|
|
79
|
+
updateDOM() {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
getKind() {
|
|
83
|
+
return this.getLatest().__kind;
|
|
84
|
+
}
|
|
85
|
+
getSrc() {
|
|
86
|
+
return this.getLatest().__src;
|
|
87
|
+
}
|
|
88
|
+
decorate() {
|
|
89
|
+
return _jsx(MediaComponent, { kind: this.__kind, src: this.__src });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function $createMediaNode({ kind, key, src }) {
|
|
93
|
+
return $applyNodeReplacement(new MediaNode(kind, src, key));
|
|
94
|
+
}
|
|
95
|
+
export function $isMediaNode(node) {
|
|
96
|
+
return node instanceof MediaNode;
|
|
97
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type MarkdownMediaKind = 'image' | 'video' | 'audio' | 'file';
|
|
2
|
+
export type MarkdownMediaUploadResult = {
|
|
3
|
+
altText?: string;
|
|
4
|
+
kind?: MarkdownMediaKind;
|
|
5
|
+
markdown?: string;
|
|
6
|
+
src: string;
|
|
7
|
+
};
|
|
8
|
+
export type MarkdownMediaUpload = (file: File) => Promise<MarkdownMediaUploadResult>;
|
|
9
|
+
export declare function MediaPasteDropPlugin({ onMediaUpload, }: {
|
|
10
|
+
onMediaUpload?: MarkdownMediaUpload;
|
|
11
|
+
}): null;
|
|
12
|
+
//# sourceMappingURL=media-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-plugin.d.ts","sourceRoot":"","sources":["../../../src/lib/lexical/media-plugin.tsx"],"names":[],"mappings":"AAeA,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;AAEpE,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE,IAAI,KACP,OAAO,CAAC,yBAAyB,CAAC,CAAA;AA2CvC,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,GACd,EAAE;IACD,aAAa,CAAC,EAAE,mBAAmB,CAAA;CACpC,QAsGA"}
|