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.
npm run import -- "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)
// 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:
saveEdits
Function:
XMLHttpRequest
to make a GET request to a specific URL constructed from the current page's URL.referrer
, gist
, and url
to identify the source and context of the edits.Inline Editor Setup:
contenteditable
attribute (indicating editable content).Autosave
plugin.autosave
configuration specifies that the saveEdits
function should be called whenever the editor's content changes.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.