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.} */ 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); });