From 6aa181d3f8d7bae52879b2c76471dadc05c83071 Mon Sep 17 00:00:00 2001 From: Tim Raasveld Date: Tue, 3 Jan 2023 10:15:29 +0100 Subject: [PATCH 1/5] Support non-json-config-properties --- admin/src/components/CKEditor/index.js | 66 ++++++++++++++++++++++---- server/controllers/config.js | 8 +++- server/routes/index.js | 20 +++++--- server/services/config.js | 12 +++++ yarn.lock | 8 ++-- 5 files changed, 94 insertions(+), 20 deletions(-) diff --git a/admin/src/components/CKEditor/index.js b/admin/src/components/CKEditor/index.js index 5c0a8f5..7f921de 100644 --- a/admin/src/components/CKEditor/index.js +++ b/admin/src/components/CKEditor/index.js @@ -1,10 +1,10 @@ -import React, { useEffect, useState, useRef } from "react"; -import { auth, prefixFileUrlWithBackendUrl, request } from "@strapi/helper-plugin"; -import styled, { createGlobalStyle } from "styled-components"; +import React, {useEffect, useRef, useState} from "react"; +import {auth, prefixFileUrlWithBackendUrl, request} from "@strapi/helper-plugin"; +import styled, {createGlobalStyle} from "styled-components"; -import { Box } from "@strapi/design-system/Box"; -import { CKEditor } from "@ckeditor/ckeditor5-react"; -import { Editor as CustomClassicEditor } from "./build/ckeditor"; +import {Box} from "@strapi/design-system/Box"; +import {CKEditor} from "@ckeditor/ckeditor5-react"; +import {Editor as CustomClassicEditor} from "./build/ckeditor"; import MediaLib from "../MediaLib"; import PropTypes from "prop-types"; import pluginId from "../../pluginId"; @@ -66,6 +66,53 @@ const Editor = ({ onChange, name, value, disabled }) => { toggleMediaLib(); }; + const requestConfig = (key) => + request(`/${pluginId}/config/${key}`, { method: "GET" }); + + const requestEditorConfigJs = async () => { + const response = await fetch(`/${pluginId}/editor-config-script`, { + method: 'GET', + headers: { Authorization: `Bearer ${auth.getToken()}` }, + }); + + return await response.text(); + } + + const insertConfigScript = (jsCode) => { + var script = document.createElement('script'); + script.textContent = jsCode; + document.body.appendChild(script); + } + + const waitForConfigToInitialize = async () => { + return new Promise((resolve) => { + (function checkConfigLoaded() { + if (globalThis.ckEditorConfig) { + resolve(globalThis.ckEditorConfig) + } else + setTimeout(checkConfigLoaded, 5) + })(); + }); + } + + const getEditorConfig = async () => { + const editorConfigJs = await requestEditorConfigJs(); + + // raw config/ckeditor.[js|ts] file + // Can be used with non-JSON serializable properties + if(editorConfigJs) { + insertConfigScript(editorConfigJs); + return waitForConfigToInitialize(); + } + + // ckeditor snippet of config/plugins.[js|ts] serialized as JSON + // Can't be used with non-JSON serializable properties + else { + return requestConfig('editor'); + } + } + + //####### config ############################################################################################# const [config, setConfig] = useState(); const [pluginCfg, setPluginCfg] = useState({}); @@ -75,9 +122,10 @@ const Editor = ({ onChange, name, value, disabled }) => { useEffect(() => { // load the editor config (async () => { - const editor = await request(`/${pluginId}/config/editor`, { method: "GET" }); - const plugin = await request(`/${pluginId}/config/plugin`, { method: "GET" }); - const upload = await request(`/${pluginId}/config/uploadcfg`, { method: "GET" }); + const editor = await getEditorConfig(); + const plugin = await requestConfig('plugin'); + const upload = await requestConfig('uploadcfg'); + //read i18n locale const urlSearchParams = new URLSearchParams(window.location.search); diff --git a/server/controllers/config.js b/server/controllers/config.js index 6391368..f254c54 100644 --- a/server/controllers/config.js +++ b/server/controllers/config.js @@ -10,6 +10,12 @@ module.exports = { const config = await strapi.plugin('ckeditor').service('config').getConfig(configKey); ctx.send(config); } - + }, + + getEditorConfigScript: async (ctx) => { + const config = await strapi.plugin('ckeditor').service('config').getEditorConfigScript(); + ctx.headers['Content-Type'] = 'application/javascript'; + ctx.send(config); + } }; \ No newline at end of file diff --git a/server/routes/index.js b/server/routes/index.js index d961b03..995e0e3 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -1,8 +1,16 @@ module.exports = [ - { - method: 'GET', - path: '/config/:configKey', - handler: 'config.getConfig', - config: { policies: [] }, - } + { + method: 'GET', + path: '/config/:configKey', + handler: 'config.getConfig', + config: {policies: []}, + }, + { + method: 'GET', + path: '/editor-config-script', + handler: 'config.getEditorConfigScript', + config: { + policies: [() => true] // public + }, + } ]; diff --git a/server/services/config.js b/server/services/config.js index 37a7cc3..c8ac44c 100644 --- a/server/services/config.js +++ b/server/services/config.js @@ -1,5 +1,7 @@ 'use strict'; +const fs = require("fs"); + /** * config.js configuration service */ @@ -9,6 +11,16 @@ module.exports = ({ strapi }) => { getConfig(key = 'editor') { return strapi.plugin('ckeditor').config(key) ?? {}; }, + getEditorConfigScript() { + const appDir = process.cwd(); + const isTSProject = fs.existsSync(`${appDir}/dist`); + const jsDir = isTSProject ? `${appDir}/dist` : appDir; + + const filename = `${jsDir}/config/ckeditor.js`; + return fs.existsSync(filename) + ? fs.readFileSync(filename) + : undefined // empty script tag causes no problems + }, getUploadConfig(name) { return strapi.plugin('upload').service(name) ?? {}; }, diff --git a/yarn.lock b/yarn.lock index 748e126..ac5abb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@ckeditor/ckeditor5-react@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-react/-/ckeditor5-react-5.0.0.tgz#b9ad16bb84415beff0b555ddc92d5e184d41f5a1" - integrity sha512-5E/Npua1goikPdbx6GpeFNwOem+lslgYs2r3CycXcbxa3/d9C6ZBVpWppLk3rlRcUKTOXvGNkjfqRoGk9QhATA== +"@ckeditor/ckeditor5-react@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-react/-/ckeditor5-react-5.0.2.tgz#446518e1d7ce842c63fc6ac24e818cc78a753903" + integrity sha512-pN4acvCAIsuXaZDMttqy4dNBKXiJ6AS6P8NM3ggMc/rQkMIPp3YPhDWWf+pyQLUiewj1Bfr5EFeBfcXPQTOn+Q== dependencies: prop-types "^15.7.2" From 8381b111dc8749bbdc86c9b62312044e7a71261c Mon Sep 17 00:00:00 2001 From: Tim Raasveld Date: Thu, 5 Jan 2023 10:09:40 +0100 Subject: [PATCH 2/5] Prefix fetch URL with backendURL --- admin/src/components/CKEditor/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/admin/src/components/CKEditor/index.js b/admin/src/components/CKEditor/index.js index 7f921de..075499f 100644 --- a/admin/src/components/CKEditor/index.js +++ b/admin/src/components/CKEditor/index.js @@ -70,6 +70,11 @@ const Editor = ({ onChange, name, value, disabled }) => { request(`/${pluginId}/config/${key}`, { method: "GET" }); const requestEditorConfigJs = async () => { + const url = strapi.backendURL !== '/' + ? `/${strapi.backendURL}${pluginId}/editor-config-script` + : `/${pluginId}/editor-config-script` + + // Can't use `request` helper, because it is hardcoded to try to convert response to JSON const response = await fetch(`/${pluginId}/editor-config-script`, { method: 'GET', headers: { Authorization: `Bearer ${auth.getToken()}` }, From 0473ca048a1d216478189c452701c261dd685439 Mon Sep 17 00:00:00 2001 From: Tim Raasveld Date: Thu, 5 Jan 2023 11:05:23 +0100 Subject: [PATCH 3/5] Fix url --- admin/src/components/CKEditor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/src/components/CKEditor/index.js b/admin/src/components/CKEditor/index.js index 075499f..6ae51e6 100644 --- a/admin/src/components/CKEditor/index.js +++ b/admin/src/components/CKEditor/index.js @@ -71,7 +71,7 @@ const Editor = ({ onChange, name, value, disabled }) => { const requestEditorConfigJs = async () => { const url = strapi.backendURL !== '/' - ? `/${strapi.backendURL}${pluginId}/editor-config-script` + ? `${strapi.backendURL}/${pluginId}/editor-config-script` : `/${pluginId}/editor-config-script` // Can't use `request` helper, because it is hardcoded to try to convert response to JSON From 8917eee5b3493a0f5e3a1a4facf8fa04d252eef5 Mon Sep 17 00:00:00 2001 From: Tim Raasveld Date: Thu, 5 Jan 2023 11:48:39 +0100 Subject: [PATCH 4/5] Fix url --- admin/src/components/CKEditor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/src/components/CKEditor/index.js b/admin/src/components/CKEditor/index.js index 6ae51e6..982c576 100644 --- a/admin/src/components/CKEditor/index.js +++ b/admin/src/components/CKEditor/index.js @@ -75,7 +75,7 @@ const Editor = ({ onChange, name, value, disabled }) => { : `/${pluginId}/editor-config-script` // Can't use `request` helper, because it is hardcoded to try to convert response to JSON - const response = await fetch(`/${pluginId}/editor-config-script`, { + const response = await fetch(url, { method: 'GET', headers: { Authorization: `Bearer ${auth.getToken()}` }, }); From 0c6ad1e1b96ce07a8ab869768cb9c828f7cb329d Mon Sep 17 00:00:00 2001 From: Tim Raasveld Date: Thu, 5 Jan 2023 13:58:41 +0100 Subject: [PATCH 5/5] Don't rely on inline styling --- admin/src/components/CKEditor/index.js | 30 +++++++++----------------- server/controllers/config.js | 2 +- server/routes/index.js | 4 ++-- server/services/config.js | 2 +- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/admin/src/components/CKEditor/index.js b/admin/src/components/CKEditor/index.js index 982c576..951f4aa 100644 --- a/admin/src/components/CKEditor/index.js +++ b/admin/src/components/CKEditor/index.js @@ -69,30 +69,21 @@ const Editor = ({ onChange, name, value, disabled }) => { const requestConfig = (key) => request(`/${pluginId}/config/${key}`, { method: "GET" }); - const requestEditorConfigJs = async () => { + const insertConfigScript = () => { const url = strapi.backendURL !== '/' - ? `${strapi.backendURL}/${pluginId}/editor-config-script` - : `/${pluginId}/editor-config-script` + ? `${strapi.backendURL}/${pluginId}/editor-config-script.js` + : `/${pluginId}/editor-config-script.js` - // Can't use `request` helper, because it is hardcoded to try to convert response to JSON - const response = await fetch(url, { - method: 'GET', - headers: { Authorization: `Bearer ${auth.getToken()}` }, - }); - - return await response.text(); - } - - const insertConfigScript = (jsCode) => { var script = document.createElement('script'); - script.textContent = jsCode; + script.id = 'editor-config' + script.src = url; document.body.appendChild(script); } const waitForConfigToInitialize = async () => { return new Promise((resolve) => { (function checkConfigLoaded() { - if (globalThis.ckEditorConfig) { + if (typeof globalThis.ckEditorConfig !== "undefined") { resolve(globalThis.ckEditorConfig) } else setTimeout(checkConfigLoaded, 5) @@ -101,13 +92,12 @@ const Editor = ({ onChange, name, value, disabled }) => { } const getEditorConfig = async () => { - const editorConfigJs = await requestEditorConfigJs(); - // raw config/ckeditor.[js|ts] file // Can be used with non-JSON serializable properties - if(editorConfigJs) { - insertConfigScript(editorConfigJs); - return waitForConfigToInitialize(); + insertConfigScript(); + const configFromScript = await waitForConfigToInitialize(); + if(configFromScript) { + return configFromScript; } // ckeditor snippet of config/plugins.[js|ts] serialized as JSON diff --git a/server/controllers/config.js b/server/controllers/config.js index f254c54..3dd361c 100644 --- a/server/controllers/config.js +++ b/server/controllers/config.js @@ -15,7 +15,7 @@ module.exports = { getEditorConfigScript: async (ctx) => { const config = await strapi.plugin('ckeditor').service('config').getEditorConfigScript(); - ctx.headers['Content-Type'] = 'application/javascript'; + ctx.type = 'application/javascript'; ctx.send(config); } }; \ No newline at end of file diff --git a/server/routes/index.js b/server/routes/index.js index 995e0e3..1437c44 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -7,10 +7,10 @@ module.exports = [ }, { method: 'GET', - path: '/editor-config-script', + path: '/editor-config-script.js', handler: 'config.getEditorConfigScript', config: { - policies: [() => true] // public + auth: false // Assume CKEditor config is not sensitive. We can't send a JWT token in a static script tag. }, } ]; diff --git a/server/services/config.js b/server/services/config.js index c8ac44c..96db448 100644 --- a/server/services/config.js +++ b/server/services/config.js @@ -19,7 +19,7 @@ module.exports = ({ strapi }) => { const filename = `${jsDir}/config/ckeditor.js`; return fs.existsSync(filename) ? fs.readFileSync(filename) - : undefined // empty script tag causes no problems + : 'globalThis.ckEditorConfig = null' // empty script tag causes no problems }, getUploadConfig(name) { return strapi.plugin('upload').service(name) ?? {};