How to customize ckeditor toolbar button behavior? - javascript

In ckeditor 4.0.1 in when "text direction from left to right" toolbar button is pushed, i type hello, the generated HTML source is:
<p dir="ltr">hello</p>
how can i change this behavior so that the generated source is look like:
<p dir="ltr" style="text-align: left;">hello</p>
Thanks in advance.

You can do this with dataProcessor:
CKEDITOR.replace( 'editor1', {
on: {
instanceReady: function () {
this.dataProcessor.htmlFilter.addRules( {
elements: {
p: function( element ) {
if ( element.attributes.dir == 'ltr' )
element.attributes.style = 'text-align: left;';
}
}
});
}
}
} );
You can also add it globally:
CKEDITOR.on( 'instanceReady', function ( event ) {
event.editor.dataProcessor.htmlFilter.addRules( {
elements: {
p: function( element ) {
if ( element.attributes.dir == 'ltr' )
element.attributes.style = 'text-align: left;';
}
}
});
} );

Related

Is there a way to close a vue component by clicking outside of it?

I am looking for a way to close a component when there it a click outisde of the element.
I tried an addEventListener.
This closes the component but after being closed it will not open again.
window.addEventListener('click', function(e){
if (document.getElementById('shopcartpreview').contains(e.target)){
console.log("Clicked in Box");
} else{
console.log("Clicked outside Box");
$('#shopcartpreview').hide();
}
})
Is there a way to accomplish this?
<template>
<div id="shopcartpreview" v-if="carthover">
<div class="cartitem" v-for="item in cartitems">
<div class="cartitempic"><img class="productImg" width="80px" height="80px" v-bind:src="'assets/products/' + item.image"></div>
<div class="cartitemdetails">
<div class="cartitemname">{{item.name}}</div>
<div class="cartitemqty">1 X </div>
<div class="cartitemprice">€{{item.unit_price}}</div>
</div>
<div class="cartitemdelete">
<img src="assets/images/icon-bin.png" width="15px" height="15px">
</div>
</div>
<div class="carttotal">
<div class="carttotaltext">TOTAL:</div>
<div class="carttotalprice">€2,860.00</div>
</div>
<div class="cartcheckouttbn">PROCEED TO CHECKOUT</div>
<div class="viewcart">VIEW CART</div>
</div>
</template>
<script>
module.exports = {
data: function () {
return{
cartitems: 0,
carthover: false,
}
},
created(){
EventBus.$on('addToCart', (payload) =>{
this.cartitems = payload
}),
EventBus.$on('mouseover', (carthover) =>{
this.carthover = carthover
})
}
}
</script>
I created a div element at the end of the component like that:
<div v-if="isPopup" class="outside" v-on:click="away()"></div>
Where .outside class is specified in CSS as follows:
.outside {
width: 100vw;
height: 100vh;
position: fixed;
top: 0px;
left: 0px;
}
And away() is a method in Vue instance as:
away() {
this.isPopup = false;
}
Easy, works well.
2020.11.10 Update
I find the previous solution I answered has so many errors, so I have to update it.
There are multiple solutions to close a component by clicking outside of it.
Firstly, there are some libraries which handle this problem, for example
simplesmiler/vue-clickaway, the nuxt also use this script if you have read the source code.
Secondly, if you want to implement it manually, here is code:
onClickOutside ( event: Event ) {
const path = event.path || (event.composedPath ? event.composedPath() : undefined)
// check if the MouseClick occurs inside the component
if (path && !path.includes(this.em) && !this.em.contains(event.target as HTMLElement)) {
this.closeThisComponent() // whatever method which close your component
}
}
Then, you must bind this eventHandler onClickOutside to the document.documentElement after open your component and remove this eventHandler from document.documentElement after you close your component.
Please notice the timing and refers to the event loop of JavaScript, you must understand the difference between MicroTask and MacroTask.
For example, open the component
openThisComponent () {
this.showThisCompoennt = true // whatever codes which open your component
// You can also use Vue.$nextTick or setTimeout
requestAnimationFrame(() => {
document.documentElement.addEventListener('click', this.onClickOutside, false)
})
}
closeThisComponent () {
this.showComponent = false // whatever codes which close your component
document.documentElement.removeEventListener('click', this.onClickOutside, false)
}
Demo Fiddle : https://jsfiddle.net/bq8m4fhe/
Create a clickoutside directive ... Detect click outside element
module.exports = {
data: function() {
return {
cartitems: 0,
carthover: false
};
},
directives: {
clickoutside: {
bind: function(el, binding, vnode) {
el.clickOutsideEvent = function(event) {
// here I check that click was outside the el and his childrens
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener("click", el.clickOutsideEvent);
document.body.addEventListener("touchstart", el.clickOutsideEvent);
},
unbind: function(el) {
document.body.removeEventListener("click", el.clickOutsideEvent);
document.body.removeEventListener("touchstart", el.clickOutsideEvent);
},
stopProp(event) {
event.stopPropagation();
}
}
},
created() {
EventBus.$on("addToCart", payload => {
this.cartitems = payload;
}),
EventBus.$on("mouseover", carthover => {
this.carthover = carthover;
});
}
};
Use that directive like this.
<div id="shopcartpreview" v-if="carthover" v-clickoutside="SHOPPING_CART_HIDE_FUNCTION">

Create non editable block with text in CKEditor 5

How do I Create a non editable block with text in CKEditor 5.
I need something which in the final view generate a:
<div>Non editable message here</div>
I tried to use a UIElement and then set innerHTML but that still makes the element editable.
Just for reference: Her is the plugin I ended up using.
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import DecoupledEditor from '#ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import Essentials from '#ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '#ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '#ckeditor/ckeditor5-basic-styles/src/bold';
import Heading from '#ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '#ckeditor/ckeditor5-basic-styles/src/italic';
import ButtonView from '#ckeditor/ckeditor5-ui/src/button/buttonview';
import imageIcon from '#ckeditor/ckeditor5-core/theme/icons/image.svg';
import Command from '#ckeditor/ckeditor5-core/src/command';
import { downcastElementToElement,downcastAttributeToAttribute,downcastAttributeToElement } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastElementToElement, upcastAttributeToAttribute,modelToViewAttributeConverter} from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
import attachPlaceholder from "#ckeditor/ckeditor5-engine/src/view/placeholder";
import ViewPosition from '#ckeditor/ckeditor5-engine/src/view/position';
import toWidgetEditable from '#ckeditor/ckeditor5-widget/src/utils';
import ClickObserver from '#ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
import UpcastDispatcher from '#ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher';
import { toWidget } from '#ckeditor/ckeditor5-widget/src/utils';
export default class TestWidget2 extends Plugin {
static get pluginName() {
return 'TestWidget2';
}
init() {
console.log('TestWidget2::init()');
const editor=this.editor;
const model=editor.model;
model.schema.register( 'myWidget', {
inheritAllFrom: '$block',
isObject: true
} );
editor.conversion.for( 'dataDowncast' )
.add( downcastElementToElement( {
model: 'myWidget',
view: ( modelItem, writer ) => {
const elm=writer.createContainerElement( 'div', { class: 'widget' } );
return toWidget( div, writer, { label: 'widget label' } );
}
} ) );
editor.conversion.for( 'editingDowncast' )
.add( downcastElementToElement( {
model: 'myWidget',
view: ( modelItem, writer ) => {
const div = writer.createContainerElement( 'div', { class: 'widget' } );
return toWidget( div, writer, { label: 'widget label' } );
}
} ) );
editor.conversion.for( 'upcast' )
.add( upcastElementToElement( {
view: {
name: 'div',
class: 'widget'
},
model: 'myWidget'
} ) );
}
}
You can create the widget API for that:
import Widget from '#ckeditor/ckeditor5-widget/src/widget';
import { toWidget } from '#ckeditor/ckeditor5-widget/src/utils';
import { downcastElementToElement } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastElementToElement } from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ Essentials, Paragraph, Widget, /* ... other plugins ... */ ],
toolbar: [ 'undo', 'redo' ]
} )
.then( editor => {
window.editor = editor;
const model = editor.model;
model.schema.register( 'myWidget', {
inheritAllFrom: '$block',
isObject: true
} );
editor.conversion.for( 'dataDowncast' )
.add( downcastElementToElement( {
model: 'myWidget',
view: ( modelItem, writer ) => {
return writer.createContainerElement( 'div', { class: 'widget' } );
}
} ) );
editor.conversion.for( 'editingDowncast' )
.add( downcastElementToElement( {
model: 'myWidget',
view: ( modelItem, writer ) => {
const div = writer.createContainerElement( 'div', { class: 'widget' } );
return toWidget( div, writer, { label: 'widget label' } );
}
} ) );
editor.conversion.for( 'upcast' )
.add( upcastElementToElement( {
view: {
name: 'div',
class: 'widget'
},
model: 'myWidget'
} ) );
editor.setData(
'<p>foobar</p>' +
'<div class="widget">Non editable text goes here</div>' +
'<p>foobar</p>'
);
} )
.catch( err => {
console.error( err.stack );
} );
The key point is to use the toWidget() function when downcasting an element to the editing view. Also, that element should be marked as an object in the schema.
Also don't forget to load the Widget plugin which turns on the support for widgets.
I just had the problem yesterday that when having a html table inside the isObject widget content, the table was still editable and also had controls.
The only way to fix it was to disable interaction inside my widget via css.
.ck-widget.my-wdiget *:not(.ck.ck-reset_all.ck-widget__type-around) {
pointer-events: none;
}
.ck.ck-reset_all.ck-widget__type-around is the class of the widget controls to add a linebreak before or after the widget. They are still usable.
I followed your steps without success..
For better knowledge (this guideline it's tested only in custom build for angular application but i think thats work everywhere)
probably you need use this command for follow correctly this guideline
npm install -g webpack
and in add to this you need install globally another dependency but i don't remember what is! (you can easly search the error on google for find it)
Create your custom build here
unzip, open folder and run npm install
install widget(with type def. if necessary) and core dependencies
npm i --save -D #ckeditor/ckeditor5-widget
npm i --save -D #types/ckeditor__ckeditor5-widget
npm i --save -D #ckeditor/ckeditor5-core
go into path src/ckeditor.js and add the following code for create the reinmar custom plugin
import {toWidget, Widget} from "#ckeditor/ckeditor5-widget";
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
class NotEditable extends Plugin {
init() {
const editor = this.editor;
const model = editor.model;
model.schema.register('myWidget', {
inheritAllFrom: '$block',
isObject: true
});
editor.conversion.for('dataDowncast')
.elementToElement({
model: 'myWidget',
view: (modelItem, writer) => {
return writer.writer.createContainerElement('div', {class: 'widget'});
}
});
editor.conversion.for('editingDowncast')
.elementToElement({
model: 'myWidget',
view: (modelItem, writer) => {
const div = writer.writer.createContainerElement('div', {class: 'widget'});
return toWidget(div, writer.writer, {label: 'widget label'});
}
});
editor.conversion.for('upcast')
.elementToElement({
view: {name: 'div', class: 'widget'},
model: 'myWidget'
});
}
}
and in Editor.builtinPlugins add Widget and NotEditable
Editor.builtinPlugins = [
//***other plugin,
Widget,
NotEditable
];
exec in the terminal npm run build
copy the build folder into your project and import the build (follow official Ckeditor5 guideline for this) {in my case i follow this guideline in angular guide}
According to the documentation isReadOnly : Boolean
Set it to true.
Official Documentation

React/JSX - changing element type conditionally with minimal code duplication

Let's say I have something that is:
<div>
{/* a lot of code */}
</div>
But if some condition is true, I want it to be:
<tr>
{/* same code as before */}
</tr>
Is there any way to achieve this without a huge amount of code duplication from copy pasting?
You could render the content in a variable using React.Fragment and choose the enclosing element depending on the condition.
Example
class App extends Component {
state = { condition: true };
render() {
const { condition } = this.state;
const content = (
<Fragment>
<h1>Hello world</h1>
<h2>This is a lot of text...</h2>
</Fragment>
);
return condition ? <div> {content} </div> : <tr> {content} </tr>;
}
}
Marked someone else as accepted answer, but the solution I ended up using was to call React.createElement(condition ? "div" : "tr", {attribute: stuff}, <div>Inner content</div>). Just an alternative for anyone who stumbles upon this in the future :)
Add your code inside the function
function returnView() {
render (
// a lot of code
);
}
if condition is true call that function
condition? returnView() : ' '
Have you tried with createElement() method? It works well for me.
So the code is:
import { createElement } from 'react';
createElement(type, props, ...children)
Example:
function Element({ condition, children, onClick }) {
if (condition) {
return createElement(
'div',
{ className: 'element-style' },
children
);
}
return createElement(
'tr',
{ className: 'element-style' },
children
);
}
If you have some of the special props and the rest of them is same, you can pass it in object variable
function Element({ condition, children, onClick }) {
const elementProps = {
className: 'element-style',
onClick,
};
if (condition) {
return createElement(
'div',
{ ...elementProps, style: { color: 'white' } },
children
);
}
return createElement(
'tr',
{ ...elementProps, style: { color: 'black' } },
children
);
}
Source: https://beta.reactjs.org/reference/react/createElement

CKeditor copy inline widget from editable widget (Firefox)

I am facing a problem in CKeditor (4.8.0) copy/paste on firefox for inline widget inside editable widget. Here is my fiddle
I want to copy both text and inline widget from the editable part of another widget :
But when i am pasting it only the start of the text is copied until the widget start :
This is working on Chrome
Html :
<textarea id="editor">foo bar
<div class='mywidget'>
<div>Header</div>
<div class='mywidget_contents'>
<p>test copy <span class="cke_placeholder">[[it]]</span> please<br></p>
</div>
</div>
</textarea>
Javascript :
CKEDITOR.plugins.add( 'mywidget', {
requires: 'widget',
icons: 'mywidget',
init: function( editor ) {
editor.widgets.add( 'mywidget', {
button: "My Widget",
template:"<div class='mywidget'><div>Header</div><div class='mywidget_contents'></div></div>",
editables: {
content: {
selector: '.mywidget_contents'
}
},
upcast: function (element) {
return element.hasClass('mywidget');
}
} );
}
} );
CKEDITOR.replace( 'editor', {
extraPlugins: 'mywidget,placeholder'
} );
Did someone find a workaround for this ?

onMouseLeave event is not triggering

This is nav component. This nav component rendered at run time. Text should be visible when mouse will enter to div with ref "text_nav" and text should hide when mouse will leave div with ref "text_nav".
onMouseLeave is not working
var React = require('React');
var $ = require('jquery');
var Nav = React.createClass({
getInitialState: function() {
return {
items: []
}
},
componentWillMount: function() {
var _this = this;
this.serverRequest =
$.post("/nav", {}, function(result) {
_this.setState({
items: result.data
});
})
},
onMouseEnter: function() {
this.refs.text_navigator.style = {display: true}
},
onMouseLeave: function() {
this.refs.text_navigator.style = {display: 'none'}
},
render: function() {
var text = this.state.items.map(function(data, index) {
var icon = "text_" + data.sname;
return (
<div id={icon} key={index} className="text_nav_item">
<p>
<span><a href={data.url}>{data.title} </a></span>
</p>
</div>
);
});
return (
<div id="nav" className="fixed" style={{zIndex: 1018}} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div id="text_nav" ref="text_navigator" style={{display: 'none'}} >
<div id="text_nav_content">
{item_text}
</div>
</div>
</div>
)
}
})
First off, the ref is text_nav in your render method, not text_navigator as you're using in onMouseEnter and onMouseLeave. But the main issue is that you can't set the display style the way you're trying to do, as
this.refs.text_navigator.style = {display: true}
The most common approach to take is to set a boolean in state, perhaps called displayTextNavigator. In getInitialState, set it to false, and then your onMouseEnter and onMouseLeave functions can be:
onMouseEnter: function() {
this.setState({ displayTextNavigator: true})
},
onMouseLeave: function() {
this.setState({ displayTextNavigator: false})
},
Now in your render method, you can change your wrapping div to look like this:
<div id="text_nav" ref="text_nav" style={{display: this.state.displayTextNavigator ? 'block': 'none'}} >
You can pull that ternary operator out to earlier in the render method if you'd like it be more readable.

Categories

Resources