@blocklet/editor 1.6.233 → 1.6.234

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.js CHANGED
@@ -1,20 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import memoize from 'lodash/memoize';
3
2
  import { createContext, useContext, useMemo } from 'react';
4
3
  const EditorConfigContext = createContext({});
5
4
  export function EditorConfigProvider({ children, value }) {
5
+ const parentCtx = useContext(EditorConfigContext);
6
6
  const _value = useMemo(() => {
7
- const result = { ...value };
8
- if (result.AI) {
9
- result.AI.fetchTemplateTags = result.AI.fetchTemplateTags ? memoize(result.AI.fetchTemplateTags) : undefined;
10
- // 注意 memoize 默认以首个参数作为 cache key, 两个属性值相同但引用不同的 object 会被认为是不同的 key
11
- result.AI.fetchTemplates = result.AI.fetchTemplates
12
- ? memoize(result.AI.fetchTemplates, (arg) => JSON.stringify(arg))
13
- : undefined;
14
- result.AI.checkAvailable = memoize(result.AI.checkAvailable);
15
- }
7
+ const result = { ...parentCtx, ...value };
16
8
  return result;
17
- }, [value]);
9
+ }, [value, parentCtx]);
18
10
  return _jsx(EditorConfigContext.Provider, { value: _value, children: children });
19
11
  }
20
12
  export const useEditorConfig = () => useContext(EditorConfigContext);
@@ -189,7 +189,7 @@ export default function AidePluginWrapper() {
189
189
  const _copy = (output) => {
190
190
  copy(output);
191
191
  };
192
- return (_jsx(AideProvider, { checkAvailable: AI.checkAvailable, fetchTemplateTags: AI.fetchTemplateTags, fetchTemplates: AI.fetchTemplates, completions: AI.completions, replaceSelection: _replaceSelection, copy: _copy, insertBelow: _insertBelow, children: _jsx(AidePlugin, {}) }));
192
+ return (_jsx(AideProvider, { checkAvailable: AI.checkAvailable, completions: AI.completions, replaceSelection: _replaceSelection, copy: _copy, insertBelow: _insertBelow, children: _jsx(AidePlugin, {}) }));
193
193
  }
194
194
  return null;
195
195
  }
@@ -1,10 +1,8 @@
1
1
  import { ReactNode } from 'react';
2
- import type { Completions, FetchTemplates, FetchTemplatesRequest, FetchTemplateTags, Mode, ParameterizedTemplate, ParameterizedTemplateCompletionsPayload, Status } from './types';
2
+ import type { Completions, FetchTemplatesRequest, Mode, ParameterizedTemplate, ParameterizedTemplateCompletionsPayload, Status } from './types';
3
3
  import { Template } from './types';
4
4
  interface AideProviderProps {
5
5
  children: ReactNode;
6
- fetchTemplateTags?: FetchTemplateTags;
7
- fetchTemplates?: FetchTemplates;
8
6
  completions: Completions;
9
7
  checkAvailable: () => Promise<boolean> | boolean;
10
8
  replaceSelection?: (selection: any, output: string) => void;
@@ -46,5 +44,5 @@ interface AideContextType extends AideContextState, Pick<AideProviderProps, 'rep
46
44
  }
47
45
  export declare const AideContext: import("react").Context<AideContextType>;
48
46
  export declare const useAideContext: () => AideContextType;
49
- export declare function AideProvider({ checkAvailable, fetchTemplateTags, fetchTemplates, completions, replaceSelection, insertBelow, copy, children, }: AideProviderProps): import("react/jsx-runtime").JSX.Element;
47
+ export declare function AideProvider({ checkAvailable, completions, replaceSelection, insertBelow, copy, children, }: AideProviderProps): import("react/jsx-runtime").JSX.Element;
50
48
  export {};
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
3
- import { useGetState } from 'ahooks';
2
+ import { createContext, useCallback, useContext, useMemo, useRef } from 'react';
3
+ import { useGetState, useRequest } from 'ahooks';
4
4
  import builtinTemplates from './builtin-templates';
5
5
  import { Template } from './types';
6
6
  export const AideContext = createContext({});
7
7
  export const useAideContext = () => useContext(AideContext);
8
- export function AideProvider({ checkAvailable, fetchTemplateTags, fetchTemplates, completions, replaceSelection, insertBelow, copy, children, }) {
8
+ export function AideProvider({ checkAvailable, completions, replaceSelection, insertBelow, copy, children, }) {
9
9
  const [state, setState, getState] = useGetState({
10
10
  available: false,
11
11
  templates: builtinTemplates,
@@ -39,60 +39,12 @@ export function AideProvider({ checkAvailable, fetchTemplateTags, fetchTemplates
39
39
  ...params,
40
40
  }));
41
41
  };
42
- const loadMoreTemplates = async (params = {}) => {
43
- if (fetchTemplates) {
44
- try {
45
- setState((prev) => ({ ...prev, externalTemplates: { ...prev.externalTemplates, loading: true } }));
46
- const { offset = state.externalTemplates.templates.length, limit = 20, tag } = params;
47
- const result = await fetchTemplates({ offset, limit: limit + 1, tag });
48
- if (result.length > 0) {
49
- const hasMore = result.length === limit + 1;
50
- const templates = hasMore ? result.slice(0, -1) : result;
51
- setState((prev) => ({
52
- ...prev,
53
- externalTemplates: {
54
- ...prev.externalTemplates,
55
- templates: offset === 0 ? templates : [...prev.externalTemplates.templates, ...templates],
56
- hasMore,
57
- tag,
58
- loading: false,
59
- },
60
- }));
61
- }
62
- }
63
- catch (e) {
64
- console.error(e);
65
- }
66
- }
67
- };
68
- useEffect(() => {
69
- const init = async () => {
70
- const available = await checkAvailable();
71
- if (available) {
72
- setState((prev) => ({ ...prev, available }));
73
- // load templates & tags
74
- const loadTemplateTags = async () => {
75
- if (fetchTemplateTags) {
76
- try {
77
- const tags = await fetchTemplateTags();
78
- setState((prev) => ({
79
- ...prev,
80
- externalTemplates: {
81
- ...prev.externalTemplates,
82
- tags,
83
- },
84
- }));
85
- }
86
- catch (e) {
87
- console.error(e);
88
- }
89
- }
90
- };
91
- await Promise.all([loadTemplateTags(), loadMoreTemplates()]);
92
- }
93
- };
94
- init();
95
- }, []);
42
+ // @deprecated
43
+ const loadMoreTemplates = async () => { };
44
+ useRequest(async () => {
45
+ const available = await checkAvailable();
46
+ setState((prev) => ({ ...prev, available }));
47
+ }, { cacheKey: 'editor-aide-available' });
96
48
  const activateAI = useCallback(
97
49
  // eslint-disable-next-line require-await
98
50
  async ({ mode = 'normal', selectionText, selection }) => {
@@ -86,7 +86,7 @@ export class AlertNode extends DecoratorBlockNode {
86
86
  editor.update(() => {
87
87
  this.setText(text);
88
88
  if (selection) {
89
- $setSelection(selection);
89
+ $setSelection(selection.clone());
90
90
  }
91
91
  });
92
92
  };
@@ -0,0 +1,7 @@
1
+ import { PropsWithChildren } from 'react';
2
+ interface ContextType {
3
+ locale: string;
4
+ }
5
+ export declare const useContentLocale: () => string;
6
+ export declare const ContentLocaleProvider: ({ locale, children }: PropsWithChildren & ContextType) => import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import { createContext, useContext, useMemo } from 'react';
4
+ const ContentLocaleContext = createContext(null);
5
+ // 该 hook 关注 editor content 的 locale, 区别于 LocaleContext
6
+ // (编辑 post 翻译版本时, 会切换 ContentLocaleContext)
7
+ export const useContentLocale = () => {
8
+ const { locale: contextLocale } = useLocaleContext();
9
+ const ctx = useContext(ContentLocaleContext);
10
+ return ctx?.locale || contextLocale;
11
+ };
12
+ export const ContentLocaleProvider = ({ locale, children }) => {
13
+ const value = useMemo(() => ({ locale }), [locale]);
14
+ return _jsx(ContentLocaleContext.Provider, { value: value, children: children });
15
+ };
@@ -0,0 +1,31 @@
1
+ /// <reference types="react" />
2
+ import type { DOMConversionMap, DOMExportOutput, EditorConfig, ElementFormatType, LexicalEditor, LexicalNode, NodeKey, Spread } from 'lexical';
3
+ import { DecoratorBlockNode, SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
4
+ import type { Properties } from './utils';
5
+ export interface ComponentData {
6
+ id: string;
7
+ name: string;
8
+ properties?: Properties;
9
+ }
10
+ export type SerializedPagesKitComponentNode = Spread<{
11
+ data: ComponentData;
12
+ type: 'pages-kit-component';
13
+ version: 1;
14
+ }, SerializedDecoratorBlockNode>;
15
+ export declare class PagesKitComponentNode extends DecoratorBlockNode {
16
+ __data: ComponentData;
17
+ static getType(): string;
18
+ static clone(node: PagesKitComponentNode): PagesKitComponentNode;
19
+ static importJSON(serializedNode: SerializedPagesKitComponentNode): PagesKitComponentNode;
20
+ exportJSON(): SerializedPagesKitComponentNode;
21
+ constructor(data: ComponentData, format?: ElementFormatType, key?: NodeKey);
22
+ exportDOM(): DOMExportOutput;
23
+ static importDOM(): DOMConversionMap | null;
24
+ updateDOM(): false;
25
+ getId(): string;
26
+ setProperties(properties: any): void;
27
+ decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element;
28
+ isInline(): false;
29
+ }
30
+ export declare function $createPagesKitComponentNode(data: ComponentData): PagesKitComponentNode;
31
+ export declare function $isPagesKitComponentNode(node: PagesKitComponentNode | LexicalNode | null | undefined): node is PagesKitComponentNode;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents';
3
+ import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
4
+ import { PagesKitComponentRenderer } from './PagesKitComponentRenderer';
5
+ function PagesKitComponent({ className, format, nodeKey, data, onPropertiesChange }) {
6
+ return (_jsx(BlockWithAlignableContents, { className: className, format: format, nodeKey: nodeKey, children: _jsx(PagesKitComponentRenderer, { id: data.id, name: data.name, properties: data.properties, onPropertiesChange: onPropertiesChange }) }));
7
+ }
8
+ function convertPagesKitComponentElement(domNode) {
9
+ const pagesKitComponentStr = domNode.getAttribute('data-lexical-pages-kit-component');
10
+ try {
11
+ const parsed = JSON.parse(pagesKitComponentStr);
12
+ if (parsed) {
13
+ const node = $createPagesKitComponentNode(parsed);
14
+ return { node };
15
+ }
16
+ }
17
+ catch (e) {
18
+ console.warn(`Failed to parse: ${pagesKitComponentStr}`, e);
19
+ }
20
+ return null;
21
+ }
22
+ export class PagesKitComponentNode extends DecoratorBlockNode {
23
+ __data;
24
+ static getType() {
25
+ return 'pages-kit-component';
26
+ }
27
+ static clone(node) {
28
+ return new PagesKitComponentNode({ ...node.__data }, node.__format, node.__key);
29
+ }
30
+ static importJSON(serializedNode) {
31
+ const node = $createPagesKitComponentNode(serializedNode.data);
32
+ node.setFormat(serializedNode.format);
33
+ return node;
34
+ }
35
+ exportJSON() {
36
+ return {
37
+ ...super.exportJSON(),
38
+ type: 'pages-kit-component',
39
+ version: 1,
40
+ data: this.__data,
41
+ };
42
+ }
43
+ constructor(data, format, key) {
44
+ super(format, key);
45
+ this.__data = data;
46
+ }
47
+ exportDOM() {
48
+ const element = document.createElement('div');
49
+ element.setAttribute('data-lexical-pages-kit-component', JSON.stringify(this.__data));
50
+ return { element };
51
+ }
52
+ static importDOM() {
53
+ return {
54
+ div: (domNode) => {
55
+ if (!domNode.hasAttribute('data-lexical-pages-kit-component')) {
56
+ return null;
57
+ }
58
+ return {
59
+ conversion: convertPagesKitComponentElement,
60
+ priority: 1,
61
+ };
62
+ },
63
+ };
64
+ }
65
+ updateDOM() {
66
+ return false;
67
+ }
68
+ getId() {
69
+ return this.__data.id;
70
+ }
71
+ setProperties(properties) {
72
+ const writable = this.getWritable();
73
+ writable.__data = { ...writable.__data, properties };
74
+ }
75
+ decorate(editor, config) {
76
+ const embedBlockTheme = config.theme.embedBlock || {};
77
+ const className = {
78
+ base: embedBlockTheme.base || '',
79
+ focus: embedBlockTheme.focus || '',
80
+ };
81
+ return (_jsx(PagesKitComponent, { className: className, format: this.__format, nodeKey: this.getKey(), data: this.__data, onPropertiesChange: (props) => {
82
+ editor.update(() => {
83
+ this.setProperties(props);
84
+ });
85
+ } }));
86
+ }
87
+ isInline() {
88
+ return false;
89
+ }
90
+ }
91
+ export function $createPagesKitComponentNode(data) {
92
+ return new PagesKitComponentNode(data);
93
+ }
94
+ export function $isPagesKitComponentNode(node) {
95
+ return node instanceof PagesKitComponentNode;
96
+ }
@@ -0,0 +1,18 @@
1
+ /// <reference types="react" />
2
+ import { LexicalCommand } from 'lexical';
3
+ type CommandPayload = {
4
+ id: string;
5
+ name: string;
6
+ };
7
+ export declare const INSERT_PAGES_KIT_COMPONENT_COMMAND: LexicalCommand<CommandPayload>;
8
+ export declare function PagesKitComponentPlugin(): JSX.Element | null;
9
+ export declare function usePagesKitComponents(): {
10
+ pagesKitComponents: {
11
+ id: string;
12
+ name: string;
13
+ properties?: {
14
+ [key: string]: any;
15
+ } | undefined;
16
+ }[];
17
+ };
18
+ export {};
@@ -0,0 +1,38 @@
1
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
2
+ import { $insertNodeToNearestRoot } from '@lexical/utils';
3
+ import { COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical';
4
+ import { useEffect } from 'react';
5
+ import { useRequest } from 'ahooks';
6
+ import { joinURL } from 'ufo';
7
+ import { $createPagesKitComponentNode, PagesKitComponentNode } from './PagesKitComponentNode';
8
+ import { getBlockletMountPointInfo, isBlockletRunning } from '../utils';
9
+ import { mode } from './utils';
10
+ export const INSERT_PAGES_KIT_COMPONENT_COMMAND = createCommand('INSERT_PAGES_KIT_COMPONENT_COMMAND');
11
+ export function PagesKitComponentPlugin() {
12
+ const [editor] = useLexicalComposerContext();
13
+ useEffect(() => {
14
+ if (!editor.hasNodes([PagesKitComponentNode])) {
15
+ throw new Error('PagesKitComponentPlugin: PagesKitComponentNode not registered on editor');
16
+ }
17
+ return editor.registerCommand(INSERT_PAGES_KIT_COMPONENT_COMMAND, (payload) => {
18
+ const node = $createPagesKitComponentNode(payload);
19
+ $insertNodeToNearestRoot(node);
20
+ return true;
21
+ }, COMMAND_PRIORITY_EDITOR);
22
+ }, [editor]);
23
+ return null;
24
+ }
25
+ export function usePagesKitComponents() {
26
+ const { data: pagesKitComponents = [] } = useRequest(() => {
27
+ if (!isBlockletRunning('pages-kit')) {
28
+ return Promise.resolve([]);
29
+ }
30
+ return fetch(joinURL(getBlockletMountPointInfo('pages-kit').mountPoint || '/', `/api/components?mode=${mode}`))
31
+ .then((res) => res.json())
32
+ .then((res) => res?.components || [])
33
+ .catch((res) => []);
34
+ }, {
35
+ cacheKey: 'editor-pages-kit-components',
36
+ });
37
+ return { pagesKitComponents };
38
+ }
@@ -0,0 +1,9 @@
1
+ import { type Properties } from './utils';
2
+ type PagesKitComponentProps = {
3
+ id: string;
4
+ name: string;
5
+ properties?: Properties;
6
+ onPropertiesChange: (properties: Properties) => void;
7
+ };
8
+ export declare function PagesKitComponentRenderer({ id, name, properties, onPropertiesChange }: PagesKitComponentProps): import("react/jsx-runtime").JSX.Element | null;
9
+ export {};
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { Box, Button, Divider, IconButton, Popover, useMediaQuery } from '@mui/material';
4
+ import { useEffect, useState } from 'react';
5
+ import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
6
+ import { CustomComponentRenderer, customComponentStates } from '@blocklet/pages-kit/components';
7
+ import { isBlockletRunning } from '../utils';
8
+ import { mode, isValidPropertyType, getLocalizedValue, getPropertyLabel } from './utils';
9
+ import { PropertyField } from './PropertyField';
10
+ import { useContentLocale } from '../ContentLocale';
11
+ export function PagesKitComponentRenderer({ id, name, properties, onPropertiesChange }) {
12
+ const locale = useContentLocale();
13
+ const [editor] = useLexicalComposerContext();
14
+ const isEditable = editor.isEditable();
15
+ const [loading, setLoading] = useState(true);
16
+ const [anchorEl, setAnchorEl] = useState(null);
17
+ const open = Boolean(anchorEl);
18
+ const upMd = useMediaQuery((theme) => theme.breakpoints.up('md'));
19
+ const [pendingProperties, setPendingProperties] = useState(properties || {});
20
+ const handleClick = (event) => {
21
+ setAnchorEl(event.currentTarget);
22
+ };
23
+ const handleCancel = () => {
24
+ setAnchorEl(null);
25
+ setPendingProperties(properties || {});
26
+ };
27
+ const handleSave = () => {
28
+ onPropertiesChange(pendingProperties);
29
+ setAnchorEl(null);
30
+ };
31
+ useEffect(() => {
32
+ const load = async () => {
33
+ const state = customComponentStates.getState();
34
+ await state.loadComponents({
35
+ mode,
36
+ locale,
37
+ instances: [{ id, component: { name } }],
38
+ });
39
+ setLoading(false);
40
+ };
41
+ if (isBlockletRunning('pages-kit')) {
42
+ load();
43
+ }
44
+ }, [id, name]);
45
+ const component = customComponentStates.getState()?.state?.components?.[id]?.component;
46
+ if (loading || !component) {
47
+ return null;
48
+ }
49
+ // 暂时仅支持基础类型的组件参数配置
50
+ const propDefinitions = Object.values(component.properties || {})
51
+ .map((x) => x.data)
52
+ .filter((x) => !!x.key && isValidPropertyType(x.type));
53
+ return (_jsxs(Box, { sx: { position: 'relative', ...(isEditable && { mt: 2 }) }, children: [_jsx(CustomComponentRenderer, { componentId: id, locale: locale, dev: { mode }, props: properties?.[locale] }), isEditable && !!propDefinitions.length && upMd && (_jsxs(_Fragment, { children: [_jsx(IconButton, { sx: { position: 'absolute', right: 0, top: -32 }, onClick: handleClick, children: _jsx(SettingsOutlinedIcon, {}) }), _jsx(Popover, { open: open, anchorEl: anchorEl, onClose: () => setAnchorEl(null), anchorOrigin: {
54
+ vertical: 'bottom',
55
+ horizontal: 'right',
56
+ }, transformOrigin: {
57
+ vertical: 'top',
58
+ horizontal: 'right',
59
+ }, children: _jsxs(Box, { sx: { width: 440, maxHeight: 500, p: 2 }, children: [_jsx(Box, { sx: { display: 'flex', alignItems: 'center' }, children: _jsxs(Box, { sx: {
60
+ display: 'flex',
61
+ alignItems: 'center',
62
+ gap: 0.5,
63
+ color: 'text.secondary',
64
+ fontSize: 14,
65
+ fontWeight: 600,
66
+ }, children: [_jsx(Box, { component: "span", className: "iconify", "data-icon": "tabler:settings-2", sx: { fontSize: 20 } }), _jsx("span", { children: "Properties" })] }) }), _jsx(Divider, { sx: { mt: 0.75, mb: 2 } }), propDefinitions.map((x) => {
67
+ // @ts-ignore
68
+ const multiline = !!x.multiline;
69
+ return (_jsx(Box, { sx: { mt: 2 }, children: _jsx(PropertyField, { label: getPropertyLabel({ locale, locales: x.locales }), type: x.type, value: pendingProperties[locale]?.[x.key] ||
70
+ getLocalizedValue({ key: 'defaultValue', locale, data: x.locales }), onChange: (v) => setPendingProperties({
71
+ ...pendingProperties,
72
+ [locale]: { ...pendingProperties[locale], [x.key]: v },
73
+ }), multiline: multiline }) }, x.key));
74
+ }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end', gap: 1, py: 2 }, children: [_jsx(Button, { color: "inherit", onClick: handleCancel, children: "Cancel" }), _jsx(Button, { color: "primary", variant: "contained", onClick: handleSave, children: "Save" })] })] }) })] }))] }));
75
+ }
@@ -0,0 +1,13 @@
1
+ import { type PropertyType } from './utils';
2
+ interface PropertyFieldProps {
3
+ type?: PropertyType;
4
+ multiline?: boolean;
5
+ label: string;
6
+ value: any;
7
+ onChange: (value: any) => void;
8
+ }
9
+ export declare function UploaderButton({ onChange }: {
10
+ onChange?: Function;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ export declare function PropertyField({ type, multiline, label, value, onChange, ...rest }: PropertyFieldProps): import("react/jsx-runtime").JSX.Element;
13
+ export {};
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { FormControlLabel, IconButton, InputAdornment, Switch, TextField } from '@mui/material';
3
+ import UploadIcon from '@mui/icons-material/Upload';
4
+ function getVideoSize(url) {
5
+ return new Promise((resolve, reject) => {
6
+ const video = document.createElement('video');
7
+ video.src = url;
8
+ video.onloadedmetadata = () => {
9
+ const { videoWidth: naturalWidth, videoHeight: naturalHeight } = video;
10
+ resolve({ naturalWidth, naturalHeight });
11
+ };
12
+ video.onerror = (e) => reject(e);
13
+ });
14
+ }
15
+ function getImageSize(url) {
16
+ return new Promise((resolve, reject) => {
17
+ const img = new Image();
18
+ img.src = url;
19
+ img.onload = () => {
20
+ const { naturalWidth, naturalHeight } = img;
21
+ resolve({ naturalWidth, naturalHeight });
22
+ };
23
+ img.onerror = (e) => reject(e);
24
+ });
25
+ }
26
+ export function UploaderButton({ onChange }) {
27
+ const handleOpen = () => {
28
+ // @ts-ignore
29
+ const uploader = uploaderRef?.current?.getUploader();
30
+ uploader?.open();
31
+ if (onChange) {
32
+ // rewrite default emitter
33
+ uploader.onceUploadSuccess((...args) => {
34
+ onChange(...args);
35
+ });
36
+ }
37
+ };
38
+ return (_jsx(IconButton, { size: "small", onClick: handleOpen, children: _jsx(UploadIcon, {}) }, "uploader-trigger"));
39
+ }
40
+ export function PropertyField({ type, multiline, label, value, onChange, ...rest }) {
41
+ if (type === 'boolean') {
42
+ return (_jsx(FormControlLabel, { sx: { display: 'inline-flex', m: 0 }, label: label, labelPlacement: "start", control: _jsx(Switch, { checked: value === true, onChange: (_, checked) => onChange?.(checked) }) }));
43
+ }
44
+ if (type === 'number') {
45
+ return (_jsx(TextField, { type: "number", size: "small", sx: { width: 1 }, label: label, value: value, onChange: (e) => onChange(e.target.value), ...rest }));
46
+ }
47
+ if (type === 'url') {
48
+ return (_jsx(TextField, { sx: { width: 1 }, size: "small", label: label, value: value.url, onChange: (e) => onChange(e.target.value), InputProps: {
49
+ endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(UploaderButton, { onChange: async ({ response }) => {
50
+ const url = response?.data?.url || response?.data?.fileUrl;
51
+ let size;
52
+ if (url) {
53
+ size = await getImageSize(url)
54
+ .catch(() => getVideoSize(url))
55
+ .catch(() => undefined);
56
+ }
57
+ onChange?.({ url, width: size?.naturalWidth, height: size?.naturalHeight });
58
+ } }) })),
59
+ }, ...rest }));
60
+ }
61
+ return (_jsx(TextField, { sx: { width: 1 }, size: "small", label: label, value: value, onChange: (e) => onChange(e.target.value), ...((multiline || type === 'json') && {
62
+ multiline: true,
63
+ rows: 2,
64
+ }), ...rest }));
65
+ }
@@ -0,0 +1,25 @@
1
+ export declare const mode: string;
2
+ export declare const propertyTypes: readonly ["string", "number", "boolean", "json", "url"];
3
+ export type PropertyType = typeof propertyTypes[number];
4
+ export type Properties = {
5
+ [locale: string]: {
6
+ [key: string]: any;
7
+ };
8
+ };
9
+ export declare const isValidPropertyType: (type: string | null | undefined) => boolean;
10
+ type Locales = {
11
+ [locale: string]: {
12
+ name?: string;
13
+ defaultValue?: any;
14
+ };
15
+ };
16
+ export declare const getLocalizedValue: ({ key, locale, data }: {
17
+ key: string;
18
+ locale: string;
19
+ data?: Properties | undefined;
20
+ }) => any;
21
+ export declare const getPropertyLabel: ({ locale, locales }: {
22
+ locale: string;
23
+ locales?: Locales | undefined;
24
+ }) => string;
25
+ export {};
@@ -0,0 +1,16 @@
1
+ export const mode = process.env.NODE_ENV === 'development' ? 'draft' : 'production';
2
+ export const propertyTypes = ['string', 'number', 'boolean', 'json', 'url'];
3
+ export const isValidPropertyType = (type) => {
4
+ return !type || propertyTypes.includes(type);
5
+ };
6
+ // 依次尝试取值
7
+ // - `data[locale][key]`
8
+ // - `data.en[key]`
9
+ // - 遍历所有 locale 取第一个有值的 `data[locale][key]`
10
+ export const getLocalizedValue = ({ key, locale, data }) => {
11
+ data = data || {};
12
+ return data[locale]?.[key] || data.en?.[key] || Object.values(data).find((x) => x?.[key])?.[key] || '';
13
+ };
14
+ export const getPropertyLabel = ({ locale, locales }) => {
15
+ return getLocalizedValue({ key: 'name', locale, data: locales });
16
+ };
@@ -50,7 +50,7 @@ export default function SubpageListingComponent({ docPostId, mode, onModeChange,
50
50
  if (loading) {
51
51
  return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', alignItems: 'center', height: 200 }, children: _jsx(CircularProgress, {}) }));
52
52
  }
53
- return (_jsxs(Box, { sx: { display: 'flex', flexWrap: 'wrap', position: 'relative', mx: -1 }, children: [isEmpty && (_jsx(Box, { sx: { width: 1, px: 1 }, children: _jsx(Alert, { severity: "info", sx: {}, children: "No subpages" }) })), isEditable && (_jsxs(_Fragment, { children: [_jsx(IconButton, { sx: { position: 'absolute', right: 0, top: -32 }, onClick: handleClick, children: _jsx(SettingsOutlinedIcon, {}) }), _jsx(Popover, { open: open, anchorEl: anchorEl, onClose: () => setAnchorEl(null), anchorOrigin: {
53
+ return (_jsxs(Box, { sx: { display: 'flex', flexWrap: 'wrap', position: 'relative', mx: -1, ...(isEditable && { mt: 2 }) }, children: [isEmpty && (_jsx(Box, { sx: { width: 1, px: 1 }, children: _jsx(Alert, { severity: "info", sx: {}, children: "No subpages" }) })), isEditable && (_jsxs(_Fragment, { children: [_jsx(IconButton, { sx: { position: 'absolute', right: 0, top: -32 }, onClick: handleClick, children: _jsx(SettingsOutlinedIcon, {}) }), _jsx(Popover, { open: open, anchorEl: anchorEl, onClose: () => setAnchorEl(null), anchorOrigin: {
54
54
  vertical: 'bottom',
55
55
  horizontal: 'right',
56
56
  }, transformOrigin: {
@@ -17,3 +17,4 @@ export declare const sleep: (ms: number) => Promise<unknown>;
17
17
  export declare const minDelay: <T>(promise: Promise<T>, ms: number) => Promise<T>;
18
18
  export declare function exhaustiveGuard(_value: never): never;
19
19
  export declare const isSameHost: (url: string | URL) => boolean;
20
+ export declare const isBlockletRunning: (name: string) => boolean;
package/lib/ext/utils.js CHANGED
@@ -86,3 +86,6 @@ export const isSameHost = (url) => {
86
86
  return false;
87
87
  }
88
88
  };
89
+ export const isBlockletRunning = (name) => {
90
+ return getBlockletMountPointInfo(name)?.status === 'running';
91
+ };
@@ -82,6 +82,7 @@ import { PdfPlugin } from '../ext/PdfPlugin';
82
82
  import { BlurTextPlugin } from '../ext/BlurTextPlugin';
83
83
  import { useResponsiveTable } from './hooks/useResponsiveTable';
84
84
  import { useTranslationListener } from './hooks/useTranslationListener';
85
+ import { PagesKitComponentPlugin } from '../ext/PagesKitComponent/PagesKitComponentPlugin';
85
86
  export default function Editor({ children, placeholder, onChange, autoFocus = true, showToolbar = true, editorRef, onReady, enableHeadingsIdPlugin, }) {
86
87
  const [editor] = useLexicalComposerContext();
87
88
  const [editable, setEditable] = useState(false);
@@ -109,7 +110,7 @@ export default function Editor({ children, placeholder, onChange, autoFocus = tr
109
110
  console.warn('You need to configure uploader to use ImagesPlugin');
110
111
  }
111
112
  }, [hasNodes('image'), hasUploader]);
112
- return (_jsxs(_Fragment, { children: [isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), isMaxLength && _jsx(MaxLengthPlugin, { maxLength: 30 }), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), _jsx(EmojiPickerPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('emoji') && _jsx(EmojisPlugin, {}), hasNodes('hashtag') && _jsx(HashtagPlugin, {}), hasNodes('keyword') && _jsx(KeywordsPlugin, {}), _jsx(SpeechToTextPlugin, {}), hasNodes('autolink') && _jsx(AutoLinkPlugin, {}), isRichText ? (_jsxs(_Fragment, { children: [_jsx(HistoryPlugin, { externalHistoryState: historyState }), _jsx(EditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), _jsx(MarkdownShortcutPlugin, {}), _jsx(TabIndentationPlugin, {}), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListPlugin, {}), hasNodes('list', 'listitem') && _jsx(CheckListPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListMaxIndentLevelPlugin, { maxDepth: 7 }), hasNodes('table', 'tablerow', 'tablecell') && editable && (_jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true })), hasNodes('table', 'tablerow', 'tablecell') && _jsx(TableCellResizer, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(BilibiliPlugin, {}), _jsx(BlockletEmbedPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), _jsx(AidePlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('equation') && _jsx(EquationsPlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), _jsx(TabFocusPlugin, {}), hasNodes('collapsible-container', 'collapsible-content', 'collapsible-title') && _jsx(CollapsiblePlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && (_jsxs(_Fragment, { children: [_jsx(DraggableBlockPlugin, { anchorElem: floatingAnchorElem }), hasNodes('code') && _jsx(CodeActionMenuPlugin, { anchorElem: floatingAnchorElem }), hasNodes('link') && _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem }), hasNodes('table') && _jsx(TableCellActionMenuPlugin, { anchorElem: floatingAnchorElem, cellMerge: true }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem })] })), enableHeadingsIdPlugin && _jsx(HeadingsIdPlugin, {})] })) : (_jsxs(_Fragment, { children: [_jsx(PlainTextPlugin, { contentEditable: _jsx(ContentEditable, {}), placeholder: _jsx(Placeholder, { children: "placeholder" }), ErrorBoundary: LexicalErrorBoundary }), _jsx(HistoryPlugin, { externalHistoryState: historyState })] })), (isCharLimit || isCharLimitUtf8) && (_jsx(CharacterLimitPlugin, { charset: isCharLimit ? 'UTF-16' : 'UTF-8', maxLength: 5 })), isAutocomplete && hasNodes('autocomplete') && _jsx(AutocompletePlugin, {}), _jsx("div", { children: showTableOfContents && _jsx(TableOfContentsPlugin, {}) }), _jsx(SelectBlockPlugin, {}), _jsx(RemoveListPlugin, {}), _jsx(MarkdownHeadTextPlugin, {}), _jsx(AiImagePlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), children] }));
113
+ return (_jsxs(_Fragment, { children: [isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), isMaxLength && _jsx(MaxLengthPlugin, { maxLength: 30 }), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), _jsx(EmojiPickerPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('emoji') && _jsx(EmojisPlugin, {}), hasNodes('hashtag') && _jsx(HashtagPlugin, {}), hasNodes('keyword') && _jsx(KeywordsPlugin, {}), _jsx(SpeechToTextPlugin, {}), hasNodes('autolink') && _jsx(AutoLinkPlugin, {}), isRichText ? (_jsxs(_Fragment, { children: [_jsx(HistoryPlugin, { externalHistoryState: historyState }), _jsx(EditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), _jsx(MarkdownShortcutPlugin, {}), _jsx(TabIndentationPlugin, {}), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListPlugin, {}), hasNodes('list', 'listitem') && _jsx(CheckListPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListMaxIndentLevelPlugin, { maxDepth: 7 }), hasNodes('table', 'tablerow', 'tablecell') && editable && (_jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true })), hasNodes('table', 'tablerow', 'tablecell') && _jsx(TableCellResizer, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(BilibiliPlugin, {}), _jsx(BlockletEmbedPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), _jsx(AidePlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('equation') && _jsx(EquationsPlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), hasNodes('collapsible-container', 'collapsible-content', 'collapsible-title') && _jsx(CollapsiblePlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && (_jsxs(_Fragment, { children: [_jsx(DraggableBlockPlugin, { anchorElem: floatingAnchorElem }), hasNodes('code') && _jsx(CodeActionMenuPlugin, { anchorElem: floatingAnchorElem }), hasNodes('link') && _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem }), hasNodes('table') && _jsx(TableCellActionMenuPlugin, { anchorElem: floatingAnchorElem, cellMerge: true }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem })] })), enableHeadingsIdPlugin && _jsx(HeadingsIdPlugin, {})] })) : (_jsxs(_Fragment, { children: [_jsx(PlainTextPlugin, { contentEditable: _jsx(ContentEditable, {}), placeholder: _jsx(Placeholder, { children: "placeholder" }), ErrorBoundary: LexicalErrorBoundary }), _jsx(HistoryPlugin, { externalHistoryState: historyState })] })), (isCharLimit || isCharLimitUtf8) && (_jsx(CharacterLimitPlugin, { charset: isCharLimit ? 'UTF-16' : 'UTF-8', maxLength: 5 })), isAutocomplete && hasNodes('autocomplete') && _jsx(AutocompletePlugin, {}), _jsx("div", { children: showTableOfContents && _jsx(TableOfContentsPlugin, {}) }), _jsx(SelectBlockPlugin, {}), _jsx(RemoveListPlugin, {}), _jsx(MarkdownHeadTextPlugin, {}), _jsx(AiImagePlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), children] }));
113
114
  }
114
115
  const EditorContent = styled.div `
115
116
  position: relative;
@@ -37,6 +37,7 @@ import { AlertNode } from '../../ext/Alert/AlertNode';
37
37
  import { VideoNode } from '../../ext/VideoPlugin';
38
38
  import { PdfNode } from '../../ext/PdfPlugin';
39
39
  import { SubpageListingNode } from '../../ext/SubpageListingPlugin/SubpageListingNode';
40
+ import { PagesKitComponentNode } from '../../ext/PagesKitComponent/PagesKitComponentNode';
40
41
  const PlaygroundNodes = [
41
42
  HeadingNode,
42
43
  ListNode,
@@ -76,5 +77,6 @@ const PlaygroundNodes = [
76
77
  AlertNode,
77
78
  PdfNode,
78
79
  SubpageListingNode,
80
+ PagesKitComponentNode,
79
81
  ];
80
82
  export default PlaygroundNodes;
@@ -18,6 +18,7 @@ import { INSERT_TABLE_COMMAND } from '@lexical/table';
18
18
  import { $createParagraphNode, $getSelection, $isRangeSelection, FORMAT_ELEMENT_COMMAND, createCommand, $insertNodes, $createTextNode, COMMAND_PRIORITY_EDITOR, } from 'lexical';
19
19
  import { mergeRegister } from '@lexical/utils';
20
20
  import { useCallback, useEffect, useMemo, useState } from 'react';
21
+ import { Box } from '@mui/material';
21
22
  import { LexicalTypeaheadMenuPlugin, TypeaheadOption, useBasicTypeaheadTriggerMatch, } from '../LexicalTypeaheadMenuPlugin';
22
23
  import useHasNodes from '../../hooks/useHasNodes';
23
24
  import useModal from '../../hooks/useModal';
@@ -35,6 +36,7 @@ import { INSERT_VIDEO_COMMAND, isVideo } from '../../../ext/VideoPlugin';
35
36
  import { INSERT_TEMPLATE_COMMAND } from '../../../ext/TemplatePlugin';
36
37
  import { INSERT_PDF_COMMAND, isPdf } from '../../../ext/PdfPlugin';
37
38
  import { $insertSubpageListing } from '../../../ext/SubpageListingPlugin/SubpageListingPlugin';
39
+ import { INSERT_PAGES_KIT_COMPONENT_COMMAND, usePagesKitComponents, } from '../../../ext/PagesKitComponent/PagesKitComponentPlugin';
38
40
  class ComponentPickerOption extends TypeaheadOption {
39
41
  // What shows up in the editor
40
42
  title;
@@ -68,6 +70,7 @@ export default function ComponentPickerMenuPlugin() {
68
70
  const [modal, showModal] = useModal();
69
71
  const [queryString, setQueryString] = useState(null);
70
72
  const config = useEditorConfig();
73
+ const { pagesKitComponents } = usePagesKitComponents();
71
74
  const hasUploader = !!config.uploader;
72
75
  const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
73
76
  minLength: 0,
@@ -290,6 +293,13 @@ export default function ComponentPickerMenuPlugin() {
290
293
  keywords: ['subpage', 'doc', 'list'],
291
294
  onSelect: () => $insertSubpageListing(editor, 'children'),
292
295
  }),
296
+ ...pagesKitComponents
297
+ .filter((x) => !!x.name)
298
+ .map((x) => new ComponentPickerOption(`${x.name}`, {
299
+ icon: (_jsx(Box, { component: "span", sx: { display: 'inline-flex', width: 20, px: 0.25, mr: '1px', fontSize: 13 }, title: "Pages kit custom component", children: "\uD83E\uDDE9" })),
300
+ keywords: [x.name],
301
+ onSelect: () => editor.dispatchCommand(INSERT_PAGES_KIT_COMPONENT_COMMAND, { id: x.id, name: x.name }),
302
+ })),
293
303
  ].filter(Boolean);
294
304
  const dynamicOptions = getDynamicOptions();
295
305
  return queryString
@@ -25,8 +25,6 @@ import { sanitizeUrl } from '../../utils/sanitizeUrl';
25
25
  // import { InsertEquationDialog } from "../EquationsPlugin";
26
26
  // import { INSERT_EXCALIDRAW_COMMAND } from "../ExcalidrawPlugin";
27
27
  import { INSERT_IMAGE_COMMAND } from '../ImagesPlugin';
28
- import { AideToolbarButton } from '../../../ext/AIPlugin';
29
- import { AITranslateToolbarButton } from '../../../ext/AITranslationPlugin';
30
28
  import { uploadFile, INSERT_COMPONENT_COMMAND } from '../ComponentPickerPlugin';
31
29
  // import { InsertNewTableDialog, InsertTableDialog } from "../TablePlugin";
32
30
  const blockTypeToBlockName = {
@@ -463,8 +461,6 @@ export default function ToolbarPlugin() {
463
461
  if (menus.length === 0) {
464
462
  return null;
465
463
  }
466
- menus.push(_jsx(AideToolbarButton, {}, "aide"));
467
- menus.push(_jsx(AITranslateToolbarButton, {}, "ai-translate"));
468
464
  return (_jsxs(Toolbar, { className: "toolbar", children: [menus, modal] }));
469
465
  }
470
466
  const Toolbar = styled.div `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/editor",
3
- "version": "1.6.233",
3
+ "version": "1.6.234",
4
4
  "main": "lib/index.js",
5
5
  "scripts": {
6
6
  "dev": "npm run storybook",
@@ -39,7 +39,8 @@
39
39
  "dependencies": {
40
40
  "@arcblock/ux": "^2.9.75",
41
41
  "@blocklet/embed": "^0.1.11",
42
- "@blocklet/pdf": "1.6.233",
42
+ "@blocklet/pages-kit": "^0.2.283",
43
+ "@blocklet/pdf": "1.6.234",
43
44
  "@excalidraw/excalidraw": "^0.14.2",
44
45
  "@iconify/iconify": "^3.0.1",
45
46
  "@lexical/clipboard": "0.13.1",
@@ -108,5 +109,5 @@
108
109
  "react": "*",
109
110
  "react-dom": "*"
110
111
  },
111
- "gitHead": "f402f1a003e98d6db8526534c95e724d5f3dfdb2"
112
+ "gitHead": "630c1a20241c7d3d65d3f90f78b7858d2198b25d"
112
113
  }