edit anywhere | read crawl files | Cell 12 | Search

This code implements real-time saving for editable content on a webpage using CKEditor 5 and a custom server-side endpoint to handle the saving process.

Run example

npm run import -- "ckeditor configuration"

ckeditor configuration

function saveEdits(data) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest()
        xhr.setHeader('X-Referrer', window.location)
        xhr.onload = function () {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr)
            } else {
                reject(new Error('The request failed!'))
            }
        }
        xhr.open('GET', window.location.href.replace('gitEditorHandler', 'gitSaveHandler')
                 + '?referrer=' + window.location.href
                 // TODO: use referer in receiving function for this
                 + '&gist=' + window.location.search.match(/[\?&]gist=([^&]*)/ig)[1]
                 + '&url=' + window.location.search.match(/[\?&]url=([^&]*)/ig)[1]
                 )
        xhr.send(data)
    })
}

var script = document.createElement('script')
script.onload = function () {
    var editors = document.querySelectorAll( '*[contenteditable]' )
    editors.forEach(e => {
        InlineEditor.create( e, {
            plugins: [
                Autosave,
            ],
            autosave: {
                save( editor ) {
                    return saveEdits( editor.getData() );
                }
            }
        })
        .catch( error => console.error( error ) )
        
    })
}
script.setAttribute('src', 'https://cdn.ckeditor.com/ckeditor5/16.0.0/inline/ckeditor.js')
document.body.appendChild(script)

function startGolden() {
    var config = {
        settings: {
            //showPopoutIcon: false,
            //showPopoutIcon: true,
            //showMaximiseIcon: true,
            //showCloseIcon: true
        },
        dimensions: {
            borderWidth: 10,
            headerHeight: 40,
        },
        content: [{
            type: 'row',
            content:[{
                type: 'component',
                componentName: 'Preview Page',
                componentState: { label: 'A' }
            },{
                type: 'column',
                content:[{
                    type: 'component',
                    componentName: 'Project Files',
                    componentState: { label: 'B' }
                },{
                    type: 'component',
                    componentName: 'Code View',
                    componentState: { label: 'C' }
                }]
            }]
        }]
    }

    var myLayout = new GoldenLayout( config )
    
    myLayout.on('tabCreated', function (tab) {
        var i = document.createElement('i')
        i.className = 'fas fa-window-close'
        tab.closeElement[0].appendChild(i)
        //if(tab.header.controlsContainer[0].getElementsByClassName('fa-window-close').length === 0) {
        //}
    })
    
    myLayout.on('itemDestroyed', function () {
    })
    
    myLayout.on('stackCreated', function (container) {
        var controls = container.header.controlsContainer[0]
        while (controls.firstChild) {
            controls.removeChild(controls.lastChild);
        }
        var li = document.createElement('li')
        li.className = 'lm_maximise'
        li.title = 'maximize'
        i = document.createElement('i')
        i.className = 'fas fa-window-maximize'
        li.appendChild(i)
        i = document.createElement('i')
        i.className = 'fas fa-window-minimize'
        li.appendChild(i)
        li.addEventListener('click', function (event) {
            container.toggleMaximise(event)
        }, { passive: true })
        controls.appendChild(li)
        li = document.createElement('li')
        li.className = 'lm_close'
        li.title = 'close'
        var i = document.createElement('i')
        i.className = 'fas fa-window-close'
        li.appendChild(i)
        li.addEventListener('click', function (event) {
            container.header.activeContentItem.close()
        }, { passive: true })
        controls.appendChild(li)
   })
    
    myLayout.registerComponent( 'Preview Page', function( container, componentState ) {
        var initial = document.getElementsByClassName('initial-page')
        container.getElement()[0].appendChild(initial[0])
        container.getElement()[0].style.minHeight = window.innerHeight + 'px'
        // TODO; add PDF and Screenshot rendering to tab control
        /*
        container.on( 'tab', function( tab ){
            tab.element.append( counter );
        });
        */
         
        container.on( 'tab', function( tab ) {
            i = document.createElement('i')
            i.className = 'fas fa-binoculars'
            tab.element[0].appendChild(i)
            i = document.createElement('i')
            i.className = 'fas fa-search'
            tab.element[0].appendChild(i)
            i = document.createElement('i')
            i.className = 'fas fa-print'
            tab.element[0].appendChild(i)
        })

       
    })
    myLayout.registerComponent( 'Project Files', function( container, componentState ) {
        var initial = document.getElementsByClassName('initial-files')
        container.getElement()[0].appendChild(initial[0])
        // TODO: add Glide and multilist and icon views to tab controls
        container.on( 'tab', function( tab ) {
            i = document.createElement('i')
            i.className = 'fas fa-exchange-alt'
            tab.element[0].appendChild(i)
            i = document.createElement('i')
            i.className = 'fas fa-columns'
            tab.element[0].appendChild(i)
            i = document.createElement('i')
            i.className = 'fas fa-stream'
            tab.element[0].appendChild(i)
        })
    })
    // TODO: add code editor
    myLayout.registerComponent( 'Code View', function( container, componentState ) {
        var initial = document.getElementsByClassName('initial-code')
        container.getElement()[0].appendChild(initial[0])
        
        container.on( 'tab', function( tab ) {
            i = document.createElement('i')
            i.className = 'fas fa-play'
            tab.element[0].appendChild(i)
            i = document.createElement('i')
            i.className = 'fas fa-stop'
            tab.element[0].appendChild(i)
            i = document.createElement('i')
            i.className = 'fas fa-backspace'
            tab.element[0].appendChild(i)
        })

        container.on('open', function () {
            var editor = ace.edit(document.getElementsByClassName('initial-code')[0], {
                useWorker: false
            })
            editor.setTheme("ace/theme/monokai")
            editor.session.setMode("ace/mode/javascript")
            componentState.editor = editor
        })
        
        container.on('resize', function () {
            if(componentState.editor)
                componentState.editor.resize()
        })
    })
    
    myLayout.init()
}

function loadScript(src) {
    return new Promise(resolve => {
        var script = document.createElement('script')
        script.onload = function () {
            resolve()
        }
        script.setAttribute('src', src)
        document.body.appendChild(script)
    })
}

loadScript('https://code.jquery.com/jquery-3.5.1.min.js') //'https://code.jquery.com/jquery-3.5.1.slim.min.js')
    .then(loadScript.bind(null, 'https://golden-layout.com/files/latest/js/goldenlayout.min.js'))
    .then(loadScript.bind(null, 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.min.js'))
    .then(loadScript.bind(null, 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/mode-javascript.min.js'))
    .then(loadScript.bind(null, 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/theme-monokai.min.js'))
    .then(startGolden)

What the code could have been:

// Import required libraries
import { JSDOM } from 'jsdom';
import axios from 'axios';

// Create a mock DOM for the script to run in
const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`);

// Function to save edits
async function saveEdits(data) {
    try {
        const response = await axios.post(
            window.location.href.replace('gitEditorHandler', 'gitSaveHandler'),
            data,
            {
                headers: {
                    'X-Referrer': window.location.href,
                },
            }
        );

        if (response.status >= 200 && response.status < 300) {
            return response;
        } else {
            throw new Error('The request failed!');
        }
    } catch (error) {
        throw error;
    }
}

// Function to load a script
async function loadScript(src) {
    try {
        const response = await axios.get(src);

        if (response.status >= 200 && response.status < 300) {
            const script = document.createElement('script');
            script.textContent = response.data;
            document.body.appendChild(script);

            return Promise.resolve();
        } else {
            throw new Error('Failed to load script!');
        }
    } catch (error) {
        throw error;
    }
}

// Function to start Golden Layout
async function startGolden() {
    try {
        // Create the Golden Layout configuration
        const config = {
            settings: {
                showPopoutIcon: false,
                showMaximiseIcon: false,
                showCloseIcon: false,
            },
            dimensions: {
                borderWidth: 10,
                headerHeight: 40,
            },
            content: [
                {
                    type: 'row',
                    content: [
                        {
                            type: 'component',
                            componentName: 'Preview Page',
                            componentState: { label: 'A' },
                        },
                        {
                            type: 'column',
                            content: [
                                {
                                    type: 'component',
                                    componentName: 'Project Files',
                                    componentState: { label: 'B' },
                                },
                                {
                                    type: 'component',
                                    componentName: 'Code View',
                                    componentState: { label: 'C' },
                                },
                            ],
                        },
                    ],
                },
            ],
        };

        // Create the Golden Layout instance
        const myLayout = new GoldenLayout(config);

        // Register components
        myLayout.registerComponent(
            'Preview Page',
            (container, componentState) => {
                const initial = dom.window.document.getElementsByClassName('initial-page');
                container.getElement()[0].appendChild(initial[0].cloneNode(true));
                container.getElement()[0].style.minHeight = window.innerHeight + 'px';

                container.on('tab', (tab) => {
                    const i = dom.window.document.createElement('i');
                    i.className = 'fas fa-binoculars';
                    tab.element[0].appendChild(i.cloneNode(true));

                    i = dom.window.document.createElement('i');
                    i.className = 'fas fa-search';
                    tab.element[0].appendChild(i.cloneNode(true));

                    i = dom.window.document.createElement('i');
                    i.className = 'fas fa-print';
                    tab.element[0].appendChild(i.cloneNode(true));
                });
            }
        );

        myLayout.registerComponent(
            'Project Files',
            (container, componentState) => {
                const initial = dom.window.document.getElementsByClassName('initial-files');
                container.getElement()[0].appendChild(initial[0].cloneNode(true));

                container.on('tab', (tab) => {
                    const i = dom.window.document.createElement('i');
                    i.className = 'fas fa-exchange-alt';
                    tab.element[0].appendChild(i.cloneNode(true));

                    i = dom.window.document.createElement('i');
                    i.className = 'fas fa-columns';
                    tab.element[0].appendChild(i.cloneNode(true));

                    i = dom.window.document.createElement('i');
                    i.className = 'fas fa-stream';
                    tab.element[0].appendChild(i.cloneNode(true));
                });
            }
        );

        myLayout.registerComponent(
            'Code View',
            (container, componentState) => {
                const initial = dom.window.document.getElementsByClassName('initial-code');
                container.getElement()[0].appendChild(initial[0].cloneNode(true));

                container.on('open', () => {
                    const editor = ace.edit(container.getElement()[0], {
                        useWorker: false,
                    });

                    editor.setTheme('ace/theme/monokai');
                    editor.session.setMode('ace/mode/javascript');
                    componentState.editor = editor;
                });

                container.on('resize', () => {
                    if (componentState.editor) {
                        componentState.editor.resize();
                    }
                });
            }
        );

        // Initialize the Golden Layout instance
        myLayout.init();
    } catch (error) {
        throw error;
    }
}

// Load required scripts
loadScript('https://code.jquery.com/jquery-3.5.1.min.js')
   .then(loadScript.bind(null, 'https://golden-layout.com/files/latest/js/goldenlayout.min.js'))
   .then(loadScript.bind(null, 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.min.js'))
   .then(loadScript.bind(null, 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/mode-javascript.min.js'))
   .then(loadScript.bind(null, 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/theme-monokai.min.js'))
   .then(startGolden);

// Initialize the DOM
dom.window.addEventListener('load', () => {
    // Create and append the script element
    const script = dom.window.document.createElement('script');
    script.onload = () => {
        // Get all contenteditable elements
        const editors = dom.window.document.querySelectorAll('*[contenteditable]');

        editors.forEach((editor) => {
            // Create an Inline Editor instance
            InlineEditor.create(editor, {
                plugins: [Autosave],
                autosave: {
                    save(editor) {
                        return saveEdits(editor.getData());
                    },
                },
            })
               .catch((error) => console.error(error));
        });
    };

    // Set the script source
    script.setAttribute('src', 'https://cdn.ckeditor.com/ckeditor5/16.0.0/inline/ckeditor.js');

    // Append the script to the body
    dom.window.document.body.appendChild(script);
});

This code sets up a system for real-time saving of edits made to content within a web page.

Here's a breakdown:

  1. saveEdits Function:

  2. Inline Editor Setup:

  3. Golden Configuration:

Overall Purpose:

This code sets up a system for real-time saving of edits made to editable content on a web page using CKEditor 5 and a custom server-side endpoint.