I'm trying to find a reliable way to perform a callback when certain text is typed into the editor. I want to run certain code when an # is typed (to then allow a selection of user's to link to).
Currently I am doing it by using the "change" event and then trying to look back at what is before the current selection:
CKEDITOR.plugins.add( 'ipsmentions', {
init: function( editor ) {
/* When the content of the editor is changed, check if it was an # */
editor.on( 'change', function(e) {
/* If we currently have just normal text selected, we're typing normally and there might be an # */
var selection = editor.getSelection();
if ( selection.getType() == CKEDITOR.SELECTION_TEXT ) {
/* Loop the ranges... */
var ranges = selection.getRanges( true );
for ( var i = 0; i < ranges.length; i++ ) {
/* If the start container and end container are the same (meaning we just have a normal caret, indicating normal typing, nothing highlighted) and that is a text node... */
if ( ranges[i].startContainer.equals( ranges[i].endContainer ) && ranges[i].endContainer instanceof CKEDITOR.dom.text ) {
if ( ranges[i].startContainer.getText().substr( ranges[i].startOffset - 1, 1 ) == '#' ) {
console.log('# pressed!');
}
}
}
}
});
}
});
But I'm guessing this isn't the best way. In particular, I notice that if I type # and then hit enter (to start a new paragraph) I get "# pressed" in the console again.
Is there a better way to go about this?
Asynchronous check should help with enter key. I would also use some backwards range.cloneContents because you never know in what type of node the selection might be anchored. JSFiddle.
CKEDITOR.replace( 'editor', {
on: {
change: function() {
CKEDITOR.tools.setTimeout( function() {
var sel = this.getSelection(),
range = sel.getRanges()[ 0 ];
if ( !range.collapsed ) {
console.warn( 'non–collapsed range' );
return;
}
if ( !range.startOffset ) {
console.warn( 'at the beginning of the node' );
return;
}
range.setStart( range.startContainer, 0 );
if ( range.cloneContents().$.textContent.substr( -1 ) == '#' ) {
console.log( '# inserted' );
}
}, 0, this );
}
}
} );
Related
I've configured extraAllowedContent in "config.js" to allow div elements with specific classes, per the Advanced Content Filtering guide, which is working.
However, I need to strip any div elements that have no attributes. Is this possible?
You can specify config.disallowedContent with match function:
CKEDITOR.replace( 'editor1', {
disallowedContent: {
div: {
match: function( el ) {
return CKEDITOR.tools.isEmpty( el.attributes );
}
}
}
} );
While it correctly filters out the contents, for some reason (a bug), it also disables the Div plugin and its dialog. Thus I'd rather suggest something like this at the moment:
CKEDITOR.replace( 'editor1', {
on: {
pluginsLoaded: function( evt ) {
var editor = evt.editor,
rules = {
elements: {
div: function( el ) {
if ( CKEDITOR.tools.isEmpty( el.attributes ) ) {
// Return false to get rid of an element with children.
return false;
// The element can also be removed preserving children.
// el.replaceWithChildren();
}
},
}
};
// Filter what comes out of an editor.
editor.dataProcessor.htmlFilter.addRules( rules );
// Filter what comes into an editor.
editor.dataProcessor.dataFilter.addRules( rules );
}
}
} );
How can i enable or disable the paragraph button of CKEditor on selection of an image. I did not want to remove it completely.
am currently using ckeditor version 4.4
Use editor.getCommand() + CKEDITOR.command API (enable and disable):
editor.getCommand( 'justifyleft' ).disable();
editor.getCommand( 'justifyleft' ).enable();
For example:
CKEDITOR.instances.editor1.on( 'selectionChange', function( evt ) {
var jLeftCommand = this.getCommand( 'justifyleft' ),
jRightCommand = this.getCommand( 'justifyright' );
if ( evt.data.path.lastElement.is( 'img' ) ) {
jLeftCommand.disable();
jRightCommand.disable();
} else {
jLeftCommand.enable();
jRightCommand.enable();
}
} );
While using the new Dojo Event (on) i'm getting a lot of loops.
I'm calling for the first time as doShowSomeDialog(null).
Why does this function get into a loop?
(dialog has been declared before as dijit/Dialog)
doShowSomeDialog = function ( value ) {
var selectName = 'selector';
if ( value ) {
dialog.set("href", "/url/"+ selectName +"/"+ value );
} else {
dialog.set("href", "/url");
dialog.show();
}
dialog.set("onDownloadEnd", function() {
on( dijit.byId(selectName ), "change", doShowSomeDialog( dijit.byId( selectName ).get('value') ) );
}); }
It seems that "on" executes on the declaration of the event.
This will execute the method when calling on. You should wrap it in it's own function.
doShowSomeDialog( dijit.byId( selectName ).get('value')
should become
function() { doShowSomeDialog( dijit.byId( selectName ).get('value'); }
I am trying to force CKEditor to set image width and height as attributes not styles. According to docs i need to set in CKEditor config allowedContent = img[!src,alt,width,height] but when i do this CKEditor mode changes from automatic to custom and filters all other html tags.
How to change allowedContent only this specific case?
As i understand correctly each plugin registers its own allowedContent, so i changed in image plugin following line allowed = 'img[alt,!src] to allowed = 'img[alt,!src, width, height] but it does not work
ACF lacks one thing - #feature event. There's currently no convenient way to alter allowedContent of a feature being registered.
In your case, you will be able to use a temporary solution I described on CKEditor forum.
Your previous attempt to alter image's allowedContent wasn't successful most likely because you haven't fully replaced it. This is a code from image plugin:
var allowed = 'img[alt,!src]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}',
required = 'img[alt,src]';
if ( CKEDITOR.dialog.isTabEnabled( editor, pluginName, 'advanced' ) )
allowed = 'img[alt,dir,id,lang,longdesc,!src,title]{*}(*)';
So if you have changed just the first occurrence, then the second overrides it.
Many thanks Rainmar for help. It seems that dialog is removing the attributes. I managed to fix that changing commit function for width and height in image/dialogs/image.js file.
The old function looked like this (for width only):
commit: function( type, element, internalCommit ) {
var value = this.getValue();
if ( type == IMAGE ) {
if ( value )
element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
else
element.removeStyle( 'width' );
!internalCommit && element.removeAttribute( 'width' );
} else if ( type == PREVIEW ) {
var aMatch = value.match( regexGetSize );
if ( !aMatch ) {
var oImageOriginal = this.getDialog().originalElement;
if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
element.setStyle( 'width', oImageOriginal.$.width + 'px' );
} else
element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
} else if ( type == CLEANUP ) {
element.removeAttribute( 'width' );
element.removeStyle( 'width' );
}
}
And change it to this:
commit: function( type, element, internalCommit ) {
var value = this.getValue();
if ( type == IMAGE ) {
if ( value ) {
element.setAttribute('width', value + 'px');
element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
}else {
element.removeAttribute('width');
element.removeStyle( 'width' );
}
!internalCommit && element.removeStyle( 'width' );
} else if ( type == PREVIEW ) {
var aMatch = value.match( regexGetSize );
if ( !aMatch ) {
var oImageOriginal = this.getDialog().originalElement;
if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
element.setStyle( 'width', oImageOriginal.$.width + 'px' );
} else
element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
} else if ( type == CLEANUP ) {
element.removeAttribute( 'width' );
element.removeStyle( 'width' );
}
}
This is now super simple:
config.allowedContent = {
$1: {
// Use the ability to specify elements as an object.
elements: CKEDITOR.dtd,
attributes: true,
styles: true,
classes: true
}
};
config.disallowedContent = 'img{width,height}';
Which will alow everything, and convert inline image width/height to attributes.
http://docs.ckeditor.com/#!/guide/dev_acf-section-example%3A-disallow-inline-styles-and-use-attributes-instead
I'm struggling with a MVC pattern around Stateful objects and Observable stores. I can't find clarity on when to use one over another and mixing them isn't as clean as I'd hoped. Any Ah! insight into how to manage an observable collection of stateful items?
If I do a item.set("key", value) I can item.watch("key", cb) but then my observe-er isn't notify-ed.
I can do a item.watch(function() { state.notify(item, item.id); }) to always notify my observer but if my observe-er calls set I get a "Query is out of date, you must observe() the query prior to any data modifications'" error I cannot reconcile.
I can decouple this via a setTimeout but it's not feeling good.
Here's some code to show what I'm trying:
crudify: function ( store, query, crud )
{
var result;
if ( !crud )
{
crud = query;
query = store;
store = null;
}
if ( store )
{
query = store.query( query );
}
result = query.observe( function ( row, deleteIndex, insertIndex )
{
var del = ( 0 <= deleteIndex ), ins = ( 0 <= insertIndex );
!del && ins && crud.c && crud.c( row );
del && ins && crud.u && crud.u( row );
del && !ins && crud.d && crud.d( row );
}, !!crud.u );
crud.c && array.forEach( query, crud.c );
return result;
}
And I have a store wrapper (for layers) which I'm hacking to pieces trying to get the observe-ers notified of data changes without getting errors:
addLayer: function ( item ) {
var that = this;
that.store.add(item);
item.watch && item.watch(function (name, prior, curr) {
if (prior !== curr) {
that._queue.push(item);
// attempting to eliminate queries which indirectly update the store (can't find source of error)
infor.delay(20, "LayerProcessorStoreNotification", function () {
that._queue.forEach(function (item) {
that.store.notify(item, that.getLayerId(item));
});
that._queue = [];
});
}
});
return item;
},
The delay method looks like this:
delay: function ( timeout, id, callback )
{
this.delays = this.delays || [];
var delay = this.delays[id];
if ( delay )
{
clearTimeout( delay );
}
delay = setTimeout( callback, timeout );
this.delays[id] = delay;
},
I can do a item.watch(function() { state.notify(item, item.id); }) to always notify my observer
I didn't have any problems using this pattern, but...
but if my observe-er calls set I get a "Query is out of date, you must observe() the query prior to any data modifications'" error I cannot reconcile.
You shouldn't have watch call notify and also have observe call set. Choose one or the other. Otherwise you're setting yourself up for an infinite loop of callbacks.
The code below works for me. The only change I made to your crudify function was to change array.forEach(query, crud.c) to query.forEach(crud.c).
<script type="text/javascript" src="../javascripts/dojo-release-1.9.3-src/dojo/dojo.js"></script>
<script type="text/javascript">
window.stackoverflow = {
crudify: function ( store, query, crud )
{
var result;
if ( !crud )
{
crud = query;
query = store;
store = null;
}
if ( store )
{
query = store.query( query );
}
result = query.observe( function ( row, deleteIndex, insertIndex )
{
console.log("Observed");
var del = ( 0 <= deleteIndex ), ins = ( 0 <= insertIndex );
!del && ins && crud.c && crud.c( row );
del && ins && crud.u && crud.u( row );
del && !ins && crud.d && crud.d( row );
}, !!crud.u );
crud.c && query.forEach( crud.c );
return result;
}
};
require([
"dojo/store/Memory",
"dojo/store/Observable",
"dojo/Stateful",
"dojo/_base/json",
"dojo/domReady!"
], function(Memory, Observable, Stateful, dojo){
var store = Observable(new Memory({ data: [] }));
var rawData = [ { id: 1, data: "A" }, { id: 2, data: "B" }, { id: 3, data: "C" }]
var myQuery = { id : 3 };
rawData.forEach(function (obj, index) {
statefulObj = new Stateful(obj);
statefulObj.watch(function(name, oldValue, newValue) {
console.log("Watched");
store.notify(statefulObj, statefulObj.id);
});
store.put(statefulObj);
});
window.setTimeout(function() {
// this directly triggers the observe() callback
store.notify(store.get(3), 3);
}, 2000);
window.setTimeout(function() {
// this triggers the watch(), which indirectly triggers observe() through notify()
store.get(3).set("data", "Sea?");
}, 4000);
window.setTimeout(function() {
// this calls no callbacks because it doesn't match the query
store.put({ id: 2, data: "Bee" });
}, 6000);
window.setTimeout(function() {
// this update triggers observe()
store.put(new Stateful({ id: 3, data: "See!?!" }));
// note that we didn't set up a watch() on this new object (see below)
}, 8000);
window.setTimeout(function() {
// whoops, this doesn't trigger a callback because we forgot to call watch() above
store.get(3).set("data", "C4");
}, 10000);
window.setTimeout(function() {
// but the observe() callback still works
store.notify(store.get(3), 3);
}, 12000);
window.setTimeout(function() {
// this deletion triggers observe()
store.remove(3);
}, 14000);
stackoverflow.crudify(
store,
myQuery,
{
u: function(obj) {
console.log("Observed update: obj: " + dojo.toJson(obj));
},
c: function(obj) {
console.log("Observed creation: obj: " + dojo.toJson(obj));
},
d: function(obj) {
console.log("Observed deletion: obj: " + dojo.toJson(obj));
}
});
});
</script>