@blocklet/editor 1.6.189 → 1.6.191
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/lib/config.d.ts +1 -1
- package/lib/ext/common.d.ts +1 -1
- package/lib/main/index.css +7 -3
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawComponent.js +6 -1
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawModal.css +7 -2
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawModal.d.ts +7 -3
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawModal.js +66 -27
- package/lib/main/nodes/ExcalidrawNode/utils.d.ts +5 -0
- package/lib/main/nodes/ExcalidrawNode/utils.js +71 -0
- package/lib/main/plugins/ComponentPickerPlugin/index.d.ts +1 -0
- package/lib/main/plugins/ComponentPickerPlugin/index.js +28 -2
- package/lib/main/plugins/ToolbarPlugin/index.js +12 -2
- package/lib/main/ui/Modal.css +1 -1
- package/lib/stories/Editor.stories.js +2 -2
- package/package.json +3 -3
package/lib/config.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { AI, UserService } from './types';
|
|
3
|
-
export type ToolbarConfigItem = 'block' | 'align' | 'font-family' | 'font-size' | 'bold' | 'italic' | 'underline' | 'code' | 'link' | 'image' | 'color' | 'bgcolor' | 'strikethrough' | 'media' | 'alert';
|
|
3
|
+
export type ToolbarConfigItem = 'component' | 'block' | 'align' | 'font-family' | 'font-size' | 'bold' | 'italic' | 'underline' | 'code' | 'link' | 'image' | 'color' | 'bgcolor' | 'strikethrough' | 'media' | 'alert';
|
|
4
4
|
export interface ToolbarConfig {
|
|
5
5
|
items?: ToolbarConfigItem[];
|
|
6
6
|
}
|
package/lib/ext/common.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const MenuWrapper: import("@emotion/styled").StyledComponent<{
|
|
|
11
11
|
}, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
|
|
12
12
|
export declare const GradientButton: import("@emotion/styled").StyledComponent<import("@mui/material").ButtonOwnProps & Omit<import("@mui/material").ButtonBaseOwnProps, "classes"> & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
|
|
13
13
|
ref?: ((instance: HTMLButtonElement | null) => void) | import("react").RefObject<HTMLButtonElement> | null | undefined;
|
|
14
|
-
}, "color" | "children" | "style" | "className" | "tabIndex" | "disabled" | "sx" | "href" | "size" | "action" | "classes" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | "disableFocusRipple" | "
|
|
14
|
+
}, "color" | "children" | "style" | "className" | "tabIndex" | "disabled" | "sx" | "href" | "size" | "action" | "classes" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | "disableFocusRipple" | "disableElevation" | "fullWidth" | "startIcon" | "endIcon" | "variant"> & {
|
|
15
15
|
theme?: import("@emotion/react").Theme | undefined;
|
|
16
16
|
}, {}, {}>;
|
|
17
17
|
export declare function NestedMenuItem({ children, subMenu, ...rest }: MenuItemProps & {
|
package/lib/main/index.css
CHANGED
|
@@ -711,8 +711,14 @@ i.prettier-error {
|
|
|
711
711
|
margin-left: 5px;
|
|
712
712
|
border-radius: 15px;
|
|
713
713
|
color: #222;
|
|
714
|
-
display: inline-block;
|
|
715
714
|
cursor: pointer;
|
|
715
|
+
display: flex;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.action-button .MuiLoadingButton-loadingIndicator {
|
|
719
|
+
position: relative;
|
|
720
|
+
left: 0;
|
|
721
|
+
margin-right: 8px;
|
|
716
722
|
}
|
|
717
723
|
|
|
718
724
|
.action-button:hover {
|
|
@@ -724,8 +730,6 @@ i.prettier-error {
|
|
|
724
730
|
animation: mic-pulsate-color 3s infinite;
|
|
725
731
|
}
|
|
726
732
|
button.action-button:disabled {
|
|
727
|
-
opacity: 0.6;
|
|
728
|
-
background: #eee;
|
|
729
733
|
cursor: not-allowed;
|
|
730
734
|
}
|
|
731
735
|
|
|
@@ -8,6 +8,7 @@ import ImageResizer from '../../ui/ImageResizer';
|
|
|
8
8
|
import { $isExcalidrawNode } from '.';
|
|
9
9
|
import ExcalidrawImage from './ExcalidrawImage';
|
|
10
10
|
import ExcalidrawModal from './ExcalidrawModal';
|
|
11
|
+
import { covertImageWithMediaKitPath } from './utils';
|
|
11
12
|
export default function ExcalidrawComponent({ nodeKey, data }) {
|
|
12
13
|
const [editor] = useLexicalComposerContext();
|
|
13
14
|
const [isModalOpen, setModalOpen] = useState(data === '[]' && editor.isEditable());
|
|
@@ -21,7 +22,9 @@ export default function ExcalidrawComponent({ nodeKey, data }) {
|
|
|
21
22
|
// 兼容 excalidraw 旧版本数据 - 缺少 "elements" key, 并且是一个 array, 新版本数据是 object
|
|
22
23
|
const parsed = useMemo(() => {
|
|
23
24
|
try {
|
|
24
|
-
|
|
25
|
+
const tempData = JSON.parse(data);
|
|
26
|
+
covertImageWithMediaKitPath(tempData.elements, tempData.files);
|
|
27
|
+
return tempData;
|
|
25
28
|
}
|
|
26
29
|
catch (e) {
|
|
27
30
|
console.error(e);
|
|
@@ -89,6 +92,8 @@ export default function ExcalidrawComponent({ nodeKey, data }) {
|
|
|
89
92
|
const node = $getNodeByKey(nodeKey);
|
|
90
93
|
if ($isExcalidrawNode(node)) {
|
|
91
94
|
if (els.length > 0 || Object.keys(fls).length > 0) {
|
|
95
|
+
// covert image with mediaKit path
|
|
96
|
+
covertImageWithMediaKitPath(els, fls);
|
|
92
97
|
node.setData(JSON.stringify({
|
|
93
98
|
appState: aps,
|
|
94
99
|
elements: els,
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
.ExcalidrawModal__row {
|
|
37
37
|
position: relative;
|
|
38
|
-
padding:
|
|
38
|
+
padding: 42px 5px 5px;
|
|
39
39
|
width: 90vw;
|
|
40
40
|
height: 90vh;
|
|
41
41
|
border-radius: 8px;
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
}
|
|
47
47
|
.ExcalidrawModal__modal {
|
|
48
48
|
position: relative;
|
|
49
|
-
z-index:
|
|
49
|
+
z-index: 99999;
|
|
50
50
|
top: 50px;
|
|
51
51
|
width: auto;
|
|
52
52
|
left: 0;
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
border-radius: 8px;
|
|
57
57
|
background-color: #eee;
|
|
58
58
|
}
|
|
59
|
+
|
|
59
60
|
.ExcalidrawModal__discardModal {
|
|
60
61
|
margin-top: 60px;
|
|
61
62
|
text-align: center;
|
|
@@ -68,3 +69,7 @@
|
|
|
68
69
|
width: 100vw;
|
|
69
70
|
height: 100vh;
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
body .excalidraw-modal-container {
|
|
74
|
+
z-index: 99999 !important;
|
|
75
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppState, BinaryFiles } from '@excalidraw/excalidraw/types/types';
|
|
1
|
+
import { AppState, BinaryFiles, LibraryItems } from '@excalidraw/excalidraw/types/types';
|
|
2
2
|
import { ReactPortal } from 'react';
|
|
3
3
|
export type ExcalidrawElementFragment = {
|
|
4
4
|
isDeleted?: boolean;
|
|
@@ -14,9 +14,13 @@ type Props = {
|
|
|
14
14
|
*/
|
|
15
15
|
initialAppState: AppState;
|
|
16
16
|
/**
|
|
17
|
-
* The initial set of
|
|
17
|
+
* The initial set of files to draw into the scene
|
|
18
18
|
*/
|
|
19
19
|
initialFiles: BinaryFiles;
|
|
20
|
+
/**
|
|
21
|
+
* The initial set of library items to draw into the scene
|
|
22
|
+
*/
|
|
23
|
+
initialLibraryItems?: LibraryItems;
|
|
20
24
|
/**
|
|
21
25
|
* Controls the visibility of the modal
|
|
22
26
|
*/
|
|
@@ -39,5 +43,5 @@ type Props = {
|
|
|
39
43
|
* A component which renders a modal with Excalidraw (a painting app)
|
|
40
44
|
* which can be used to export an editable image
|
|
41
45
|
*/
|
|
42
|
-
export default function ExcalidrawModal({ closeOnClickOutside, onSave, initialElements, initialAppState, initialFiles, isShown, onDelete, onClose, }: Props): ReactPortal | null;
|
|
46
|
+
export default function ExcalidrawModal({ closeOnClickOutside, onSave, initialElements, initialAppState, initialFiles, initialLibraryItems, isShown, onDelete, onClose, }: Props): ReactPortal | null;
|
|
43
47
|
export {};
|
|
@@ -6,29 +6,50 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
|
-
import { Excalidraw } from '@excalidraw/excalidraw';
|
|
9
|
+
import { Excalidraw, useHandleLibrary, serializeLibraryAsJSON } from '@excalidraw/excalidraw';
|
|
10
10
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
11
11
|
import { createPortal } from 'react-dom';
|
|
12
|
+
import LoadingButton from '@mui/lab/LoadingButton';
|
|
12
13
|
import Button from '../../ui/Button';
|
|
13
14
|
import Modal from '../../ui/Modal';
|
|
14
15
|
import { useBeforeUnload } from '../../hooks/useBeforeUnload';
|
|
16
|
+
import { uploadImageToMediaKit } from './utils';
|
|
17
|
+
const safeParseJSON = (json) => {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(json);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const LIBRARY_KEY = 'excalidraw-library';
|
|
15
26
|
/**
|
|
16
27
|
* @explorer-desc
|
|
17
28
|
* A component which renders a modal with Excalidraw (a painting app)
|
|
18
29
|
* which can be used to export an editable image
|
|
19
30
|
*/
|
|
20
|
-
export default function ExcalidrawModal({ closeOnClickOutside = false, onSave, initialElements, initialAppState, initialFiles, isShown = false, onDelete, onClose, }) {
|
|
31
|
+
export default function ExcalidrawModal({ closeOnClickOutside = false, onSave, initialElements, initialAppState, initialFiles, initialLibraryItems = safeParseJSON(localStorage.getItem(LIBRARY_KEY))?.libraryItems, isShown = false, onDelete, onClose, }) {
|
|
21
32
|
const excaliDrawModelRef = useRef(null);
|
|
22
33
|
const excaliDrawSceneRef = useRef(null);
|
|
23
34
|
const [discardModalOpen, setDiscardModalOpen] = useState(false);
|
|
24
35
|
const [elements, setElements] = useState(initialElements);
|
|
25
36
|
const [files, setFiles] = useState(initialFiles);
|
|
37
|
+
const [loading, setLoading] = useState(false);
|
|
38
|
+
const [saveText, setSaveText] = useState('Save');
|
|
26
39
|
const [fullScreen, setFullScreen] = useState(false);
|
|
27
40
|
useEffect(() => {
|
|
41
|
+
// @ts-ignore set window.name to add library target
|
|
42
|
+
window.name = window.location.href;
|
|
28
43
|
if (excaliDrawModelRef.current !== null) {
|
|
29
44
|
excaliDrawModelRef.current.focus();
|
|
30
45
|
}
|
|
31
46
|
}, []);
|
|
47
|
+
useHandleLibrary({
|
|
48
|
+
excalidrawAPI: excaliDrawSceneRef.current,
|
|
49
|
+
getInitialLibraryItems: () => {
|
|
50
|
+
return initialLibraryItems;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
32
53
|
useBeforeUnload(isShown);
|
|
33
54
|
useEffect(() => {
|
|
34
55
|
let modalOverlayElement = null;
|
|
@@ -68,30 +89,44 @@ export default function ExcalidrawModal({ closeOnClickOutside = false, onSave, i
|
|
|
68
89
|
}
|
|
69
90
|
};
|
|
70
91
|
}, [elements, files, onDelete]);
|
|
71
|
-
const save = () => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
const save = async () => {
|
|
93
|
+
setLoading(true);
|
|
94
|
+
setSaveText('Saving...');
|
|
95
|
+
try {
|
|
96
|
+
if (elements.filter((el) => !el.isDeleted).length > 0) {
|
|
97
|
+
const appState = excaliDrawSceneRef?.current?.getAppState();
|
|
98
|
+
// We only need a subset of the state
|
|
99
|
+
const partialState = appState
|
|
100
|
+
? {
|
|
101
|
+
exportBackground: appState.exportBackground,
|
|
102
|
+
exportScale: appState.exportScale,
|
|
103
|
+
exportWithDarkMode: appState.theme === 'dark',
|
|
104
|
+
isBindingEnabled: appState.isBindingEnabled,
|
|
105
|
+
isLoading: appState.isLoading,
|
|
106
|
+
name: appState.name,
|
|
107
|
+
theme: appState.theme,
|
|
108
|
+
viewBackgroundColor: appState.viewBackgroundColor,
|
|
109
|
+
viewModeEnabled: appState.viewModeEnabled,
|
|
110
|
+
zenModeEnabled: appState.zenModeEnabled,
|
|
111
|
+
zoom: appState.zoom,
|
|
112
|
+
}
|
|
113
|
+
: {};
|
|
114
|
+
setSaveText('Uploading...');
|
|
115
|
+
await uploadImageToMediaKit(elements, files);
|
|
116
|
+
setSaveText('Saving...');
|
|
117
|
+
await onSave(elements, partialState, files);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// delete node if the scene is clear
|
|
121
|
+
await onDelete();
|
|
122
|
+
}
|
|
91
123
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error('Error saving Excalidraw', error);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
setLoading(false);
|
|
129
|
+
setSaveText('Save');
|
|
95
130
|
}
|
|
96
131
|
};
|
|
97
132
|
const discard = () => {
|
|
@@ -122,14 +157,18 @@ export default function ExcalidrawModal({ closeOnClickOutside = false, onSave, i
|
|
|
122
157
|
setElements(els);
|
|
123
158
|
setFiles(fls);
|
|
124
159
|
};
|
|
160
|
+
const onLibraryChange = (items) => {
|
|
161
|
+
const serializeJSON = serializeLibraryAsJSON(items);
|
|
162
|
+
localStorage.setItem(LIBRARY_KEY, serializeJSON);
|
|
163
|
+
};
|
|
125
164
|
// This is a hacky work-around for Excalidraw + Vite.
|
|
126
165
|
// In DEV, Vite pulls this in fine, in prod it doesn't. It seems
|
|
127
166
|
// like a module resolution issue with ESM vs CJS?
|
|
128
167
|
// @ts-ignore
|
|
129
168
|
const _Excalidraw = Excalidraw.$$typeof != null ? Excalidraw : Excalidraw.default;
|
|
130
|
-
return createPortal(_jsx("div", { className: `ExcalidrawModal__overlay ${fullScreen ? 'ExcalidrawModal__fullscreen' : ''}`, role: "dialog", children: _jsx("div", { className: "ExcalidrawModal__modal", ref: excaliDrawModelRef, tabIndex: -1, children: _jsxs("div", { className: "ExcalidrawModal__row", children: [discardModalOpen && _jsx(ShowDiscardDialog, {}), _jsx(_Excalidraw, { onChange: onChange, ref: excaliDrawSceneRef, initialData: {
|
|
169
|
+
return createPortal(_jsx("div", { className: `ExcalidrawModal__overlay ${fullScreen ? 'ExcalidrawModal__fullscreen' : ''}`, role: "dialog", children: _jsx("div", { className: "ExcalidrawModal__modal", ref: excaliDrawModelRef, tabIndex: -1, children: _jsxs("div", { className: "ExcalidrawModal__row", children: [discardModalOpen && _jsx(ShowDiscardDialog, {}), _jsx(_Excalidraw, { onChange: onChange, onLibraryChange: onLibraryChange, ref: excaliDrawSceneRef, initialData: {
|
|
131
170
|
appState: initialAppState || { isLoading: false },
|
|
132
171
|
elements: initialElements,
|
|
133
172
|
files: initialFiles,
|
|
134
|
-
} }), _jsxs("div", { className: "ExcalidrawModal__actions", children: [!fullScreen && (_jsx(
|
|
173
|
+
} }), _jsxs("div", { className: "ExcalidrawModal__actions", children: [!fullScreen && (_jsx(LoadingButton, { className: "action-button", style: { width: 32, padding: 0, display: 'flex', alignItems: 'center' }, onClick: () => setFullScreen(true), children: _jsx("i", { className: "iconify", "data-icon": "material-symbols:fullscreen", "data-height": "18" }) })), fullScreen && (_jsx(LoadingButton, { className: "action-button", style: { width: 32, padding: 0, display: 'flex', alignItems: 'center' }, onClick: () => setFullScreen(false), children: _jsx("i", { className: "iconify", "data-icon": "material-symbols:fullscreen-exit", "data-height": "18" }) })), !initialAppState.viewModeEnabled && (_jsxs(_Fragment, { children: [_jsx(LoadingButton, { loadingPosition: "start", disabled: loading, className: "action-button", onClick: discard, children: "Discard" }), _jsx(LoadingButton, { loadingPosition: "start", loading: loading, className: "action-button", onClick: save, children: saveText })] })), initialAppState.viewModeEnabled && (_jsx(LoadingButton, { className: "action-button", onClick: () => onClose(), children: "Close" }))] })] }) }) }), document.body);
|
|
135
174
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const getBlockletMountPointInfo: (name: string) => any;
|
|
2
|
+
export declare function isLocalImage(src: string): boolean;
|
|
3
|
+
export declare function joinImageUrl(src: string): string;
|
|
4
|
+
export declare function uploadImageToMediaKit(elements: any, files: any): Promise<void>;
|
|
5
|
+
export declare function covertImageWithMediaKitPath(elements: any, files: any): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { joinURL } from 'ufo';
|
|
2
|
+
// chain promise
|
|
3
|
+
const runPromiseInSequence = (list, fn) => {
|
|
4
|
+
return list.reduce((p, item) => {
|
|
5
|
+
return p.then(() => fn(item));
|
|
6
|
+
}, Promise.resolve()); // initial
|
|
7
|
+
};
|
|
8
|
+
export const getBlockletMountPointInfo = (name) => {
|
|
9
|
+
return window.blocklet?.componentMountPoints?.find((x) => x.name === name || x.did === name);
|
|
10
|
+
};
|
|
11
|
+
export function isLocalImage(src) {
|
|
12
|
+
return src.startsWith('data:image') || src.startsWith('blob:');
|
|
13
|
+
}
|
|
14
|
+
export function joinImageUrl(src) {
|
|
15
|
+
if (src?.startsWith('http')) {
|
|
16
|
+
return src;
|
|
17
|
+
}
|
|
18
|
+
// media kit
|
|
19
|
+
// const mountPoint = getBlockletMountPointInfo('z8ia1mAXo8ZE7ytGF36L5uBf9kD2kenhqFGp9');
|
|
20
|
+
// discuss kit
|
|
21
|
+
const mountPoint = getBlockletMountPointInfo('did-comments')?.mountPoint;
|
|
22
|
+
if (mountPoint) {
|
|
23
|
+
const prefix = joinURL(window.location.origin, mountPoint, 'uploads');
|
|
24
|
+
// not add prefix
|
|
25
|
+
if (!src.startsWith(prefix)) {
|
|
26
|
+
return joinURL(prefix, src);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return src;
|
|
30
|
+
}
|
|
31
|
+
export async function uploadImageToMediaKit(elements, files) {
|
|
32
|
+
const filteredImages = elements?.filter((el) => el.type === 'image');
|
|
33
|
+
if (filteredImages.length > 0) {
|
|
34
|
+
// upload image to Media Kit
|
|
35
|
+
await runPromiseInSequence(filteredImages, async (el) => {
|
|
36
|
+
const fileItem = files[el.fileId];
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
const uploader = window?.uploaderRef?.current?.getUploader?.();
|
|
39
|
+
const prefix = joinImageUrl('/');
|
|
40
|
+
// if not uploaded yet and is has uploader
|
|
41
|
+
if (isLocalImage(fileItem.dataURL) && !!uploader) {
|
|
42
|
+
// try to upload the image
|
|
43
|
+
const blobFile = await fetch(fileItem.dataURL).then((res) => res.blob());
|
|
44
|
+
const { data } = await uploader.uploadFile(blobFile).then((result) => {
|
|
45
|
+
// https://uppy.io/docs/uppy/#options
|
|
46
|
+
return result?.response;
|
|
47
|
+
});
|
|
48
|
+
// only save filename
|
|
49
|
+
files[el.fileId].dataURL = data.filename;
|
|
50
|
+
}
|
|
51
|
+
else if (fileItem.dataURL?.startsWith(prefix)) {
|
|
52
|
+
// remove the prefix
|
|
53
|
+
files[el.fileId].dataURL = fileItem.dataURL.replace(prefix, '');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// promise all to chain the upload
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function covertImageWithMediaKitPath(elements, files) {
|
|
60
|
+
const filteredImages = elements?.filter((el) => el.type === 'image') || [];
|
|
61
|
+
if (filteredImages?.length > 0) {
|
|
62
|
+
// upload image to Media Kit
|
|
63
|
+
filteredImages.forEach((el) => {
|
|
64
|
+
const fileItem = files[el.fileId];
|
|
65
|
+
if (!isLocalImage(fileItem.dataURL)) {
|
|
66
|
+
// replace dataURL with Media Kit URL
|
|
67
|
+
files[el.fileId].dataURL = joinImageUrl(fileItem.dataURL);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/// <reference types="react" />
|
|
9
9
|
import { LexicalEditor } from 'lexical';
|
|
10
|
+
export declare const INSERT_COMPONENT_COMMAND: import("lexical").LexicalCommand<unknown>;
|
|
10
11
|
export default function ComponentPickerMenuPlugin(): JSX.Element;
|
|
11
12
|
export declare const uploadFile: (editor: LexicalEditor) => void;
|
|
@@ -15,8 +15,9 @@ import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontal
|
|
|
15
15
|
import { $createHeadingNode, $createQuoteNode } from '@lexical/rich-text';
|
|
16
16
|
import { $wrapNodes } from '@lexical/selection';
|
|
17
17
|
import { INSERT_TABLE_COMMAND } from '@lexical/table';
|
|
18
|
-
import { $createParagraphNode, $getSelection, $isRangeSelection, FORMAT_ELEMENT_COMMAND, } from 'lexical';
|
|
19
|
-
import {
|
|
18
|
+
import { $createParagraphNode, $getSelection, $isRangeSelection, FORMAT_ELEMENT_COMMAND, createCommand, $insertNodes, $createTextNode, COMMAND_PRIORITY_EDITOR, } from 'lexical';
|
|
19
|
+
import { mergeRegister } from '@lexical/utils';
|
|
20
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
20
21
|
import { LexicalTypeaheadMenuPlugin, TypeaheadOption, useBasicTypeaheadTriggerMatch, } from '../LexicalTypeaheadMenuPlugin';
|
|
21
22
|
import useHasNodes from '../../hooks/useHasNodes';
|
|
22
23
|
import useModal from '../../hooks/useModal';
|
|
@@ -61,6 +62,7 @@ function ComponentPickerMenuItem({ index, isSelected, onClick, onMouseEnter, opt
|
|
|
61
62
|
}
|
|
62
63
|
return (_jsxs("li", { tabIndex: -1, className: className, ref: option.setRefElement, role: "option", "aria-selected": isSelected, id: `typeahead-item-${index}`, onMouseEnter: onMouseEnter, onClick: onClick, children: [option.icon, _jsx("span", { className: "text", children: option.title })] }, option.key));
|
|
63
64
|
}
|
|
65
|
+
export const INSERT_COMPONENT_COMMAND = createCommand('INSERT_COMPONENT_COMMAND');
|
|
64
66
|
export default function ComponentPickerMenuPlugin() {
|
|
65
67
|
const [editor] = useLexicalComposerContext();
|
|
66
68
|
const [modal, showModal] = useModal();
|
|
@@ -310,6 +312,30 @@ export default function ComponentPickerMenuPlugin() {
|
|
|
310
312
|
closeMenu();
|
|
311
313
|
});
|
|
312
314
|
}, [editor]);
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
return mergeRegister(editor.registerCommand(INSERT_COMPONENT_COMMAND, (payload) => {
|
|
317
|
+
editor.update(() => {
|
|
318
|
+
const selection = $getSelection();
|
|
319
|
+
// @ts-ignore
|
|
320
|
+
const anchorNode = selection?.anchor?.getNode();
|
|
321
|
+
const oldText = anchorNode?.getTextContent();
|
|
322
|
+
// check if has payload in current line
|
|
323
|
+
if (oldText?.endsWith(payload)) {
|
|
324
|
+
// set selection to the end of the line
|
|
325
|
+
anchorNode?.selectEnd();
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// has content should add space before payload
|
|
329
|
+
if (oldText.length > 0 && !oldText?.endsWith(' ')) {
|
|
330
|
+
payload = ` ${payload}`;
|
|
331
|
+
}
|
|
332
|
+
const textNode = $createTextNode(payload);
|
|
333
|
+
$insertNodes([textNode]);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return true;
|
|
337
|
+
}, COMMAND_PRIORITY_EDITOR));
|
|
338
|
+
}, [editor]);
|
|
313
339
|
return (_jsxs(_Fragment, { children: [modal, _jsx(LexicalTypeaheadMenuPlugin, { onQueryChange: setQueryString, onSelectOption: onSelectOption, triggerFn: checkForTriggerMatch, options: options, menuRenderFn: (anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }) => {
|
|
314
340
|
return anchorElementRef.current && options.length
|
|
315
341
|
? createPortal(_jsx("div", { className: "typeahead-popover component-picker-menu", children: _jsx(List, { children: options.map((option, i) => (_jsx(ComponentPickerMenuItem, { index: i, isSelected: selectedIndex === i, onClick: () => {
|
|
@@ -27,7 +27,7 @@ import { sanitizeUrl } from '../../utils/sanitizeUrl';
|
|
|
27
27
|
import { INSERT_IMAGE_COMMAND } from '../ImagesPlugin';
|
|
28
28
|
import { AideToolbarButton } from '../../../ext/AIPlugin';
|
|
29
29
|
import { AITranslateToolbarButton } from '../../../ext/AITranslationPlugin';
|
|
30
|
-
import { uploadFile } from '../ComponentPickerPlugin';
|
|
30
|
+
import { uploadFile, INSERT_COMPONENT_COMMAND } from '../ComponentPickerPlugin';
|
|
31
31
|
// import { InsertNewTableDialog, InsertTableDialog } from "../TablePlugin";
|
|
32
32
|
const blockTypeToBlockName = {
|
|
33
33
|
bullet: 'Bulleted List',
|
|
@@ -300,6 +300,7 @@ export default function ToolbarPlugin() {
|
|
|
300
300
|
};
|
|
301
301
|
const config = useEditorConfig().toolbar;
|
|
302
302
|
const items = config?.items ?? [
|
|
303
|
+
'component',
|
|
303
304
|
'block',
|
|
304
305
|
'align',
|
|
305
306
|
'bold',
|
|
@@ -315,9 +316,18 @@ export default function ToolbarPlugin() {
|
|
|
315
316
|
const menus = [];
|
|
316
317
|
for (const item of items) {
|
|
317
318
|
switch (item) {
|
|
319
|
+
case 'component': {
|
|
320
|
+
menus.push(_jsx(FormatButton, { disabled: !isEditable, onClick: () => {
|
|
321
|
+
// add '/' to editor
|
|
322
|
+
editor.dispatchCommand(INSERT_COMPONENT_COMMAND, '/');
|
|
323
|
+
}, className: "toolbar-item spaced", title: "Component Picker", "aria-label": "Open component picker", children: _jsx("i", { className: "iconify", "data-icon": "tabler:circle-plus" }) }, item));
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
318
326
|
case 'block': {
|
|
319
327
|
if (blockType in blockTypeToBlockName && activeEditor === editor) {
|
|
320
|
-
menus.push(_jsx(BlockFormatDropDown, { disabled: !isEditable, blockType: blockType, editor: editor }, "block")
|
|
328
|
+
menus.push(_jsx(BlockFormatDropDown, { disabled: !isEditable, blockType: blockType, editor: editor }, "block")
|
|
329
|
+
// <Divider key="block-divider" />
|
|
330
|
+
);
|
|
321
331
|
}
|
|
322
332
|
if (blockType === 'code') {
|
|
323
333
|
menus.push(_jsx(DropDown, { disabled: !isEditable, buttonClassName: "toolbar-item code-language", buttonLabel: getLanguageFriendlyName(codeLanguage), buttonAriaLabel: "Select language", children: CODE_LANGUAGE_OPTIONS.map(([value, name]) => {
|
package/lib/main/ui/Modal.css
CHANGED
|
@@ -101,7 +101,7 @@ import { HeadingNode } from '@lexical/rich-text';
|
|
|
101
101
|
<EditorConfigProvider
|
|
102
102
|
value={{
|
|
103
103
|
toolbar: {
|
|
104
|
-
items: ['block', 'font-family', 'font-size', 'italic', 'bold', 'underline'],
|
|
104
|
+
items: ['component','block', 'font-family', 'font-size', 'italic', 'bold', 'underline'],
|
|
105
105
|
},
|
|
106
106
|
}}>
|
|
107
107
|
<BlockletEditor />
|
|
@@ -113,7 +113,7 @@ import { HeadingNode } from '@lexical/rich-text';
|
|
|
113
113
|
};
|
|
114
114
|
CustomToolbar.args = {
|
|
115
115
|
toolbar: {
|
|
116
|
-
items: ['block', 'font-family', 'font-size', 'italic', 'bold', 'underline'],
|
|
116
|
+
items: ['component', 'block', 'font-family', 'font-size', 'italic', 'bold', 'underline'],
|
|
117
117
|
},
|
|
118
118
|
};
|
|
119
119
|
export const CustomPlaceholder = (props) => _jsx(BlockletEditor, { ...props });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/editor",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.191",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "npm run storybook",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@arcblock/ux": "^2.9.58",
|
|
41
41
|
"@blocklet/embed": "^0.1.11",
|
|
42
|
-
"@blocklet/pdf": "1.6.
|
|
42
|
+
"@blocklet/pdf": "1.6.191",
|
|
43
43
|
"@excalidraw/excalidraw": "^0.14.2",
|
|
44
44
|
"@iconify/iconify": "^3.0.1",
|
|
45
45
|
"@lexical/clipboard": "0.13.1",
|
|
@@ -108,5 +108,5 @@
|
|
|
108
108
|
"react": "*",
|
|
109
109
|
"react-dom": "*"
|
|
110
110
|
},
|
|
111
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "e0bfe0bdc7ce8d39f4f3684f7170075025bbcb0a"
|
|
112
112
|
}
|