remons-markdown-editor 1.0.0-beta.6 → 1.0.0-beta.8
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/hooks/useRegisterToolbar.d.ts +53 -3
- package/dist/index.cjs +147 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +43 -44
- package/dist/index.js +148 -20
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +78 -6
- package/package.json +79 -81
- package/readme.md +309 -248
|
@@ -9,15 +9,36 @@ import { ComponentSchema } from '../types';
|
|
|
9
9
|
* 2. 标签包裹模式:name + wrapTags
|
|
10
10
|
* 插入简单的标签包裹文本,如 <plugin-container>...</plugin-container>
|
|
11
11
|
*
|
|
12
|
-
* @example 容器模式
|
|
12
|
+
* @example 容器模式 - 使用 fields 自动生成表单(最简)
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const schema: ComponentSchema = {
|
|
15
|
+
* name: 'alert',
|
|
16
|
+
* label: '警告框',
|
|
17
|
+
* icon: <AlertOutlined />,
|
|
18
|
+
* fields: [
|
|
19
|
+
* { key: 'type', name: '类型', componentType: 'select',
|
|
20
|
+
* componentProps: { options: [{ value: 'price', label: '价格' }, { value: 'info', label: '信息' }] } },
|
|
21
|
+
* { key: 'content', name: '内容', componentType: 'textarea',
|
|
22
|
+
* componentProps: { rows: 3 } },
|
|
23
|
+
* { key: 'enabled', name: '启用', componentType: 'switch' },
|
|
24
|
+
* ],
|
|
25
|
+
* }
|
|
26
|
+
* // 支持的 componentType: 'input' | 'textarea' | 'number' | 'select' | 'switch' | 'slider' | 'rate' | 'color' | 'date' | 'dateRange'
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example 容器模式 - 使用 Form.Item 自动绑定(推荐)
|
|
13
30
|
* ```tsx
|
|
14
31
|
* const schema: ComponentSchema = {
|
|
15
32
|
* name: 'alert',
|
|
16
33
|
* label: '警告框',
|
|
17
34
|
* icon: <AlertOutlined />,
|
|
18
35
|
* fields: [{ key: 'level', defaultValue: 'info' }],
|
|
19
|
-
* renderDialog: (
|
|
20
|
-
* <
|
|
36
|
+
* renderDialog: ({ form }) => (
|
|
37
|
+
* <Form form={form} layout="vertical">
|
|
38
|
+
* <Form.Item name="level" label="级别">
|
|
39
|
+
* <Input />
|
|
40
|
+
* </Form.Item>
|
|
41
|
+
* </Form>
|
|
21
42
|
* ),
|
|
22
43
|
* }
|
|
23
44
|
*
|
|
@@ -27,6 +48,35 @@ import { ComponentSchema } from '../types';
|
|
|
27
48
|
* }
|
|
28
49
|
* ```
|
|
29
50
|
*
|
|
51
|
+
* @example 容器模式 - 手动控制
|
|
52
|
+
* ```tsx
|
|
53
|
+
* const schema: ComponentSchema = {
|
|
54
|
+
* name: 'alert',
|
|
55
|
+
* label: '警告框',
|
|
56
|
+
* icon: <AlertOutlined />,
|
|
57
|
+
* fields: [{ key: 'level', defaultValue: 'info' }],
|
|
58
|
+
* renderDialog: ({ props, onChange }) => (
|
|
59
|
+
* <Input value={props.level} onChange={e => onChange('level', e.target.value)} />
|
|
60
|
+
* ),
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @example 容器模式 - 完全自定义弹窗
|
|
65
|
+
* ```tsx
|
|
66
|
+
* const schema: ComponentSchema = {
|
|
67
|
+
* name: 'alert',
|
|
68
|
+
* label: '警告框',
|
|
69
|
+
* icon: <AlertOutlined />,
|
|
70
|
+
* fields: [{ key: 'level', defaultValue: 'info' }],
|
|
71
|
+
* renderDialog: ({ props, onSave }) => (
|
|
72
|
+
* <MyCustomEditor
|
|
73
|
+
* value={props.level}
|
|
74
|
+
* onSave={({ level }) => onSave({ level })}
|
|
75
|
+
* />
|
|
76
|
+
* ),
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
30
80
|
* @example 标签包裹模式
|
|
31
81
|
* ```tsx
|
|
32
82
|
* function MyPage() {
|
package/dist/index.cjs
CHANGED
|
@@ -577,15 +577,16 @@ var Preview = (0, react.forwardRef)(({ content, previewOptions }, ref) => {
|
|
|
577
577
|
Preview.displayName = "Preview";
|
|
578
578
|
//#endregion
|
|
579
579
|
//#region src/components/Panel/SplitPanels.tsx
|
|
580
|
+
var { Panel } = antd.Splitter;
|
|
580
581
|
function SplitPanels({ leftPanel, rightPanel }) {
|
|
581
582
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(antd.Splitter, {
|
|
582
583
|
className: "split-panels",
|
|
583
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
584
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Panel, {
|
|
584
585
|
defaultSize: "50%",
|
|
585
586
|
min: "20%",
|
|
586
587
|
max: "80%",
|
|
587
588
|
children: leftPanel
|
|
588
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
589
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Panel, { children: rightPanel })]
|
|
589
590
|
});
|
|
590
591
|
}
|
|
591
592
|
//#endregion
|
|
@@ -1066,12 +1067,59 @@ function Toolbar({ editorView, fullscreen = true, onToggleFullscreen, extraItems
|
|
|
1066
1067
|
//#endregion
|
|
1067
1068
|
//#region src/components/Dialog/EditDialog.tsx
|
|
1068
1069
|
/**
|
|
1070
|
+
* 组件类型到 antd 表单组件的映射
|
|
1071
|
+
*/
|
|
1072
|
+
var FormComponents = {
|
|
1073
|
+
input: antd.Input,
|
|
1074
|
+
textarea: antd.Input.TextArea,
|
|
1075
|
+
number: antd.InputNumber,
|
|
1076
|
+
select: antd.Select,
|
|
1077
|
+
switch: antd.Switch,
|
|
1078
|
+
slider: antd.Slider,
|
|
1079
|
+
rate: antd.Rate,
|
|
1080
|
+
color: antd.ColorPicker,
|
|
1081
|
+
date: antd.DatePicker,
|
|
1082
|
+
dateRange: antd.DatePicker.RangePicker
|
|
1083
|
+
};
|
|
1084
|
+
/**
|
|
1085
|
+
* 渲染对应类型的 antd 表单组件
|
|
1086
|
+
*/
|
|
1087
|
+
function renderFormItemComponent(field) {
|
|
1088
|
+
const props = field.componentProps || {};
|
|
1089
|
+
const Component = field.componentType ? FormComponents[field.componentType] : antd.Input;
|
|
1090
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...props });
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* 根据 fields 自动生成 Form.Item 列表
|
|
1094
|
+
*/
|
|
1095
|
+
function renderAutoForm(form, fields) {
|
|
1096
|
+
if (!fields || fields.length === 0) return null;
|
|
1097
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Form, {
|
|
1098
|
+
form,
|
|
1099
|
+
layout: "vertical",
|
|
1100
|
+
children: fields.map((field) => {
|
|
1101
|
+
if (!field.componentType) return null;
|
|
1102
|
+
const formItemProps = {
|
|
1103
|
+
name: field.key,
|
|
1104
|
+
label: field.name || field.key,
|
|
1105
|
+
valuePropName: field.componentType === "switch" ? "checked" : void 0,
|
|
1106
|
+
...field.componentProps
|
|
1107
|
+
};
|
|
1108
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Form.Item, {
|
|
1109
|
+
...formItemProps,
|
|
1110
|
+
children: renderFormItemComponent(field)
|
|
1111
|
+
}, field.key);
|
|
1112
|
+
})
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1069
1116
|
* 属性编辑弹窗组件
|
|
1070
1117
|
* 根据组件 schema 的 renderDialog 函数渲染弹窗内容
|
|
1071
1118
|
*/
|
|
1072
1119
|
function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
1073
1120
|
const schema = componentRegistry.get(typeName);
|
|
1074
1121
|
const [formProps, setFormProps] = (0, react.useState)({});
|
|
1122
|
+
const [form] = antd.Form.useForm();
|
|
1075
1123
|
const mergedProps = (0, react.useMemo)(() => {
|
|
1076
1124
|
if (!schema) return currentProps;
|
|
1077
1125
|
const defaults = {};
|
|
@@ -1081,24 +1129,52 @@ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
|
1081
1129
|
...currentProps
|
|
1082
1130
|
};
|
|
1083
1131
|
}, [schema, currentProps]);
|
|
1132
|
+
(0, react.useEffect)(() => {
|
|
1133
|
+
if (visible) {
|
|
1134
|
+
form.setFieldsValue(mergedProps);
|
|
1135
|
+
setFormProps({});
|
|
1136
|
+
}
|
|
1137
|
+
}, [
|
|
1138
|
+
visible,
|
|
1139
|
+
mergedProps,
|
|
1140
|
+
form
|
|
1141
|
+
]);
|
|
1084
1142
|
const handleDialogChange = (key, value) => {
|
|
1085
1143
|
setFormProps((prev) => ({
|
|
1086
1144
|
...prev,
|
|
1087
1145
|
[key]: value
|
|
1088
1146
|
}));
|
|
1089
1147
|
};
|
|
1090
|
-
const handleOk = () => {
|
|
1091
|
-
|
|
1148
|
+
const handleOk = async () => {
|
|
1149
|
+
let finalProps = visible ? {
|
|
1092
1150
|
...mergedProps,
|
|
1093
1151
|
...formProps
|
|
1094
|
-
} : mergedProps
|
|
1095
|
-
|
|
1152
|
+
} : mergedProps;
|
|
1153
|
+
try {
|
|
1154
|
+
const formValues = await form.validateFields();
|
|
1155
|
+
finalProps = {
|
|
1156
|
+
...finalProps,
|
|
1157
|
+
...formValues
|
|
1158
|
+
};
|
|
1159
|
+
} catch {}
|
|
1160
|
+
onSave(finalProps);
|
|
1096
1161
|
};
|
|
1097
1162
|
const handleClose = () => {
|
|
1098
1163
|
setFormProps({});
|
|
1099
1164
|
onClose();
|
|
1100
1165
|
};
|
|
1101
|
-
if (!schema
|
|
1166
|
+
if (!schema) return null;
|
|
1167
|
+
const context = {
|
|
1168
|
+
props: mergedProps,
|
|
1169
|
+
form,
|
|
1170
|
+
onChange: handleDialogChange,
|
|
1171
|
+
onSave
|
|
1172
|
+
};
|
|
1173
|
+
const renderDialogContent = () => {
|
|
1174
|
+
if (schema.renderDialog) return schema.renderDialog(context);
|
|
1175
|
+
if (schema.fields?.some((f) => f.componentType) && schema.fields) return renderAutoForm(form, schema.fields);
|
|
1176
|
+
return null;
|
|
1177
|
+
};
|
|
1102
1178
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Modal, {
|
|
1103
1179
|
title: `${schema.label} 属性编辑`,
|
|
1104
1180
|
open: visible,
|
|
@@ -1106,10 +1182,7 @@ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
|
1106
1182
|
onCancel: handleClose,
|
|
1107
1183
|
okText: "保存",
|
|
1108
1184
|
cancelText: "取消",
|
|
1109
|
-
children:
|
|
1110
|
-
...mergedProps,
|
|
1111
|
-
...formProps
|
|
1112
|
-
} : mergedProps, handleDialogChange)
|
|
1185
|
+
children: renderDialogContent()
|
|
1113
1186
|
});
|
|
1114
1187
|
}
|
|
1115
1188
|
//#endregion
|
|
@@ -1219,7 +1292,7 @@ function useScrollSync(sourceRef, targetRef) {
|
|
|
1219
1292
|
}
|
|
1220
1293
|
//#endregion
|
|
1221
1294
|
//#region src/MarkdownEditor.tsx
|
|
1222
|
-
var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ defaultValue = "", value, onChange, showPreview = true, fullscreen: fullscreenProp =
|
|
1295
|
+
var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ defaultValue = "", value, onChange, showPreview = true, fullscreen: fullscreenProp = true, previewOptions, uploadConfig }, ref) {
|
|
1223
1296
|
const [content, setContent] = (0, react.useState)(value ?? defaultValue);
|
|
1224
1297
|
const [editorView, setEditorView] = (0, react.useState)(null);
|
|
1225
1298
|
const scrollDOMRef = (0, react.useRef)(null);
|
|
@@ -1238,14 +1311,19 @@ var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ def
|
|
|
1238
1311
|
onChange?.(newContent);
|
|
1239
1312
|
}, [onChange]);
|
|
1240
1313
|
(0, react.useImperativeHandle)(ref, () => ({ getContent: () => content }), [content]);
|
|
1241
|
-
const extraToolbarItems = componentRegistry.getAll().
|
|
1314
|
+
const extraToolbarItems = componentRegistry.getAll().flatMap((schema) => {
|
|
1315
|
+
if (schema.wrapTags) return {
|
|
1316
|
+
id: `wrap-${schema.name}`,
|
|
1317
|
+
label: schema.label,
|
|
1318
|
+
icon: () => schema.icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.ReadOutlined, {}),
|
|
1319
|
+
wrapTags: schema.wrapTags
|
|
1320
|
+
};
|
|
1242
1321
|
const defaultProps = {};
|
|
1243
1322
|
for (const field of schema?.fields ?? []) defaultProps[field.key] = field.defaultValue ?? "";
|
|
1244
|
-
const iconNode = schema.icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.ReadOutlined, {});
|
|
1245
1323
|
return {
|
|
1246
1324
|
id: `container-${schema.name}`,
|
|
1247
1325
|
label: schema.label,
|
|
1248
|
-
icon: () =>
|
|
1326
|
+
icon: () => schema.icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.ReadOutlined, {}),
|
|
1249
1327
|
handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
|
|
1250
1328
|
};
|
|
1251
1329
|
});
|
|
@@ -1272,7 +1350,7 @@ var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ def
|
|
|
1272
1350
|
setDialogVisible(false);
|
|
1273
1351
|
}, []);
|
|
1274
1352
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1275
|
-
className: fullscreen ? "app fullscreen" : "app",
|
|
1353
|
+
className: fullscreen ? "markdown-editor-app fullscreen" : "markdown-editor-app",
|
|
1276
1354
|
children: [
|
|
1277
1355
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toolbar, {
|
|
1278
1356
|
editorView,
|
|
@@ -1327,15 +1405,36 @@ var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ def
|
|
|
1327
1405
|
* 2. 标签包裹模式:name + wrapTags
|
|
1328
1406
|
* 插入简单的标签包裹文本,如 <plugin-container>...</plugin-container>
|
|
1329
1407
|
*
|
|
1330
|
-
* @example 容器模式
|
|
1408
|
+
* @example 容器模式 - 使用 fields 自动生成表单(最简)
|
|
1409
|
+
* ```tsx
|
|
1410
|
+
* const schema: ComponentSchema = {
|
|
1411
|
+
* name: 'alert',
|
|
1412
|
+
* label: '警告框',
|
|
1413
|
+
* icon: <AlertOutlined />,
|
|
1414
|
+
* fields: [
|
|
1415
|
+
* { key: 'type', name: '类型', componentType: 'select',
|
|
1416
|
+
* componentProps: { options: [{ value: 'price', label: '价格' }, { value: 'info', label: '信息' }] } },
|
|
1417
|
+
* { key: 'content', name: '内容', componentType: 'textarea',
|
|
1418
|
+
* componentProps: { rows: 3 } },
|
|
1419
|
+
* { key: 'enabled', name: '启用', componentType: 'switch' },
|
|
1420
|
+
* ],
|
|
1421
|
+
* }
|
|
1422
|
+
* // 支持的 componentType: 'input' | 'textarea' | 'number' | 'select' | 'switch' | 'slider' | 'rate' | 'color' | 'date' | 'dateRange'
|
|
1423
|
+
* ```
|
|
1424
|
+
*
|
|
1425
|
+
* @example 容器模式 - 使用 Form.Item 自动绑定(推荐)
|
|
1331
1426
|
* ```tsx
|
|
1332
1427
|
* const schema: ComponentSchema = {
|
|
1333
1428
|
* name: 'alert',
|
|
1334
1429
|
* label: '警告框',
|
|
1335
1430
|
* icon: <AlertOutlined />,
|
|
1336
1431
|
* fields: [{ key: 'level', defaultValue: 'info' }],
|
|
1337
|
-
* renderDialog: (
|
|
1338
|
-
* <
|
|
1432
|
+
* renderDialog: ({ form }) => (
|
|
1433
|
+
* <Form form={form} layout="vertical">
|
|
1434
|
+
* <Form.Item name="level" label="级别">
|
|
1435
|
+
* <Input />
|
|
1436
|
+
* </Form.Item>
|
|
1437
|
+
* </Form>
|
|
1339
1438
|
* ),
|
|
1340
1439
|
* }
|
|
1341
1440
|
*
|
|
@@ -1345,6 +1444,35 @@ var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ def
|
|
|
1345
1444
|
* }
|
|
1346
1445
|
* ```
|
|
1347
1446
|
*
|
|
1447
|
+
* @example 容器模式 - 手动控制
|
|
1448
|
+
* ```tsx
|
|
1449
|
+
* const schema: ComponentSchema = {
|
|
1450
|
+
* name: 'alert',
|
|
1451
|
+
* label: '警告框',
|
|
1452
|
+
* icon: <AlertOutlined />,
|
|
1453
|
+
* fields: [{ key: 'level', defaultValue: 'info' }],
|
|
1454
|
+
* renderDialog: ({ props, onChange }) => (
|
|
1455
|
+
* <Input value={props.level} onChange={e => onChange('level', e.target.value)} />
|
|
1456
|
+
* ),
|
|
1457
|
+
* }
|
|
1458
|
+
* ```
|
|
1459
|
+
*
|
|
1460
|
+
* @example 容器模式 - 完全自定义弹窗
|
|
1461
|
+
* ```tsx
|
|
1462
|
+
* const schema: ComponentSchema = {
|
|
1463
|
+
* name: 'alert',
|
|
1464
|
+
* label: '警告框',
|
|
1465
|
+
* icon: <AlertOutlined />,
|
|
1466
|
+
* fields: [{ key: 'level', defaultValue: 'info' }],
|
|
1467
|
+
* renderDialog: ({ props, onSave }) => (
|
|
1468
|
+
* <MyCustomEditor
|
|
1469
|
+
* value={props.level}
|
|
1470
|
+
* onSave={({ level }) => onSave({ level })}
|
|
1471
|
+
* />
|
|
1472
|
+
* ),
|
|
1473
|
+
* }
|
|
1474
|
+
* ```
|
|
1475
|
+
*
|
|
1348
1476
|
* @example 标签包裹模式
|
|
1349
1477
|
* ```tsx
|
|
1350
1478
|
* function MyPage() {
|