239 lines
8.0 KiB
JavaScript

import ClassicEditor from './src/ckeditor';
import './src/override-django.css';
window.ClassicEditor = ClassicEditor;
window.ckeditorRegisterCallback = registerCallback;
window.ckeditorUnregisterCallback = unregisterCallback;
window.editors = {};
let editors = {};
let callbacks = {};
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function getCSRFToken(cookieName) {
let token = getCookie(cookieName);
if (!token) {
token = document.querySelector('input[name=csrfmiddlewaretoken]')?.value;
}
return token;
}
/**
* Checks whether the element or its children match the query and returns
* an array with the matches.
*
* @param {!HTMLElement} element
* @param {!string} query
*
* @returns {array.<HTMLElement>}
*/
function resolveElementArray(element, query) {
return element.matches(query) ? [element] : [...element.querySelectorAll(query)];
}
/**
* This function initializes the CKEditor inputs within an optional element and
* assigns properties necessary for the correct operation
*
* @param {HTMLElement} [element=document.body] - The element to search for elements
*
* @returns {void}
*/
function createEditors(element = document.body) {
const allEditors = resolveElementArray(element, '.django_ckeditor_5');
allEditors.forEach(editorEl => {
if (
editorEl.id.indexOf('__prefix__') !== -1 ||
editorEl.getAttribute('data-processed') === '1'
) {
return;
}
const script_id = `${editorEl.id}_script`;
// remove next sibling if it is an empty text node
if (editorEl.nextSibling.nodeType == Node.TEXT_NODE && editorEl.nextSibling.textContent.trim() === '') {
editorEl.nextSibling.remove();
}
const upload_url = element.querySelector(
`#${script_id}-ck-editor-5-upload-url`
).getAttribute('data-upload-url');
const upload_file_types = JSON.parse(element.querySelector(
`#${script_id}-ck-editor-5-upload-url`
).getAttribute('data-upload-file-types'));
const csrf_cookie_name = element.querySelector(
`#${script_id}-ck-editor-5-upload-url`
).getAttribute('data-csrf_cookie_name');
const labelElement = element.querySelector(`[for$="${editorEl.id}"]`);
if (labelElement) {
labelElement.style.float = 'none';
}
const config = JSON.parse(
element.querySelector(`#${script_id}-span`).textContent,
(key, value) => {
var match = value.toString().match(new RegExp('^/(.*?)/([gimy]*)$'));
if (match) {
var regex = new RegExp(match[1], match[2]);
return regex;
}
if (typeof value === 'string' && value.startsWith('callback:')) {
var callbackName = value.substring(9);
var callback = window[callbackName];
if (typeof callback === 'function') {
return callback;
}
}
return value;
}
);
config.simpleUpload = {
'uploadUrl': upload_url,
'headers': {
'X-CSRFToken': getCSRFToken(csrf_cookie_name),
},
};
config.fileUploader = {
'fileTypes': upload_file_types
};
config.licenseKey = 'GPL';
// Configure autosave if enabled
if (config.autosave) {
config.autosave.save = function(editor) {
return new Promise((resolve, reject) => {
const textarea = document.querySelector(`#${editorEl.id}`);
const data = editor.getData();
textarea.value = data;
// If save URL is provided, send to server
if (config.autosave.saveUrl) {
fetch(config.autosave.saveUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(csrf_cookie_name),
},
body: JSON.stringify({
id: editorEl.id,
content: data
})
})
.then(response => response.json())
.then(result => resolve(result))
.catch(error => reject(error));
} else {
// Just update textarea
resolve();
}
});
};
}
ClassicEditor.create(
editorEl,
config
).then(editor => {
const textarea = document.querySelector(`#${editorEl.id}`);
editor.model.document.on('change:data', () => {
textarea.value = editor.getData();
});
if (editor.plugins.has('WordCount')) {
const wordCountPlugin = editor.plugins.get('WordCount');
const wordCountWrapper = element.querySelector(`#${script_id}-word-count`);
wordCountWrapper.innerHTML = '';
wordCountWrapper.appendChild(wordCountPlugin.wordCountContainer);
}
if (editorEl.hasAttribute('disabled')) {
editor.enableReadOnlyMode('django-ckeditor-5');
}
editors[editorEl.id] = editor;
if (callbacks[editorEl.id]) {
callbacks[editorEl.id](editor);
}
}).catch(error => {
console.error((error));
});
editorEl.setAttribute('data-processed', '1');
});
window.editors = editors;
}
/**
* This function filters the list of mutations only by added elements, thus
* eliminates the occurrence of text nodes and tags where it does not make sense
* to try to use with `QuerySelectorAll()` and `matches()` functions.
*
* @param {MutationRecord} recordList - It is the object inside the array
* passed to the callback of a MutationObserver.
*
* @returns {Array} Array containing filtered nodes.
*/
function getAddedNodes(recordList) {
return recordList
.flatMap(({ addedNodes }) => Array.from(addedNodes))
.filter(node => node.nodeType === 1);
}
/**
* Register a callback for when an editor with `id` is created.
*
* @param {!string} id - the id of the ckeditor element.
* @callback callback - the callback function to be invoked.
*/
function registerCallback(id, callback) {
callbacks[id] = callback;
}
/**
* Unregister a previously registered callback.
*
* @param {!string} id - the id of the ckeditor element.
*/
function unregisterCallback(id) {
callbacks[id] = null;
}
document.addEventListener("DOMContentLoaded", () => {
createEditors();
if (typeof django === "object" && django.jQuery) {
django.jQuery(document).on("formset:added", () => {createEditors();});
}
const observer = new MutationObserver((mutations) => {
let addedNodes = getAddedNodes(mutations);
addedNodes.forEach(node => {
// Initializes editors
createEditors(node);
});
});
// Configure MutationObserver options
const observerOptions = {
childList: true,
subtree: true,
};
// Selects the parent element where the events occur
const mainContent = document.body;
// Starts to observe the selected father element with the configured options
observer.observe(mainContent, observerOptions);
});