65cd8246d7 | ||
---|---|---|
.circleci | ||
.dumi/theme | ||
.github/ISSUE_TEMPLATE | ||
api | ||
config | ||
docs | ||
examples | ||
locale | ||
ot-server | ||
packages | ||
plugins | ||
scripts | ||
.editorconfig | ||
.fatherrc.ts | ||
.gitignore | ||
.prettierignore | ||
.prettierrc | ||
LICENSE | ||
README.md | ||
README.zh-CN.md | ||
lerna.json | ||
package.json | ||
tsconfig.json | ||
typings.d.ts |
README.md
am-editor
A rich text editor that supports collaborative editing, you can freely use React, Vue and other front-end common libraries to extend and define plug-ins.
中文 · Demo · Documentation · Plugins · QQ-Group 907664876 ·
Thanks to Google Translate
Vue2
example https://github.com/zb201307/am-editor-vue2
Vue3
example https://github.com/yanmao-cc/am-editor/tree/master/examples/vue
React
example https://github.com/yanmao-cc/am-editor/tree/master/examples/react
Vue2 DEMO
https://github.com/yanmao-cc/am-editor-demo-vue2
Vue2 Nuxt DEMO
https://github.com/yanmao-cc/am-editor-nuxt
Fundamental
Use the contenteditable
attribute provided by the browser to make a DOM node editable:
<div contenteditable="true"></div>
So its value looks like this:
<div data-element="root" contenteditable="true">
<p>Hello world!</p>
<p><br /></p>
</div>
Of course, in some scenarios, for the convenience of operation, an API that converts to a JSON type value is also provided:
[
'div', // node name
// All attributes of the node
{
'data-element': 'root',
contenteditable: 'true',
},
// child node 1
[
// child node name
'p',
// Child node attributes
{},
// child node of byte point
'Hello world!',
],
// child node 2
['p', {}, ['br', {}]],
];
The editor relies on the input capabilities provided by the contenteditable attribute and cursor control capabilities. Therefore, it has all the default browser behaviors, but the default behavior of the browser has different processing methods under different browser vendors' implementations, so we intercept most of its default behaviors and customize them.
For example, during the input process, beforeinput
, input
, delete, enter, and shortcut keys related to mousedown
, mouseup
, click
and other events will be intercepted and customized processing will be performed.
After taking over the event, what the editor does is to manage all the child nodes under the root node based on the contenteditable
property, such as inserting text, deleting text, inserting pictures, and so on.
In summary, the data structure in editing is a DOM tree structure, and all operations are performed directly on the DOM tree, not a typical MVC mode that drives view rendering with a data model.
Node constraints
In order to manage nodes more conveniently and reduce complexity. The editor abstracts node attributes and functions, and formulates four types of nodes, mark
, inline
, block
, and card
. They are composed of different attributes, styles, or html
structures, and use the schema
uniformly. They are constrained.
A simple schema
looks like this:
{
name:'p', // node name
type:'block' // node type
}
In addition, you can also describe attributes, styles, etc., such as:
{
name:'span', // node name
type:'mark', // node type
attributes: {
// The node has a style attribute
style: {
// Must contain a color style
color: {
required: true, // must contain
value:'@color' // The value is a color value that conforms to the css specification. @color is the color validation defined in the editor. Here, methods and regular expressions can also be used to determine whether the required rules are met
}
},
// Optional include a test attribute, its value can be arbitrary, but it is not required
test:'*'
}
}
The following types of nodes conform to the above rules:
<span style="color:#fff"></span>
<span style="color:#fff" test="test123" test1="test1"></span>
<span style="color:#fff;background-color:#000;"></span>
<span style="color:#fff;background-color:#000;" test="test123"></span>
But except that color and test have been defined in schema
, other attributes (background-color, test1) will be filtered out by the editor during processing.
The nodes in the editable area have four types of combined nodes of mark
, inline
, block, and
cardthrough the
schemarule. They are composed of different attributes, styles or
html` structures. Certain constraints are imposed on nesting.
Features
- Out of the box, it provides dozens of rich plug-ins to meet most needs
- High extensibility, in addition to the basic plug-in of
mark
, inlineand
blocktype
, we also providecard
component combined withReact
,Vue
and other front-end libraries to render the plug-in UI - Rich multimedia support, not only supports pictures, audio and video, but also supports insertion of embedded multimedia content
- Support Markdown syntax
- Support internationalization
- The engine is written in pure JavaScript and does not rely on any front-end libraries. Plug-ins can be rendered using front-end libraries such as
React
andVue
. Easily cope with complex architecture - Built-in collaborative editing program, ready to use with lightweight configuration
- Compatible with most of the latest mobile browsers
Plugins
Getting Started
Installation
The editor consists of engine
, toolbar
, and plugin
. Engine
provides us with core editing capabilities.
Install engine package using npm or yarn
$ npm install @aomao/engine
# or
$ yarn add @aomao/engine
Usage
We follow the convention to output a Hello word!
import React, { useEffect, useRef, useState } from 'react';
import Engine, { EngineInterface } from '@aomao/engine';
const EngineDemo = () => {
//Editor container
const ref = useRef<HTMLDivElement | null>(null);
//Engine instance
const [engine, setEngine] = useState<EngineInterface>();
//Editor content
const [content, setContent] = useState<string>('<p>Hello word!</p>');
useEffect(() => {
if (!ref.current) return;
//Instantiate the engine
const engine = new Engine(ref.current);
//Set the editor value
engine.setValue(content);
//Listen to the editor value change event
engine.on('change', () => {
const value = engine.getValue();
setContent(value);
console.log(`value:${value}`);
});
//Set the engine instance
setEngine(engine);
}, []);
return <div ref={ref} />;
};
export default EngineDemo;
Plugins
Import @aomao/plugin-bold
bold plug-in
import Bold from '@aomao/plugin-bold';
Add the Bold
plugin to the engine
//Instantiate the engine
const engine = new Engine(ref.current, {
plugins: [Bold],
});
Card
A card is a separate area in the editor. The UI and logic inside the card can be customized using React, Vue or other front-end libraries to customize the rendering content, and finally mount it to the editor.
Import the @aomao/plugin-codeblock
code block plugin. The Language drop-down box
of this plugin is rendered using React
, so there is a distinction. Vue3
uses @aomao/plugin-codeblock-vue
import CodeBlock, { CodeBlockComponent } from '@aomao/plugin-codeblock';
Add the CodeBlock
plugin and CodeBlockComponent
card component to the engine
//Instantiate the engine
const engine = new Engine(ref.current, {
plugins: [CodeBlock],
cards: [CodeBlockComponent],
});
The CodeBlock
plugin supports markdown
by default. Enter the code block syntax ````javascript` at the beginning of a line in the editor to trigger it after pressing Enter.
Toolbar
Import the @aomao/toolbar
toolbar. Due to the complex interaction, the toolbar is basically rendered using React
+ Antd
UI components, while Vue3
uses @aomao/toolbar-vue
Except for UI interaction, most of the work of the toolbar is just to call the engine to execute the corresponding plug-in commands after different button events are triggered. In the case of complicated requirements or the need to re-customize the UI, it is easier to modify after the fork.
import Toolbar, { ToolbarPlugin, ToolbarComponent } from '@aomao/toolbar';
Add the ToolbarPlugin
plugin and ToolbarComponent
card component to the engine, which allows us to use the shortcut key /
in the editor to wake up the card toolbar
//Instantiate the engine
const engine = new Engine(ref.current, {
plugins: [ToolbarPlugin],
cards: [ToolbarComponent],
});
Rendering toolbar, the toolbar has been configured with all plug-ins, here we only need to pass in the plug-in name
return (
...
{
engine && (
<Toolbar
engine={engine}
items={[
['collapse'],
[
'bold',
],
]}
/>
)
}
...
)
For more complex toolbar configuration, please check the document https://editor.aomao.com/config/toolbar
Collaborative editing
Use the MutationObserver
to monitor the mutation of the html
structure in the editable area (contenteditable root node) to reverse infer OT. Connect to ShareDB through Websocket
, and then use commands to add, delete, modify, and check the data saved in ShareDB.
Interactive mode
Each editor acts as a Client through WebSocket
and Server Communication and exchange of data in json0
format generated by the editor.
The server will keep a copy of the html
structure data in the json
format. After receiving the instructions from the client, it will modify the data, and finally forward it to each client.
Before enabling collaborative editing, we need to configure Client and Server
The server is a NodeJs
environment, and a network service built using express
+ WebSocket
.
Example
In the example, we have a relatively basic client code
View the complete React example
View the complete example of Vue3
View the complete example of Vue2
//Instantiate the collaborative editing client and pass in the current editor engine instance
const otClient = new OTClient(engine);
//Connect to the collaboration server, `demo` is the same as the server document ID
otClient.connect(
`ws://127.0.0.1:8080${currentMember ? '?uid=' + currentMember.id : ''}`,
'demo',
);
Project icon
Development
React
Need to install dependencies in `am-editor
//After the dependencies are installed, you only need to execute the following commands in the root directory
yarn start
packages
engine and toolbarplugins
all pluginsapi
supports api access required by some plugins. By default, https://editor.aomao.com is used as the api serviceot-server
collaborative server. Start: yarn dev
Visit localhost:7001 after startup
Vue
Just enter the examples/vue directory to install the dependencies
//After the dependencies are installed, execute the following commands in the examples/vue directory
yarn serve
In the Vue runtime environment, the default is the installed code that has been published to npm. If you need to modify the code of the engine or plug-in and see the effect immediately, we need to do the following steps:
- Delete the examples/vue/node_modules/@aomao folder
- Delete the examples/vue/node_modules/vue folder. Because there are plugins that depend on Vue, the Vue package will be installed in the project root directory. If you do not delete the Vue package in examples/vue, and the Vue package of the plugin is not in the same environment, the plugin cannot be loaded
- Execute and install all dependent commands in the root directory of am-editor, for example:
yarn
- Finally restart in examples/vue
No back-end API is configured in the Vue
case. For details, please refer to React
and api
to set reverse proxy
Contribution
Thanks pleasedmi、Elena211314、zb201307 for donation