add translate
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,3 +23,5 @@ docs-dist
|
||||
# ide
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
.env
|
24
package.json
24
package.json
@@ -5,23 +5,29 @@
|
||||
],
|
||||
"scripts": {
|
||||
"start": "lerna exec npm run start --scope=dooringx-lib",
|
||||
"start:example":"lerna exec npm run start --scope=dooringx-example",
|
||||
"start:doc":"lerna exec npm run start --scope=dooringx-dumi-doc",
|
||||
"start:example": "lerna exec npm run start --scope=dooringx-example",
|
||||
"start:doc": "lerna exec npm run start --scope=dooringx-dumi-doc",
|
||||
"build": "lerna exec npm run build --scope=dooringx-lib",
|
||||
"deploy": "lerna exec npm run docs:build --scope=dooringx-dumi-doc",
|
||||
"pub": "node ./script/publish.js",
|
||||
"changelog": "node ./script/changelog.js"
|
||||
"changelog": "node ./script/changelog.js",
|
||||
"translate": "node ./script/translate.js",
|
||||
"translateText":"node ./script/translateText.js"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"lerna": "^3.22.1",
|
||||
"husky": "4.3.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"@types/md5": "^2.3.1",
|
||||
"axios": "^0.24.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"husky": "4.3.0",
|
||||
"lerna": "^3.22.1",
|
||||
"lint-staged": "^11.0.0",
|
||||
"md5": "^2.3.0",
|
||||
"prettier": "^2.2.0",
|
||||
"typescript": "^4.1.2",
|
||||
"tslib": "^2.1.0"
|
||||
"rimraf": "^3.0.2",
|
||||
"tslib": "^2.1.0",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
@@ -1,192 +1,101 @@
|
||||
---
|
||||
title: ChangeLog
|
||||
title: change log
|
||||
toc: menu
|
||||
nav:
|
||||
title: ChangeLog
|
||||
title: change log
|
||||
order: 6
|
||||
---
|
||||
|
||||
|
||||
## 0.10.3
|
||||
Fix the left tab color difference problem.
|
||||
## 0.10.2
|
||||
|
||||
修改timeline选中底色,增加item类名方便修改。
|
||||
|
||||
Modify the timeline, select the background color, and add the item class name to facilitate modification.
|
||||
## 0.10.1
|
||||
|
||||
修改timeline类名,方便修改样式。
|
||||
|
||||
Modify the timeline class name to facilitate style modification.
|
||||
## 0.10.0
|
||||
|
||||
函数名提出name作为单独配置项传递,第一个参数作为id,最后个参数作为函数名,这样函数的显示名称可以支持转换。
|
||||
|
||||
弹窗事件如果不传递弹窗名字,则不会出现弹窗。
|
||||
|
||||
The function name proposes name as a separate configuration item, the first parameter as ID and the last parameter as function name, so that the display name of the function can support conversion.
|
||||
Pop up event if the pop-up name is not passed, no pop-up will appear.
|
||||
## 0.9.5
|
||||
|
||||
preview组件scripts加载由并发变为线性。
|
||||
|
||||
编辑模式下使用远程组件会储存当前画布状态。
|
||||
|
||||
Scripts loading of preview component changes from concurrency to linearity.
|
||||
Using remote components in edit mode will save the current canvas state.
|
||||
## 0.9.4
|
||||
|
||||
修复preview组件不能解除loading的bug
|
||||
|
||||
Fix the bug that the preview component cannot release loading
|
||||
## 0.9.3
|
||||
|
||||
增加双击置入画布。
|
||||
|
||||
变更元素初始focus状态。
|
||||
|
||||
存在元素宽高,则置入时定位于元素中心。
|
||||
|
||||
Add double click to place the canvas.
|
||||
Change the initial focus state of the element.
|
||||
If there is an element width and height, it is positioned at the center of the element when placed.
|
||||
## 0.9.2
|
||||
|
||||
增加远程组件调用全流程,component中增加url属性,便于script加载。
|
||||
|
||||
Add remote component to call the whole process, and Add URL attribute in component to facilitate script loading.
|
||||
## 0.9.1
|
||||
|
||||
增加config.i18n配置,不使用国际化则不需要导入intl的context。
|
||||
control组件第一个按钮变更为拖拽,功能合并至timeline
|
||||
|
||||
Add config.i18n configuration. If internationalization is not used, there is no need to import the context of Intl.
|
||||
The first button of the control component is changed to drag and drop, and the functions are merged into the timeline
|
||||
## 0.9.0
|
||||
|
||||
增加react-intl,修改部分样式。
|
||||
|
||||
Add react Intl and modify some styles.
|
||||
## 0.8.4
|
||||
|
||||
增加preview的属性,可外界控制loading。
|
||||
Add the preview attribute to control external loading.
|
||||
## 0.8.3
|
||||
|
||||
增加timeline拖动条与选中。
|
||||
修复antd menu属性报错。
|
||||
|
||||
Add timeline drag bar and select.
|
||||
Fix the error reported by the antd menu attribute.
|
||||
## 0.8.2
|
||||
|
||||
增加左侧面板配置。
|
||||
|
||||
修复timeline闪烁问题。
|
||||
|
||||
Add left panel configuration.
|
||||
Fix timeline flicker.
|
||||
## 0.8.1
|
||||
|
||||
新增动画组件timeline。可以更好预览所有动画。
|
||||
|
||||
Add animation component timeline. You can preview all animations better.
|
||||
## 0.8.0
|
||||
|
||||
动画部分重构,可支持多动画同时配置。
|
||||
|
||||
画布拖动最小值变更为0。
|
||||
|
||||
|
||||
The animation part is reconstructed, which can support the simultaneous configuration of multiple animations.
|
||||
The minimum value of canvas drag is changed to 0.
|
||||
## 0.7.7
|
||||
|
||||
优化画布拖拽逻辑,更平滑移动。
|
||||
|
||||
Optimize the canvas drag logic to move more smoothly.
|
||||
## 0.7.6
|
||||
|
||||
右侧自定义rightGlobalCustom类型变更为函数传入config
|
||||
|
||||
The custom rightglobalcustom type on the right is changed to the function passed in config
|
||||
## 0.7.5
|
||||
|
||||
修改滚轮方向。
|
||||
|
||||
|
||||
Change the roller direction.
|
||||
## 0.7.4
|
||||
|
||||
修复框选移动bug。
|
||||
|
||||
|
||||
Fix box move bug.
|
||||
## 0.7.3
|
||||
|
||||
修复弹窗位置与选中问题。
|
||||
|
||||
|
||||
Fix pop-up location and selection issues.
|
||||
## 0.7.2
|
||||
|
||||
修复锁定组件影响,锁定中无法拖拽,缩放,旋转。
|
||||
|
||||
Fix the impact of locking components. You can't drag, zoom or rotate during locking.
|
||||
## 0.7.1
|
||||
|
||||
修复锁定组件无法选中解锁。
|
||||
|
||||
|
||||
The repair lock component cannot be unlocked.
|
||||
## 0.7.0
|
||||
|
||||
已支持组件旋转!
|
||||
|
||||
修复拖拽参考线等优化逻辑。
|
||||
|
||||
Component rotation is supported!
|
||||
Fix optimization logic such as dragging guides.
|
||||
## 0.6.0
|
||||
|
||||
已支持编辑模式使用Iframe!
|
||||
|
||||
修复选中条件。
|
||||
|
||||
|
||||
The use of iframe in edit mode is supported!
|
||||
Repair the selected condition.
|
||||
## 0.5.1
|
||||
|
||||
修复右侧选中不能取消选中问题。
|
||||
|
||||
Fix the problem on the right that cannot be unchecked.
|
||||
## 0.5.0
|
||||
|
||||
修复control组件宽度不够问题。
|
||||
|
||||
移除antd自定义icon,容器底部icon可配置。
|
||||
|
||||
Fix the problem that the control component is not wide enough.
|
||||
Remove the antd custom icon, and the icon at the bottom of the container can be configured.
|
||||
## 0.4.2
|
||||
|
||||
修复animate错误初始值。
|
||||
|
||||
修改markline样式。
|
||||
|
||||
全局设置增加容器高度。
|
||||
Fix animation error initial value.
|
||||
Modify the markline style.
|
||||
Global settings increase container height.
|
||||
## 0.4.1
|
||||
|
||||
去除lib自动导入样式。
|
||||
|
||||
Remove the Lib auto import style.
|
||||
## 0.4.0
|
||||
|
||||
去除runtime导出,所有属性从config中获取。
|
||||
|
||||
Remove the runtime export and get all properties from config.
|
||||
## 0.3.1
|
||||
|
||||
1、由uuid更换为nanoid。
|
||||
|
||||
2、control组件增加标尺控制。
|
||||
|
||||
1. Replace UUID with nanoid.
|
||||
2. Control component adds ruler control.
|
||||
## 0.3.0
|
||||
|
||||
1、增加标尺,ContainerWrapper需要传递config才可使用。
|
||||
|
||||
2、修改容器最小拖动667。修复画布缩放下拖拽时与鼠标距离不一致。
|
||||
|
||||
3、innerContainerDragUp需要传递config。
|
||||
|
||||
1. Add a ruler. The containerwrapper needs to pass config before it can be used.
|
||||
2. Modify container minimum drag 667. Fix the inconsistency with the mouse distance when the canvas is zoomed down and dragged.
|
||||
3. Innercontainerdragup needs to pass config.
|
||||
## 0.2.0
|
||||
|
||||
commander的传递进行修改,可以获得config了,commander不再从index中导出 ,需要使用时从config中获取。增加左侧类名,方便自定义。
|
||||
|
||||
Modify the transfer of the commander to obtain the config. The commander is no longer exported from the index. When it needs to be used, it is obtained from the config. Add the class name on the left to facilitate customization.
|
||||
## 0.1.10
|
||||
|
||||
修改eslint依赖推荐
|
||||
|
||||
|
||||
Modify eslint dependency recommendation
|
||||
## 0.1.9
|
||||
|
||||
增加全局body设置
|
||||
|
||||
Add global body settings
|
||||
## 0.1.8
|
||||
|
||||
增加弹窗设置,移除modalContainer
|
||||
|
||||
Add pop-up settings and remove modalcontainer
|
||||
## 0.1.7
|
||||
|
||||
修改预览特殊条件显示,删除console
|
||||
Modify the preview special condition display and delete the console
|
||||
## 0.1.6
|
||||
|
||||
调整初始缩放,画布初始比例,增加回正画布功能。
|
||||
Adjust the initial zoom, the initial scale of the canvas, and add the function of righting the canvas.
|
||||
## 0.1.5
|
||||
|
||||
删除未作按钮,增加fixed配置
|
||||
Delete the button not made and add the fixed configuration
|
||||
## 0.1.4
|
||||
|
||||
基础功能
|
||||
Basic functions
|
@@ -1,6 +1,29 @@
|
||||
---
|
||||
title: FAQ
|
||||
toc: menu
|
||||
nav:
|
||||
title: FAQ
|
||||
order: 4
|
||||
---
|
||||
## The chart is constantly refreshed as it moves
|
||||
Please use fast deep equal to compare the required data. If they are the same, ignore the update.
|
||||
## Unable to select components or preview adaptation issues
|
||||
The absolute positioning component must have an initial width and height. Although the width and height will be added to the component during dragging, if the user does not drag at the beginning, the component has no width and height, which will not only affect the selection judgment, but also affect the calculation of the final preview.
|
||||
## The functions dynamically registered by the component are always retained
|
||||
The component function needs to call the unloading method when the component is unloaded, otherwise it will always exist.
|
||||
## Form verification submission ideas
|
||||
There are many ways to verify and submit forms, because all the data is connected, or you can write a form component directly.
|
||||
When the form component is not used, the simple way is to make a verification function and submission function for each input component.
|
||||
In this way, whether to verify depends on the user's selection, and the thrown input allows the user to choose where to put it, and the user names the variable.
|
||||
When clicking the submit button, call the verification function and submission function of all components to throw them to the context, then aggregate them into objects through the context aggregation function, and finally send them to the corresponding back end through the sending function, so as to complete the whole process. You can try this demo in example.
|
||||
If the operator can understand the interface documents provided by the back-end, the operator can spell out the fields desired by the back-end through naming.
|
||||
If you don't need documentation, some values can also be written dead at development time.
|
||||
Another way is to write a submit button specifically, fix the parameters and some rules, such as stipulating that all forms in the page will be collected and submitted.
|
||||
Then we can use the data source to automatically submit all form output contents to the data source. The last submit button extracts the key in the format specified by the data source and sends it to the back end.
|
||||
## Top to bottom problem
|
||||
A small partner responded that the operation of placing the top and bottom is a little unreasonable. In fact, zindex should not be used to make the top and bottom, which is inconsistent with the order of layers, and there will be problems when using zindex to set the bottom, because the element zindex cannot be lower than the canvas, so it is common sense to raise the order.
|
||||
## Multilingual removal
|
||||
If I18N in config is set to false, intlprovider does not need to be applied in the outer layer.
|
||||
```js
|
||||
config.i18n = false;
|
||||
```
|
33
packages/dooringx-dumi-doc/docs/Guide/basic.en.md
Normal file
33
packages/dooringx-dumi-doc/docs/Guide/basic.en.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: dooringx lib Foundation
|
||||
toc: menu
|
||||
order: 2
|
||||
---
|
||||
### store
|
||||
Store is similar to the concept of redux. It internally implements the functions of redo, undo, publish and subscribe, replace data, forced refresh and so on.
|
||||
Store can be obtained in config.
|
||||
At the beginning, usestorestate and react need to be combined. At this time, store.forceupdate can be used at any position, or state can be used to obtain the data in the store.
|
||||
The most important function of the store is to save the JSON queue for each modification.
|
||||
If you need to update the data, use the SetData method to update after the deep copy.
|
||||
If you don't leave a record on redo or undo when you need to update, please operate the queue to delete the saved content.
|
||||
If you want to see the view update after changing the data, you can use forceupdate.
|
||||
### Events
|
||||
The events of dooringx lib are on the eventcenter, which integrates the function center and an event chain.
|
||||
You can get the time of component registration in the event center. Timing is similar to the component life cycle. It can be called at the corresponding timing after registration.
|
||||
The functions in the function center will be combined with the timing, and then the event chain will uniformly process the queue set by the user.
|
||||
Each event chain will have a context object during execution, which will run through the event chain.
|
||||
### Command
|
||||
The commands of dooringx lib are managed by the commander.
|
||||
Redo and undo commands are provided internally by default. You can add a commander through a plug-in.
|
||||
The commander integrates shortcut key configuration internally, and uses the keys of keyboard events for registration. If Ctrl, ALT and meta keys are used, the corresponding plus sign can be added for key combination registration, and the case is ignored internally (note! The case of the registered key name is not ignored, but the keys of a and a are equivalent in processing).
|
||||
### Pop up window
|
||||
Dooringx lib has a built-in pop-up system, which is converted through storechanger.
|
||||
Therefore, when making some scenes, you may need to consider whether to edit them in pop-up window.
|
||||
Each pop-up window only saves the data in the block, while data such as events will only exist in the master data.
|
||||
After the pop-up window is saved, the pop-up window data will be replaced in the main data memory and replaced again when editing.
|
||||
You can use the methods on storechanger to judge, or directly obtain the data source data. See API for details.
|
||||
### Data source
|
||||
The data source of dooringx lib is not the same thing as the store mentioned above.
|
||||
It is located in the datacenter. The original intention of designing data source is to make people who do not understand the code better understand.
|
||||
The operation of events can be completely separated from the data source, as long as the user knows how to fill in parameters.
|
||||
Therefore, during event configuration, multiple options can be used to obtain data from the data source and convert it into parameters.
|
69
packages/dooringx-dumi-doc/docs/Guide/basic.md
Normal file
69
packages/dooringx-dumi-doc/docs/Guide/basic.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: dooringx-lib基础
|
||||
toc: menu
|
||||
order: 2
|
||||
---
|
||||
|
||||
|
||||
### store
|
||||
|
||||
store 类似于 redux 的概念,它内部实现了 redo、undo、发布订阅、置换数据、强制刷新等功能。
|
||||
|
||||
store 可以在 config 中获取。
|
||||
|
||||
在最开始时,需要通过 useStoreState 与 react 结合,此时可以在任意位置使用 store.forceUpdate 强刷,也可以使用 state 获取 store 中的数据。
|
||||
|
||||
store 的最重要功能是保存着每次修改 json 队列。
|
||||
|
||||
如果你需要更新数据,在深拷贝后使用 setData 方法进行更新。
|
||||
|
||||
如果你需要更新时不记录在 redo 或 undo 上留下记录,那么请操作队列删除其中保存内容即可。
|
||||
|
||||
对于改变数据后想即使看见视图更新,那么使用forceUpdate即可。
|
||||
|
||||
|
||||
### 事件
|
||||
|
||||
dooringx-lib 的事件是在eventCenter上,它上面会集成functionCenter与一个事件链。
|
||||
|
||||
在eventCenter中可以获取组件注册的时机。时机类似于组件生命周期一样,可以注册后在对应的时机进行调用。
|
||||
|
||||
而functionCenter中的函数则会与时机结合,再由事件链对用户设定的队列进行统一处理。
|
||||
|
||||
每个事件链在执行中会有上下文对象,这个对象会贯穿这个事件链。
|
||||
|
||||
### 命令
|
||||
|
||||
|
||||
dooringx-lib 的命令是由commander进行管理。
|
||||
|
||||
内部默认提供redo与undo的命令,您可以通过插件方式增加commander。
|
||||
|
||||
commander内部集成了快捷键配置,使用键盘事件的key进行注册,如果ctrl、alt、meta键,可加对应的加号进行组合键注册,内部忽略大小写(注意!不是忽略注册的键名大小写,而是A和a的key处理时等价)。
|
||||
|
||||
### 弹窗
|
||||
|
||||
|
||||
dooringx-lib内置弹窗系统,弹窗系统是通过storeChanger进行转换而成。
|
||||
|
||||
所以在某些情景制作时,可能需要考虑是否在弹窗编辑下的情况。
|
||||
|
||||
每个弹窗是只保存block中的数据,而事件等数据只会存在主数据内。
|
||||
|
||||
在弹窗保存后,弹窗数据会被置换于主数据内存着,需要编辑时重新置换出来。
|
||||
|
||||
可以使用storeChanger上的方法进行判断,或者直接获取数据源数据等,具体见API。
|
||||
|
||||
|
||||
### 数据源
|
||||
|
||||
|
||||
dooringx-lib 的数据源和前面说的store中存储的不是一个东西。
|
||||
|
||||
它位于dataCenter中。设计数据源的初衷是为了让不懂代码的人更好理解。
|
||||
|
||||
事件的运行完全可以脱离数据源运行,只要使用者知道如何去填写参数。
|
||||
|
||||
所以在事件配置时,可以多个选项在数据源中去获得数据转变为参数。
|
||||
|
||||
|
593
packages/dooringx-dumi-doc/docs/Guide/develop.en.md
Normal file
593
packages/dooringx-dumi-doc/docs/Guide/develop.en.md
Normal file
@@ -0,0 +1,593 @@
|
||||
---
|
||||
title: dooringx lib plug-in development
|
||||
toc: menu
|
||||
order: 3
|
||||
---
|
||||
### Plug in import
|
||||
The dooringx lib plug-in requires an object of type 'partial < initconfig >'.
|
||||
For multiple plug-ins, you need to use the 'userconfigmerge' exported by dooringx lib to merge.
|
||||
Not all properties of userconfigmerge will be merged, and some properties will be overwritten.
|
||||
```
|
||||
* 部分无法合并属性如果b传了会以b为准
|
||||
* initstore不合并
|
||||
* leftallregistmap合并
|
||||
* leftRenderListCategory合并
|
||||
* rightRenderListCategory合并
|
||||
* rightGlobalCustom 不合并
|
||||
* initComponentCache合并
|
||||
* initFunctionMap合并
|
||||
* initDataCenterMap合并
|
||||
* initCommandModule合并
|
||||
* initFormComponents合并
|
||||
```
|
||||
Config supports partial configuration of asynchronous import, such as classification on the left. This is an experimental function, so it is not recommended.
|
||||
### Left panel
|
||||
On the left panel, enter leftrenderlistcategory.
|
||||
```js
|
||||
leftRenderListCategory: [
|
||||
{
|
||||
type: 'basic',
|
||||
icon: <HighlightOutlined />,
|
||||
displayName: '基础组件',
|
||||
},
|
||||
{
|
||||
type: 'xxc',
|
||||
icon: <ContainerOutlined />,
|
||||
custom: true,
|
||||
customRender: <div>我是自定义渲染</div>,
|
||||
},
|
||||
],
|
||||
```
|
||||
Type is the category. The category displayed by the component on the left is determined by this field.
|
||||
Icon is a small icon on the left.
|
||||
When custom is true, you can use customrender to customize the rendering.
|
||||
### Left side assembly
|
||||
### Plug in import
|
||||
The left component is placed in the leftregistmap of the object.
|
||||
The left component supports synchronous import or asynchronous import.
|
||||
```js
|
||||
const LeftRegistMap: LeftRegistComponentMapItem[] = [
|
||||
{
|
||||
type: 'basic',
|
||||
component: 'button',
|
||||
img: 'icon-anniu',
|
||||
displayName: '按钮',
|
||||
urlFn: () => import('./registComponents/button'),
|
||||
},
|
||||
];
|
||||
```
|
||||
If you need to import components asynchronously, you need to fill in urlfn and a function that returns promise. Remote loading of components can also be supported, as long as webpack is equipped with it.
|
||||
If you need to import components synchronously, you need to put the components into the initcomponentcache of the configuration item, so that they will be registered in the componentregister when loaded.
|
||||
```js
|
||||
initComponentCache: {
|
||||
modalMask: { component: MmodalMask },
|
||||
},
|
||||
```
|
||||
### Component writing
|
||||
Component needs to export an object generated by componentitemfactory.
|
||||
```js
|
||||
const MButton = new ComponentItemFactory(
|
||||
'button',
|
||||
'按钮',
|
||||
{
|
||||
style: [
|
||||
createPannelOptions<FormMap, 'input'>('input', {
|
||||
receive: 'text',
|
||||
label: '文字',
|
||||
}),
|
||||
],
|
||||
animate: [createPannelOptions<FormMap, 'animateControl'>('animateControl', {})],
|
||||
actions: [createPannelOptions<FormMap, 'actionButton'>('actionButton', {})],
|
||||
},
|
||||
{
|
||||
props: {
|
||||
...
|
||||
text:'yehuozhili'// input配置项组件接收的初始值
|
||||
},
|
||||
},
|
||||
(data, context, store, config) => {
|
||||
return <ButtonTemp data={data} store={store} context={context} config={config}></ButtonTemp>;
|
||||
},
|
||||
true
|
||||
);
|
||||
export default MButton;
|
||||
```
|
||||
The first parameter is the component registration name, and the second parameter is used to display the usage.
|
||||
The third parameter is used to configure the configuration item component of the right panel. The key is the classification of the right panel, and the value is the configuration item component array.
|
||||
The fourth parameter will configure the initial value of the component. In particular, the manufacturing component must have the initial width and height (not supported by the content), otherwise there will be a problem when selecting all during adaptation.
|
||||
There are many useful properties in this initial value. For example, fixed represents fixed positioning. You can change this value in combination with configuration items so that components can be fixed positioning.
|
||||
Also, candrag is similar to the lock command. Locked elements cannot be dragged.
|
||||
The rotate in the initial value needs an object. Value represents the rotation angle and canrotate represents whether the rotation can be operated. (supported from version 0.7.0)
|
||||
The fifth parameter is a function. You will get the configuration from the receive attribute in the configuration item (the default configuration is receive for the time being). For example, if the received in the above example is text, the field will be received in the data in the function.
|
||||
Context generally has only preview and edit for environment judgment.
|
||||
Config can get all the data for use when making events.
|
||||
The sixth parameter resize is used to determine whether scaling can be performed. When it is false, scaling cannot be performed.
|
||||
The seventh parameter is needposition. After some components are moved into the canvas, the drop point will be adopted by default. This configuration item is true by default, that is, the position to be dragged. When it is false, the component's top and left positioning will be used to place it.
|
||||
### Event registration
|
||||
#### Timing registration
|
||||
As mentioned earlier, events have timing and functions, so hook can be used to register timing in the component:
|
||||
```js
|
||||
useDynamicAddEventCenter(pr, `${pr.data.id}-init`, '初始渲染时机'); //注册名必须带id 约定!
|
||||
useDynamicAddEventCenter(pr, `${pr.data.id}-click`, '点击执行时机');
|
||||
```
|
||||
The first parameter of usedynamicaddevencenter is an object composed of four parameters of render. The second parameter is the registered time name, which must be related to the ID, which is a convention. Otherwise, multiple components may cause name conflicts, and it is convenient to find the time.
|
||||
After registering the timing, you need to put the timing into the corresponding trigger position. For example, the click execution timing of this button is put into onclick:
|
||||
```js
|
||||
<Button
|
||||
onClick={() => {
|
||||
eventCenter.runEventQueue(`${pr.data.id}-click`, pr.config);
|
||||
}}
|
||||
>
|
||||
yehuozhili
|
||||
</Button>
|
||||
```
|
||||
The first parameter is the registered time name, and the second parameter is the last parameter config in the render function
|
||||
#### Function registration
|
||||
Functions are thrown by components and can be loaded into the event chain. For example, if I register a function to change the text, I can call the function at the time of any component, so as to trigger the component to change the text.
|
||||
The function registration needs to be put into useeffect, and the function needs to be unloaded when the component is unloaded! Otherwise, there will be more and more functions.
|
||||
Note that the component ID should be taken with the component ID, because a component can drag n components to generate n functions.
|
||||
```js
|
||||
useEffect(() => {
|
||||
const functionCenter = eventCenter.getFunctionCenter();
|
||||
const unregist = functionCenter.register(
|
||||
`${pr.data.id}+改变文本函数`,
|
||||
async (ctx, next, config, args, _eventList, iname) => {
|
||||
const userSelect = iname.data;
|
||||
const ctxVal = changeUserValue(
|
||||
userSelect['改变文本数据源'],
|
||||
args,
|
||||
'_changeval',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
const text = ctxVal[0];
|
||||
setText(text);
|
||||
next();
|
||||
},
|
||||
[
|
||||
{
|
||||
name: '改变文本数据源',
|
||||
data: ['ctx', 'input', 'dataSource'],
|
||||
options: {
|
||||
receive: '_changeval',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
`${pr.data.id}+改变文本函数`
|
||||
);
|
||||
return () => {
|
||||
unregist();
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
See later function development for parameters and configuration in the function.
|
||||
### Right side panel
|
||||
The configuration of the right panel is the same as that of the left panel:
|
||||
```js
|
||||
export interface RightMapRenderListPropsItemCategory {
|
||||
type: string;
|
||||
icon: ReactNode;
|
||||
custom?: boolean;
|
||||
customRender?: (type: string, current: IBlockType) => ReactNode;
|
||||
}
|
||||
```
|
||||
Type will affect the key name of the third parameter of the left component during development. The key name represents the type displayed on the right.
|
||||
Icon can put text or icons for panel switching.
|
||||
If custom is true, the display under this panel can be customized through customrender.
|
||||
### Right side assembly
|
||||
#### Right side component import
|
||||
When importing, just match the developed components into an object and put it into initformcomponents.
|
||||
```js
|
||||
initFormComponents: Formmodules,
|
||||
```
|
||||
#### Right side component development
|
||||
First, for a good development experience, you need to define a formmap type:
|
||||
```js
|
||||
export interface FormBaseType {
|
||||
receive?: string;
|
||||
}
|
||||
export interface FormInputType extends FormBaseType {
|
||||
label: string;
|
||||
}
|
||||
export interface FormActionButtonType {}
|
||||
export interface FormAnimateControlType {}
|
||||
export interface FormMap {
|
||||
input: FormInputType;
|
||||
actionButton: FormActionButtonType;
|
||||
animateControl: FormAnimateControlType;
|
||||
}
|
||||
```
|
||||
The key name of formmap is the key name of initformcomponents. The value of formmap corresponds to the value that the component needs to receive.
|
||||
Take the input component as an example. At this time, forminputtype has two attributes, label and receive.
|
||||
When developing this component, props will receive:
|
||||
```js
|
||||
interface MInputProps {
|
||||
data: CreateOptionsRes<FormMap, 'input'>;
|
||||
current: IBlockType;
|
||||
config: UserConfig;
|
||||
}
|
||||
```
|
||||
That is, where data is the formmap type and current is the currently clicked component, needless to say config.
|
||||
Remember the third parameter in component development on the left? So it's all related:
|
||||
```js
|
||||
style: [
|
||||
createPannelOptions<FormMap, 'input'>('input', {
|
||||
receive: 'text',
|
||||
label: '文字',
|
||||
}),
|
||||
],
|
||||
```
|
||||
The generics of the createpanneloptions function are filled with the corresponding components, which will give a good prompt to the received configuration items.
|
||||
All you need to do in the configuration item component is to receive the configuration item from the component, and then modify the property of current:
|
||||
```js
|
||||
function MInput(props: MInputProps) {
|
||||
const option = useMemo(() => {
|
||||
return props.data?.option || {};
|
||||
}, [props.data]);
|
||||
return (
|
||||
<Row style={{ padding: '10px 20px' }}>
|
||||
<Col span={6} style={{ lineHeight: '30px' }}>
|
||||
{(option as any)?.label || '文字'}:
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Input
|
||||
value={props.current.props[(option as any).receive] || ''}
|
||||
onChange={(e) => {
|
||||
const receive = (option as any).receive;
|
||||
const clonedata = deepCopy(store.getData());
|
||||
const newblock = clonedata.block.map((v: IBlockType) => {
|
||||
if (v.id === props.current.id) {
|
||||
v.props[receive] = e.target.value;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
store.setData({ ...clonedata, block: [...newblock] });
|
||||
}}
|
||||
></Input>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
```
|
||||
Because you can easily get the store, you can modify the data anywhere.
|
||||
Associate the value of the component with the property of current, and onchange to modify the store, thus completing a two-way binding.
|
||||
Note: if your right component needs attributes other than block, you may need to judge whether it is in pop-up mode.
|
||||
####Import of commands
|
||||
The command object can be imported into the initcommandmodule of the plug-in
|
||||
```js
|
||||
initCommandModule: commandModules,
|
||||
```
|
||||
#### Command development
|
||||
The command needs to export an object generated by commanderitemfactory.
|
||||
```js
|
||||
import { CommanderItemFactory } from 'dooringx-lib';
|
||||
const undo = new CommanderItemFactory(
|
||||
'redo',
|
||||
'Control+Shift+z',
|
||||
(store) => {
|
||||
store.redo();
|
||||
},
|
||||
'redo '
|
||||
);
|
||||
export default undo;
|
||||
```
|
||||
The first parameter is the registration name.
|
||||
The second parameter is the shortcut key name, and the shortcut key mapping is the key value of the keyboard event:
|
||||
```js
|
||||
Cancel: 3,
|
||||
Help: 6,
|
||||
Backspace: 8,
|
||||
Tab: 9,
|
||||
Clear: 12,
|
||||
Enter: 13,
|
||||
Shift: 16,
|
||||
Control: 17,
|
||||
Alt: 18,
|
||||
Pause: 19,
|
||||
CapsLock: 20,
|
||||
Escape: 27,
|
||||
Convert: 28,
|
||||
NonConvert: 29,
|
||||
Accept: 30,
|
||||
ModeChange: 31,
|
||||
' ': 32,
|
||||
PageUp: 33,
|
||||
PageDown: 34,
|
||||
End: 35,
|
||||
Home: 36,
|
||||
ArrowLeft: 37,
|
||||
ArrowUp: 38,
|
||||
ArrowRight: 39,
|
||||
ArrowDown: 40,
|
||||
Select: 41,
|
||||
Print: 42,
|
||||
Execute: 43,
|
||||
PrintScreen: 44,
|
||||
Insert: 45,
|
||||
Delete: 46,
|
||||
0: 48,
|
||||
')': 48,
|
||||
1: 49,
|
||||
'!': 49,
|
||||
2: 50,
|
||||
'@': 50,
|
||||
3: 51,
|
||||
'#': 51,
|
||||
4: 52,
|
||||
$: 52,
|
||||
5: 53,
|
||||
'%': 53,
|
||||
6: 54,
|
||||
'^': 54,
|
||||
7: 55,
|
||||
'&': 55,
|
||||
8: 56,
|
||||
'*': 56,
|
||||
9: 57,
|
||||
'(': 57,
|
||||
a: 65,
|
||||
A: 65,
|
||||
b: 66,
|
||||
B: 66,
|
||||
c: 67,
|
||||
C: 67,
|
||||
d: 68,
|
||||
D: 68,
|
||||
e: 69,
|
||||
E: 69,
|
||||
f: 70,
|
||||
F: 70,
|
||||
g: 71,
|
||||
G: 71,
|
||||
h: 72,
|
||||
H: 72,
|
||||
i: 73,
|
||||
I: 73,
|
||||
j: 74,
|
||||
J: 74,
|
||||
k: 75,
|
||||
K: 75,
|
||||
l: 76,
|
||||
L: 76,
|
||||
m: 77,
|
||||
M: 77,
|
||||
n: 78,
|
||||
N: 78,
|
||||
o: 79,
|
||||
O: 79,
|
||||
p: 80,
|
||||
P: 80,
|
||||
q: 81,
|
||||
Q: 81,
|
||||
r: 82,
|
||||
R: 82,
|
||||
s: 83,
|
||||
S: 83,
|
||||
t: 84,
|
||||
T: 84,
|
||||
u: 85,
|
||||
U: 85,
|
||||
v: 86,
|
||||
V: 86,
|
||||
w: 87,
|
||||
W: 87,
|
||||
x: 88,
|
||||
X: 88,
|
||||
y: 89,
|
||||
Y: 89,
|
||||
z: 90,
|
||||
Z: 90,
|
||||
OS: 91,
|
||||
ContextMenu: 93,
|
||||
F1: 112,
|
||||
F2: 113,
|
||||
F3: 114,
|
||||
F4: 115,
|
||||
F5: 116,
|
||||
F6: 117,
|
||||
F7: 118,
|
||||
F8: 119,
|
||||
F9: 120,
|
||||
F10: 121,
|
||||
F11: 122,
|
||||
F12: 123,
|
||||
F13: 124,
|
||||
F14: 125,
|
||||
F15: 126,
|
||||
F16: 127,
|
||||
F17: 128,
|
||||
F18: 129,
|
||||
F19: 130,
|
||||
F20: 131,
|
||||
F21: 132,
|
||||
F22: 133,
|
||||
F23: 134,
|
||||
F24: 135,
|
||||
NumLock: 144,
|
||||
ScrollLock: 145,
|
||||
VolumeMute: 181,
|
||||
VolumeDown: 182,
|
||||
VolumeUp: 183,
|
||||
';': 186,
|
||||
':': 186,
|
||||
'=': 187,
|
||||
'+': 187,
|
||||
',': 188,
|
||||
'<': 188,
|
||||
'-': 189,
|
||||
_: 189,
|
||||
'.': 190,
|
||||
'>': 190,
|
||||
'/': 191,
|
||||
'?': 191,
|
||||
'`': 192,
|
||||
'~': 192,
|
||||
'[': 219,
|
||||
'{': 219,
|
||||
'\\': 220,
|
||||
'|': 220,
|
||||
']': 221,
|
||||
'}': 221,
|
||||
"'": 222,
|
||||
'"': 222,
|
||||
Meta: 224,
|
||||
AltGraph: 225,
|
||||
Attn: 246,
|
||||
CrSel: 247,
|
||||
ExSel: 248,
|
||||
EraseEof: 249,
|
||||
Play: 250,
|
||||
ZoomOut: 251,
|
||||
```
|
||||
26 English letters ignore case. At present, only one shortcut key can be registered for a command. If you don't need to register a shortcut key, fill in the blank string.
|
||||
Metakey is the same as controlkey. Just write control.
|
||||
At present, the third parameter can only obtain store, which needs to be modified later. In version 0.2.0, the second parameter can obtain config. At the same time, the commander does not export from index, but obtains it from config when it needs to be used.
|
||||
The last parameter is the display name.
|
||||
### Right click menu
|
||||
The right-click menu can be customized:
|
||||
```js
|
||||
//Custom right click
|
||||
const contextMenuState = config.getContextMenuState();
|
||||
const unmountContextMenu = contextMenuState.unmountContextMenu;
|
||||
const commander = config.getCommanderRegister();
|
||||
const ContextMenu = () => {
|
||||
const handleclick = () => {
|
||||
unmountContextMenu();
|
||||
};
|
||||
const forceUpdate = useState(0)[1];
|
||||
contextMenuState.forceUpdate = () => {
|
||||
forceUpdate((pre) => pre + 1);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
left: contextMenuState.left,
|
||||
top: contextMenuState.top,
|
||||
position: 'fixed',
|
||||
background: 'rgb(24, 23, 23)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ width: '100%' }}
|
||||
onClick={() => {
|
||||
commander.exec('redo');
|
||||
handleclick();
|
||||
}}
|
||||
>
|
||||
< button > Customize < / button >
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
contextMenuState.contextMenu = <ContextMenu></ContextMenu>;
|
||||
```
|
||||
Get the contextmenustate first. There is an unmountcontextmenu on the contextmenustate, which is the method to close the right-click menu.
|
||||
Therefore, you need to call close after clicking.
|
||||
At the same time, the left and top above are the right-click positions.
|
||||
In addition, you also need to add a strong brush assignment to forceupdate in the component to follow when the component moves.
|
||||
### Function development
|
||||
#### Function import
|
||||
The function import is made into an object and placed in initfunctionmap
|
||||
```js
|
||||
initFunctionMap: functionMap,
|
||||
```
|
||||
#### Function development
|
||||
The key name will be displayed, so the key name is unique.
|
||||
Its values are two objects, one is the function content FN, and the other is the configuration item config (the function name needs to be passed in for display above 0.10.0).
|
||||
Each configuration in the array in config will be displayed for the user to configure. Name is the display name. Data represents where to get the data. You can choose to get it from input, data source and context (CTX). In addition, there is a special pop-up window (modal).
|
||||
The receive in options indicates which key args will get the value from.
|
||||
Multi indicates whether multiple option configurations are allowed.
|
||||
Two functions changeuservalue and changeuservaluerecord are written in dooringx lib. The first function will make the obtained results into an array. If it is not multi, the first result will be taken. The second function will make the results into objects. For example, if the user selects Keya in the data source, the key value pair of the data source will be returned as an object.
|
||||
FN, the first CTX parameter represents the context. If there is a conversion function, it may need to be used (for example, the result of the first function should be exported to the following functions)
|
||||
The second parameter next needs to be executed after running, otherwise the event chain will not exit in the function all the time.
|
||||
The third parameter config can get the entire config object.
|
||||
The fourth parameter args is filled in by the user and will be returned according to the fields filled in options.
|
||||
The fifth is eventlist, which can obtain the parameters of the whole event chain.
|
||||
The sixth parameter Iname can get the user's choice.
|
||||
```js
|
||||
General get request function:{
|
||||
fn: (ctx, next, config, args, _eventList, iname) => {
|
||||
Console.log (args, 'parameter x');
|
||||
const userSelect = iname.data;
|
||||
const urlVal = changeUserValue(
|
||||
Userselect ['request URL'],
|
||||
args,
|
||||
'_url',
|
||||
config,
|
||||
ctx
|
||||
); / / input datasource CTX / / datasource will get the value, and CTX will get the field on CTX
|
||||
const paramSource = changeUserValueRecord(
|
||||
//Settings can only be retrieved from datasource or CTX
|
||||
Userselect ['request parameter'],
|
||||
args,
|
||||
'_origin',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
const ctxVal = changeUserValue(
|
||||
Userselect ['return context field'],
|
||||
args,
|
||||
'_ctx',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
//Check whether the parameter exists
|
||||
//All are arrays, and non multi takes the first one.
|
||||
const url = urlVal[0];
|
||||
if (!url) {
|
||||
return next();
|
||||
}
|
||||
const ctxKey = ctxVal[0];
|
||||
axios
|
||||
.get(url, {
|
||||
params: {
|
||||
...paramSource,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
const data = res.data;
|
||||
ctx[ctxKey] = data;
|
||||
next();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
next();
|
||||
});
|
||||
},
|
||||
config: [
|
||||
{
|
||||
Name: 'request URL',
|
||||
data: ['dataSource', 'ctx', 'input'],
|
||||
options: {
|
||||
receive: '_url',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: 'request parameters',
|
||||
data: ['dataSource', 'ctx'],
|
||||
options: {
|
||||
receive: '_origin',
|
||||
multi: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: 'return context field',
|
||||
data: ['input'],
|
||||
options: {
|
||||
receive: '_ctx',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
Name: 'general get request function'
|
||||
},
|
||||
```
|
||||
#### Timing and function loading
|
||||
If necessary, generally use:
|
||||
```js
|
||||
eventCenter.manualUpdateMap(cur, displayName, arr);
|
||||
```
|
||||
In the manual updatemap, the first is the time name, the second is the display name, and the third is the user's choice.
|
||||
After updating the event center, you also need to update the store, and the result is subject to the store.
|
733
packages/dooringx-dumi-doc/docs/Guide/develop.md
Normal file
733
packages/dooringx-dumi-doc/docs/Guide/develop.md
Normal file
@@ -0,0 +1,733 @@
|
||||
---
|
||||
title: dooringx-lib插件开发
|
||||
toc: menu
|
||||
order: 3
|
||||
---
|
||||
|
||||
### 插件导入
|
||||
|
||||
dooringx-lib的插件需要一个类型为`Partial<InitConfig>`的对象。
|
||||
|
||||
对于多个插件,需要使用dooringx-lib导出的`userConfigMerge`来进行合并。
|
||||
|
||||
userConfigMerge不是所有属性都会合并,部分属性会进行覆盖。
|
||||
|
||||
```
|
||||
* 部分无法合并属性如果b传了会以b为准
|
||||
* initstore不合并
|
||||
* leftallregistmap合并
|
||||
* leftRenderListCategory合并
|
||||
* rightRenderListCategory合并
|
||||
* rightGlobalCustom 不合并
|
||||
* initComponentCache合并
|
||||
* initFunctionMap合并
|
||||
* initDataCenterMap合并
|
||||
* initCommandModule合并
|
||||
* initFormComponents合并
|
||||
```
|
||||
|
||||
config支持部分配置异步导入,比如左侧分类等,这个是实验性功能,所以不推荐这么做。
|
||||
|
||||
|
||||
### 左侧面板
|
||||
|
||||
|
||||
左侧面板传入leftRenderListCategory即可。
|
||||
|
||||
```js
|
||||
leftRenderListCategory: [
|
||||
{
|
||||
type: 'basic',
|
||||
icon: <HighlightOutlined />,
|
||||
displayName: '基础组件',
|
||||
},
|
||||
{
|
||||
type: 'xxc',
|
||||
icon: <ContainerOutlined />,
|
||||
custom: true,
|
||||
customRender: <div>我是自定义渲染</div>,
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
type是分类,左侧组件显示在哪个分类由该字段决定。
|
||||
|
||||
icon则是左侧分类小图标。
|
||||
|
||||
当custom为true时,可以使用customRender自定义渲染。
|
||||
|
||||
### 左侧组件
|
||||
|
||||
|
||||
### 插件导入
|
||||
|
||||
左侧组件要至于对象的LeftRegistMap中。
|
||||
|
||||
左侧组件支持同步导入或者异步导入。
|
||||
|
||||
```js
|
||||
const LeftRegistMap: LeftRegistComponentMapItem[] = [
|
||||
{
|
||||
type: 'basic',
|
||||
component: 'button',
|
||||
img: 'icon-anniu',
|
||||
displayName: '按钮',
|
||||
urlFn: () => import('./registComponents/button'),
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
如果需要异步导入组件,则需要填写urlFn,需要一个返回promise的函数。也可以支持远程载入组件,只要webpack配上就行了。
|
||||
|
||||
如果需要同步导入组件,则需要将组件放入配置项的initComponentCache中,这样在载入时便会注册进componentRegister里。
|
||||
|
||||
```js
|
||||
initComponentCache: {
|
||||
modalMask: { component: MmodalMask },
|
||||
},
|
||||
```
|
||||
|
||||
### 组件编写
|
||||
|
||||
组件需要导出一个由ComponentItemFactory生成的对象。
|
||||
|
||||
```js
|
||||
const MButton = new ComponentItemFactory(
|
||||
'button',
|
||||
'按钮',
|
||||
{
|
||||
style: [
|
||||
createPannelOptions<FormMap, 'input'>('input', {
|
||||
receive: 'text',
|
||||
label: '文字',
|
||||
}),
|
||||
],
|
||||
animate: [createPannelOptions<FormMap, 'animateControl'>('animateControl', {})],
|
||||
actions: [createPannelOptions<FormMap, 'actionButton'>('actionButton', {})],
|
||||
},
|
||||
{
|
||||
props: {
|
||||
...
|
||||
text:'yehuozhili'// input配置项组件接收的初始值
|
||||
},
|
||||
},
|
||||
(data, context, store, config) => {
|
||||
return <ButtonTemp data={data} store={store} context={context} config={config}></ButtonTemp>;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
export default MButton;
|
||||
|
||||
```
|
||||
|
||||
其中第一个参数为组件注册名,第二个参数用来展示使用。
|
||||
|
||||
|
||||
第三个参数用来配置右侧面板的配置项组件。其中键为右侧面板的分类,值为配置项组件数组。
|
||||
|
||||
第四个参数会配置组件的初始值,特别注意的是,制作组件必须要有初始宽度高度(非由内容撑开),否则会在适配时全选时产生问题。
|
||||
|
||||
这个初始值里有很多有用的属性,比如fixed代表使用固定定位,可以结合配置项更改该值,使得组件可以fixed定位。
|
||||
|
||||
还有canDrag类似于锁定命令,锁定的元素不可拖拽。
|
||||
|
||||
初始值里的rotate需要个对象,value代表旋转角度,canRotate 代表是否可以操作旋转。(0.7.0版本开始支持)
|
||||
|
||||
第五个参数是个函数,你将获得配置项中的receive属性(暂且都默认该配置为receive)传来的配置,比如上例中receive的是text,则该函数中data里会收到该字段。
|
||||
|
||||
context一般只有preview和edit,用来进行环境判断。
|
||||
|
||||
config可以拿到所有数据,用来制作事件时使用。
|
||||
|
||||
第六个参数resize 是为了判断是否能进行缩放,当为false时,无法进行缩放。
|
||||
|
||||
第七个参数needPosition,某些组件移入画布后会默认采取拖拽的落点,该配置项默认为true,就是需要拖拽的位置,为false时将使用组件自身top和left定位来放置。
|
||||
|
||||
|
||||
|
||||
### 事件注册
|
||||
|
||||
#### 时机注册
|
||||
|
||||
前面说了事件有时机和函数,所以组件内可以使用hook注册时机:
|
||||
|
||||
```js
|
||||
useDynamicAddEventCenter(pr, `${pr.data.id}-init`, '初始渲染时机'); //注册名必须带id 约定!
|
||||
useDynamicAddEventCenter(pr, `${pr.data.id}-click`, '点击执行时机');
|
||||
```
|
||||
|
||||
useDynamicAddEventCenter第一个参数是render的四个参数组成的对象。第二个参数是注册的时机名,必须跟id相关,这是约定,否则多个组件可能会导致名称冲突,并且方便查找该时机。
|
||||
|
||||
注册完时机后,你需要将时机放入对应的触发位置上,比如这个button的点击执行时机就放到onclick中:
|
||||
|
||||
```js
|
||||
<Button
|
||||
onClick={() => {
|
||||
eventCenter.runEventQueue(`${pr.data.id}-click`, pr.config);
|
||||
}}
|
||||
>
|
||||
yehuozhili
|
||||
</Button>
|
||||
```
|
||||
|
||||
其中第一个参数则为注册的时机名,第二个为render函数中最后个参数config
|
||||
|
||||
|
||||
#### 函数注册
|
||||
|
||||
函数由组件抛出,可以加载到事件链上。比如,注册个改变文本函数,那么我可以在任意组件的时机中去调用该函数,从而触发该组件改变文本。
|
||||
|
||||
函数注册需要放入useEffect中,在组件卸载时需要卸载函数!否则会导致函数越来越多。
|
||||
|
||||
注意id要带上组件id,因为一个组件可以拖出n个组件生成n个函数。
|
||||
|
||||
```js
|
||||
useEffect(() => {
|
||||
const functionCenter = eventCenter.getFunctionCenter();
|
||||
const unregist = functionCenter.register(
|
||||
`${pr.data.id}+改变文本函数`,
|
||||
async (ctx, next, config, args, _eventList, iname) => {
|
||||
const userSelect = iname.data;
|
||||
const ctxVal = changeUserValue(
|
||||
userSelect['改变文本数据源'],
|
||||
args,
|
||||
'_changeval',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
const text = ctxVal[0];
|
||||
setText(text);
|
||||
next();
|
||||
},
|
||||
[
|
||||
{
|
||||
name: '改变文本数据源',
|
||||
data: ['ctx', 'input', 'dataSource'],
|
||||
options: {
|
||||
receive: '_changeval',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
`${pr.data.id}+改变文本函数`
|
||||
);
|
||||
return () => {
|
||||
unregist();
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
函数中参数与配置见后面函数开发。
|
||||
|
||||
### 右侧面板
|
||||
|
||||
|
||||
右侧面板的配置和左侧面板一样:
|
||||
|
||||
```js
|
||||
export interface RightMapRenderListPropsItemCategory {
|
||||
type: string;
|
||||
icon: ReactNode;
|
||||
custom?: boolean;
|
||||
customRender?: (type: string, current: IBlockType) => ReactNode;
|
||||
}
|
||||
```
|
||||
|
||||
type会影响左侧组件在开发时第三个参数的键名。那个键名中即代表该右侧中展示的type。
|
||||
|
||||
icon则是可以放文字或者图标用来进行面板切换的。
|
||||
|
||||
如果custom为true,该面板下的显示可以通过customRender自定义。
|
||||
|
||||
### 右侧组件
|
||||
|
||||
|
||||
#### 右侧组件导入
|
||||
|
||||
导入时,只要将开发的组件配成一个对象放入initFormComponents即可。
|
||||
|
||||
```js
|
||||
initFormComponents: Formmodules,
|
||||
```
|
||||
|
||||
#### 右侧组件开发
|
||||
|
||||
首先为了良好的开发体验,需要定义个formMap类型:
|
||||
|
||||
```js
|
||||
export interface FormBaseType {
|
||||
receive?: string;
|
||||
}
|
||||
export interface FormInputType extends FormBaseType {
|
||||
label: string;
|
||||
}
|
||||
export interface FormActionButtonType {}
|
||||
export interface FormAnimateControlType {}
|
||||
export interface FormMap {
|
||||
input: FormInputType;
|
||||
actionButton: FormActionButtonType;
|
||||
animateControl: FormAnimateControlType;
|
||||
}
|
||||
```
|
||||
formMap的键名就是initFormComponents键名,formMap的值对应组件需要收到的值。
|
||||
|
||||
以input组件为例,FormInputType此时有2个属性,label,receive。
|
||||
|
||||
那么在其开发该组件时,props会收到:
|
||||
|
||||
```js
|
||||
interface MInputProps {
|
||||
data: CreateOptionsRes<FormMap, 'input'>;
|
||||
current: IBlockType;
|
||||
config: UserConfig;
|
||||
}
|
||||
```
|
||||
也就是其中data是formMap类型,而current是当前点击的组件,config就不用说了。
|
||||
|
||||
还记得在左侧组件开发中的第三个参数吗?这样就都关联起来了:
|
||||
|
||||
```js
|
||||
|
||||
style: [
|
||||
createPannelOptions<FormMap, 'input'>('input', {
|
||||
receive: 'text',
|
||||
label: '文字',
|
||||
}),
|
||||
],
|
||||
|
||||
```
|
||||
createPannelOptions 这个函数的泛型里填入对应的组件,将会给收到的配置项良好的提示。
|
||||
|
||||
在配置项组件里所要做的就是接收组件传来的配置项,然后去修改current的属性:
|
||||
|
||||
|
||||
```js
|
||||
function MInput(props: MInputProps) {
|
||||
const option = useMemo(() => {
|
||||
return props.data?.option || {};
|
||||
}, [props.data]);
|
||||
return (
|
||||
<Row style={{ padding: '10px 20px' }}>
|
||||
<Col span={6} style={{ lineHeight: '30px' }}>
|
||||
{(option as any)?.label || '文字'}:
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Input
|
||||
value={props.current.props[(option as any).receive] || ''}
|
||||
onChange={(e) => {
|
||||
const receive = (option as any).receive;
|
||||
const clonedata = deepCopy(store.getData());
|
||||
const newblock = clonedata.block.map((v: IBlockType) => {
|
||||
if (v.id === props.current.id) {
|
||||
v.props[receive] = e.target.value;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
store.setData({ ...clonedata, block: [...newblock] });
|
||||
}}
|
||||
></Input>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
由于可以很轻松的拿到store,所以可以在任意地方进行修改数据。
|
||||
|
||||
将组件的value关联current的属性,onChange去修改store,这样就完成了个双向绑定。
|
||||
|
||||
注意:如果你的右侧组件需要用到block以外的属性,可能需要去判断是否处于弹窗模式。
|
||||
|
||||
|
||||
### 命令开发
|
||||
|
||||
|
||||
|
||||
#### 命令的导入
|
||||
|
||||
命令对象导入到插件的initCommandModule里即可
|
||||
|
||||
```js
|
||||
initCommandModule: commandModules,
|
||||
```
|
||||
|
||||
#### 命令的开发
|
||||
|
||||
命令需要导出一个CommanderItemFactory生成的对象。
|
||||
|
||||
```js
|
||||
import { CommanderItemFactory } from 'dooringx-lib';
|
||||
const undo = new CommanderItemFactory(
|
||||
'redo',
|
||||
'Control+Shift+z',
|
||||
(store) => {
|
||||
store.redo();
|
||||
},
|
||||
'重做'
|
||||
);
|
||||
|
||||
export default undo;
|
||||
```
|
||||
|
||||
第一个参数是注册名。
|
||||
第二个参数是快捷键名,快捷键映射是键盘事件key值:
|
||||
|
||||
```js
|
||||
Cancel: 3,
|
||||
Help: 6,
|
||||
Backspace: 8,
|
||||
Tab: 9,
|
||||
Clear: 12,
|
||||
Enter: 13,
|
||||
Shift: 16,
|
||||
Control: 17,
|
||||
Alt: 18,
|
||||
Pause: 19,
|
||||
CapsLock: 20,
|
||||
Escape: 27,
|
||||
Convert: 28,
|
||||
NonConvert: 29,
|
||||
Accept: 30,
|
||||
ModeChange: 31,
|
||||
' ': 32,
|
||||
PageUp: 33,
|
||||
PageDown: 34,
|
||||
End: 35,
|
||||
Home: 36,
|
||||
ArrowLeft: 37,
|
||||
ArrowUp: 38,
|
||||
ArrowRight: 39,
|
||||
ArrowDown: 40,
|
||||
Select: 41,
|
||||
Print: 42,
|
||||
Execute: 43,
|
||||
PrintScreen: 44,
|
||||
Insert: 45,
|
||||
Delete: 46,
|
||||
0: 48,
|
||||
')': 48,
|
||||
1: 49,
|
||||
'!': 49,
|
||||
2: 50,
|
||||
'@': 50,
|
||||
3: 51,
|
||||
'#': 51,
|
||||
4: 52,
|
||||
$: 52,
|
||||
5: 53,
|
||||
'%': 53,
|
||||
6: 54,
|
||||
'^': 54,
|
||||
7: 55,
|
||||
'&': 55,
|
||||
8: 56,
|
||||
'*': 56,
|
||||
9: 57,
|
||||
'(': 57,
|
||||
a: 65,
|
||||
A: 65,
|
||||
b: 66,
|
||||
B: 66,
|
||||
c: 67,
|
||||
C: 67,
|
||||
d: 68,
|
||||
D: 68,
|
||||
e: 69,
|
||||
E: 69,
|
||||
f: 70,
|
||||
F: 70,
|
||||
g: 71,
|
||||
G: 71,
|
||||
h: 72,
|
||||
H: 72,
|
||||
i: 73,
|
||||
I: 73,
|
||||
j: 74,
|
||||
J: 74,
|
||||
k: 75,
|
||||
K: 75,
|
||||
l: 76,
|
||||
L: 76,
|
||||
m: 77,
|
||||
M: 77,
|
||||
n: 78,
|
||||
N: 78,
|
||||
o: 79,
|
||||
O: 79,
|
||||
p: 80,
|
||||
P: 80,
|
||||
q: 81,
|
||||
Q: 81,
|
||||
r: 82,
|
||||
R: 82,
|
||||
s: 83,
|
||||
S: 83,
|
||||
t: 84,
|
||||
T: 84,
|
||||
u: 85,
|
||||
U: 85,
|
||||
v: 86,
|
||||
V: 86,
|
||||
w: 87,
|
||||
W: 87,
|
||||
x: 88,
|
||||
X: 88,
|
||||
y: 89,
|
||||
Y: 89,
|
||||
z: 90,
|
||||
Z: 90,
|
||||
OS: 91,
|
||||
ContextMenu: 93,
|
||||
F1: 112,
|
||||
F2: 113,
|
||||
F3: 114,
|
||||
F4: 115,
|
||||
F5: 116,
|
||||
F6: 117,
|
||||
F7: 118,
|
||||
F8: 119,
|
||||
F9: 120,
|
||||
F10: 121,
|
||||
F11: 122,
|
||||
F12: 123,
|
||||
F13: 124,
|
||||
F14: 125,
|
||||
F15: 126,
|
||||
F16: 127,
|
||||
F17: 128,
|
||||
F18: 129,
|
||||
F19: 130,
|
||||
F20: 131,
|
||||
F21: 132,
|
||||
F22: 133,
|
||||
F23: 134,
|
||||
F24: 135,
|
||||
NumLock: 144,
|
||||
ScrollLock: 145,
|
||||
VolumeMute: 181,
|
||||
VolumeDown: 182,
|
||||
VolumeUp: 183,
|
||||
';': 186,
|
||||
':': 186,
|
||||
'=': 187,
|
||||
'+': 187,
|
||||
',': 188,
|
||||
'<': 188,
|
||||
'-': 189,
|
||||
_: 189,
|
||||
'.': 190,
|
||||
'>': 190,
|
||||
'/': 191,
|
||||
'?': 191,
|
||||
'`': 192,
|
||||
'~': 192,
|
||||
'[': 219,
|
||||
'{': 219,
|
||||
'\\': 220,
|
||||
'|': 220,
|
||||
']': 221,
|
||||
'}': 221,
|
||||
"'": 222,
|
||||
'"': 222,
|
||||
Meta: 224,
|
||||
AltGraph: 225,
|
||||
Attn: 246,
|
||||
CrSel: 247,
|
||||
ExSel: 248,
|
||||
EraseEof: 249,
|
||||
Play: 250,
|
||||
ZoomOut: 251,
|
||||
```
|
||||
|
||||
26个英文字母是忽略大小写的,一个命令目前只能注册一个快捷键。不需要注册快捷键则填空字符串即可。
|
||||
|
||||
metakey与Controlkey相同,写Control即可。
|
||||
|
||||
目前第三个参数只能获得store,后续需要修改下。 0.2.0 版本第二个参数可以获得config,同时commander不从index中导出,需要使用时从config中获取。
|
||||
|
||||
最后个参数是展示名。
|
||||
|
||||
|
||||
### 右键菜单
|
||||
|
||||
右键菜单可以进行自定义:
|
||||
|
||||
```js
|
||||
// 自定义右键
|
||||
const contextMenuState = config.getContextMenuState();
|
||||
const unmountContextMenu = contextMenuState.unmountContextMenu;
|
||||
const commander = config.getCommanderRegister();
|
||||
const ContextMenu = () => {
|
||||
const handleclick = () => {
|
||||
unmountContextMenu();
|
||||
};
|
||||
const forceUpdate = useState(0)[1];
|
||||
contextMenuState.forceUpdate = () => {
|
||||
forceUpdate((pre) => pre + 1);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
left: contextMenuState.left,
|
||||
top: contextMenuState.top,
|
||||
position: 'fixed',
|
||||
background: 'rgb(24, 23, 23)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ width: '100%' }}
|
||||
onClick={() => {
|
||||
commander.exec('redo');
|
||||
handleclick();
|
||||
}}
|
||||
>
|
||||
<Button>自定义</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
contextMenuState.contextMenu = <ContextMenu></ContextMenu>;
|
||||
```
|
||||
先拿到contextMenuState,contextMenuState上有个unmountContextMenu是关闭右键菜单方法。
|
||||
|
||||
所以在点击后需要调用关闭。
|
||||
|
||||
同时上面的left和top是右键的位置。
|
||||
|
||||
另外,你还需要在组件内增加个强刷赋值给forceUpdate,用于在组件移动时进行跟随。
|
||||
|
||||
|
||||
### 函数开发
|
||||
|
||||
|
||||
|
||||
#### 函数导入
|
||||
|
||||
函数导入做成对象置入initFunctionMap即可
|
||||
|
||||
```js
|
||||
initFunctionMap: functionMap,
|
||||
```
|
||||
|
||||
#### 函数开发
|
||||
|
||||
|
||||
键名会显示出来所以键名是唯一的。
|
||||
|
||||
它的值是2个对象,一个是函数内容fn,一个是配置项config (0.10.0以上还需要传入函数名称,用于显示)。
|
||||
|
||||
config中的数组里每个配置会显示出来让用户去配置,name则是展示名字,data代表数据去哪里获取,可以选择从输入框(input),数据源(dataSource),上下文(ctx)中获取,另外还有个特殊的弹窗(modal)。
|
||||
|
||||
options中的receive表示会从args哪个键上获取该值。
|
||||
|
||||
multi代表是否允许多个选项配置。
|
||||
|
||||
dooringx-lib中写好了2个函数changeUserValue与changeUserValueRecord,第一个函数会将得到的结果做成数组,如果非multi则取第一个结果就行。而第二个函数会将结果做成对象,比如用户在数据源中选了keya,那么就会把数据源的键值对作为个对象返回。
|
||||
|
||||
|
||||
fn中,第一个ctx参数代表上下文,如果有转换函数之类,可能需要使用(比如要把第一个函数的结果导给后面的函数)
|
||||
|
||||
第二个参数next是需要运行完毕后执行的,否则事件链会一直在该函数中不退出。
|
||||
|
||||
第三个参数config就可以拿到整个config对象。
|
||||
|
||||
第四个参数args是用户填写的参数,会根据options里填写的字段进行返回。
|
||||
|
||||
第五个是eventList,可以获取整个事件链的参数。
|
||||
|
||||
第六个参数iname可以拿到用户的选择项。
|
||||
|
||||
|
||||
```js
|
||||
通用GET请求函数: {
|
||||
fn: (ctx, next, config, args, _eventList, iname) => {
|
||||
console.log(args, '参数x');
|
||||
const userSelect = iname.data;
|
||||
const urlVal = changeUserValue(
|
||||
userSelect['请求url'],
|
||||
args,
|
||||
'_url',
|
||||
config,
|
||||
ctx
|
||||
); // input datasource ctx //datasource会去取值 ,ctx取ctx上字段
|
||||
const paramSource = changeUserValueRecord(
|
||||
// 设定只能从datasource或者ctx里取
|
||||
userSelect['请求参数'],
|
||||
args,
|
||||
'_origin',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
const ctxVal = changeUserValue(
|
||||
userSelect['返回上下文字段'],
|
||||
args,
|
||||
'_ctx',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
// 检查参数是否存在
|
||||
// 都是数组,非multi则取第一个。
|
||||
const url = urlVal[0];
|
||||
if (!url) {
|
||||
return next();
|
||||
}
|
||||
const ctxKey = ctxVal[0];
|
||||
|
||||
axios
|
||||
.get(url, {
|
||||
params: {
|
||||
...paramSource,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
const data = res.data;
|
||||
ctx[ctxKey] = data;
|
||||
next();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
next();
|
||||
});
|
||||
},
|
||||
config: [
|
||||
{
|
||||
name: '请求url',
|
||||
data: ['dataSource', 'ctx', 'input'],
|
||||
options: {
|
||||
receive: '_url',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '请求参数',
|
||||
data: ['dataSource', 'ctx'],
|
||||
options: {
|
||||
receive: '_origin',
|
||||
multi: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '返回上下文字段',
|
||||
data: ['input'],
|
||||
options: {
|
||||
receive: '_ctx',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
name: '通用GET请求函数'
|
||||
},
|
||||
```
|
||||
|
||||
#### 时机与函数装载
|
||||
|
||||
如果有需要,一般使用:
|
||||
|
||||
```js
|
||||
eventCenter.manualUpdateMap(cur, displayName, arr);
|
||||
```
|
||||
|
||||
manualUpdateMap第一个是时机名,第二个是展示名,第三个是用户选择。
|
||||
|
||||
更新事件中心后还需要更新store,结果以store为准。
|
@@ -1,7 +1,234 @@
|
||||
---
|
||||
title: Guide
|
||||
title: Introduction
|
||||
toc: menu
|
||||
order: 1
|
||||
nav:
|
||||
title: Guide
|
||||
order: 1
|
||||
---
|
||||
### What is dooringx lib?
|
||||
Dooringx lib is the base of dooringx and a visual drag and drop framework with dooringx plug-ins removed.
|
||||
Dooring x lib provides its own set of data flow event mechanism, pop-up window and other solutions, which can enable you to customize and develop your own visual drag and drop platform faster.
|
||||
### How does dooringx lib work?
|
||||
Dooringx lib maintains a set of data flow at runtime, which is mainly divided into JSON data part, left component part, right configuration item part, shortcut key part, pop-up window part, event and function part and data source part.
|
||||
In addition to providing basic drag, move, zoom, select all, rotate and other functions, it can also use exposed components. If you feel that the components are not customized enough, you can adjust the style or rewrite it yourself.
|
||||
### Get started quickly
|
||||
#### Installation
|
||||
Install using NPM or yarn
|
||||
```bash
|
||||
npm i dooringx-lib
|
||||
```
|
||||
Dooring x lib provides two containers for editing, which can be used as needed.
|
||||
One is an ordinary container and the other is an iframe container. These two containers are slightly different in some implementations.
|
||||
If you use a normal container, that is, the div that is normal when editing is not iframe, while if you use iframe, you will see the iframe content when editing. When previewing, use the preview component, and the preview can be placed in any container, including iframe.
|
||||
It is recommended to use iframe to view the preview during preview. If there is a pop-up window, exceptions will be displayed in non iframe or PC.
|
||||
The iframe container may have a slight delay in operation because it uses PostMessage communication. If the style isolation requirements are not high, you can use ordinary containers, and the preview style can be normal.
|
||||
Common container usage reference Demo:
|
||||
```js
|
||||
import {
|
||||
RightConfig,
|
||||
Container,
|
||||
useStoreState,
|
||||
innerContainerDragUp,
|
||||
LeftConfig,
|
||||
ContainerWrapper,
|
||||
Control,
|
||||
} from 'dooringx-lib';
|
||||
import { useContext } from 'react';
|
||||
import { configContext } from '@/layouts';
|
||||
import { useCallback } from 'react';
|
||||
import { PREVIEWSTATE } from '@/constant';
|
||||
export const HeaderHeight = '40px';
|
||||
export default function IndexPage() {
|
||||
const config = useContext(configContext);
|
||||
const everyFn = () => {};
|
||||
const subscribeFn = useCallback(() => {
|
||||
localStorage.setItem(PREVIEWSTATE, JSON.stringify(config.getStore().getData()));
|
||||
}, [config]);
|
||||
const [state] = useStoreState(config, subscribeFn, everyFn);
|
||||
return (
|
||||
<div {...innerContainerDragUp(config)}>
|
||||
<div style={{ height: HeaderHeight }}>
|
||||
head
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open('/iframe');
|
||||
}}
|
||||
>
|
||||
go preview
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open('/preview');
|
||||
}}
|
||||
>
|
||||
go preview
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: `calc(100vh - ${HeaderHeight})`,
|
||||
width: '100vw',
|
||||
}}
|
||||
>
|
||||
<div style={{ height: '100%' }}>
|
||||
<LeftConfig config={config}></LeftConfig>
|
||||
</div>
|
||||
<ContainerWrapper config={config}>
|
||||
<>
|
||||
<Control
|
||||
config={config}
|
||||
style={{ position: 'fixed', bottom: '60px', right: '450px', zIndex: 100 }}
|
||||
></Control>
|
||||
<Container state={state} config={config} context="edit"></Container>
|
||||
</>
|
||||
</ContainerWrapper>
|
||||
<div className="rightrender" style={{ height: '100%' }}>
|
||||
<RightConfig state={state} config={config}></RightConfig>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
Iframe container use reference Demo:
|
||||
index.tsx
|
||||
```js
|
||||
import {
|
||||
RightConfig,
|
||||
useStoreState,
|
||||
innerContainerDragUp,
|
||||
LeftConfig,
|
||||
IframeContainerWrapper,
|
||||
Control,
|
||||
useIframeHook,
|
||||
IframeTarget,
|
||||
} from 'dooringx-lib';
|
||||
import { useContext } from 'react';
|
||||
import { configContext } from '@/layouts';
|
||||
import { useCallback } from 'react';
|
||||
import { PREVIEWSTATE } from '@/constant';
|
||||
export const HeaderHeight = '40px';
|
||||
export default function IndexPage() {
|
||||
const config = useContext(configContext);
|
||||
const subscribeFn = useCallback(() => {
|
||||
localStorage.setItem(PREVIEWSTATE, JSON.stringify(config.getStore().getData()));
|
||||
}, [config]);
|
||||
const [state] = useStoreState(config, subscribeFn);
|
||||
useIframeHook(`${location.origin}/container`, config);
|
||||
return (
|
||||
<div {...innerContainerDragUp(config, true)}>
|
||||
<div style={{ height: HeaderHeight }}>
|
||||
head
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open('/iframe');
|
||||
}}
|
||||
>
|
||||
go preview
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open('/preview');
|
||||
}}
|
||||
>
|
||||
go preview
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: `calc(100vh - ${HeaderHeight})`,
|
||||
width: '100vw',
|
||||
}}
|
||||
>
|
||||
<div style={{ height: '100%' }}>
|
||||
<LeftConfig config={config}></LeftConfig>
|
||||
</div>
|
||||
<IframeContainerWrapper
|
||||
config={config}
|
||||
extra={
|
||||
<Control
|
||||
config={config}
|
||||
style={{ position: 'fixed', bottom: '60px', right: '450px', zIndex: 100 }}
|
||||
></Control>
|
||||
}
|
||||
>
|
||||
<IframeTarget
|
||||
config={config}
|
||||
iframeProps={{
|
||||
src: '/container',
|
||||
}}
|
||||
></IframeTarget>
|
||||
</IframeContainerWrapper>
|
||||
<div className="rightrender" style={{ height: '100%' }}>
|
||||
<RightConfig state={state} config={config}></RightConfig>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
Container Routing:
|
||||
```js
|
||||
import { configContext } from '@/layouts';
|
||||
import { useContext } from 'react';
|
||||
import { IframeContainer } from 'dooringx-lib';
|
||||
function ContainerPage() {
|
||||
const config = useContext(configContext);
|
||||
return (
|
||||
<div>
|
||||
<IframeContainer config={config} context="edit"></IframeContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default ContainerPage;
|
||||
```
|
||||
Preview iframe:
|
||||
```html
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<iframe style={{ width: '375px', height: '667px' }} src="/preview"></iframe>
|
||||
</div>
|
||||
```
|
||||
Preview route:
|
||||
```js
|
||||
import { PREVIEWSTATE } from '@/constant';
|
||||
import { Preview, UserConfig } from 'dooringx-lib';
|
||||
import plugin from '../../plugin';
|
||||
const config = new UserConfig(plugin);
|
||||
function PreviewPage() {
|
||||
const data = localStorage.getItem(PREVIEWSTATE);
|
||||
if (data) {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
config.resetData([json]);
|
||||
} catch {
|
||||
console.log('err');
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Preview config={config}></Preview>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default PreviewPage;
|
||||
```
|
||||
For the API section, refer to API
|
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: 指南
|
||||
title: 介绍
|
||||
toc: menu
|
||||
order: 1
|
||||
nav:
|
||||
title: 指南
|
||||
order: 1
|
||||
---
|
||||
|
||||
|
||||
## 介绍
|
||||
|
||||
### dooringx-lib 是什么?
|
||||
|
||||
@@ -285,797 +285,3 @@ export default PreviewPage;
|
||||
|
||||
有关 api 部分请参考 api
|
||||
|
||||
|
||||
## dooringx-lib基础
|
||||
|
||||
### store
|
||||
|
||||
store 类似于 redux 的概念,它内部实现了 redo、undo、发布订阅、置换数据、强制刷新等功能。
|
||||
|
||||
store 可以在 config 中获取。
|
||||
|
||||
在最开始时,需要通过 useStoreState 与 react 结合,此时可以在任意位置使用 store.forceUpdate 强刷,也可以使用 state 获取 store 中的数据。
|
||||
|
||||
store 的最重要功能是保存着每次修改 json 队列。
|
||||
|
||||
如果你需要更新数据,在深拷贝后使用 setData 方法进行更新。
|
||||
|
||||
如果你需要更新时不记录在 redo 或 undo 上留下记录,那么请操作队列删除其中保存内容即可。
|
||||
|
||||
对于改变数据后想即使看见视图更新,那么使用forceUpdate即可。
|
||||
|
||||
|
||||
### 事件
|
||||
|
||||
dooringx-lib 的事件是在eventCenter上,它上面会集成functionCenter与一个事件链。
|
||||
|
||||
在eventCenter中可以获取组件注册的时机。时机类似于组件生命周期一样,可以注册后在对应的时机进行调用。
|
||||
|
||||
而functionCenter中的函数则会与时机结合,再由事件链对用户设定的队列进行统一处理。
|
||||
|
||||
每个事件链在执行中会有上下文对象,这个对象会贯穿这个事件链。
|
||||
|
||||
### 命令
|
||||
|
||||
|
||||
dooringx-lib 的命令是由commander进行管理。
|
||||
|
||||
内部默认提供redo与undo的命令,您可以通过插件方式增加commander。
|
||||
|
||||
commander内部集成了快捷键配置,使用键盘事件的key进行注册,如果ctrl、alt、meta键,可加对应的加号进行组合键注册,内部忽略大小写(注意!不是忽略注册的键名大小写,而是A和a的key处理时等价)。
|
||||
|
||||
### 弹窗
|
||||
|
||||
|
||||
dooringx-lib内置弹窗系统,弹窗系统是通过storeChanger进行转换而成。
|
||||
|
||||
所以在某些情景制作时,可能需要考虑是否在弹窗编辑下的情况。
|
||||
|
||||
每个弹窗是只保存block中的数据,而事件等数据只会存在主数据内。
|
||||
|
||||
在弹窗保存后,弹窗数据会被置换于主数据内存着,需要编辑时重新置换出来。
|
||||
|
||||
可以使用storeChanger上的方法进行判断,或者直接获取数据源数据等,具体见API。
|
||||
|
||||
|
||||
### 数据源
|
||||
|
||||
|
||||
dooringx-lib 的数据源和前面说的store中存储的不是一个东西。
|
||||
|
||||
它位于dataCenter中。设计数据源的初衷是为了让不懂代码的人更好理解。
|
||||
|
||||
事件的运行完全可以脱离数据源运行,只要使用者知道如何去填写参数。
|
||||
|
||||
所以在事件配置时,可以多个选项在数据源中去获得数据转变为参数。
|
||||
|
||||
|
||||
## dooringx-lib插件开发
|
||||
|
||||
### 插件导入
|
||||
|
||||
dooringx-lib的插件需要一个类型为`Partial<InitConfig>`的对象。
|
||||
|
||||
对于多个插件,需要使用dooringx-lib导出的`userConfigMerge`来进行合并。
|
||||
|
||||
userConfigMerge不是所有属性都会合并,部分属性会进行覆盖。
|
||||
|
||||
```
|
||||
* 部分无法合并属性如果b传了会以b为准
|
||||
* initstore不合并
|
||||
* leftallregistmap合并
|
||||
* leftRenderListCategory合并
|
||||
* rightRenderListCategory合并
|
||||
* rightGlobalCustom 不合并
|
||||
* initComponentCache合并
|
||||
* initFunctionMap合并
|
||||
* initDataCenterMap合并
|
||||
* initCommandModule合并
|
||||
* initFormComponents合并
|
||||
```
|
||||
|
||||
config支持部分配置异步导入,比如左侧分类等,这个是实验性功能,所以不推荐这么做。
|
||||
|
||||
|
||||
### 左侧面板
|
||||
|
||||
|
||||
左侧面板传入leftRenderListCategory即可。
|
||||
|
||||
```js
|
||||
leftRenderListCategory: [
|
||||
{
|
||||
type: 'basic',
|
||||
icon: <HighlightOutlined />,
|
||||
displayName: '基础组件',
|
||||
},
|
||||
{
|
||||
type: 'xxc',
|
||||
icon: <ContainerOutlined />,
|
||||
custom: true,
|
||||
customRender: <div>我是自定义渲染</div>,
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
type是分类,左侧组件显示在哪个分类由该字段决定。
|
||||
|
||||
icon则是左侧分类小图标。
|
||||
|
||||
当custom为true时,可以使用customRender自定义渲染。
|
||||
|
||||
### 左侧组件
|
||||
|
||||
|
||||
### 插件导入
|
||||
|
||||
左侧组件要至于对象的LeftRegistMap中。
|
||||
|
||||
左侧组件支持同步导入或者异步导入。
|
||||
|
||||
```js
|
||||
const LeftRegistMap: LeftRegistComponentMapItem[] = [
|
||||
{
|
||||
type: 'basic',
|
||||
component: 'button',
|
||||
img: 'icon-anniu',
|
||||
displayName: '按钮',
|
||||
urlFn: () => import('./registComponents/button'),
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
如果需要异步导入组件,则需要填写urlFn,需要一个返回promise的函数。也可以支持远程载入组件,只要webpack配上就行了。
|
||||
|
||||
如果需要同步导入组件,则需要将组件放入配置项的initComponentCache中,这样在载入时便会注册进componentRegister里。
|
||||
|
||||
```js
|
||||
initComponentCache: {
|
||||
modalMask: { component: MmodalMask },
|
||||
},
|
||||
```
|
||||
|
||||
### 组件编写
|
||||
|
||||
组件需要导出一个由ComponentItemFactory生成的对象。
|
||||
|
||||
```js
|
||||
const MButton = new ComponentItemFactory(
|
||||
'button',
|
||||
'按钮',
|
||||
{
|
||||
style: [
|
||||
createPannelOptions<FormMap, 'input'>('input', {
|
||||
receive: 'text',
|
||||
label: '文字',
|
||||
}),
|
||||
],
|
||||
animate: [createPannelOptions<FormMap, 'animateControl'>('animateControl', {})],
|
||||
actions: [createPannelOptions<FormMap, 'actionButton'>('actionButton', {})],
|
||||
},
|
||||
{
|
||||
props: {
|
||||
...
|
||||
text:'yehuozhili'// input配置项组件接收的初始值
|
||||
},
|
||||
},
|
||||
(data, context, store, config) => {
|
||||
return <ButtonTemp data={data} store={store} context={context} config={config}></ButtonTemp>;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
export default MButton;
|
||||
|
||||
```
|
||||
|
||||
其中第一个参数为组件注册名,第二个参数用来展示使用。
|
||||
|
||||
|
||||
第三个参数用来配置右侧面板的配置项组件。其中键为右侧面板的分类,值为配置项组件数组。
|
||||
|
||||
第四个参数会配置组件的初始值,特别注意的是,制作组件必须要有初始宽度高度(非由内容撑开),否则会在适配时全选时产生问题。
|
||||
|
||||
这个初始值里有很多有用的属性,比如fixed代表使用固定定位,可以结合配置项更改该值,使得组件可以fixed定位。
|
||||
|
||||
还有canDrag类似于锁定命令,锁定的元素不可拖拽。
|
||||
|
||||
初始值里的rotate需要个对象,value代表旋转角度,canRotate 代表是否可以操作旋转。(0.7.0版本开始支持)
|
||||
|
||||
第五个参数是个函数,你将获得配置项中的receive属性(暂且都默认该配置为receive)传来的配置,比如上例中receive的是text,则该函数中data里会收到该字段。
|
||||
|
||||
context一般只有preview和edit,用来进行环境判断。
|
||||
|
||||
config可以拿到所有数据,用来制作事件时使用。
|
||||
|
||||
第六个参数resize 是为了判断是否能进行缩放,当为false时,无法进行缩放。
|
||||
|
||||
第七个参数needPosition,某些组件移入画布后会默认采取拖拽的落点,该配置项默认为true,就是需要拖拽的位置,为false时将使用组件自身top和left定位来放置。
|
||||
|
||||
|
||||
|
||||
### 事件注册
|
||||
|
||||
#### 时机注册
|
||||
|
||||
前面说了事件有时机和函数,所以组件内可以使用hook注册时机:
|
||||
|
||||
```js
|
||||
useDynamicAddEventCenter(pr, `${pr.data.id}-init`, '初始渲染时机'); //注册名必须带id 约定!
|
||||
useDynamicAddEventCenter(pr, `${pr.data.id}-click`, '点击执行时机');
|
||||
```
|
||||
|
||||
useDynamicAddEventCenter第一个参数是render的四个参数组成的对象。第二个参数是注册的时机名,必须跟id相关,这是约定,否则多个组件可能会导致名称冲突,并且方便查找该时机。
|
||||
|
||||
注册完时机后,你需要将时机放入对应的触发位置上,比如这个button的点击执行时机就放到onclick中:
|
||||
|
||||
```js
|
||||
<Button
|
||||
onClick={() => {
|
||||
eventCenter.runEventQueue(`${pr.data.id}-click`, pr.config);
|
||||
}}
|
||||
>
|
||||
yehuozhili
|
||||
</Button>
|
||||
```
|
||||
|
||||
其中第一个参数则为注册的时机名,第二个为render函数中最后个参数config
|
||||
|
||||
|
||||
#### 函数注册
|
||||
|
||||
函数由组件抛出,可以加载到事件链上。比如,注册个改变文本函数,那么我可以在任意组件的时机中去调用该函数,从而触发该组件改变文本。
|
||||
|
||||
函数注册需要放入useEffect中,在组件卸载时需要卸载函数!否则会导致函数越来越多。
|
||||
|
||||
注意id要带上组件id,因为一个组件可以拖出n个组件生成n个函数。
|
||||
|
||||
```js
|
||||
useEffect(() => {
|
||||
const functionCenter = eventCenter.getFunctionCenter();
|
||||
const unregist = functionCenter.register(
|
||||
`${pr.data.id}+改变文本函数`,
|
||||
async (ctx, next, config, args, _eventList, iname) => {
|
||||
const userSelect = iname.data;
|
||||
const ctxVal = changeUserValue(
|
||||
userSelect['改变文本数据源'],
|
||||
args,
|
||||
'_changeval',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
const text = ctxVal[0];
|
||||
setText(text);
|
||||
next();
|
||||
},
|
||||
[
|
||||
{
|
||||
name: '改变文本数据源',
|
||||
data: ['ctx', 'input', 'dataSource'],
|
||||
options: {
|
||||
receive: '_changeval',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
`${pr.data.id}+改变文本函数`
|
||||
);
|
||||
return () => {
|
||||
unregist();
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
函数中参数与配置见后面函数开发。
|
||||
|
||||
### 右侧面板
|
||||
|
||||
|
||||
右侧面板的配置和左侧面板一样:
|
||||
|
||||
```js
|
||||
export interface RightMapRenderListPropsItemCategory {
|
||||
type: string;
|
||||
icon: ReactNode;
|
||||
custom?: boolean;
|
||||
customRender?: (type: string, current: IBlockType) => ReactNode;
|
||||
}
|
||||
```
|
||||
|
||||
type会影响左侧组件在开发时第三个参数的键名。那个键名中即代表该右侧中展示的type。
|
||||
|
||||
icon则是可以放文字或者图标用来进行面板切换的。
|
||||
|
||||
如果custom为true,该面板下的显示可以通过customRender自定义。
|
||||
|
||||
### 右侧组件
|
||||
|
||||
|
||||
#### 右侧组件导入
|
||||
|
||||
导入时,只要将开发的组件配成一个对象放入initFormComponents即可。
|
||||
|
||||
```js
|
||||
initFormComponents: Formmodules,
|
||||
```
|
||||
|
||||
#### 右侧组件开发
|
||||
|
||||
首先为了良好的开发体验,需要定义个formMap类型:
|
||||
|
||||
```js
|
||||
export interface FormBaseType {
|
||||
receive?: string;
|
||||
}
|
||||
export interface FormInputType extends FormBaseType {
|
||||
label: string;
|
||||
}
|
||||
export interface FormActionButtonType {}
|
||||
export interface FormAnimateControlType {}
|
||||
export interface FormMap {
|
||||
input: FormInputType;
|
||||
actionButton: FormActionButtonType;
|
||||
animateControl: FormAnimateControlType;
|
||||
}
|
||||
```
|
||||
formMap的键名就是initFormComponents键名,formMap的值对应组件需要收到的值。
|
||||
|
||||
以input组件为例,FormInputType此时有2个属性,label,receive。
|
||||
|
||||
那么在其开发该组件时,props会收到:
|
||||
|
||||
```js
|
||||
interface MInputProps {
|
||||
data: CreateOptionsRes<FormMap, 'input'>;
|
||||
current: IBlockType;
|
||||
config: UserConfig;
|
||||
}
|
||||
```
|
||||
也就是其中data是formMap类型,而current是当前点击的组件,config就不用说了。
|
||||
|
||||
还记得在左侧组件开发中的第三个参数吗?这样就都关联起来了:
|
||||
|
||||
```js
|
||||
|
||||
style: [
|
||||
createPannelOptions<FormMap, 'input'>('input', {
|
||||
receive: 'text',
|
||||
label: '文字',
|
||||
}),
|
||||
],
|
||||
|
||||
```
|
||||
createPannelOptions 这个函数的泛型里填入对应的组件,将会给收到的配置项良好的提示。
|
||||
|
||||
在配置项组件里所要做的就是接收组件传来的配置项,然后去修改current的属性:
|
||||
|
||||
|
||||
```js
|
||||
function MInput(props: MInputProps) {
|
||||
const option = useMemo(() => {
|
||||
return props.data?.option || {};
|
||||
}, [props.data]);
|
||||
return (
|
||||
<Row style={{ padding: '10px 20px' }}>
|
||||
<Col span={6} style={{ lineHeight: '30px' }}>
|
||||
{(option as any)?.label || '文字'}:
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Input
|
||||
value={props.current.props[(option as any).receive] || ''}
|
||||
onChange={(e) => {
|
||||
const receive = (option as any).receive;
|
||||
const clonedata = deepCopy(store.getData());
|
||||
const newblock = clonedata.block.map((v: IBlockType) => {
|
||||
if (v.id === props.current.id) {
|
||||
v.props[receive] = e.target.value;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
store.setData({ ...clonedata, block: [...newblock] });
|
||||
}}
|
||||
></Input>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
由于可以很轻松的拿到store,所以可以在任意地方进行修改数据。
|
||||
|
||||
将组件的value关联current的属性,onChange去修改store,这样就完成了个双向绑定。
|
||||
|
||||
注意:如果你的右侧组件需要用到block以外的属性,可能需要去判断是否处于弹窗模式。
|
||||
|
||||
|
||||
### 命令开发
|
||||
|
||||
|
||||
|
||||
#### 命令的导入
|
||||
|
||||
命令对象导入到插件的initCommandModule里即可
|
||||
|
||||
```js
|
||||
initCommandModule: commandModules,
|
||||
```
|
||||
|
||||
#### 命令的开发
|
||||
|
||||
命令需要导出一个CommanderItemFactory生成的对象。
|
||||
|
||||
```js
|
||||
import { CommanderItemFactory } from 'dooringx-lib';
|
||||
const undo = new CommanderItemFactory(
|
||||
'redo',
|
||||
'Control+Shift+z',
|
||||
(store) => {
|
||||
store.redo();
|
||||
},
|
||||
'重做'
|
||||
);
|
||||
|
||||
export default undo;
|
||||
```
|
||||
|
||||
第一个参数是注册名。
|
||||
第二个参数是快捷键名,快捷键映射是键盘事件key值:
|
||||
|
||||
```js
|
||||
Cancel: 3,
|
||||
Help: 6,
|
||||
Backspace: 8,
|
||||
Tab: 9,
|
||||
Clear: 12,
|
||||
Enter: 13,
|
||||
Shift: 16,
|
||||
Control: 17,
|
||||
Alt: 18,
|
||||
Pause: 19,
|
||||
CapsLock: 20,
|
||||
Escape: 27,
|
||||
Convert: 28,
|
||||
NonConvert: 29,
|
||||
Accept: 30,
|
||||
ModeChange: 31,
|
||||
' ': 32,
|
||||
PageUp: 33,
|
||||
PageDown: 34,
|
||||
End: 35,
|
||||
Home: 36,
|
||||
ArrowLeft: 37,
|
||||
ArrowUp: 38,
|
||||
ArrowRight: 39,
|
||||
ArrowDown: 40,
|
||||
Select: 41,
|
||||
Print: 42,
|
||||
Execute: 43,
|
||||
PrintScreen: 44,
|
||||
Insert: 45,
|
||||
Delete: 46,
|
||||
0: 48,
|
||||
')': 48,
|
||||
1: 49,
|
||||
'!': 49,
|
||||
2: 50,
|
||||
'@': 50,
|
||||
3: 51,
|
||||
'#': 51,
|
||||
4: 52,
|
||||
$: 52,
|
||||
5: 53,
|
||||
'%': 53,
|
||||
6: 54,
|
||||
'^': 54,
|
||||
7: 55,
|
||||
'&': 55,
|
||||
8: 56,
|
||||
'*': 56,
|
||||
9: 57,
|
||||
'(': 57,
|
||||
a: 65,
|
||||
A: 65,
|
||||
b: 66,
|
||||
B: 66,
|
||||
c: 67,
|
||||
C: 67,
|
||||
d: 68,
|
||||
D: 68,
|
||||
e: 69,
|
||||
E: 69,
|
||||
f: 70,
|
||||
F: 70,
|
||||
g: 71,
|
||||
G: 71,
|
||||
h: 72,
|
||||
H: 72,
|
||||
i: 73,
|
||||
I: 73,
|
||||
j: 74,
|
||||
J: 74,
|
||||
k: 75,
|
||||
K: 75,
|
||||
l: 76,
|
||||
L: 76,
|
||||
m: 77,
|
||||
M: 77,
|
||||
n: 78,
|
||||
N: 78,
|
||||
o: 79,
|
||||
O: 79,
|
||||
p: 80,
|
||||
P: 80,
|
||||
q: 81,
|
||||
Q: 81,
|
||||
r: 82,
|
||||
R: 82,
|
||||
s: 83,
|
||||
S: 83,
|
||||
t: 84,
|
||||
T: 84,
|
||||
u: 85,
|
||||
U: 85,
|
||||
v: 86,
|
||||
V: 86,
|
||||
w: 87,
|
||||
W: 87,
|
||||
x: 88,
|
||||
X: 88,
|
||||
y: 89,
|
||||
Y: 89,
|
||||
z: 90,
|
||||
Z: 90,
|
||||
OS: 91,
|
||||
ContextMenu: 93,
|
||||
F1: 112,
|
||||
F2: 113,
|
||||
F3: 114,
|
||||
F4: 115,
|
||||
F5: 116,
|
||||
F6: 117,
|
||||
F7: 118,
|
||||
F8: 119,
|
||||
F9: 120,
|
||||
F10: 121,
|
||||
F11: 122,
|
||||
F12: 123,
|
||||
F13: 124,
|
||||
F14: 125,
|
||||
F15: 126,
|
||||
F16: 127,
|
||||
F17: 128,
|
||||
F18: 129,
|
||||
F19: 130,
|
||||
F20: 131,
|
||||
F21: 132,
|
||||
F22: 133,
|
||||
F23: 134,
|
||||
F24: 135,
|
||||
NumLock: 144,
|
||||
ScrollLock: 145,
|
||||
VolumeMute: 181,
|
||||
VolumeDown: 182,
|
||||
VolumeUp: 183,
|
||||
';': 186,
|
||||
':': 186,
|
||||
'=': 187,
|
||||
'+': 187,
|
||||
',': 188,
|
||||
'<': 188,
|
||||
'-': 189,
|
||||
_: 189,
|
||||
'.': 190,
|
||||
'>': 190,
|
||||
'/': 191,
|
||||
'?': 191,
|
||||
'`': 192,
|
||||
'~': 192,
|
||||
'[': 219,
|
||||
'{': 219,
|
||||
'\\': 220,
|
||||
'|': 220,
|
||||
']': 221,
|
||||
'}': 221,
|
||||
"'": 222,
|
||||
'"': 222,
|
||||
Meta: 224,
|
||||
AltGraph: 225,
|
||||
Attn: 246,
|
||||
CrSel: 247,
|
||||
ExSel: 248,
|
||||
EraseEof: 249,
|
||||
Play: 250,
|
||||
ZoomOut: 251,
|
||||
```
|
||||
|
||||
26个英文字母是忽略大小写的,一个命令目前只能注册一个快捷键。不需要注册快捷键则填空字符串即可。
|
||||
|
||||
metakey与Controlkey相同,写Control即可。
|
||||
|
||||
目前第三个参数只能获得store,后续需要修改下。 0.2.0 版本第二个参数可以获得config,同时commander不从index中导出,需要使用时从config中获取。
|
||||
|
||||
最后个参数是展示名。
|
||||
|
||||
|
||||
### 右键菜单
|
||||
|
||||
右键菜单可以进行自定义:
|
||||
|
||||
```js
|
||||
// 自定义右键
|
||||
const contextMenuState = config.getContextMenuState();
|
||||
const unmountContextMenu = contextMenuState.unmountContextMenu;
|
||||
const commander = config.getCommanderRegister();
|
||||
const ContextMenu = () => {
|
||||
const handleclick = () => {
|
||||
unmountContextMenu();
|
||||
};
|
||||
const forceUpdate = useState(0)[1];
|
||||
contextMenuState.forceUpdate = () => {
|
||||
forceUpdate((pre) => pre + 1);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
left: contextMenuState.left,
|
||||
top: contextMenuState.top,
|
||||
position: 'fixed',
|
||||
background: 'rgb(24, 23, 23)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ width: '100%' }}
|
||||
onClick={() => {
|
||||
commander.exec('redo');
|
||||
handleclick();
|
||||
}}
|
||||
>
|
||||
<Button>自定义</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
contextMenuState.contextMenu = <ContextMenu></ContextMenu>;
|
||||
```
|
||||
先拿到contextMenuState,contextMenuState上有个unmountContextMenu是关闭右键菜单方法。
|
||||
|
||||
所以在点击后需要调用关闭。
|
||||
|
||||
同时上面的left和top是右键的位置。
|
||||
|
||||
另外,你还需要在组件内增加个强刷赋值给forceUpdate,用于在组件移动时进行跟随。
|
||||
|
||||
|
||||
### 函数开发
|
||||
|
||||
|
||||
|
||||
#### 函数导入
|
||||
|
||||
函数导入做成对象置入initFunctionMap即可
|
||||
|
||||
```js
|
||||
initFunctionMap: functionMap,
|
||||
```
|
||||
|
||||
#### 函数开发
|
||||
|
||||
|
||||
键名会显示出来所以键名是唯一的。
|
||||
|
||||
它的值是2个对象,一个是函数内容fn,一个是配置项config (0.10.0以上还需要传入函数名称,用于显示)。
|
||||
|
||||
config中的数组里每个配置会显示出来让用户去配置,name则是展示名字,data代表数据去哪里获取,可以选择从输入框(input),数据源(dataSource),上下文(ctx)中获取,另外还有个特殊的弹窗(modal)。
|
||||
|
||||
options中的receive表示会从args哪个键上获取该值。
|
||||
|
||||
multi代表是否允许多个选项配置。
|
||||
|
||||
dooringx-lib中写好了2个函数changeUserValue与changeUserValueRecord,第一个函数会将得到的结果做成数组,如果非multi则取第一个结果就行。而第二个函数会将结果做成对象,比如用户在数据源中选了keya,那么就会把数据源的键值对作为个对象返回。
|
||||
|
||||
|
||||
fn中,第一个ctx参数代表上下文,如果有转换函数之类,可能需要使用(比如要把第一个函数的结果导给后面的函数)
|
||||
|
||||
第二个参数next是需要运行完毕后执行的,否则事件链会一直在该函数中不退出。
|
||||
|
||||
第三个参数config就可以拿到整个config对象。
|
||||
|
||||
第四个参数args是用户填写的参数,会根据options里填写的字段进行返回。
|
||||
|
||||
第五个是eventList,可以获取整个事件链的参数。
|
||||
|
||||
第六个参数iname可以拿到用户的选择项。
|
||||
|
||||
|
||||
```js
|
||||
通用GET请求函数: {
|
||||
fn: (ctx, next, config, args, _eventList, iname) => {
|
||||
console.log(args, '参数x');
|
||||
const userSelect = iname.data;
|
||||
const urlVal = changeUserValue(
|
||||
userSelect['请求url'],
|
||||
args,
|
||||
'_url',
|
||||
config,
|
||||
ctx
|
||||
); // input datasource ctx //datasource会去取值 ,ctx取ctx上字段
|
||||
const paramSource = changeUserValueRecord(
|
||||
// 设定只能从datasource或者ctx里取
|
||||
userSelect['请求参数'],
|
||||
args,
|
||||
'_origin',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
const ctxVal = changeUserValue(
|
||||
userSelect['返回上下文字段'],
|
||||
args,
|
||||
'_ctx',
|
||||
config,
|
||||
ctx
|
||||
);
|
||||
// 检查参数是否存在
|
||||
// 都是数组,非multi则取第一个。
|
||||
const url = urlVal[0];
|
||||
if (!url) {
|
||||
return next();
|
||||
}
|
||||
const ctxKey = ctxVal[0];
|
||||
|
||||
axios
|
||||
.get(url, {
|
||||
params: {
|
||||
...paramSource,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
const data = res.data;
|
||||
ctx[ctxKey] = data;
|
||||
next();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
next();
|
||||
});
|
||||
},
|
||||
config: [
|
||||
{
|
||||
name: '请求url',
|
||||
data: ['dataSource', 'ctx', 'input'],
|
||||
options: {
|
||||
receive: '_url',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '请求参数',
|
||||
data: ['dataSource', 'ctx'],
|
||||
options: {
|
||||
receive: '_origin',
|
||||
multi: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '返回上下文字段',
|
||||
data: ['input'],
|
||||
options: {
|
||||
receive: '_ctx',
|
||||
multi: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
name: '通用GET请求函数'
|
||||
},
|
||||
```
|
||||
|
||||
#### 时机与函数装载
|
||||
|
||||
如果有需要,一般使用:
|
||||
|
||||
```js
|
||||
eventCenter.manualUpdateMap(cur, displayName, arr);
|
||||
```
|
||||
|
||||
manualUpdateMap第一个是时机名,第二个是展示名,第三个是用户选择。
|
||||
|
||||
更新事件中心后还需要更新store,结果以store为准。
|
258
script/translate.js
Normal file
258
script/translate.js
Normal file
@@ -0,0 +1,258 @@
|
||||
require('dotenv').config();
|
||||
const https = require('https');
|
||||
const md5 = require('md5');
|
||||
const axios = require('axios');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const apiUrl = 'http://api.fanyi.baidu.com/api/trans/vip/translate';
|
||||
const appid = process.env.APPID;
|
||||
const secret = process.env.SECRET;
|
||||
|
||||
const requestTranslate = (q) => {
|
||||
const salt = Math.random();
|
||||
const sign = md5(appid + q + salt + secret);
|
||||
const params = {
|
||||
q,
|
||||
from: 'zh',
|
||||
to: 'en',
|
||||
salt,
|
||||
appid,
|
||||
sign,
|
||||
};
|
||||
return axios.get(apiUrl, {
|
||||
params,
|
||||
});
|
||||
};
|
||||
// requestTranslate(qs).then((v) => {
|
||||
// console.log(v.data.trans_result);
|
||||
// });
|
||||
const concatResult = (result) => {
|
||||
let line = 0;
|
||||
let navstart = 0;
|
||||
if (result[0].src === '---') {
|
||||
//查找末尾的---
|
||||
const len = result.length;
|
||||
for (let i = 1; i < len; i++) {
|
||||
//查找nav起始
|
||||
let cur = result[i].src;
|
||||
if (cur === 'nav:') {
|
||||
navstart = i;
|
||||
}
|
||||
if (cur === '---') {
|
||||
line = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.forEach((v, i) => {
|
||||
if (i === 0) {
|
||||
finalResult = i < line ? v.src : v.dst;
|
||||
} else {
|
||||
//替换Title:
|
||||
if (v.src.startsWith('title:') && v.dst.startsWith('Title:')) {
|
||||
v.dst = v.dst.replace('Title:', 'title:');
|
||||
}
|
||||
if (i > navstart && i < line) {
|
||||
finalResult = finalResult + `\r\n ` + v.dst;
|
||||
} else {
|
||||
finalResult = finalResult + '\r\n' + v.dst;
|
||||
}
|
||||
}
|
||||
});
|
||||
return finalResult;
|
||||
};
|
||||
|
||||
// 翻译changelog
|
||||
const changelogPath = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'ChangeLog',
|
||||
'index.md'
|
||||
);
|
||||
const changelogEnPath = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'ChangeLog',
|
||||
'index.en.md'
|
||||
);
|
||||
|
||||
const changelogTranslate = async () => {
|
||||
const changelog = fs.readFileSync(changelogPath).toString();
|
||||
const data = await requestTranslate(changelog);
|
||||
const result = concatResult(data.data.trans_result);
|
||||
fs.removeSync(changelogEnPath);
|
||||
fs.writeFileSync(changelogEnPath, result);
|
||||
};
|
||||
changelogTranslate();
|
||||
|
||||
//----------------文本翻译-----------
|
||||
|
||||
const textConcatResult = (result) => {
|
||||
let line = 0;
|
||||
let navstart = 0;
|
||||
const passline = [];
|
||||
let code = false;
|
||||
const reg = /(#)\1+/g;
|
||||
if (result[0].src === '---') {
|
||||
const len = result.length;
|
||||
for (let i = 1; i < len; i++) {
|
||||
//查找nav起始
|
||||
let cur = result[i].src;
|
||||
if (cur === 'nav:' && line === 0) {
|
||||
navstart = i;
|
||||
}
|
||||
if (cur === '---' && line === 0) {
|
||||
line = i;
|
||||
}
|
||||
//查找代码块
|
||||
if (cur.startsWith('```')) {
|
||||
if (code === true) {
|
||||
code = false;
|
||||
} else {
|
||||
code = true;
|
||||
}
|
||||
}
|
||||
if (code === true) {
|
||||
passline.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
result.forEach((v, i) => {
|
||||
if (i === 0) {
|
||||
finalResult = i < line ? v.src : v.dst;
|
||||
} else {
|
||||
//替换Title:
|
||||
if (v.src.startsWith('title:') && v.dst.startsWith('Title:')) {
|
||||
v.dst = v.dst.replace('Title:', 'title:');
|
||||
}
|
||||
//替换#
|
||||
if (reg.exec(v.dst)) {
|
||||
v.dst = v.dst.replace(reg, '$& ');
|
||||
}
|
||||
if (i > navstart && i < line) {
|
||||
finalResult = finalResult + `\r\n ` + v.dst;
|
||||
} else {
|
||||
if (passline.includes(i)) {
|
||||
finalResult = finalResult + '\r\n' + v.src;
|
||||
} else {
|
||||
//替换##
|
||||
finalResult = finalResult + '\r\n' + v.dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return finalResult;
|
||||
};
|
||||
const faqPath = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'FAQ',
|
||||
'index.md'
|
||||
);
|
||||
|
||||
const faqEnPath = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'FAQ',
|
||||
'index.en.md'
|
||||
);
|
||||
const faqtranslate = async () => {
|
||||
await toTranslateText(faqPath, faqEnPath);
|
||||
};
|
||||
const guidePath1 = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'Guide',
|
||||
'index.md'
|
||||
);
|
||||
|
||||
const guidePathEnPath1 = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'Guide',
|
||||
'index.en.md'
|
||||
);
|
||||
const guidePath2 = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'Guide',
|
||||
'basic.md'
|
||||
);
|
||||
|
||||
const guidePathEnPath2 = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'Guide',
|
||||
'basic.en.md'
|
||||
);
|
||||
const guidePath3 = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'Guide',
|
||||
'develop.md'
|
||||
);
|
||||
|
||||
const guidePathEnPath3 = path.resolve(
|
||||
process.cwd(),
|
||||
'packages',
|
||||
'dooringx-dumi-doc',
|
||||
'docs',
|
||||
'Guide',
|
||||
'develop.en.md'
|
||||
);
|
||||
const guidetranslate = async () => {
|
||||
await toTranslateText(guidePath1, guidePathEnPath1);
|
||||
await toTranslateText(guidePath2, guidePathEnPath2);
|
||||
await toTranslateText(guidePath3, guidePathEnPath3);
|
||||
};
|
||||
|
||||
const toTranslateText = async (originPath, targetPath) => {
|
||||
const txt = fs.readFileSync(originPath).toString();
|
||||
if (originPath === guidePath3) {
|
||||
const k = txt.split('### 命令开发');
|
||||
const data1 = await requestTranslate(k[0]);
|
||||
const result1 = textConcatResult(data1.data.trans_result);
|
||||
const data2 = await requestTranslate(k[1]);
|
||||
const result2 = textConcatResult(data2.data.trans_result);
|
||||
fs.removeSync(targetPath);
|
||||
const final = result1 + '\r\n' + result2;
|
||||
fs.writeFileSync(targetPath, final);
|
||||
} else {
|
||||
try {
|
||||
const data = await requestTranslate(txt);
|
||||
if (!data.data || !data.data.trans_result) {
|
||||
console.log('baidu调用失败:');
|
||||
console.log(data.data);
|
||||
return;
|
||||
}
|
||||
const result = textConcatResult(data.data.trans_result);
|
||||
fs.removeSync(targetPath);
|
||||
fs.writeFileSync(targetPath, result);
|
||||
} catch (e) {
|
||||
console.log('调用失败,可能文章太长');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
faqtranslate,
|
||||
guidetranslate,
|
||||
};
|
3
script/translateText.js
Normal file
3
script/translateText.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { faqtranslate, guidetranslate } = require('./translate');
|
||||
faqtranslate();
|
||||
guidetranslate();
|
45
yarn.lock
45
yarn.lock
@@ -4044,6 +4044,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mathjax/-/mathjax-0.0.36.tgz#18cf766f88ac0cd4e7ee8282b1286049bb6aa682"
|
||||
integrity sha512-TqDJc2GWuTqd/m+G/FbNkN+/TF2OCCHvcawmhIrUaZkdVquMdNZmNiNUkupNg9qctorXXkVLVSogZv1DhmgLmg==
|
||||
|
||||
"@types/md5@^2.3.1":
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.1.tgz#010bcf3bb50a2cff3a574cb1c0b4051a9c67d6bc"
|
||||
integrity sha512-OK3oe+ALIoPSo262lnhAYwpqFNXbiwH2a+0+Z5YBnkQEwWD8fk5+PIeRhYA48PzvX9I4SGNpWy+9bLj8qz92RQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mdast@^3.0.0":
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
|
||||
@@ -5558,6 +5565,13 @@ axe-core@^4.0.2:
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.3.tgz#2a3afc332f0031b42f602f4a3de03c211ca98f72"
|
||||
integrity sha512-pXnVMfJKSIWU2Ml4JHP7pZEPIrgBO1Fd3WGx+fPBsS+KRGhE4vxooD8XBGWbQOIVSZsVK7pUDBBkCicNu80yzQ==
|
||||
|
||||
axios@^0.24.0:
|
||||
version "0.24.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
|
||||
integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.4"
|
||||
|
||||
axobject-query@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
||||
@@ -6390,6 +6404,11 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
charenc@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
|
||||
|
||||
chokidar@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
|
||||
@@ -7116,6 +7135,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.3:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
crypt@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
|
||||
|
||||
crypto-browserify@^3.11.0:
|
||||
version "3.12.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
|
||||
@@ -7730,6 +7754,11 @@ dot-prop@^6.0.1:
|
||||
dependencies:
|
||||
is-obj "^2.0.0"
|
||||
|
||||
dotenv@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||
|
||||
dumi-assets-types@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dumi-assets-types/-/dumi-assets-types-1.0.0.tgz#d5368cb11045b203bf1ef1080e553b2287a2ec81"
|
||||
@@ -8819,6 +8848,11 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.14.4:
|
||||
version "1.14.5"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381"
|
||||
integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==
|
||||
|
||||
for-each@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
||||
@@ -10223,7 +10257,7 @@ is-boolean-object@^1.1.0:
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-buffer@^1.1.5:
|
||||
is-buffer@^1.1.5, is-buffer@~1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||
@@ -12471,6 +12505,15 @@ md5.js@^1.3.4:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
md5@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
|
||||
integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
|
||||
dependencies:
|
||||
charenc "0.0.2"
|
||||
crypt "0.0.2"
|
||||
is-buffer "~1.1.6"
|
||||
|
||||
mdast-util-definitions@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2"
|
||||
|
Reference in New Issue
Block a user