Capitalize first letter of sentence in CKeditor - ckeditor

I wish to capitalize the first letter of a sentence, on-the-fly, as the user types the text in a CKEditor content instance.
The strategy consists in catching each keystroke and try to replace it when necessary, that is for instance, when the inserted character follows a dot and a space. I'm fine with catching the event, but can't find a way to parse characters surrounding the caret position:
var instance = CKEDITOR.instances.htmlarea
instance.document.getBody().on('keyup', function(event) {
console.log(event);
// Would like to parse here from the event object...
event.data.preventDefault();
});
Any help would be much appreciated including a strategy alternative.

You should use keydown event (close to what you proposed):
var editor = CKEDITOR.instances.editor1;
editor.document.getBody().on('keydown', function(event) {
if (event.data.getKeystroke() === 65 /*a*/ && isFirstLetter()) {
// insert 'A' instead of 'a'
editor.insertText('A');
event.data.preventDefault();
}
});
Now - how should isFirstLetter() look like?
You have to start from editor.getSelection().getRanges() to get caret position.
You're interested only in the first range from the collection.
To extract text content from before the caret use small trick:
move start of the range to the beginning of document: range.setStartAt( editor.document.getBody(), CKEDITOR.POSITION_AFTER_START ),
use CKEDITOR.dom.walker to traverse through DOM tree in source order,
collect text nodes and find out what's before caret (is it /\. $/) - remember that you have to skip inline tags and stop on block tags - hint: return false from guard function to stop traversing.
Example of how you can use walker on range:
var range, walker, node;
range = editor.getSelection().getRanges()[0];
range.setStartAt(editor.document.getBody(), CKEDITOR.POSITION_AFTER_START);
walker = new CKEDITOR.dom.walker(range);
walker.guard = function(node) {
console.log(node);
};
while (node = walker.previous()) {}
And now few sad things.
We assumed that selection is empty when you type - that doesn't have to be true. When selection is not collapsed (empty) then you'll have to manually remove its content before calling insertText. You can use range#deleteContents to do this.
But this is not all - after deleting range's content you have to place caret in correct position - this isn't trivial. Basically you can use range#select (on the range after deleteContents), but in some cases it can place caret in incorrect place - like between paragraphs. Fixing this is... is not doable without deeeeeeeep knowledge about HTML+editables+insertions+other things :).
This solution is not complete - you have to handle paste event, deleting content (one can delete words from the start of sentence), etc, etc.
I guess there are couple of other problems I didn't even thought about :P.
So this approach isn't realistic. If you still want to implement this feature I think that you should set timer and by traversing DOM (you can use walker on range containing entire document, or recently typed text (hard to find out where it is)) find all sentences starting from lower letter and fix them.

This is what worked for me in Ckeditor 4.
var editor = CKEDITOR.instances.editor1;
editor.document.getBody().on('keydown', function(event) {
if (event.data.getKeystroke() >= 65 && event.data.getKeystroke()<=91 && encodeURI(this.getText())=="%0A" && this.getText().length==1 ) {
//uppercase the char
editor.insertText(String.fromCharCode(event.data.getKeystroke()));
event.data.preventDefault();
}
});

Related

kendo ui editor how to modify user selection with range object

Kendo UI 2015.2.805 Kendo UI Editor for Jacascript
I want to extend the kendo ui editor by adding a custom tool that will convert a user selected block that spans two or more paragraphs into block of single spaced text. This can be done by locating all interior p tags and converting them into br tags, taking care not to change the first or last tag.
My problem is working with the range object.
Getting the range is easy:
var range = editor.getRange();
The range object has a start and end container, and a start and end offset (within that container). I can access the text (without markup)
console.log(range.toString());
Oddly, other examples I have seen, including working examples, show that
console.log(range);
will dump the text, however that does not work in my project, I just get the word 'Range', which is the type of the object. This concerns me.
However, all I really need however is a start and end offset in the editor's markup (editor.value()) then I can locate and change the p's to br's.
I've read the telerik documentation and the referenced quirksmode site's explanation of html ranges, and while informative nothing shows how to locate the range withing the text (which seems pretty basic to me).
I suspect I'm overlooking something simple.
Given a range object how can I locate the start and end offset within the editor's content?
EDIT: After additional research it appears much more complex than I anticipated. It seems I must deal with the range and/or selection objects rather than directly with the editor content. Smarter minds than I came up with the range object for reasons I cannot fathom.
Here is what I have so far:
var range = letterEditor.editor.getRange();
var divSelection;
divSelection = range.cloneRange();
//cloning may be needless extra work...
//here manipulate the divSelection to how I want it.
//divSeletion is a range, not sure how to manipulate it
var sel = letterEditor.editor.getSelection()
sel.removeAllRanges();
sel.addRange(divSelection);
EDIT 2:
Based on Tim Down's Solution I came up with this simple test:
var html;
var sel = letterEditor.editor.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
html = html.replace("</p><p>", "<br/>")
var range = letterEditor.editor.getRange();
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = html;
var frag = document.createDocumentFragment(), child;
while ((child = div.firstChild)) {
frag.appendChild(child);
}
range.insertNode(frag);
The first part, getting the html selection works fine, the second part also works however the editor inserts tags around all lines so the result is incorrect; extra lines including fragments of the selection.
The editor supports a view html popup which shows the editor content as html and it allows for editing the html. If I change the targeted p tags to br's I get the desired result. (The editor does support br as a default line feed vs p, but I want p's most of the time). That I can edit the html with the html viewer tool lets me know this is possible, I just need identify the selection start and end in the editor content, then a simple textual replacement via regex on the editor value would do the trick.
Edit 3:
Poking around kendo.all.max.js I discovered that pressing shift+enter creates a br instead of a p tag for the line feed. I was going to extend it to do just that as a workaround for the single-space tool. I would still like a solution to this if anyone knows, but for now I will instruct users to shift-enter for single spaced blocks of text.
This will accomplish it. Uses Tim Down's code to get html. RegEx could probably be made more efficient. 'Trick' is using split = false in insertHtml.
var sel = letterEditor.editor.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
var block = container.innerHTML;
var rgx = new RegExp(/<br class="k-br">/gi);
block = block.replace(rgx, "");
rgx = new RegExp(/<\/p><p>/gi);
block = block.replace(rgx, "<br/>");
rgx = new RegExp(/<\/p>|<p>/gi);
block = block.replace(rgx, "");
letterEditor.editor.exec("insertHtml", { html: block, split: false });
}

only select one pie slice [duplicate]

I have a dc.rowchart that has 5 different "categories". Initially, all are selected. When I click on one, then only that one is highlighted. When I click on a second one... both the first and second one I clicked are highlighted.
How can I make it/configure the rowchart such that only a single category is highlighted every time a bar is clicked?
dc.filter("category1");
dc.filter("category2");
Both of these in sequence appear to "append" filters than replace.
Ethan's answer is almost there. It's likely unintentional, but dc.js doesn't appear to use the return value for addFilterHandler, so you'll have to modify the filters parameter for it to work, like so:
myChart.addFilterHandler(function (filters, filter) {
filters.length = 0; // empty the array
filters.push(filter);
return filters;
});
This works great as long as your chart doesn't use cap (e.g. a pie chart with limited slices), as this handler prevents the others group from working. I've yet to come up with a satisfactory solution for that case, unfortunately.
I assume you'll want addFilterHandler - https://github.com/dc-js/dc.js/blob/master/web/docs/api-latest.md#dc.baseMixin+addFilterHandler
You'd probably want to do the following, but it's untested.
myChart.addFilterHander(function (filters, filter) {
return [filter];
});

How to indent the first line of a paragraph in CKEditor

I'm using CKEditor and I want to indent just the first line of the paragraph. What I've done before is click "Source" and edit the <p> style to include text-indent:12.7mm;, but when I click "Source" again to go back to the normal editor, my changes are gone and I have no idea why.
My preference would be to create a custom toolbar button, but I'm not sure how to do so or where to edit so that clicking a custom button would edit the <p> with the style attribute I want it to have.
Depending on which version of CKE you use, your changes most likely disappear because ether the style attribute or the text-indent style is not allowed in the content. This is due to the Allowed Content Filter feature of CKEditor, read more here: http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter
Like Ervald said in the comments, you can also use CSS to do this without adding the code manually - however, your targeting options are limited. Either you have to target all paragraphs or add an id or class property to your paragraph(s) and target that. Or if you use a selector like :first-child you are restricted to always having the first element indented only (which might be what you want, I don't know :D).
To use CSS like that, you have to add the relevant code to contents.css, which is the CSS file used in the Editor contents and also you have to include it wherever you output the Editor contents.
In my opinion the best solution would indeed be making a plugin that places an icon on the toolbar and that button, when clicked, would add or remove a class like "indentMePlease" to the currently active paragraph. Developing said plugin is quite simple and well documented, see the excellent example at http://docs.ckeditor.com/#!/guide/plugin_sdk_sample_1 - if you need more info or have questions about that, ask in the comments :)
If you do do that, you again need to add the "indentMePlease" style implementation in contents.css and the output page.
I've got a way to indent the first line without using style, because I'm using iReport to generate automatic reports. Jasper does not understand styles. So I assign by jQuery an onkeydown method to the main iframe of CKEditor 4.6 and I check the TAB and Shift key to do and undo the first line indentation.
// TAB
$(document).ready(function(){
startTab();
});
function startTab() {
setTimeout(function(){
var $iframe_document;
var $iframe;
$iframe_document = $('.cke_wysiwyg_frame').contents();
$iframe = $iframe_document.find('body');
$iframe.keydown(function(e){
event_onkeydown(e);
});
},300);
}
function event_onkeydown(event){
if(event.keyCode===9) { // key tab
event.preventDefault();
setTimeout(function(){
var editor = CKEDITOR.instances['editor1'], //get your CKEDITOR instance here
range = editor.getSelection().getRanges()[0],
startNode = range.startContainer,
element = startNode.$,
parent;
if(element.parentNode.tagName != 'BODY') // If you take an inner element of the paragraph, get the parentNode (P)
parent = element.parentNode;
else // If it takes BODY as parentNode, it updates the inner element
parent = element;
if(event.shiftKey) { // reverse tab
var res = parent.innerHTML.toString().split(' ');
var aux = [];
var count_space = 0;
for(var i=0;i<res.length;i++) {
// console.log(res[i]);
if(res[i] == "")
count_space++;
if(count_space > 8 || res[i] != "") {
if(!count_space > 8)
count_space = 9;
aux.push(res[i]);
}
}
parent.innerHTML = aux.join(' ');
}
else { // tab
var spaces = " ";
parent.innerHTML = spaces + parent.innerHTML;
}
},200);
}
}

Modifing Selection Ckeditor

At the moment i have the following problem, i'm applying span tags with the applyStyle Method from CKEDITOR 4.x. But when a span is partial selected and i execute the applyStyle method a new span will be made with the selection, but the other half of the selected span isn't restored and loses his span.
First Question: Is it possible to prevent partial selection of a certain element?
IF NOT My Second Question: Is it possible to extend the Selection only on one side, the side where the span(With a certain class or attribute) is partial selected. So that it will be fully selected for processing.
A Example:
This is 'my text <span class"testClass">, This' is </span> Other Text
And now we want a solution to create:
This is <span class"testClass2"> my text, This</span> <span class"testClass"> is </span> Other Text
Please take notice of the following:
The hard part in this is to maintain the html structure. when half of the selection is in an other block level element, it may not brake! That is the reason that i started using the applyStyle method.
First Question: Is it possible to prevent partial selection of a certain element?
Hmm... You can check placeholder plugin's sample - it uses non-editable inline elements to create those placeholders which at least on Chrome cannot be partially selected. Though, I think it's not a satisfying solution for you :)
Another possible solution is using editor#selectionChange event on which you can check if one of selection ends is located inside that element and if yes, set it before or after that element. It'd look like (I haven't tested this code, it's just a proto):
editor.on( 'selectionChange', function( evt ) {
var sel = evt.data.selection,
range = sel.getRanges()[ 0 ];
if ( protectedElement.contains( range.startContainer ) || protectedElement.equals( range.startContainer ) )
range.setStartAt( protectedElement, CKEDITOR.POSITION_BEFORE_START );
if ( protectedElement.contains( range.endContainer ) || protectedElement.equals( range.endContainer ) )
range.setEndAt( protectedElement, CKEDITOR.POSITION_AFTER_END );
sel.selectRanges( [ range ] );
} );
Although, this kind of solutions are always dangerous and can cause many unpredictable situations. But it may be worth checking it.
Back to the root of your problem - I understand that you want to create styles which work on the same level - i.e. only one can be applied in one place. This isn't possible using styling system. You would have to prepare range before applying style. The code would be similar to the selectionChange listener - you check if ends are anchored in style element, if yes you need to move range's ends out of it. The only question is how to exclude entire element from range in this situation:
<p>foo[bar<span class="st1">bom</span>bim]foo</p>
The result should be two ranges:
<p>foo[bar]<span class="st1">bom</span>[bim]foo</p>
Unfortunately current range's API does not include helpful method like range#exclude, therefore you need to implement yours. I would try doing this with walker. Iterate from range's start to end and remember all style elements. If you'll do this in both directions you'll gather also partially selected elements on both ends, so the first step I described will be unnecessary. When you'll have list of elements which you want to exclude from range, then you just need to create ranges at both ends and between these elements - this part should be easy. Element#getPosition will be helpful, but you'll need to check its code to understand how to use it because it isn't documented.
I have been looking and trying for hours. And chose to make an enlarge function myself to expand the selection. I made my own enlarge/expand function as i wanted to have more control which the enlarge of CKEDITOR doesn't provide.
The code:
//Vars
var firstNode = range.startContainer.getParent();
var lastNode = range.endContainer.getParent();
//Make end Get full if is tcElement
if(lastNode.type === CKEDITOR.NODE_ELEMENT && lastNode.getName() === "myElement")
{
range.setEndAfter(lastNode);
}
//Make end Get full if is tcElement
if(firstNode.type === CKEDITOR.NODE_ELEMENT && firstNode.getName() === "myElement")
{
range.setStartBefore(firstNode);
}
range.select();
Other nice piece of code, which isn't very hard but can be useful for other people.
This code i used to split the code in 2 or 3 parts.. where part 1 and 3 are the partial selection if existed.
Spliting to multiple ranges
//Vars
var newRanges = [];
var allWithinRangeParent = range.getCommonAncestor().getChildren();
var firstNode = range.startContainer;
var lastNode = range.endContainer;
var firstNodeStart = range.startOffset;
var lastNodeEnd = range.endOffset;
//TODO make if to check if this needs to be made.
//make end partial
var newEndRange = new CKEDITOR.dom.range( editor.document );
newEndRange.selectNodeContents( lastNode );
newEndRange.endOffset = lastNodeEnd;
newRanges.push(newEndRange);
//TODO make if to check if this needs to be made.
//Make start partial
var newStartRange = new CKEDITOR.dom.range( editor.document );
newStartRange.selectNodeContents( firstNode );
newStartRange.startOffset = firstNodeStart;
newRanges.push(newStartRange);
//Make center selection.
var tempRange = new CKEDITOR.dom.range( editor.document );
tempRange.setStartBefore(firstNode.getParent().getNext());
tempRange.setEndAfter(lastNode.getParent().getPrevious());
newRanges.push(tempRange);
selection.selectRanges(newRanges);

CKEditor: How to stop angle brackets from converting to HTML entities

Our site is using tags like <# TAGNAME #> but CKEditor converts < and > to &lt and &gt which breaks these tags for use in our software.
I've discovered this option: config.protectedSource.push( /<#[\s\S]*##>/g ); which seems to stop the conversion if the data is saved from Source mode, but in WYSIWYG mode I can't find a way to stop the conversion. I've tried many options in their API but none of them seem to have helped, how can I fix this problem?
Were were looking at using CKEDitor to edit Smarty templates. The problem we were hitting was that it was replacing all the angle brackets and ampersands within the curly brackets, which messed everything up. This came up in a Google search so our solution should help anyone with similar issues.
CKEditor rebuilds the HTML every time you switch to Source mode and when you save, so you need to add to the HTML http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Data_Processor htmlFilter.
This worked for us:
//replace Form_content with whatever your editor's id is.
htmlParser = CKEDITOR.instances.Form_content.dataProcessor.htmlFilter;
//We don't want HTML encoding on smarty tags
//so we need to change things in curly brackets
htmlParser.onText = function(text) {
//find all bits in curly brackets
var matches = text.match(/\{([^}]+)\}/g);
//go through each match and replace the encoded characters
if (matches!=null) {
for (match in matches) {
var replacedString=matches[match];
replacedString = matches[match].replace(/>/g,'>');
replacedString = replacedString.replace(/</g,'<');
replacedString = replacedString.replace(/&/g,'&');
text = text.replace(matches[match],replacedString);
}
}
return text;
}
The onText function processes all the bits that aren't in tags or comments.
I'd imagine you can do something similar by altering the code above - I've left it as is as I think our problems and required solutions are very similar.
editor.on( 'mode', function(ev) {
if ( ev.editor.mode == 'source' ) {
var str=ev.editor.getData();
str=str.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, "\"");
ev.editor.textarea.setValue(str);
}
});
http://cksource.com/forums/viewtopic.php?f=11&t=20647&start=10
If you type < or > in any WYSIWYG editor, they will be converted to their HTML entities in source mode.

Resources