@_sh/strapi-plugin-ckeditor
Version:
Integrates CKEditor 5 into your Strapi project as a fully customizable custom field. (Community Edition)
1,328 lines (1,252 loc) • 38.5 kB
JavaScript
import * as yup from "yup";
import { css, styled } from "styled-components";
import { Plugin, ButtonView, FileRepository, logWarning, Alignment, Autoformat, AutoImage, BalloonToolbar, BlockQuote, Bold, Code, CodeBlock, Essentials, FontBackgroundColor, FontColor, FontFamily, FontSize, GeneralHtmlSupport, Heading, HorizontalLine, HtmlEmbed, Image, ImageCaption, ImageInsert, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Indent, IndentBlock, Italic, List, ListProperties, Link, LinkImage, MediaEmbed, Paragraph, PageBreak, PasteFromOffice, PictureEditing, RemoveFormat, SourceEditing, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, ShowBlocks, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableToolbar, TodoList, Underline, WordCount, IconIndent, Markdown, TextTransformation } from "ckeditor5";
import * as sanitizeHtml from "sanitize-html";
import { jsx, jsxs } from "react/jsx-runtime";
import { Flex, lightTheme } from "@strapi/design-system";
import cloneDeep from "lodash/cloneDeep";
const name = "@_sh/strapi-plugin-ckeditor";
const version = "6.0.1";
const description = "Integrates CKEditor 5 into your Strapi project as a fully customizable custom field. (Community Edition)";
const keywords = [
"strapi",
"ckeditor",
"ckeditor5",
"ckeditor 5",
"wysiwyg",
"rich text",
"editor"
];
const author = {
name: "nshenderov"
};
const homepage = "https://market.strapi.io/plugins/@_sh-strapi-plugin-ckeditor";
const repository = {
type: "git",
url: "https://github.com/nshenderov/strapi-plugin-ckeditor.git"
};
const strapi = {
name: "ckeditor5",
displayName: "CKEditor 5",
description: "Integrates CKEditor 5 into your Strapi project as a fully customizable custom field. (Community Edition)",
kind: "plugin"
};
const type = "commonjs";
const exports = {
"./package.json": "./package.json",
".": {
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"
},
"./strapi-admin": {
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"
},
"./strapi-server": {
types: "./dist/server/src/index.d.ts",
source: "./server/src/index.ts",
"import": "./dist/server/index.mjs",
require: "./dist/server/index.js",
"default": "./dist/server/index.js"
}
};
const files = [
"dist"
];
const scripts = {
build: "strapi-plugin build",
watch: "strapi-plugin watch",
"watch:link": "strapi-plugin watch:link",
verify: "strapi-plugin verify",
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
"test:ts:back": "run -T tsc -p server/tsconfig.json",
release: "release-it",
"release:info": "release-it --changelog"
};
const dependencies = {
"@ckeditor/ckeditor5-react": "~9.5.0",
"@strapi/design-system": "2.0.0-rc.18",
"@strapi/icons": "2.0.0-rc.18",
ckeditor5: "~45.2.0",
lodash: "4.17.21",
"sanitize-html": "2.13.0",
yup: "0.32.9"
};
const devDependencies = {
"@release-it/conventional-changelog": "10.0.0",
"@strapi/sdk-plugin": "5.3.2",
"@strapi/strapi": "5.11.0",
"@strapi/typescript-utils": "5.11.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/sanitize-html": "2.13.0",
"@typescript-eslint/eslint-plugin": "7.0.0",
"@typescript-eslint/parser": "7.0.0",
eslint: "8.2.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "18.0.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.25.3",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.3.0",
prettier: "3.4.0",
react: "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.30.0",
"release-it": "18.1.2",
"styled-components": "6.1.15",
typescript: "5.6.3"
};
const peerDependencies = {
"@strapi/strapi": "^5.0.0",
react: "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.0.0",
"styled-components": "^6.0.0"
};
const engines = {
node: ">=18.0.0 <=22.x.x",
npm: ">=6.0.0"
};
const license = "MIT";
const pluginPkg = {
name,
version,
description,
keywords,
author,
homepage,
repository,
strapi,
type,
exports,
files,
scripts,
dependencies,
devDependencies,
peerDependencies,
engines,
license
};
const PLUGIN_ID = pluginPkg.strapi.name;
function isImageResponsive(imgFormats) {
const formats = Object.keys(imgFormats);
const isResponsive = !(formats.length === 1 && formats[0] === "thumbnail");
return isResponsive;
}
function prefixFileUrlWithBackendUrl(fileURL) {
return !!fileURL && fileURL.startsWith("/") ? `${window.strapi.backendURL}${fileURL}` : fileURL;
}
const colors = css`
${({ theme }) => css`
:root {
/* -- Generic colors ---------------------------------------------------------------------- */
--ck-color-focus-outer-shadow: ${theme.colors.primary600}1F ;
--ck-color-focus-disabled-shadow: ${theme.colors.primary200} ;
--ck-focus-ring: 1px solid ${theme.colors.primary600}9F ;
--ck-color-button-default-hover-background: ${theme.colors.primary100} ;
}
.ck {
/* -- Generic colors ---------------------------------------------------------------------- */
--ck-color-base-focus: ${theme.colors.primary200};
--ck-color-base-active: ${theme.colors.primary500}8F;
--ck-color-base-active-focus: ${theme.colors.primary600};
--ck-color-editor-base-text: ${theme.colors.neutral800};
--ck-color-base-border: ${theme.colors.neutral200};
--ck-color-base-background: ${theme.colors.neutral0};
--ck-custom-foreground: ${theme.colors.neutral200};
--ck-color-base-foreground: var(--ck-color-base-background);
--ck-color-focus-border: ${theme.colors.primary500};
--ck-color-text: ${theme.colors.neutral800};
--ck-custom-white: hsl(0, 0%, 100%);
/* -- Buttons ----------------------------------------------------------------------------- */
--ck-color-button-default-background: var(--ck-color-base-background);
--ck-color-button-default-hover-background: ${theme.colors.primary100};
--ck-color-button-default-active-background: ${theme.colors.primary100};
--ck-color-button-default-active-shadow: ${theme.colors.primary100};
--ck-color-button-default-disabled-background: var(--ck-color-base-background);
--ck-color-button-on-color: ${theme.colors.primary600};
--ck-color-button-on-background: ${theme.colors.primary100};
--ck-color-button-on-hover-background: ${theme.colors.primary200};
--ck-color-button-on-active-background: ${theme.colors.primary100};
--ck-color-button-on-disabled-background: var(--ck-custom-foreground);
--ck-color-button-on-active-shadow: ${theme.colors.primary200};
--ck-color-button-action-background: ${theme.colors.success500}E5;
--ck-color-button-action-hover-background: ${theme.colors.success500}F5;
--ck-color-button-action-active-background: ${theme.colors.success500};
--ck-color-button-action-disabled-background: ${theme.colors.success500}99;
--ck-color-button-action-text: var(--ck-custom-white);
--ck-color-switch-button-off-background: ${theme.colors.neutral400};
--ck-color-switch-button-off-hover-background: ${theme.colors.neutral500};
--ck-color-switch-button-on-background: var(--ck-color-button-action-background);
--ck-color-switch-button-on-hover-background: var(--ck-color-button-action-hover-background);
--ck-color-button-save: ${theme.colors.success500};
--ck-color-button-cancel: ${theme.colors.danger500};
--ck-color-split-button-hover-background: var(--ck-color-button-default-hover-background);
--ck-color-split-button-hover-border: var(--ck-custom-foreground);
/* -- Dropdown ---------------------------------------------------------------------------- */
--ck-color-dropdown-panel-background: var(--ck-color-base-background);
--ck-color-dropdown-panel-border: var(--ck-custom-foreground);
/* -- Input ------------------------------------------------------------------------------- */
--ck-color-input-background: var(--ck-color-base-background);
--ck-color-input-border: var(--ck-color-base-border);
--ck-color-input-text: var(--ck-color-editor-base-text);
--ck-color-input-disabled-background: ${theme.colors.neutral100};
--ck-color-input-disabled-border: ${theme.colors.neutral150};
--ck-color-input-disabled-text: ${theme.colors.neutral400};
--ck-color-labeled-field-label-background: var(--ck-color-base-background);
/* -- List -------------------------------------------------------------------------------- */
--ck-color-list-background: var(--ck-color-base-background);
--ck-color-list-button-hover-background: ${theme.colors.primary100};
--ck-color-list-button-on-background: var(--ck-color-base-active);
--ck-color-list-button-on-background-focus: var(--ck-color-base-active-focus);
/* -- Panel ------------------------------------------------------------------------------- */
--ck-color-panel-background: var(--ck-color-base-background);
--ck-color-panel-border: var(--ck-color-base-border);
--ck-style-panel-button-label-background: ${theme.colors.neutral100};
--ck-style-panel-button-hover-label-background: ${theme.colors.neutral150};
--ck-style-panel-button-hover-border-color: var(--ck-color-base-border);
/* -- Toolbar ----------------------------------------------------------------------------- */
--ck-color-toolbar-background: var(--ck-color-base-background);
--ck-color-toolbar-border: var(--ck-color-base-border);
/* -- Tooltip ----------------------------------------------------------------------------- */
--ck-color-tooltip-background: ${theme.colors.neutral800};
--ck-color-tooltip-text: ${theme.colors.neutral100};
/* -- Image ------------------------------------------------------------------------------- */
--ck-color-image-caption-background: ${theme.colors.neutral100};
--ck-color-image-caption-text: ${theme.colors.neutral800};
/* -- Widget ------------------------------------------------------------------------------ */
--ck-color-widget-blurred-border: ${theme.colors.neutral400};
--ck-color-widget-hover-border: ${theme.colors.neutral300};
--ck-color-widget-editable-focus-background: ${theme.colors.neutral150};
--ck-color-widget-type-around-button-active: var(--ck-color-focus-border);
--ck-color-widget-type-around-button-hover: ${theme.colors.neutral300};
/* -- Link -------------------------------------------------------------------------------- */
--ck-color-link-fake-selection: ${theme.colors.primary200};
--ck-color-link-selected-background: ${theme.colors.primary200};
/* -- Dialog ------------------------------------------------------------------------------ */
--ck-color-dialog-background: var(--ck-color-base-background);
--ck-color-dialog-form-header-border: var(--ck-color-base-border);
/* -- PoweredBy --------------------------------------------------------------------------- */
--ck-powered-by-background: transparrent;
}
`}
`;
const light = css`
${colors}
`;
const dark = css`
${colors}
.ck.ck-powered-by > a > svg > path:nth-child(3) {
fill: rgb(172, 156, 251);
}
`;
const reset = css`
${({ theme }) => css`
.ck {
--ck-color-image-caption-background: hsl(0, 0%, 97%);
--ck-color-image-caption-text: hsl(0, 0%, 20%);
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
--ck-color-mention-text: hsl(341, 100%, 30%);
--ck-color-table-caption-background: hsl(0, 0%, 97%);
--ck-color-table-caption-text: hsl(0, 0%, 20%);
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
--ck-highlight-marker-green: hsl(120, 93%, 68%);
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
--ck-highlight-pen-green: hsl(112, 100%, 27%);
--ck-highlight-pen-red: hsl(0, 85%, 49%);
--ck-image-style-spacing: 1.5em;
--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);
--ck-todo-list-checkmark-size: 16px;
--ck-border-radius: ${theme.borderRadius};
font-size: ${theme.fontSizes[2]};
}
.ck.ck-reset.ck-dropdown__panel.ck-dropdown__panel_sw.ck-dropdown__panel-visible {
border-radius: var(--ck-border-radius);
}
.ck-editor__main {
--ck-font-face: 'Source Sans Pro', system-ui, Roboto, 'Helvetica Neue', 'Helvetica', Arial,
sans-serif;
color: var(--ck-color-editor-base-text);
font-family: var(--ck-font-face);
* {
font: revert;
margin: revert;
}
h1 {
font-size: 2.3em;
}
h2 {
font-size: 1.84em;
}
h3 {
font-size: 1.48em;
}
h4 {
font-size: 1.22em;
}
h5 {
font-size: 1.06em;
}
h6 {
font-size: 1em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.2em;
padding-top: 0.8em;
margin-bottom: 0.4em;
}
blockquote,
ol,
p,
ul {
font-size: 1em;
line-height: 1.6em;
padding-top: 0.2em;
margin-bottom: var(--ck-spacing-large);
}
figcaption {
background-color: var(--ck-color-image-caption-background);
caption-side: bottom;
color: var(--ck-color-image-caption-text);
display: table-caption;
font-size: 0.75em;
outline-offset: -1px;
padding: 0.6em;
word-break: break-word;
}
a {
text-decoration: none;
color: #1b3af2;
}
a:hover {
text-decoration: underline;
}
.table {
margin: 0;
}
ul.todo-list {
list-style: none;
margin: revert;
color: revert;
font-family: revert;
margin-left: 2rem;
}
ul,
ol {
list-style: initial;
margin-left: 2rem;
}
ol {
list-style: decimal;
}
sub {
vertical-align: sub;
}
sup {
vertical-align: super;
}
}
`}
`;
const plugin = css`
${({ theme }) => css`
.ck {
scrollbar-width: thin;
scrollbar-color: ${`${theme.colors.neutral300} ${theme.colors.neutral150}`};
@-moz-document url-prefix() {
scrollbar-width: auto;
}
}
.ck.ck-content.ck-editor__editable {
line-height: initial;
height: auto;
min-height: var(--ck-editor-min-height);
border: none ;
max-width: var(--ck-editor-max-width) ;
margin: 0 auto;
&.ck-focused:not(.ck-editor__nested-editable) {
box-shadow: none;
}
}
.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content {
border: none ;
}
.ck.ck-editor__main {
min-height: var(--ck-editor-min-height) ;
max-height: var(--ck-editor-max-height) ;
overflow-y: auto;
overflow-x: hidden;
border-top: 1px solid var(--ck-color-base-border);
border-bottom-left-radius: var(--ck-border-radius);
border-bottom-right-radius: var(--ck-border-radius);
}
.ck .ck-source-editing-area,
.ck .ck-source-editing-area textarea {
color: var(--ck-color-text);
background-color: var(--ck-color-base-background);
border: none ;
box-shadow: none ;
min-height: var(--ck-editor-min-height);
max-width: var(--ck-editor-max-width);
margin: 0 auto;
}
.ck .ck-block-toolbar-button {
min-width: 0 ;
min-height: 0 ;
width: 20px ;
height: 25px ;
margin-left: -2px ;
& svg {
color: var(--ck-color-text) ;
position: absolute;
width: 20px;
height: 20px;
}
}
.ck-word-count {
display: flex;
position: absolute;
justify-content: end;
gap: 0.3rem;
font-size: 1rem;
font-weight: 500;
text-transform: lowercase;
z-index: 2;
bottom: -2rem;
right: 0;
}
.ck[dir='rtl'] {
.ck-block-toolbar-button {
margin-left: 2px ;
}
& + div {
justify-content: flex-start;
& > .ck-word-count {
& > div:first-child {
order: 2;
}
& > div:last-child {
order: 1;
}
}
}
}
.ck.ck-editor__editable > .ck-placeholder::before {
color: var(--ck-color-editor-base-text);
opacity: 0.65;
}
.ck.ck-powered-by > a > svg > path:first-child {
fill: ${theme.colors.neutral800};
}
`}
`;
const expanded = css`
.ck-editor__expanded {
.ck.ck-content.ck-editor__editable,
.ck-source-editing-area {
min-height: 100% ;
height: auto ;
max-height: none ;
border-radius: var(--ck-border-radius) ;
border: 1px solid var(--ck-color-base-border) ;
overflow: auto ;
box-sizing: border-box;
}
.ck.ck-editor__top {
box-shadow: 0 0 5px hsla(0, 0%, 0%, 0.2);
z-index: var(--ck-z-panel);
}
.ck.ck-editor {
display: flex;
flex-direction: column;
}
.ck.ck-editor,
.ck.ck-content,
.ck.ck-editor__main {
height: calc(100% - 0px) ;
}
.ck.ck-editor__main {
min-height: none ;
max-height: none ;
overflow-y: scroll ;
padding: calc(2 * var(--ck-spacing-large));
}
.ck-word-count {
bottom: 0.3rem;
right: 1.2rem;
}
}
`;
const common = css`
${reset}
${plugin}
${expanded}
`;
const editorSizes = css`
:root {
--ck-editor-full-screen-box-max-width: 1700px;
}
.ck {
--ck-editor-max-width: 1366px;
--ck-editor-min-height: 200px;
--ck-editor-max-height: 500px;
}
`;
const additional = css`
${editorSizes}
`;
const defaultTheme = {
light,
dark,
common,
additional
};
const mediaLibIcon = '<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.3.6a.9.9 0 100 1.8h15.311a.9.9 0 100-1.8H4.301zm17.1 3.7A1.6 1.6 0 0123 5.9v15.5a1.6 1.6 0 01-1.6 1.6H2.6A1.601 1.601 0 011 21.4V8 5.915C1 5.03 1.716 4.3 2.6 4.3h18.8zM5.032 19.18h14.336l-3.136-3.205-1.792 1.831-4.032-4.12-5.376 5.494zm13.44-8.697c0 1.282-.985 2.289-2.24 2.289-1.254 0-2.24-1.007-2.24-2.29 0-1.281.986-2.288 2.24-2.288 1.255 0 2.24 1.007 2.24 2.289z"></path></svg>';
class StrapiMediaLib extends Plugin {
/**
* Strapi function used to show media library modal.
* Should be provided via connect method before using toggle method.
*/
strapiToggle = null;
static get pluginName() {
return "StrapiMediaLib";
}
init() {
this.editor.ui.componentFactory.add("strapiMediaLib", () => {
const button = new ButtonView();
button.set({
label: "Media Library",
icon: mediaLibIcon,
tooltip: true
});
button.on("execute", this.toggle.bind(this));
return button;
});
}
connect(strapiToggle) {
if (typeof strapiToggle !== "function") {
throw new Error("The input parameter for toggle must be a function.");
}
this.strapiToggle = strapiToggle;
}
toggle() {
if (typeof this.strapiToggle !== "function") {
throw new Error(
"The Strapi Media Library toggle function is not connected. Use the connect function first."
);
}
this.strapiToggle();
}
}
class StrapiUploadAdapter extends Plugin {
static get requires() {
return [FileRepository];
}
static get pluginName() {
return "StrapiUploadAdapter";
}
init() {
}
initAdapter(config) {
if (!config.uploadUrl) {
logWarning("strapi-upload-adapter-missing-uploadurl");
return;
}
this.editor.plugins.get(FileRepository).createUploadAdapter = (loader) => new Adapter(loader, config);
}
}
class Adapter {
/**
* FileLoader instance to use during the upload.
*/
loader;
/**
* The configuration of the adapter.
*/
config;
xhr;
/**
* Creates a new adapter instance.
*/
constructor(loader, config) {
this.loader = loader;
this.config = config;
}
/**
* Starts the upload process.
*/
upload() {
return this.loader.file.then(
(file) => new Promise((resolve, reject) => {
this.initRequest();
this.initListeners(resolve, reject, file);
this.sendRequest(file);
})
);
}
/**
* Aborts the upload process.
*/
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
/**
* Initializes the `XMLHttpRequest` object using the URL specified as
* `strapiUpload.uploadUrl` in the editor's
* configuration.
*/
initRequest() {
const xhr = new XMLHttpRequest();
this.xhr = xhr;
xhr.open("POST", this.config.uploadUrl, true);
xhr.responseType = "json";
}
/**
* Initializes XMLHttpRequest listeners
*
* resolve - Callback function to be called when the request is successful.
* reject - Callback function to be called when the request cannot be completed.
* file - Native File object.
*/
initListeners(resolve, reject, file) {
const xhr = this.xhr;
const { loader } = this;
const genericErrorText = `Couldn't upload file: ${file.name}.`;
xhr.addEventListener("error", () => reject(genericErrorText));
xhr.addEventListener("abort", () => reject());
xhr.addEventListener("load", () => {
const { response } = xhr;
if (!Array.isArray(response) || typeof response === "object" && "error" in response || response.length !== 1) {
return reject(
response && response.error && response.error.message ? response.error.message : genericErrorText
);
}
const { name: name2, url, alternativeText, formats } = response[0];
const urls = { default: prefixFileUrlWithBackendUrl(url) };
const alt = alternativeText || name2;
if (formats && isImageResponsive(formats)) {
const sortedFormatsKeys = Object.keys(formats).sort(
(a, b) => formats[a].width - formats[b].width
);
sortedFormatsKeys.forEach((k) => {
urls[formats[k].width] = prefixFileUrlWithBackendUrl(formats[k].url);
});
}
resolve(url ? { alt, urls } : {});
return void 0;
});
if (xhr.upload) {
xhr.upload.addEventListener("progress", (evt) => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}
/**
* Prepares the data and sends the request.
*
* File - instance to be uploaded.
*/
sendRequest(file) {
const headers = this.config.headers || {};
const withCredentials = this.config.withCredentials || false;
Object.keys(headers).forEach((headerName) => {
this.xhr.setRequestHeader(headerName, headers[headerName]);
});
this.xhr.withCredentials = withCredentials;
const data = new FormData();
data.append("files", file);
this.xhr.send(data);
}
}
const editorConfig$1 = {
licenseKey: "GPL",
plugins: [
Alignment,
Autoformat,
AutoImage,
BalloonToolbar,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
HorizontalLine,
HtmlEmbed,
Image,
ImageCaption,
ImageInsert,
ImageResize,
ImageStyle,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
List,
ListProperties,
Link,
LinkImage,
MediaEmbed,
Paragraph,
PageBreak,
PasteFromOffice,
PictureEditing,
RemoveFormat,
SourceEditing,
SpecialCharacters,
SpecialCharactersEssentials,
Strikethrough,
Style,
Subscript,
Superscript,
ShowBlocks,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TodoList,
Underline,
WordCount,
StrapiMediaLib,
StrapiUploadAdapter
],
toolbar: [
"showBlocks",
"|",
"heading",
"|",
"fontSize",
"fontFamily",
"alignment",
{
label: "Indentation",
icon: IconIndent,
items: ["outdent", "indent"]
},
"bulletedList",
"numberedList",
"todoList",
"link",
"mediaEmbed",
"insertImage",
"strapiMediaLib",
"blockquote",
"insertTable",
"codeBlock",
"htmlEmbed",
"SourceEditing",
"specialCharacters",
"horizontalLine",
"pageBreak",
"|",
"undo",
"redo"
],
balloonToolbar: [
"bold",
"italic",
"fontColor",
"fontBackgroundColor",
{
label: "Other formatting options",
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"
],
fontFamily: {
supportAllValues: true
},
fontSize: {
options: [10, 12, 14, "default", 18, 20, 22],
supportAllValues: true
},
heading: {
options: [
{ model: "paragraph", title: "Paragraph", class: "ck-heading_paragraph" },
{
model: "heading1",
view: "h1",
title: "Heading 1",
class: "ck-heading_heading1"
},
{
model: "heading2",
view: "h2",
title: "Heading 2",
class: "ck-heading_heading2"
},
{
model: "heading3",
view: "h3",
title: "Heading 3",
class: "ck-heading_heading3"
},
{
model: "heading4",
view: "h4",
title: "Heading 4",
class: "ck-heading_heading4"
},
{
model: "heading5",
view: "h5",
title: "Heading 5",
class: "ck-heading_heading5"
},
{
model: "heading6",
view: "h6",
title: "Heading 6",
class: "ck-heading_heading6"
}
]
},
htmlSupport: {
allow: [
{
name: /.*/,
attributes: true,
classes: true,
styles: true
}
],
disallow: [
{
attributes: [
{ key: /^on(.*)/i, value: true },
{
key: /.*/,
value: /(\b)(on\S+)(\s*)=|javascript:|(<\s*)(\/*)script/i
},
{ key: /.*/, value: /data:(?!image\/(png|jpeg|gif|webp))/i }
]
},
{ name: "script" }
]
},
htmlEmbed: {
showPreviews: true,
sanitizeHtml: (inputHtml) => {
const outputHtml = sanitizeHtml.default(inputHtml);
return {
html: outputHtml,
hasChanged: true
};
}
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true
}
},
table: {
contentToolbar: [
"tableColumn",
"tableRow",
"mergeTableCells",
"tableProperties",
"tableCellProperties",
"toggleTableCaption"
]
},
image: {
styles: {
options: [
"inline",
"alignLeft",
"alignRight",
"alignCenter",
"alignBlockLeft",
"alignBlockRight",
"block",
"side"
]
},
resizeOptions: [
{
name: "resizeImage:original",
label: "Default image width",
value: null
},
{
name: "resizeImage:50",
label: "50% page width",
value: "50"
},
{
name: "resizeImage:75",
label: "75% page width",
value: "75"
}
],
toolbar: [
"imageTextAlternative",
"toggleImageCaption",
"linkImage",
"|",
"imageStyle:inline",
"imageStyle:wrapText",
"imageStyle:breakText",
"imageStyle:side",
"|",
"resizeImage"
]
},
link: {
decorators: {
toggleDownloadable: {
mode: "manual",
label: "Downloadable",
attributes: {
download: "file"
}
}
},
addTargetToExternalLinks: true,
defaultProtocol: "https://"
},
ui: {
poweredBy: {
position: "inside",
side: "left",
label: "",
verticalOffset: 0,
horizontalOffset: 0
}
}
};
const defaultHtmlPreset = {
name: "defaultHtml",
description: "Default HTML editor",
editorConfig: editorConfig$1
};
const editorConfig = {
licenseKey: "GPL",
plugins: [
Autoformat,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
Heading,
HorizontalLine,
Image,
ImageToolbar,
ImageUpload,
ImageInsert,
Italic,
List,
Link,
Markdown,
Paragraph,
SourceEditing,
SpecialCharacters,
SpecialCharactersEssentials,
Strikethrough,
Table,
TableToolbar,
TextTransformation,
TodoList,
WordCount,
StrapiMediaLib,
StrapiUploadAdapter
],
toolbar: [
"sourceEditing",
"|",
"heading",
"|",
"bold",
"italic",
"fontColor",
"strikethrough",
"code",
"bulletedList",
"numberedList",
"todoList",
"link",
"insertImage",
"strapiMediaLib",
"blockQuote",
"insertTable",
"codeBlock",
"specialCharacters",
"horizontalLine",
"undo",
"redo"
],
heading: {
options: [
{ model: "paragraph", title: "Paragraph", class: "ck-heading_paragraph" },
{
model: "heading1",
view: "h1",
title: "Heading 1",
class: "ck-heading_heading1"
},
{
model: "heading2",
view: "h2",
title: "Heading 2",
class: "ck-heading_heading2"
},
{
model: "heading3",
view: "h3",
title: "Heading 3",
class: "ck-heading_heading3"
},
{
model: "heading4",
view: "h4",
title: "Heading 4",
class: "ck-heading_heading4"
},
{
model: "heading5",
view: "h5",
title: "Heading 5",
class: "ck-heading_heading5"
},
{
model: "heading6",
view: "h6",
title: "Heading 6",
class: "ck-heading_heading6"
}
]
},
image: {
toolbar: ["imageTextAlternative"]
},
table: {
contentToolbar: ["tableColumn", "tableRow", "mergeTableCells"]
},
ui: {
poweredBy: {
position: "inside",
side: "left",
label: "",
verticalOffset: 0,
horizontalOffset: 0
}
}
};
const defaultMarkdownPreset = {
name: "defaultMarkdown",
description: "Default Markdown editor",
editorConfig
};
const PLUGIN_CONFIG = {
presets: {
defaultHtml: defaultHtmlPreset,
defaultMarkdown: defaultMarkdownPreset
},
theme: defaultTheme
};
function setPluginConfig(userPluginConfig) {
const { presets: userPresets, theme: userTheme } = userPluginConfig || {};
if (userPresets) {
PLUGIN_CONFIG.presets = {};
userPresets.forEach((preset) => {
PLUGIN_CONFIG.presets[preset.name] = preset;
});
}
if (userTheme) {
PLUGIN_CONFIG.theme = userTheme;
}
}
function getPluginPresets() {
return PLUGIN_CONFIG.presets;
}
function getPluginTheme() {
return PLUGIN_CONFIG.presets;
}
function getPluginConfig() {
return PLUGIN_CONFIG;
}
function CKEditorIcon() {
return /* @__PURE__ */ jsx(IconBox, { justifyContent: "center", alignItems: "center", width: 7, height: 6, hasRadius: true, "aria-hidden": true, children: /* @__PURE__ */ jsx(SvgIcon, {}) });
}
function SvgIcon() {
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "-8 -7 37 37", children: [
/* @__PURE__ */ jsx(
"path",
{
d: "M15.04 16.726a3.98 3.98 0 0 0-1.535.524 3.96 3.96 0 0 0-1.402 1.364c-.108.18-.716 1.347-.716 1.347l-2.09 3.82.022.016c.097.063.2.116.308.159.317.113.65.175.987.18 1.264.058 2.529-.016 3.793-.007.725.023 1.45.005 2.172-.053.348-.017.687-.117.99-.29.31-.178.576-.423.78-.717.138-.205.283-.407.409-.62.6-1.02 1.199-2.043 1.794-3.065.187-.321.37-.644.555-.966l.281-.481c.236-.392.367-.838.381-1.294l-3.258.057s-3.147-.012-3.472.026Zm2.764.903Z",
fill: lightTheme.colors.secondary700
}
),
/* @__PURE__ */ jsx(
"path",
{
d: "m7.12 22.61 1.901-3.477.46-.877c-.31-.168-.614-.35-.918-.528-.866-.508-1.766-.957-2.613-1.498a2.459 2.459 0 0 1-.609-.517c-.27-.336-.341-.736-.362-1.15-.052-1.022-.003-2.045-.02-3.068-.01-.487 0-.975.005-1.462.01-.806.384-1.382 1.069-1.783L8.115 7.03c.55-.322 1.102-.642 1.654-.961.127-.073.263-.13.395-.192.68-.321 1.298-.119 1.9.213.039.02.075.045.112.068.306.149.605.313.895.491.794.445 1.587.893 2.378 1.343.239.139.469.292.688.458.485.36.636.875.666 1.445.039.71.017 1.422.013 2.134-.002.698.01 1.396.003 2.094 1.478-.006 3.146 0 3.146 0l1.807-.032c-.006-.73-.016-1.46-.017-2.19 0-1.31.037-2.62-.039-3.928-.061-1.05-.34-2-1.232-2.666a12.549 12.549 0 0 0-1.264-.848c-1.454-.834-2.91-1.664-4.37-2.49-.545-.308-1.067-.659-1.644-.91-.069-.043-.135-.089-.205-.128-1.106-.613-2.24-.992-3.485-.405-.242.115-.49.218-.723.352-1.011.58-2.02 1.166-3.026 1.757-1.271.744-2.54 1.488-3.81 2.234C.705 5.602.025 6.66.012 8.144c-.008.897-.02 1.794 0 2.691.039 1.884-.045 3.77.058 5.652.042.761.174 1.499.672 2.12.32.377.698.7 1.121.956 1.556 1.001 3.209 1.835 4.8 2.775l.457.271Z",
fill: lightTheme.colors.secondary600
}
)
] });
}
const IconBox = styled(Flex)`
background-color: ${lightTheme.colors.secondary100};
border: 1px solid ${lightTheme.colors.secondary200};
`;
const clonedDefaultTheme = cloneDeep(defaultTheme);
const clonedDefaultHtmlPreset = cloneDeep(defaultHtmlPreset);
const clonedDefaultMarkdownPreset = cloneDeep(defaultMarkdownPreset);
const AVAILABLE_OPTIONS = [];
function fillAvailableOptions() {
const { presets } = getPluginConfig();
Object.values(presets).forEach(({ name: name2, description: description2 }) => {
const option = {
key: name2,
value: name2,
metadatas: {
intlLabel: {
id: `${PLUGIN_ID}.preset.${name2}.label`,
defaultMessage: description2
}
}
};
AVAILABLE_OPTIONS.push(option);
});
}
const index = {
bootstrap() {
fillAvailableOptions();
},
async register(app) {
app.customFields.register({
name: "CKEditor",
type: "richtext",
pluginId: PLUGIN_ID,
icon: CKEditorIcon,
intlLabel: {
id: `${PLUGIN_ID}.label`,
defaultMessage: "CKEditor 5"
},
intlDescription: {
id: `${PLUGIN_ID}.description`,
defaultMessage: "The advanced rich text editor. (Community Edition)"
},
components: {
Input: async () => import("./Field-DwEixuI8.mjs").then((module) => ({
default: module.Field
}))
},
options: {
base: [
{
intlLabel: {
id: `${PLUGIN_ID}.preset.label`,
defaultMessage: "Preset"
},
description: {
id: `${PLUGIN_ID}.preset.description`,
defaultMessage: " "
},
name: "options.preset",
type: "select",
options: AVAILABLE_OPTIONS
}
],
advanced: [
{
name: "required",
type: "checkbox",
intlLabel: {
id: `${PLUGIN_ID}.required.label`,
defaultMessage: "Required field"
},
description: {
id: `${PLUGIN_ID}.required.description`,
defaultMessage: "You won't be able to create an entry if this field is empty"
}
},
{
sectionTitle: {
id: `${PLUGIN_ID}.options.advanced.limiters`,
defaultMessage: "Input limiters"
},
items: [
{
name: "options.maxLengthWords",
type: "checkbox-with-number-field",
intlLabel: {
id: `${PLUGIN_ID}.maxLengthWords.label`,
defaultMessage: "Words limit"
}
},
{
name: "options.maxLengthCharacters",
type: "checkbox-with-number-field",
intlLabel: {
id: `${PLUGIN_ID}.maxLengthCharacters.label`,
defaultMessage: "Characters limit"
}
}
]
}
],
validator: () => ({
preset: yup.string().required({
id: `${PLUGIN_ID}.preset.error.required`,
defaultMessage: "Editor preset is required"
})
})
}
});
}
};
export {
StrapiMediaLib as S,
index as a,
clonedDefaultHtmlPreset as b,
clonedDefaultTheme as c,
clonedDefaultMarkdownPreset as d,
getPluginPresets as e,
getPluginTheme as f,
getPluginConfig as g,
StrapiUploadAdapter as h,
isImageResponsive as i,
prefixFileUrlWithBackendUrl as p,
setPluginConfig as s
};