I am using CKEditor's editing capabilities, but with my own ui controls that calls into CKEditor's api to perform its commands. E.g.,
var style = new CKEDITOR.style({
element: 'span',
attributes: {
'style': 'font-size: 20px'
}
});
editor.applyStyle(style);
to set the font size of the selected text.
Problem is that I need a way to know the status of the currently selected text so I can update the controls accordingly. Is it bold? Then the bold button should be in an activated state, and clicking it should remove the boldness instead of attempting to add it again.
Is there a way to query CKEditor for certain style attributes of the currently selected text? Much like how tinymce.activeEditor.queryCommandValue('bold') works in tinyMCE.
Usually, the best way to create a button-command-style trio is like it is done in the basicstyles plugin:
var style = new CKEDITOR.style( { ... } );
editor.attachStyleStateChange( style, function( state ) {
!editor.readOnly && editor.getCommand( 'commandName' ).setState( state );
} );
editor.addCommand( 'commandName', new CKEDITOR.styleCommand( style ) );
editor.ui.addButton( 'buttonName', {
label: 'buttonLabel',
command: 'commandName'
} );
This code will take care of everything - the command and the button state will be updated according to selection changes. You can also get the command state easily:
editor.getCommand( 'commandName' ).state; // Returns on of CKEDITOR.TRISTATE_*.
However, if you want to query the state of the style directly, then you can use the style.checkActive() method:
style.checkActive( editor.elementPath(), editor );
You don't need to create command and buttons for this to work.
Edit
The CKEditor styles system has its limitations. For example, you cannot have variable font size in the style. This means that to check the current font size you need to do a quick search in the DOM:
function getFontSize() {
var span = editor.elementPath().contains( isFontSizeSpan );
return span ? span.getStyle( 'font-size' ) : null;
function isFontSizeSpan( el ) {
return el.is( 'span' ) && el.getStyle( 'font-size' );
}
}
Now, just use this method in an editor#selectionChange event listener to update your control's value.
Related
I'm working on a Google Fonts plugin for WordPress and I try to have the same effect as the core WYSIWYG editor. Basically when you click on element (inside the Editor) with font class I want to get the class and then based on that reload the font family/style listbox in the Toolbar.
(I found couple of hacks here on SO like this one Proper Way Of Modifying Toolbar After Init in TinyMCE but nothing that works like the WP core example)
There is the same functionality when you click on P, H1, H3, H3 ... How they do it? Can you point me at least to the JS file in WordPress distro; I think I can figure it out if see the code.
Here is GIF that demonstrates what I'm talking about. Thanks in advance.
I found the solution and because it's not a hack, like the other ones I found on SO, I will post it in here and hopes it will help anyone else that's trying to do something similar.
First to access the button/listbox need to use onpostrender with a callback function.
editor.addButton( 'developry_google_font_family_button', {
type : 'listbox',
onpostrender : fontFamilyNodeChange,
value : '',
...
Next the callback function should look something like this:
function fontFamilyNodeChange() {
var listbox = this;
editor.on('NodeChange', function( e ) {
// This next part is specific for my needs but I will post it as an example.
var selected = [];
if ( $( e.element ).hasClass( 'mce-ga' ) ) { // this a class I add to all elements that have google fonts format
// Then I strip the classes from classList that I don't need and add the rest into an array (e.g ['roboto', '100'])
var gfont_options = $( e.element ).attr('class')
.replace('mce-ga', '')
.replace('mce-family-', '')
.replace('mce-weight-', '')
.trim()
.split(' ');
selected.push( gfont_options );
// At end I add the new value to listbox select[0][0] (e.g. 'roboto')
listbox.value(selected[0][0]);
}
});
}
And here is an example:
I am using draft.js, and I have everything I need working except for one thing.
I want to be able to add a custom block option that will apply a span with a custom class (e.g. content) around the selected content in the editor.
Is this possible with draft-js custom blocks?
Any good examples out there? (didn't find anything when googling)
You can do it without wrapping text to the element with a custom class. You can style selected text with method RichUtils.toggleInlineStyle. More details described here.
Look at this working example - https://jsfiddle.net/x2gsp6ju/2/
Define customStyleMap object. Keys of this object should be unique names of your custom styles and values - objects with appropriate styles.
const customStyleMap = {
redBackground: {
backgroundColor: 'red'
},
underlined: {
textDecoration: 'underline',
fontSize: 26
},
};
Pass this object to customStyleMap property of Editor component:
<Editor
placeholder="Type away :)"
editorState={this.state.editorState}
onChange={this._handleChange}
customStyleMap={customStyleMap}
/>
In this example, I apply styles for selected text after click on appropriate buttons, I call this.applyCustomSTyles method and pass style-name as first argument. In this method I generate new editorState with RichUtils.toggleInlineStyles:
applyCustomStyles = (nameOfCustomStyle) => {
this._handleChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
nameOfCustomStyle
)
);
}
Is there a way to define own list styles for ckeditor. I have found the plugin http://ckeditor.com/addon/liststyle but it lets me choose only things like circle or square.
I want to define own css classes for ol or ul in my application that i can use. For example a class to define more space between list elements. the users of the editor should pick the list class via a context menu like in the nice "liststyle" plugin.
Is there a way to do this?
Confirmed the approach mentioned above works, I am using Drupal, Ckeditor List Style (plugin) and the Ckeditor List Style module (Drupal module).
I needed to make a change to the lang > en.js file to add the appropriate Title in instead of the function as the OP.
cute: 'Cute',
Once that was done, inside the liststyle.js file I updated the existing code to this:
Existing code in liststyle.js file:
commit: function(element) {
var value = this.getValue();
if (value)
element.setStyle('list-style-type', value);
else
element.removeStyle('list-style-type');
}
New code:
commit: function(element) {
var value = this.getValue();
if (value) {
if (value == 'cute') {
element.setAttribute("class", 'cute');
element.removeStyle('list-style-type');
} else {
element.setStyle('list-style-type', value);
}
} else {
element.removeStyle('list-style-type');
}
}
I am dealing with CKEditor to add custom list styling to the liststyle plugin.
I added one new style (you can add more if you like) using the CSS class.
Here's how: in liststyle.js (after de-obfuscating) I insert my .logo class:
..........
function e(c,e){
c.lang.liststyle.logo="My bullet"; // BBoyanov - adding 'My bullet' as title in dropdown list (in current language), otherwise it stay "empty" title
var b=c.lang.liststyle;
........
style:"width:150px",
items:[[b.notset,""],[b.circle,"circle"],[b.disc,"disc"],[b.square,"square"],
[b.logo,"logo"]],//BBoyanov - css class 'logo' as Bullet \,[b.logo,"logo"]\
........
commit:function(a){
var b=this.getValue();b?a.setStyle("list-style-type",b):a.removeStyle("list-style-type");
"logo"==b?a.setAttribute("class",'logo'):a.removeAttribute("class");//BBoyanv set 'logo' as CSS class
........
h={a:"lower-alpha",A:"upper-alpha",i:"lower-roman",I:"upper-roman",
1:"decimal", disc:"disc", circle:"circle", square:"square",logo:"logo"};//BBoyanov \,logo:"logo"\
........
You define the CSS class in ckeditor.css (to be visualised in CKEditor) and in your own CSS file.
If you prefer different titles for different languages, you must put translation in the corresponding language .js file of CKEditor.
It worked for me.
However, probably this is injection because it takes over the allowedContent - need tests and confirmation.
With web components one of the elements that people want to create and override most is <input>. Input elements are bad because they are many things depending on their type and usually hard to customize, so it's normal that people always want to modify their looks and behavior.
Two years ago more or less, when I first heard of web components, I was pretty excited and the first kind of elements that came to my mind that I wanted to create were custom input elements. Now that the spec is finished it looks like the need I had for input elements is not solved. Shadow DOM was supposed to allow me to change their internal structure and looks but input elements are blacklisted and can not have a shadow root because they already have a hidden one. If I want add extra logic and behavior, custom, built-in elements with the is attribute should do the trick; I can't do the shadow DOM magic but at least I have this, right? well Safari is not going to implement it, polymer won't use them for that reason which smells like a standard that is going to be deprecated soon.
So I'm left with normal custom elements; they can use the shadow DOM and have whatever logic I want, but I want them to be inputs! they should work inside a <form>, but if I'm correct, form elements don't like them. Do I have to write my own custom form element as well that replicates all of what the native one does? Do I have to say goodbye to FormData, validation API, etc? Do I lose the ability to have a form with inputs that works without javascript?
You can create a custom element with the look and behavior you want.
Put inside it a hidden <input> element with the right name (that will be passed to the <form>).
Update its value attribute whenever the custom element "visible value" is modified.
I posted an example in this answer to a similar SO question.
class CI extends HTMLElement
{
constructor ()
{
super()
var sh = this.attachShadow( { mode: 'open' } )
sh.appendChild( tpl.content.cloneNode( true ) )
}
connectedCallback ()
{
var view = this
var name = this.getAttribute( 'name' )
//proxy input elemnt
var input = document.createElement( 'input' )
input.name = name
input.value = this.getAttribute( 'value' )
input.id = 'realInput'
input.style = 'width:0;height:0;border:none;background:red'
input.tabIndex = -1
this.appendChild( input )
//content editable
var content = this.shadowRoot.querySelector( '#content' )
content.textContent = this.getAttribute( 'value' )
content.oninput = function ()
{
//console.warn( 'content editable changed to', content.textContent )
view.setAttribute( 'value', content.textContent)
}
//click on label
var label = document.querySelector( 'label[for="' + name + '"]' )
label.onclick = function () { content.focus() }
//autofill update
input.addEventListener( 'change', function ()
{
//console.warn( 'real input changed' )
view.setAttribute( 'value', this.value )
content.value = this.value
} )
this.connected = true
}
attributeChangedCallback ( name, old, value )
{
//console.info( 'attribute %s changed to %s', name, value )
if ( this.connected )
{
this.querySelector( '#realInput' ).value = value
this.shadowRoot.querySelector( '#content' ).textContent = value
}
}
}
CI.observedAttributes = [ "value" ]
customElements.define( 'custom-input', CI )
//Submit
function submitF ()
{
for( var i = 0 ; i < this.length ; i++ )
{
var input = this[i]
if ( input.name ) console.log( '%s=%s', input.name, input.value )
}
}
S1.onclick = function () { submitF.apply(form1) }
<form id=form1>
<table>
<tr><td><label for=name>Name</label> <td><input name=name id=name>
<tr><td><label for=address>Address</label> <td><input name=address id=address>
<tr><td><label for=city>City</label> <td><custom-input id=city name=city></custom-input>
<tr><td><label for=zip>Zip</label> <td><input name=zip id=zip>
<tr><td colspan=2><input id=S1 type=button value="Submit">
</table>
</form>
<hr>
<div>
<button onclick="document.querySelector('custom-input').setAttribute('value','Paris')">city => Paris</button>
</div>
<template id=tpl>
<style>
#content {
background: dodgerblue;
color: white;
min-width: 50px;
font-family: Courier New, Courier, monospace;
font-size: 1.3em;
font-weight: 600;
display: inline-block;
padding: 2px;
}
</style>
<div contenteditable id=content></div>
<slot></slot>
</template>
UPDATE:
Some time has passed and I ran into this post describing form-associated custom elements https://web.dev/more-capable-form-controls, it seems there will finally be an appropriate way to create custom elements that can be used as form controls, no need to wrap inputs or be limited by the bad support and inability of having a shadow DOM in built-in custom elements. I created a toy component to play with latest APIs(chrome only ATM) https://github.com/olanod/do-chat there chat messages are produced by a form that has a custom element field that is seen as a regular input and sets its value in the form whenever it's changed.
Check the article for more details and perhaps experiment with it creating a PR with a new custom chat message field? ;)
OLD:
I think #supersharp's answer is the most practical solution for this problem but I'll also answer my self with a less exiting solution. Don't use custom elements to create custom inputs and complain about the spec being flawed.
Other things to do:
Assuming the is attribute is dead from its birth, I think we can achieve similar functionality by just using proxies. Here's an idea that would need some refinement:
class CrazyInput {
constructor(wowAnActualDependency) { ... }
doCrazyStuff() { ... }
}
const behavesLike = (elementName, constructor ) => new Proxy(...)
export default behavesLike('input', CrazyInput)
// use it later
import CrazyInput from '...'
const myCrazyInput = new CrazyInput( awesomeDependency )
myCrazyInput.value = 'whatever'
myCrazyInput.doCrazyStuff()
This just solves the part of creating instances of the custom elements, to use them with the browser APIs some potentially ugly hacking around methods like querySelector,appendChild needs to be done to accept and return the proxied elements and probably use mutation observers and a dependency injection system to automatically create instances of your elements.
On the complaining about the spec side, I still find it a valid option to want something better. For mortals like myself who don't have the whole big picture is a bit difficult to do anything and can naively propose and say things like, hey! instead of having is on native elements let's have it on the custom ones(<my-input is='input'>) so we can have a shadow root and user defined behavior on a custom input that works as a native one. But of course I bet many smart people who have worked on refining those specs all this years have though of all of the use cases and scenarios where something different wouldn't work in this broken Web of ours. But I just hope they will try harder because a use case like this one is something that should have been solved with the web components holy grail and I find it hard to believe that we can't do better.
I created a custom image browser/uploader plugin for ckeditor. I'm having some trouble integrating the advanced content filter
Basically what's happening is I have a field on a dialog tab that allows a user to edit the inline style for the image. This is working correctly it seems as the styles get added to the img tag and after saving to the database and when viewing the generated html the image is styled correctly. However when you go to edit the document again CKeditor has stripped out the style attribute.
in plugin.js I am setting the allowedContent property to include the style attribute
editor.addCommand( 'sbImageDialog', new CKEDITOR.dialogCommand( 'sbImageDialog',
{allowedContent: 'img[src][style]', requiredContent: 'img[src]' }
));
in the dialog js I am defining a tab called "advanced" that requires the style attribute to be allowed in order to show
{
id: 'advanced',
label: 'Advanced',
elements: [
{
type: 'text',
id: 'style',
label: 'Style',
requiredContent: 'img[style]',
setup: function( element ) {
this.setValue( element.getAttribute('style') );
},
commit: function ( element ) {
var style = this.getValue();
if ( style )
element.setAttribute( 'style', style );
else if ( !this.insertMode )
element.removeAttribute( 'style' );
}
},
]
}
since that tab does show up and the image is styled correctly when viewed it seems like I have it set up correctly.
So why is CKeditor stripping out the style attribute when I return to edit the document? Can anyone see what I'm missing?
I found the answer so I'll add it here in case it will help someone else.
Where in plugin.js I had
editor.addCommand( 'sbImageDialog', new CKEDITOR.dialogCommand( 'sbImageDialog',
{allowedContent: 'img[src][style]', requiredContent: 'img[src]' }
));
I had to change the allowedContent property from 'img[src][style]' to 'img[src][style]{*}'
The curly braces indicate what css style properties the element is allowed to have. By putting * I am allowing all css style properties.