Some time ago I was looking for some information about rich text editors, and I found this project named Pell. It is a WYSIWYG text editor. Although its function is very simple, it is surprisingly only 1KB in size. The project’s core file, pel.js, is only 130 lines long, and even with the rest, the total js count is less than 200 lines. This intrigued me and I decided to see how its source code did this.

The main code of the project is in the pel.js file, its structure is very simple, the realization of the main function depends on the following several parts

  • actionsobject
  • exec()function
  • init()function

Document.execCommand()

Starting with the simplest part, the exec() function has just three lines:

export const exec = (command, value = null) => {
    document.execCommand(command, false, value);
};Copy the code

It wraps a simple wrapper around Document.execcommand (), which is the core of the editor, with the following syntax

bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)Copy the code
  • aCommandNameIs a string representing the command you want to execute, for example: bold ‘bold’, create a link ‘createLink’, change the fontSize ‘fontSize’, etc
  • aShowDefaultUIWhether to display the default user interface
  • aValueArgumentSome commands require additional input, such as inserting images or providing addresses for links

Note: In Chrome, changing the value of aShowDefaultUI did not affect the stackOverflow issue. This parameter is from an older version of IE, so set it to false by default.

The actions object

The file defines an object named Actions, which corresponds to the row of buttons on the toolbar below. Each child object in Actions holds a button property.

Part code:

const actions = {
    bold: {
        icon: '<b>B</b>',
        title: 'Bold',
        result: () => exec('bold')
    },
    italic: {
        icon: '<i>I</i>',
        title: 'Italic',
        result: () => exec('italic')
    },
    underline: {
        icon: '<u>U</u>',
        title: 'Underline',
        result: () => exec('underline')
    },
    // …
}Copy the code

In this code, the three object attributes named bold, italic, underline are displayed, corresponding to the bold, italic, underline buttons in the front of the toolbar. It can be seen that they have the same structure, with the following three attributes:

  • icon: How to display in the toolbar
  • title: Title
  • result: a function that assigns the button as a click event, calling the previously mentionedexec()Function to manipulate text

Now that you have an Actions object, how do you use it? This takes us to the init() function, which, according to certain rules, selects elements from the Actions object to form an array, each of which generates a button. Settings. actions in the code below is this array, each element of which corresponds to a button displayed on the toolbar. The settings.actions generation rules are explained later.

// Settings.actions.foreach (Action => {// Create a new button element const button = document.createElement('button') // Add CSS styles to the button. Button.classname = settings.classes.button // Display the icon property as content Button.title = action.title // Assign the result attribute to the button as the click event button.onclick = action.result // add the created button to the toolbar actionbar.appendChild(button) })Copy the code

This generates a toolbar button for each element in the array.

Init () initializes the function

When you want to use the Pell editor, you simply call the init() function to initialize one. It takes a setting object as an argument, which contains properties like this:

  • element: DOM element for the editor
  • styleWithCSS: will be used when true<span style="font-weight: bold;" ></span>Instead of<b></b>
  • actions
  • onChange

The most important of these is Actions, which is an array containing a list of buttons that you want to display in the toolbar.

The actions array can have these elements:

  • A string
  • anameProperty object
  • One object, nonenameProperty, but has the necessary properties to generate a buttonicon.result
actions: [ 'bold', 'underline', 'italic', { name: 'image', result: () => { const url = window.prompt('Enter the image URL') if (url) window.pell.exec('insertImage', ensureHTTP(url)) } }, / /...Copy the code

The actions parameter is combined with the actions object defined in pel.js in init(). You can use the actions object as a default setting by looking at the following code:

// Init () function settings.actions = settings.actions? Settings.actions. map(Action => {if (typeof Action === 'string') return actions[action] // If the action passed in as a parameter already exists in default Settings, Else if (Actions [action.name]) {return {... actions[action.name], ... action } } return action }) : Object.keys(actions).map(action => actions[action])Copy the code

If the parameter object setting does not contain an Actions array, the actions object defined previously is initialized by default.

Another important part of the init() function is to create an editable region, where a div element is created and its contentEditable property is set to true, allowing the previously mentioned document.execcommand () command to be used.

Settings.element.content = document.createElement('div') // make div editable Settings. The element. The content. contentEditable = true Settings. The element. The content. the className = Settings. Classes. The content / / when the user input, Update the Settings of the corresponding portions of the page. The element. The content. the oninput = event = > Settings. The onChange (event. Target. InnerHTML) settings.element.content.onkeydown = preventTab settings.element.appendChild(settings.element.content)Copy the code

The process to arrange

Finally, take “insert link” as an example to sort out the whole editor process:

When init() is called, add the following item to the action array of the argument object

{
    name: 'link',
    result: () => {
        const url = window.prompt('Enter the link URL')
        if (url) window.pell.exec('createLink', ensureHTTP(url))
    }
}Copy the code

2. During init(), we check if the link property exists in the actions we have defined. The property is checked to be present

Link: {icon: '🔗', title: 'link ', result: () => { const url = window.prompt('Enter the link URL') if (url) exec('createLink', url) } }Copy the code

Since result is the parameter passed in, replace the default value in the Link object with result, and place the modified Link object into the settings.actions array.

3. Iterate through the settings.actions array to generate the toolbar, as one of which the Link object generates an insert link button. The Result property becomes its click event.

4. After clicking the insert link button, it will ask you to enter a URL and then call exec(‘createLink’, URL) to insert the link in the edit area.

The flow of other buttons in the editor is similar.

This completes most of the Pell editor, leaving the rest to look at the source code. After all, the project code is not long, which makes it a good starting point for a text editor.


The last article of 2017. Bye, 2017.

Original address of this article