How can I add content into a CKeditor5 by clicking on an outside element - ckeditor

I have a few buttons outside of the Ckeditor on my page, and I want to click on them and add content to the Ckeditor5.
By clicking on the buttons, I make an ajax call to retrieve the content from the server, but I'm having trouble inserting it. This is my code right now:
success: function (data) {
ClassicEditor
.create( document.querySelector( '.ckeditor' ), {
language: 'he',
} )
.then( editor => {
editor.model.change( writer => {
const insertPosition = editor.model.document.selection.getFirstPosition();
writer.insertText(data, insertPosition );
} );
} )
.catch( error => {
console.error( error );
} );
}
But this code duplicates my editor, and I don't know how to get the "editor" object without using ".create". can anyone help me with this?

Related

CKEditor5: add text when copying/cutting

I am trying to capture the event when a user uses copy/cut so that I can add some text at the end of the text placed into the pastebin.
On a traditional textarea I would listen to the "copy" event.
On CKEditor the appropriate event seems to be clipboardOutput.
But I am not clear now how to hook into CKEditor specific events and how to integrate into output pipeline.
Would appreciate some pointers.
I have the editor running with this code:
ClassicEditor
.create( document.querySelector( '#editor' ), { } )
.catch( error => {
console.error( error );
} );
I tried adding this code below but it doesn't seem to trigger
ClassicEditor.model.document.on( 'clipboardOutput', (eventInfo, data) => {
console.log(eventInfo, data);
} );
The clipboardOutput event is fired on view.Document instance.
You could use UpcastWriter to modify view.DocumentFragment provided in data.content property of the event data.
Example:
editor.editing.view.document.on( 'clipboardOutput', ( eventInfo, data ) => {
const writer = new UpcastWriter( editor.editing.view.document );
const paragraph = writer.createElement( 'p', null, [
writer.createText( 'foobar' )
] );
writer.appendChild( paragraph, data.content );
} );

this.props.editor.create is not a function in CKEditor - NextJS

I'm following the steps from this. And my CKEditor now can run on my nextjs app. But the problem is when I wanna put simpleUploadAdapter, there is an error message saying props.editor.create is not a function. Here's the code :
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import React, { useState, useEffect, useRef } from 'react'
export default function Home() {
const editorCKRef = useRef()
const [editorLoaded, setEditorLoaded] = useState(false)
const { CKEditor, SimpleUploadAdapter, ClassicEditor } = editorCKRef.current || {}
useEffect(() => {
editorCKRef.current = {
CKEditor: require('#ckeditor/ckeditor5-react'),
// SimpleUploadAdapter: require('#ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter'),
ClassicEditor: require('#ckeditor/ckeditor5-build-classic')
}
setEditorLoaded(true)
}, [])
return (
<div className={styles.container}>
<Head>
<title>My CKEditor 5</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<h2>Using CKEditor 5 build in Next JS</h2>
{editorLoaded && ClassicEditor &&
<CKEditor
name="editor"
editor={ typeof ClassicEditor !== 'undefined' ?
ClassicEditor.create(
document.getElementsByName("editor"), {
plugins: [ SimpleUploadAdapter],
//toolbar: [ ... ],
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: 'http://example.com',
// Enable the XMLHttpRequest.withCredentials property.
withCredentials: false
}
}
): ''
}
data="<p>Hello from CKEditor 5!</p>"
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();
console.log('ON CHANGE')
// console.log(ClassicEditor.create())
// console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
config={
{
simpleUpload: {
uploadUrl: 'localhost:8000/api/files/upload/question/1'
}
}
}
/>
}
</div>
)
}
and this is the error:
So what's the problem in here? Thank you
I got mine to work by wrapping the CKEditor component in a class component of my own.
class RichTextEditor extends React.Component<Props, State> {
render() {
const { content } = this.props;
return (
<CKEditor
editor={ClassicEditor}
data={content}
/>
);
}
}
It seems CKEditor just doesn't play nice with function components. Then use dynamic import to load the wrapper if you're using NextJS.
const RichTextEditor = dynamic(() => import("/path/to/RichTextEditor"), {
ssr: false,
});
I remembered that CKEditor4 is easier to setup in Next.js.
CKEditor5 require more work, you have to use dynamic import with mode ssr=false
But in your case, you also want to use another plugin SimpleUploadAdapter
I tried using CKEditor React component + build classic + SimpleUploadAdapter but meets the error "Code duplication between build classic and source (SimpleUploadAdapter)".
So I decided to custom the ckeditor5-build-classic, add the plugin into there and rebuild, then make it works :)(https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/custom-builds.html)
Here are few remarks:
Custom ckeditor5-build-classic
// ckeditor5-build-classic-custom local package
// Add SimpleUploadAdapter into the plugin list
// src/ckeditor.js
import SimpleUploadAdapter from '#ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter';
ClassicEditor.builtinPlugins = [
...
SimpleUploadAdapter
...
]
// Rebuild for using in our app
npm run build
Use the custom build in our app
// app/components/Editor.js
import CKEditor from "#ckeditor/ckeditor5-react";
import ClassicEditor from "#ckeditor/ckeditor5-build-classic";
...
<CKEditor
editor={ClassicEditor}
config={{
// Pass the config for SimpleUploadAdapter
// https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/simple-upload-adapter.html
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: "http://example.com",
// Enable the XMLHttpRequest.withCredentials property.
withCredentials: true,
// Headers sent along with the XMLHttpRequest to the upload server.
headers: {
"X-CSRF-TOKEN": "CSRF-Token",
Authorization: "Bearer <JSON Web Token>",
},
},
}}
...
Dynamic import for loading the editor from client-side
// pages/index.js
import dynamic from "next/dynamic";
const Editor = dynamic(() => import("../components/editor"), {ssr: false})
To sum up:
Custom the CKEditor build, add needed plugins... then rebuild. Make them as a local package
Use that local package in our app!
Check my git sample with long comments: https://github.com/nghiaht/nextjs-ckeditor5

Can you render user message before it appears in webchat?

For MS Botframework webchat, Is there a way to intercept user message before being rendered in webchat and change it?
This is easy to accomplish using the createStore() method.
In the web chat script located in your page, create the store using the above method. In it, match the action.type to 'WEB_CHAT/SEND_MESSAGE'. This will capture every message that is passed thru the web chat component before it is displayed.
Be aware, this altered text (or whatever value you are changing) is what is sent to the bot. action is the root object. action.payload, effectively, represents the activity. This is where you will find the text value, etc.
Within the if statement, perform whatever change you are looking to make, then return the action object.
Lastly, include the store object within the renderWebChat component. This should set you up.
In the example below, I am appending text to the text field altering it before it is rendered and displayed.
<script>
( async function () {
const res = await fetch( 'http://somesite/directline/token', { method: 'POST' } );
const { token } = await res.json();
// We are using a customized store to add hooks to connect event
const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => action => {
if ( action.type === 'WEB_CHAT/SEND_MESSAGE' ) {
action.payload.text = action.payload.text + ' (Hello from behind the curtain)'
}
return next( action );
} );
window.WebChat.renderWebChat( {
directLine: window.WebChat.createDirectLine( { token } ),
userID: 'user123',
username: 'johndoe',
botAvatarInitials: 'BB',
userAvatarInitials: 'JD',
store
}, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
} )().catch( err => console.error( err ) );
</script>
Hope of help!

React navigation Tab in Stack with button in Header

I'd like to have a TabNavigator embedded in a StackNavigator with a button in the Header used to navigate in the main Stack.
Here is a snack of my issue: https://snack.expo.io/#guigui64/tabs-in-stack
The propblem is when I create the TabNavigator:
const TabNavigator = createMaterialTopTabNavigator(
{
ScreenA,
ScreenB,
},
{
navigationOptions: {
headerTitle: 'Title when in tab',
headerRight: (
<Button
onPress={() => this.props.navigation.navigate('C')} // here is the issue !
title="ScreenC"
/>
),
},
}
);
I also tried creating a Component with static navigationOptions and have render() return a TabNavigator. In this case, the header appears fine but not the tabs.
Thanks in advance !

Why does CKEditor5 use a promise to initialize it?

Example:
ClassicEditor
.create( document.querySelector( '#editor' ) )
.then( editor => {
console.log( editor );
} )
.catch( error => {
console.error( error );
} );
Why would editor creation need to be asynchronous?
The editor initialization can be asynchronous because some editor features or editor UI may require asynchronous initialisation.
I'm inconcrete on purpose here. As a framework developer, I don't know what kind of features the users of the framework will want to implement. However, I know some examples from the past:
an <iframe>-based editor (iframes are initialized asynchronously), i.e. an editor in which the content is edited within an <iframe>,
real-time collaboration features which need to retrieve the content from the server.
If you're implementing a plugin which need to defer initialisation, then you can simply return a promise from its init() or afterInit() methods:
class MyPlugin extends Plugin {
init() {
return new Promise( resolve => {
// Call resolve() once your plugin is ready:
resolve();
} );
}
}

Resources