Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
lamuertepeluda opened this issue Apr 13, 2023 · 10 comments
Labels
issue: enhancement Issue suggesting an enhancement to an existing feature priority: low

Comments

@lamuertepeluda
Copy link
Contributor

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
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
Copy link

ScreamZ commented May 16, 2023

@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
Copy link
Contributor Author

@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
Copy link

renanhrocha commented Sep 29, 2023

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
Copy link

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.

@nshenderov nshenderov added the issue: enhancement Issue suggesting an enhancement to an existing feature label Aug 4, 2024
@bogdankorshunov
Copy link

@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
Copy link
Contributor Author

@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
Copy link

@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
Copy link
Contributor Author

@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
Copy link

bogdankorshunov commented Oct 17, 2024

@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
Copy link
Contributor Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
issue: enhancement Issue suggesting an enhancement to an existing feature priority: low
Projects
None yet
Development

No branches or pull requests

6 participants