@inshapardaz/likhari-react 0.1.6 → 0.1.7-dev.73
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/EditorRoot.d.ts +25 -1
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +24 -5
- package/dist/PortalTargetContext.d.ts +16 -0
- package/dist/PortalTargetContext.d.ts.map +1 -0
- package/dist/PortalTargetContext.js +18 -0
- package/dist/components/LinkDialog.d.ts.map +1 -1
- package/dist/components/LinkDialog.js +7 -3
- package/dist/components/Toolbar.d.ts +4 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +246 -33
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/strings.d.ts +158 -0
- package/dist/i18n/strings.d.ts.map +1 -0
- package/dist/i18n/strings.js +425 -0
- package/dist/i18n/useStrings.d.ts +10 -0
- package/dist/i18n/useStrings.d.ts.map +1 -0
- package/dist/i18n/useStrings.js +14 -0
- package/dist/image/ImageComponent.d.ts +23 -0
- package/dist/image/ImageComponent.d.ts.map +1 -0
- package/dist/image/ImageComponent.js +171 -0
- package/dist/image/ImageCropDialog.d.ts +22 -0
- package/dist/image/ImageCropDialog.d.ts.map +1 -0
- package/dist/image/ImageCropDialog.js +120 -0
- package/dist/image/ImageCropper.d.ts +15 -0
- package/dist/image/ImageCropper.d.ts.map +1 -0
- package/dist/image/ImageCropper.js +120 -0
- package/dist/image/ImageDialog.d.ts +37 -0
- package/dist/image/ImageDialog.d.ts.map +1 -0
- package/dist/image/ImageDialog.js +176 -0
- package/dist/image/ImageNode.d.ts +64 -0
- package/dist/image/ImageNode.d.ts.map +1 -0
- package/dist/image/ImageNode.js +133 -0
- package/dist/image/ImageOptionsContext.d.ts +24 -0
- package/dist/image/ImageOptionsContext.d.ts.map +1 -0
- package/dist/image/ImageOptionsContext.js +10 -0
- package/dist/image/cropMath.d.ts +35 -0
- package/dist/image/cropMath.d.ts.map +1 -0
- package/dist/image/cropMath.js +92 -0
- package/dist/image/imageEdit.d.ts +49 -0
- package/dist/image/imageEdit.d.ts.map +1 -0
- package/dist/image/imageEdit.js +132 -0
- package/dist/image/imageUrl.d.ts +14 -0
- package/dist/image/imageUrl.d.ts.map +1 -0
- package/dist/image/imageUrl.js +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +2 -0
- package/dist/theme/EditorThemeProvider.d.ts +17 -2
- package/dist/theme/EditorThemeProvider.d.ts.map +1 -1
- package/dist/theme/EditorThemeProvider.js +27 -3
- package/dist/theme/accentColor.d.ts +43 -0
- package/dist/theme/accentColor.d.ts.map +1 -0
- package/dist/theme/accentColor.js +84 -0
- package/dist/theme/editor.css +125 -0
- package/dist/theme/editorTheme.d.ts.map +1 -1
- package/dist/theme/editorTheme.js +1 -0
- package/package.json +9 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DecoratorNode, } from 'lexical';
|
|
3
|
+
import { ImageComponent } from './ImageComponent.js';
|
|
4
|
+
/**
|
|
5
|
+
* Block-level image (editor-architecture-design.md §3.5). The caption is a
|
|
6
|
+
* plain string (null = no caption) edited through the image's right-click menu
|
|
7
|
+
* / edit dialog — not the nested sub-editor the architecture doc leaves open —
|
|
8
|
+
* which keeps every edit in the parent editor's undo history.
|
|
9
|
+
*/
|
|
10
|
+
export class ImageNode extends DecoratorNode {
|
|
11
|
+
static getType() {
|
|
12
|
+
return 'image';
|
|
13
|
+
}
|
|
14
|
+
static clone(node) {
|
|
15
|
+
return new ImageNode(node.__src, node.__altText, node.__caption, node.__linkType, node.__width, node.__height, node.__key);
|
|
16
|
+
}
|
|
17
|
+
static importJSON(json) {
|
|
18
|
+
return $createImageNode({
|
|
19
|
+
src: json.src,
|
|
20
|
+
altText: json.altText,
|
|
21
|
+
caption: json.caption,
|
|
22
|
+
linkType: json.linkType,
|
|
23
|
+
width: json.width,
|
|
24
|
+
height: json.height,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
constructor(src, altText = '', caption = null, linkType = 'linked', width = null, height = null, key) {
|
|
28
|
+
super(key);
|
|
29
|
+
this.__src = src;
|
|
30
|
+
this.__altText = altText;
|
|
31
|
+
this.__caption = caption;
|
|
32
|
+
this.__linkType = linkType;
|
|
33
|
+
this.__width = width;
|
|
34
|
+
this.__height = height;
|
|
35
|
+
}
|
|
36
|
+
exportJSON() {
|
|
37
|
+
return {
|
|
38
|
+
type: 'image',
|
|
39
|
+
version: 1,
|
|
40
|
+
src: this.__src,
|
|
41
|
+
altText: this.__altText,
|
|
42
|
+
caption: this.__caption,
|
|
43
|
+
linkType: this.__linkType,
|
|
44
|
+
width: this.__width,
|
|
45
|
+
height: this.__height,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** `<figure><img><figcaption>` — the figure/figcaption semantics from spec §4.7. */
|
|
49
|
+
exportDOM() {
|
|
50
|
+
const figure = document.createElement('figure');
|
|
51
|
+
const img = document.createElement('img');
|
|
52
|
+
img.setAttribute('src', this.__src);
|
|
53
|
+
img.setAttribute('alt', this.__altText);
|
|
54
|
+
if (this.__width)
|
|
55
|
+
img.setAttribute('width', String(this.__width));
|
|
56
|
+
if (this.__height)
|
|
57
|
+
img.setAttribute('height', String(this.__height));
|
|
58
|
+
figure.appendChild(img);
|
|
59
|
+
if (this.__caption) {
|
|
60
|
+
const caption = document.createElement('figcaption');
|
|
61
|
+
caption.textContent = this.__caption;
|
|
62
|
+
figure.appendChild(caption);
|
|
63
|
+
}
|
|
64
|
+
return { element: figure };
|
|
65
|
+
}
|
|
66
|
+
createDOM(config) {
|
|
67
|
+
const div = document.createElement('div');
|
|
68
|
+
const className = config.theme.image;
|
|
69
|
+
if (typeof className === 'string')
|
|
70
|
+
div.className = className;
|
|
71
|
+
return div;
|
|
72
|
+
}
|
|
73
|
+
updateDOM() {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
isInline() {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
getSrc() {
|
|
80
|
+
return this.__src;
|
|
81
|
+
}
|
|
82
|
+
getAltText() {
|
|
83
|
+
return this.__altText;
|
|
84
|
+
}
|
|
85
|
+
getCaption() {
|
|
86
|
+
return this.__caption;
|
|
87
|
+
}
|
|
88
|
+
getLinkType() {
|
|
89
|
+
return this.__linkType;
|
|
90
|
+
}
|
|
91
|
+
getWidth() {
|
|
92
|
+
return this.__width;
|
|
93
|
+
}
|
|
94
|
+
getHeight() {
|
|
95
|
+
return this.__height;
|
|
96
|
+
}
|
|
97
|
+
setCaption(caption) {
|
|
98
|
+
const writable = this.getWritable();
|
|
99
|
+
writable.__caption = caption || null;
|
|
100
|
+
return writable;
|
|
101
|
+
}
|
|
102
|
+
setAltText(altText) {
|
|
103
|
+
const writable = this.getWritable();
|
|
104
|
+
writable.__altText = altText;
|
|
105
|
+
return writable;
|
|
106
|
+
}
|
|
107
|
+
/** Points the node at new image data (a replaced URL/upload, or edited pixels). */
|
|
108
|
+
setSource(src, linkType) {
|
|
109
|
+
const writable = this.getWritable();
|
|
110
|
+
writable.__src = src;
|
|
111
|
+
writable.__linkType = linkType;
|
|
112
|
+
return writable;
|
|
113
|
+
}
|
|
114
|
+
/** Display size in px; null = the image's natural size. */
|
|
115
|
+
setDimensions(width, height) {
|
|
116
|
+
const writable = this.getWritable();
|
|
117
|
+
writable.__width = width;
|
|
118
|
+
writable.__height = height;
|
|
119
|
+
return writable;
|
|
120
|
+
}
|
|
121
|
+
getTextContent() {
|
|
122
|
+
return this.__altText || this.__caption || '';
|
|
123
|
+
}
|
|
124
|
+
decorate() {
|
|
125
|
+
return (_jsx(ImageComponent, { nodeKey: this.getKey(), src: this.__src, altText: this.__altText, caption: this.__caption, linkType: this.__linkType, width: this.__width, height: this.__height }));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export function $createImageNode({ src, altText, caption, linkType, width, height, key }) {
|
|
129
|
+
return new ImageNode(src, altText, caption ?? null, linkType, width ?? null, height ?? null, key);
|
|
130
|
+
}
|
|
131
|
+
export function $isImageNode(node) {
|
|
132
|
+
return node instanceof ImageNode;
|
|
133
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Image settings from `EditorRoot` (feature config + upload handler), shared
|
|
2
|
+
* with the toolbar's insert dialog and with each image's own edit menu/dialog. */
|
|
3
|
+
export interface ImageOptions {
|
|
4
|
+
/** `images.linked` — images may reference an external URL. */
|
|
5
|
+
allowLinked: boolean;
|
|
6
|
+
/** `images.embedded` — images may be uploaded. */
|
|
7
|
+
allowEmbedded: boolean;
|
|
8
|
+
/** `images.caption` — images may have a caption. */
|
|
9
|
+
allowCaption: boolean;
|
|
10
|
+
/** `images.maxSizeMB` */
|
|
11
|
+
maxSizeMB: number;
|
|
12
|
+
/** Host upload handler; without it, uploads are embedded as base64 data URIs. */
|
|
13
|
+
onImageUpload?: (file: File) => Promise<string>;
|
|
14
|
+
/** Host override for downloading a linked image's bytes, used when converting
|
|
15
|
+
* a link to an embedded image or applying a crop/rotate/flip. Without it, the
|
|
16
|
+
* browser fetches the URL directly, which fails for any server that doesn't
|
|
17
|
+
* send CORS headers. A host can provide this to route the request through
|
|
18
|
+
* its own backend (or a CORS proxy) instead, since that restriction can only
|
|
19
|
+
* be worked around server-side, never from the browser alone. */
|
|
20
|
+
fetchImage?: (url: string) => Promise<Blob>;
|
|
21
|
+
}
|
|
22
|
+
export declare const ImageOptionsContext: import("react").Context<ImageOptions>;
|
|
23
|
+
export declare function useImageOptions(): ImageOptions;
|
|
24
|
+
//# sourceMappingURL=ImageOptionsContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageOptionsContext.d.ts","sourceRoot":"","sources":["../../src/image/ImageOptionsContext.tsx"],"names":[],"mappings":"AAEA;kFACkF;AAClF,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,WAAW,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,aAAa,EAAE,OAAO,CAAC;IACvB,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;qEAKiE;IACjE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,eAAO,MAAM,mBAAmB,uCAK9B,CAAC;AAEH,wBAAgB,eAAe,IAAI,YAAY,CAE9C"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
export const ImageOptionsContext = createContext({
|
|
3
|
+
allowLinked: true,
|
|
4
|
+
allowEmbedded: true,
|
|
5
|
+
allowCaption: true,
|
|
6
|
+
maxSizeMB: 5,
|
|
7
|
+
});
|
|
8
|
+
export function useImageOptions() {
|
|
9
|
+
return useContext(ImageOptionsContext);
|
|
10
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CropRect } from './imageEdit.js';
|
|
2
|
+
export type CropHandle = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
|
|
3
|
+
/** Smallest selection, as a fraction of the image. */
|
|
4
|
+
export declare const MIN_CROP = 0.02;
|
|
5
|
+
/** Size of the image's on-screen box in px; needed to keep an aspect ratio,
|
|
6
|
+
* since rectangle coordinates are fractions. */
|
|
7
|
+
export interface Box {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A new selection dragged from `anchor` to `point` (both fractions). With a
|
|
13
|
+
* `ratio` (width / height, in pixels) the selection keeps it, shrinking to stay
|
|
14
|
+
* inside the image.
|
|
15
|
+
*/
|
|
16
|
+
export declare function rectFromDrag(anchor: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
}, point: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
}, ratio: number | null, box: Box): CropRect;
|
|
23
|
+
/** Moves a selection by (dx, dy) fractions, keeping it inside the image. */
|
|
24
|
+
export declare function moveRect(rect: CropRect, dx: number, dy: number): CropRect;
|
|
25
|
+
/**
|
|
26
|
+
* Resizes a selection by dragging one of its handles to `point` (a fraction).
|
|
27
|
+
* Corners anchor the opposite corner; edges move one side. With a `ratio` the
|
|
28
|
+
* aspect is kept (an edge handle then grows the other dimension around the
|
|
29
|
+
* selection's centre). Never smaller than MIN_CROP, never outside the image.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resizeRect(rect: CropRect, handle: CropHandle, point: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
}, ratio: number | null, box: Box): CropRect;
|
|
35
|
+
//# sourceMappingURL=cropMath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cropMath.d.ts","sourceRoot":"","sources":["../../src/image/cropMath.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3E,sDAAsD;AACtD,eAAO,MAAM,QAAQ,OAAO,CAAC;AAE7B;gDACgD;AAChD,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,QAAQ,CAexI;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,QAAQ,CAwDxI"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Smallest selection, as a fraction of the image. */
|
|
2
|
+
export const MIN_CROP = 0.02;
|
|
3
|
+
const clamp = (v, min = 0, max = 1) => Math.min(Math.max(v, min), max);
|
|
4
|
+
/**
|
|
5
|
+
* A new selection dragged from `anchor` to `point` (both fractions). With a
|
|
6
|
+
* `ratio` (width / height, in pixels) the selection keeps it, shrinking to stay
|
|
7
|
+
* inside the image.
|
|
8
|
+
*/
|
|
9
|
+
export function rectFromDrag(anchor, point, ratio, box) {
|
|
10
|
+
let dx = clamp(point.x) - anchor.x;
|
|
11
|
+
let dy = clamp(point.y) - anchor.y;
|
|
12
|
+
if (ratio) {
|
|
13
|
+
let widthPx = Math.abs(dx) * box.width;
|
|
14
|
+
let heightPx = widthPx / ratio;
|
|
15
|
+
const maxHeightPx = (dy >= 0 ? 1 - anchor.y : anchor.y) * box.height;
|
|
16
|
+
if (heightPx > maxHeightPx) {
|
|
17
|
+
heightPx = maxHeightPx;
|
|
18
|
+
widthPx = heightPx * ratio;
|
|
19
|
+
}
|
|
20
|
+
dx = (dx >= 0 ? 1 : -1) * (widthPx / box.width);
|
|
21
|
+
dy = (dy >= 0 ? 1 : -1) * (heightPx / box.height);
|
|
22
|
+
}
|
|
23
|
+
return { x: Math.min(anchor.x, anchor.x + dx), y: Math.min(anchor.y, anchor.y + dy), w: Math.abs(dx), h: Math.abs(dy) };
|
|
24
|
+
}
|
|
25
|
+
/** Moves a selection by (dx, dy) fractions, keeping it inside the image. */
|
|
26
|
+
export function moveRect(rect, dx, dy) {
|
|
27
|
+
return { ...rect, x: clamp(rect.x + dx, 0, 1 - rect.w), y: clamp(rect.y + dy, 0, 1 - rect.h) };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resizes a selection by dragging one of its handles to `point` (a fraction).
|
|
31
|
+
* Corners anchor the opposite corner; edges move one side. With a `ratio` the
|
|
32
|
+
* aspect is kept (an edge handle then grows the other dimension around the
|
|
33
|
+
* selection's centre). Never smaller than MIN_CROP, never outside the image.
|
|
34
|
+
*/
|
|
35
|
+
export function resizeRect(rect, handle, point, ratio, box) {
|
|
36
|
+
const left = rect.x;
|
|
37
|
+
const right = rect.x + rect.w;
|
|
38
|
+
const top = rect.y;
|
|
39
|
+
const bottom = rect.y + rect.h;
|
|
40
|
+
const px = clamp(point.x);
|
|
41
|
+
const py = clamp(point.y);
|
|
42
|
+
if (handle.length === 2) {
|
|
43
|
+
// Corner: the opposite corner stays put.
|
|
44
|
+
const anchor = { x: handle.includes('w') ? right : left, y: handle.includes('n') ? bottom : top };
|
|
45
|
+
return rectFromDrag(anchor, { x: px, y: py }, ratio, box);
|
|
46
|
+
}
|
|
47
|
+
const horizontal = handle === 'e' || handle === 'w';
|
|
48
|
+
if (!ratio) {
|
|
49
|
+
const next = { left, right, top, bottom };
|
|
50
|
+
if (handle === 'w')
|
|
51
|
+
next.left = Math.min(px, right - MIN_CROP);
|
|
52
|
+
if (handle === 'e')
|
|
53
|
+
next.right = Math.max(px, left + MIN_CROP);
|
|
54
|
+
if (handle === 'n')
|
|
55
|
+
next.top = Math.min(py, bottom - MIN_CROP);
|
|
56
|
+
if (handle === 's')
|
|
57
|
+
next.bottom = Math.max(py, top + MIN_CROP);
|
|
58
|
+
return { x: next.left, y: next.top, w: next.right - next.left, h: next.bottom - next.top };
|
|
59
|
+
}
|
|
60
|
+
// Edge with a locked ratio: the dragged side sets one dimension, the other follows.
|
|
61
|
+
const cx = left + rect.w / 2;
|
|
62
|
+
const cy = top + rect.h / 2;
|
|
63
|
+
if (horizontal) {
|
|
64
|
+
const fixed = handle === 'e' ? left : right;
|
|
65
|
+
let w = Math.max(Math.abs(px - fixed), MIN_CROP);
|
|
66
|
+
let h = w * box.width / ratio / box.height;
|
|
67
|
+
if (h > 1) {
|
|
68
|
+
h = 1;
|
|
69
|
+
w = (h * box.height * ratio) / box.width;
|
|
70
|
+
}
|
|
71
|
+
// Stay inside the image horizontally.
|
|
72
|
+
const room = handle === 'e' ? 1 - left : right;
|
|
73
|
+
if (w > room) {
|
|
74
|
+
w = room;
|
|
75
|
+
h = (w * box.width) / ratio / box.height;
|
|
76
|
+
}
|
|
77
|
+
return { x: handle === 'e' ? left : right - w, y: clamp(cy - h / 2, 0, 1 - h), w, h };
|
|
78
|
+
}
|
|
79
|
+
const fixed = handle === 's' ? top : bottom;
|
|
80
|
+
let h = Math.max(Math.abs(py - fixed), MIN_CROP);
|
|
81
|
+
let w = (h * box.height * ratio) / box.width;
|
|
82
|
+
if (w > 1) {
|
|
83
|
+
w = 1;
|
|
84
|
+
h = (w * box.width) / ratio / box.height;
|
|
85
|
+
}
|
|
86
|
+
const room = handle === 's' ? 1 - top : bottom;
|
|
87
|
+
if (h > room) {
|
|
88
|
+
h = room;
|
|
89
|
+
w = (h * box.height * ratio) / box.width;
|
|
90
|
+
}
|
|
91
|
+
return { x: clamp(cx - w / 2, 0, 1 - w), y: handle === 's' ? top : bottom - h, w, h };
|
|
92
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** A crop rectangle as fractions (0–1) of the image's width and height. */
|
|
2
|
+
export interface CropRect {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
w: number;
|
|
6
|
+
h: number;
|
|
7
|
+
}
|
|
8
|
+
/** Converts a fractional crop rectangle to whole pixels inside a width x height
|
|
9
|
+
* image, clamped so it never leaves the image and is at least 1px. */
|
|
10
|
+
export declare function toPixelRect(rect: CropRect, width: number, height: number): {
|
|
11
|
+
sx: number;
|
|
12
|
+
sy: number;
|
|
13
|
+
sw: number;
|
|
14
|
+
sh: number;
|
|
15
|
+
};
|
|
16
|
+
/** When one dimension changes with the aspect ratio locked, returns both. */
|
|
17
|
+
export declare function fitDimension(changed: 'width' | 'height', value: number, naturalWidth: number, naturalHeight: number): {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
/** MIME type to re-encode an edited image as: its own if it is a lossy/lossless
|
|
22
|
+
* format canvas can write (JPEG, WebP, PNG), otherwise PNG (GIF, SVG, unknown). */
|
|
23
|
+
export declare function mimeFromSrc(src: string): string;
|
|
24
|
+
/** Approximate decoded size, in bytes, of a base64 data URI. */
|
|
25
|
+
export declare function dataUrlBytes(dataUrl: string): number;
|
|
26
|
+
export declare function dataUrlToFile(dataUrl: string, name: string): File;
|
|
27
|
+
export declare const CANNOT_DOWNLOAD = "Couldn't download this image (its server doesn't allow it). Download the file and upload it instead.";
|
|
28
|
+
/** Downloads an image and returns it as a base64 data URI, to embed it in the
|
|
29
|
+
* document. Without a `fetchBlob` override, needs the image's server to allow
|
|
30
|
+
* cross-origin reads (CORS) — a host can pass one to route the download
|
|
31
|
+
* through its own backend or a CORS proxy instead. */
|
|
32
|
+
export declare function fetchImageAsDataUrl(url: string, fetchBlob?: (url: string) => Promise<Blob>): Promise<string>;
|
|
33
|
+
export type PixelOp = {
|
|
34
|
+
type: 'rotate';
|
|
35
|
+
direction: 'cw' | 'ccw';
|
|
36
|
+
} | {
|
|
37
|
+
type: 'flip';
|
|
38
|
+
axis: 'horizontal' | 'vertical';
|
|
39
|
+
} | {
|
|
40
|
+
type: 'crop';
|
|
41
|
+
rect: CropRect;
|
|
42
|
+
};
|
|
43
|
+
/** Loads an image for canvas use. External URLs are requested with CORS so the
|
|
44
|
+
* canvas isn't tainted; a server that doesn't allow that makes this reject. */
|
|
45
|
+
export declare function loadImage(src: string): Promise<HTMLImageElement>;
|
|
46
|
+
export declare const CANNOT_EDIT_PIXELS = "This image can't be edited here (its server doesn't allow it). Upload the file instead to crop or rotate it.";
|
|
47
|
+
/** Applies a rotate / flip / crop to an image and returns the result as a data URI. */
|
|
48
|
+
export declare function applyPixelOp(src: string, op: PixelOp): Promise<string>;
|
|
49
|
+
//# sourceMappingURL=imageEdit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageEdit.d.ts","sourceRoot":"","sources":["../../src/image/imageEdit.ts"],"names":[],"mappings":"AAEA,2EAA2E;AAC3E,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;sEACsE;AACtE,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;EAMxE;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,GAAG,QAAQ,EAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GACpB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAKnC;AAED;mFACmF;AACnF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AASD,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAOjE;AAED,eAAO,MAAM,eAAe,yGAC4E,CAAC;AAEzG;;;sDAGsD;AACtD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAIpC,GACH,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,IAAI,GAAG,KAAK,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,GAAG,UAAU,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAErC;+EAC+E;AAC/E,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAQhE;AAED,eAAO,MAAM,kBAAkB,iHACiF,CAAC;AAEjH,uFAAuF;AACvF,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAuC5E"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { ACCEPTED_IMAGE_TYPES } from './imageUrl.js';
|
|
2
|
+
/** Converts a fractional crop rectangle to whole pixels inside a width x height
|
|
3
|
+
* image, clamped so it never leaves the image and is at least 1px. */
|
|
4
|
+
export function toPixelRect(rect, width, height) {
|
|
5
|
+
const sx = Math.min(Math.max(Math.round(rect.x * width), 0), width - 1);
|
|
6
|
+
const sy = Math.min(Math.max(Math.round(rect.y * height), 0), height - 1);
|
|
7
|
+
const sw = Math.min(Math.max(Math.round(rect.w * width), 1), width - sx);
|
|
8
|
+
const sh = Math.min(Math.max(Math.round(rect.h * height), 1), height - sy);
|
|
9
|
+
return { sx, sy, sw, sh };
|
|
10
|
+
}
|
|
11
|
+
/** When one dimension changes with the aspect ratio locked, returns both. */
|
|
12
|
+
export function fitDimension(changed, value, naturalWidth, naturalHeight) {
|
|
13
|
+
const v = Math.max(1, Math.round(value));
|
|
14
|
+
return changed === 'width'
|
|
15
|
+
? { width: v, height: Math.max(1, Math.round((v * naturalHeight) / naturalWidth)) }
|
|
16
|
+
: { width: Math.max(1, Math.round((v * naturalWidth) / naturalHeight)), height: v };
|
|
17
|
+
}
|
|
18
|
+
/** MIME type to re-encode an edited image as: its own if it is a lossy/lossless
|
|
19
|
+
* format canvas can write (JPEG, WebP, PNG), otherwise PNG (GIF, SVG, unknown). */
|
|
20
|
+
export function mimeFromSrc(src) {
|
|
21
|
+
const data = /^data:(image\/[a-z0-9.+-]+)[;,]/i.exec(src);
|
|
22
|
+
const type = data ? data[1].toLowerCase() : extensionMime(src);
|
|
23
|
+
return type === 'image/jpeg' || type === 'image/webp' ? type : 'image/png';
|
|
24
|
+
}
|
|
25
|
+
function extensionMime(src) {
|
|
26
|
+
const match = /\.(jpe?g|webp|png|gif|svg)(?:[?#].*)?$/i.exec(src);
|
|
27
|
+
if (!match)
|
|
28
|
+
return 'image/png';
|
|
29
|
+
const ext = match[1].toLowerCase();
|
|
30
|
+
return ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg' : ext === 'webp' ? 'image/webp' : 'image/png';
|
|
31
|
+
}
|
|
32
|
+
/** Approximate decoded size, in bytes, of a base64 data URI. */
|
|
33
|
+
export function dataUrlBytes(dataUrl) {
|
|
34
|
+
const comma = dataUrl.indexOf(',');
|
|
35
|
+
const base64 = comma === -1 ? '' : dataUrl.slice(comma + 1);
|
|
36
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
|
|
37
|
+
return Math.floor((base64.length * 3) / 4) - padding;
|
|
38
|
+
}
|
|
39
|
+
export function dataUrlToFile(dataUrl, name) {
|
|
40
|
+
const comma = dataUrl.indexOf(',');
|
|
41
|
+
const mime = /^data:([^;,]+)/.exec(dataUrl)?.[1] ?? 'image/png';
|
|
42
|
+
const binary = atob(dataUrl.slice(comma + 1));
|
|
43
|
+
const bytes = new Uint8Array(binary.length);
|
|
44
|
+
for (let i = 0; i < binary.length; i++)
|
|
45
|
+
bytes[i] = binary.charCodeAt(i);
|
|
46
|
+
return new File([bytes], name, { type: mime });
|
|
47
|
+
}
|
|
48
|
+
export const CANNOT_DOWNLOAD = "Couldn't download this image (its server doesn't allow it). Download the file and upload it instead.";
|
|
49
|
+
/** Downloads an image and returns it as a base64 data URI, to embed it in the
|
|
50
|
+
* document. Without a `fetchBlob` override, needs the image's server to allow
|
|
51
|
+
* cross-origin reads (CORS) — a host can pass one to route the download
|
|
52
|
+
* through its own backend or a CORS proxy instead. */
|
|
53
|
+
export async function fetchImageAsDataUrl(url, fetchBlob = (u) => fetch(u, { mode: 'cors' }).then((response) => {
|
|
54
|
+
if (!response.ok)
|
|
55
|
+
throw new Error('bad status');
|
|
56
|
+
return response.blob();
|
|
57
|
+
})) {
|
|
58
|
+
if (/^data:image\//i.test(url))
|
|
59
|
+
return url;
|
|
60
|
+
let blob;
|
|
61
|
+
try {
|
|
62
|
+
blob = await fetchBlob(url);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new Error(CANNOT_DOWNLOAD);
|
|
66
|
+
}
|
|
67
|
+
if (!ACCEPTED_IMAGE_TYPES.includes(blob.type))
|
|
68
|
+
throw new Error("That URL doesn't point to a PNG, JPEG, GIF, WebP or SVG image");
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const reader = new FileReader();
|
|
71
|
+
reader.onload = () => resolve(String(reader.result));
|
|
72
|
+
reader.onerror = () => reject(new Error(CANNOT_DOWNLOAD));
|
|
73
|
+
reader.readAsDataURL(blob);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Loads an image for canvas use. External URLs are requested with CORS so the
|
|
77
|
+
* canvas isn't tainted; a server that doesn't allow that makes this reject. */
|
|
78
|
+
export function loadImage(src) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const img = new Image();
|
|
81
|
+
if (/^https?:/i.test(src))
|
|
82
|
+
img.crossOrigin = 'anonymous';
|
|
83
|
+
img.onload = () => resolve(img);
|
|
84
|
+
img.onerror = () => reject(new Error('IMAGE_LOAD_FAILED'));
|
|
85
|
+
img.src = src;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export const CANNOT_EDIT_PIXELS = "This image can't be edited here (its server doesn't allow it). Upload the file instead to crop or rotate it.";
|
|
89
|
+
/** Applies a rotate / flip / crop to an image and returns the result as a data URI. */
|
|
90
|
+
export async function applyPixelOp(src, op) {
|
|
91
|
+
let img;
|
|
92
|
+
try {
|
|
93
|
+
img = await loadImage(src);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
throw new Error(CANNOT_EDIT_PIXELS);
|
|
97
|
+
}
|
|
98
|
+
const width = img.naturalWidth;
|
|
99
|
+
const height = img.naturalHeight;
|
|
100
|
+
if (!width || !height)
|
|
101
|
+
throw new Error('This image has no pixel size to edit (some SVGs). Upload a PNG or JPEG instead.');
|
|
102
|
+
const canvas = document.createElement('canvas');
|
|
103
|
+
const ctx = canvas.getContext('2d');
|
|
104
|
+
if (!ctx)
|
|
105
|
+
throw new Error('Image editing is not available in this browser');
|
|
106
|
+
if (op.type === 'rotate') {
|
|
107
|
+
canvas.width = height;
|
|
108
|
+
canvas.height = width;
|
|
109
|
+
ctx.translate(height / 2, width / 2);
|
|
110
|
+
ctx.rotate(op.direction === 'cw' ? Math.PI / 2 : -Math.PI / 2);
|
|
111
|
+
ctx.drawImage(img, -width / 2, -height / 2);
|
|
112
|
+
}
|
|
113
|
+
else if (op.type === 'flip') {
|
|
114
|
+
canvas.width = width;
|
|
115
|
+
canvas.height = height;
|
|
116
|
+
ctx.translate(op.axis === 'horizontal' ? width : 0, op.axis === 'vertical' ? height : 0);
|
|
117
|
+
ctx.scale(op.axis === 'horizontal' ? -1 : 1, op.axis === 'vertical' ? -1 : 1);
|
|
118
|
+
ctx.drawImage(img, 0, 0);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const { sx, sy, sw, sh } = toPixelRect(op.rect, width, height);
|
|
122
|
+
canvas.width = sw;
|
|
123
|
+
canvas.height = sh;
|
|
124
|
+
ctx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
return canvas.toDataURL(mimeFromSrc(src), 0.92);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new Error(CANNOT_EDIT_PIXELS);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates the source of a linked image. Accepts http(s) URLs, relative
|
|
3
|
+
* paths, and base64 raster/SVG data URIs (what an embedded image stores);
|
|
4
|
+
* anything else (javascript:, other data: types, file:, ...) is rejected.
|
|
5
|
+
* Returns the trimmed URL, or null when it should be refused.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeImageUrl(input: string): string | null;
|
|
8
|
+
export declare const ACCEPTED_IMAGE_TYPES: string[];
|
|
9
|
+
/** Returns an error message if the file can't be inserted, otherwise null. */
|
|
10
|
+
export declare function validateImageFile(file: {
|
|
11
|
+
type: string;
|
|
12
|
+
size: number;
|
|
13
|
+
}, maxSizeMB: number): string | null;
|
|
14
|
+
//# sourceMappingURL=imageUrl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageUrl.d.ts","sourceRoot":"","sources":["../../src/image/imageUrl.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO9D;AAED,eAAO,MAAM,oBAAoB,UAA0E,CAAC;AAE5G,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIxG"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates the source of a linked image. Accepts http(s) URLs, relative
|
|
3
|
+
* paths, and base64 raster/SVG data URIs (what an embedded image stores);
|
|
4
|
+
* anything else (javascript:, other data: types, file:, ...) is rejected.
|
|
5
|
+
* Returns the trimmed URL, or null when it should be refused.
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeImageUrl(input) {
|
|
8
|
+
const url = input.trim();
|
|
9
|
+
if (!url)
|
|
10
|
+
return null;
|
|
11
|
+
if (/^https?:\/\/\S+$/i.test(url))
|
|
12
|
+
return url;
|
|
13
|
+
if (/^(\/|\.\.?\/)\S*$/.test(url))
|
|
14
|
+
return url;
|
|
15
|
+
if (/^data:image\/(png|jpe?g|gif|webp|svg\+xml);base64,[a-z0-9+/=]+$/i.test(url))
|
|
16
|
+
return url;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export const ACCEPTED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml'];
|
|
20
|
+
/** Returns an error message if the file can't be inserted, otherwise null. */
|
|
21
|
+
export function validateImageFile(file, maxSizeMB) {
|
|
22
|
+
if (!ACCEPTED_IMAGE_TYPES.includes(file.type))
|
|
23
|
+
return 'Choose a PNG, JPEG, GIF, WebP or SVG image';
|
|
24
|
+
if (file.size > maxSizeMB * 1024 * 1024)
|
|
25
|
+
return `Image is larger than ${maxSizeMB} MB`;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { EditorRoot } from './EditorRoot.js';
|
|
2
2
|
export type { EditorRootProps, EditorRef, EditorInitialContent } from './EditorRoot.js';
|
|
3
3
|
export { defaultMantineTheme } from './theme/mantineTheme.js';
|
|
4
|
+
export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
|
|
5
|
+
export type { ImagePayload, ImageLinkType, SerializedImageNode } from './image/ImageNode.js';
|
|
4
6
|
export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fonts/index.js';
|
|
5
7
|
export type { FontOption } from './fonts/index.js';
|
|
8
|
+
export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
|
|
9
|
+
export type { Locale, Strings } from './i18n/index.js';
|
|
6
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { EditorRoot } from './EditorRoot.js';
|
|
2
2
|
export { defaultMantineTheme } from './theme/mantineTheme.js';
|
|
3
|
+
export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
|
|
3
4
|
export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fonts/index.js';
|
|
5
|
+
export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
|
package/dist/nodes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAOlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAQ5C,CAAC"}
|
package/dist/nodes.js
CHANGED
|
@@ -2,6 +2,7 @@ import { HeadingNode, QuoteNode } from '@lexical/rich-text';
|
|
|
2
2
|
import { ListItemNode, ListNode } from '@lexical/list';
|
|
3
3
|
import { LinkNode } from '@lexical/link';
|
|
4
4
|
import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
|
|
5
|
+
import { ImageNode } from './image/ImageNode.js';
|
|
5
6
|
/**
|
|
6
7
|
* Node types registered on the editor at construction time. Per
|
|
7
8
|
* editor-architecture-design.md §2, node registration cannot be conditional
|
|
@@ -18,4 +19,5 @@ export const EDITOR_NODES = [
|
|
|
18
19
|
ListItemNode,
|
|
19
20
|
LinkNode,
|
|
20
21
|
HorizontalRuleNode,
|
|
22
|
+
ImageNode,
|
|
21
23
|
];
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { type MantineThemeOverride } from '@mantine/core';
|
|
2
|
-
import '@mantine/core/styles.css';
|
|
3
2
|
import './editor.css';
|
|
4
3
|
export interface EditorThemeProviderProps {
|
|
5
4
|
theme?: MantineThemeOverride;
|
|
6
5
|
colorScheme?: 'light' | 'dark';
|
|
6
|
+
/** Overrides both Mantine's primary color and the `--editor-accent` /
|
|
7
|
+
* `--editor-accent-soft` CSS variables — see EditorRootProps.accentColor. */
|
|
8
|
+
accentColor?: string;
|
|
9
|
+
/**
|
|
10
|
+
* The id of the portal-anchor element EditorRoot renders inside
|
|
11
|
+
* `.likhari-root` (see PortalTargetContext.tsx). Scopes Mantine's OWN
|
|
12
|
+
* `data-mantine-color-scheme` attribute and CSS variables to that single
|
|
13
|
+
* element instead of the default `document.documentElement` — without
|
|
14
|
+
* this, Mantine's Modal/Menu/Select/Tooltip surfaces (background, text,
|
|
15
|
+
* border colors) would always render in Mantine's global light scheme
|
|
16
|
+
* regardless of `colorScheme`, since nothing ever told MantineProvider
|
|
17
|
+
* which scheme to use; forcing it at `document.documentElement` instead
|
|
18
|
+
* would work but would leak dark mode onto the rest of the host page and
|
|
19
|
+
* collide between multiple EditorRoot instances with different schemes.
|
|
20
|
+
*/
|
|
21
|
+
scopeElementId?: string;
|
|
7
22
|
children: React.ReactNode;
|
|
8
23
|
}
|
|
9
|
-
export declare function EditorThemeProvider({ theme, colorScheme, children }: EditorThemeProviderProps): import("react").JSX.Element;
|
|
24
|
+
export declare function EditorThemeProvider({ theme, colorScheme, accentColor, scopeElementId, children }: EditorThemeProviderProps): import("react").JSX.Element;
|
|
10
25
|
//# sourceMappingURL=EditorThemeProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorThemeProvider.d.ts","sourceRoot":"","sources":["../../src/theme/EditorThemeProvider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EditorThemeProvider.d.ts","sourceRoot":"","sources":["../../src/theme/EditorThemeProvider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAO3E,OAAO,cAAc,CAAC;AAsBtB,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B;iFAC6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BA+C1H"}
|