From 6acf17efc6aa3a737e7fdd86100fb4ca0093e269 Mon Sep 17 00:00:00 2001 From: nshenderov Date: Thu, 5 Dec 2024 15:11:48 +0300 Subject: [PATCH 1/2] new plugin configuration method initial --- admin/custom.d.ts | 3 ++ admin/src/components/CKEReact.tsx | 2 +- admin/src/components/EditorProvider.tsx | 24 +++++++-- admin/src/components/GlobalStyling.tsx | 7 +-- admin/src/components/MediaLib.tsx | 2 +- admin/src/config/expToGlobal.ts | 19 -------- admin/src/config/index.ts | 2 - admin/src/config/pluginConfig.ts | 43 ++++++++-------- admin/src/config/presets.ts | 57 ---------------------- admin/src/config/types.ts | 42 ++-------------- admin/src/exports.ts | 3 +- admin/src/index.ts | 31 ++++++++++-- package.json | 4 +- server/src/controllers/configController.ts | 11 ++--- server/src/index.ts | 2 - server/src/routes/index.ts | 4 +- server/src/services/configService.ts | 53 -------------------- server/src/services/index.ts | 5 -- 18 files changed, 93 insertions(+), 221 deletions(-) delete mode 100644 admin/src/config/expToGlobal.ts delete mode 100644 admin/src/config/presets.ts delete mode 100644 server/src/services/configService.ts delete mode 100644 server/src/services/index.ts diff --git a/admin/custom.d.ts b/admin/custom.d.ts index 26db8f3..564534a 100644 --- a/admin/custom.d.ts +++ b/admin/custom.d.ts @@ -5,5 +5,8 @@ declare global { strapi: { backendURL: string; }; + SH_CKE: { + IS_UPLOAD_RESPONSIVE: boolean; + }; } } diff --git a/admin/src/components/CKEReact.tsx b/admin/src/components/CKEReact.tsx index 8d4e3a7..1aaa780 100644 --- a/admin/src/components/CKEReact.tsx +++ b/admin/src/components/CKEReact.tsx @@ -135,7 +135,7 @@ export function CKEReact() { uploadUrl: `${backendURL}/upload`, backendUrl: backendURL, headers: { Authorization: `Bearer ${token}` }, - responsive: window.SH_CKE_UPLOAD_ADAPTER_IS_RESPONSIVE, + responsive: window.SH_CKE.IS_UPLOAD_RESPONSIVE, }; StrapiUploadAdapterPlugin.initAdapter(config); diff --git a/admin/src/components/EditorProvider.tsx b/admin/src/components/EditorProvider.tsx index 511bfd0..af2e592 100644 --- a/admin/src/components/EditorProvider.tsx +++ b/admin/src/components/EditorProvider.tsx @@ -1,7 +1,7 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import type { InputProps } from '@strapi/strapi/admin'; -import { type Preset, getClonedPreset, setUpLanguage } from '../config'; +import { type Preset, setUpLanguage, getPluginConfig } from '../config'; import type { WordCountPluginStats } from './CKEReact'; type EditorProviderBaseProps = Pick< @@ -54,8 +54,8 @@ export function EditorProvider({ useEffect(() => { (async () => { - const currentPreset = getClonedPreset(presetName); - + const { presets } = getPluginConfig(); + const currentPreset = clonePreset(presets[presetName]); await setUpLanguage(currentPreset.editorConfig, isFieldLocalized); if (placeholder) { @@ -132,3 +132,21 @@ export function EditorProvider({ return {children}; } + +function clonePreset(preset: Preset): Preset { + const clonedPreset = { + ...preset, + editorConfig: { + ...preset.editorConfig, + }, + }; + + Object.entries(clonedPreset.editorConfig).forEach(([key, val]) => { + if (val && typeof val === 'object' && Object.getPrototypeOf(val) === Object.prototype) { + // @ts-ignore + clonedPreset.editorConfig[key] = { ...val }; + } + }); + + return clonedPreset; +} diff --git a/admin/src/components/GlobalStyling.tsx b/admin/src/components/GlobalStyling.tsx index f80395b..e8c2b6f 100644 --- a/admin/src/components/GlobalStyling.tsx +++ b/admin/src/components/GlobalStyling.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { createGlobalStyle, css } from 'styled-components'; -import { defaultTheme } from '../theme'; import { getProfileTheme } from '../utils'; -import type { Theme, Styles } from '../config'; +import { type Theme, type Styles, getPluginConfig } from '../config'; const GlobalStyle = createGlobalStyle<{ $editortTheme?: Theme; @@ -23,11 +22,9 @@ const getSystemColorScheme = (): 'light' | 'dark' => window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; function GlobalStyling() { - const { theme: userTheme, dontMergeTheme } = window.SH_CKE_CONFIG || {}; - + const { theme } = getPluginConfig(); const profileTheme = getProfileTheme(); const variant = profileTheme && profileTheme !== 'system' ? profileTheme : getSystemColorScheme(); - const theme = dontMergeTheme ? userTheme : { ...defaultTheme, ...userTheme }; return ; } diff --git a/admin/src/components/MediaLib.tsx b/admin/src/components/MediaLib.tsx index 326b3df..6ca9b88 100644 --- a/admin/src/components/MediaLib.tsx +++ b/admin/src/components/MediaLib.tsx @@ -34,7 +34,7 @@ function MediaLib({ isOpen = false, toggle, handleChangeAssets }: MediaLibProps) assets.forEach(({ name, url, alt, formats, mime, width, height }: any) => { if (mime.includes('image')) { - if (formats && window.SH_CKE_UPLOAD_ADAPTER_IS_RESPONSIVE) { + if (formats && window.SH_CKE.IS_UPLOAD_RESPONSIVE) { const set = formSet(formats); newElems += `${alt}`; } else { diff --git a/admin/src/config/expToGlobal.ts b/admin/src/config/expToGlobal.ts deleted file mode 100644 index 70545f4..0000000 --- a/admin/src/config/expToGlobal.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as CKE from 'ckeditor5'; - -import { StrapiMediaLib, StrapiUploadAdapter } from '../plugins'; -import { materialColors } from './colors'; - -export type ExportToGlobal = typeof CKE & { - StrapiMediaLib: typeof StrapiMediaLib; - StrapiUploadAdapter: typeof StrapiUploadAdapter; - MaterialColors: typeof materialColors; -}; - -export function exportToGlobal(): void { - window.SH_CKE = { - ...CKE, - StrapiMediaLib, - StrapiUploadAdapter, - MaterialColors: materialColors, - }; -} diff --git a/admin/src/config/index.ts b/admin/src/config/index.ts index 41cd632..6e1b1fa 100644 --- a/admin/src/config/index.ts +++ b/admin/src/config/index.ts @@ -3,5 +3,3 @@ export * from './language'; export * from './pluginConfig'; export * from './colors'; export * from './defaultPreset'; -export * from './presets'; -export * from './expToGlobal'; diff --git a/admin/src/config/pluginConfig.ts b/admin/src/config/pluginConfig.ts index c11be93..9ae767c 100644 --- a/admin/src/config/pluginConfig.ts +++ b/admin/src/config/pluginConfig.ts @@ -1,27 +1,32 @@ +import { defaultTheme } from '../theme'; +import { defaultPreset } from './defaultPreset'; import type { PluginConfig } from './types'; -import { PLUGIN_ID } from '../utils'; -export async function getPluginConfig(): Promise { - const config = await loadConfig().catch(error => console.error('CKEditor: ', error)); +const PLUGIN_CONFIG: PluginConfig = { + presets: { + default: defaultPreset, + }, + theme: defaultTheme, +}; - return config || null; +export function setPluginConfig(userConfig: Partial): void { + const { presets = PLUGIN_CONFIG.presets, theme = PLUGIN_CONFIG.theme } = userConfig; + PLUGIN_CONFIG.presets = presets; + PLUGIN_CONFIG.theme = theme; + deepFreeze(PLUGIN_CONFIG); } -async function loadConfig(): Promise { - return new Promise((resolve, reject) => { - const { backendURL } = window.strapi; - const url = - backendURL !== '/' - ? `${backendURL}/${PLUGIN_ID}/config/ckeditor` - : `/${PLUGIN_ID}/config/ckeditor`; - - const script = document.createElement('script'); - script.id = 'ckeditor-config'; - script.src = url; - - script.onload = () => resolve(window.SH_CKE_CONFIG); - script.onerror = () => reject(new Error('Failed loading config script')); +export function getPluginConfig(): PluginConfig { + if (!Object.isFrozen(PLUGIN_CONFIG)) deepFreeze(PLUGIN_CONFIG); + return PLUGIN_CONFIG; +} - document.body.appendChild(script); +function deepFreeze(obj: Object): Readonly { + (Object.keys(obj) as (keyof typeof obj)[]).forEach(p => { + if (typeof obj[p] === 'object' && obj[p] !== null && !Object.isFrozen(obj[p])) { + deepFreeze(obj[p]); + } }); + + return Object.freeze(obj); } diff --git a/admin/src/config/presets.ts b/admin/src/config/presets.ts deleted file mode 100644 index 4e2a59d..0000000 --- a/admin/src/config/presets.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { defaultPreset } from './defaultPreset'; -import type { Field, PluginConfig, Preset } from './types'; - -export const pluginPresets: Record = { - default: defaultPreset, -}; - -export function getClonedPreset(presetName: string): Preset { - const { presets: userPresets, dontMergePresets } = window.SH_CKE_CONFIG || {}; - - if (dontMergePresets && !userPresets) { - console.error('CKEditor: No presets found'); - } - - const preset = - dontMergePresets && userPresets ? userPresets[presetName] : pluginPresets[presetName]; - - const clonedPreset = { - ...preset, - editorConfig: { - ...preset.editorConfig, - }, - }; - - return clonedPreset; -} - -export function getPresetsFields(config: PluginConfig | null): Field[] { - const { presets: userPresets = {}, dontMergePresets = false } = config ?? {}; - - if (!dontMergePresets && userPresets) { - mergePresets(userPresets as Record, pluginPresets); - } - - const fields: Field[] = []; - Object.values(pluginPresets).forEach(preset => fields.push(preset.field)); - - return fields; -} - -function mergePresets(from: Record, to: Record): void { - Object.keys(from).forEach(presetName => { - if (to[presetName]) { - to[presetName].field = { - ...to[presetName].field, - ...from[presetName].field, - }; - to[presetName].styles = from[presetName].styles || to[presetName].styles; - to[presetName].editorConfig = { - ...to[presetName].editorConfig, - ...from[presetName].editorConfig, - }; - } else { - to[presetName] = from[presetName]; - } - }); -} diff --git a/admin/src/config/types.ts b/admin/src/config/types.ts index 496db66..c5ffd81 100644 --- a/admin/src/config/types.ts +++ b/admin/src/config/types.ts @@ -1,28 +1,10 @@ import type { EditorConfig as CKE5EditorConfig } from 'ckeditor5'; import type { Interpolation } from 'styled-components'; -import type { ExportToGlobal } from './expToGlobal'; -export type PluginConfig = - | { - dontMergePresets: true; - presets: Record; - dontMergeTheme?: boolean; - theme?: Theme; - } - | { - dontMergePresets?: false; - presets?: { - default: Partial; - /** - * New presets must use Preset type. - * Partial is included only for compatibility - * with previous versions and should not be used. - */ - [k: string]: Preset | PartialIsNotAllowedForNewPresets; - }; - dontMergeTheme?: boolean; - theme?: Theme; - }; +export type PluginConfig = { + presets: Record; + theme: Theme; +}; export type Theme = { common?: Styles; @@ -34,13 +16,7 @@ export type Theme = { export type Preset = { field: Field; styles?: Styles; - editorConfig: Partial; -}; - -export type PartialIsNotAllowedForNewPresets = { - field?: Field; - styles?: Styles; - editorConfig?: Partial; + editorConfig: EditorConfig; }; export type EditorConfig = CKE5EditorConfig; @@ -64,11 +40,3 @@ export type IntlLabel = { }; export type Styles = string | Interpolation[]; - -declare global { - interface Window { - SH_CKE: ExportToGlobal; - SH_CKE_CONFIG: PluginConfig | null; - SH_CKE_UPLOAD_ADAPTER_IS_RESPONSIVE: boolean; - } -} diff --git a/admin/src/exports.ts b/admin/src/exports.ts index 61f8f64..4be5d64 100644 --- a/admin/src/exports.ts +++ b/admin/src/exports.ts @@ -1,2 +1,3 @@ export type * from './config/types'; -export { defaultPreset } from './config'; +export { setPluginConfig, defaultPreset, materialColors } from './config'; +export { StrapiMediaLib, StrapiUploadAdapter } from './plugins'; diff --git a/admin/src/index.ts b/admin/src/index.ts index fd7c9d6..673c6b8 100644 --- a/admin/src/index.ts +++ b/admin/src/index.ts @@ -1,15 +1,36 @@ import * as yup from 'yup'; import { PLUGIN_ID } from './utils'; -import { getPresetsFields, getPluginConfig, exportToGlobal } from './config'; +import { getPluginConfig, Field } from './config'; import { CKEditorIcon } from './components/CKEditorIcon'; +export * from './exports'; + +const AVAILABLE_OPTIONS: Field[] = []; + +function fillUpOptions(): void { + const { presets } = getPluginConfig(); + Object.values(presets).forEach(({ field }) => AVAILABLE_OPTIONS.push(field)); +} + +async function setUpGlobal() { + const { backendURL } = window.strapi; + const route = 'config/is-responsive-dimensions'; + const url = backendURL !== '/' ? `${backendURL}/${PLUGIN_ID}/${route}` : `/${PLUGIN_ID}/${route}`; + const { isResponsiveDimensions } = await fetch(url).then(res => res.json()); + + window.SH_CKE = { + IS_UPLOAD_RESPONSIVE: isResponsiveDimensions, + }; +} + // eslint-disable-next-line import/no-default-export export default { + bootstrap() { + fillUpOptions(); + }, async register(app: any): Promise { - exportToGlobal(); - const pluginConfig = await getPluginConfig(); - const optionsPreset = getPresetsFields(pluginConfig); + await setUpGlobal(); app.customFields.register({ name: 'CKEditor', @@ -43,7 +64,7 @@ export default { }, name: 'options.preset', type: 'select', - options: optionsPreset, + options: AVAILABLE_OPTIONS, }, ], advanced: [ diff --git a/package.json b/package.json index cbc4f0b..5ea8337 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,8 @@ "exports": { "./package.json": "./package.json", ".": { - "types": "./dist/admin/src/exports.d.ts", - "source": "./admin/src/exports.ts", + "types": "./dist/admin/src/index.d.ts", + "source": "./admin/src/index.ts", "import": "./dist/admin/index.mjs", "require": "./dist/admin/index.js", "default": "./dist/admin/index.js" diff --git a/server/src/controllers/configController.ts b/server/src/controllers/configController.ts index b2cb52f..4441a74 100644 --- a/server/src/controllers/configController.ts +++ b/server/src/controllers/configController.ts @@ -1,19 +1,16 @@ import type { Core } from '@strapi/strapi'; -import PLUGIN_ID from '../utils/pluginId'; export default function configController({ strapi }: { strapi: Core.Strapi }): Core.Controller { return { - async getConfig(ctx): Promise { + async isResponsiveDimensions(ctx): Promise { const { responsiveDimensions = false } = await strapi .plugin('upload') .service('upload') .getSettings(); - let config = await strapi.plugin(PLUGIN_ID).service('configService').readConfig(); - config += `window.SH_CKE_UPLOAD_ADAPTER_IS_RESPONSIVE = ${responsiveDimensions}\n`; - - ctx.type = 'application/javascript'; - ctx.send(config); + ctx.send({ + isResponsiveDimensions: responsiveDimensions, + }); }, }; } diff --git a/server/src/index.ts b/server/src/index.ts index 70c2369..f0e7855 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,11 +1,9 @@ import register from './register'; import controllers from './controllers'; import routes from './routes'; -import services from './services'; export default { register, controllers, routes, - services, }; diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index ad258db..c19a1da 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -4,8 +4,8 @@ export default { routes: [ { method: 'GET', - path: '/config/ckeditor', - handler: 'configController.getConfig', + path: '/config/is-responsive-dimensions', + handler: 'configController.isResponsiveDimensions', config: { auth: false, }, diff --git a/server/src/services/configService.ts b/server/src/services/configService.ts deleted file mode 100644 index 0d0572a..0000000 --- a/server/src/services/configService.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { existsSync } from 'fs'; -import { readFile } from 'fs/promises'; -import type { Core } from '@strapi/strapi'; - -export default function configService(): Core.Service { - return { - async readConfig(): Promise { - const configFileContent = await readConfigFile(); - let config = configFileContent && trimConfig(configFileContent); - config &&= `${config}\nwindow.SH_CKE_CONFIG = CKEConfig()\n`; - config ??= `window.SH_CKE_CONFIG = null\n`; - - return config; - }, - }; -} - -function readConfigFile(): Promise { - const appDir = process.cwd(); - const isTSProject = existsSync(`${appDir}/dist`); - const envName = process.env.NODE_ENV; - - const cfgDir = isTSProject ? `${appDir}/dist/config` : `${appDir}/config`; - const cfgFileName = 'ckeditor.js'; - - const envFilePath = `${cfgDir}/env/${envName}/${cfgFileName}`; - const baseFilePath = `${cfgDir}/${cfgFileName}`; - - if (existsSync(envFilePath)) { - return readFile(envFilePath, 'utf8'); - } - - if (existsSync(baseFilePath)) { - return readFile(baseFilePath, 'utf8'); - } - - return null; -} - -function trimConfig(rawBuf: string): string | null { - let config: string | null = null; - - ['const CKEConfig', 'function CKEConfig'].some(func => { - const idx = rawBuf.indexOf(func); - if (idx >= 0) { - config = `${rawBuf.substring(idx)}`; - return true; - } - return false; - }); - - return config; -} diff --git a/server/src/services/index.ts b/server/src/services/index.ts deleted file mode 100644 index 8d435a6..0000000 --- a/server/src/services/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import configService from './configService'; - -export default { - configService, -}; From 0c526e62052b769de2ec7f1a78b017e080d88b2f Mon Sep 17 00:00:00 2001 From: nshenderov Date: Thu, 5 Dec 2024 18:12:59 +0300 Subject: [PATCH 2/2] add missing type modifiers on imports --- admin/src/components/Field.tsx | 2 +- admin/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/src/components/Field.tsx b/admin/src/components/Field.tsx index 7d10808..007933c 100644 --- a/admin/src/components/Field.tsx +++ b/admin/src/components/Field.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { type InputProps, type FieldValue } from '@strapi/strapi/admin'; +import type { InputProps, FieldValue } from '@strapi/strapi/admin'; import { Editor } from './Editor'; import { EditorProvider } from './EditorProvider'; diff --git a/admin/src/index.ts b/admin/src/index.ts index 673c6b8..8b36128 100644 --- a/admin/src/index.ts +++ b/admin/src/index.ts @@ -1,7 +1,7 @@ import * as yup from 'yup'; import { PLUGIN_ID } from './utils'; -import { getPluginConfig, Field } from './config'; +import { getPluginConfig, type Field } from './config'; import { CKEditorIcon } from './components/CKEditorIcon'; export * from './exports';