Photo credit: unsplash.com/photos/b18T…

preface

The purpose of this post is to document my first implementation of a very simple rich text editor that uses Webpack and TypeScript.

Project introduction

Project directory file

, | | - rich text editor -- README. Md, | -- index. HTML, | -- package - lock. Json, | -- package. Json, | -- tsconfig. Json, | -- webpack. Build. Config. Js, | -- webpack. Config. Js, | - dist, / / packaging generated file | | -- yangEditor. Js, | - lib, | | - index. The CSS, | |-- index.js, | |-- yangEditor.ts, |-- src, |-- index.js,Copy the code

Project installation dependencies

"clean-webpack-plugin": "^ 3.0.0"."css-loader": "^ 5.2.0." "."html-webpack-plugin": "^ 5.3.1"."style-loader": "^ 2.0.0." "."ts-loader": "^ 8.1.0"."typescript": "^ holdings"."webpack": "^ 5.30.0"."webpack-cli": "^ 4.6.0"."webpack-dev-server": "^ 3.11.2"
Copy the code

webpack.config.jsDevelopment environmentWebpackconfiguration

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
    mode: "development".entry: "./src/index.js".devServer: {
        port: 8089.// Set the port number
    },

    module: {
        rules: [{test: /\.ts$/,
                use: "ts-loader".exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: ["style-loader"."css-loader"]]}},plugins: [
        new HtmlWebpackPlugin({
            template: "./index.html"}})]Copy the code

webpack.build.config.jsProduction environmentalWebpackconfiguration

const path = require("path")

module.exports = {
    mode: "production".entry: "./lib/index.js".output: {
        filename: "yangEditor.js".path: path.resolve(__dirname, "./dist"),
        libraryTarget: 'umd'
    },
    
    module: {
        rules: [{test: /\.ts$/,
                use: "ts-loader".exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: ["style-loader"."css-loader"[}]}Copy the code

Project run command

"scripts": {
		"dev": "webpack serve --open Chrome.exe"."build": "webpack --config webpack.build.config.js"
	},
Copy the code

document.execCommand

Document. ExecCommand obsolete

When an HTML document is switched to design mode, document exposes the execCommand method, which allows commands to be run to manipulate elements in the editable content area, and is used to implement the rich text editor in this project.

grammar

  • bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument), the return value is oneBooleanIf it isfalsesaidOperation not supportedorHas not been enabled.

parameter

  • aCommandName: a DOMString, the name of the command.
  • aShowDefaultUI: Whether to display the user interface. The value is usually false.
  • aValueArgument: Some commands (such as insertImage) require additional arguments (insertImage needs to provide the URL to insert the image), which defaults to null.

Simple rich text concrete implementation

yangEditor.ts

The file is in lib/ yangeditor.ts

First we create a class that passes in a DOM node string to get the DOM node.

export default class YangEditor {
	constructor(dom: string) {
		if(! dom) {throw new Error('Please pass in the DOM node!! ');
		}
		let editWrapper: HTMLElement = document.getElementById(dom); }}Copy the code

The Command instruction

Instructions to set the title, set the font color, set the font bold.

// The instruction array
const actions = [
	{
		command: 'formatblock'.values: [{text: '- Set the title size -'.value: 'selected' },
			{ text: 'H1 title'.value: 'h1' },
			{ text: 'H2 heading'.value: 'h2' },
			{ text: 'H3 title'.value: 'h3' },
			{ text: 'H4 title'.value: 'h4' },
			{ text: 'the H5 title'.value: 'h5'},],}, {command: 'forecolor'.values: [{text: '- Set font color -'.value: 'selected' },
			{ text: 'red'.value: 'red' },
			{ text: 'blue'.value: 'blue' },
			{ text: 'green'.value: 'green' },
			{ text: 'black'.value: 'black'},],}, {command: 'bold'.values: [{text: '- Set font bold -'.value: 'selected' },
			{ text: 'bold'.value: ' '},],},];Copy the code

createDOMnode

// Create a DOM nodeprivate createDOM(type: string, className? : string): HTMLElement {let dom = document.createElement(type);
		dom.className = className || ' ';
		return dom;
	}
Copy the code

Dynamically createselectThe label

// Create a select node
	private createSelectDOM(commandItem: ActionsItem): HTMLSelectElement {
		let select = document.createElement('select');
		commandItem.values.forEach((item) = > {
			select.add(new Option(item.text, item.value));
			select.id = `${commandItem.command}`;
		});
		
		select.onchange = () = > {
                       // Select the onchange event tag to call the execCommand method
			this.execCommand(commandItem.command, select.options[select.selectedIndex].value);
		};
		return select;
	}
Copy the code

usedocument.execCommandmethods

Invoke the onChange event on the select tag and call the execCommand method.

private execCommand(cmd: string, value: string): void {
		//execCommand bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
		// aCommandName A DOMString, the name of the command.
		//aShowDefaultUI a Boolean indicating whether to display the user interface, usually false
		Some commands (such as insertImage) require additional arguments (insertImage requires the url to insert the image), which defaults to null.
		document.execCommand(cmd, false, value);
	}
Copy the code

Clicking outside the edit area still works

     let range: Range = null; // To cache Range objects

     // Move the mouse over the editing area
     editContent.onmousemove = _.debounce(() = > {
        let selection = window.getSelection();
        if(! selection.isCollapsed) { range = selection.getRangeAt(0); // Return a reference to the specified Range that the selection contains}},100);
        
        
        // Create a select node
	private createSelectDOM(commandItem: ActionsItem): HTMLSelectElement {
		let select = document.createElement('select');
		commandItem.values.forEach((item) = > {
			select.add(new Option(item.text, item.value));
			select.id = `${commandItem.command}`;
		});

		// Select tag onchange event
		select.onchange = () = > {
			let selection = window.getSelection();

			selection.removeRange(selection.getRangeAt(0)); // Remove the Range object from the selection.

			selection.addRange(range); // A Range object will be selected.

			this.execCommand(commandItem.command, select.options[select.selectedIndex].value);
		};
		return select;
	}
Copy the code

Method of use

import { YangEditor } from ".. /lib/index"
new YangEditor("yangEdit")
Copy the code

Implementation effect

Program source code

Editor source address

The resources

MDN execCommand