PluginProbe
Property Hive / 2.4.0
Property Hive v2.4.0
2.4.0 2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 All 262 releases
propertyhive / assets / js / tinymce-mention-plugin / tinymce-mention-plugin.js

tinymce-mention-plugin.js in Property Hive 2.4.0, at assets/js/tinymce-mention-plugin/tinymce-mention-plugin.js

431 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*global tinymce, module, require, define, global, self */
2
3 ;(function (f) {
4 'use strict';
5
6 // CommonJS
7 if (typeof exports === 'object' && typeof module !== 'undefined') {
8 module.exports = f(require('jquery'));
9
10 // RequireJS
11 } else if (typeof define === 'function' && define.amd) {
12 define(['jquery'], f);
13
14 // <script>
15 } else {
16 var g;
17 if (typeof window !== 'undefined') {
18 g = window;
19 } else if (typeof global !== 'undefined') {
20 g = global;
21 } else if (typeof self !== 'undefined') {
22 g = self;
23 } else {
24 g = this;
25 }
26
27 f(g.jQuery);
28 }
29
30 })(function ($) {
31 'use strict';
32
33 var AutoComplete = function (ed, options) {
34 this.editor = ed;
35
36 this.options = $.extend({}, {
37 source: [],
38 delay: 500,
39 queryBy: 'name',
40 items: 10,
41 mentioned_style: 'background-color: antiquewhite;padding: 2px 4px;border-radius: 5px;',
42 loading_text: "Loading...",
43 loading_class: null
44 }, options);
45
46 this.options.insertFrom = this.options.insertFrom || this.options.queryBy;
47
48 this.matcher = this.options.matcher || this.matcher;
49 this.sorter = this.options.sorter || this.sorter;
50 this.renderDropdown = this.options.renderDropdown || this.renderDropdown;
51 this.render = this.options.render || this.render;
52 this.insert = this.options.insert || this.insert;
53 this.highlighter = this.options.highlighter || this.highlighter;
54
55 this.query = '';
56 this.hasFocus = true;
57
58 this.renderInput();
59
60 this.bindEvents();
61 };
62
63 AutoComplete.prototype = {
64
65 constructor: AutoComplete,
66
67 renderInput: function () {
68 var rawHtml = '<span id="autocomplete-container" style="'+ this.options.mentioned_style +'">' +
69 '<span id="autocomplete-delimiter">' + this.options.delimiter + '</span>' +
70 '<span id="autocomplete-searchtext"><span class="dummy">\u200b</span></span>' +
71 '</span>';
72 this.editor.execCommand('mceInsertContent', false, rawHtml);
73 this.editor.focus();
74 this.editor.selection.select(this.editor.selection.dom.select('span#autocomplete-searchtext span')[0]);
75 this.editor.selection.collapse(0);
76 },
77
78 bindEvents: function () {
79 this.editor.on('keyup', this.editorKeyUpProxy = $.proxy(this.rteKeyUp, this));
80 this.editor.on('keydown', this.editorKeyDownProxy = $.proxy(this.rteKeyDown, this), true);
81 this.editor.on('click', this.editorClickProxy = $.proxy(this.rteClicked, this));
82
83 $('body').on('click', this.bodyClickProxy = $.proxy(this.rteLostFocus, this));
84
85 $(this.editor.getWin()).on('scroll', this.rteScroll = $.proxy(function () { this.cleanUp(true); }, this));
86 },
87
88 unbindEvents: function () {
89 this.editor.off('keyup', this.editorKeyUpProxy);
90 this.editor.off('keydown', this.editorKeyDownProxy);
91 this.editor.off('click', this.editorClickProxy);
92
93 $('body').off('click', this.bodyClickProxy);
94
95 $(this.editor.getWin()).off('scroll', this.rteScroll);
96 },
97
98 rteKeyUp: function (e) {
99 switch (e.which || e.keyCode) {
100 //DOWN ARROW
101 case 40:
102 //UP ARROW
103 case 38:
104 //SHIFT
105 case 16:
106 //CTRL
107 case 17:
108 //ALT
109 case 18:
110 break;
111
112 //BACKSPACE
113 case 8:
114 if (this.query === '') {
115 this.cleanUp(true);
116 } else {
117 this.lookup();
118 }
119 break;
120
121 //TAB
122 //ENTER
123 case 9:
124 case 13:
125 var item = (this.$dropdown !== undefined) ? this.$dropdown.find('li.active') : [];
126 if (item.length) {
127 this.select(item.data());
128 this.cleanUp(false);
129 } else {
130 this.cleanUp(true);
131 }
132 break;
133
134 //ESC
135 case 27:
136 this.cleanUp(true);
137 break;
138
139 default:
140 this.lookup();
141 }
142 },
143
144 rteKeyDown: function (e) {
145 switch (e.which || e.keyCode) {
146 //TAB
147 case 9:
148 //ENTER
149 case 13:
150 //ESC
151 case 27:
152 e.preventDefault();
153 break;
154
155 //UP ARROW
156 case 38:
157 e.preventDefault();
158 if (this.$dropdown !== undefined) {
159 this.highlightPreviousResult();
160 }
161 break;
162 //DOWN ARROW
163 case 40:
164 e.preventDefault();
165 if (this.$dropdown !== undefined) {
166 this.highlightNextResult();
167 }
168 break;
169 }
170
171 e.stopPropagation();
172 },
173
174 rteClicked: function (e) {
175 var $target = $(e.target);
176 if (this.hasFocus && $target.parent().attr('id') !== 'autocomplete-searchtext') {
177 this.cleanUp(true);
178 }
179 },
180
181 rteLostFocus: function () {
182 if (this.hasFocus) {
183 this.cleanUp(true);
184 }
185 },
186
187 lookup: function () {
188 this.query = $.trim($(this.editor.getBody()).find('#autocomplete-searchtext').text()).replace('\u200b', '');
189
190 if (this.query.length < 2) {
191 if (this.$dropdown) {
192 this.$dropdown.hide();
193 }
194 return;
195 }
196
197 if (this.$dropdown === undefined) {
198 this.show();
199 }
200
201 clearTimeout(this.searchTimeout);
202 this.searchTimeout = setTimeout($.proxy(function () {
203 // Added delimiter parameter as last argument for backwards compatibility.
204 var items = $.isFunction(this.options.source) ? this.options.source(this.query, $.proxy(this.process, this), this.options.delimiter) : this.options.source;
205 if (items) {
206 this.process(items);
207 }
208 }, this), this.options.delay);
209 },
210
211 matcher: function (item) {
212 return ~item[this.options.queryBy].toLowerCase().indexOf(this.query.toLowerCase());
213 },
214
215 sorter: function (items) {
216 var beginswith = [],
217 caseSensitive = [],
218 caseInsensitive = [],
219 item;
220
221 while ((item = items.shift()) !== undefined) {
222 if (!item[this.options.queryBy].toLowerCase().indexOf(this.query.toLowerCase())) {
223 beginswith.push(item);
224 } else if (~item[this.options.queryBy].indexOf(this.query)) {
225 caseSensitive.push(item);
226 } else {
227 caseInsensitive.push(item);
228 }
229 }
230
231 return beginswith.concat(caseSensitive, caseInsensitive);
232 },
233
234 highlighter: function (text) {
235 return text.replace(new RegExp('(' + this.query.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1') + ')', 'ig'), function ($1, match) {
236 return '<strong>' + match + '</strong>';
237 });
238 },
239
240 show: function () {
241 var offset = this.editor.inline ? this.offsetInline() : this.offset();
242
243 this.$dropdown = $(this.renderDropdown()).css({ 'top': offset.top, 'left': offset.left });
244
245 $('body').append(this.$dropdown);
246
247 this.$dropdown.on('click', $.proxy(this.autoCompleteClick, this));
248 },
249
250 process: function (data) {
251 if (!this.hasFocus) {
252 return;
253 }
254
255 var _this = this,
256 result = [],
257 items = $.grep(data, function (item) {
258 return _this.matcher(item);
259 });
260
261 items = _this.sorter(items);
262
263 items = items.slice(0, this.options.items);
264
265 $.each(items, function (i, item) {
266 var $element = $(_this.render(item, i));
267
268 $element.html($element.html().replace($element.text(), _this.highlighter($element.text())));
269
270 $.each(items[i], function (key, val) {
271 $element.attr('data-' + key, val);
272 });
273
274 result.push($element[0].outerHTML);
275 });
276
277 if (result.length) {
278 this.$dropdown.html(result.join('')).show();
279 } else {
280 this.$dropdown.hide();
281 this.$dropdown.find('li').removeClass('active');
282 }
283 },
284
285 renderDropdown: function () {
286 return '<ul class="rte-autocomplete dropdown-menu" style="position:absolute;"><li style="list-style:none; padding: 3px 20px;">' + this.options.loading_text + '</li></ul>';
287 },
288
289 render: function (item, index) {
290 var icon = '';
291 if ( item.type == 'contact' )
292 {
293 icon = '<span class="dashicons dashicons-admin-users" style="font-size:16px; vertical-align:middle"></span> ';
294 }
295 if ( item.type == 'property' )
296 {
297 icon = '<span class="dashicons dashicons-admin-home" style="font-size:16px; vertical-align:middle"></span> ';
298 }
299 return '<li class="dropdown-item">' +
300 '<a href="javascript:;"><span>' + icon + item[this.options.queryBy] + '</span><br><span class="details" style="font-size:11px;">' + item.details + '</span></a>' +
301 '</li>';
302 },
303
304 autoCompleteClick: function (e) {
305 var item = $(e.target).closest('li').data();
306 if (!$.isEmptyObject(item)) {
307 this.select(item);
308 this.cleanUp(false);
309 }
310 e.stopPropagation();
311 e.preventDefault();
312 },
313
314 highlightPreviousResult: function () {
315 var currentIndex = this.$dropdown.find('li.active').index(),
316 index = (currentIndex === 0) ? this.$dropdown.find('li').length - 1 : --currentIndex;
317
318 this.$dropdown.find('li').removeClass('active').eq(index).addClass('active');
319 },
320
321 highlightNextResult: function () {
322 var currentIndex = this.$dropdown.find('li.active').index(),
323 index = (currentIndex === this.$dropdown.find('li').length - 1) ? 0 : ++currentIndex;
324
325 this.$dropdown.find('li').removeClass('active').eq(index).addClass('active');
326 },
327
328 select: function (item) {
329 this.editor.focus();
330 var selection = this.editor.dom.select('span#autocomplete-container')[0];
331 this.editor.dom.remove(selection);
332 this.editor.execCommand('mceInsertContent', false, this.insert(item));
333 },
334
335 insert: function (item) {
336 return '<span contenteditable="false" style="'+ this.options.mentioned_style +'" data-post-id="' + item.id + '">' + item[this.options.insertFrom] + '</span>&nbsp;';
337 },
338
339 cleanUp: function (rollback) {
340 this.unbindEvents();
341 this.hasFocus = false;
342
343 if (this.$dropdown !== undefined) {
344 this.$dropdown.remove();
345 delete this.$dropdown;
346 }
347
348 if (rollback) {
349 var text = this.query,
350 $selection = $(this.editor.dom.select('span#autocomplete-container'));
351
352 if (!$selection.length) {
353 return;
354 }
355
356 var replacement = $('<p>' + this.options.delimiter + text + '</p>')[0].firstChild,
357 focus = $(this.editor.selection.getNode()).offset().top === ($selection.offset().top + (($selection.outerHeight() - $selection.height()) / 2));
358
359 this.editor.dom.replace(replacement, $selection[0]);
360
361 if (focus) {
362 this.editor.selection.select(replacement);
363 this.editor.selection.collapse();
364 }
365 }
366 },
367
368 offset: function () {
369 var rtePosition = $(this.editor.getContainer()).offset(),
370 contentAreaPosition = $(this.editor.getContentAreaContainer()).position(),
371 nodePosition = $(this.editor.dom.select('span#autocomplete-container')).position();
372 return {
373 top: rtePosition.top + contentAreaPosition.top + nodePosition.top + $(this.editor.selection.getNode()).innerHeight() - $(this.editor.getDoc()).scrollTop() + 5,
374 left: rtePosition.left + contentAreaPosition.left + nodePosition.left
375 };
376 },
377
378 offsetInline: function () {
379 var nodePosition = $(this.editor.dom.select('span#autocomplete-container')).offset();
380
381 return {
382 top: nodePosition.top + $(this.editor.selection.getNode()).innerHeight() + 5,
383 left: nodePosition.left
384 };
385 }
386
387 };
388
389 function plugin() {
390 return {
391 init: function (ed) {
392
393 var autoComplete,
394 autoCompleteData = ed.getParam('mentions');
395
396 // If the delimiter is undefined set default value to ['@'].
397 // If the delimiter is a string value convert it to an array. (backwards compatibility)
398 autoCompleteData.delimiter = (autoCompleteData.delimiter !== undefined) ? !$.isArray(autoCompleteData.delimiter) ? [autoCompleteData.delimiter] : autoCompleteData.delimiter : ['@'];
399
400 function prevCharIsSpace() {
401 var start = ed.selection.getRng(true).startOffset,
402 text = ed.selection.getRng(true).startContainer.data || '',
403 charachter = text.substr(start > 0 ? start - 1 : 0, 1);
404
405 return (!!$.trim(charachter).length) ? false : true;
406 }
407
408 ed.on('keypress', function (e) {
409 var delimiterIndex = $.inArray(String.fromCharCode(e.which || e.keyCode), autoCompleteData.delimiter);
410 if (delimiterIndex > -1 && prevCharIsSpace()) {
411 if (autoComplete === undefined || (autoComplete.hasFocus !== undefined && !autoComplete.hasFocus)) {
412 e.preventDefault();
413 // Clone options object and set the used delimiter.
414 autoComplete = new AutoComplete(ed, $.extend({}, autoCompleteData, { delimiter: autoCompleteData.delimiter[delimiterIndex] }));
415 }
416 }
417 });
418 },
419
420 getInfo: function () {
421 return {
422 longname: 'mention',
423 author: 'Steven Devooght',
424 version: tinymce.majorVersion + '.' + tinymce.minorVersion
425 };
426 }
427 }
428 }
429
430 tinymce.PluginManager.add('mention', plugin);
431 });