mirror of
https://github.com/UnickSoft/graphonline.git
synced 2026-04-21 07:30:14 +00:00
first commit
This commit is contained in:
19
lib/ckeditor4/plugins/lite/js/plugins/IceAddTitlePlugin/IceAddTitlePlugin.js
Executable file
19
lib/ckeditor4/plugins/lite/js/plugins/IceAddTitlePlugin/IceAddTitlePlugin.js
Executable file
@@ -0,0 +1,19 @@
|
||||
(function() {
|
||||
|
||||
var exports = this, IceAddTitlePlugin;
|
||||
|
||||
IceAddTitlePlugin = function(ice_instance) {
|
||||
this._ice = ice_instance;
|
||||
};
|
||||
|
||||
IceAddTitlePlugin.prototype = {
|
||||
nodeCreated: function(node, option) {
|
||||
node.setAttribute('title', (option.action || 'Modified') + ' by ' + node.getAttribute(this._ice.userNameAttribute)
|
||||
+ ' - ' + ice.dom.date('m/d/Y h:ia', parseInt(node.getAttribute(this._ice.timeAttribute))));
|
||||
}
|
||||
};
|
||||
|
||||
ice.dom.noInclusionInherits(IceAddTitlePlugin, ice.IcePlugin);
|
||||
exports._plugin.IceAddTitlePlugin = IceAddTitlePlugin;
|
||||
|
||||
}).call(this.ice);
|
||||
510
lib/ckeditor4/plugins/lite/js/plugins/IceCopyPastePlugin/IceCopyPastePlugin.js
Executable file
510
lib/ckeditor4/plugins/lite/js/plugins/IceCopyPastePlugin/IceCopyPastePlugin.js
Executable file
@@ -0,0 +1,510 @@
|
||||
(function() {
|
||||
|
||||
var exports = this, IceCopyPastePlugin;
|
||||
|
||||
IceCopyPastePlugin = function(ice_instance) {
|
||||
this._ice = ice_instance;
|
||||
this._tmpNode = null;
|
||||
this._tmpNodeTagName = 'icepaste';
|
||||
this._pasteId = 'icepastediv';
|
||||
var self = this;
|
||||
|
||||
// API
|
||||
|
||||
// 'formatted' - paste will be MS Word cleaned.
|
||||
// 'formattedClean' - paste will be MS Word cleaned, insert and
|
||||
// delete tags will be removed keeping insert content in place,
|
||||
// and tags not found in `preserve` will be stripped.
|
||||
this.pasteType = 'formattedClean';
|
||||
|
||||
// Subset of tags that will not be stripped when pasteType
|
||||
// is set to 'formattedClean'. Parameter is of type string with
|
||||
// comma delimited tag and attribute definitions. For example:
|
||||
// 'p,a[href],i[style|title],span[*]'
|
||||
// Would allow `p`, `a`, `i` and `span` tags. The attributes for
|
||||
// each one of these tags would be cleaned as follows: `p` tags
|
||||
// would have all attributes removed, `a` tags will have all but
|
||||
// `href` attributes removed, `i` tags will have all but `style`
|
||||
// and `title` attributes removed, and `span` tags will keep all attributes.
|
||||
this.preserve = 'p';
|
||||
|
||||
// Callback triggered before any paste cleaning happens
|
||||
this.beforePasteClean = function(body) { return body; };
|
||||
|
||||
// Callback triggered at the end of the paste cleaning
|
||||
this.afterPasteClean = function(body) { return body; };
|
||||
|
||||
// Event Listeners
|
||||
ice_instance.element.oncopy = function() { return self.handleCopy.apply(self); };
|
||||
// ice_instance.element.oncut = function() { return self.handleCut.apply(self); };
|
||||
|
||||
// We can't listen for `onpaste` unless we use an algorithm that temporarily switches
|
||||
// out the body and lets the browser paste there (found it hard to maintain in mce).
|
||||
// Instead, we'll watch the keydown event and handle paste with a typical temp element
|
||||
// algorithm, which means that pasting from the context menu won't work.
|
||||
};
|
||||
|
||||
IceCopyPastePlugin.prototype = {
|
||||
|
||||
setSettings: function(settings) {
|
||||
settings = settings || {};
|
||||
ice.dom.extend(this, settings);
|
||||
|
||||
this.preserve += ',' + this._tmpNodeTagName;
|
||||
this.setupPreserved();
|
||||
},
|
||||
|
||||
keyDown: function(e) {
|
||||
if (e.metaKey !== true && e.ctrlKey !== true) {
|
||||
return;
|
||||
}
|
||||
if(e.keyCode == 86) {
|
||||
this.handlePaste();
|
||||
}
|
||||
else if(e.keyCode == 88) {
|
||||
this.handleCut();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
keyPress: function(e){
|
||||
var c = null;
|
||||
if (e.which == null) {
|
||||
// IE.
|
||||
c = String.fromCharCode(e.keyCode);
|
||||
} else if (e.which > 0) {
|
||||
c = String.fromCharCode(e.which);
|
||||
}
|
||||
var self = this;
|
||||
if(this.cutElement && c === 'x'){
|
||||
if(ice.dom.isBrowser("webkit")){
|
||||
self.cutElement.focus();
|
||||
}
|
||||
} else if (c === 'v'){
|
||||
if(ice.dom.isBrowser("webkit")){
|
||||
var div = document.getElementById(self._pasteId);
|
||||
div.focus();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleCopy: function(e) {},
|
||||
|
||||
// Inserts a temporary placeholder for the current range and removes
|
||||
// the contents of the ice element body and calls a paste handler.
|
||||
handlePaste: function(e) {
|
||||
|
||||
var range = this._ice.getCurrentRange();
|
||||
|
||||
if(!range.collapsed) {
|
||||
if(this._ice.isTracking) {
|
||||
this._ice.deleteContents();
|
||||
range = range.cloneRange();
|
||||
} else {
|
||||
range.deleteContents();
|
||||
range.collapse(true);
|
||||
}
|
||||
}
|
||||
|
||||
if(this._ice.isTracking)
|
||||
this._ice._moveRangeToValidTrackingPos(range);
|
||||
|
||||
if(range.startContainer == this._ice.element) {
|
||||
// Fix a potentially empty body with a bad selection
|
||||
var firstBlock = ice.dom.find(this._ice.element, this._ice.blockEl)[0];
|
||||
if(!firstBlock) {
|
||||
firstBlock = ice.dom.create('<' + this._ice.blockEl + ' ><br/></' + this._ice.blockEl + '>');
|
||||
this._ice.element.appendChild(firstBlock);
|
||||
}
|
||||
range.setStart(firstBlock, 0);
|
||||
range.collapse(true);
|
||||
this._ice.env.selection.addRange(range);
|
||||
}
|
||||
|
||||
|
||||
this._tmpNode = this._ice.env.document.createElement(this._tmpNodeTagName);
|
||||
range.insertNode(this._tmpNode);
|
||||
|
||||
switch (this.pasteType) {
|
||||
case 'formatted':
|
||||
this.setupPaste();
|
||||
break;
|
||||
case 'formattedClean':
|
||||
this.setupPaste(true);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// Create a temporary div and set focus to it so that the browser can paste into it.
|
||||
// Set a timeout to push a paste handler on to the end of the execution stack.
|
||||
setupPaste: function(stripTags) {
|
||||
var div = this.createDiv(this._pasteId), self = this;
|
||||
|
||||
div.onpaste = function() {
|
||||
setTimeout(function(){
|
||||
self.handlePasteValue(stripTags);
|
||||
},0);
|
||||
};
|
||||
|
||||
if(this._ice.env.frame){
|
||||
if(ice.dom.isBrowser("webkit")){
|
||||
div.blur();
|
||||
setTimeout(function(){
|
||||
div.focus();
|
||||
}, 0);
|
||||
} else {
|
||||
div.focus();
|
||||
}
|
||||
}
|
||||
else{
|
||||
div.focus();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// By the time we get here, the pasted content will already be in the body. Extract the
|
||||
// paste, format it, remove any Microsoft or extraneous tags outside of `this.preserve`
|
||||
// and merge the pasted content into the original fragment body.
|
||||
handlePasteValue: function(stripTags) {
|
||||
// Get the pasted content.
|
||||
var html = ice.dom.getHtml(document.getElementById(this._pasteId));
|
||||
var childBlocks = ice.dom.children('<div>' + html + '</div>', this._ice.blockEl);
|
||||
if(childBlocks.length === 1 && ice.dom.getNodeTextContent('<div>' + html + '</div>') === ice.dom.getNodeTextContent(childBlocks)) {
|
||||
html = ice.dom.getHtml(html);
|
||||
}
|
||||
|
||||
html = this.beforePasteClean.call(this, html);
|
||||
|
||||
if(stripTags) {
|
||||
|
||||
// Strip out change tracking tags.
|
||||
html = this._ice.getCleanContent(html);
|
||||
html = this.stripPaste(html);
|
||||
}
|
||||
|
||||
html = this.afterPasteClean.call(this, html);
|
||||
html = ice.dom.trim(html);
|
||||
var range = this._ice.getCurrentRange();
|
||||
range.setStartAfter(this._tmpNode);
|
||||
range.collapse(true);
|
||||
|
||||
var innerBlock = null, lastEl = null, newEl = null;
|
||||
var fragment = range.createContextualFragment(html);
|
||||
var changeid = this._ice.startBatchChange();
|
||||
|
||||
// If fragment contains block level elements, most likely we will need to
|
||||
// do some splitting so we do not have P tags in P tags, etc. Split the
|
||||
// container from current selection and then insert paste contents after it.
|
||||
if(ice.dom.hasBlockChildren(fragment)) {
|
||||
// Split from current selection.
|
||||
var block = ice.dom.isChildOfTagName(this._tmpNode, this._ice.blockEl);
|
||||
range.setEndAfter(block.lastChild);
|
||||
this._ice.selection.addRange(range);
|
||||
var contents = range.extractContents();
|
||||
var newblock = this._ice.env.document.createElement(this._ice.blockEl);
|
||||
newblock.appendChild(contents);
|
||||
ice.dom.insertAfter(block, newblock);
|
||||
|
||||
range.setStart(newblock, 0);
|
||||
range.collapse(true);
|
||||
this._ice.selection.addRange(range);
|
||||
var prevBlock = range.startContainer;
|
||||
|
||||
// Paste all of the children in the fragment.
|
||||
while(fragment.firstChild) {
|
||||
if(fragment.firstChild.nodeType === 3 && !jQuery.trim(fragment.firstChild.nodeValue)) {
|
||||
fragment.removeChild(fragment.firstChild);
|
||||
continue;
|
||||
}
|
||||
// We may have blocks with text nodes at the beginning or end. For example, this paste:
|
||||
// textnode <p>blocktext</p> <p>blocktext</p> moretext
|
||||
// In which case we wrap the leading or trailing text nodes in blocks.
|
||||
if(ice.dom.isBlockElement(fragment.firstChild)) {
|
||||
if(fragment.firstChild.textContent !== "") {
|
||||
innerBlock = null;
|
||||
var insert = null;
|
||||
if(this._ice.isTracking) {
|
||||
insert = this._ice.createIceNode('insertType');
|
||||
this._ice.addChange('insertType', [insert]);
|
||||
newEl = document.createElement(fragment.firstChild.tagName);
|
||||
insert.innerHTML = fragment.firstChild.innerHTML;
|
||||
newEl.appendChild(insert);
|
||||
} else {
|
||||
insert = newEl = document.createElement(fragment.firstChild.tagName);
|
||||
newEl.innerHTML = fragment.firstChild.innerHTML;
|
||||
}
|
||||
lastEl = insert;
|
||||
ice.dom.insertBefore(prevBlock, newEl);
|
||||
}
|
||||
fragment.removeChild(fragment.firstChild);
|
||||
} else {
|
||||
if(!innerBlock) {
|
||||
// Create a new block and append an insert
|
||||
newEl = document.createElement(this._ice.blockEl);
|
||||
ice.dom.insertBefore(prevBlock, newEl);
|
||||
if(this._ice.isTracking) {
|
||||
innerBlock = this._ice.createIceNode('insertType');
|
||||
this._ice.addChange('insertType', [innerBlock]);
|
||||
newEl.appendChild(innerBlock);
|
||||
} else {
|
||||
innerBlock = newEl;
|
||||
}
|
||||
}
|
||||
lastEl = innerBlock;
|
||||
innerBlock.appendChild(fragment.removeChild(fragment.firstChild));
|
||||
}
|
||||
}
|
||||
if (!newblock.textContent) {
|
||||
newblock.parentNode.removeChild(newblock);
|
||||
}
|
||||
|
||||
} else {
|
||||
if(this._ice.isTracking) {
|
||||
newEl = this._ice.createIceNode('insertType', fragment);
|
||||
this._ice.addChange('insertType', [newEl]);
|
||||
range.insertNode(newEl);
|
||||
lastEl = newEl;
|
||||
} else {
|
||||
var child;
|
||||
while((child = fragment.firstChild)) {
|
||||
range.insertNode(child);
|
||||
range.setStartAfter(child);
|
||||
range.collapse(true);
|
||||
lastEl = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._ice.endBatchChange(changeid);
|
||||
this._cleanup(lastEl);
|
||||
},
|
||||
|
||||
|
||||
createDiv: function(id) {
|
||||
var oldEl = ice.dom.getId(id);
|
||||
if(oldEl) {
|
||||
ice.dom.remove(oldEl);
|
||||
}
|
||||
|
||||
var div = this._ice.env.document.createElement('div');
|
||||
div.id = id;
|
||||
div.setAttribute('contentEditable', true);
|
||||
ice.dom.setStyle(div, 'width', '1px');
|
||||
ice.dom.setStyle(div, 'height', '1px');
|
||||
ice.dom.setStyle(div, 'overflow', 'hidden');
|
||||
ice.dom.setStyle(div, 'position', 'fixed');
|
||||
ice.dom.setStyle(div, 'top', '10px');
|
||||
ice.dom.setStyle(div, 'left', '10px');
|
||||
|
||||
document.body.appendChild(div);
|
||||
return div;
|
||||
},
|
||||
|
||||
// Intercepts cut operation and handles by creating an editable div, copying the current selection
|
||||
// into it, deleting the current selection with track changes, and selecting the contents in the
|
||||
// editable div.
|
||||
handleCut: function() {
|
||||
this.cutElementId = 'icecut';
|
||||
this.cutElement = this.createDiv(this.cutElementId);
|
||||
var range = this._ice.getCurrentRange();
|
||||
if(range.collapsed) return;
|
||||
var html = range.getHTMLContents();
|
||||
if (this._ice.isTracking) this._ice.deleteContents();
|
||||
else range.deleteContents();
|
||||
// var crange = range.cloneRange();
|
||||
// var crange = rangy.createRange();
|
||||
var crange = document.createRange();
|
||||
// crange.collapse(true);
|
||||
this.cutElement.innerHTML = html;
|
||||
|
||||
crange.setStart(this.cutElement.firstChild, 0);
|
||||
crange.setEndAfter(this.cutElement.lastChild);
|
||||
var self = this;
|
||||
|
||||
// this.cutElement.blur();
|
||||
if(this._ice.env.frame){
|
||||
// TINYMCE
|
||||
setTimeout(function(){
|
||||
self.cutElement.focus();
|
||||
|
||||
// After the browser cuts out of the `cutElement`, reset the range and remove the cut element.
|
||||
setTimeout(function() {
|
||||
ice.dom.remove(self.cutElement);
|
||||
range.setStart(range.startContainer, range.startOffset);
|
||||
range.collapse(false);
|
||||
self._ice.env.selection.addRange(range);
|
||||
// Set focus back to ice element.
|
||||
if(self._ice.env.frame) {
|
||||
self._ice.env.frame.contentWindow.focus();
|
||||
} else {
|
||||
self._ice.element.focus();
|
||||
}
|
||||
}, 100);
|
||||
}, 0);
|
||||
} else {
|
||||
// Vanilla Div
|
||||
setTimeout(function(){
|
||||
self.cutElement.focus();
|
||||
|
||||
// After the browser cuts out of the `cutElement`, reset the range and remove the cut element.
|
||||
setTimeout(function() {
|
||||
range.setStart(range.startContainer, range.startOffset);
|
||||
range.collapse(false);
|
||||
self._ice.env.selection.addRange(range);
|
||||
// Set focus back to ice element.
|
||||
if(self._ice.env.frame) {
|
||||
self._ice.env.frame.contentWindow.focus();
|
||||
} else {
|
||||
self._ice.element.focus();
|
||||
}
|
||||
}, 100);
|
||||
}, 0);
|
||||
|
||||
if(ice.dom.getWebkitType() === "chrome"){
|
||||
self.cutElement.focus();
|
||||
}
|
||||
}
|
||||
self._ice.env.selection.addRange(crange);
|
||||
},
|
||||
|
||||
|
||||
// Strips ice change tracking tags, Microsoft Word styling/content, and any
|
||||
// tags and attributes not found in `preserve` from the given `content`.
|
||||
stripPaste: function(content) {
|
||||
// Clean word stuff out and strip tags that are not in `this.preserve`.
|
||||
content = this._cleanWordPaste(content);
|
||||
content = this.cleanPreserved(content);
|
||||
return content;
|
||||
},
|
||||
|
||||
// Parses `preserve` to setup `_tags` with a comma delimited list of all of the
|
||||
// defined tags, and the `_attributesMap` with a mapping between the allowed tags and
|
||||
// an array of their allowed attributes. For example, given this value:
|
||||
// `preserve` = 'p,a[href|class],span[*]'
|
||||
// The following will result:
|
||||
// `_tags` = 'p,a,span'
|
||||
// `_attributesMap` = ['p' => [], 'a' => ['href', 'class'], 'span' => ['*']]
|
||||
setupPreserved: function() {
|
||||
var self = this;
|
||||
this._tags = '';
|
||||
this._attributesMap = [];
|
||||
|
||||
ice.dom.each(this.preserve.split(','), function(i, tagAttr) {
|
||||
// Extract the tag and attributes list
|
||||
tagAttr.match(/(\w+)(\[(.+)\])?/);
|
||||
var tag = RegExp.$1;
|
||||
var attr = RegExp.$3;
|
||||
|
||||
if(self._tags) self._tags += ',';
|
||||
self._tags += tag.toLowerCase();
|
||||
self._attributesMap[tag] = attr.split('|');
|
||||
});
|
||||
},
|
||||
|
||||
// Cleans the given `body` by removing any tags not found in `_tags` and replacing them with
|
||||
// their inner contents, and removes attributes from any tags that aren't mapped in `_attributesMap`.
|
||||
cleanPreserved: function(body) {
|
||||
var self = this;
|
||||
var bodyel = this._ice.env.document.createElement('div');
|
||||
bodyel.innerHTML = body;
|
||||
|
||||
// Strip out any tags not found in `this._tags`, replacing the tags with their inner contents.
|
||||
bodyel = ice.dom.stripEnclosingTags(bodyel, this._tags);
|
||||
|
||||
// Strip out any attributes from the allowed set of tags that don't match what is in the `_attributesMap`
|
||||
ice.dom.each(ice.dom.find(bodyel, this._tags), function(i, el) {
|
||||
if (ice.dom.hasClass(el, 'skip-clean')) {
|
||||
return true;
|
||||
}
|
||||
var tag = el.tagName.toLowerCase();
|
||||
var attrMatches = self._attributesMap[tag];
|
||||
|
||||
// Kleene star - keep all of the attributes for this tag.
|
||||
if(attrMatches[0] && attrMatches[0] === '*')
|
||||
return true;
|
||||
|
||||
// Remove any foreign attributes that do not match the map.
|
||||
if(el.hasAttributes()) {
|
||||
var attributes = el.attributes;
|
||||
for(var i = attributes.length - 1; i >= 0; i--) {
|
||||
if(!ice.dom.inArray(attributes[i].name, attrMatches)) {
|
||||
el.removeAttribute(attributes[i].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return bodyel.innerHTML;
|
||||
},
|
||||
|
||||
_cleanWordPaste: function(content) {
|
||||
// Meta and link tags.
|
||||
content = content.replace(/<(meta|link)[^>]+>/g, "");
|
||||
|
||||
// Comments.
|
||||
content = content.replace(/<!--(.|\s)*?-->/g, '');
|
||||
|
||||
// Remove style tags.
|
||||
content = content.replace(/<style>[\s\S]*?<\/style>/g, '');
|
||||
|
||||
// Remove span and o:p etc. tags.
|
||||
//content = content.replace(/<\/?span[^>]*>/gi, "");
|
||||
content = content.replace(/<\/?\w+:[^>]*>/gi, '' );
|
||||
|
||||
// Remove XML tags.
|
||||
content = content.replace(/<\\?\?xml[^>]*>/gi, '');
|
||||
|
||||
// Generic cleanup.
|
||||
content = this._cleanPaste(content);
|
||||
|
||||
// Remove class, lang and style attributes.
|
||||
content = content.replace(/<(\w[^>]*) (lang)=([^ |>]*)([^>]*)/gi, "<$1$4");
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
_cleanPaste: function(content) {
|
||||
// Some generic content cleanup. Change all b/i tags to strong/em.
|
||||
content = content.replace(/<b(\s+|>)/g, "<strong$1");
|
||||
content = content.replace(/<\/b(\s+|>)/g, "</strong$1");
|
||||
content = content.replace(/<i(\s+|>)/g, "<em$1");
|
||||
content = content.replace(/<\/i(\s+|>)/g, "</em$1");
|
||||
return content;
|
||||
},
|
||||
|
||||
_cleanup: function(moveTo) {
|
||||
try {
|
||||
moveTo = moveTo && moveTo.lastChild || moveTo || this._tmpNode;
|
||||
// Move the range to the end of moveTo so that the cursor will be at the end of the paste.
|
||||
var range = this._ice.getCurrentRange();
|
||||
range.setStartAfter(moveTo);
|
||||
range.collapse(true);
|
||||
this._ice.selection.addRange(range);
|
||||
// Set focus back to ice element.
|
||||
if(this._ice.env.frame) {
|
||||
this._ice.env.frame.contentWindow.focus();
|
||||
} else {
|
||||
this._ice.element.focus();
|
||||
}
|
||||
// Kill the tmp node.
|
||||
this._tmpNode.parentNode.removeChild(this._tmpNode);
|
||||
this._tmpNode = null;
|
||||
// Kill any empty change nodes.
|
||||
var ins = this._ice.env.document.getElementsByClassName(this._ice.changeTypes['insertType'].alias);
|
||||
for(var i = 0; i < ins.length; i++) {
|
||||
if(!ins[i].textContent) {
|
||||
if(ins[i].parentNode) {
|
||||
ins[i].parentNode.removeChild(ins[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
window.console && console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ice.dom.noInclusionInherits(IceCopyPastePlugin, ice.IcePlugin);
|
||||
exports._plugin.IceCopyPastePlugin = IceCopyPastePlugin;
|
||||
|
||||
}).call(this.ice);
|
||||
76
lib/ckeditor4/plugins/lite/js/plugins/IceEmdashPlugin/IceEmdashPlugin.js
Executable file
76
lib/ckeditor4/plugins/lite/js/plugins/IceEmdashPlugin/IceEmdashPlugin.js
Executable file
@@ -0,0 +1,76 @@
|
||||
(function() {
|
||||
|
||||
var exports = this;
|
||||
|
||||
/**
|
||||
* When active, this plugin will convert two successively typed dashes, within
|
||||
* the ice block element, into an emdash.
|
||||
*/
|
||||
var IceEmdashPlugin = function(ice_instance) {
|
||||
this._ice = ice_instance;
|
||||
};
|
||||
|
||||
IceEmdashPlugin.prototype = {
|
||||
|
||||
keyDown: function(e) {
|
||||
// Catch dashes.
|
||||
if(ice.dom.isBrowser('mozilla')) {
|
||||
var version = parseInt(ice.dom.browser().version);
|
||||
if ( (version > 14 && e.keyCode === 173) || (version <= 14 && e.keyCode === 109) ) {
|
||||
return this.convertEmdash(e);
|
||||
}
|
||||
} else if(e.keyCode === 189) {
|
||||
return this.convertEmdash(e);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
convertEmdash: function(e) {
|
||||
var range = this._ice.getCurrentRange();
|
||||
if(range.collapsed) {
|
||||
try {
|
||||
// Move the start back one character so we can enclose the range around the previous character to check if it is a dash
|
||||
range.moveStart(ice.dom.CHARACTER_UNIT, -1);
|
||||
// Get the parent block element for the start and end containers
|
||||
var startBlock = ice.dom.getParents(range.startContainer, this._ice.blockEl)[0];
|
||||
var endBlock = ice.dom.getParents(range.endContainer, this._ice.blockEl)[0];
|
||||
// Make sure that the start and end containers aren't in different blocks, or that the start isn't in a delete.
|
||||
if(startBlock === endBlock && !this._ice.getIceNode(range.startContainer, 'deleteType')) {
|
||||
// Get the last character and check to see if it is a dash.
|
||||
c = range.toHtml();
|
||||
if(c === '-') {
|
||||
// Extract the last character/dash and insert an emdash
|
||||
range.extractContents();
|
||||
range.collapse();
|
||||
var mdash = this._ice.env.document.createTextNode('\u2014');
|
||||
if (this._ice.isTracking) {
|
||||
this._ice._insertNode(mdash, range);
|
||||
} else {
|
||||
range.insertNode(mdash);
|
||||
/* TO be reverted once mozilla fixes FF 15 issue */
|
||||
range.setStart(mdash, 1);
|
||||
range.collapse(true);
|
||||
/* FINISH revert */
|
||||
}
|
||||
/* TO be reverted once mozilla fixes FF 15 issue
|
||||
range = this._ice.getCurrentRange();
|
||||
range.moveStart(ice.dom.CHARACTER_UNIT, 1);
|
||||
range.collapse(true);
|
||||
this._ice.env.selection.addRange(range);
|
||||
*/
|
||||
this._ice._preventKeyPress = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
range.collapse();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ice.dom.noInclusionInherits(IceEmdashPlugin, ice.IcePlugin);
|
||||
exports._plugin.IceEmdashPlugin = IceEmdashPlugin;
|
||||
|
||||
}).call(this.ice);
|
||||
@@ -0,0 +1,184 @@
|
||||
(function() {
|
||||
|
||||
var exports = this, ice = this.ice;
|
||||
|
||||
var IceSmartQuotesPlugin = function(ice_instance) {
|
||||
this._ice = ice_instance;
|
||||
};
|
||||
|
||||
IceSmartQuotesPlugin.prototype = {
|
||||
|
||||
/**
|
||||
* Finds each block in `element` and converts quotes into smart quotes.
|
||||
*/
|
||||
convert: function(element) {
|
||||
var self = this;
|
||||
try {
|
||||
self._ice.placeholdDeletes();
|
||||
ice.dom.each(element.getElementsByTagName(this._ice.blockEl), function(i, el) {
|
||||
self._convertBlock(el);
|
||||
});
|
||||
} catch(e) {
|
||||
window.console && console.error(e);
|
||||
} finally {
|
||||
self._ice.revertDeletePlaceholders();
|
||||
}
|
||||
},
|
||||
|
||||
// Converts the quotes in the given element to smart quotes.
|
||||
_convertBlock: function(el) {
|
||||
|
||||
// If there are less than 2 characters, we don't have enough to go on.
|
||||
if (ice.dom.getNodeTextContent(el) < 2) return;
|
||||
|
||||
var previous, current, next, data, html = '', getNextChar,
|
||||
regularSingle = "'",
|
||||
regularDouble = '"',
|
||||
smartSingleLeft = String.fromCharCode(8216), // aka - open curly single quote
|
||||
smartSingleRight = String.fromCharCode(8217), // aka - close curly single quote
|
||||
smartDoubleLeft = String.fromCharCode(8220), // aka - open curly double quote
|
||||
smartDoubleRight = String.fromCharCode(8221), // aka - close curly double quote
|
||||
isDigit = function(c) { return /\d/.test(c); },
|
||||
isChar = function(c) { return /\w/.test(c); },
|
||||
isSpace = function(c) { return c === String.fromCharCode(160) || c === String.fromCharCode(32); },
|
||||
isStartChar = function(c) { return isSpace(c) || c === '('; },
|
||||
isEndChar = function(c) { return isSpace(c) || c == null || c === ';' || c === ')' || c == '.' || c === '!' || c === ',' || c === '?' || c === ':'; },
|
||||
isNonSpace = function(c) { return !isSpace(c); },
|
||||
isDouble = function(c) { return c === regularDouble || c === smartDoubleLeft || c === smartDoubleRight; },
|
||||
isSingle = function(c) { return c === regularSingle || c === smartSingleLeft || c === smartSingleRight; };
|
||||
|
||||
// Split the html into array allocations with the following criteria:
|
||||
// html tags: starts with "<" and ends with ">"
|
||||
// html entities: starts with "&" and ends with ";"
|
||||
// characters: any character outside of an html tag or entity
|
||||
// So the following html:
|
||||
// n&ce <b id="bold">test</b>
|
||||
// Would split into the following array:
|
||||
// ['n', '&', 'c', 'e', ' ', '<b id="bold">', 't', 'e', 's', 't', '</b>'];
|
||||
data = ice.dom.getHtml(el).match(/(<("[^"]*"|'[^']*'|[^'">])*>|&.*;|.)/g);
|
||||
|
||||
// Searches through the `data` array from the given index a given number of
|
||||
// characters forward or backward and returns the found character.
|
||||
getNextChar = function(data, fromIndex, nCharacters) {
|
||||
var dLength = data.length,
|
||||
addWith = nCharacters < 0 ? -1 : 1;
|
||||
|
||||
return (function getChar(data, fromIndex, nCharacters) {
|
||||
// Base case - did we move outside of the bounds of the data array?
|
||||
if (fromIndex < 0 || fromIndex >= dLength) return null;
|
||||
|
||||
var next = data[fromIndex + addWith];
|
||||
|
||||
// If we find a character and we have moved through the
|
||||
// nCharacters, then the recursion is done.
|
||||
if (next && next.length == 1) {
|
||||
nCharacters += (addWith * -1);
|
||||
if (!nCharacters) return next;
|
||||
}
|
||||
return getChar(data, fromIndex + addWith, nCharacters);
|
||||
|
||||
})(data, fromIndex, nCharacters);
|
||||
};
|
||||
|
||||
ice.dom.each(data, function(pos, val) {
|
||||
// Convert space entities so that they can be processed as normal characters.
|
||||
if (val == ' ') val = data[pos] = ' ';
|
||||
|
||||
// If the val is a character, then examine the surroundings
|
||||
// and convert smart quotes, if necessary.
|
||||
if (val.length == 1) {
|
||||
|
||||
// Keep convenience pointers to the previous, current and next characters.
|
||||
previous = getNextChar(data, pos, -1);
|
||||
current = val;
|
||||
next = getNextChar(data, pos, 1);
|
||||
|
||||
switch (current) {
|
||||
|
||||
/**
|
||||
* Conversion Rules:
|
||||
* ----------------
|
||||
*
|
||||
* START: assign smart left/open
|
||||
* [SPACE|START_PARENTHESIS]'word
|
||||
* [SPACE|START_PARENTHESIS]"word
|
||||
*
|
||||
* END: assign smart right/close
|
||||
* word'[SPACE|SEMICOLON|COLON|PERIOD|COMMA|EXCLAMATION_MARK|QUESTION_MARK|END_PARENTHESIS|NULL]
|
||||
* word"[SPACE|SEMICOLON|COLON|PERIOD|COMMA|EXCLAMATION_MARK|QUESTION_MARK|END_PARENTHESIS|NULL]
|
||||
*
|
||||
* PLURAL_CONTRACTION: assign smart right/close
|
||||
* Matt's
|
||||
* can't
|
||||
* O'Reilly
|
||||
*
|
||||
* YEAR_ABBREVIATION: assign smart right/close
|
||||
* [SPACE|NULL]'99[SPACE|SEMICOLON|COLON|PERIOD|COMMA|EXCLAMATION_MARK|QUESTION_MARK|END_PARENTHESIS|NULL]
|
||||
*
|
||||
* NESTED_START: assign smart left/open
|
||||
* [SPACE|NULL]"[SPACE]'word
|
||||
*
|
||||
* NESTED_END: assign smart left/open
|
||||
* word'[SPACE]"[SPACE|SEMICOLON|COLON|PERIOD|COMMA|EXCLAMATION_MARK|QUESTION_MARK|END_PARENTHESIS|NULL]
|
||||
*
|
||||
* Notes:
|
||||
* - The following will not be converted correctly - ...word 'Til Death - it should
|
||||
* get a right/close smart quote, but will get a left/open.
|
||||
* - Distinguishing between year abbreviation, '99, and when to use an open single quote
|
||||
* could fail if a single quoted region starts with a double digit number - '99 problems'
|
||||
* - Since they are a rare case and there are many permutations, measurements are not being
|
||||
* handled (6'8", 6' 8", 6', 8").
|
||||
*/
|
||||
|
||||
// Convert smart single quotes to non-smart quote and fall through to single quote
|
||||
// handling, in case the context has changed and we need to update the smart quote.
|
||||
case smartSingleLeft:
|
||||
case smartSingleRight:
|
||||
current = regularSingle;
|
||||
case regularSingle:
|
||||
// YEAR_ABBREVIATION - look 2 ahead to see if there are two digits in a row - not fool proof
|
||||
if ((previous == null || isSpace(previous)) && isDigit(next) && isDigit(getNextChar(data, pos, 2)) && isEndChar(getNextChar(data, pos, 3)))
|
||||
current = smartSingleRight;
|
||||
// START
|
||||
else if (previous == null || (isStartChar(previous) && isNonSpace(next)))
|
||||
current = smartSingleLeft;
|
||||
// END
|
||||
else if (next == null || (isNonSpace(previous) && isEndChar(next)))
|
||||
current = smartSingleRight;
|
||||
// PLURAL_CONTRACTION
|
||||
else if (isChar(previous) && isChar(next))
|
||||
current = smartSingleRight;
|
||||
break;
|
||||
|
||||
// Convert smart double quotes to non-smart quote and fall through to double quote
|
||||
// handling, in case the context has changed and we need to update the smart quote.
|
||||
case smartDoubleLeft:
|
||||
case smartDoubleRight:
|
||||
current = regularDouble;
|
||||
case regularDouble:
|
||||
// NESTED_END
|
||||
if (isEndChar(next) && isSpace(previous) && isSingle(getNextChar(data, pos, -2)))
|
||||
current = smartDoubleRight;
|
||||
// START
|
||||
else if (previous == null || (isStartChar(previous) && isNonSpace(next)))
|
||||
current = smartDoubleLeft;
|
||||
// END
|
||||
else if (next == null || (isNonSpace(previous) && isEndChar(next)))
|
||||
current = smartDoubleRight;
|
||||
// NESTED_START
|
||||
else if ((previous == null || isSpace(previous)) && (isSpace(next) && isSingle(getNextChar(data, pos, 1))))
|
||||
current = smartDoubleLeft;
|
||||
break;
|
||||
}
|
||||
if (current != null) data[pos] = current;
|
||||
}
|
||||
});
|
||||
|
||||
ice.dom.setHtml(el, data.join(''));
|
||||
}
|
||||
};
|
||||
|
||||
ice.dom.noInclusionInherits(IceSmartQuotesPlugin, ice.IcePlugin);
|
||||
exports.ice._plugin.IceSmartQuotesPlugin = IceSmartQuotesPlugin;
|
||||
|
||||
}).call(this);
|
||||
Reference in New Issue
Block a user