nshenderov / strapi-plugin-ckeditor

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

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

Closed lamuertepeluda closed 3 months 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 1 year 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 1 year 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.

bogdankorshunov commented 1 month ago

@lamuertepeluda I'm new to strapi and I couldn't set it up according to your guide. Can you explain in more detail what the problem might be? Maybe you registered the config as an additional plugin or used middleware?

lamuertepeluda commented 1 month ago

@lamuertepeluda I'm new to strapi and I couldn't set it up according to your guide. Can you explain in more detail what the problem might be? Maybe you registered the config as an additional plugin or used middleware?

Hi @bogdankorshunov, it's almost 2 years since my original post. In the meanwhile Strapi moved on, and so did I, meaning that I am not using Strapi anymore, almost since then, and Strapi got several updates, and so did CKEditor. So I couldn't tell now if, using my proposed configuration, this would still work. But at the time, following those instructions, you could have updated CKEditor and have TS in the configuration file of the plain text one

bogdankorshunov commented 1 month ago

@lamuertepeluda Thanks for the reply. And why don't you use it anymore, if it's not a secret. Some alternative to strapi and ckeditor?

lamuertepeluda commented 1 month ago

@bogdankorshunov no secret at all 😄 : I changed job and in my current company I don't work on projects with Strapi. We use Strapi in my current company too, but I am pretty sure we don't use CKEditor as we have different use cases that do not require and advanced text editor like that.

bogdankorshunov commented 1 month ago

@lamuertepeluda And what are the other use cases?😊 I'm just trying to set up ckeditor right now, but I think the implementation will really be redundant. In Rich Text, for example, there is no table in ckeditor, well, there are other functions

lamuertepeluda commented 1 month ago

@bogdankorshunov we have now much simpler use cases. Like bold, italic and that's it. No tables, highlighting whatsoever, and all the advanced features the CKEditor offers wrt the base RichText embedded with Strapi