Improved text module feature.

This commit is contained in:
Martin Edenhofer 2013-03-07 23:24:26 +01:00
parent 72778a2d78
commit a9e8bef64a
6 changed files with 543 additions and 3 deletions

View file

@ -31,7 +31,9 @@ class Index extends App.Controller
cache = App.Store.get( @key ) cache = App.Store.get( @key )
if cache if cache
@load(cache) @load(cache)
update = =>
@fetch(@ticket_id) @fetch(@ticket_id)
@interval( update, 30000, 'zoom_check' )
fetch: (ticket_id) -> fetch: (ticket_id) ->
@ -44,6 +46,17 @@ class Index extends App.Controller
view: @view view: @view
processData: true processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>
if _.isEqual( @dataLastCall, data)
return
if @dataLastCall && $('[name="body"]').val()
App.Event.trigger 'notify', {
type: 'success'
msg: App.i18n.translateContent('Ticket has changed!')
timeout: 30000
}
return
@dataLastCall = data
@load(data) @load(data)
App.Store.write( @key, data ) App.Store.write( @key, data )
) )

View file

@ -1,6 +1,35 @@
$ = jQuery.sub()
class App.TextModuleUI extends App.Controller class App.TextModuleUI extends App.Controller
constructor: ->
super
ui = @
values = []
all = App.Collection.all( type: 'TextModule' )
for item in all
if item.active is true
contentNew = item.content.replace( /<%=\s{0,2}(.+?)\s{0,2}%>/g, ( all, key ) ->
key = key.replace( /@/g, 'ui.data.' )
varString = "#{key}" + ''
try
key = eval (varString)
catch error
# console.log( "tag replacement: " + error )
key = ''
return key
)
value = { val: contentNew, keywords: item.keywords || item.name }
values.push value
customItemTemplate = "<div><span />&nbsp;<small /></div>"
elementFactory = (element, e) ->
template = $(customItemTemplate).find('span')
.text(e.val).end()
.find('small')
.text("(" + e.keywords + ")").end()
element.append(template)
$('textarea').sew({values: values, token: ':', elementFactory: elementFactory })
class App.TextModuleUIOld extends App.Controller
events: events:
'click [data-type=save]': 'create', 'click [data-type=save]': 'create',
'click [data-type=text_module_delete]': 'delete', 'click [data-type=text_module_delete]': 'delete',

View file

@ -0,0 +1,185 @@
/**
* jQuery plugin for getting position of cursor in textarea
* @license under GNU license
* @author Bevis Zhao (i@bevis.me, http://bevis.me)
*/
$(function() {
var calculator = {
// key styles
primaryStyles: ['fontFamily', 'fontSize', 'fontWeight', 'fontVariant', 'fontStyle',
'paddingLeft', 'paddingTop', 'paddingBottom', 'paddingRight',
'marginLeft', 'marginTop', 'marginBottom', 'marginRight',
'borderLeftColor', 'borderTopColor', 'borderBottomColor', 'borderRightColor',
'borderLeftStyle', 'borderTopStyle', 'borderBottomStyle', 'borderRightStyle',
'borderLeftWidth', 'borderTopWidth', 'borderBottomWidth', 'borderRightWidth',
'line-height', 'outline'],
specificStyle: {
'word-wrap': 'break-word',
'overflow-x': 'hidden',
'overflow-y': 'auto'
},
simulator : $('<div id="textarea_simulator"/>').css({
position: 'absolute',
top: 0,
left: 0,
visibility: 'hidden'
}).appendTo(document.body),
toHtml : function(text) {
return text.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g, '<br>')
.split(' ').join('<span style="white-space:prev-wrap">&nbsp;</span>');
},
// calculate position
getCaretPosition: function() {
var cal = calculator, self = this, element = self[0], elementOffset = self.offset();
// IE has easy way to get caret offset position
if ($.browser.msie && $.browser.version <= 9) {
// must get focus first
element.focus();
var range = document.selection.createRange();
$('#hskeywords').val(element.scrollTop);
return {
left: range.boundingLeft - elementOffset.left,
top: parseInt(range.boundingTop) - elementOffset.top + element.scrollTop
+ document.documentElement.scrollTop + parseInt(self.getComputedStyle("fontSize"))
};
}
cal.simulator.empty();
// clone primary styles to imitate textarea
$.each(cal.primaryStyles, function(index, styleName) {
self.cloneStyle(cal.simulator, styleName);
});
// caculate width and height
cal.simulator.css($.extend({
'width': self.width(),
'height': self.height()
}, cal.specificStyle));
var value = (self.val() || self.text()), cursorPosition = self.getCursorPosition();
var beforeText = value.substring(0, cursorPosition),
afterText = value.substring(cursorPosition);
var before = $('<span class="before"/>').html(cal.toHtml(beforeText)),
focus = $('<span class="focus"/>'),
after = $('<span class="after"/>').html(cal.toHtml(afterText));
cal.simulator.append(before).append(focus).append(after);
var focusOffset = focus.offset(), simulatorOffset = cal.simulator.offset();
// alert(focusOffset.left + ',' + simulatorOffset.left + ',' + element.scrollLeft);
return {
top: focusOffset.top - simulatorOffset.top - element.scrollTop
// calculate and add the font height except Firefox
+ ($.browser.mozilla ? 0 : parseInt(self.getComputedStyle("fontSize"))),
left: focus[0].offsetLeft - cal.simulator[0].offsetLeft - element.scrollLeft
};
}
};
$.fn.extend({
setCursorPosition : function(position){
if(this.length == 0) return this;
return $(this).setSelection(position, position);
},
setSelection: function(selectionStart, selectionEnd) {
if(this.length == 0) return this;
input = this[0];
if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
} else if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
} else {
var el = this.get(0);
var range = document.createRange();
range.collapse(true);
range.setStart(el.childNodes[0], selectionStart);
range.setEnd(el.childNodes[0], selectionEnd);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
return this;
},
getComputedStyle: function(styleName) {
if (this.length == 0) return;
var thiz = this[0];
var result = this.css(styleName);
result = result || ($.browser.msie ?
thiz.currentStyle[styleName]:
document.defaultView.getComputedStyle(thiz, null)[styleName]);
return result;
},
// easy clone method
cloneStyle: function(target, styleName) {
var styleVal = this.getComputedStyle(styleName);
if (!!styleVal) {
$(target).css(styleName, styleVal);
}
},
cloneAllStyle: function(target, style) {
var thiz = this[0];
for (var styleName in thiz.style) {
var val = thiz.style[styleName];
typeof val == 'string' || typeof val == 'number'
? this.cloneStyle(target, styleName)
: NaN;
}
},
getCursorPosition : function() {
var element = input = this[0];
var value = (input.value || input.innerText)
if(!this.data("lastCursorPosition")){
this.data("lastCursorPosition",0);
}
var lastCursorPosition = this.data("lastCursorPosition");
if (document.selection) {
input.focus();
var sel = document.selection.createRange();
var selLen = document.selection.createRange().text.length;
sel.moveStart('character', -value.length);
lastCursorPosition = sel.text.length - selLen;
} else if (input.selectionStart || input.selectionStart == '0') {
return input.selectionStart;
} else if (typeof window.getSelection != "undefined" && window.getSelection().rangeCount>0) {
try{
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
lastCursorPosition = preCaretRange.toString().length;
}catch(e){
lastCursorPosition = this.data("lastCursorPosition");
}
} else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
var textRange = document.selection.createRange();
var preCaretTextRange = document.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
lastCursorPosition = preCaretTextRange.text.length;
}
this.data("lastCursorPosition",lastCursorPosition);
return lastCursorPosition;
},
getCaretPosition: calculator.getCaretPosition
});
});

View file

@ -0,0 +1,275 @@
/**
* jQuery plugin for getting position of cursor in textarea
* @license under dfyw (do the fuck you want)
* @author leChantaux (@leChantaux)
*/
(function ($, window, undefined) {
// Create the defaults once
var elementFactory = function (element, value) {
element.text(value.val);
};
var pluginName = 'sew',
document = window.document,
defaults = {
token: '@',
elementFactory: elementFactory,
values: [],
unique: false,
repeat: true
};
function Plugin(element, options) {
this.element = element;
this.$element = $(element);
this.$itemList = $(Plugin.MENU_TEMPLATE);
this.options = $.extend({}, defaults, options);
this.reset();
this._defaults = defaults;
this._name = pluginName;
// this.expression = new RegExp('(?:^|\\b|\\s)' + this.options.token + '([\\w.]*)$');
this.expression = new RegExp('' + this.options.token + '([\\w.]*)$');
this.cleanupHandle = null;
this.init();
}
Plugin.MENU_TEMPLATE = "<div class='-sew-list-container' style='display: none; position: absolute;'><ul class='-sew-list'></ul></div>";
Plugin.ITEM_TEMPLATE = '<li class="-sew-list-item"></li>';
Plugin.KEYS = [40, 38, 13, 27, 9];
Plugin.prototype.init = function () {
if(this.options.values.length < 1) return;
this.$element
.bind('keyup', $.proxy(this.onKeyUp, this))
.bind('keydown', $.proxy(this.onKeyDown, this))
.bind('focus', $.proxy(this.renderElements, this, this.options.values))
.bind('blur', $.proxy(this.remove, this));
};
Plugin.prototype.reset = function () {
if(this.options.unique) {
this.options.values = Plugin.getUniqueElements(this.options.values);
}
this.index = 0;
this.matched = false;
this.dontFilter = false;
this.lastFilter = undefined;
this.filtered = this.options.values.slice(0);
};
Plugin.prototype.next = function () {
this.index = (this.index + 1) % this.filtered.length;
this.hightlightItem();
};
Plugin.prototype.prev = function () {
this.index = (this.index + this.filtered.length - 1) % this.filtered.length;
this.hightlightItem();
};
Plugin.prototype.select = function () {
this.replace(this.filtered[this.index].val);
this.hideList();
};
Plugin.prototype.remove = function () {
this.$itemList.fadeOut('slow');
this.cleanupHandle = window.setTimeout($.proxy(function () {
this.$itemList.remove();
}, this), 1000);
};
Plugin.prototype.replace = function (replacement) {
var startpos = this.$element.getCursorPosition();
// var separator = startpos === 1 ? '' : ' ';
var separator = startpos === 1 ? '' : '';
var fullStuff = this.getText();
var val = fullStuff.substring(0, startpos);
// val = val.replace(this.expression, separator + this.options.token + replacement);
val = val.replace(this.expression, separator + replacement);
var posfix = fullStuff.substring(startpos, fullStuff.length);
var separator2 = posfix.match(/^\s/) ? '' : ' ';
var finalFight = val + separator2 + posfix;
console.log('222', finalFight)
this.setText(finalFight);
this.$element.setCursorPosition(val.length + 1);
};
Plugin.prototype.hightlightItem = function () {
this.$itemList.find(".-sew-list-item").removeClass("selected");
var container = this.$itemList.find(".-sew-list-item").parent();
var element = this.filtered[this.index].element.addClass("selected");
var scrollPosition = element.position().top;
container.scrollTop(container.scrollTop() + scrollPosition);
};
Plugin.prototype.renderElements = function (values) {
$("body").append(this.$itemList);
var container = this.$itemList.find('ul').empty();
values.forEach($.proxy(function (e, i) {
var $item = $(Plugin.ITEM_TEMPLATE);
this.options.elementFactory($item, e);
e.element = $item.appendTo(container).bind('click', $.proxy(this.onItemClick, this, e)).bind('mouseover', $.proxy(this.onItemHover, this, i));
}, this));
this.index = 0;
this.hightlightItem();
};
Plugin.prototype.displayList = function () {
if(!this.filtered.length) return;
this.$itemList.show();
var element = this.$element;
var offset = this.$element.offset();
var pos = element.getCaretPosition();
this.$itemList.css({
left: offset.left + pos.left,
top: offset.top + pos.top
});
};
Plugin.prototype.hideList = function () {
this.$itemList.hide();
this.reset();
};
Plugin.prototype.filterList = function (val) {
if(val == this.lastFilter) return;
this.lastFilter = val;
this.$itemList.find(".-sew-list-item").remove();
var values = this.options.values;
var vals = this.filtered = values.filter($.proxy(function (e) {
var exp = new RegExp('\\W*' + this.options.token + e.val + '(\\W|$)');
if(!this.options.repeat && this.getText().match(exp)) {
return false;
}
return val === "" ||
e.val.toLowerCase().indexOf(val.toLowerCase()) >= 0 ||
(e.meta || "").toLowerCase().indexOf(val.toLowerCase()) >= 0;
}, this));
if(vals.length) {
this.renderElements(vals);
this.$itemList.show();
} else {
this.hideList();
}
};
Plugin.getUniqueElements = function (elements) {
var target = [];
elements.forEach(function (e) {
var hasElement = target.map(function (j) { return j.val; }).indexOf(e.val) >= 0;
if(hasElement) return;
target.push(e);
});
return target;
};
Plugin.prototype.getText = function () {
return(this.$element.val() || this.$element.text());
};
Plugin.prototype.setText = function (text) {
if(this.$element.prop('tagName').match(/input|textarea/i)) {
this.$element.val(text);
} else {
// poors man sanitization
text = $("<span>").text(text).html().replace(/\s/g, '&nbsp');
this.$element.html(text);
}
};
Plugin.prototype.onKeyUp = function (e) {
var startpos = this.$element.getCursorPosition();
var val = this.getText().substring(0, startpos);
var matches = val.match(this.expression);
if(!matches && this.matched) {
this.matched = false;
this.dontFilter = false;
this.hideList();
return;
}
if(matches && !this.matched) {
this.displayList();
this.lastFilter = "\n";
this.matched = true;
}
if(matches && !this.dontFilter) {
this.filterList(matches[1]);
}
};
Plugin.prototype.onKeyDown = function (e) {
var listVisible = this.$itemList.is(":visible");
if(!listVisible || (Plugin.KEYS.indexOf(e.keyCode) < 0)) return;
switch(e.keyCode) {
case 9:
case 13:
this.select();
break;
case 40:
this.next();
break;
case 38:
this.prev();
break;
case 27:
this.$itemList.hide();
this.dontFilter = true;
break;
}
e.preventDefault();
};
Plugin.prototype.onItemClick = function (element, e) {
if(this.cleanupHandle) window.clearTimeout(this.cleanupHandle);
this.replace(element.val);
this.hideList();
};
Plugin.prototype.onItemHover = function (index, e) {
this.index = index;
this.hightlightItem();
};
$.fn[pluginName] = function (options) {
return this.each(function () {
if(!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
}
});
};
}(jQuery, window));

View file

@ -9,6 +9,7 @@
*= require ./jquery.noty.css *= require ./jquery.noty.css
*= require ./jquery.tagsinput.css *= require ./jquery.tagsinput.css
*= require ./noty_theme_twitter.css *= require ./noty_theme_twitter.css
*= require ./sew.css
*= require ./zzz.css *= require ./zzz.css
* *
*= require_tree ./custom/ *= require_tree ./custom/

View file

@ -0,0 +1,37 @@
.-sew-list-container {
background: white;
border: 1px solid #DDD;
border-radius: 3px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
min-width: 180px;
}
.-sew-list {
list-style: none;
margin: 0;
padding: 0;
max-height: 100px;
overflow: scroll;
}
.-sew-list-item {
display: block;
padding: 5px 10px;
border-bottom: 1px solid #DDD;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 400px;
}
.-sew-list-item small {
color: #afafaf;
}
.-sew-list-item.selected {
color: white;
background: #4183C4;
text-decoration: none;
}