<\/div>$/im)
+ false
diff --git a/app/assets/javascripts/app/lib/base/jquery.contenteditable.js b/app/assets/javascripts/app/lib/base/jquery.contenteditable.js
index f47e1ac43..ee1d5ac8c 100644
--- a/app/assets/javascripts/app/lib/base/jquery.contenteditable.js
+++ b/app/assets/javascripts/app/lib/base/jquery.contenteditable.js
@@ -25,6 +25,7 @@
40: true, // left
91: true, // cmd left
92: true, // cmd right
+ 224: true, // cmd left
},
extraAllowKey: {
65: true, // a + ctrl - select all
@@ -38,6 +39,7 @@
73: true, // i
85: true, // u
},
+ //maxlength: 20,
};
function Plugin( element, options ) {
@@ -58,13 +60,9 @@
this.preventInput = false
this.init();
- // bind
-
- // bind paste
}
-
Plugin.prototype.init = function () {
var _this = this
@@ -84,98 +82,55 @@
e.preventDefault()
return
}
- // limit check
- if ( !_this.maxLengthOk( true ) ) {
+ }
+
+ // limit check
+ if ( !_this.allowKey(e) ) {
+ if ( !_this.maxLengthOk( 1 ) ) {
e.preventDefault()
return
}
}
})
- this.$element.on('keyup', function (e) {
- console.log('KU', e.ctrlKey)
- // do not remove tags on space, enter or backspace key, it's needed for FF
- if ( _this.options.mode === 'textonly' ) {
- if ( !_this.options.multiline ) {
-
- // do tricky this for FF
- if ( e.keyCode !== 32 && e.keyCode !== 13 && e.keyCode !== 8 ) {
-
- // start request to remove tags
- _this.htmlRemoveTags()
- }
- else {
-
- // clear request to delete tags, in FF we need
anytime at the end
- _this.htmlRemoveTagsClearClearTimeout()
- }
- }
- else {
- App.Utils.htmlRemoveRichtext(_this.$element)
- }
- }
- else {
- App.Utils.htmlClanup(_this.$element)
- }
- })
-
-
// just paste text
this.$element.on('paste', function (e) {
console.log('paste')
+
+ // check existing + paste text for limit
+ var text
+ if (window.clipboardData) { // IE
+ text = window.clipboardData.getData('Text')
+ }
+ else {
+ text = (e.originalEvent || e).clipboardData.getData('text/plain')
+ }
+
+ if ( !_this.maxLengthOk( text.length) ) {
+ e.preventDefault()
+ return
+ }
+
+ // use setTimeout() with 0 to execute it right after paste event
if ( _this.options.mode === 'textonly' ) {
if ( !_this.options.multiline ) {
- _this.htmlRemoveTags()
+ setTimeout($.proxy(function(){
+ App.Utils.htmlRemoveTags(this.$element)
+ }, _this), 0)
}
else {
- App.Utils.htmlRemoveRichtext(_this.$element)
+ setTimeout($.proxy(function(){
+ App.Utils.htmlRemoveRichtext(this.$element)
+ }, _this), 0)
}
}
else {
- App.Utils.htmlClanup(_this.$element)
+ setTimeout($.proxy(function(){
+ App.Utils.htmlClanup(this.$element)
+ }, _this), 0)
}
+
return true
- if ( this.options.mode === 'textonly' ) {
- e.preventDefault()
- var text
- if (window.clipboardData) { // IE
- text = window.clipboardData.getData('Text')
- }
- else {
- text = (e.originalEvent || e).clipboardData.getData('text/plain')
- }
- var overlimit = false
- if (text) {
-
- // replace new lines
- if ( !_this.options.multiline ) {
- text = text.replace(/\n/g, '')
- text = text.replace(/\r/g, '')
- text = text.replace(/\t/g, '')
- }
-
- // limit length, limit paste string
- if ( _this.options.maxlength ) {
- var pasteLength = text.length
- var currentLength = _this.$element.text().length
- var overSize = ( currentLength + pasteLength ) - _this.options.maxlength
- if ( overSize > 0 ) {
- text = text.substr( 0, pasteLength - overSize )
- overlimit = true
- }
- }
-
- // insert new text
- if (document.selection) { // IE
- var range = document.selection.createRange()
- range.pasteHTML(text)
- }
- else {
- document.execCommand('inserttext', false, text)
- }
- _this.maxLengthOk( overlimit )
- }
- }
})
// disable rich text b/u/i
@@ -188,23 +143,15 @@
}
}
- // check if rich text key is pressed
- Plugin.prototype.htmlRemoveTags = function() {
-
- // clear old clear request
- this.htmlRemoveTagsClearClearTimeout()
-
- // set new clear request
- this._setTimeOutReformat = setTimeout($.proxy(function(){
- App.Utils.htmlRemoveTags(this.$element)
- }, this), 100)
- console.log('htmlRemoveTagsClearSetTimeout', this._setTimeOutReformat)
- }
- Plugin.prototype.htmlRemoveTagsClearClearTimeout = function() {
- if (this._setTimeOutReformat) {
- console.log('htmlRemoveTagsClearClearTimeout', this._setTimeOutReformat)
- clearTimeout(this._setTimeOutReformat)
+ // check if key is allowed, even if length limit is reached
+ Plugin.prototype.allowKey = function(e) {
+ if ( this.options.allowKey[ e.keyCode ] ) {
+ return true
}
+ if ( ( e.ctrlKey || e.metaKey ) && this.options.extraAllowKey[ e.keyCode ] ) {
+ return true
+ }
+ return false
}
// check if rich text key is pressed
@@ -225,10 +172,14 @@
// max length check
Plugin.prototype.maxLengthOk = function(typeAhead) {
+ if ( !this.options.maxlength ) {
+ return true
+ }
var length = this.$element.text().length
if (typeAhead) {
- length = length + 1
+ length = length + typeAhead
}
+ console.log('maxLengthOk', length, this.options.maxlength)
if ( length > this.options.maxlength ) {
// try to set error on framework form
diff --git a/app/assets/javascripts/app/lib/base/jquery.textmodule.js b/app/assets/javascripts/app/lib/base/jquery.textmodule.js
index 989443f39..613b79271 100644
--- a/app/assets/javascripts/app/lib/base/jquery.textmodule.js
+++ b/app/assets/javascripts/app/lib/base/jquery.textmodule.js
@@ -22,8 +22,8 @@
this._name = pluginName
this.collection = []
- this.active = false
- this.buffer = ''
+ this.active = false
+ this.buffer = ''
// check if ce exists
if ( $.data(element, 'plugin_ce') ) {
@@ -52,18 +52,22 @@
e.preventDefault()
var id = _this.$widget.find('.dropdown-menu li.active a').data('id')
_this.take(id)
+ return
}
- // arrow keys
- if ( e.keyCode === 37 || e.keyCode === 38 || e.keyCode === 39 || e.keyCode === 40 ) {
+ // arrow keys left/right
+ if ( e.keyCode === 37 || e.keyCode === 39 ) {
e.preventDefault()
+ return
}
// up
if ( e.keyCode === 38 ) {
+ e.preventDefault()
if ( !_this.$widget.find('.dropdown-menu li.active')[0] ) {
var top = _this.$widget.find('.dropdown-menu li').last().addClass('active').position().top
_this.$widget.find('.dropdown-menu').scrollTop( top );
+ return
}
else {
var prev = _this.$widget.find('.dropdown-menu li.active').removeClass('active').prev()
@@ -72,15 +76,17 @@
top = prev.addClass('active').position().top
}
_this.$widget.find('.dropdown-menu').scrollTop( top );
+ return
}
}
// down
if ( e.keyCode === 40 ) {
+ e.preventDefault()
if ( !_this.$widget.find('.dropdown-menu li.active')[0] ) {
var top = _this.$widget.find('.dropdown-menu li').first().addClass('active').position().top
_this.$widget.find('.dropdown-menu').scrollTop( top );
-
+ return
}
else {
var next = _this.$widget.find('.dropdown-menu li.active').removeClass('active').next()
@@ -89,6 +95,7 @@
top = next.addClass('active').position().top
}
_this.$widget.find('.dropdown-menu').scrollTop( top );
+ return
}
}
@@ -100,9 +107,15 @@
// backspace
if ( e.keyCode === 8 && _this.buffer ) {
+
+ // backspace + buffer === :: -> close textmodule
if ( _this.buffer === '::' ) {
_this.close()
+ e.preventDefault()
+ return
}
+
+ // reduce buffer and show new result
var length = _this.buffer.length
_this.buffer = _this.buffer.substr( 0, length-1 )
console.log('BS backspace', _this.buffer)
@@ -115,22 +128,26 @@
console.log('BUFF', _this.buffer, e.keyCode, String.fromCharCode(e.which) )
// shift
- if ( e.keyCode === 16 ) {
- return
- }
+ if ( e.keyCode === 16 ) return
// enter
- if ( e.keyCode === 13 ) {
- return
- }
+ if ( e.keyCode === 13 ) return
// arrow keys
- if ( e.keyCode === 37 || e.keyCode === 38 || e.keyCode === 39 || e.keyCode === 40 ) {
- return
+ if ( e.keyCode === 37 || e.keyCode === 38 || e.keyCode === 39 || e.keyCode === 40 ) return
+
+ // observer other second key
+ if ( _this.buffer === ':' && String.fromCharCode(e.which) !== ':' ) {
+ _this.buffer = ''
}
- // enter :
- if ( String.fromCharCode(e.which) === ':' ) {
+ // oberserve second :
+ if ( _this.buffer === ':' && String.fromCharCode(e.which) === ':' ) {
+ _this.buffer = _this.buffer + ':'
+ }
+
+ // oberserve first :
+ if ( !_this.buffer && String.fromCharCode(e.which) === ':' ) {
_this.buffer = _this.buffer + ':'
}
@@ -172,57 +189,38 @@
this.$widget = this.$element.next()
}
- // update widget position
+ // set height of widget
+ Plugin.prototype.movePosition = function() {
+ if (!this._position) return
+ var height = this.$element.height() + 20
+ var widgetHeight = this.$widget.find('ul').height() //+ 60 // + height
+ var top = -( widgetHeight + height ) + this._position.top
+ this.$widget.css('top', top)
+ this.$widget.css('left', this._position.left)
+ }
+
+ // set position of widget
Plugin.prototype.updatePosition = function() {
this.$widget.find('.dropdown-menu').scrollTop( 300 );
if ( !this.$element.is(':visible') ) return
// get cursor position
var marker = '
'
-
- // IE9 and non-IE
- sel = window.getSelection();
- if (sel.getRangeAt && sel.rangeCount) {
- range = sel.getRangeAt(0);
- range.deleteContents();
-
- // Range.createContextualFragment() would be useful here but is
- // only relatively recently standardized and is not supported in
- // some browsers (IE9, for one)
- var el = document.createElement("div");
- el.innerHTML = marker;
- var frag = document.createDocumentFragment(), node, lastNode;
- while ( (node = el.firstChild) ) {
- lastNode = frag.appendChild(node);
- }
- range.insertNode(frag);
-
- // Preserve the selection
- if (lastNode) {
- range = range.cloneRange();
- range.setStartAfter(lastNode);
- range.collapse(true);
- sel.removeAllRanges();
- sel.addRange(range);
- }
- }
- position = $('#js-cursor-position').position()
+ var range = this.getFirstRange();
+ var clone = range.cloneRange()
+ clone.pasteHtml(marker)
+ this._position = $('#js-cursor-position').position()
$('#js-cursor-position').remove()
- if (!position) return
+ if (!this._position) return
// set position of widget
- var height = this.$element.height()
- var widgetHeight = this.$widget.find('ul').height() //+ 60 // + height
- var top = -( widgetHeight + height ) + position.top
- this.$widget.css('top', top)
- this.$widget.css('left', position.left)
+ this.movePosition()
}
// open widget
Plugin.prototype.open = function() {
this.active = true
this.updatePosition()
-
b = $.proxy(function() {
this.$widget.addClass('open')
}, this)
@@ -231,9 +229,11 @@
// close widget
Plugin.prototype.close = function() {
- this.active = false
- this.cutInput()
this.$widget.removeClass('open')
+ if ( this.active ) {
+ this.cutInput(true)
+ }
+ this.active = false
}
// check if widget is active/open
@@ -255,11 +255,15 @@
// cut some content
Plugin.prototype.cut = function(string) {
+ var range = this.getFirstRange();
+ if (!range) return
+ /*
var sel = window.getSelection()
if ( !sel || sel.rangeCount < 1) {
return
}
var range = sel.getRangeAt(0)
+ */
var clone = range.cloneRange()
// improve error handling
@@ -267,9 +271,34 @@
if (start < 0) {
start = 0
}
+
+ // for chrome, remove also leading space, add it later - otherwice space will be tropped
+ if (start) {
+ clone.setStart(range.startContainer, start-1)
+ clone.setEnd(range.startContainer, start)
+ var spacerChar = clone.toString()
+ if ( spacerChar === ' ' ) {
+ start = start - 1
+ }
+ }
+ //console.log('CUT FOR', string, "-"+clone.toString()+"-", start, range.startOffset)
clone.setStart(range.startContainer, start)
clone.setEnd(range.startContainer, range.startOffset)
clone.deleteContents()
+
+ // for chrome, insert space again
+ if (start) {
+ if ( spacerChar === ' ' ) {
+ string = " "
+ if (document.selection) { // IE
+ var range = document.selection.createRange()
+ range.pasteHTML(string)
+ }
+ else {
+ document.execCommand('insertHTML', false, string)
+ }
+ }
+ }
}
// select text module and insert into text
@@ -291,15 +320,15 @@
return
}
+ Plugin.prototype.getFirstRange = function() {
+ var sel = rangy.getSelection();
+ return sel.rangeCount ? sel.getRangeAt(0) : null;
+ }
+
// cut out search string from text
Plugin.prototype.cutInput = function() {
if (!this.buffer) return
if (!this.$element.text()) return
- var sel = window.getSelection()
- if ( !sel || sel.rangeCount < 1) {
- this.buffer = ''
- return
- }
this.cut(this.buffer)
this.buffer = ''
}
@@ -322,9 +351,9 @@
console.log('result', term, result)
for (var i = 0; i < result.length; i++) {
var item = result[i]
- var template = "
" + item.name
+ var template = "" + App.Utils.htmlEscape(item.name)
if (item.keywords) {
- template = template + " (" + item.keywords + ")"
+ template = template + " (" + App.Utils.htmlEscape(item.keywords) + ")"
}
template = template + ""
this.$widget.find('ul').append(template)
@@ -340,10 +369,9 @@
_this.take(id)
}
)
- this.updatePosition()
+ this.movePosition()
}
-
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
diff --git a/app/assets/javascripts/app/lib/base/rangy-textrange.js b/app/assets/javascripts/app/lib/base/rangy-textrange.js
new file mode 100755
index 000000000..58d7528b5
--- /dev/null
+++ b/app/assets/javascripts/app/lib/base/rangy-textrange.js
@@ -0,0 +1,1925 @@
+/**
+ * Text range module for Rangy.
+ * Text-based manipulation and searching of ranges and selections.
+ *
+ * Features
+ *
+ * - Ability to move range boundaries by character or word offsets
+ * - Customizable word tokenizer
+ * - Ignores text nodes inside