nshenderov / strapi-plugin-ckeditor

Integrates CKEditor 5 into your Strapi project as a fully customizable custom field. (Unofficial integration)
https://www.npmjs.com/package/@_sh/strapi-plugin-ckeditor
MIT License
84 stars 53 forks source link

Use TypeScript/JavaScript for configuring the editor (Workaround Provided) #100

Open lamuertepeluda opened 1 year ago

lamuertepeluda commented 1 year ago

First of all, thanks for this plugin which is currently more advanced than the "official" one.

Is your feature request related to a problem? Please describe. Using a txt file for configuring the editor is not the best choice in my opinion, especially when the configuration is complex, such as in CKEditor case. The least problem you have is loosing syntax highlight.

Describe the solution you'd like Possibility to use also .ts or .js files. Consider integrating the following solution somehow in the plugin logic. Alternatively, this can be cited in the docs.

Describe alternatives you've considered I have found a working workaround by using Strapi plugin extension feature. This example is in TypeScript as I am using TS in my Strapi projects.

  1. Create a src/extensions/ckeditor5 folder
  2. Add src/extensions/ckeditor5/strapi-server.ts and src/extensions/ckeditor5/config.ts
  3. Edit src/extensions/ckeditor5/strapi-server.ts as follows
    
    import config from "./ckeditor";

export default (plugin) => { plugin.services.config = ({ strapi }) => ({ getUploadConfig(name) { return strapi.plugin("upload").service(name) ?? {}; }, getCKEditorConfig: () => { // Wrap in a closure return (${config.toString()})(); }, }); return plugin; };

1. Edit `src/extensions/ckeditor5/config.ts` as follows
  ```typescript
  const config = () => {
  const CKEditor5 = globalThis.CKEditor5;

  const basePlugins = [
    CKEditor5.alignment.Alignment,
    CKEditor5.autoformat.Autoformat,
    CKEditor5.image.AutoImage
    ...
    /* full list in this plugin source */ 
  ]

  globalThis.CKEditorConfig = {
     theme: {/*...*/}
     configs: {
     /*... stuff ... */
     editorConfig: {
          plugins: [
            ...basePlugins,
            // Any Custom plugins that you imported in src/admin/app.tsx
          ],
      }
  }
 };

export default config;

Explanation

The config closure body is stringified and passed to the browser by strapi-server.ts. The browser will be able to evaluate it, provided that it does reference only global objects in the browser (i.e. declared in the app.tsx or other frontend libraries), such as CKEditor5. If using TS, it works since the config.ts gets transpiled to JS before everything above.

Advantages over txt file: you get both syntax highlight, formatting (e.g. in IDE/with Prettier) and type checking You still cannot use non-global browser variables (e.g. import ...) but that isn't possible even with .txt. Everything must be defined in the config closure.

You can import your custom plugin and widgets in src/admin/app.tsx (check Strapi docs). Those will register their namespace in the global CKEditor5 object in the browser Window. You can then refer them here in the configuration

Additional context The latest CKEditor Release uses TypeScript. I don't see breaking changes and perhaps this is even easier to get integrated with (e.g. importing types in the config).

What do you think?

ScreamZ commented 1 year ago

@lamuertepeluda Your code isn't working, there are few typescript validation errors, also a lot of failing statement import config from "./ckeditor"; does not point to any files.

Can you paste an updated version, please, after some tests I'm still struggling and I get white screen on adding a new field ? (Minimum reproducible)

lamuertepeluda commented 1 year ago

@ScreamZ can you check that you have this folder structure please? Maybe the paths in the previous message were slightly inaccurate. But this is definitely working for me.

image

Content of src/extensions/ckeditor5/strapi-server.ts

// Override plugin service as per https://docs.strapi.io/dev-docs/plugins-extension#extending-a-plugin-s-interface
import config from "./ckeditor";

export default (plugin) => {
  plugin.services.config = ({ strapi }) => ({
    getUploadConfig(name) {
      return strapi.plugin("upload").service(name) ?? {};
    },
    getCKEditorConfig: () => {
      // Wrap in a closure
      return `(${config.toString()})()`;
    },
  });
  return plugin;
};

Content of src/extensions/ckeditor5/ckeditor.ts (or config.ts if you prefer, but then change the import above)

I'll pass mine here as an example. The new profile name in strapi is "custom". Keep in mind that this is but one of several possible CKEditor 5 configurations

const config = () => {
  const CKEditor5 = globalThis.CKEditor5;
  // Same base plugins as in @_sh/strapi-plugin-ckeditor/admin/src/components/Input/CKEditor/configs/base.js
  // toolbarEditorConfig basePlugins). Those commented out may be incompatible  with others
  const basePlugins = [
    CKEditor5.alignment.Alignment,
    CKEditor5.autoformat.Autoformat,
    CKEditor5.image.AutoImage,
    // CKEditor5.link.AutoLink,
    // CKEditor5.autosave.Autosave,
    // CKEditor5.ui.BalloonToolbar,
    CKEditor5.blockQuote.BlockQuote,
    // CKEditor5.ui.BlockToolbar,
    CKEditor5.basicStyles.Bold,
    CKEditor5.basicStyles.Code,
    CKEditor5.codeBlock.CodeBlock,
    // CKEditor5.htmlSupport.DataFilter,
    // CKEditor5.htmlSupport.DataSchema,
    CKEditor5.list.DocumentList,
    CKEditor5.list.DocumentListProperties,
    CKEditor5.essentials.Essentials,
    // CKEditor5.findAndReplace.FindAndReplace,
    // CKEditor5.fontWithPicker.FontBackgroundColor,
    // CKEditor5.fontWithPicker.FontColor,
    CKEditor5.fontWithPicker.FontFamily,
    CKEditor5.fontWithPicker.FontSize,
    CKEditor5.htmlSupport.GeneralHtmlSupport,
    CKEditor5.heading.Heading,
    // CKEditor5.heading.HeadingButtonsUI,
    // CKEditor5.highlight.Highlight,
    CKEditor5.horizontalLine.HorizontalLine,
    // CKEditor5.htmlSupport.HtmlComment,
    CKEditor5.htmlEmbed.HtmlEmbed,
    CKEditor5.image.Image,
    CKEditor5.image.ImageCaption,
    CKEditor5.image.ImageInsert,
    CKEditor5.image.ImageResize,
    CKEditor5.image.ImageStyle,
    CKEditor5.image.ImageToolbar,
    CKEditor5.image.ImageUpload,
    CKEditor5.indent.Indent,
    CKEditor5.indent.IndentBlock,
    CKEditor5.basicStyles.Italic,
    // CKEditor5.link.Link,
    // CKEditor5.link.LinkImage,
    // CKEditor5.list.List,
    // CKEditor5.list.ListProperties,
    CKEditor5.mediaEmbed.MediaEmbed,
    // CKEditor5.mediaEmbed.MediaEmbedToolbar,
    // CKEditor5.mention.Mention,
    CKEditor5.pageBreak.PageBreak,
    CKEditor5.paragraph.Paragraph,
    // CKEditor5.paragraph.ParagraphButtonUI,
    CKEditor5.pasteFromOffice.PasteFromOffice,
    CKEditor5.removeFormat.RemoveFormat,
    CKEditor5.sourceEditing.SourceEditing,
    CKEditor5.specialCharacters.SpecialCharacters,
    CKEditor5.specialCharacters.SpecialCharactersArrows,
    CKEditor5.specialCharacters.SpecialCharactersCurrency,
    // CKEditor5.specialCharacters.SpecialCharactersEssentials,
    CKEditor5.specialCharacters.SpecialCharactersLatin,
    CKEditor5.specialCharacters.SpecialCharactersMathematical,
    CKEditor5.specialCharacters.SpecialCharactersText,
    CKEditor5.strapiPlugins.StrapiMediaLib,
    CKEditor5.strapiPlugins.StrapiUploadAdapter,
    CKEditor5.basicStyles.Strikethrough,
    CKEditor5.style.Style,
    CKEditor5.basicStyles.Subscript,
    CKEditor5.basicStyles.Superscript,
    CKEditor5.table.Table,
    CKEditor5.table.TableCaption,
    CKEditor5.table.TableCellProperties,
    CKEditor5.table.TableColumnResize,
    CKEditor5.table.TableProperties,
    CKEditor5.table.TableToolbar,
    // CKEditor5.language.TextPartLanguage,
    // CKEditor5.list.TodoList,
    CKEditor5.basicStyles.Underline,
    CKEditor5.wordCount.WordCount,
  ];

  // Extended from @_sh/strapi-plugin-ckeditor/admin/src/components/Input/CKEditor/configs/base.js toolbarEditorConfig
  const toolbar = [
    {
      label: " ",
      tooltip: "Formatting",
      icon: "paragraph",
      items: ["heading", /* "style",  */ "SourceEditing"],
    },
    "|",
    {
      label: " ",
      tooltip: null,
      icon: "text",
      items: [
        "bold",
        "italic",
        "fontSize",
        "fontFamily",
        "fontColor",
        "fontBackgroundColor",
      ],
    },
    {
      label: " ",
      tooltip: null,
      icon: `
        <svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
        <rect x="0" fill="none" width="24" height="24"/>
        <g>
        <path d="M14.348 12H21v2h-4.613c.24.515.368 1.094.368 1.748 0 1.317-.474 2.355-1.423 3.114-.947.76-2.266 1.138-3.956 1.138-1.557 0-2.934-.293-4.132-.878v-2.874c.985.44 1.818.75 2.5.928.682.18 1.306.27 1.872.27.68 0 1.2-.13 1.562-.39.363-.26.545-.644.545-1.158 0-.285-.08-.54-.24-.763-.16-.222-.394-.437-.704-.643-.18-.12-.483-.287-.88-.49H3v-2H14.347zm-3.528-2c-.073-.077-.143-.155-.193-.235-.126-.202-.19-.44-.19-.713 0-.44.157-.795.47-1.068.313-.273.762-.41 1.348-.41.492 0 .993.064 1.502.19.51.127 1.153.35 1.93.67l1-2.405c-.753-.327-1.473-.58-2.16-.76-.69-.18-1.414-.27-2.173-.27-1.544 0-2.753.37-3.628 1.108-.874.738-1.312 1.753-1.312 3.044 0 .302.036.58.088.848h3.318z"/>
        </g>
        </svg>`,
      items: ["underline", "strikethrough", "superscript", "subscript"],
    },
    "removeFormat",
    "|",
    "alignment",
    "outdent",
    "indent",
    "|",
    "bulletedList",
    "numberedList",
    "|",
    "insertImage",
    "mediaEmbed",
    "strapiMediaLib",
    "link",
    "blockquote",
    "insertTable",
    "specialCharacters",
    "htmlEmbed",
    "codeBlock",
    "|",
    "horizontalLine",
    "pageBreak",
    "|",
    "|",
    "undo",
    "redo",
    "|",
  ];

  globalThis.CKEditorConfig = {
    theme: {
      // set theme always light
      light: "light",
      dark: "light",
    },
    configs: {
      // define a custom editor preset based on toolbar editor preset
      custom: {
        field: {
          key: "custom",
          value: "custom",
          metadatas: {
            intlLabel: {
              id: "ckeditor.preset.custom.label",
              defaultMessage: "Custom version",
            },
          },
        },
        editorConfig: {
          plugins: [
            ...basePlugins,
            // Custom plugins
            CKEditor5["enhancedLink"].Link,
          ],
          toolbar,
          image: {
            toolbar: [
              "imageStyle:inline",
              "imageStyle:block",
              "imageStyle:side",
              "|",
              "toggleImageCaption",
              "imageTextAlternative",
            ],
          },
          /* By default, for plugin's UI will use
                    the language defined in this file
                    or the preferred language from strapi's user config
                    and 'en' as a fallback.
                    language.ui -> preferred language -> 'en' */

          /* For content it will use language based on i18n (if! ignorei18n)
                    or language.content defined here
                    and it will use UI language as a fallback.
                    ignorei18n ? language.content : i18n; -> language.ui */

          language: {
            ignorei18n: true,
            ui: "en",
          },

        },
      },
    },
  };
};
export default config;

About TS validation errors, here's my tsconfig.json (kinda the default one, if you ask me)

{
  "extends": "@strapi/typescript-utils/tsconfigs/server",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": ".",
    "sourceMap": true
  },
  "include": ["./", "./**/*.ts", "./**/*.js", "src/**/*.json"],
  "exclude": [
    "node_modules/",
    "build/",
    "dist/",
    ".cache/",
    ".tmp/",
    "src/admin/",
    "**/*.test.*",
    "plugins/**"
  ]
}
renanhrocha commented 9 months ago

Code_4zvdqflVms I did this whole process, using the same folder architecture, but I'm getting an error: Code_4zvdqflVms :Error: Could not find Custom Field: plugin::ckeditor5.CKEditor at Object.get (C:\Users\rhr_c\OneDrive\Área de Trabalho\ckeditor\strapi\node_modules\@strapi\strapi\dist\core\registries\custom-fields.js:31:23) at convertCustomFieldType (C:\Users\rhr_c\OneDrive\Área de Trabalho\ckeditor\strapi\node_modules\@strapi\strapi\dist\utils\convert-custom-field-type.js:13:75) at Strapi.register (C:\Users\rhr_c\OneDrive\Área de Trabalho\ckeditor\strapi\node_modules\@strapi\strapi\dist\Strapi.js:430:49) at async Strapi.load (C:\Users\rhr_c\OneDrive\Área de Trabalho\ckeditor\strapi\node_modules\@strapi\strapi\dist\Strapi.js:493:9) at async workerProcess (C:\Users\rhr_c\OneDrive\Área de Trabalho\ckeditor\strapi\node_modules\@strapi\strapi\dist\commands\actions\develop\action.js:100:28) at async exports.default (C:\Users\rhr_c\OneDrive\Área de Trabalho\ckeditor\strapi\node_modules\@strapi\strapi\dist\commands\actions\develop\action.js:38:20)

mansisindhu commented 9 months ago

I have tried the same thing, created my own custom plugin. But when I try to add it in my custom plugins, I'm getting errors. Even the src/extensions/ckeditor5/strapi-server.ts file is not being executing. Tried multiple things, but nothing worked.