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.
Related
UPDATED ...
I'm trying to use imported object in my nuxt.config.js
This is Nuxt.js configuration file.
I have this:
seo.js
export default {
link: [
{
rel: 'icon',
type: 'image/x-icon',
href: '/img/favicon.ico',
},
],
meta: [
{
charset: 'utf-8'
}
]
}
Then in nuxt.config.js:
import seo from './seo'
export default {
head() {
return {
...seo,
}
},
... other config setting ...
}
Then I'm getting an error: "_seo is not defined"
I'm not trying to export head as function, this is config file and there are more settings.
While this works, nuxt.config.js:
import seo from './seo'
export default {
head: {
...seo,
}
}
... other config settings ...
}
It seams that I can't use imported object in function. I'm confused, what is the problem here and how can I use that imported object?
Change you nuxt.config.js to the following
import seo from "./seo";
export const head = () => {
return {
...seo
};
};
export default head;
I have put this on a code sandbox, if you look at the console log it is outputting the correct information
https://codesandbox.io/s/vigilant-almeida-j8dyq?file=/src/App.js
for such exports, it is recommended to use the named export.
export const links = [
{
rel: 'icon',
type: 'image/x-icon',
href: '/img/favicon.ico',
},
]
And Import it as follow:
import {seo} from './seo'
export default {
head: {
...seo,
}
}
}
Instead of exporting head as a method of the default object, you can directly default export the function
import seo from "./seo";
export default function() {
return {
...seo
};
};
I configured storybook in Angular app and tried running following first story using storybook demo components.
import { Button } from '#storybook/angular/demo';
export default { title: 'My Button' };
export const withText = () => ({
component: Button,
props: {
text: 'Hello Button',
},
});
export const withEmoji = () => ({
component: Button,
props: {
text: '😀 😎 👍 💯',
},
});
This above example works perfectly fine. Now when I import anything from #storybook/angular like this
import { moduleMetadata } from '#storybook/angular';
// OR
import { storiesOf } from '#storybook/angular';
Build breaks with following errors
ERROR in node_modules/#types/webpack-env/index.d.ts(281,11):
TS2320: Interface 'NodeRequire' cannot simultaneously extend types 'Require' and 'WebpackRequire'.
Named property 'context' of types 'Require' and 'WebpackRequire' are not identical.
ERROR in node_modules/#types/webpack-env/index.d.ts(281,11):
TS2320: Interface 'NodeRequire' cannot simultaneously extend types 'Require' and 'WebpackRequire'.
Named property 'ensure' of types 'Require' and 'WebpackRequire' are not identical.
ERROR in node_modules/#types/webpack-env/index.d.ts(354,11):
TS2320: Interface 'NodeModule' cannot simultaneously extend types 'Module' and 'WebpackModule'.
Named property 'hot' of types 'Module' and 'WebpackModule' are not identical.
I tried multiple solutions but none of them worked.
Update:
I was able temporarily fix it to move forward by modifying the interfaces in node_modules/#types/webpack-env/index.d.ts
// this line
interface NodeModule extends NodeJS.Module {}
// replaced with
interface NodeModule {}
Similarly
interface NodeRequire extends NodeJS.Require {}
// replaced with
interface NodeRequire {}
and finally
interface Module extends __WebpackModuleApi.Module {}
// replaced with
interface Module {}
I want to import several entries from a module and assign to some alias. Is that possible? Currently I do
import {
mdiAlert,
mdiCheck,
mdiDelete,
... 40 more ...
} from '#mdi/js'
export default new Vuetify({
icons: {
values: {
mdiAlert,
mdiCheck,
mdiDelete,
... 40 more ...
}
}
})
Is is possible to import { one, two, three } as something from 'module' somehow to avoid code duplication?
No, you cannot import a few module entries into an object. You need to import the bindings under a local alias and then build the object from them separately.
What you can however do is to import the complete module as a namespace object:
import * as mdiValues from '#mdi/js'
export default new Vuetify({
icons: {
values: mdiValues,
}
})
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'
}
} }
/>
My Problem
I've been very successful creating TypeScript classes that import Dojo classes like array, lang, Deferred, on, dom-construct, and all of that. For the first time, I'm trying to pull some of the dijit stuff. Specifically, dijit/form/HorizontalSlider and dijit/form/VerticalSlider. But, when I compile, I keep getting:
error TS2304: Cannot find name 'HorizontalSlider'
My Code
I've got a project-wide tsd.d.ts that looks like this:
/// <reference path="./dojo/dijit.d.ts" />
/// <reference path="./dojo/dojo.d.ts" />
I'm using the dts from Mayhem, because they are better than the typings at DefinitelyTyped (https://github.com/SitePen/mayhem/tree/master/typings/dojo). Here's the definition for HorizontalSlider:
declare module 'dijit/form/HorizontalSlider' {
import _FormValueWidget = require('dijit/form/_FormValueWidget');
interface HorizontalSlider extends _FormValueWidget {
}
export = HorizontalSlider;
}
EDIT: to clarify that syntax, based on Ryan Cavanaugh's response, here is the DropDownButton from the same dts:
declare module 'dijit/form/DropDownButton' {
import Button = require('dijit/form/Button');
interface DropDownButton extends Button {
}
var DropDownButton:{
new (kwArgs?:Object, srcNodeRef?:HTMLElement):DropDownButton;
};
export = DropDownButton;
}
Finally, in my class, things look as they should, like this:
/// <reference path="./../typings/tsd.d.ts" />
import HorizontalSlider = require('dijit/form/HorizontalSlider');
class MyDijit {
constructor() {
var foo = new HorizontalSlider(...);
}
}
And, I get the dreaded TS2304.
What I've Tried
It seems like the compiler can't see the dijit dts. So, I tried adding the dts to the grunt-typescript task.
typescript: {
build: {
src: ['src/**/*.ts', 'typings/tsd.d.ts'],
options: {
module: 'amd',
target: 'es3',
sourceMap: true,
declaration: false
}
}
I also tried updating my tsconfig.json
{
"compilerOptions": {
"declaration": false,
"module": "amd",
"noImplicitAny": true,
"target": "es3",
"filesGlob": [
"./src/**/*.ts",
"./typings/tsd.d.ts"
]
}
}
That didn't help, either! Lastly, thinking that the compiler was stripping those classes as being unused, I even tried this:
/// <amd-dependency path="dijit/form/HorizontalSlider"/>
And, no luck. So, here I am on Stack Overflow hoping that someday has gotten dijit/form/xxx to compile inside of a TypeScript class. There's one similar SO question, but not quite the same: Typescript cannot find name even though it is referenced
declare module 'dijit/form/HorizontalSlider' {
import _FormValueWidget = require('dijit/form/_FormValueWidget');
interface HorizontalSlider extends _FormValueWidget {
}
export = HorizontalSlider;
}
What you have here is a module that only exports a type (not a value), hence you cannot new it (see "difference between declare class and interface"). It seems likely you want a module that exports a class instead:
declare module 'dijit/form/HorizontalSlider' {
import _FormValueWidget = require('dijit/form/_FormValueWidget');
class HorizontalSlider extends _FormValueWidget { // interface -> class
}
export = HorizontalSlider;
}