EDIT: I've opened an issue on Github: https://github.com/ckeditor/ckeditor5-editor-classic/issues/98
I've spent about 2 days trying to figure this out.
The editor works fine, but when I try to add an image there's an error:
filerepository-no-upload-adapter: Upload adapter is not defined. Read
more:
https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-filerepository-no-upload-adapter
I browsed the documentation for hours, but I could not figure out a solution. You can see below the steps in the documentation I tried to follow.
This is my code:
import React, { Component } from 'react';
import CKEditor from '#ckeditor/ckeditor5-react';
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
console.log(ClassicEditor.builtinPlugins.map( plugin => plugin.pluginName ));
class EditorComponent extends Component {
constructor(props) {
super(props);
this.state = {
id: props.id,
content: props.content,
handleWYSIWYGInput: props.handleWYSIWYGInput,
editor: ClassicEditor
}
}
render() {
return (
<div className="Editor-content">
<CKEditor
editor={ this.state.editor }
data={this.state.content}
onInit={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
//this.state.handleWYSIWYGInput(this.props.id, data);
console.log( { event, editor, data } );
console.log(this.state.content);
} }
onBlur={ editor => {
console.log( 'Blur.', editor );
} }
onFocus={ editor => {
console.log( 'Focus.', editor );
} }
/>
</div>
);
}
}
export default EditorComponent;
If you open the link in the error it says:
If you see this warning when using one of the CKEditor 5 Builds it
means that you did not configure any of the upload adapters available
by default in those builds.
See the comprehensive "Image upload overview" to learn which upload
adapters are available in the builds and how to configure them.
Then you can follow this link: https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/image-upload.html
Which will give you a few options to configure the upload adapter. I'd like to use CKFinder, hence: https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/ckfinder.html
And then you read this:
This feature is enabled by default in all builds.
So I suppose the feature is present in all builds, but still needs to be "configured". How do I do this in ReactJS?
I tried to implement the code linked in the page, but the syntax is not working in ReactJS and anyway adding import CKFinder from '#ckeditor/ckeditor5-ckfinder/src/ckfinder'; would generate another error:
ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated.
Read more:
https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-ckeditor-duplicated-modules
The code in the documentation's page:
import CKFinder from '#ckeditor/ckeditor5-ckfinder/src/ckfinder';
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ CKFinder, ... ],
// Enable the "Insert image" button in the toolbar.
toolbar: [ 'imageUpload', ... ],
ckfinder: {
// Upload the images to the server using the CKFinder QuickUpload command.
uploadUrl: 'https://example.com/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images&responseType=json'
}
} )
.then( ... )
.catch( ... );
How can I make it work?
In order to make it work you should add only:
config={{ckfinder: {
// Upload the images to the server using the CKFinder QuickUpload command
// You have to change this address to your server that has the ckfinder php connector
uploadUrl: 'https://example.com/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images&responseType=json'
}}}
Adding this part of code will stop showing up the upload adapter error. This won't upload pictures until you set up the server side. You can follow these instructions to install the php connector: https://ckeditor.com/docs/ckfinder/ckfinder3-php/quickstart.html
The full code:
import React, { Component } from 'react';
import CKEditor from '#ckeditor/ckeditor5-react';
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
console.log(ClassicEditor.builtinPlugins.map( plugin => plugin.pluginName ));
class EditorComponent extends Component {
constructor(props) {
super(props);
this.state = {
id: props.id,
content: props.content,
handleWYSIWYGInput: props.handleWYSIWYGInput,
editor: ClassicEditor
}
}
render() {
return (
<div className="Editor-content">
<CKEditor
editor={ this.state.editor }
data={this.state.content}
config={{ckfinder: {
// Upload the images to the server using the CKFinder QuickUpload command.
uploadUrl: 'https://example.com/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images&responseType=json'
}}}
onInit={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
//this.state.handleWYSIWYGInput(this.props.id, data);
console.log( { event, editor, data } );
console.log(this.state.content);
} }
onBlur={ editor => {
console.log( 'Blur.', editor );
} }
onFocus={ editor => {
console.log( 'Focus.', editor );
} }
/>
</div>
);
}
}
export default EditorComponent;
I think you are confused about how to configure the ckeditor setting in React. Mostly people are like me at the start but to do configuration in the ckeditor for react component you have to follow it like this. I take config as an object which take another object inside that's how we add and remove plugins.
Here's an example in the documentation of CKeditor 5.
https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/react.html#using-ckeditor-5-source
<CKEditor
data="<p>Editor' content</p>"
config={ {
plugins: [ CKFinder, ... ],
toolbar: [ 'imageUpload', ... ],
ckfinder: {
uploadUrl: 'https://example.com/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images&responseType=json'
}
} }
/>
Related
I am using highlight.js for Froala editor code blocks at React. But I'm getting en error. I used registerLanguage function but it didn't work.
Error in browser:
Here is my code :
import FroalaEditor from 'react-froala-wysiwyg';
import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/javascript';
import 'highlight.js/styles/androidstudio.css';
hljs.registerLanguage('javascript', javascript)
export default function Editor({ handleEditor, data }: EditorType) {
return (
<div className="unreset">
<FroalaEditor
tag="textarea"
onModelChange={handleEditor}
model={data}
config={{
placeholderText: '',
events: {
//editor event
contentChanged: function () {
//.fr-element is editor wrapper tag
document
.querySelectorAll('.fr-element pre')
.forEach((el: any) => {
// WARN: Falling back to no-highlight mode for this block.
hljs.highlightElement(el);
});
},
},
}}
/>
</div>
);
}
Why does not work? Thanks.
I'm just now learning WP block development, however, I've been creating NextJS React apps for over a year now, and have an okay grasp on React in general.
The WP documentation points to using npx #wordpress/create-block to generate a new block file structure. This is pretty cool, and saves a lot of typing! Out of the box, it looks like this:
registerBlockType( 'create-block/some-block-name, {
.... other stuff ...
edit: Edit,
save,
}
This command generates and edit.js and a save.js. In concept, that seems pretty self explanatory, but how do you pass attributes through to create editable blocks?
registerBlockType( 'create-block/some-block-name', {
.... other stuff ...
edit: Edit, <--- how do we pass attributes to this? (points to edit.js)
save,
}
All documentation or examples I can find simply pass (props) into edit: and save: in the index.js file. Like this:
registerBlockType( 'create-block/some-block-name', {
.... other stuff ...
edit: (props) => {
... do some stuff ...
},
save: (props) => {
... do some stuff ...
},
}
What is the new WordPress standard for this? Should I be using edit.js and save.js, or just do all the work in the index.js file, and ignore these files?
I've been able to figure this out.
I was getting lost in the WordPress documentation, as most of it refers to using the edit and save parts of the index.js for functionality. There is other documentation that has you put the functionality into the edit.js and save.js files.
The combination of passing the array of {attributes, className, setAttributes} in the react hook like way, as well as using the WP useBlockProps() was what I was missing in the documentation.
Here's a generic index.js file that registers a block:
import { registerBlockType } from '#wordpress/blocks';
import './style.scss';
import Edit from './edit';
import save from './save';
registerBlockType( 'my-custom-blocks/header-bg-line', {
attributes: {
content: {
type: "string",
source: "html",
selector: "h2",
},
},
supports: {
html: true,
},
edit: Edit,
save,
});
Here are the sample edit.js and save.js files that work correctly:
edit.js
import { useBlockProps, RichText } from '#wordpress/block-editor';
export default function Edit({ attributes, setAttributes, className }) {
const blockProps = useBlockProps();
return (
<h2 {...blockProps}>
<RichText
tagName="span"
value={attributes.content}
allowedFormats={["core/italic"]}
onChange={(content) => setAttributes({ content })}
placeholder={__("Heading with a background line...")}
/>
</h2>
);
}
save.js
import { useBlockProps, RichText } from '#wordpress/block-editor';
export default function save({ attributes }) {
const blockProps = useBlockProps.save();
return (
<h2 {...blockProps}>
<RichText.Content
tagName="span"
value={attributes.content}
/>
</h2>
);
}
Look inside the index.js file. The save.js and edit.js are just imported there. I think it is up to you, whether you use them or simply put everything inside index.js. If your functions get large, it is probably better to put them in separate files.
I am trying to get EditorJS working in NextJS. The editor loads fine without plugins, having the only paragraph as a block option. However, when I attempt to add plugins via tools prop console throws the following warning:
editor.js?9336:2 Module Tools was skipped because of TypeError: Cannot read property 'prepare' of undefined
When I click on the editor in the browser, it is throwing:
Uncaught TypeError: Cannot read property 'holder' of undefined
I have tested editor plugins in the normal React app, and they load fine. Meaning that the problem is in EditorJS and NextJS import and handling of plugins. I have tried to import editor and plugins in componentDidMount hook using require but had the same problem as with NextJS dynamic imports. Attempted to get component using React ref but found that currently NextJS has problems with getting components' refs, Tried suggested workaround but still had no result. The instance of the editor is not available until onChange is triggered, so plugins just cannot hook into the editor due to that 'prepare' property or the whole editor are being undefined until an event on editor has happened, but the editor outputs into the console that it is ready.
My component's code:
import React from "react";
import dynamic from "next/dynamic";
const EditorNoSSR = dynamic(() => import("react-editor-js"), { ssr: false });
const Embed = dynamic(() => import("#editorjs/embed"), { ssr: false });
class Editor extends React.Component {
state = {
editorContent: {
blocks: [
{
data: {
text: "Test text",
},
type: "paragraph",
},
],
},
};
constructor(props) {
super(props);
this.editorRef = React.createRef();
}
componentDidMount() {
console.log(this.editorRef.current);
console.log(this.editorInstance);
}
onEdit(api, newData) {
console.log(this.editorRef.current);
console.log(this.editorInstance);
this.setState({ editorContent: newData });
}
render() {
return (
<EditorNoSSR
data={this.state.editorContent}
onChange={(api, newData) => this.onEdit(api, newData)}
tools={{ embed: Embed }}
ref={(el) => {
this.editorRef = el;
}}
instanceRef={(instance) => (this.editorInstance = instance)}
/>
);
}
}
export default Editor;
Is there any solution to this problem? I know SSR is challenging with client side rendering of components that access DOM, but there was condition used that checked whether window object is undefined, however, it does not look like an issue in my situation.
UPDATE:
I have found a solution but it is rather not a NextJS way of solving the problem, however, it works. It does not require a react-editorjs and implemented as creation of EditorJS instance as with normal EditorJS.
class Editor extends React.Component {
constructor(props) {
super(props);
this.editor = null;
}
async componentDidMount() {
this.initEditor();
}
initEditor = () => {
const EditorJS = require("#editorjs/editorjs");
const Header = require("#editorjs/header");
const Embed = require("#editorjs/embed");
const Delimiter = require("#editorjs/delimiter");
const List = require("#editorjs/list");
const InlineCode = require("#editorjs/inline-code");
const Table = require("#editorjs/table");
const Quote = require("#editorjs/quote");
const Code = require("#editorjs/code");
const Marker = require("#editorjs/marker");
const Checklist = require("#editorjs/checklist");
let content = null;
if (this.props.data !== undefined) {
content = this.props.data;
}
this.editor = new EditorJS({
holder: "editorjs",
logLevel: "ERROR",
tools: {
header: Header,
embed: {
class: Embed,
config: {
services: {
youtube: true,
coub: true,
},
},
},
list: List,
inlineCode: InlineCode,
code: Code,
table: Table,
quote: Quote,
marker: Marker,
checkList: Checklist,
delimiter: Delimiter,
},
data: content,
});
};
async onSave(e) {
let data = await this.editor.saver.save();
this.props.save(data);
}
render() {
return (
<>
<button onClick={(e) => this.onSave(e)}>Save</button>
<div id={"editorjs"} onChange={(e) => this.onChange(e)}></div>
</>
);
}
}
This implementation works in NextJS
I will update code if I find a better solution.
UPDATE 2:
The answer suggested by Rising Odegua is working.
You have to create a seperate component and then import all your tools there:
import EditorJs from "react-editor-js";
import Embed from "#editorjs/embed";
import Table from "#editorjs/table";
import List from "#editorjs/list";
import Warning from "#editorjs/warning";
import Code from "#editorjs/code";
import LinkTool from "#editorjs/link";
import Image from "#editorjs/image";
import Raw from "#editorjs/raw";
import Header from "#editorjs/header";
import Quote from "#editorjs/quote";
import Marker from "#editorjs/marker";
import CheckList from "#editorjs/checklist";
import Delimiter from "#editorjs/delimiter";
import InlineCode from "#editorjs/inline-code";
import SimpleImage from "#editorjs/simple-image";
const CustomEditor = () => {
const EDITOR_JS_TOOLS = {
embed: Embed,
table: Table,
marker: Marker,
list: List,
warning: Warning,
code: Code,
linkTool: LinkTool,
image: Image,
raw: Raw,
header: Header,
quote: Quote,
checklist: CheckList,
delimiter: Delimiter,
inlineCode: InlineCode,
simpleImage: SimpleImage
};
return (
<EditorJs tools={EDITOR_JS_TOOLS} />
);
}
export default CustomEditor;
Then in your NextJS page, use a dynamic import like this:
let CustomEditor;
if (typeof window !== "undefined") {
CustomEditor = dynamic(() => import('../src/components/CustomEditor'));
}
And you can use your component:
return (
{CustomEditor && <CustomEditor />}
)
Source : https://github.com/Jungwoo-An/react-editor-js/issues/31
My FilePond Poster preview code not working, please help me find what i'm doing wrong.
i tried different variations of the code seen below but with no luck.
-i tried to enable instant Upload (didn't work)
import React, { Component, Fragment } from 'react';
import { FilePond, File, registerPlugin } from 'react-filepond';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
import FilePondPluginFilePoster from 'filepond-plugin-file-poster';
import 'filepond-plugin-file-poster/dist/filepond-plugin-file-poster.css';
import 'filepond/dist/filepond.min.css';
registerPlugin(FilePondPluginImagePreview, FilePondPluginFilePoster);
export default class MyFilePond extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
handleInit() {
console.log('FilePond instance has initialised', this.pond);
}
render() {
return (
<Fragment>
<FilePond
ref={ref => (this.pond = ref)}
allowFilePoster={true}
instantUpload={false}
server={{}}
name="image"
acceptedFileTypes={['image/*']}
oninit={() => this.handleInit()}
>
<File
file={{
name: 'my-file.png',
size: 3001025,
type: 'image/png'
}}
metadata={{poster : 'https://www.pngarts.com/files/3/Spongebob-Squarepants-Transparent.png'}}
source="https://www.pngarts.com/files/3/Spongebob-Squarepants-Transparent.png"
/>
</FilePond>
</Fragment>
);
}
}
Are you sure you can access the file on the remote server without setting CORS headers? I expect your developer console is showing a security warning.
If you keep getting CORS error like my case, you can try this hack:
+ Clone the poster plugin and make it your own plugin.
+ In the source code of poster plugin find and remove this line: img.crossOrigin = 'Anonymous';
Please Follow the Filepond Structure Mention Below,
FilePond.create(el, {
files: [
{
source:1234,
options: {
type: 'local',
file: {
name: 'my-file.png',
size: 1234,
type: 'image/png'
},
metadata:{
poster: 'https://i.imgur.com/hRliFiT.jpg'
}
}
}
]
});
I am trying to make a custom image plugin for CKEditor which integrates with my custom-made image upload system. Mainly, I run into problems while setting up this plugin. When I load "out-of-the-box" plugins, everything works fine (also, when I remove my own plugin, everything works again as it used to).
I get the following console error:
main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:1322 TypeError: Cannot read property 'pluginName' of undefined
at new ga (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:360)
at new Ul (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:521)
at new Lc (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:643)
at new pp (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:1318)
at n (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:643)
at new Promise (<anonymous>)
at Function.create (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:643)
at Module.<anonymous> (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:1322)
at n (main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:1)
at main.511663b82f6b3e2bb9df.js?2754ab1fde8ef5d8fd3d:1
I couldn't find anything about the property pluginName, apart from the following excerpt of documentation over at CKEditor:
pluginName : String | undefined
An optional name of the plugin. If set, the plugin will be available
in get by its name and its constructor. If not, then only by its
constructor.
The name should reflect the constructor name.
To keep the plugin class definition tight it is recommended to define
this property as a static getter:
export default class ImageCaption {
static get pluginName() {
return 'ImageCaption';
}
}
Note: The native Function.name property could not be used to keep the plugin name because it will be mangled during code
minification.
Inserting this function into my plugin code did not work, so I am kind of lost here what the problem could be. I've included my code below. I've set it up according to the CKEditor advanced setup, first option, made in Webpack.
Am I missing something, or is there a problem in my code?
index.js
import ClassicEditor from './ckeditor'; // ckeditor.js in the same folder
import ModelElement from '#ckeditor/ckeditor5-engine/src/model/element';
require("./css/index.css");
ClassicEditor
// Note that you do not have to specify the plugin and toolbar configuration — using defaults from the build.
.create( document.querySelector( '#editor' ))
.then( editor => {
editor.commands.get( 'imageStyle' ).on( 'execute', ( evt, args ) => {
// ...
// this snippet of code works; it concerns hooking into the default image plugin
// ...
} );
} )
.catch( error => {
console.error( error.stack );
} );
ckeditor.js
import ClassicEditorBase from '#ckeditor/ckeditor5-editor-classic/src/classiceditor';
import EssentialsPlugin from '#ckeditor/ckeditor5-essentials/src/essentials';
import UploadAdapterPlugin from '#ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter';
import AutoformatPlugin from '#ckeditor/ckeditor5-autoformat/src/autoformat';
import BoldPlugin from '#ckeditor/ckeditor5-basic-styles/src/bold';
import ItalicPlugin from '#ckeditor/ckeditor5-basic-styles/src/italic';
import ImagePlugin from '#ckeditor/ckeditor5-image/src/image';
import ImageCaptionPlugin from '#ckeditor/ckeditor5-image/src/imagecaption';
import ImageStylePlugin from '#ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbarPlugin from '#ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUploadPlugin from '#ckeditor/ckeditor5-image/src/imageupload';
import LinkPlugin from '#ckeditor/ckeditor5-link/src/link';
import ListPlugin from '#ckeditor/ckeditor5-list/src/list';
import ParagraphPlugin from '#ckeditor/ckeditor5-paragraph/src/paragraph';
import Highlight from '#ckeditor/ckeditor5-highlight/src/highlight';
import MediaEmbed from '#ckeditor/ckeditor5-media-embed/src/mediaembed';
import Table from '#ckeditor/ckeditor5-table/src/table';
import TableToolbar from '#ckeditor/ckeditor5-table/src/tabletoolbar';
import ImageLibrary from './js/image-library.js'; // file containing the code for my custom plugin
export default class ClassicEditor extends ClassicEditorBase {}
ClassicEditor.builtinPlugins = [
EssentialsPlugin,
UploadAdapterPlugin,
AutoformatPlugin,
BoldPlugin,
ItalicPlugin,
Highlight,
MediaEmbed,
Table,
TableToolbar,
ImagePlugin,
ImageCaptionPlugin,
ImageStylePlugin,
ImageToolbarPlugin,
ImageUploadPlugin,
LinkPlugin,
ListPlugin,
ParagraphPlugin,
ImageLibrary // my custom plugin
];
ClassicEditor.defaultConfig = {
highlight: {
options: [
{
model: 'redPen',
class: 'pen-red',
title: 'Red pen',
color: '#DD3300',
type: 'pen'
},
{
model: 'bluePen',
class: 'pen-blue',
title: 'Blue pen',
color: '#0066EE',
type: 'pen'
},
{
model: 'greenPen',
class: 'pen-green',
title: 'Green pen',
color: '#22AA22',
type: 'pen'
}
]
},
toolbar: {
items: [
//'heading',
//'|',
'bold',
'italic',
'link',
'highlight:redPen', 'highlight:greenPen', 'highlight:bluePen', 'removeHighlight',
'|',
'bulletedList',
'numberedList',
'|',
'mediaembed',
'inserttable',
'|',
'undo',
'redo'
]
},
image: {
toolbar: [
'imageStyle:full',
'imageStyle:alignCenter',
'|',
'imageTextAlternative'
],
styles: [
'full','alignCenter'
]
},
table : {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
language: 'nl'
};
image-library.js
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import ModelElement from '#ckeditor/ckeditor5-engine/src/model/element';
import Command from '#ckeditor/ckeditor5-core/src/command';
class RoodCMSImageCommand extends Command {
static get requires() {
return [ ModelElement ];
}
execute( message ) {
console.log(message);
}
}
class ImageLibrary extends Plugin {
static get requires() {
return [ ModelElement ];
}
static get pluginName() {
return 'ImageLibrary';
}
init() {
// Initialize your plugin here.
const editor = this.editor;
console.log("plugin initialized.",editor);
}
}
Update: solution based on Maciej Bukowski's answer
Maciej pointed out that the class ImageLibrary (which I tried to import) lacked the export. Something that I've easily missed, was that whenever you're importing something, you're going to have to also export it, otherwise it won't be available. The keywords export default did the trick for me.
The culprit was in image-library.js, for which I change the following line:
class ImageLibrary extends Plugin {
// ... this failed, as it missed the `export default` keywords
}
Into the following:
export default class ImageLibrary extends Plugin {
// ... works, as I properly export what I want to import.
}
import ImageLibrary from './js/image-library.js';
You don't export that library from a file, so that's why you have an error Cannot read property 'pluginName' of undefined. The ImageLibrary in the ckeditor.js becomes the undefined as it's can't be found in the image-library file.