Editor.js

/**
 * Provide a word-processor-like control.
 * <br><br>
 * This class is a wrapper around the public CKEditor 5 utility.  It is intended that this class be used exclusively
 * rather than any of the raw CKEditor API.  It provides a higher-level and more convenient API.
 * <br><br>
 * Please refer to CKEditor documentation on <a href="https://ckeditor.com/docs">https://ckeditor.com/docs</a> for more information.
 */
class Editor {

    constructor(parentId) {
        this.parentId = parentId;
        this.editorId = parentId + "-editor";
        this.toolbarId = parentId + "-toolbar";
    }

    async initialize() {
        const self = this;

        const parentDiv = document.getElementById(this.parentId);
        parentDiv.style.border = '1px solid black';
        parentDiv.style.backgroundColor = 'white';

        // Create toolbar div
        const toolbarDiv = document.createElement('div');
        toolbarDiv  .id = this.toolbarId;
        parentDiv.appendChild(toolbarDiv  );

        // Create editor div
        const editorDiv = document.createElement('div');
        editorDiv.id = this.editorId;
        const parentHeight = parseInt(parentDiv.style.height);
        editorDiv.style.height = (parentHeight - 41) + 'px';
        parentDiv.appendChild(editorDiv);

        return new Promise(function (resolve, reject) {
            DecoupledEditor
                .create( editorDiv, {
                    fontSize: {
                        options: [
                            9,
                            11,
                            13,
                            'default',
                            17,
                            19,
                            21,
                            23,
                            25
                        ],
                        supportAllValues: true
                    }
                } )
                .then( editor => {
                    toolbarDiv.appendChild(editor.ui.view.toolbar.element);
                    resolve( editor );
                } )
                .catch( error => {
                    console.error( error );
                    reject( undefined );
                } );
        });
    }

    /**
     * Creates a new instance of the Editor class and initializes it.
     *
     * @param {string} parentId - The ID of the editor div.
     * @return {Promise<Editor>} A promise that resolves to the initialized Editor instance.
     */
    static async create(parentId) {
        const ins = new Editor(parentId);
        ins.editor = await ins.initialize();
        Editor.addEditor(ins);
        return ins;
    }

    /**
     * Retrieves the HTML content from the editor.
     *
     * @return {string} The HTML content of the editor.
     */
    getHtml() {
        return this.editor.getData();
    }

    /**
     * Retrieves the text (not HTML) content of the editor.
     *
     * @returns {string}
     */
    getText() {
        return Utils.htmlToText(this.editor.getData());
    }

    /**
     * Sets the HTML content in the editor.
     *
     * @param html
     */
    setHtml(html) {
        this.editor.setData(html);
    }

    /**
     * Clears the content of the editor.
     */
    clear() {
        this.editor.setData('');
    }

    /**
     * Sets the editor to read-only mode.
     */
    readOnly() {
        this.editor.enableReadOnlyMode('xx');
    }

    /**
     * Sets the editor to read-write mode.
     */
    readWrite() {
        this.editor.disableReadOnlyMode('xx');
    }

    /**
     * Dispose of the editor when it is no longer needed.
     * Generally, Kiss handles this automatically.
     */
    dispose() {
        this.editor.destroy().then(() => {
            const parentDiv = document.getElementById(this.parentId);

            // Check if the first child exists and remove it
            const toolbar = document.getElementById(this.toolbarId);
            if (toolbar) 
                parentDiv.removeChild(toolbar);

            // Check if the second child exists and remove it
            const editor = document.getElementById(this.editorId);
            if (editor) 
                parentDiv.removeChild(editor);
        } ).catch(error => {
            console.log(error);
        });
    }
    
    /**
     * Create a new editor context.
     */
    static newEditorContext() {
        Editor.editorContext.push([]);
    }

    /**
     * Add an editor to the current context.
     *
     * @param editor
     */
    static addEditor(editor) {
        const cc = Editor.editorContext[Editor.editorContext.length - 1];
        cc.push(editor);
    }

    /**
     * Destroy all editors in last context and remove the context
     */
    static popEditorContext() {
        const c = Editor.editorContext.pop();
        if (c)
            for (let i = 0; i < c.length; i++)
                c[i].dispose();
    }

    /**
     * Destroys all popup and screen editors that have been created
     */
    static popAllEditorContexts() {
        while (Editor.editorContext.length)
            Editor.popEditorContext();
    }

    /**
     * Print the contents of the editor
     */
    printEditorContent() {
        const printWindow = window.open('', '_blank');
        printWindow.document.write('<html lang="en"><head><title>Print</title></head><body>');
        printWindow.document.write(this.editor.getData()); // Get the data from CKEditor
        printWindow.document.write('</body></html>');
        printWindow.document.close();

        printWindow.focus();
        printWindow.print();

        printWindow.close();
    }
}

Editor.editorContext = [];      //  An array of arrays.  The outer array represents a stack of contexts.
                                //  The inner array is an array of editors that'll need to be disposed.
                                //  Basically, each context (except the first) represents a popup.
                                //  The first represents the current screen.
                                //  Each inner array contains an array of editors in that context.