diff --git a/browser/css/vidjil.less b/browser/css/vidjil.less index 012551205f5c2fd2f18324f3b2290740bf92a203..1d1c026f7efad4af128a08dfadaf498f063d44af 100644 --- a/browser/css/vidjil.less +++ b/browser/css/vidjil.less @@ -2118,4 +2118,79 @@ label.highlight_label { .widestBox { min-width: @width_axisBox; -} \ No newline at end of file +} + +/* atwho css */ + +.atwho-view { + position:absolute; + top: 0; + left: 0; + display: none; + margin-top: 18px; + background: white; + color: black; + border: 1px solid #DDD; + border-radius: 3px; + box-shadow: 0 0 5px rgba(0,0,0,0.1); + min-width: 120px; + z-index: 11110 !important; +} + +.atwho-view .atwho-header { + padding: 5px; + margin: 5px; + cursor: pointer; + border-bottom: solid 1px #eaeff1; + color: #6f8092; + font-size: 11px; + font-weight: bold; +} + +.atwho-view .atwho-header .small { + color: #6f8092; + float: right; + padding-top: 2px; + margin-right: -5px; + font-size: 12px; + font-weight: normal; +} + +.atwho-view .atwho-header:hover { + cursor: default; +} + +.atwho-view .cur { + background: #3366FF; + color: white; +} +.atwho-view .cur small { + color: white; +} +.atwho-view strong { + color: #3366FF; +} +.atwho-view .cur strong { + color: white; + font:bold; +} +.atwho-view ul { + /* width: 100px; */ + list-style:none; + padding:0; + margin:auto; + max-height: 200px; + overflow-y: auto; +} +.atwho-view ul li { + display: block; + padding: 5px 10px; + border-bottom: 1px solid #DDD; + cursor: pointer; + /* border-top: 1px solid #C8C8C8; */ +} +.atwho-view small { + font-size: smaller; + color: #777; + font-weight: normal; +} diff --git a/browser/js/app.js b/browser/js/app.js index 67b7a7095b4cb3951a64d46df5f25f43f9fc6d80..a51091a65383a7f7f409ac4b6fb61a6297bedb11 100644 --- a/browser/js/app.js +++ b/browser/js/app.js @@ -13,7 +13,8 @@ require(["jquery", "file", "tsne", "jstree.min", - "jquery.autocomplete"], function() { + "jquery.caret", + "jquery.atwho"], function() { // Then config file (needed by Vidjil) require(['../conf'], function() { loadAfterConf() diff --git a/browser/js/autocomplete.js b/browser/js/autocomplete.js index 323f8316231d6d1ecbf25d8a38272ac6c613d40e..a9d8df7dbe4b97a6eb048236cf718984fc877863 100644 --- a/browser/js/autocomplete.js +++ b/browser/js/autocomplete.js @@ -20,13 +20,147 @@ * along with "Vidjil". If not, see */ +// This code is heavily inspired by the code produced for Gitlab's GfmAutoComplete + +function VidjilAutoComplete(datasource) { + if (typeof VidjilAutoComplete.instance === 'object') { + return VidjilAutoComplete.instance; + } + + this.datasource = datasource; + this.isLoadingData = {}; + this.cachedData = {}; + this.loadedGroup = -1; + + VidjilAutoComplete.instance = this; + + return this; +} + +// static methods + +VidjilAutoComplete.defaultLoadingData = ['loading']; + +VidjilAutoComplete.isLoading = function(data) { + dataToInspect = data; + if (data && data.length > 0) { + dataToInspect = data[0]; + } + + var loadingState = VidjilAutoComplete.defaultLoadingData[0]; + return dataToInspect && (dataToInspect === loadingState || dataToInspect.name === loadingState); +} + +// instance methods +VidjilAutoComplete.prototype = { + + initCache : function(at) { + this.cachedData[at] = {}; + }, + + clearCache : function() { + this.cachedData = {}; + }, + + isLoaded : function(group_id) { + return this.loadedGroup == group_id; + }, + + setupAtWho: function(input) { + const $input = $(input); + if ($input.data('needs-atwho')) { + $input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupTags.bind(this, $input)); + $input.on('change.atwho', () => input.dispatchEvent(new Event('input'))); + // This triggers at.js again + // Needed for quick actions with suffixes (ex: /label ~) + $input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup')); + $input.on('clear-commands-cache.atwho', () => this.clearCache()); + $input.data('needs-atwho', false); + + // hackiness to get the autocompletion ready right away + $input.trigger('focus.setupAtWho'); + } + }, + + setupTags : function($input) { + console.log("setup"); + var self = this; + var at = '#'; + this.initCache(at); + $input.atwho({ + at: at, + alias: 'tags', + data: VidjilAutoComplete.defaultLoadingData, + callbacks: { + ...self.getDefaultCallbacks() + }, + searchKey: 'search', + }); + }, + + getDefaultCallbacks : function() { + const fetchData = this.fetchData.bind(this); + const isLoaded = this.isLoaded.bind(this); + var callbacks = { + filter : function(query, data, searchKey) { + var group_id = this.$inputor.data('group-id'); + if (VidjilAutoComplete.isLoading(data) || !isLoaded(group_id)) { + this.$inputor.atwho('load', this.at, VidjilAutoComplete.defaultLoadingData); + fetchData(this.$inputor, this.at, group_id); + return data; + } + return $.fn.atwho.default.callbacks.filter(query, data, searchKey); + }, + beforeSave(tags) { + return $.map(tags, (i) => { + if (i.name == null) { + return i; + } + return { + id: i.id, + name: i.name, + search: `${i.id} ${i.name}`, + }; + }); + }, + } + return callbacks; + }, + + fetchData : function($input, at, group_id) { + var self = this; + if (this.isLoadingData[at]) return; + this.isLoadingData[at] = true; + if (this.cachedData[at][group_id]) { + this.loadData($input, at, this.cachedData[at][group_id], group_id); + } else { + $.ajax({ + type: "GET", + data: { + group_id: group_id + }, + timeout: 5000, + crossDomain: true, + url: self.datasource, + success: function (data) { + var my_data = JSON.parse(data); + self.loadData($input, at, my_data, group_id); + }, + error: function (request, status, error) { + self.isLoadingData[at] = false; + } + }); + } + }, + + loadData : function($input, at, data, group_id) { + this.isLoadingData[at] = false; + this.cachedData[at][group_id] = data; + this.loadedGroup = group_id; + $input.atwho('load', at, data); + // This trigger at.js again + // otherwise we would be stuck with loading until the user types + return $input.trigger('keyup'); + }, -function init_autocomplete(elem, group_id) { - var service = 'tag/auto_complete'; - var address = db.db_address + service; - $(elem).autocomplete({'serviceUrl': address, - 'dataType': 'json', - 'params': {'group_id': group_id}, - 'delimiter': /[,\.-_=+()$%^&*!@\[\]\{\}\"|'?\\\/><\s]/ - }); } diff --git a/browser/js/lib/jquery.atwho.js b/browser/js/lib/jquery.atwho.js new file mode 100644 index 0000000000000000000000000000000000000000..0d295ebe5af5fa2fa9d8dafed9cb94cfea52b866 --- /dev/null +++ b/browser/js/lib/jquery.atwho.js @@ -0,0 +1,1202 @@ +/** + * at.js - 1.5.1 + * Copyright (c) 2016 chord.luo ; + * Homepage: http://ichord.github.com/At.js + * License: MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define(["jquery"], function (a0) { + return (factory(a0)); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(require("jquery")); + } else { + factory(jQuery); + } +}(this, function ($) { +var DEFAULT_CALLBACKS, KEY_CODE; + +KEY_CODE = { + DOWN: 40, + UP: 38, + ESC: 27, + TAB: 9, + ENTER: 13, + CTRL: 17, + A: 65, + P: 80, + N: 78, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + BACKSPACE: 8, + SPACE: 32 +}; + +DEFAULT_CALLBACKS = { + beforeSave: function(data) { + return Controller.arrayToDefaultHash(data); + }, + matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) { + var _a, _y, match, regexp, space; + flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + if (should_startWithSpace) { + flag = '(?:^|\\s)' + flag; + } + _a = decodeURI("%C3%80"); + _y = decodeURI("%C3%BF"); + space = acceptSpaceBar ? "\ " : ""; + regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi'); + match = regexp.exec(subtext); + if (match) { + return match[2] || match[1]; + } else { + return null; + } + }, + filter: function(query, data, searchKey) { + var _results, i, item, len; + _results = []; + for (i = 0, len = data.length; i < len; i++) { + item = data[i]; + if (~new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase())) { + _results.push(item); + } + } + return _results; + }, + remoteFilter: null, + sorter: function(query, items, searchKey) { + var _results, i, item, len; + if (!query) { + return items; + } + _results = []; + for (i = 0, len = items.length; i < len; i++) { + item = items[i]; + item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase()); + if (item.atwho_order > -1) { + _results.push(item); + } + } + return _results.sort(function(a, b) { + return a.atwho_order - b.atwho_order; + }); + }, + tplEval: function(tpl, map) { + var error, error1, template; + template = tpl; + try { + if (typeof tpl !== 'string') { + template = tpl(map); + } + return template.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) { + return map[key]; + }); + } catch (error1) { + error = error1; + return ""; + } + }, + highlighter: function(li, query) { + var regexp; + if (!query) { + return li; + } + regexp = new RegExp(">\\s*(\\w*?)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig'); + return li.replace(regexp, function(str, $1, $2, $3) { + return '> ' + $1 + '' + $2 + '' + $3 + ' <'; + }); + }, + beforeInsert: function(value, $li, e) { + return value; + }, + beforeReposition: function(offset) { + return offset; + }, + afterMatchFailed: function(at, el) {} +}; + +var App; + +App = (function() { + function App(inputor) { + this.currentFlag = null; + this.controllers = {}; + this.aliasMaps = {}; + this.$inputor = $(inputor); + this.setupRootElement(); + this.listen(); + } + + App.prototype.createContainer = function(doc) { + var ref; + if ((ref = this.$el) != null) { + ref.remove(); + } + return $(doc.body).append(this.$el = $("
")); + }; + + App.prototype.setupRootElement = function(iframe, asRoot) { + var error, error1; + if (asRoot == null) { + asRoot = false; + } + if (iframe) { + this.window = iframe.contentWindow; + this.document = iframe.contentDocument || this.window.document; + this.iframe = iframe; + } else { + this.document = this.$inputor[0].ownerDocument; + this.window = this.document.defaultView || this.document.parentWindow; + try { + this.iframe = this.window.frameElement; + } catch (error1) { + error = error1; + this.iframe = null; + if ($.fn.atwho.debug) { + throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error); + } + } + } + return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document); + }; + + App.prototype.controller = function(at) { + var c, current, currentFlag, ref; + if (this.aliasMaps[at]) { + current = this.controllers[this.aliasMaps[at]]; + } else { + ref = this.controllers; + for (currentFlag in ref) { + c = ref[currentFlag]; + if (currentFlag === at) { + current = c; + break; + } + } + } + if (current) { + return current; + } else { + return this.controllers[this.currentFlag]; + } + }; + + App.prototype.setContextFor = function(at) { + this.currentFlag = at; + return this; + }; + + App.prototype.reg = function(flag, setting) { + var base, controller; + controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag)); + if (setting.alias) { + this.aliasMaps[setting.alias] = flag; + } + controller.init(setting); + return this; + }; + + App.prototype.listen = function() { + return this.$inputor.on('compositionstart', (function(_this) { + return function(e) { + var ref; + if ((ref = _this.controller()) != null) { + ref.view.hide(); + } + _this.isComposing = true; + return null; + }; + })(this)).on('compositionend', (function(_this) { + return function(e) { + _this.isComposing = false; + setTimeout(function(e) { + return _this.dispatch(e); + }); + return null; + }; + })(this)).on('keyup.atwhoInner', (function(_this) { + return function(e) { + return _this.onKeyup(e); + }; + })(this)).on('keydown.atwhoInner', (function(_this) { + return function(e) { + return _this.onKeydown(e); + }; + })(this)).on('blur.atwhoInner', (function(_this) { + return function(e) { + var c; + if (c = _this.controller()) { + c.expectedQueryCBId = null; + return c.view.hide(e, c.getOpt("displayTimeout")); + } + }; + })(this)).on('click.atwhoInner', (function(_this) { + return function(e) { + return _this.dispatch(e); + }; + })(this)).on('scroll.atwhoInner', (function(_this) { + return function() { + var lastScrollTop; + lastScrollTop = _this.$inputor.scrollTop(); + return function(e) { + var currentScrollTop, ref; + currentScrollTop = e.target.scrollTop; + if (lastScrollTop !== currentScrollTop) { + if ((ref = _this.controller()) != null) { + ref.view.hide(e); + } + } + lastScrollTop = currentScrollTop; + return true; + }; + }; + })(this)()); + }; + + App.prototype.shutdown = function() { + var _, c, ref; + ref = this.controllers; + for (_ in ref) { + c = ref[_]; + c.destroy(); + delete this.controllers[_]; + } + this.$inputor.off('.atwhoInner'); + return this.$el.remove(); + }; + + App.prototype.dispatch = function(e) { + var _, c, ref, results; + ref = this.controllers; + results = []; + for (_ in ref) { + c = ref[_]; + results.push(c.lookUp(e)); + } + return results; + }; + + App.prototype.onKeyup = function(e) { + var ref; + switch (e.keyCode) { + case KEY_CODE.ESC: + e.preventDefault(); + if ((ref = this.controller()) != null) { + ref.view.hide(); + } + break; + case KEY_CODE.DOWN: + case KEY_CODE.UP: + case KEY_CODE.CTRL: + case KEY_CODE.ENTER: + $.noop(); + break; + case KEY_CODE.P: + case KEY_CODE.N: + if (!e.ctrlKey) { + this.dispatch(e); + } + break; + default: + this.dispatch(e); + } + }; + + App.prototype.onKeydown = function(e) { + var ref, view; + view = (ref = this.controller()) != null ? ref.view : void 0; + if (!(view && view.visible())) { + return; + } + switch (e.keyCode) { + case KEY_CODE.ESC: + e.preventDefault(); + view.hide(e); + break; + case KEY_CODE.UP: + e.preventDefault(); + view.prev(); + break; + case KEY_CODE.DOWN: + e.preventDefault(); + view.next(); + break; + case KEY_CODE.P: + if (!e.ctrlKey) { + return; + } + e.preventDefault(); + view.prev(); + break; + case KEY_CODE.N: + if (!e.ctrlKey) { + return; + } + e.preventDefault(); + view.next(); + break; + case KEY_CODE.TAB: + case KEY_CODE.ENTER: + case KEY_CODE.SPACE: + if (!view.visible()) { + return; + } + if (!this.controller().getOpt('spaceSelectsMatch') && e.keyCode === KEY_CODE.SPACE) { + return; + } + if (!this.controller().getOpt('tabSelectsMatch') && e.keyCode === KEY_CODE.TAB) { + return; + } + if (view.highlighted()) { + e.preventDefault(); + view.choose(e); + } else { + view.hide(e); + } + break; + default: + $.noop(); + } + }; + + return App; + +})(); + +var Controller, + slice = [].slice; + +Controller = (function() { + Controller.prototype.uid = function() { + return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime()); + }; + + function Controller(app, at1) { + this.app = app; + this.at = at1; + this.$inputor = this.app.$inputor; + this.id = this.$inputor[0].id || this.uid(); + this.expectedQueryCBId = null; + this.setting = null; + this.query = null; + this.pos = 0; + this.range = null; + if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) { + this.app.$el.append(this.$el = $("
")); + } + this.model = new Model(this); + this.view = new View(this); + } + + Controller.prototype.init = function(setting) { + this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting); + this.view.init(); + return this.model.reload(this.setting.data); + }; + + Controller.prototype.destroy = function() { + this.trigger('beforeDestroy'); + this.model.destroy(); + this.view.destroy(); + return this.$el.remove(); + }; + + Controller.prototype.callDefault = function() { + var args, error, error1, funcName; + funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + try { + return DEFAULT_CALLBACKS[funcName].apply(this, args); + } catch (error1) { + error = error1; + return $.error(error + " Or maybe At.js doesn't have function " + funcName); + } + }; + + Controller.prototype.trigger = function(name, data) { + var alias, eventName; + if (data == null) { + data = []; + } + data.push(this); + alias = this.getOpt('alias'); + eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho"; + return this.$inputor.trigger(eventName, data); + }; + + Controller.prototype.callbacks = function(funcName) { + return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName]; + }; + + Controller.prototype.getOpt = function(at, default_value) { + var e, error1; + try { + return this.setting[at]; + } catch (error1) { + e = error1; + return null; + } + }; + + Controller.prototype.insertContentFor = function($li) { + var data, tpl; + tpl = this.getOpt('insertTpl'); + data = $.extend({}, $li.data('item-data'), { + 'atwho-at': this.at + }); + return this.callbacks("tplEval").call(this, tpl, data, "onInsert"); + }; + + Controller.prototype.renderView = function(data) { + var searchKey; + searchKey = this.getOpt("searchKey"); + data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), searchKey); + return this.view.render(data.slice(0, this.getOpt('limit'))); + }; + + Controller.arrayToDefaultHash = function(data) { + var i, item, len, results; + if (!$.isArray(data)) { + return data; + } + results = []; + for (i = 0, len = data.length; i < len; i++) { + item = data[i]; + if ($.isPlainObject(item)) { + results.push(item); + } else { + results.push({ + name: item + }); + } + } + return results; + }; + + Controller.prototype.lookUp = function(e) { + var query, wait; + if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) { + return; + } + if (this.getOpt('suspendOnComposing') && this.app.isComposing) { + return; + } + query = this.catchQuery(e); + if (!query) { + this.expectedQueryCBId = null; + return query; + } + this.app.setContextFor(this.at); + if (wait = this.getOpt('delay')) { + this._delayLookUp(query, wait); + } else { + this._lookUp(query); + } + return query; + }; + + Controller.prototype._delayLookUp = function(query, wait) { + var now, remaining; + now = Date.now ? Date.now() : new Date().getTime(); + this.previousCallTime || (this.previousCallTime = now); + remaining = wait - (now - this.previousCallTime); + if ((0 < remaining && remaining < wait)) { + this.previousCallTime = now; + this._stopDelayedCall(); + return this.delayedCallTimeout = setTimeout((function(_this) { + return function() { + _this.previousCallTime = 0; + _this.delayedCallTimeout = null; + return _this._lookUp(query); + }; + })(this), wait); + } else { + this._stopDelayedCall(); + if (this.previousCallTime !== now) { + this.previousCallTime = 0; + } + return this._lookUp(query); + } + }; + + Controller.prototype._stopDelayedCall = function() { + if (this.delayedCallTimeout) { + clearTimeout(this.delayedCallTimeout); + return this.delayedCallTimeout = null; + } + }; + + Controller.prototype._generateQueryCBId = function() { + return {}; + }; + + Controller.prototype._lookUp = function(query) { + var _callback; + _callback = function(queryCBId, data) { + if (queryCBId !== this.expectedQueryCBId) { + return; + } + if (data && data.length > 0) { + return this.renderView(this.constructor.arrayToDefaultHash(data)); + } else { + return this.view.hide(); + } + }; + this.expectedQueryCBId = this._generateQueryCBId(); + return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId)); + }; + + return Controller; + +})(); + +var TextareaController, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +TextareaController = (function(superClass) { + extend(TextareaController, superClass); + + function TextareaController() { + return TextareaController.__super__.constructor.apply(this, arguments); + } + + TextareaController.prototype.catchQuery = function() { + var caretPos, content, end, isString, query, start, subtext; + content = this.$inputor.val(); + caretPos = this.$inputor.caret('pos', { + iframe: this.app.iframe + }); + subtext = content.slice(0, caretPos); + query = this.callbacks("matcher").call(this, this.at, subtext, this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar")); + isString = typeof query === 'string'; + if (isString && query.length < this.getOpt('minLen', 0)) { + return; + } + if (isString && query.length <= this.getOpt('maxLen', 20)) { + start = caretPos - query.length; + end = start + query.length; + this.pos = start; + query = { + 'text': query, + 'headPos': start, + 'endPos': end + }; + this.trigger("matched", [this.at, query.text]); + } else { + query = null; + this.view.hide(); + } + return this.query = query; + }; + + TextareaController.prototype.rect = function() { + var c, iframeOffset, scaleBottom; + if (!(c = this.$inputor.caret('offset', this.pos - 1, { + iframe: this.app.iframe + }))) { + return; + } + if (this.app.iframe && !this.app.iframeAsRoot) { + iframeOffset = $(this.app.iframe).offset(); + c.left += iframeOffset.left; + c.top += iframeOffset.top; + } + scaleBottom = this.app.document.selection ? 0 : 2; + return { + left: c.left, + top: c.top, + bottom: c.top + c.height + scaleBottom + }; + }; + + TextareaController.prototype.insert = function(content, $li) { + var $inputor, source, startStr, suffix, text; + $inputor = this.$inputor; + source = $inputor.val(); + startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0)); + suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " "; + content += suffix; + text = "" + startStr + content + (source.slice(this.query['endPos'] || 0)); + $inputor.val(text); + $inputor.caret('pos', startStr.length + content.length, { + iframe: this.app.iframe + }); + if (!$inputor.is(':focus')) { + $inputor.focus(); + } + return $inputor.change(); + }; + + return TextareaController; + +})(Controller); + +var EditableController, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +EditableController = (function(superClass) { + extend(EditableController, superClass); + + function EditableController() { + return EditableController.__super__.constructor.apply(this, arguments); + } + + EditableController.prototype._getRange = function() { + var sel; + sel = this.app.window.getSelection(); + if (sel.rangeCount > 0) { + return sel.getRangeAt(0); + } + }; + + EditableController.prototype._setRange = function(position, node, range) { + if (range == null) { + range = this._getRange(); + } + if (!range) { + return; + } + node = $(node)[0]; + if (position === 'after') { + range.setEndAfter(node); + range.setStartAfter(node); + } else { + range.setEndBefore(node); + range.setStartBefore(node); + } + range.collapse(false); + return this._clearRange(range); + }; + + EditableController.prototype._clearRange = function(range) { + var sel; + if (range == null) { + range = this._getRange(); + } + sel = this.app.window.getSelection(); + if (this.ctrl_a_pressed == null) { + sel.removeAllRanges(); + return sel.addRange(range); + } + }; + + EditableController.prototype._movingEvent = function(e) { + var ref; + return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN); + }; + + EditableController.prototype._unwrap = function(node) { + var next; + node = $(node).unwrap().get(0); + if ((next = node.nextSibling) && next.nodeValue) { + node.nodeValue += next.nodeValue; + $(next).remove(); + } + return node; + }; + + EditableController.prototype.catchQuery = function(e) { + var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range; + if (!(range = this._getRange())) { + return; + } + if (!range.collapsed) { + return; + } + if (e.which === KEY_CODE.ENTER) { + ($query = $(range.startContainer).closest('.atwho-query')).contents().unwrap(); + if ($query.is(':empty')) { + $query.remove(); + } + ($query = $(".atwho-query", this.app.document)).text($query.text()).contents().last().unwrap(); + this._clearRange(); + return; + } + if (/firefox/i.test(navigator.userAgent)) { + if ($(range.startContainer).is(this.$inputor)) { + this._clearRange(); + return; + } + if (e.which === KEY_CODE.BACKSPACE && range.startContainer.nodeType === document.ELEMENT_NODE && (offset = range.startOffset - 1) >= 0) { + _range = range.cloneRange(); + _range.setStart(range.startContainer, offset); + if ($(_range.cloneContents()).contents().last().is('.atwho-inserted')) { + inserted = $(range.startContainer).contents().get(offset); + this._setRange('after', $(inserted).contents().last()); + } + } else if (e.which === KEY_CODE.LEFT && range.startContainer.nodeType === document.TEXT_NODE) { + $inserted = $(range.startContainer.previousSibling); + if ($inserted.is('.atwho-inserted') && range.startOffset === 0) { + this._setRange('after', $inserted.contents().last()); + } + } + } + $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query'); + if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) { + $query.remove(); + } + if (!this._movingEvent(e)) { + $query.removeClass('atwho-inserted'); + } + if ($query.length > 0) { + switch (e.which) { + case KEY_CODE.LEFT: + this._setRange('before', $query.get(0), range); + $query.removeClass('atwho-query'); + return; + case KEY_CODE.RIGHT: + this._setRange('after', $query.get(0).nextSibling, range); + $query.removeClass('atwho-query'); + return; + } + } + if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) { + $query.empty().html(query_content).attr('data-atwho-at-query', null); + this._setRange('after', $query.get(0), range); + } + _range = range.cloneRange(); + _range.setStart(range.startContainer, 0); + matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar")); + isString = typeof matched === 'string'; + if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) { + range.setStart(range.startContainer, index); + $query = $('', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query'); + range.surroundContents($query.get(0)); + lastNode = $query.contents().last().get(0); + if (/firefox/i.test(navigator.userAgent)) { + range.setStart(lastNode, lastNode.length); + range.setEnd(lastNode, lastNode.length); + this._clearRange(range); + } else { + this._setRange('after', lastNode, range); + } + } + if (isString && matched.length < this.getOpt('minLen', 0)) { + return; + } + if (isString && matched.length <= this.getOpt('maxLen', 20)) { + query = { + text: matched, + el: $query + }; + this.trigger("matched", [this.at, query.text]); + return this.query = query; + } else { + this.view.hide(); + this.query = { + el: $query + }; + if ($query.text().indexOf(this.at) >= 0) { + if (this._movingEvent(e) && $query.hasClass('atwho-inserted')) { + $query.removeClass('atwho-query'); + } else if (false !== this.callbacks('afterMatchFailed').call(this, this.at, $query)) { + this._setRange("after", this._unwrap($query.text($query.text()).contents().first())); + } + } + return null; + } + }; + + EditableController.prototype.rect = function() { + var $iframe, iframeOffset, rect; + rect = this.query.el.offset(); + if (this.app.iframe && !this.app.iframeAsRoot) { + iframeOffset = ($iframe = $(this.app.iframe)).offset(); + rect.left += iframeOffset.left - this.$inputor.scrollLeft(); + rect.top += iframeOffset.top - this.$inputor.scrollTop(); + } + rect.bottom = rect.top + this.query.el.height(); + return rect; + }; + + EditableController.prototype.insert = function(content, $li) { + var data, range, suffix, suffixNode; + if (!this.$inputor.is(':focus')) { + this.$inputor.focus(); + } + suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || "\u00A0"; + data = $li.data('item-data'); + this.query.el.removeClass('atwho-query').addClass('atwho-inserted').html(content).attr('data-atwho-at-query', "" + data['atwho-at'] + this.query.text); + if (range = this._getRange()) { + range.setEndAfter(this.query.el[0]); + range.collapse(false); + range.insertNode(suffixNode = this.app.document.createTextNode("\u200D" + suffix)); + this._setRange('after', suffixNode, range); + } + if (!this.$inputor.is(':focus')) { + this.$inputor.focus(); + } + return this.$inputor.change(); + }; + + return EditableController; + +})(Controller); + +var Model; + +Model = (function() { + function Model(context) { + this.context = context; + this.at = this.context.at; + this.storage = this.context.$inputor; + } + + Model.prototype.destroy = function() { + return this.storage.data(this.at, null); + }; + + Model.prototype.saved = function() { + return this.fetch() > 0; + }; + + Model.prototype.query = function(query, callback) { + var _remoteFilter, data, searchKey; + data = this.fetch(); + searchKey = this.context.getOpt("searchKey"); + data = this.context.callbacks('filter').call(this.context, query, data, searchKey) || []; + _remoteFilter = this.context.callbacks('remoteFilter'); + if (data.length > 0 || (!_remoteFilter && data.length === 0)) { + return callback(data); + } else { + return _remoteFilter.call(this.context, query, callback); + } + }; + + Model.prototype.fetch = function() { + return this.storage.data(this.at) || []; + }; + + Model.prototype.save = function(data) { + return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || [])); + }; + + Model.prototype.load = function(data) { + if (!(this.saved() || !data)) { + return this._load(data); + } + }; + + Model.prototype.reload = function(data) { + return this._load(data); + }; + + Model.prototype._load = function(data) { + if (typeof data === "string") { + return $.ajax(data, { + dataType: "json" + }).done((function(_this) { + return function(data) { + return _this.save(data); + }; + })(this)); + } else { + return this.save(data); + } + }; + + return Model; + +})(); + +var View; + +View = (function() { + function View(context) { + this.context = context; + this.$el = $("
    "); + this.$elUl = this.$el.children(); + this.timeoutID = null; + this.context.$el.append(this.$el); + this.bindEvent(); + } + + View.prototype.init = function() { + var header_tpl, id; + id = this.context.getOpt("alias") || this.context.at.charCodeAt(0); + header_tpl = this.context.getOpt("headerTpl"); + if (header_tpl && this.$el.children().length === 1) { + this.$el.prepend(header_tpl); + } + return this.$el.attr({ + 'id': "at-view-" + id + }); + }; + + View.prototype.destroy = function() { + return this.$el.remove(); + }; + + View.prototype.bindEvent = function() { + var $menu, lastCoordX, lastCoordY; + $menu = this.$el.find('ul'); + lastCoordX = 0; + lastCoordY = 0; + return $menu.on('mousemove.atwho-view', 'li', (function(_this) { + return function(e) { + var $cur; + if (lastCoordX === e.clientX && lastCoordY === e.clientY) { + return; + } + lastCoordX = e.clientX; + lastCoordY = e.clientY; + $cur = $(e.currentTarget); + if ($cur.hasClass('cur')) { + return; + } + $menu.find('.cur').removeClass('cur'); + return $cur.addClass('cur'); + }; + })(this)).on('click.atwho-view', 'li', (function(_this) { + return function(e) { + $menu.find('.cur').removeClass('cur'); + $(e.currentTarget).addClass('cur'); + _this.choose(e); + return e.preventDefault(); + }; + })(this)); + }; + + View.prototype.visible = function() { + return this.$el.is(":visible"); + }; + + View.prototype.highlighted = function() { + return this.$el.find(".cur").length > 0; + }; + + View.prototype.choose = function(e) { + var $li, content; + if (($li = this.$el.find(".cur")).length) { + content = this.context.insertContentFor($li); + this.context._stopDelayedCall(); + this.context.insert(this.context.callbacks("beforeInsert").call(this.context, content, $li, e), $li); + this.context.trigger("inserted", [$li, e]); + this.hide(e); + } + if (this.context.getOpt("hideWithoutSuffix")) { + return this.stopShowing = true; + } + }; + + View.prototype.reposition = function(rect) { + var _window, offset, overflowOffset, ref; + _window = this.context.app.iframeAsRoot ? this.context.app.window : window; + if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) { + rect.bottom = rect.top - this.$el.height(); + } + if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) { + rect.left = overflowOffset; + } + offset = { + left: rect.left, + top: rect.bottom + }; + if ((ref = this.context.callbacks("beforeReposition")) != null) { + ref.call(this.context, offset); + } + this.$el.offset(offset); + return this.context.trigger("reposition", [offset]); + }; + + View.prototype.next = function() { + var cur, next, nextEl, offset; + cur = this.$el.find('.cur').removeClass('cur'); + next = cur.next(); + if (!next.length) { + next = this.$el.find('li:first'); + } + next.addClass('cur'); + nextEl = next[0]; + offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0); + return this.scrollTop(Math.max(0, offset - this.$el.height())); + }; + + View.prototype.prev = function() { + var cur, offset, prev, prevEl; + cur = this.$el.find('.cur').removeClass('cur'); + prev = cur.prev(); + if (!prev.length) { + prev = this.$el.find('li:last'); + } + prev.addClass('cur'); + prevEl = prev[0]; + offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0); + return this.scrollTop(Math.max(0, offset - this.$el.height())); + }; + + View.prototype.scrollTop = function(scrollTop) { + var scrollDuration; + scrollDuration = this.context.getOpt('scrollDuration'); + if (scrollDuration) { + return this.$elUl.animate({ + scrollTop: scrollTop + }, scrollDuration); + } else { + return this.$elUl.scrollTop(scrollTop); + } + }; + + View.prototype.show = function() { + var rect; + if (this.stopShowing) { + this.stopShowing = false; + return; + } + if (!this.visible()) { + this.$el.show(); + this.$el.scrollTop(0); + this.context.trigger('shown'); + } + if (rect = this.context.rect()) { + return this.reposition(rect); + } + }; + + View.prototype.hide = function(e, time) { + var callback; + if (!this.visible()) { + return; + } + if (isNaN(time)) { + this.$el.hide(); + return this.context.trigger('hidden', [e]); + } else { + callback = (function(_this) { + return function() { + return _this.hide(); + }; + })(this); + clearTimeout(this.timeoutID); + return this.timeoutID = setTimeout(callback, time); + } + }; + + View.prototype.render = function(list) { + var $li, $ul, i, item, len, li, tpl; + if (!($.isArray(list) && list.length > 0)) { + this.hide(); + return; + } + this.$el.find('ul').empty(); + $ul = this.$el.find('ul'); + tpl = this.context.getOpt('displayTpl'); + for (i = 0, len = list.length; i < len; i++) { + item = list[i]; + item = $.extend({}, item, { + 'atwho-at': this.context.at + }); + li = this.context.callbacks("tplEval").call(this.context, tpl, item, "onDisplay"); + $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text)); + $li.data("item-data", item); + $ul.append($li); + } + this.show(); + if (this.context.getOpt('highlightFirst')) { + return $ul.find("li:first").addClass("cur"); + } + }; + + return View; + +})(); + +var Api; + +Api = { + load: function(at, data) { + var c; + if (c = this.controller(at)) { + return c.model.load(data); + } + }, + isSelecting: function() { + var ref; + return !!((ref = this.controller()) != null ? ref.view.visible() : void 0); + }, + hide: function() { + var ref; + return (ref = this.controller()) != null ? ref.view.hide() : void 0; + }, + reposition: function() { + var c; + if (c = this.controller()) { + return c.view.reposition(c.rect()); + } + }, + setIframe: function(iframe, asRoot) { + this.setupRootElement(iframe, asRoot); + return null; + }, + run: function() { + return this.dispatch(); + }, + destroy: function() { + this.shutdown(); + return this.$inputor.data('atwho', null); + } +}; + +$.fn.atwho = function(method) { + var _args, result; + _args = arguments; + result = null; + this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() { + var $this, app; + if (!(app = ($this = $(this)).data("atwho"))) { + $this.data('atwho', (app = new App(this))); + } + if (typeof method === 'object' || !method) { + return app.reg(method.at, method); + } else if (Api[method] && app) { + return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1)); + } else { + return $.error("Method " + method + " does not exist on jQuery.atwho"); + } + }); + if (result != null) { + return result; + } else { + return this; + } +}; + +$.fn.atwho["default"] = { + at: void 0, + alias: void 0, + data: null, + displayTpl: "
  • ${name}
  • ", + insertTpl: "${atwho-at}${name}", + headerTpl: null, + callbacks: DEFAULT_CALLBACKS, + searchKey: "name", + suffix: void 0, + hideWithoutSuffix: false, + startWithSpace: true, + acceptSpaceBar: false, + highlightFirst: true, + limit: 5, + maxLen: 20, + minLen: 0, + displayTimeout: 300, + delay: null, + spaceSelectsMatch: false, + tabSelectsMatch: true, + editableAtwhoQueryAttrs: {}, + scrollDuration: 150, + suspendOnComposing: true, + lookUpOnClick: true +}; + +$.fn.atwho.debug = false; + +})); diff --git a/browser/js/lib/jquery.autocomplete.js b/browser/js/lib/jquery.autocomplete.js deleted file mode 100644 index d93174b87b00ade63d80197655a88dc0cacb3d1e..0000000000000000000000000000000000000000 --- a/browser/js/lib/jquery.autocomplete.js +++ /dev/null @@ -1,986 +0,0 @@ -/** -* Ajax Autocomplete for jQuery, version %version% -* (c) 2017 Tomas Kirda -* -* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. -* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete -*/ - -/*jslint browser: true, white: true, single: true, this: true, multivar: true */ -/*global define, window, document, jQuery, exports, require */ - -// Expose plugin as an AMD module if AMD loader is present: -(function (factory) { - "use strict"; - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof exports === 'object' && typeof require === 'function') { - // Browserify - factory(require('jquery')); - } else { - // Browser globals - factory(jQuery); - } -}(function ($) { - 'use strict'; - - var - utils = (function () { - return { - escapeRegExChars: function (value) { - return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&"); - }, - createNode: function (containerClass) { - var div = document.createElement('div'); - div.className = containerClass; - div.style.position = 'absolute'; - div.style.display = 'none'; - return div; - } - }; - }()), - - keys = { - ESC: 27, - TAB: 9, - RETURN: 13, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40 - }, - - noop = $.noop; - - function Autocomplete(el, options) { - var that = this; - - // Shared variables: - that.element = el; - that.el = $(el); - that.suggestions = []; - that.badQueries = []; - that.selectedIndex = -1; - that.currentValue = that.element.value; - that.timeoutId = null; - that.cachedResponse = {}; - that.onChangeTimeout = null; - that.onChange = null; - that.isLocal = false; - that.suggestionsContainer = null; - that.noSuggestionsContainer = null; - that.options = $.extend({}, Autocomplete.defaults, options); - that.classes = { - selected: 'autocomplete-selected', - suggestion: 'autocomplete-suggestion' - }; - that.hint = null; - that.hintValue = ''; - that.selection = null; - - // Initialize and set options: - that.initialize(); - that.setOptions(options); - } - - Autocomplete.utils = utils; - - $.Autocomplete = Autocomplete; - - Autocomplete.defaults = { - ajaxSettings: {}, - autoSelectFirst: false, - appendTo: 'body', - serviceUrl: null, - lookup: null, - onSelect: null, - width: 'auto', - minChars: 1, - maxHeight: 300, - deferRequestBy: 0, - params: {}, - formatResult: _formatResult, - formatGroup: _formatGroup, - delimiter: null, - zIndex: 9999, - type: 'GET', - noCache: false, - onSearchStart: noop, - onSearchComplete: noop, - onSearchError: noop, - preserveInput: false, - containerClass: 'autocomplete-suggestions', - tabDisabled: false, - dataType: 'text', - currentRequest: null, - triggerSelectOnValidInput: true, - preventBadQueries: true, - lookupFilter: _lookupFilter, - paramName: 'query', - transformResult: _transformResult, - showNoSuggestionNotice: false, - noSuggestionNotice: 'No results', - orientation: 'bottom', - forceFixPosition: false - }; - - function _lookupFilter(suggestion, originalQuery, queryLowerCase) { - return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; - }; - - function _transformResult(response) { - return typeof response === 'string' ? $.parseJSON(response) : response; - }; - - function _formatResult(suggestion, currentValue) { - // Do not replace anything if the current value is empty - if (!currentValue) { - return suggestion.value; - } - - var pattern = '(' + utils.escapeRegExChars(currentValue) + ')'; - - return suggestion.value - .replace(new RegExp(pattern, 'gi'), '$1<\/strong>') - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/<(\/?strong)>/g, '<$1>'); - }; - - function _formatGroup(suggestion, category) { - return '
    ' + category + '
    '; - }; - - Autocomplete.prototype = { - - initialize: function () { - var that = this, - suggestionSelector = '.' + that.classes.suggestion, - selected = that.classes.selected, - options = that.options, - container; - - // Remove autocomplete attribute to prevent native suggestions: - that.element.setAttribute('autocomplete', 'off'); - - // html() deals with many types: htmlString or Element or Array or jQuery - that.noSuggestionsContainer = $('
    ') - .html(this.options.noSuggestionNotice).get(0); - - that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass); - - container = $(that.suggestionsContainer); - - container.appendTo(options.appendTo || 'body'); - - // Only set width if it was provided: - if (options.width !== 'auto') { - container.css('width', options.width); - } - - // Listen for mouse over event on suggestions list: - container.on('mouseover.autocomplete', suggestionSelector, function () { - that.activate($(this).data('index')); - }); - - // Deselect active element when mouse leaves suggestions container: - container.on('mouseout.autocomplete', function () { - that.selectedIndex = -1; - container.children('.' + selected).removeClass(selected); - }); - - - // Listen for click event on suggestions list: - container.on('click.autocomplete', suggestionSelector, function () { - that.select($(this).data('index')); - }); - - container.on('click.autocomplete', function () { - clearTimeout(that.blurTimeoutId); - }) - - that.fixPositionCapture = function () { - if (that.visible) { - that.fixPosition(); - } - }; - - $(window).on('resize.autocomplete', that.fixPositionCapture); - - that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); }); - that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); }); - that.el.on('blur.autocomplete', function () { that.onBlur(); }); - that.el.on('focus.autocomplete', function () { that.onFocus(); }); - that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); }); - that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); }); - }, - - onFocus: function () { - var that = this; - - that.fixPosition(); - - if (that.el.val().length >= that.options.minChars) { - that.onValueChange(); - } - }, - - onBlur: function () { - var that = this; - - // If user clicked on a suggestion, hide() will - // be canceled, otherwise close suggestions - that.blurTimeoutId = setTimeout(function () { - that.hide(); - }, 200); - }, - - abortAjax: function () { - var that = this; - if (that.currentRequest) { - that.currentRequest.abort(); - that.currentRequest = null; - } - }, - - setOptions: function (suppliedOptions) { - var that = this, - options = that.options; - - this.options = $.extend({}, options, suppliedOptions); - - that.isLocal = Array.isArray(options.lookup); - - if (that.isLocal) { - options.lookup = that.verifySuggestionsFormat(options.lookup); - } - - options.orientation = that.validateOrientation(options.orientation, 'bottom'); - - // Adjust height, width and z-index: - $(that.suggestionsContainer).css({ - 'max-height': options.maxHeight + 'px', - 'width': options.width + 'px', - 'z-index': options.zIndex - }); - }, - - - clearCache: function () { - this.cachedResponse = {}; - this.badQueries = []; - }, - - clear: function () { - this.clearCache(); - this.currentValue = ''; - this.suggestions = []; - }, - - disable: function () { - var that = this; - that.disabled = true; - clearTimeout(that.onChangeTimeout); - that.abortAjax(); - }, - - enable: function () { - this.disabled = false; - }, - - fixPosition: function () { - // Use only when container has already its content - - var that = this, - $container = $(that.suggestionsContainer), - containerParent = $container.parent().get(0); - // Fix position automatically when appended to body. - // In other cases force parameter must be given. - if (containerParent !== document.body && !that.options.forceFixPosition) { - return; - } - - // Choose orientation - var orientation = that.options.orientation, - containerHeight = $container.outerHeight(), - height = that.el.outerHeight(), - offset = that.el.offset(), - styles = { 'top': offset.top, 'left': offset.left }; - - if (orientation === 'auto') { - var viewPortHeight = $(window).height(), - scrollTop = $(window).scrollTop(), - topOverflow = -scrollTop + offset.top - containerHeight, - bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight); - - orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom'; - } - - if (orientation === 'top') { - styles.top += -containerHeight; - } else { - styles.top += height; - } - - // If container is not positioned to body, - // correct its position using offset parent offset - if(containerParent !== document.body) { - var opacity = $container.css('opacity'), - parentOffsetDiff; - - if (!that.visible){ - $container.css('opacity', 0).show(); - } - - parentOffsetDiff = $container.offsetParent().offset(); - styles.top -= parentOffsetDiff.top; - styles.left -= parentOffsetDiff.left; - - if (!that.visible){ - $container.css('opacity', opacity).hide(); - } - } - - if (that.options.width === 'auto') { - styles.width = that.el.outerWidth() + 'px'; - } - - $container.css(styles); - }, - - isCursorAtEnd: function () { - var that = this, - valLength = that.el.val().length, - selectionStart = that.element.selectionStart, - range; - - if (typeof selectionStart === 'number') { - return selectionStart === valLength; - } - if (document.selection) { - range = document.selection.createRange(); - range.moveStart('character', -valLength); - return valLength === range.text.length; - } - return true; - }, - - onKeyPress: function (e) { - var that = this; - - // If suggestions are hidden and user presses arrow down, display suggestions: - if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) { - that.suggest(); - return; - } - - if (that.disabled || !that.visible) { - return; - } - - switch (e.which) { - case keys.ESC: - that.el.val(that.currentValue); - that.hide(); - break; - case keys.RIGHT: - if (that.hint && that.options.onHint && that.isCursorAtEnd()) { - that.selectHint(); - break; - } - return; - case keys.TAB: - if (that.hint && that.options.onHint) { - that.selectHint(); - return; - } - if (that.selectedIndex === -1) { - that.hide(); - return; - } - that.select(that.selectedIndex); - if (that.options.tabDisabled === false) { - return; - } - break; - case keys.RETURN: - if (that.selectedIndex === -1) { - that.hide(); - return; - } - that.select(that.selectedIndex); - break; - case keys.UP: - that.moveUp(); - break; - case keys.DOWN: - that.moveDown(); - break; - default: - return; - } - - // Cancel event if function did not return: - e.stopImmediatePropagation(); - e.preventDefault(); - }, - - onKeyUp: function (e) { - var that = this; - - if (that.disabled) { - return; - } - - switch (e.which) { - case keys.UP: - case keys.DOWN: - return; - } - - clearTimeout(that.onChangeTimeout); - - if (that.currentValue !== that.el.val()) { - that.findBestHint(); - if (that.options.deferRequestBy > 0) { - // Defer lookup in case when value changes very quickly: - that.onChangeTimeout = setTimeout(function () { - that.onValueChange(); - }, that.options.deferRequestBy); - } else { - that.onValueChange(); - } - } - }, - - onValueChange: function () { - var that = this, - options = that.options, - value = that.el.val(), - query = that.getQuery(value); - - if (that.selection && that.currentValue !== query) { - that.selection = null; - (options.onInvalidateSelection || $.noop).call(that.element); - } - - clearTimeout(that.onChangeTimeout); - that.currentValue = value; - that.selectedIndex = -1; - - // Check existing suggestion for the match before proceeding: - if (options.triggerSelectOnValidInput && that.isExactMatch(query)) { - that.select(0); - return; - } - - if (query.length < options.minChars) { - that.hide(); - } else { - that.getSuggestions(query); - } - }, - - isExactMatch: function (query) { - var suggestions = this.suggestions; - - return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase()); - }, - - getQuery: function (value) { - var delimiter = this.options.delimiter, - parts; - - if (!delimiter) { - return value; - } - parts = value.split(delimiter); - return $.trim(parts[parts.length - 1]); - }, - - getSuggestionsLocal: function (query) { - var that = this, - options = that.options, - queryLowerCase = query.toLowerCase(), - filter = options.lookupFilter, - limit = parseInt(options.lookupLimit, 10), - data; - - data = { - suggestions: $.grep(options.lookup, function (suggestion) { - return filter(suggestion, query, queryLowerCase); - }) - }; - - if (limit && data.suggestions.length > limit) { - data.suggestions = data.suggestions.slice(0, limit); - } - - return data; - }, - - getSuggestions: function (q) { - var response, - that = this, - options = that.options, - serviceUrl = options.serviceUrl, - params, - cacheKey, - ajaxSettings; - - options.params[options.paramName] = q; - - if (options.onSearchStart.call(that.element, options.params) === false) { - return; - } - - params = options.ignoreParams ? null : options.params; - - if ($.isFunction(options.lookup)){ - options.lookup(q, function (data) { - that.suggestions = data.suggestions; - that.suggest(); - options.onSearchComplete.call(that.element, q, data.suggestions); - }); - return; - } - - if (that.isLocal) { - response = that.getSuggestionsLocal(q); - } else { - if ($.isFunction(serviceUrl)) { - serviceUrl = serviceUrl.call(that.element, q); - } - cacheKey = serviceUrl + '?' + $.param(params || {}); - response = that.cachedResponse[cacheKey]; - } - - if (response && Array.isArray(response.suggestions)) { - that.suggestions = response.suggestions; - that.suggest(); - options.onSearchComplete.call(that.element, q, response.suggestions); - } else if (!that.isBadQuery(q)) { - that.abortAjax(); - - ajaxSettings = { - url: serviceUrl, - data: params, - type: options.type, - dataType: options.dataType - }; - - $.extend(ajaxSettings, options.ajaxSettings); - - that.currentRequest = $.ajax(ajaxSettings).done(function (data) { - var result; - that.currentRequest = null; - result = options.transformResult(data, q); - that.processResponse(result, q, cacheKey); - options.onSearchComplete.call(that.element, q, result.suggestions); - }).fail(function (jqXHR, textStatus, errorThrown) { - options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown); - }); - } else { - options.onSearchComplete.call(that.element, q, []); - } - }, - - isBadQuery: function (q) { - if (!this.options.preventBadQueries){ - return false; - } - - var badQueries = this.badQueries, - i = badQueries.length; - - while (i--) { - if (q.indexOf(badQueries[i]) === 0) { - return true; - } - } - - return false; - }, - - hide: function () { - var that = this, - container = $(that.suggestionsContainer); - - if ($.isFunction(that.options.onHide) && that.visible) { - that.options.onHide.call(that.element, container); - } - - that.visible = false; - that.selectedIndex = -1; - clearTimeout(that.onChangeTimeout); - $(that.suggestionsContainer).hide(); - that.signalHint(null); - }, - - suggest: function () { - if (!this.suggestions.length) { - if (this.options.showNoSuggestionNotice) { - this.noSuggestions(); - } else { - this.hide(); - } - return; - } - - var that = this, - options = that.options, - groupBy = options.groupBy, - formatResult = options.formatResult, - value = that.getQuery(that.currentValue), - className = that.classes.suggestion, - classSelected = that.classes.selected, - container = $(that.suggestionsContainer), - noSuggestionsContainer = $(that.noSuggestionsContainer), - beforeRender = options.beforeRender, - html = '', - category, - formatGroup = function (suggestion, index) { - var currentCategory = suggestion.data[groupBy]; - - if (category === currentCategory){ - return ''; - } - - category = currentCategory; - - return options.formatGroup(suggestion, category); - }; - - if (options.triggerSelectOnValidInput && that.isExactMatch(value)) { - that.select(0); - return; - } - - // Build suggestions inner HTML: - $.each(that.suggestions, function (i, suggestion) { - if (groupBy){ - html += formatGroup(suggestion, value, i); - } - - html += '
    ' + formatResult(suggestion, value, i) + '
    '; - }); - - this.adjustContainerWidth(); - - noSuggestionsContainer.detach(); - container.html(html); - - if ($.isFunction(beforeRender)) { - beforeRender.call(that.element, container, that.suggestions); - } - - that.fixPosition(); - container.show(); - - // Select first value by default: - if (options.autoSelectFirst) { - that.selectedIndex = 0; - container.scrollTop(0); - container.children('.' + className).first().addClass(classSelected); - } - - that.visible = true; - that.findBestHint(); - }, - - noSuggestions: function() { - var that = this, - beforeRender = that.options.beforeRender, - container = $(that.suggestionsContainer), - noSuggestionsContainer = $(that.noSuggestionsContainer); - - this.adjustContainerWidth(); - - // Some explicit steps. Be careful here as it easy to get - // noSuggestionsContainer removed from DOM if not detached properly. - noSuggestionsContainer.detach(); - - // clean suggestions if any - container.empty(); - container.append(noSuggestionsContainer); - - if ($.isFunction(beforeRender)) { - beforeRender.call(that.element, container, that.suggestions); - } - - that.fixPosition(); - - container.show(); - that.visible = true; - }, - - adjustContainerWidth: function() { - var that = this, - options = that.options, - width, - container = $(that.suggestionsContainer); - - // If width is auto, adjust width before displaying suggestions, - // because if instance was created before input had width, it will be zero. - // Also it adjusts if input width has changed. - if (options.width === 'auto') { - width = that.el.outerWidth(); - container.css('width', width > 0 ? width : 300); - } else if(options.width === 'flex') { - // Trust the source! Unset the width property so it will be the max length - // the containing elements. - container.css('width', ''); - } - }, - - findBestHint: function () { - var that = this, - value = that.el.val().toLowerCase(), - bestMatch = null; - - if (!value) { - return; - } - - $.each(that.suggestions, function (i, suggestion) { - var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0; - if (foundMatch) { - bestMatch = suggestion; - } - return !foundMatch; - }); - - that.signalHint(bestMatch); - }, - - signalHint: function (suggestion) { - var hintValue = '', - that = this; - if (suggestion) { - hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length); - } - if (that.hintValue !== hintValue) { - that.hintValue = hintValue; - that.hint = suggestion; - (this.options.onHint || $.noop)(hintValue); - } - }, - - verifySuggestionsFormat: function (suggestions) { - // If suggestions is string array, convert them to supported format: - if (suggestions.length && typeof suggestions[0] === 'string') { - return $.map(suggestions, function (value) { - return { value: value, data: null }; - }); - } - - return suggestions; - }, - - validateOrientation: function(orientation, fallback) { - orientation = $.trim(orientation || '').toLowerCase(); - - if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){ - orientation = fallback; - } - - return orientation; - }, - - processResponse: function (result, originalQuery, cacheKey) { - var that = this, - options = that.options; - - result.suggestions = that.verifySuggestionsFormat(result.suggestions); - - // Cache results if cache is not disabled: - if (!options.noCache) { - that.cachedResponse[cacheKey] = result; - if (options.preventBadQueries && !result.suggestions.length) { - that.badQueries.push(originalQuery); - } - } - - // Return if originalQuery is not matching current query: - if (originalQuery !== that.getQuery(that.currentValue)) { - return; - } - - that.suggestions = result.suggestions; - that.suggest(); - }, - - activate: function (index) { - var that = this, - activeItem, - selected = that.classes.selected, - container = $(that.suggestionsContainer), - children = container.find('.' + that.classes.suggestion); - - container.find('.' + selected).removeClass(selected); - - that.selectedIndex = index; - - if (that.selectedIndex !== -1 && children.length > that.selectedIndex) { - activeItem = children.get(that.selectedIndex); - $(activeItem).addClass(selected); - return activeItem; - } - - return null; - }, - - selectHint: function () { - var that = this, - i = $.inArray(that.hint, that.suggestions); - - that.select(i); - }, - - select: function (i) { - var that = this; - that.hide(); - that.onSelect(i); - }, - - moveUp: function () { - var that = this; - - if (that.selectedIndex === -1) { - return; - } - - if (that.selectedIndex === 0) { - $(that.suggestionsContainer).children().first().removeClass(that.classes.selected); - that.selectedIndex = -1; - that.el.val(that.currentValue); - that.findBestHint(); - return; - } - - that.adjustScroll(that.selectedIndex - 1); - }, - - moveDown: function () { - var that = this; - - if (that.selectedIndex === (that.suggestions.length - 1)) { - return; - } - - that.adjustScroll(that.selectedIndex + 1); - }, - - adjustScroll: function (index) { - var that = this, - activeItem = that.activate(index); - - if (!activeItem) { - return; - } - - var offsetTop, - upperBound, - lowerBound, - heightDelta = $(activeItem).outerHeight(); - - offsetTop = activeItem.offsetTop; - upperBound = $(that.suggestionsContainer).scrollTop(); - lowerBound = upperBound + that.options.maxHeight - heightDelta; - - if (offsetTop < upperBound) { - $(that.suggestionsContainer).scrollTop(offsetTop); - } else if (offsetTop > lowerBound) { - $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta); - } - - if (!that.options.preserveInput) { - that.el.val(that.getValue(that.suggestions[index].value)); - } - that.signalHint(null); - }, - - onSelect: function (index) { - var that = this, - onSelectCallback = that.options.onSelect, - suggestion = that.suggestions[index]; - - that.currentValue = that.getValue(suggestion.value); - - if (that.currentValue !== that.el.val() && !that.options.preserveInput) { - that.el.val(that.currentValue); - } - - that.signalHint(null); - that.suggestions = []; - that.selection = suggestion; - - if ($.isFunction(onSelectCallback)) { - onSelectCallback.call(that.element, suggestion); - } - }, - - getValue: function (value) { - var that = this, - delimiter = that.options.delimiter, - currentValue, - parts; - - if (!delimiter) { - return value; - } - - currentValue = that.currentValue; - parts = currentValue.split(delimiter); - - if (parts.length === 1) { - return value; - } - - return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value; - }, - - dispose: function () { - var that = this; - that.el.off('.autocomplete').removeData('autocomplete'); - $(window).off('resize.autocomplete', that.fixPositionCapture); - $(that.suggestionsContainer).remove(); - } - }; - - // Create chainable jQuery plugin: - $.fn.devbridgeAutocomplete = function (options, args) { - var dataKey = 'autocomplete'; - // If function invoked without argument return - // instance of the first matched element: - if (!arguments.length) { - return this.first().data(dataKey); - } - - return this.each(function () { - var inputElement = $(this), - instance = inputElement.data(dataKey); - - if (typeof options === 'string') { - if (instance && typeof instance[options] === 'function') { - instance[options](args); - } - } else { - // If instance already exists, destroy it: - if (instance && instance.dispose) { - instance.dispose(); - } - instance = new Autocomplete(this, options); - inputElement.data(dataKey, instance); - } - }); - }; - - // Don't overwrite if it already exists - if (!$.fn.autocomplete) { - $.fn.autocomplete = $.fn.devbridgeAutocomplete; - } -})); diff --git a/browser/js/lib/jquery.caret.js b/browser/js/lib/jquery.caret.js new file mode 100644 index 0000000000000000000000000000000000000000..811ec63ee47bb5c71b244c8491820df47015371b --- /dev/null +++ b/browser/js/lib/jquery.caret.js @@ -0,0 +1,436 @@ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(["jquery"], function ($) { + return (root.returnExportsGlobal = factory($)); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(require("jquery")); + } else { + factory(jQuery); + } +}(this, function ($) { + +/* + Implement Github like autocomplete mentions + http://ichord.github.com/At.js + + Copyright (c) 2013 chord.luo@gmail.com + Licensed under the MIT license. +*/ + +/* +本插件操作 textarea 或者 input 内的插入符 +只实现了获得插入符在文本框中的位置,我设置 +插入符的位置. +*/ + +"use strict"; +var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy; + +pluginName = 'caret'; + +EditableCaret = (function() { + function EditableCaret($inputor) { + this.$inputor = $inputor; + this.domInputor = this.$inputor[0]; + } + + EditableCaret.prototype.setPos = function(pos) { + var fn, found, offset, sel; + if (sel = oWindow.getSelection()) { + offset = 0; + found = false; + (fn = function(pos, parent) { + var node, range, _i, _len, _ref, _results; + _ref = parent.childNodes; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + if (found) { + break; + } + if (node.nodeType === 3) { + if (offset + node.length >= pos) { + found = true; + range = oDocument.createRange(); + range.setStart(node, pos - offset); + sel.removeAllRanges(); + sel.addRange(range); + break; + } else { + _results.push(offset += node.length); + } + } else { + _results.push(fn(pos, node)); + } + } + return _results; + })(pos, this.domInputor); + } + return this.domInputor; + }; + + EditableCaret.prototype.getIEPosition = function() { + return this.getPosition(); + }; + + EditableCaret.prototype.getPosition = function() { + var inputor_offset, offset; + offset = this.getOffset(); + inputor_offset = this.$inputor.offset(); + offset.left -= inputor_offset.left; + offset.top -= inputor_offset.top; + return offset; + }; + + EditableCaret.prototype.getOldIEPos = function() { + var preCaretTextRange, textRange; + textRange = oDocument.selection.createRange(); + preCaretTextRange = oDocument.body.createTextRange(); + preCaretTextRange.moveToElementText(this.domInputor); + preCaretTextRange.setEndPoint("EndToEnd", textRange); + return preCaretTextRange.text.length; + }; + + EditableCaret.prototype.getPos = function() { + var clonedRange, pos, range; + if (range = this.range()) { + clonedRange = range.cloneRange(); + clonedRange.selectNodeContents(this.domInputor); + clonedRange.setEnd(range.endContainer, range.endOffset); + pos = clonedRange.toString().length; + clonedRange.detach(); + return pos; + } else if (oDocument.selection) { + return this.getOldIEPos(); + } + }; + + EditableCaret.prototype.getOldIEOffset = function() { + var range, rect; + range = oDocument.selection.createRange().duplicate(); + range.moveStart("character", -1); + rect = range.getBoundingClientRect(); + return { + height: rect.bottom - rect.top, + left: rect.left, + top: rect.top + }; + }; + + EditableCaret.prototype.getOffset = function(pos) { + var clonedRange, offset, range, rect, shadowCaret; + if (oWindow.getSelection && (range = this.range())) { + if (range.endOffset - 1 > 0 && range.endContainer !== this.domInputor) { + clonedRange = range.cloneRange(); + clonedRange.setStart(range.endContainer, range.endOffset - 1); + clonedRange.setEnd(range.endContainer, range.endOffset); + rect = clonedRange.getBoundingClientRect(); + offset = { + height: rect.height, + left: rect.left + rect.width, + top: rect.top + }; + clonedRange.detach(); + } + if (!offset || (offset != null ? offset.height : void 0) === 0) { + clonedRange = range.cloneRange(); + shadowCaret = $(oDocument.createTextNode("|")); + clonedRange.insertNode(shadowCaret[0]); + clonedRange.selectNode(shadowCaret[0]); + rect = clonedRange.getBoundingClientRect(); + offset = { + height: rect.height, + left: rect.left, + top: rect.top + }; + shadowCaret.remove(); + clonedRange.detach(); + } + } else if (oDocument.selection) { + offset = this.getOldIEOffset(); + } + if (offset) { + offset.top += $(oWindow).scrollTop(); + offset.left += $(oWindow).scrollLeft(); + } + return offset; + }; + + EditableCaret.prototype.range = function() { + var sel; + if (!oWindow.getSelection) { + return; + } + sel = oWindow.getSelection(); + if (sel.rangeCount > 0) { + return sel.getRangeAt(0); + } else { + return null; + } + }; + + return EditableCaret; + +})(); + +InputCaret = (function() { + function InputCaret($inputor) { + this.$inputor = $inputor; + this.domInputor = this.$inputor[0]; + } + + InputCaret.prototype.getIEPos = function() { + var endRange, inputor, len, normalizedValue, pos, range, textInputRange; + inputor = this.domInputor; + range = oDocument.selection.createRange(); + pos = 0; + if (range && range.parentElement() === inputor) { + normalizedValue = inputor.value.replace(/\r\n/g, "\n"); + len = normalizedValue.length; + textInputRange = inputor.createTextRange(); + textInputRange.moveToBookmark(range.getBookmark()); + endRange = inputor.createTextRange(); + endRange.collapse(false); + if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { + pos = len; + } else { + pos = -textInputRange.moveStart("character", -len); + } + } + return pos; + }; + + InputCaret.prototype.getPos = function() { + if (oDocument.selection) { + return this.getIEPos(); + } else { + return this.domInputor.selectionStart; + } + }; + + InputCaret.prototype.setPos = function(pos) { + var inputor, range; + inputor = this.domInputor; + if (oDocument.selection) { + range = inputor.createTextRange(); + range.move("character", pos); + range.select(); + } else if (inputor.setSelectionRange) { + inputor.setSelectionRange(pos, pos); + } + return inputor; + }; + + InputCaret.prototype.getIEOffset = function(pos) { + var h, textRange, x, y; + textRange = this.domInputor.createTextRange(); + pos || (pos = this.getPos()); + textRange.move('character', pos); + x = textRange.boundingLeft; + y = textRange.boundingTop; + h = textRange.boundingHeight; + return { + left: x, + top: y, + height: h + }; + }; + + InputCaret.prototype.getOffset = function(pos) { + var $inputor, offset, position; + $inputor = this.$inputor; + if (oDocument.selection) { + offset = this.getIEOffset(pos); + offset.top += $(oWindow).scrollTop() + $inputor.scrollTop(); + offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft(); + return offset; + } else { + offset = $inputor.offset(); + position = this.getPosition(pos); + return offset = { + left: offset.left + position.left - $inputor.scrollLeft(), + top: offset.top + position.top - $inputor.scrollTop(), + height: position.height + }; + } + }; + + InputCaret.prototype.getPosition = function(pos) { + var $inputor, at_rect, end_range, format, html, mirror, start_range; + $inputor = this.$inputor; + format = function(value) { + value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "
    "); + if (/firefox/i.test(navigator.userAgent)) { + value = value.replace(/\s/g, ' '); + } + return value; + }; + if (pos === void 0) { + pos = this.getPos(); + } + start_range = $inputor.val().slice(0, pos); + end_range = $inputor.val().slice(pos); + html = "" + format(start_range) + ""; + html += "|"; + html += "" + format(end_range) + ""; + mirror = new Mirror($inputor); + return at_rect = mirror.create(html).rect(); + }; + + InputCaret.prototype.getIEPosition = function(pos) { + var h, inputorOffset, offset, x, y; + offset = this.getIEOffset(pos); + inputorOffset = this.$inputor.offset(); + x = offset.left - inputorOffset.left; + y = offset.top - inputorOffset.top; + h = offset.height; + return { + left: x, + top: y, + height: h + }; + }; + + return InputCaret; + +})(); + +Mirror = (function() { + Mirror.prototype.css_attr = ["borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "boxSizing", "fontFamily", "fontSize", "fontWeight", "height", "letterSpacing", "lineHeight", "marginBottom", "marginLeft", "marginRight", "marginTop", "outlineWidth", "overflow", "overflowX", "overflowY", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textAlign", "textOverflow", "textTransform", "whiteSpace", "wordBreak", "wordWrap"]; + + function Mirror($inputor) { + this.$inputor = $inputor; + } + + Mirror.prototype.mirrorCss = function() { + var css, + _this = this; + css = { + position: 'absolute', + left: -9999, + top: 0, + zIndex: -20000 + }; + if (this.$inputor.prop('tagName') === 'TEXTAREA') { + this.css_attr.push('width'); + } + $.each(this.css_attr, function(i, p) { + return css[p] = _this.$inputor.css(p); + }); + return css; + }; + + Mirror.prototype.create = function(html) { + this.$mirror = $('
    '); + this.$mirror.css(this.mirrorCss()); + this.$mirror.html(html); + this.$inputor.after(this.$mirror); + return this; + }; + + Mirror.prototype.rect = function() { + var $flag, pos, rect; + $flag = this.$mirror.find("#caret"); + pos = $flag.position(); + rect = { + left: pos.left, + top: pos.top, + height: $flag.height() + }; + this.$mirror.remove(); + return rect; + }; + + return Mirror; + +})(); + +Utils = { + contentEditable: function($inputor) { + return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true'); + } +}; + +methods = { + pos: function(pos) { + if (pos || pos === 0) { + return this.setPos(pos); + } else { + return this.getPos(); + } + }, + position: function(pos) { + if (oDocument.selection) { + return this.getIEPosition(pos); + } else { + return this.getPosition(pos); + } + }, + offset: function(pos) { + var offset; + offset = this.getOffset(pos); + return offset; + } +}; + +oDocument = null; + +oWindow = null; + +oFrame = null; + +setContextBy = function(settings) { + var iframe; + if (iframe = settings != null ? settings.iframe : void 0) { + oFrame = iframe; + oWindow = iframe.contentWindow; + return oDocument = iframe.contentDocument || oWindow.document; + } else { + oFrame = void 0; + oWindow = window; + return oDocument = document; + } +}; + +discoveryIframeOf = function($dom) { + var error; + oDocument = $dom[0].ownerDocument; + oWindow = oDocument.defaultView || oDocument.parentWindow; + try { + return oFrame = oWindow.frameElement; + } catch (_error) { + error = _error; + } +}; + +$.fn.caret = function(method, value, settings) { + var caret; + if (methods[method]) { + if ($.isPlainObject(value)) { + setContextBy(value); + value = void 0; + } else { + setContextBy(settings); + } + caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this); + return methods[method].apply(caret, [value]); + } else { + return $.error("Method " + method + " does not exist on jQuery.caret"); + } +}; + +$.fn.caret.EditableCaret = EditableCaret; + +$.fn.caret.InputCaret = InputCaret; + +$.fn.caret.Utils = Utils; + +$.fn.caret.apis = methods; + + +})); diff --git a/browser/js/main.js b/browser/js/main.js index e66a2ebdfebfe407d110e2e36e43138cdf76423b..be69e927ae515da4bd202dfdc97381e2db020b28 100644 --- a/browser/js/main.js +++ b/browser/js/main.js @@ -87,6 +87,8 @@ try { //TODO initMenu(); + + new VidjilAutoComplete(db.db_address + 'tag/auto_complete'); } catch(err) { this.db.log_error(err) } diff --git a/server/web2py/applications/vidjil/controllers/tag.py b/server/web2py/applications/vidjil/controllers/tag.py index 88007ef8b320e4c30e86c2f2170846d2b47f4050..5131017c91c3d3928ebbf1527e7d4c961d20cf35 100644 --- a/server/web2py/applications/vidjil/controllers/tag.py +++ b/server/web2py/applications/vidjil/controllers/tag.py @@ -10,10 +10,7 @@ def auto_complete(): if "group_id" not in request.vars: return error_message("missing group id") - if "query" not in request.vars or request.vars["query"][0] != "#": - tags = [] - else: - prefix = get_tag_prefix() - tags = get_tags(db, request.vars["group_id"], request.vars["query"][len(prefix):]) + prefix = get_tag_prefix() + tags = get_tags(db, request.vars["group_id"]) return tags_to_json(tags) diff --git a/server/web2py/applications/vidjil/modules/tag.py b/server/web2py/applications/vidjil/modules/tag.py index 477f5815f3599c310a55b58f9fff9d3b5727770b..6cc38b1b7f8d7713d148aaff06139662b53e9254 100644 --- a/server/web2py/applications/vidjil/modules/tag.py +++ b/server/web2py/applications/vidjil/modules/tag.py @@ -87,11 +87,9 @@ def register_tags(db, table, record_id, text, group_id, reset=False): tag_extractor = TagExtractor(tag_prefix, db) tags = tag_extractor.execute(table, record_id, text, group_id, reset) -def get_tags(db, group_id, query): - like = '%s%%' % query +def get_tags(db, group_id): return db((db.tag.id == db.group_tag.tag_id) & - (db.group_tag.group_id == group_id) & - (db.tag.name.like(like, case_sensitive=False)) + (db.group_tag.group_id == group_id) ).select(db.tag.ALL) def tags_to_json(tags): @@ -100,8 +98,7 @@ def tags_to_json(tags): for tag in tags: tag_dict = {} tag_dict['id'] = tag.id - tag_dict['value'] = '%s%s' % (prefix, tag.name) + tag_dict['name'] = tag.name tag_list.append(tag_dict) - suggestions = {'suggestions': tag_list} - return json.dumps(suggestions) + return json.dumps(tag_list) diff --git a/server/web2py/applications/vidjil/views/patient/add.html b/server/web2py/applications/vidjil/views/patient/add.html index 2bb96ea08a4fe67ac3e0b15a68f484635da97b82..7c014c361b1db37a086f767753bf5004d013af06 100644 --- a/server/web2py/applications/vidjil/views/patient/add.html +++ b/server/web2py/applications/vidjil/views/patient/add.html @@ -32,7 +32,7 @@ - + diff --git a/server/web2py/applications/vidjil/views/patient/edit.html b/server/web2py/applications/vidjil/views/patient/edit.html index c9d131a1706836c2e783d457296f5d4f4229c757..15b9cea3e12c028c63b694db0278803a64cd8851 100644 --- a/server/web2py/applications/vidjil/views/patient/edit.html +++ b/server/web2py/applications/vidjil/views/patient/edit.html @@ -30,7 +30,7 @@ info = db.patient[request.vars["id"]] - + diff --git a/server/web2py/applications/vidjil/views/run/add.html b/server/web2py/applications/vidjil/views/run/add.html index b200e02b777e1452320c55fef55c72427be73314..deeda9f992a6bc9e8a11b2a8f4de7a2451056d01 100644 --- a/server/web2py/applications/vidjil/views/run/add.html +++ b/server/web2py/applications/vidjil/views/run/add.html @@ -27,7 +27,7 @@ - + diff --git a/server/web2py/applications/vidjil/views/run/edit.html b/server/web2py/applications/vidjil/views/run/edit.html index 2683a8ec34551282eb7349673f0fc295371f3821..611c95e7ed46f934f0f213b09aaff2505eff3c6b 100644 --- a/server/web2py/applications/vidjil/views/run/edit.html +++ b/server/web2py/applications/vidjil/views/run/edit.html @@ -25,7 +25,7 @@ info = db.run[request.vars["id"]] - + diff --git a/server/web2py/applications/vidjil/views/sample_set/add.html b/server/web2py/applications/vidjil/views/sample_set/add.html index 1afa4fcd721c5a6f7ffb9e1bb3cef9cfcaca033f..bb0278673db39a91cbc92dedf317e540dd085bf3 100644 --- a/server/web2py/applications/vidjil/views/sample_set/add.html +++ b/server/web2py/applications/vidjil/views/sample_set/add.html @@ -17,7 +17,7 @@ - + diff --git a/server/web2py/applications/vidjil/views/sample_set/edit.html b/server/web2py/applications/vidjil/views/sample_set/edit.html index 6ac55d6ae6dfbff5f89dee37c21f5a201daafc71..9838846bca819f01ba6e083808c3550acf4e3399 100644 --- a/server/web2py/applications/vidjil/views/sample_set/edit.html +++ b/server/web2py/applications/vidjil/views/sample_set/edit.html @@ -15,7 +15,7 @@ info = db.generic[request.vars["id"]] - +