flashgrid-ext 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,196 @@
1
+ +function ($) {
2
+ 'use strict';
3
+
4
+ // CAROUSEL CLASS DEFINITION
5
+ // =========================
6
+
7
+ var Carousel = function (element, options) {
8
+ this.$element = $(element)
9
+ this.$indicators = this.$element.find('.carousel-indicators')
10
+ this.options = options
11
+ this.paused =
12
+ this.sliding =
13
+ this.interval =
14
+ this.$active =
15
+ this.$items = null
16
+
17
+ this.options.pause == 'hover' && this.$element
18
+ .on('mouseenter', $.proxy(this.pause, this))
19
+ .on('mouseleave', $.proxy(this.cycle, this))
20
+ }
21
+
22
+ Carousel.DEFAULTS = {
23
+ interval: 5000,
24
+ pause: 'hover',
25
+ wrap: true
26
+ }
27
+
28
+ Carousel.prototype.cycle = function (e) {
29
+ e || (this.paused = false)
30
+
31
+ this.interval && clearInterval(this.interval)
32
+
33
+ this.options.interval
34
+ && !this.paused
35
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
36
+
37
+ return this
38
+ }
39
+
40
+ Carousel.prototype.getActiveIndex = function () {
41
+ this.$active = this.$element.find('.item.active')
42
+ this.$items = this.$active.parent().children()
43
+
44
+ return this.$items.index(this.$active)
45
+ }
46
+
47
+ Carousel.prototype.to = function (pos) {
48
+ var that = this
49
+ var activeIndex = this.getActiveIndex()
50
+
51
+ if (pos > (this.$items.length - 1) || pos < 0) return
52
+
53
+ if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) })
54
+ if (activeIndex == pos) return this.pause().cycle()
55
+
56
+ return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
57
+ }
58
+
59
+ Carousel.prototype.pause = function (e) {
60
+ e || (this.paused = true)
61
+
62
+ if (this.$element.find('.next, .prev').length && $.support.transition) {
63
+ this.$element.trigger($.support.transition.end)
64
+ this.cycle(true)
65
+ }
66
+
67
+ this.interval = clearInterval(this.interval)
68
+
69
+ return this
70
+ }
71
+
72
+ Carousel.prototype.next = function () {
73
+ if (this.sliding) return
74
+ return this.slide('next')
75
+ }
76
+
77
+ Carousel.prototype.prev = function () {
78
+ if (this.sliding) return
79
+ return this.slide('prev')
80
+ }
81
+
82
+ Carousel.prototype.slide = function (type, next) {
83
+ var $active = this.$element.find('.item.active')
84
+ var $next = next || $active[type]()
85
+ var isCycling = this.interval
86
+ var direction = type == 'next' ? 'left' : 'right'
87
+ var fallback = type == 'next' ? 'first' : 'last'
88
+ var that = this
89
+
90
+ if (!$next.length) {
91
+ if (!this.options.wrap) return
92
+ $next = this.$element.find('.item')[fallback]()
93
+ }
94
+
95
+ if ($next.hasClass('active')) return this.sliding = false
96
+
97
+ var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
98
+ this.$element.trigger(e)
99
+ if (e.isDefaultPrevented()) return
100
+
101
+ this.sliding = true
102
+
103
+ isCycling && this.pause()
104
+
105
+ if (this.$indicators.length) {
106
+ this.$indicators.find('.active').removeClass('active')
107
+ this.$element.one('slid.bs.carousel', function () {
108
+ var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
109
+ $nextIndicator && $nextIndicator.addClass('active')
110
+ })
111
+ }
112
+
113
+ if ($.support.transition && this.$element.hasClass('slide')) {
114
+ $next.addClass(type)
115
+ $next[0].offsetWidth // force reflow
116
+ $active.addClass(direction)
117
+ $next.addClass(direction)
118
+ $active
119
+ .one($.support.transition.end, function () {
120
+ $next.removeClass([type, direction].join(' ')).addClass('active')
121
+ $active.removeClass(['active', direction].join(' '))
122
+ that.sliding = false
123
+ setTimeout(function () { that.$element.trigger('slid.bs.carousel') }, 0)
124
+ })
125
+ .emulateTransitionEnd($active.css('transition-duration').slice(0, -1) * 1000)
126
+ } else {
127
+ $active.removeClass('active')
128
+ $next.addClass('active')
129
+ this.sliding = false
130
+ this.$element.trigger('slid.bs.carousel')
131
+ }
132
+
133
+ isCycling && this.cycle()
134
+
135
+ return this
136
+ }
137
+
138
+
139
+ // CAROUSEL PLUGIN DEFINITION
140
+ // ==========================
141
+
142
+ var old = $.fn.carousel
143
+
144
+ $.fn.carousel = function (option) {
145
+ return this.each(function () {
146
+ var $this = $(this)
147
+ var data = $this.data('bs.carousel')
148
+ var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
149
+ var action = typeof option == 'string' ? option : options.slide
150
+
151
+ if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
152
+ if (typeof option == 'number') data.to(option)
153
+ else if (action) data[action]()
154
+ else if (options.interval) data.pause().cycle()
155
+ })
156
+ }
157
+
158
+ $.fn.carousel.Constructor = Carousel
159
+
160
+
161
+ // CAROUSEL NO CONFLICT
162
+ // ====================
163
+
164
+ $.fn.carousel.noConflict = function () {
165
+ $.fn.carousel = old
166
+ return this
167
+ }
168
+
169
+
170
+ // CAROUSEL DATA-API
171
+ // =================
172
+
173
+ $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
174
+ var $this = $(this), href
175
+ var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
176
+ var options = $.extend({}, $target.data(), $this.data())
177
+ var slideIndex = $this.attr('data-slide-to')
178
+ if (slideIndex) options.interval = false
179
+
180
+ $target.carousel(options)
181
+
182
+ if (slideIndex = $this.attr('data-slide-to')) {
183
+ $target.data('bs.carousel').to(slideIndex)
184
+ }
185
+
186
+ e.preventDefault()
187
+ })
188
+
189
+ $(window).on('load', function () {
190
+ $('[data-ride="carousel"]').each(function () {
191
+ var $carousel = $(this)
192
+ $carousel.carousel($carousel.data())
193
+ })
194
+ })
195
+
196
+ }(jQuery);
@@ -0,0 +1,2580 @@
1
+ (function (factory) {
2
+ /* global define */
3
+ if (typeof define === 'function' && define.amd) {
4
+ // AMD. Register as an anonymous module.
5
+ define(['jquery', 'codemirror'], factory);
6
+ } else {
7
+ // Browser globals: jQuery, CodeMirror
8
+ factory(window.jQuery, window.CodeMirror);
9
+ }
10
+ }(function ($, CodeMirror) {
11
+
12
+
13
+
14
+ // Array.prototype.reduce fallback
15
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
16
+ if ('function' !== typeof Array.prototype.reduce) {
17
+ Array.prototype.reduce = function (callback, optInitialValue) {
18
+ var idx, value, length = this.length >>> 0, isValueSet = false;
19
+ if (1 < arguments.length) {
20
+ value = optInitialValue;
21
+ isValueSet = true;
22
+ }
23
+ for (idx = 0; length > idx; ++idx) {
24
+ if (this.hasOwnProperty(idx)) {
25
+ if (isValueSet) {
26
+ value = callback(value, this[idx], idx, this);
27
+ } else {
28
+ value = this[idx];
29
+ isValueSet = true;
30
+ }
31
+ }
32
+ }
33
+ if (!isValueSet) {
34
+ throw new TypeError('Reduce of empty array with no initial value');
35
+ }
36
+ return value;
37
+ };
38
+ }
39
+
40
+ /**
41
+ * object which check platform/agent
42
+ */
43
+ var agent = {
44
+ bMac: navigator.appVersion.indexOf('Mac') > -1,
45
+ bMSIE: navigator.userAgent.indexOf('MSIE') > -1,
46
+ bFF: navigator.userAgent.indexOf('Firefox') > -1,
47
+ bCodeMirror: !!CodeMirror
48
+ };
49
+
50
+ /**
51
+ * func utils (for high-order func's arg)
52
+ */
53
+ var func = (function () {
54
+ var eq = function (elA) {
55
+ return function (elB) {
56
+ return elA === elB;
57
+ };
58
+ };
59
+
60
+ var eq2 = function (elA, elB) {
61
+ return elA === elB;
62
+ };
63
+
64
+ var fail = function () {
65
+ return false;
66
+ };
67
+
68
+ var not = function (f) {
69
+ return function () {
70
+ return !f.apply(f, arguments);
71
+ };
72
+ };
73
+
74
+ var self = function (a) {
75
+ return a;
76
+ };
77
+
78
+ return {
79
+ eq: eq,
80
+ eq2: eq2,
81
+ fail: fail,
82
+ not: not,
83
+ self: self
84
+ };
85
+ })();
86
+
87
+ /**
88
+ * list utils
89
+ */
90
+ var list = (function () {
91
+ var head = function (array) { return array[0]; };
92
+ var last = function (array) { return array[array.length - 1]; };
93
+ var initial = function (array) { return array.slice(0, array.length - 1); };
94
+ var tail = function (array) { return array.slice(1); };
95
+
96
+ /**
97
+ * get sum from a list
98
+ * @param {array} array - array
99
+ * @param {function} fn - iterator
100
+ */
101
+ var sum = function (array, fn) {
102
+ fn = fn || func.self;
103
+ return array.reduce(function (memo, v) {
104
+ return memo + fn(v);
105
+ }, 0);
106
+ };
107
+
108
+ /**
109
+ * returns a copy of the collection with array type.
110
+ * @param {collection} collection - collection eg) node.childNodes, ...
111
+ */
112
+ var from = function (collection) {
113
+ var result = [], idx = -1, length = collection.length;
114
+ while (++idx < length) {
115
+ result[idx] = collection[idx];
116
+ }
117
+ return result;
118
+ };
119
+
120
+ /**
121
+ * cluster item by second function
122
+ * @param {array} array - array
123
+ * @param {function} fn - predicate function for cluster rule
124
+ */
125
+ var clusterBy = function (array, fn) {
126
+ if (array.length === 0) { return []; }
127
+ var aTail = tail(array);
128
+ return aTail.reduce(function (memo, v) {
129
+ var aLast = last(memo);
130
+ if (fn(last(aLast), v)) {
131
+ aLast[aLast.length] = v;
132
+ } else {
133
+ memo[memo.length] = [v];
134
+ }
135
+ return memo;
136
+ }, [[head(array)]]);
137
+ };
138
+
139
+ /**
140
+ * returns a copy of the array with all falsy values removed
141
+ * @param {array} array - array
142
+ * @param {function} fn - predicate function for cluster rule
143
+ */
144
+ var compact = function (array) {
145
+ var aResult = [];
146
+ for (var idx = 0, sz = array.length; idx < sz; idx ++) {
147
+ if (array[idx]) { aResult.push(array[idx]); }
148
+ }
149
+ return aResult;
150
+ };
151
+
152
+ return { head: head, last: last, initial: initial, tail: tail,
153
+ sum: sum, from: from, compact: compact, clusterBy: clusterBy };
154
+ })();
155
+
156
+ /**
157
+ * dom utils
158
+ */
159
+ var dom = (function () {
160
+ /**
161
+ * returns predicate which judge whether nodeName is same
162
+ */
163
+ var makePredByNodeName = function (sNodeName) {
164
+ // nodeName of element is always uppercase.
165
+ return function (node) {
166
+ return node && node.nodeName === sNodeName;
167
+ };
168
+ };
169
+
170
+ var isPara = function (node) {
171
+ // Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
172
+ return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName);
173
+ };
174
+
175
+ var isList = function (node) {
176
+ return node && /^UL|^OL/.test(node.nodeName);
177
+ };
178
+
179
+ /**
180
+ * returns whether node is `editor-editable` or not.
181
+ */
182
+ var isEditable = function (node) {
183
+ return node && $(node).hasClass('editor-editable');
184
+ };
185
+
186
+ var isControlSizing = function (node) {
187
+ return node && $(node).hasClass('editor-control-sizing');
188
+ };
189
+
190
+ /**
191
+ * find nearest ancestor predicate hit
192
+ * @param {element} node
193
+ * @param {function} pred - predicate function
194
+ */
195
+ var ancestor = function (node, pred) {
196
+ while (node) {
197
+ if (pred(node)) { return node; }
198
+ if (isEditable(node)) { break; }
199
+
200
+ node = node.parentNode;
201
+ }
202
+ return null;
203
+ };
204
+
205
+ /**
206
+ * returns new array of ancestor nodes (until predicate hit).
207
+ * @param {element} node
208
+ * @param {function} [optional] pred - predicate function
209
+ */
210
+ var listAncestor = function (node, pred) {
211
+ pred = pred || func.fail;
212
+
213
+ var aAncestor = [];
214
+ ancestor(node, function (el) {
215
+ aAncestor.push(el);
216
+ return pred(el);
217
+ });
218
+ return aAncestor;
219
+ };
220
+
221
+ /**
222
+ * returns common ancestor node between two nodes.
223
+ * @param {element} nodeA
224
+ * @param {element} nodeB
225
+ */
226
+ var commonAncestor = function (nodeA, nodeB) {
227
+ var aAncestor = listAncestor(nodeA);
228
+ for (var n = nodeB; n; n = n.parentNode) {
229
+ if ($.inArray(n, aAncestor) > -1) { return n; }
230
+ }
231
+ return null; // difference document area
232
+ };
233
+
234
+ /**
235
+ * listing all Nodes between two nodes.
236
+ * FIXME: nodeA and nodeB must be sorted, use comparePoints later.
237
+ * @param {element} nodeA
238
+ * @param {element} nodeB
239
+ */
240
+ var listBetween = function (nodeA, nodeB) {
241
+ var aNode = [];
242
+
243
+ var bStart = false, bEnd = false;
244
+ var fnWalk = function (node) {
245
+ if (!node) { return; } // traverse fisnish
246
+ if (node === nodeA) { bStart = true; } // start point
247
+ if (bStart && !bEnd) { aNode.push(node); } // between
248
+ if (node === nodeB) { bEnd = true; return; } // end point
249
+
250
+ for (var idx = 0, sz = node.childNodes.length; idx < sz; idx++) {
251
+ fnWalk(node.childNodes[idx]);
252
+ }
253
+ };
254
+
255
+ fnWalk(commonAncestor(nodeA, nodeB)); // DFS with commonAcestor.
256
+ return aNode;
257
+ };
258
+
259
+ /**
260
+ * listing all prevSiblings (until predicate hit).
261
+ * @param {element} node
262
+ * @param {function} [optional] pred - predicate function
263
+ */
264
+ var listPrev = function (node, pred) {
265
+ pred = pred || func.fail;
266
+
267
+ var aNext = [];
268
+ while (node) {
269
+ aNext.push(node);
270
+ if (pred(node)) { break; }
271
+ node = node.previousSibling;
272
+ }
273
+ return aNext;
274
+ };
275
+
276
+ /**
277
+ * listing nextSiblings (until predicate hit).
278
+ * @param {element} node
279
+ * @param {function} pred [optional] - predicate function
280
+ */
281
+ var listNext = function (node, pred) {
282
+ pred = pred || func.fail;
283
+
284
+ var aNext = [];
285
+ while (node) {
286
+ aNext.push(node);
287
+ if (pred(node)) { break; }
288
+ node = node.nextSibling;
289
+ }
290
+ return aNext;
291
+ };
292
+
293
+ /**
294
+ * insert node after preceding
295
+ * @param {element} node
296
+ * @param {element} preceding - predicate function
297
+ */
298
+ var insertAfter = function (node, preceding) {
299
+ var next = preceding.nextSibling, parent = preceding.parentNode;
300
+ if (next) {
301
+ parent.insertBefore(node, next);
302
+ } else {
303
+ parent.appendChild(node);
304
+ }
305
+ return node;
306
+ };
307
+
308
+ /**
309
+ * append children
310
+ * @param {element} node
311
+ * @param {collection} aChild
312
+ */
313
+ var appends = function (node, aChild) {
314
+ $.each(aChild, function (idx, child) {
315
+ node.appendChild(child);
316
+ });
317
+ return node;
318
+ };
319
+
320
+ var isText = makePredByNodeName('#text');
321
+
322
+ /**
323
+ * returns #text's text size or element's childNodes size
324
+ * @param {element} node
325
+ */
326
+ var length = function (node) {
327
+ if (isText(node)) { return node.nodeValue.length; }
328
+ return node.childNodes.length;
329
+ };
330
+
331
+ /**
332
+ * returns offset from parent.
333
+ * @param {element} node
334
+ */
335
+ var position = function (node) {
336
+ var offset = 0;
337
+ while ((node = node.previousSibling)) { offset += 1; }
338
+ return offset;
339
+ };
340
+
341
+ /**
342
+ * return offsetPath(array of offset) from ancestor
343
+ * @param {element} ancestor - ancestor node
344
+ * @param {element} node
345
+ */
346
+ var makeOffsetPath = function (ancestor, node) {
347
+ var aAncestor = list.initial(listAncestor(node, func.eq(ancestor)));
348
+ return $.map(aAncestor, position).reverse();
349
+ };
350
+
351
+ /**
352
+ * return element from offsetPath(array of offset)
353
+ * @param {element} ancestor - ancestor node
354
+ * @param {array} aOffset - offsetPath
355
+ */
356
+ var fromOffsetPath = function (ancestor, aOffset) {
357
+ var current = ancestor;
358
+ for (var i = 0, sz = aOffset.length; i < sz; i++) {
359
+ current = current.childNodes[aOffset[i]];
360
+ }
361
+ return current;
362
+ };
363
+
364
+ /**
365
+ * split element or #text
366
+ * @param {element} node
367
+ * @param {number} offset
368
+ */
369
+ var splitData = function (node, offset) {
370
+ if (offset === 0) { return node; }
371
+ if (offset >= length(node)) { return node.nextSibling; }
372
+
373
+ // splitText
374
+ if (isText(node)) { return node.splitText(offset); }
375
+
376
+ // splitElement
377
+ var child = node.childNodes[offset];
378
+ node = insertAfter(node.cloneNode(false), node);
379
+ return appends(node, listNext(child));
380
+ };
381
+
382
+ /**
383
+ * split dom tree by boundaryPoint(pivot and offset)
384
+ * @param {element} root
385
+ * @param {element} pivot - this will be boundaryPoint's node
386
+ * @param {number} offset - this will be boundaryPoint's offset
387
+ */
388
+ var split = function (root, pivot, offset) {
389
+ var aAncestor = listAncestor(pivot, func.eq(root));
390
+ if (aAncestor.length === 1) { return splitData(pivot, offset); }
391
+ return aAncestor.reduce(function (node, parent) {
392
+ var clone = parent.cloneNode(false);
393
+ insertAfter(clone, parent);
394
+ if (node === pivot) {
395
+ node = splitData(node, offset);
396
+ }
397
+ appends(clone, listNext(node));
398
+ return clone;
399
+ });
400
+ };
401
+
402
+ /**
403
+ * remove node, (bRemoveChild: remove child or not)
404
+ * @param {element} node
405
+ * @param {boolean} bRemoveChild
406
+ */
407
+ var remove = function (node, bRemoveChild) {
408
+ if (!node || !node.parentNode) { return; }
409
+ if (node.removeNode) { return node.removeNode(bRemoveChild); }
410
+
411
+ var elParent = node.parentNode;
412
+ if (!bRemoveChild) {
413
+ var aNode = [];
414
+ var i, sz;
415
+ for (i = 0, sz = node.childNodes.length; i < sz; i++) {
416
+ aNode.push(node.childNodes[i]);
417
+ }
418
+
419
+ for (i = 0, sz = aNode.length; i < sz; i++) {
420
+ elParent.insertBefore(aNode[i], node);
421
+ }
422
+ }
423
+
424
+ elParent.removeChild(node);
425
+ };
426
+
427
+ var html = function ($node) {
428
+ return dom.isTextarea($node[0]) ? $node.val() : $node.html();
429
+ };
430
+
431
+ return {
432
+ blank: agent.bMSIE ? '&nbsp;' : '<br/>',
433
+ emptyPara: '<p><br/></p>',
434
+ isText: isText,
435
+ isPara: isPara,
436
+ isList: isList,
437
+ isEditable: isEditable,
438
+ isControlSizing: isControlSizing,
439
+ isAnchor: makePredByNodeName('A'),
440
+ isDiv: makePredByNodeName('DIV'),
441
+ isLi: makePredByNodeName('LI'),
442
+ isSpan: makePredByNodeName('SPAN'),
443
+ isB: makePredByNodeName('B'),
444
+ isU: makePredByNodeName('U'),
445
+ isS: makePredByNodeName('S'),
446
+ isI: makePredByNodeName('I'),
447
+ isImg: makePredByNodeName('IMG'),
448
+ isTextarea: makePredByNodeName('TEXTAREA'),
449
+ ancestor: ancestor,
450
+ listAncestor: listAncestor,
451
+ listNext: listNext,
452
+ listPrev: listPrev,
453
+ commonAncestor: commonAncestor,
454
+ listBetween: listBetween,
455
+ insertAfter: insertAfter,
456
+ position: position,
457
+ makeOffsetPath: makeOffsetPath,
458
+ fromOffsetPath: fromOffsetPath,
459
+ split: split,
460
+ remove: remove,
461
+ html: html
462
+ };
463
+ })();
464
+
465
+ /**
466
+ * range module
467
+ */
468
+ var range = (function () {
469
+ var bW3CRangeSupport = !!document.createRange;
470
+
471
+ // return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
472
+ var textRange2bp = function (textRange, bStart) {
473
+ var elCont = textRange.parentElement(), nOffset;
474
+
475
+ var tester = document.body.createTextRange(), elPrevCont;
476
+ var aChild = list.from(elCont.childNodes);
477
+ for (nOffset = 0; nOffset < aChild.length; nOffset++) {
478
+ if (dom.isText(aChild[nOffset])) { continue; }
479
+ tester.moveToElementText(aChild[nOffset]);
480
+ if (tester.compareEndPoints('StartToStart', textRange) >= 0) { break; }
481
+ elPrevCont = aChild[nOffset];
482
+ }
483
+
484
+ if (nOffset !== 0 && dom.isText(aChild[nOffset - 1])) {
485
+ var textRangeStart = document.body.createTextRange(), elCurText = null;
486
+ textRangeStart.moveToElementText(elPrevCont || elCont);
487
+ textRangeStart.collapse(!elPrevCont);
488
+ elCurText = elPrevCont ? elPrevCont.nextSibling : elCont.firstChild;
489
+
490
+ var pointTester = textRange.duplicate();
491
+ pointTester.setEndPoint('StartToStart', textRangeStart);
492
+ var nTextCount = pointTester.text.replace(/[\r\n]/g, '').length;
493
+
494
+ while (nTextCount > elCurText.nodeValue.length && elCurText.nextSibling) {
495
+ nTextCount -= elCurText.nodeValue.length;
496
+ elCurText = elCurText.nextSibling;
497
+ }
498
+
499
+ /* jshint ignore:start */
500
+ var sDummy = elCurText.nodeValue; //enforce IE to re-reference elCurText, hack
501
+ /* jshint ignore:end */
502
+
503
+ if (bStart && elCurText.nextSibling && dom.isText(elCurText.nextSibling) &&
504
+ nTextCount === elCurText.nodeValue.length) {
505
+ nTextCount -= elCurText.nodeValue.length;
506
+ elCurText = elCurText.nextSibling;
507
+ }
508
+
509
+ elCont = elCurText;
510
+ nOffset = nTextCount;
511
+ }
512
+
513
+ return {cont: elCont, offset: nOffset};
514
+ };
515
+
516
+ // return TextRange from boundary point (inspired by google closure-library)
517
+ var bp2textRange = function (bp) {
518
+ var textRangeInfo = function (elCont, nOffset) {
519
+ var elNode, bCollapseToStart;
520
+
521
+ if (dom.isText(elCont)) {
522
+ var aPrevText = dom.listPrev(elCont, func.not(dom.isText));
523
+ var elPrevCont = list.last(aPrevText).previousSibling;
524
+ elNode = elPrevCont || elCont.parentNode;
525
+ nOffset += list.sum(list.tail(aPrevText), dom.length);
526
+ bCollapseToStart = !elPrevCont;
527
+ } else {
528
+ elNode = elCont.childNodes[nOffset] || elCont;
529
+ if (dom.isText(elNode)) {
530
+ return textRangeInfo(elNode, nOffset);
531
+ }
532
+
533
+ nOffset = 0;
534
+ bCollapseToStart = false;
535
+ }
536
+
537
+ return {cont: elNode, collapseToStart: bCollapseToStart, offset: nOffset};
538
+ };
539
+
540
+ var textRange = document.body.createTextRange();
541
+ var info = textRangeInfo(bp.cont, bp.offset);
542
+
543
+ textRange.moveToElementText(info.cont);
544
+ textRange.collapse(info.collapseToStart);
545
+ textRange.moveStart('character', info.offset);
546
+ return textRange;
547
+ };
548
+
549
+ // {startContainer, startOffset, endContainer, endOffset}
550
+ var WrappedRange = function (sc, so, ec, eo) {
551
+ this.sc = sc;
552
+ this.so = so;
553
+ this.ec = ec;
554
+ this.eo = eo;
555
+
556
+ // nativeRange: get nativeRange from sc, so, ec, eo
557
+ var nativeRange = function () {
558
+ if (bW3CRangeSupport) {
559
+ var w3cRange = document.createRange();
560
+ w3cRange.setStart(sc, so);
561
+ w3cRange.setEnd(ec, eo);
562
+ return w3cRange;
563
+ } else {
564
+ var textRange = bp2textRange({cont: sc, offset: so});
565
+ textRange.setEndPoint('EndToEnd', bp2textRange({cont: ec, offset: eo}));
566
+ return textRange;
567
+ }
568
+ };
569
+
570
+ // select: update visible range
571
+ this.select = function () {
572
+ var nativeRng = nativeRange();
573
+ if (bW3CRangeSupport) {
574
+ var selection = document.getSelection();
575
+ if (selection.rangeCount > 0) { selection.removeAllRanges(); }
576
+ selection.addRange(nativeRng);
577
+ } else {
578
+ nativeRng.select();
579
+ }
580
+ };
581
+
582
+ // listPara: listing paragraphs on range
583
+ this.listPara = function () {
584
+ var aNode = dom.listBetween(sc, ec);
585
+ var aPara = list.compact($.map(aNode, function (node) {
586
+ return dom.ancestor(node, dom.isPara);
587
+ }));
588
+ return $.map(list.clusterBy(aPara, func.eq2), list.head);
589
+ };
590
+
591
+ // makeIsOn: return isOn(pred) function
592
+ var makeIsOn = function (pred) {
593
+ return function () {
594
+ var elAncestor = dom.ancestor(sc, pred);
595
+ return elAncestor && (elAncestor === dom.ancestor(ec, pred));
596
+ };
597
+ };
598
+
599
+ // isOnEditable: judge whether range is on editable or not
600
+ this.isOnEditable = makeIsOn(dom.isEditable);
601
+ // isOnList: judge whether range is on list node or not
602
+ this.isOnList = makeIsOn(dom.isList);
603
+ // isOnAnchor: judge whether range is on anchor node or not
604
+ this.isOnAnchor = makeIsOn(dom.isAnchor);
605
+ // isCollapsed: judge whether range was collapsed
606
+ this.isCollapsed = function () { return sc === ec && so === eo; };
607
+
608
+ // insertNode
609
+ this.insertNode = function (node) {
610
+ var nativeRng = nativeRange();
611
+ if (bW3CRangeSupport) {
612
+ nativeRng.insertNode(node);
613
+ } else {
614
+ nativeRng.pasteHTML(node.outerHTML); // NOTE: missing node reference.
615
+ }
616
+ };
617
+
618
+ this.toString = function () {
619
+ var nativeRng = nativeRange();
620
+ return bW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
621
+ };
622
+
623
+ //bookmark: offsetPath bookmark
624
+ this.bookmark = function (elEditable) {
625
+ return {
626
+ s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
627
+ e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
628
+ };
629
+ };
630
+ };
631
+
632
+ return { // Range Object
633
+ // create Range Object From arguments or Browser Selection
634
+ create : function (sc, so, ec, eo) {
635
+ if (arguments.length === 0) { // from Browser Selection
636
+ if (bW3CRangeSupport) { // webkit, firefox
637
+ var selection = document.getSelection();
638
+ if (selection.rangeCount === 0) { return null; }
639
+
640
+ var nativeRng = selection.getRangeAt(0);
641
+ sc = nativeRng.startContainer;
642
+ so = nativeRng.startOffset;
643
+ ec = nativeRng.endContainer;
644
+ eo = nativeRng.endOffset;
645
+ } else { // IE8: TextRange
646
+ var textRange = document.selection.createRange();
647
+ var textRangeEnd = textRange.duplicate();
648
+ textRangeEnd.collapse(false);
649
+ var textRangeStart = textRange;
650
+ textRangeStart.collapse(true);
651
+
652
+ var bpStart = textRange2bp(textRangeStart, true),
653
+ bpEnd = textRange2bp(textRangeEnd, false);
654
+
655
+ sc = bpStart.cont;
656
+ so = bpStart.offset;
657
+ ec = bpEnd.cont;
658
+ eo = bpEnd.offset;
659
+ }
660
+ } else if (arguments.length === 2) { //collapsed
661
+ ec = sc;
662
+ eo = so;
663
+ }
664
+ return new WrappedRange(sc, so, ec, eo);
665
+ },
666
+ // createFromBookmark
667
+ createFromBookmark : function (elEditable, bookmark) {
668
+ var sc = dom.fromOffsetPath(elEditable, bookmark.s.path);
669
+ var so = bookmark.s.offset;
670
+ var ec = dom.fromOffsetPath(elEditable, bookmark.e.path);
671
+ var eo = bookmark.e.offset;
672
+ return new WrappedRange(sc, so, ec, eo);
673
+ }
674
+ };
675
+ })();
676
+
677
+ /**
678
+ * aysnc functions which returns deferred object
679
+ */
680
+ var async = (function () {
681
+ /**
682
+ * readFile
683
+ * @param {file} file - file object
684
+ */
685
+ var readFile = function (file) {
686
+ return $.Deferred(function (deferred) {
687
+ var reader = new FileReader();
688
+ reader.onload = function (e) { deferred.resolve(e.target.result); };
689
+ reader.onerror = function () { deferred.reject(this); };
690
+ reader.readAsDataURL(file);
691
+ }).promise();
692
+ };
693
+
694
+ /**
695
+ * loadImage from url string
696
+ * @param {string} sUrl
697
+ */
698
+ var loadImage = function (sUrl) {
699
+ return $.Deferred(function (deferred) {
700
+ var image = new Image();
701
+ image.onload = loaded;
702
+ image.onerror = errored; // URL returns 404, etc
703
+ image.onabort = errored; // IE may call this if user clicks "Stop"
704
+ image.src = sUrl;
705
+
706
+ function loaded() {
707
+ unbindEvents();
708
+ deferred.resolve(image);
709
+ }
710
+ function errored() {
711
+ unbindEvents();
712
+ deferred.reject(image);
713
+ }
714
+ function unbindEvents() {
715
+ image.onload = null;
716
+ image.onerror = null;
717
+ image.onabort = null;
718
+ }
719
+ }).promise();
720
+ };
721
+ return { readFile: readFile, loadImage: loadImage };
722
+ })();
723
+
724
+ /**
725
+ * Style
726
+ */
727
+ var Style = function () {
728
+ // para level style
729
+ this.stylePara = function (rng, oStyle) {
730
+ var aPara = rng.listPara();
731
+ $.each(aPara, function (idx, elPara) {
732
+ $.each(oStyle, function (sKey, sValue) {
733
+ elPara.style[sKey] = sValue;
734
+ });
735
+ });
736
+ };
737
+
738
+ // get current style, elTarget: target element on event.
739
+ this.current = function (rng, elTarget) {
740
+ var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
741
+ var oStyle = $cont.css(['font-size', 'text-align',
742
+ 'list-style-type', 'line-height']) || {};
743
+
744
+ oStyle['font-size'] = parseInt(oStyle['font-size']);
745
+
746
+ // document.queryCommandState for toggle state
747
+ oStyle['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal';
748
+ oStyle['font-italic'] = document.queryCommandState('italic') ? 'italic' : 'normal';
749
+ oStyle['font-underline'] = document.queryCommandState('underline') ? 'underline' : 'normal';
750
+
751
+ // list-style-type to list-style(unordered, ordered)
752
+ if (!rng.isOnList()) {
753
+ oStyle['list-style'] = 'none';
754
+ } else {
755
+ var aOrderedType = ['circle', 'disc', 'disc-leading-zero', 'square'];
756
+ var bUnordered = $.inArray(oStyle['list-style-type'], aOrderedType) > -1;
757
+ oStyle['list-style'] = bUnordered ? 'unordered' : 'ordered';
758
+ }
759
+
760
+ var elPara = dom.ancestor(rng.sc, dom.isPara);
761
+ if (elPara && elPara.style['line-height']) {
762
+ oStyle['line-height'] = elPara.style.lineHeight;
763
+ } else {
764
+ var lineHeight = parseInt(oStyle['line-height']) / parseInt(oStyle['font-size']);
765
+ oStyle['line-height'] = lineHeight.toFixed(1);
766
+ }
767
+
768
+ oStyle.image = dom.isImg(elTarget) && elTarget;
769
+ oStyle.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
770
+ oStyle.aAncestor = dom.listAncestor(rng.sc, dom.isEditable);
771
+
772
+ return oStyle;
773
+ };
774
+ };
775
+
776
+ /**
777
+ * History
778
+ */
779
+ var History = function () {
780
+ var aUndo = [], aRedo = [];
781
+
782
+ var makeSnap = function ($editable) {
783
+ var elEditable = $editable[0], rng = range.create();
784
+ return {
785
+ contents: $editable.html(),
786
+ bookmark: rng.bookmark(elEditable),
787
+ scrollTop: $editable.scrollTop()
788
+ };
789
+ };
790
+
791
+ var applySnap = function ($editable, oSnap) {
792
+ $editable.html(oSnap.contents).scrollTop(oSnap.scrollTop);
793
+ range.createFromBookmark($editable[0], oSnap.bookmark).select();
794
+ };
795
+
796
+ this.undo = function ($editable) {
797
+ var oSnap = makeSnap($editable);
798
+ if (aUndo.length === 0) { return; }
799
+ applySnap($editable, aUndo.pop());
800
+ aRedo.push(oSnap);
801
+ };
802
+
803
+ this.redo = function ($editable) {
804
+ var oSnap = makeSnap($editable);
805
+ if (aRedo.length === 0) { return; }
806
+ applySnap($editable, aRedo.pop());
807
+ aUndo.push(oSnap);
808
+ };
809
+
810
+ this.recordUndo = function ($editable) {
811
+ aRedo = [];
812
+ aUndo.push(makeSnap($editable));
813
+ };
814
+ };
815
+
816
+ var Table = function () {
817
+ /**
818
+ * Create empty table element
819
+ * @param nRow {number}
820
+ * @param nCol {number}
821
+ */
822
+ this.createTable = function (nCol, nRow) {
823
+ var aTD = [], sTD;
824
+ for (var idxCol = 0; idxCol < nCol; idxCol++) {
825
+ aTD.push('<td>' + dom.blank + '</td>');
826
+ }
827
+ sTD = aTD.join('');
828
+
829
+ var aTR = [], sTR;
830
+ for (var idxRow = 0; idxRow < nRow; idxRow++) {
831
+ aTR.push('<tr>' + sTD + '</tr>');
832
+ }
833
+ sTR = aTR.join('');
834
+ var sTable = '<table class="table table-bordered">' + sTR + '</table>';
835
+
836
+ return $(sTable)[0];
837
+ };
838
+ };
839
+
840
+ /**
841
+ * Editor
842
+ */
843
+ var Editor = function () {
844
+
845
+ var style = new Style();
846
+ var table = new Table();
847
+
848
+ /**
849
+ * save current range
850
+ * @param $editable {jQuery}
851
+ */
852
+ this.saveRange = function ($editable) {
853
+ $editable.data('range', range.create());
854
+ };
855
+
856
+ /**
857
+ * restore lately range
858
+ * @param $editable {jQuery}
859
+ */
860
+ this.restoreRange = function ($editable) {
861
+ var rng = $editable.data('range');
862
+ if (rng) { rng.select(); }
863
+ };
864
+
865
+ /**
866
+ * currentStyle
867
+ * @param elTarget {element}
868
+ */
869
+ this.currentStyle = function (elTarget) {
870
+ var rng = range.create();
871
+ return rng.isOnEditable() && style.current(rng, elTarget);
872
+ };
873
+
874
+ /**
875
+ * undo
876
+ * @param $editable {jQuery}
877
+ */
878
+ this.undo = function ($editable) {
879
+ $editable.data('NoteHistory').undo($editable);
880
+ };
881
+
882
+ /**
883
+ * redo
884
+ * @param $editable {jQuery}
885
+ */
886
+ this.redo = function ($editable) {
887
+ $editable.data('NoteHistory').redo($editable);
888
+ };
889
+
890
+ /**
891
+ * record Undo
892
+ * @param $editable {jQuery}
893
+ */
894
+ var recordUndo = this.recordUndo = function ($editable) {
895
+ $editable.data('NoteHistory').recordUndo($editable);
896
+ };
897
+
898
+ /* jshint ignore:start */
899
+ // native commands(with execCommand), generate function for execCommand
900
+ var aCmd = ['bold', 'italic', 'underline', 'strikethrough',
901
+ 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
902
+ 'insertOrderedList', 'insertUnorderedList',
903
+ 'indent', 'outdent', 'formatBlock', 'removeFormat',
904
+ 'backColor', 'foreColor', 'insertHorizontalRule'];
905
+
906
+ for (var idx = 0, len = aCmd.length; idx < len; idx ++) {
907
+ this[aCmd[idx]] = (function (sCmd) {
908
+ return function ($editable, sValue) {
909
+ recordUndo($editable);
910
+ document.execCommand(sCmd, false, sValue);
911
+ };
912
+ })(aCmd[idx]);
913
+ }
914
+ /* jshint ignore:end */
915
+
916
+ /**
917
+ * handle tag key
918
+ * @param $editable {jQuery}
919
+ */
920
+ this.tab = function ($editable) {
921
+ recordUndo($editable);
922
+ var rng = range.create();
923
+ var sNbsp = new Array($editable.data('tabsize') + 1).join('&nbsp;');
924
+ rng.insertNode($('<span id="editorTab">' + sNbsp + '</span>')[0]);
925
+ var $tab = $('#editorTab').removeAttr('id');
926
+ rng = range.create($tab[0], 1);
927
+ rng.select();
928
+ dom.remove($tab[0]);
929
+ };
930
+
931
+ /**
932
+ * insert Image
933
+ * @param $editable {jQuery}
934
+ * @param sUrl {string}
935
+ */
936
+ this.insertImage = function ($editable, sUrl) {
937
+ async.loadImage(sUrl).done(function (image) {
938
+ recordUndo($editable);
939
+ var $image = $('<img>').attr('src', sUrl);
940
+ $image.css('width', Math.min($editable.width(), image.width));
941
+ range.create().insertNode($image[0]);
942
+ }).fail(function () {
943
+ var callbacks = $editable.data('callbacks');
944
+ if (callbacks.onImageUploadError) {
945
+ callbacks.onImageUploadError();
946
+ }
947
+ });
948
+ };
949
+
950
+ /**
951
+ * insert video
952
+ * @param $editable {jQuery}
953
+ * @param sUrl {string}
954
+ */
955
+ this.insertVideo = function ($editable, sUrl) {
956
+ // video url patterns(youtube, instagram, vimeo, dailymotion, youku)
957
+ var ytRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
958
+ var ytMatch = sUrl.match(ytRegExp);
959
+
960
+ var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/;
961
+ var igMatch = sUrl.match(igRegExp);
962
+
963
+ var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
964
+ var vMatch = sUrl.match(vRegExp);
965
+
966
+ var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
967
+ var vimMatch = sUrl.match(vimRegExp);
968
+
969
+ var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
970
+ var dmMatch = sUrl.match(dmRegExp);
971
+
972
+ var ykRegExp = /\/\/(v.youku.com)\/v_show\/id_(.+?).html/;
973
+ var ykMatch = sUrl.match(ykRegExp);
974
+
975
+ var $video;
976
+ if (ytMatch && ytMatch[2].length === 11) {
977
+ var youtubeId = ytMatch[2];
978
+ $video = $('<iframe>')
979
+ .attr('src', 'http://www.youtube.com/embed/' + youtubeId)
980
+ .attr('width', '640').attr('height', '360');
981
+ } else if (igMatch && igMatch[0].length > 0) {
982
+ $video = $('<iframe>')
983
+ .attr('src', igMatch[0] + '/embed/')
984
+ .attr('width', '612').attr('height', '710')
985
+ .attr('scrolling', 'no')
986
+ .attr('allowtransparency', 'true');
987
+ } else if (vMatch && vMatch[0].length > 0) {
988
+ $video = $('<iframe>')
989
+ .attr('src', vMatch[0] + '/embed/simple')
990
+ .attr('width', '600').attr('height', '600')
991
+ .attr('class', 'vine-embed');
992
+ } else if (vimMatch && vimMatch[3].length > 0) {
993
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
994
+ .attr('src', '//player.vimeo.com/video/' + vimMatch[3])
995
+ .attr('width', '640').attr('height', '360');
996
+ } else if (dmMatch && dmMatch[2].length > 0) {
997
+ $video = $('<iframe>')
998
+ .attr('src', 'http://www.dailymotion.com/embed/video/' + dmMatch[2])
999
+ .attr('width', '640').attr('height', '360');
1000
+ } else if (ykMatch && ykMatch[2].length > 0) {
1001
+ $video = $('<iframe>')
1002
+ .attr('src', 'http://player.youku.com/player.php/sid/' + ykMatch[2] + '/v.swf')
1003
+ .attr('width', '600').attr('height', '360');
1004
+ } else {
1005
+ // this is not a known video link. Now what, Cat? Now what?
1006
+ }
1007
+
1008
+ if ($video) {
1009
+ $video.attr('frameborder', 0);
1010
+ range.create().insertNode($video[0]);
1011
+ }
1012
+ };
1013
+
1014
+ /**
1015
+ * formatBlock
1016
+ * @param $editable {jQuery}
1017
+ * @param sTagName {string} - tag name
1018
+ */
1019
+ this.formatBlock = function ($editable, sTagName) {
1020
+ recordUndo($editable);
1021
+ sTagName = agent.bMSIE ? '<' + sTagName + '>' : sTagName;
1022
+ document.execCommand('FormatBlock', false, sTagName);
1023
+ };
1024
+
1025
+ /**
1026
+ * fontsize
1027
+ * @param $editable {jQuery}
1028
+ * @param sValue {string} - fontsize (px)
1029
+ */
1030
+ this.fontSize = function ($editable, sValue) {
1031
+ recordUndo($editable);
1032
+ document.execCommand('fontSize', false, 3);
1033
+ if (agent.bFF) {
1034
+ // firefox: <font size="3"> to <span style='font-size={sValue}px;'>, buggy
1035
+ $editable.find('font[size=3]').removeAttr('size').css('font-size', sValue + 'px');
1036
+ } else {
1037
+ // chrome: <span style="font-size: medium"> to <span style='font-size={sValue}px;'>
1038
+ $editable.find('span').filter(function () {
1039
+ return this.style.fontSize === 'medium';
1040
+ }).css('font-size', sValue + 'px');
1041
+ }
1042
+ };
1043
+
1044
+ /**
1045
+ * lineHeight
1046
+ * @param $editable {jQuery}
1047
+ * @param sValue {string} - lineHeight
1048
+ */
1049
+ this.lineHeight = function ($editable, sValue) {
1050
+ recordUndo($editable);
1051
+ style.stylePara(range.create(), {lineHeight: sValue});
1052
+ };
1053
+
1054
+ /**
1055
+ * unlink
1056
+ * @param $editable {jQuery}
1057
+ */
1058
+ this.unlink = function ($editable) {
1059
+ var rng = range.create();
1060
+ if (rng.isOnAnchor()) {
1061
+ recordUndo($editable);
1062
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1063
+ rng = range.create(elAnchor, 0, elAnchor, 1);
1064
+ rng.select();
1065
+ document.execCommand('unlink');
1066
+ }
1067
+ };
1068
+
1069
+ this.setLinkDialog = function ($editable, fnShowDialog) {
1070
+ var rng = range.create();
1071
+ if (rng.isOnAnchor()) {
1072
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1073
+ rng = range.create(elAnchor, 0, elAnchor, 1);
1074
+ }
1075
+ fnShowDialog({
1076
+ range: rng,
1077
+ text: rng.toString(),
1078
+ url: rng.isOnAnchor() ? dom.ancestor(rng.sc, dom.isAnchor).href : ''
1079
+ }, function (sLinkUrl) {
1080
+ rng.select();
1081
+ recordUndo($editable);
1082
+
1083
+ var sLinkUrlWithProtocol;
1084
+ if (sLinkUrl.indexOf('@') !== -1) { // email address
1085
+ sLinkUrlWithProtocol = sLinkUrl.indexOf(':') !== -1 ? sLinkUrl : 'mailto:' + sLinkUrl;
1086
+ } else { // normal address
1087
+ sLinkUrlWithProtocol = sLinkUrl.indexOf('://') !== -1 ? sLinkUrl : 'http://' + sLinkUrl;
1088
+ }
1089
+
1090
+ //IE: createLink when range collapsed.
1091
+ if (agent.bMSIE && rng.isCollapsed()) {
1092
+ rng.insertNode($('<A id="linkAnchor">' + sLinkUrl + '</A>')[0]);
1093
+ var $anchor = $('#linkAnchor').removeAttr('id')
1094
+ .attr('href', sLinkUrlWithProtocol);
1095
+ rng = range.create($anchor[0], 0, $anchor[0], 1);
1096
+ rng.select();
1097
+ } else {
1098
+ document.execCommand('createlink', false, sLinkUrlWithProtocol);
1099
+ }
1100
+ });
1101
+ };
1102
+
1103
+ this.setVideoDialog = function ($editable, fnShowDialog) {
1104
+ var rng = range.create();
1105
+ var editor = this;
1106
+
1107
+ if (rng.isOnAnchor()) {
1108
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1109
+ rng = range.create(elAnchor, 0, elAnchor, 1);
1110
+ }
1111
+
1112
+ fnShowDialog({
1113
+ range: rng,
1114
+ text: rng.toString()
1115
+ }, function (sLinkUrl) {
1116
+ rng.select();
1117
+ recordUndo($editable);
1118
+ editor.insertVideo($editable, sLinkUrl);
1119
+ });
1120
+ };
1121
+
1122
+ this.color = function ($editable, sObjColor) {
1123
+ var oColor = JSON.parse(sObjColor);
1124
+ var foreColor = oColor.foreColor, backColor = oColor.backColor;
1125
+
1126
+ recordUndo($editable);
1127
+ if (foreColor) { document.execCommand('foreColor', false, foreColor); }
1128
+ if (backColor) { document.execCommand('backColor', false, backColor); }
1129
+ };
1130
+
1131
+ this.insertTable = function ($editable, sDim) {
1132
+ recordUndo($editable);
1133
+ var aDim = sDim.split('x');
1134
+ range.create().insertNode(table.createTable(aDim[0], aDim[1]));
1135
+ };
1136
+
1137
+ this.floatMe = function ($editable, sValue, elTarget) {
1138
+ recordUndo($editable);
1139
+ elTarget.style.cssFloat = sValue;
1140
+ };
1141
+
1142
+ /**
1143
+ * resize target
1144
+ * @param $editable {jQuery}
1145
+ * @param sValue {string}
1146
+ * @param elTarget {element} - target element
1147
+ */
1148
+ this.resize = function ($editable, sValue, elTarget) {
1149
+ recordUndo($editable);
1150
+ elTarget.style.width = $editable.width() * sValue + 'px';
1151
+ elTarget.style.height = '';
1152
+ };
1153
+
1154
+ this.resizeTo = function (pos, $target) {
1155
+ var newRatio = pos.y / pos.x;
1156
+ var ratio = $target.data('ratio');
1157
+
1158
+ $target.css({
1159
+ width: ratio > newRatio ? pos.x : pos.y / ratio,
1160
+ height: ratio > newRatio ? pos.x * ratio : pos.y
1161
+ });
1162
+ };
1163
+ };
1164
+
1165
+ /**
1166
+ * Toolbar
1167
+ */
1168
+ var Toolbar = function () {
1169
+ this.update = function ($toolbar, oStyle) {
1170
+ //handle selectbox for fontsize, lineHeight
1171
+ var checkDropdownMenu = function ($btn, nValue) {
1172
+ $btn.find('.dropdown-menu li a').each(function () {
1173
+ var bChecked = parseFloat($(this).data('value')) === nValue;
1174
+ this.className = bChecked ? 'checked' : '';
1175
+ });
1176
+ };
1177
+
1178
+ var $fontsize = $toolbar.find('.editor-fontsize');
1179
+ $fontsize.find('.editor-current-fontsize').html(oStyle['font-size']);
1180
+ checkDropdownMenu($fontsize, parseFloat(oStyle['font-size']));
1181
+
1182
+ var $lineHeight = $toolbar.find('.editor-height');
1183
+ checkDropdownMenu($lineHeight, parseFloat(oStyle['line-height']));
1184
+
1185
+ //check button state
1186
+ var btnState = function (sSelector, pred) {
1187
+ var $btn = $toolbar.find(sSelector);
1188
+ $btn[pred() ? 'addClass' : 'removeClass']('active');
1189
+ };
1190
+
1191
+ btnState('button[data-event="bold"]', function () {
1192
+ return oStyle['font-bold'] === 'bold';
1193
+ });
1194
+ btnState('button[data-event="italic"]', function () {
1195
+ return oStyle['font-italic'] === 'italic';
1196
+ });
1197
+ btnState('button[data-event="underline"]', function () {
1198
+ return oStyle['font-underline'] === 'underline';
1199
+ });
1200
+ btnState('button[data-event="justifyLeft"]', function () {
1201
+ return oStyle['text-align'] === 'left' || oStyle['text-align'] === 'start';
1202
+ });
1203
+ btnState('button[data-event="justifyCenter"]', function () {
1204
+ return oStyle['text-align'] === 'center';
1205
+ });
1206
+ btnState('button[data-event="justifyRight"]', function () {
1207
+ return oStyle['text-align'] === 'right';
1208
+ });
1209
+ btnState('button[data-event="justifyFull"]', function () {
1210
+ return oStyle['text-align'] === 'justify';
1211
+ });
1212
+ btnState('button[data-event="insertUnorderedList"]', function () {
1213
+ return oStyle['list-style'] === 'unordered';
1214
+ });
1215
+ btnState('button[data-event="insertOrderedList"]', function () {
1216
+ return oStyle['list-style'] === 'ordered';
1217
+ });
1218
+ };
1219
+
1220
+ this.updateRecentColor = function (elBtn, sEvent, sValue) {
1221
+ var $color = $(elBtn).closest('.editor-color');
1222
+ var $recentColor = $color.find('.editor-recent-color');
1223
+ var oColor = JSON.parse($recentColor.attr('data-value'));
1224
+ oColor[sEvent] = sValue;
1225
+ $recentColor.attr('data-value', JSON.stringify(oColor));
1226
+ var sKey = sEvent === 'backColor' ? 'background-color' : 'color';
1227
+ $recentColor.find('i').css(sKey, sValue);
1228
+ };
1229
+
1230
+ this.updateFullscreen = function ($toolbar, bFullscreen) {
1231
+ var $btn = $toolbar.find('button[data-event="fullscreen"]');
1232
+ $btn[bFullscreen ? 'addClass' : 'removeClass']('active');
1233
+ };
1234
+ this.updateCodeview = function ($toolbar, bCodeview) {
1235
+ var $btn = $toolbar.find('button[data-event="codeview"]');
1236
+ $btn[bCodeview ? 'addClass' : 'removeClass']('active');
1237
+ };
1238
+
1239
+ this.enable = function ($toolbar) {
1240
+ $toolbar.find('button').not('button[data-event="codeview"]').removeClass('disabled');
1241
+ };
1242
+
1243
+ this.disable = function ($toolbar) {
1244
+ $toolbar.find('button').not('button[data-event="codeview"]').addClass('disabled');
1245
+ };
1246
+ };
1247
+
1248
+ /**
1249
+ * Popover (http://getbootstrap.com/javascript/#popovers)
1250
+ */
1251
+ var Popover = function () {
1252
+ var showPopover = function ($popover, elPlaceholder) {
1253
+ var $placeholder = $(elPlaceholder);
1254
+ var pos = $placeholder.position(), height = $placeholder.height();
1255
+ $popover.css({
1256
+ display: 'block',
1257
+ left: pos.left,
1258
+ top: pos.top + height
1259
+ });
1260
+ };
1261
+
1262
+ this.update = function ($popover, oStyle) {
1263
+ var $linkPopover = $popover.find('.editor-link-popover'),
1264
+ $imagePopover = $popover.find('.editor-image-popover');
1265
+ if (oStyle.anchor) {
1266
+ var $anchor = $linkPopover.find('a');
1267
+ $anchor.attr('href', oStyle.anchor.href).html(oStyle.anchor.href);
1268
+ showPopover($linkPopover, oStyle.anchor);
1269
+ } else {
1270
+ $linkPopover.hide();
1271
+ }
1272
+
1273
+ if (oStyle.image) {
1274
+ showPopover($imagePopover, oStyle.image);
1275
+ } else {
1276
+ $imagePopover.hide();
1277
+ }
1278
+ };
1279
+
1280
+ this.hide = function ($popover) {
1281
+ $popover.children().hide();
1282
+ };
1283
+ };
1284
+
1285
+ /**
1286
+ * Handle
1287
+ */
1288
+ var Handle = function () {
1289
+ this.update = function ($handle, oStyle) {
1290
+ var $selection = $handle.find('.editor-control-selection');
1291
+ if (oStyle.image) {
1292
+ var $image = $(oStyle.image);
1293
+ var pos = $image.position();
1294
+ var szImage = {w: $image.width(), h: $image.height()};
1295
+ $selection.css({
1296
+ display: 'block',
1297
+ left: pos.left,
1298
+ top: pos.top,
1299
+ width: szImage.w,
1300
+ height: szImage.h
1301
+ }).data('target', oStyle.image); // save current image element.
1302
+ var sSizing = szImage.w + 'x' + szImage.h;
1303
+ $selection.find('.editor-control-selection-info').text(sSizing);
1304
+ } else {
1305
+ $selection.hide();
1306
+ }
1307
+ };
1308
+
1309
+ this.hide = function ($handle) {
1310
+ $handle.children().hide();
1311
+ };
1312
+ };
1313
+
1314
+ /**
1315
+ * Dialog
1316
+ */
1317
+ var Dialog = function () {
1318
+ /**
1319
+ * toggle button status
1320
+ * @param $btn {jquery}
1321
+ * @param bEnable {boolean}
1322
+ */
1323
+ var toggleBtn = function ($btn, bEnable) {
1324
+ if (bEnable) {
1325
+ $btn.removeClass('disabled').attr('disabled', false);
1326
+ } else {
1327
+ $btn.addClass('disabled').attr('disabled', true);
1328
+ }
1329
+ };
1330
+
1331
+ /**
1332
+ * show image dialog
1333
+ * @param $dialog {jquery}
1334
+ * @param fnInsertImages {function}
1335
+ * @param fnInsertImage {function}
1336
+ */
1337
+ this.showImageDialog = function ($dialog, fnInsertImages, fnInsertImage) {
1338
+ var $imageDialog = $dialog.find('.editor-image-dialog');
1339
+ var $imageInput = $dialog.find('.editor-image-input'),
1340
+ $imageUrl = $dialog.find('.editor-image-url'),
1341
+ $imageBtn = $dialog.find('.editor-image-btn');
1342
+
1343
+ $imageDialog.on('shown.bs.modal', function () {
1344
+ $imageInput.on('change', function () {
1345
+ fnInsertImages(this.files);
1346
+ $(this).val('');
1347
+ $imageDialog.modal('hide');
1348
+ });
1349
+ $imageUrl.val('').keyup(function () {
1350
+ toggleBtn($imageBtn, $imageUrl.val());
1351
+ }).trigger('focus');
1352
+ $imageBtn.click(function (event) {
1353
+ $imageDialog.modal('hide');
1354
+ fnInsertImage($imageUrl.val());
1355
+ event.preventDefault();
1356
+ });
1357
+ }).on('hidden.bs.modal', function () {
1358
+ $imageInput.off('change');
1359
+ $imageDialog.off('shown.bs.modal hidden.bs.modal');
1360
+ $imageUrl.off('keyup');
1361
+ $imageBtn.off('click');
1362
+ }).modal('show');
1363
+ };
1364
+
1365
+ /**
1366
+ * show video dialog
1367
+ * @param $dialog {jquery}
1368
+ * @param videoInfo {object}
1369
+ * @param callback {function}
1370
+ */
1371
+ this.showVideoDialog = function ($dialog, videoInfo, callback) {
1372
+ var $videoDialog = $dialog.find('.editor-video-dialog');
1373
+ var $videoUrl = $videoDialog.find('.editor-video-url'),
1374
+ $videoBtn = $videoDialog.find('.editor-video-btn');
1375
+
1376
+ $videoDialog.on('shown.bs.modal', function () {
1377
+ $videoUrl.val(videoInfo.text).keyup(function () {
1378
+ toggleBtn($videoBtn, $videoUrl.val());
1379
+ }).trigger('keyup').trigger('focus');
1380
+
1381
+ $videoBtn.click(function (event) {
1382
+ $videoDialog.modal('hide');
1383
+ callback($videoUrl.val());
1384
+ event.preventDefault();
1385
+ });
1386
+ }).on('hidden.bs.modal', function () {
1387
+ $videoUrl.off('keyup');
1388
+ $videoBtn.off('click');
1389
+ $videoDialog.off('show.bs.modal hidden.bs.modal');
1390
+ }).modal('show');
1391
+ };
1392
+
1393
+ /**
1394
+ * show link dialog
1395
+ * @param $dialog {jquery}
1396
+ * @param linkInfo {object}
1397
+ * @param callback {function}
1398
+ */
1399
+ this.showLinkDialog = function ($dialog, linkInfo, callback) {
1400
+ var $linkDialog = $dialog.find('.editor-link-dialog');
1401
+ var $linkText = $linkDialog.find('.editor-link-text'),
1402
+ $linkUrl = $linkDialog.find('.editor-link-url'),
1403
+ $linkBtn = $linkDialog.find('.editor-link-btn');
1404
+
1405
+ $linkDialog.on('shown.bs.modal', function () {
1406
+ $linkText.html(linkInfo.text);
1407
+ $linkUrl.val(linkInfo.url).keyup(function () {
1408
+ toggleBtn($linkBtn, $linkUrl.val());
1409
+ if (!linkInfo.text) { $linkText.html($linkUrl.val()); }
1410
+ }).trigger('focus');
1411
+ $linkBtn.click(function (event) {
1412
+ $linkDialog.modal('hide'); //hide and createLink (ie9+)
1413
+ callback($linkUrl.val());
1414
+ event.preventDefault();
1415
+ });
1416
+ }).on('hidden.bs.modal', function () {
1417
+ $linkUrl.off('keyup');
1418
+ $linkBtn.off('click');
1419
+ $linkDialog.off('shown.bs.modal hidden.bs.modal');
1420
+ }).modal('show');
1421
+ };
1422
+
1423
+ /**
1424
+ * show help dialog
1425
+ * @param $dialog {jquery}
1426
+ */
1427
+ this.showHelpDialog = function ($dialog) {
1428
+ $dialog.find('.editor-help-dialog').modal('show');
1429
+ };
1430
+ };
1431
+
1432
+ /**
1433
+ * EventHandler
1434
+ *
1435
+ * handle mouse & key event on editor
1436
+ */
1437
+ var EventHandler = function () {
1438
+ var editor = new Editor();
1439
+ var toolbar = new Toolbar(), popover = new Popover();
1440
+ var handle = new Handle(), dialog = new Dialog();
1441
+
1442
+ var key = { BACKSPACE: 8, TAB: 9, ENTER: 13, SPACE: 32,
1443
+ NUM0: 48, NUM1: 49, NUM6: 54, NUM7: 55, NUM8: 56,
1444
+ B: 66, E: 69, I: 73, J: 74, K: 75, L: 76, R: 82, S: 83, U: 85,
1445
+ Y: 89, Z: 90, SLASH: 191,
1446
+ LEFTBRACKET: 219, BACKSLACH: 220, RIGHTBRACKET: 221 };
1447
+
1448
+ // makeLayoutInfo from editor's descendant node.
1449
+ var makeLayoutInfo = function (descendant) {
1450
+ var $editor = $(descendant).closest('.editor-canvas');
1451
+ return {
1452
+ editor: function () { return $editor; },
1453
+ toolbar: function () { return $editor.find('.editor-toolbar'); },
1454
+ editable: function () { return $editor.find('.editor-editable'); },
1455
+ codable: function () { return $editor.find('.editor-codable'); },
1456
+ statusbar: function () { return $editor.find('.editor-statusbar'); },
1457
+ popover: function () { return $editor.find('.editor-popover'); },
1458
+ handle: function () { return $editor.find('.editor-handle'); },
1459
+ dialog: function () { return $editor.find('.editor-dialog'); }
1460
+ };
1461
+ };
1462
+
1463
+ var hKeydown = function (event) {
1464
+ var bCmd = agent.bMac ? event.metaKey : event.ctrlKey,
1465
+ bShift = event.shiftKey, keyCode = event.keyCode;
1466
+
1467
+ // optimize
1468
+ var bExecCmd = (bCmd || bShift || keyCode === key.TAB);
1469
+ var oLayoutInfo = (bExecCmd) ? makeLayoutInfo(event.target) : null;
1470
+
1471
+ if (keyCode === key.TAB && oLayoutInfo.editable().data('tabsize')) {
1472
+ editor.tab(oLayoutInfo.editable());
1473
+ } else if (bCmd && ((bShift && keyCode === key.Z) || keyCode === key.Y)) {
1474
+ editor.redo(oLayoutInfo.editable());
1475
+ } else if (bCmd && keyCode === key.Z) {
1476
+ editor.undo(oLayoutInfo.editable());
1477
+ } else if (bCmd && keyCode === key.B) {
1478
+ editor.bold(oLayoutInfo.editable());
1479
+ } else if (bCmd && keyCode === key.I) {
1480
+ editor.italic(oLayoutInfo.editable());
1481
+ } else if (bCmd && keyCode === key.U) {
1482
+ editor.underline(oLayoutInfo.editable());
1483
+ } else if (bCmd && bShift && keyCode === key.S) {
1484
+ editor.strikethrough(oLayoutInfo.editable());
1485
+ } else if (bCmd && keyCode === key.BACKSLACH) {
1486
+ editor.removeFormat(oLayoutInfo.editable());
1487
+ } else if (bCmd && keyCode === key.K) {
1488
+ oLayoutInfo.editable();
1489
+ editor.setLinkDialog(oLayoutInfo.editable(), function (linkInfo, cb) {
1490
+ dialog.showLinkDialog(oLayoutInfo.dialog(), linkInfo, cb);
1491
+ });
1492
+ } else if (bCmd && keyCode === key.SLASH) {
1493
+ dialog.showHelpDialog(oLayoutInfo.dialog());
1494
+ } else if (bCmd && bShift && keyCode === key.L) {
1495
+ editor.justifyLeft(oLayoutInfo.editable());
1496
+ } else if (bCmd && bShift && keyCode === key.E) {
1497
+ editor.justifyCenter(oLayoutInfo.editable());
1498
+ } else if (bCmd && bShift && keyCode === key.R) {
1499
+ editor.justifyRight(oLayoutInfo.editable());
1500
+ } else if (bCmd && bShift && keyCode === key.J) {
1501
+ editor.justifyFull(oLayoutInfo.editable());
1502
+ } else if (bCmd && bShift && keyCode === key.NUM7) {
1503
+ editor.insertUnorderedList(oLayoutInfo.editable());
1504
+ } else if (bCmd && bShift && keyCode === key.NUM8) {
1505
+ editor.insertOrderedList(oLayoutInfo.editable());
1506
+ } else if (bCmd && keyCode === key.LEFTBRACKET) {
1507
+ editor.outdent(oLayoutInfo.editable());
1508
+ } else if (bCmd && keyCode === key.RIGHTBRACKET) {
1509
+ editor.indent(oLayoutInfo.editable());
1510
+ } else if (bCmd && keyCode === key.NUM0) { // formatBlock Paragraph
1511
+ editor.formatBlock(oLayoutInfo.editable(), 'P');
1512
+ } else if (bCmd && (key.NUM1 <= keyCode && keyCode <= key.NUM6)) {
1513
+ var sHeading = 'H' + String.fromCharCode(keyCode); // H1~H6
1514
+ editor.formatBlock(oLayoutInfo.editable(), sHeading);
1515
+ } else if (bCmd && keyCode === key.ENTER) {
1516
+ editor.insertHorizontalRule(oLayoutInfo.editable());
1517
+ } else {
1518
+ if (keyCode === key.BACKSPACE || keyCode === key.ENTER ||
1519
+ keyCode === key.SPACE) {
1520
+ editor.recordUndo(makeLayoutInfo(event.target).editable());
1521
+ }
1522
+ return; // not matched
1523
+ }
1524
+ event.preventDefault(); //prevent default event for FF
1525
+ };
1526
+
1527
+ var insertImages = function ($editable, files) {
1528
+ var callbacks = $editable.data('callbacks');
1529
+ editor.restoreRange($editable);
1530
+ if (callbacks.onImageUpload) { // call custom handler
1531
+ callbacks.onImageUpload(files, editor, $editable);
1532
+ } else {
1533
+ $.each(files, function (idx, file) {
1534
+ async.readFile(file).done(function (sURL) {
1535
+ editor.insertImage($editable, sURL);
1536
+ }).fail(function () {
1537
+ if (callbacks.onImageUploadError) {
1538
+ callbacks.onImageUploadError();
1539
+ }
1540
+ });
1541
+ });
1542
+ }
1543
+ };
1544
+
1545
+ var hDropImage = function (event) {
1546
+ var dataTransfer = event.originalEvent.dataTransfer;
1547
+ if (dataTransfer && dataTransfer.files) {
1548
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
1549
+ oLayoutInfo.editable().focus();
1550
+ insertImages(oLayoutInfo.editable(), dataTransfer.files);
1551
+ }
1552
+ event.preventDefault();
1553
+ };
1554
+
1555
+ var hMousedown = function (event) {
1556
+ //preventDefault Selection for FF, IE8+
1557
+ if (dom.isImg(event.target)) { event.preventDefault(); }
1558
+ };
1559
+
1560
+ var hToolbarAndPopoverUpdate = function (event) {
1561
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
1562
+ var oStyle = editor.currentStyle(event.target);
1563
+ if (!oStyle) { return; }
1564
+ toolbar.update(oLayoutInfo.toolbar(), oStyle);
1565
+ popover.update(oLayoutInfo.popover(), oStyle);
1566
+ handle.update(oLayoutInfo.handle(), oStyle);
1567
+ };
1568
+
1569
+ var hScroll = function (event) {
1570
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
1571
+ //hide popover and handle when scrolled
1572
+ popover.hide(oLayoutInfo.popover());
1573
+ handle.hide(oLayoutInfo.handle());
1574
+ };
1575
+
1576
+ var hHandleMousedown = function (event) {
1577
+ if (dom.isControlSizing(event.target)) {
1578
+ var oLayoutInfo = makeLayoutInfo(event.target),
1579
+ $handle = oLayoutInfo.handle(), $popover = oLayoutInfo.popover(),
1580
+ $editable = oLayoutInfo.editable(), $editor = oLayoutInfo.editor();
1581
+
1582
+ var elTarget = $handle.find('.editor-control-selection').data('target'),
1583
+ $target = $(elTarget);
1584
+ var posStart = $target.offset(),
1585
+ scrollTop = $(document).scrollTop(), posDistance;
1586
+
1587
+ $editor.on('mousemove', function (event) {
1588
+ posDistance = {x: event.clientX - posStart.left,
1589
+ y: event.clientY - (posStart.top - scrollTop)};
1590
+ editor.resizeTo(posDistance, $target);
1591
+ handle.update($handle, {image: elTarget});
1592
+ popover.update($popover, {image: elTarget});
1593
+ }).on('mouseup', function () {
1594
+ $editor.off('mousemove').off('mouseup');
1595
+ });
1596
+
1597
+ if (!$target.data('ratio')) { // original ratio.
1598
+ $target.data('ratio', $target.height() / $target.width());
1599
+ }
1600
+
1601
+ editor.recordUndo($editable);
1602
+ event.stopPropagation();
1603
+ event.preventDefault();
1604
+ }
1605
+ };
1606
+
1607
+ var hToolbarAndPopoverMousedown = function (event) {
1608
+ // prevent default event when insertTable (FF, Webkit)
1609
+ var $btn = $(event.target).closest('[data-event]');
1610
+ if ($btn.length > 0) { event.preventDefault(); }
1611
+ };
1612
+
1613
+ var hToolbarAndPopoverClick = function (event) {
1614
+ var $btn = $(event.target).closest('[data-event]');
1615
+
1616
+ if ($btn.length > 0) {
1617
+ var sEvent = $btn.attr('data-event'),
1618
+ sValue = $btn.attr('data-value');
1619
+
1620
+ var oLayoutInfo = makeLayoutInfo(event.target);
1621
+ var $editor = oLayoutInfo.editor(),
1622
+ $toolbar = oLayoutInfo.toolbar(),
1623
+ $dialog = oLayoutInfo.dialog(),
1624
+ $editable = oLayoutInfo.editable(),
1625
+ $codable = oLayoutInfo.codable();
1626
+
1627
+ // before command
1628
+ var elTarget;
1629
+ if ($.inArray(sEvent, ['resize', 'floatMe']) !== -1) {
1630
+ var $handle = oLayoutInfo.handle();
1631
+ var $selection = $handle.find('.editor-control-selection');
1632
+ elTarget = $selection.data('target');
1633
+ }
1634
+
1635
+ if (editor[sEvent]) { // on command
1636
+ $editable.trigger('focus');
1637
+ editor[sEvent]($editable, sValue, elTarget);
1638
+ }
1639
+
1640
+ // after command
1641
+ if ($.inArray(sEvent, ['backColor', 'foreColor']) !== -1) {
1642
+ toolbar.updateRecentColor($btn[0], sEvent, sValue);
1643
+ } else if (sEvent === 'showLinkDialog') { // popover to dialog
1644
+ $editable.focus();
1645
+ editor.setLinkDialog($editable, function (linkInfo, cb) {
1646
+ dialog.showLinkDialog($dialog, linkInfo, cb);
1647
+ });
1648
+ } else if (sEvent === 'showImageDialog') {
1649
+ $editable.focus();
1650
+ dialog.showImageDialog($dialog, function (files) {
1651
+ insertImages($editable, files);
1652
+ }, function (sUrl) {
1653
+ editor.restoreRange($editable);
1654
+ editor.insertImage($editable, sUrl);
1655
+ });
1656
+ } else if (sEvent === 'showVideoDialog') {
1657
+ $editable.focus();
1658
+ editor.setVideoDialog($editable, function (linkInfo, cb) {
1659
+ dialog.showVideoDialog($dialog, linkInfo, cb);
1660
+ });
1661
+ } else if (sEvent === 'showHelpDialog') {
1662
+ dialog.showHelpDialog($dialog);
1663
+ } else if (sEvent === 'fullscreen') {
1664
+ $editor.toggleClass('fullscreen');
1665
+
1666
+ var hResizeFullscreen = function () {
1667
+ var nHeight = $(window).height() - $toolbar.outerHeight();
1668
+ $editable.css('height', nHeight);
1669
+ };
1670
+
1671
+ var bFullscreen = $editor.hasClass('fullscreen');
1672
+ if (bFullscreen) {
1673
+ $editable.data('orgHeight', $editable.css('height'));
1674
+ $(window).resize(hResizeFullscreen).trigger('resize');
1675
+ } else {
1676
+ var hasOptionHeight = !!$editable.data('optionHeight');
1677
+ $editable.css('height', hasOptionHeight ? $editable.data('orgHeight') : 'auto');
1678
+ $(window).off('resize');
1679
+ }
1680
+
1681
+ toolbar.updateFullscreen($toolbar, bFullscreen);
1682
+ } else if (sEvent === 'codeview') {
1683
+ $editor.toggleClass('codeview');
1684
+
1685
+ var bCodeview = $editor.hasClass('codeview');
1686
+ if (bCodeview) {
1687
+ $codable.val($editable.html());
1688
+ $codable.height($editable.height());
1689
+ toolbar.disable($toolbar);
1690
+ $codable.focus();
1691
+
1692
+ // activate CodeMirror as codable
1693
+ if (agent.bCodeMirror) {
1694
+ var cmEditor = CodeMirror.fromTextArea($codable[0], $.extend({
1695
+ mode: 'text/html',
1696
+ lineNumbers: true
1697
+ }, $editor.data('options').codemirror));
1698
+ // CodeMirror hasn't Padding.
1699
+ cmEditor.setSize(null, $editable.outerHeight());
1700
+ // autoFormatRange If formatting included
1701
+ if (cmEditor.autoFormatRange) {
1702
+ cmEditor.autoFormatRange({line: 0, ch: 0}, {
1703
+ line: cmEditor.lineCount(),
1704
+ ch: cmEditor.getTextArea().value.length
1705
+ });
1706
+ }
1707
+ $codable.data('cmEditor', cmEditor);
1708
+ }
1709
+ } else {
1710
+ // deactivate CodeMirror as codable
1711
+ if (agent.bCodeMirror) {
1712
+ $codable.data('cmEditor').toTextArea();
1713
+ }
1714
+
1715
+ $editable.html($codable.val() || dom.emptyPara);
1716
+ $editable.height($editable.data('optionHeight') ? $codable.height() : 'auto');
1717
+
1718
+ toolbar.enable($toolbar);
1719
+ $editable.focus();
1720
+ }
1721
+
1722
+ toolbar.updateCodeview(oLayoutInfo.toolbar(), bCodeview);
1723
+ }
1724
+
1725
+ hToolbarAndPopoverUpdate(event);
1726
+ }
1727
+ };
1728
+
1729
+ var EDITABLE_PADDING = 24;
1730
+ var hStatusbarMousedown = function (event) {
1731
+ var $document = $(document);
1732
+ var oLayoutInfo = makeLayoutInfo(event.target);
1733
+ var $editable = oLayoutInfo.editable();
1734
+
1735
+ var nEditableTop = $editable.offset().top - $document.scrollTop();
1736
+ var hMousemove = function (event) {
1737
+ $editable.height(event.clientY - (nEditableTop + EDITABLE_PADDING));
1738
+ };
1739
+ var hMouseup = function () {
1740
+ $document.unbind('mousemove', hMousemove)
1741
+ .unbind('mouseup', hMouseup);
1742
+ };
1743
+ $document.mousemove(hMousemove).mouseup(hMouseup);
1744
+ event.stopPropagation();
1745
+ event.preventDefault();
1746
+ };
1747
+
1748
+ var PX_PER_EM = 18;
1749
+ var hDimensionPickerMove = function (event) {
1750
+ var $picker = $(event.target.parentNode); // target is mousecatcher
1751
+ var $dimensionDisplay = $picker.next();
1752
+ var $catcher = $picker.find('.editor-dimension-picker-mousecatcher');
1753
+ var $highlighted = $picker.find('.editor-dimension-picker-highlighted');
1754
+ var $unhighlighted = $picker.find('.editor-dimension-picker-unhighlighted');
1755
+ var posOffset;
1756
+ if (event.offsetX === undefined) {
1757
+ // HTML5 with jQuery - e.offsetX is undefined in Firefox
1758
+ var posCatcher = $(event.target).offset();
1759
+ posOffset = {x: event.pageX - posCatcher.left,
1760
+ y: event.pageY - posCatcher.top};
1761
+ } else {
1762
+ posOffset = {x: event.offsetX, y: event.offsetY};
1763
+ }
1764
+
1765
+ var dim = {c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
1766
+ r: Math.ceil(posOffset.y / PX_PER_EM) || 1};
1767
+
1768
+ $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });
1769
+ $catcher.attr('data-value', dim.c + 'x' + dim.r);
1770
+
1771
+ if (3 < dim.c && dim.c < 10) { // 5~10
1772
+ $unhighlighted.css({ width: dim.c + 1 + 'em'});
1773
+ }
1774
+
1775
+ if (3 < dim.r && dim.r < 10) { // 5~10
1776
+ $unhighlighted.css({ height: dim.r + 1 + 'em'});
1777
+ }
1778
+
1779
+ $dimensionDisplay.html(dim.c + ' x ' + dim.r);
1780
+ };
1781
+
1782
+ /**
1783
+ * attach Drag and Drop Events
1784
+ * @param oLayoutInfo {object} - layout Informations
1785
+ */
1786
+ var attachDragAndDropEvent = function (oLayoutInfo) {
1787
+ var collection = $(), $dropzone = oLayoutInfo.dropzone,
1788
+ $dropzoneMessage = oLayoutInfo.dropzone.find('.editor-dropzone-message');
1789
+
1790
+ // show dropzone on dragenter when dragging a object to document.
1791
+ $(document).on('dragenter', function (e) {
1792
+ var bCodeview = oLayoutInfo.editor.hasClass('codeview');
1793
+ if (!bCodeview && collection.length === 0) {
1794
+ oLayoutInfo.editor.addClass('dragover');
1795
+ $dropzone.width(oLayoutInfo.editor.width());
1796
+ $dropzone.height(oLayoutInfo.editor.height());
1797
+ $dropzoneMessage.text('Drag Image Here');
1798
+ }
1799
+ collection = collection.add(e.target);
1800
+ }).on('dragleave', function (e) {
1801
+ collection = collection.not(e.target);
1802
+ if (collection.length === 0) {
1803
+ oLayoutInfo.editor.removeClass('dragover');
1804
+ }
1805
+ }).on('drop', function () {
1806
+ collection = $();
1807
+ oLayoutInfo.editor.removeClass('dragover');
1808
+ });
1809
+
1810
+ // change dropzone's message on hover.
1811
+ $dropzone.on('dragenter', function () {
1812
+ $dropzone.addClass('hover');
1813
+ $dropzoneMessage.text('Drop Image');
1814
+ }).on('dragleave', function () {
1815
+ $dropzone.removeClass('hover');
1816
+ $dropzoneMessage.text('Drag Image Here');
1817
+ });
1818
+
1819
+ // attach dropImage
1820
+ $dropzone.on('drop', function (e) {
1821
+ hDropImage(e);
1822
+ }).on('dragover', false); // prevent default dragover event
1823
+ };
1824
+
1825
+ /**
1826
+ * Attach eventhandler
1827
+ * @param {object} oLayoutInfo - layout Informations
1828
+ * @param {object} options - user options include custom event handlers
1829
+ * @param {function} options.enter - enter key handler
1830
+ */
1831
+ this.attach = function (oLayoutInfo, options) {
1832
+ oLayoutInfo.editable.on('keydown', hKeydown);
1833
+ oLayoutInfo.editable.on('mousedown', hMousedown);
1834
+ oLayoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
1835
+ oLayoutInfo.editable.on('scroll', hScroll);
1836
+
1837
+ attachDragAndDropEvent(oLayoutInfo);
1838
+
1839
+ oLayoutInfo.handle.on('mousedown', hHandleMousedown);
1840
+ oLayoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
1841
+ oLayoutInfo.popover.on('click', hToolbarAndPopoverClick);
1842
+ oLayoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
1843
+ oLayoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
1844
+ oLayoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
1845
+
1846
+ //toolbar table dimension
1847
+ var $toolbar = oLayoutInfo.toolbar;
1848
+ var $catcher = $toolbar.find('.editor-dimension-picker-mousecatcher');
1849
+ $catcher.on('mousemove', hDimensionPickerMove);
1850
+
1851
+ // save selection when focusout
1852
+ oLayoutInfo.editable.on('blur', function () {
1853
+ editor.saveRange(oLayoutInfo.editable);
1854
+ });
1855
+
1856
+ // basic event callbacks (lowercase)
1857
+ // enter, focus, blur, keyup, keydown
1858
+ if (options.onenter) {
1859
+ oLayoutInfo.editable.keypress(function (event) {
1860
+ if (event.keyCode === key.ENTER) { options.onenter(event); }
1861
+ });
1862
+ }
1863
+ if (options.onfocus) { oLayoutInfo.editable.focus(options.onfocus); }
1864
+ if (options.onblur) { oLayoutInfo.editable.blur(options.onblur); }
1865
+ if (options.onkeyup) { oLayoutInfo.editable.keyup(options.onkeyup); }
1866
+ if (options.onkeydown) { oLayoutInfo.editable.keydown(options.onkeydown); }
1867
+
1868
+ // callbacks for advanced features (camel)
1869
+ // All editor status will be saved on editable with jquery's data
1870
+ // for support multiple editor with singleton object.
1871
+ oLayoutInfo.editable.data('callbacks', {
1872
+ onChange: options.onChange,
1873
+ onAutoSave: options.onAutoSave,
1874
+ onPasteBefore: options.onPasteBefore,
1875
+ onPasteAfter: options.onPasteAfter,
1876
+ onImageUpload: options.onImageUpload,
1877
+ onImageUploadError: options.onImageUpload,
1878
+ onFileUpload: options.onFileUpload,
1879
+ onFileUploadError: options.onFileUpload
1880
+ });
1881
+ };
1882
+
1883
+ this.dettach = function (oLayoutInfo) {
1884
+ oLayoutInfo.editable.off();
1885
+ oLayoutInfo.toolbar.off();
1886
+ oLayoutInfo.handle.off();
1887
+ oLayoutInfo.popover.off();
1888
+ };
1889
+ };
1890
+
1891
+ /**
1892
+ * Renderer
1893
+ *
1894
+ * rendering toolbar and editable
1895
+ */
1896
+ var Renderer = function () {
1897
+ var tplToolbarInfo, tplPopover, tplhandle, tplDialog, tplStatusbar;
1898
+
1899
+ /* jshint ignore:start */
1900
+ tplToolbarInfo = {
1901
+ picture: function (lang) {
1902
+ return '<button type="button" class="btn btn-small" title="' + lang.image.image + '" data-event="showImageDialog" tabindex="-1"><i class="fa fa-picture-o"></i></button>';
1903
+ },
1904
+ link: function (lang) {
1905
+ return '<button type="button" class="btn btn-small" title="' + lang.link.link + '" data-event="showLinkDialog" data-shortcut="Ctrl+K" data-mac-shortcut="⌘+K" tabindex="-1"><i class="fa fa-link"></i></button>';
1906
+ },
1907
+ video: function (lang) {
1908
+ return '<button type="button" class="btn btn-small" title="' + lang.video.video + '" data-event="showVideoDialog" tabindex="-1"><i class="fa fa-video-camera"></i></button>';
1909
+ },
1910
+ table: function (lang) {
1911
+ return '<button type="button" class="btn btn-small dropdown-toggle" title="' + lang.table.table + '" data-toggle="dropdown" tabindex="-1"><i class="fa fa-table"></i> <i class="fa fa-caret-down"></i></button>' +
1912
+ '<ul class="dropdown-menu">' +
1913
+ '<div class="editor-dimension-picker">' +
1914
+ '<div class="editor-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
1915
+ '<div class="editor-dimension-picker-highlighted"></div>' +
1916
+ '<div class="editor-dimension-picker-unhighlighted"></div>' +
1917
+ '</div>' +
1918
+ '<div class="editor-dimension-display"> 1 x 1 </div>' +
1919
+ '</ul>';
1920
+ },
1921
+ style: function (lang) {
1922
+ return '<button type="button" class="btn btn-small dropdown-toggle" title="' + lang.style.style + '" data-toggle="dropdown" tabindex="-1"><i class="fa fa-magic"></i> <i class="fa fa-caret-down"></i></button>' +
1923
+ '<ul class="dropdown-menu">' +
1924
+ '<li><a data-event="formatBlock" data-value="p">' + lang.style.normal + '</a></li>' +
1925
+ '<li><a data-event="formatBlock" data-value="blockquote"><blockquote>' + lang.style.blockquote + '</blockquote></a></li>' +
1926
+ '<li><a data-event="formatBlock" data-value="pre"><code>' + lang.style.pre + '</code></a></li>' +
1927
+ '<li><a data-event="formatBlock" data-value="h1"><h1>' + lang.style.h1 + '</h1></a></li>' +
1928
+ '<li><a data-event="formatBlock" data-value="h2"><h2>' + lang.style.h2 + '</h2></a></li>' +
1929
+ '<li><a data-event="formatBlock" data-value="h3"><h3>' + lang.style.h3 + '</h3></a></li>' +
1930
+ '<li><a data-event="formatBlock" data-value="h4"><h4>' + lang.style.h4 + '</h4></a></li>' +
1931
+ '<li><a data-event="formatBlock" data-value="h5"><h5>' + lang.style.h5 + '</h5></a></li>' +
1932
+ '<li><a data-event="formatBlock" data-value="h6"><h6>' + lang.style.h6 + '</h6></a></li>' +
1933
+ '</ul>';
1934
+ },
1935
+ fontsize: function (lang) {
1936
+ return '<button type="button" class="btn btn-small dropdown-toggle" data-toggle="dropdown" title="' + lang.font.size + '" tabindex="-1"><span class="editor-current-fontsize">16</span> <i class="fa fa-caret-down"></i></button>' +
1937
+ '<ul class="dropdown-menu">' +
1938
+ '<li><a data-event="fontSize" data-value="8"><i class="fa fa-check"></i> 8</a></li>' +
1939
+ '<li><a data-event="fontSize" data-value="9"><i class="fa fa-check"></i> 9</a></li>' +
1940
+ '<li><a data-event="fontSize" data-value="10"><i class="fa fa-check"></i> 10</a></li>' +
1941
+ '<li><a data-event="fontSize" data-value="11"><i class="fa fa-check"></i> 11</a></li>' +
1942
+ '<li><a data-event="fontSize" data-value="12"><i class="fa fa-check"></i> 12</a></li>' +
1943
+ '<li><a data-event="fontSize" data-value="16"><i class="fa fa-check"></i> 16</a></li>' +
1944
+ '<li><a data-event="fontSize" data-value="14"><i class="fa fa-check"></i> 14</a></li>' +
1945
+ '<li><a data-event="fontSize" data-value="18"><i class="fa fa-check"></i> 18</a></li>' +
1946
+ '<li><a data-event="fontSize" data-value="24"><i class="fa fa-check"></i> 24</a></li>' +
1947
+ '<li><a data-event="fontSize" data-value="36"><i class="fa fa-check"></i> 36</a></li>' +
1948
+ '</ul>';
1949
+ },
1950
+ color: function (lang) {
1951
+ return '<button type="button" class="btn btn-small editor-recent-color" title="' + lang.color.recent + '" data-event="color" data-value=\'{"backColor":"orange"}\' tabindex="-1"><i class="fa fa-font" style="background-color:orange;"></i></button>' +
1952
+ '<button type="button" class="btn btn-small dropdown-toggle" title="' + lang.color.more + '" data-toggle="dropdown" tabindex="-1">' +
1953
+ '<i class="fa fa-caret-down"></i>' +
1954
+ '</button>' +
1955
+ '<ul class="dropdown-menu">' +
1956
+ '<li>' +
1957
+ '<div class="btn-group">' +
1958
+ '<div class="editor-palette-title">' + lang.color.background + '</div>' +
1959
+ '<div class="editor-color-reset" data-event="backColor" data-value="inherit" title="' + lang.color.transparent + '">' + lang.color.setTransparent + '</div>' +
1960
+ '<div class="editor-color-palette" data-target-event="backColor"></div>' +
1961
+ '</div>' +
1962
+ '<div class="btn-group">' +
1963
+ '<div class="editor-palette-title">' + lang.color.foreground + '</div>' +
1964
+ '<div class="editor-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' + lang.color.resetToDefault + '</div>' +
1965
+ '<div class="editor-color-palette" data-target-event="foreColor"></div>' +
1966
+ '</div>' +
1967
+ '</li>' +
1968
+ '</ul>';
1969
+ },
1970
+ bold: function (lang) {
1971
+ return '<button type="button" class="btn btn-small" title="' + lang.font.bold + '" data-shortcut="Ctrl+B" data-mac-shortcut="⌘+B" data-event="bold" tabindex="-1"><i class="fa fa-bold"></i></button>';
1972
+ },
1973
+ italic: function (lang) {
1974
+ return '<button type="button" class="btn btn-small" title="' + lang.font.italic + '" data-shortcut="Ctrl+I" data-mac-shortcut="⌘+I" data-event="italic" tabindex="-1"><i class="fa fa-italic"></i></button>';
1975
+ },
1976
+ underline: function (lang) {
1977
+ return '<button type="button" class="btn btn-small" title="' + lang.font.underline + '" data-shortcut="Ctrl+U" data-mac-shortcut="⌘+U" data-event="underline" tabindex="-1"><i class="fa fa-underline"></i></button>';
1978
+ },
1979
+ clear: function (lang) {
1980
+ return '<button type="button" class="btn btn-small" title="' + lang.font.clear + '" data-shortcut="Ctrl+\\" data-mac-shortcut="⌘+\\" data-event="removeFormat" tabindex="-1"><i class="fa fa-eraser"></i></button>';
1981
+ },
1982
+ ul: function (lang) {
1983
+ return '<button type="button" class="btn btn-small" title="' + lang.lists.unordered + '" data-shortcut="Ctrl+Shift+8" data-mac-shortcut="⌘+⇧+7" data-event="insertUnorderedList" tabindex="-1"><i class="fa fa-list-ul"></i></button>';
1984
+ },
1985
+ ol: function (lang) {
1986
+ return '<button type="button" class="btn btn-small" title="' + lang.lists.ordered + '" data-shortcut="Ctrl+Shift+7" data-mac-shortcut="⌘+⇧+8" data-event="insertOrderedList" tabindex="-1"><i class="fa fa-list-ol"></i></button>';
1987
+ },
1988
+ paragraph: function (lang) {
1989
+ return '<button type="button" class="btn btn-small dropdown-toggle" title="' + lang.paragraph.paragraph + '" data-toggle="dropdown" tabindex="-1"><i class="fa fa-align-left"></i> <i class="fa fa-caret-down"></i></button>' +
1990
+ '<ul class="dropdown-menu">' +
1991
+ '<li>' +
1992
+ '<div class="editor-align btn-group">' +
1993
+ '<button type="button" class="btn btn-mini" title="' + lang.paragraph.left + '" data-shortcut="Ctrl+Shift+L" data-mac-shortcut="⌘+⇧+L" data-event="justifyLeft" tabindex="-1"><i class="fa fa-align-left"></i></button>' +
1994
+ '<button type="button" class="btn btn-mini" title="' + lang.paragraph.center + '" data-shortcut="Ctrl+Shift+E" data-mac-shortcut="⌘+⇧+E" data-event="justifyCenter" tabindex="-1"><i class="fa fa-align-center"></i></button>' +
1995
+ '<button type="button" class="btn btn-mini" title="' + lang.paragraph.right + '" data-shortcut="Ctrl+Shift+R" data-mac-shortcut="⌘+⇧+R" data-event="justifyRight" tabindex="-1"><i class="fa fa-align-right"></i></button>' +
1996
+ '<button type="button" class="btn btn-mini" title="' + lang.paragraph.justify + '" data-shortcut="Ctrl+Shift+J" data-mac-shortcut="⌘+⇧+J" data-event="justifyFull" tabindex="-1"><i class="fa fa-align-justify"></i></button>' +
1997
+ '</div>' +
1998
+ '</li>' +
1999
+ '<li>' +
2000
+ '<div class="editor-list btn-group">' +
2001
+ '<button type="button" class="btn btn-mini" title="' + lang.paragraph.outdent + '" data-shortcut="Ctrl+[" data-mac-shortcut="⌘+[" data-event="outdent" tabindex="-1"><i class="fa fa-outdent"></i></button>' +
2002
+ '<button type="button" class="btn btn-mini" title="' + lang.paragraph.indent + '" data-shortcut="Ctrl+]" data-mac-shortcut="⌘+]" data-event="indent" tabindex="-1"><i class="fa fa-indent"></i></button>' +
2003
+ '</div>' +
2004
+ '</li>' +
2005
+ '</ul>';
2006
+ },
2007
+ height: function (lang) {
2008
+ return '<button type="button" class="btn btn-small dropdown-toggle" data-toggle="dropdown" title="' + lang.font.height + '" tabindex="-1"><i class="fa fa-text-height"></i>&nbsp; <i class="fa fa-caret-down"></i></button>' +
2009
+ '<ul class="dropdown-menu">' +
2010
+ '<li><a data-event="lineHeight" data-value="1.0"><i class="fa fa-check"></i> 1.0</a></li>' +
2011
+ '<li><a data-event="lineHeight" data-value="1.2"><i class="fa fa-check"></i> 1.2</a></li>' +
2012
+ '<li><a data-event="lineHeight" data-value="1.4"><i class="fa fa-check"></i> 1.4</a></li>' +
2013
+ '<li><a data-event="lineHeight" data-value="1.5"><i class="fa fa-check"></i> 1.5</a></li>' +
2014
+ '<li><a data-event="lineHeight" data-value="1.6"><i class="fa fa-check"></i> 1.6</a></li>' +
2015
+ '<li><a data-event="lineHeight" data-value="1.8"><i class="fa fa-check"></i> 1.8</a></li>' +
2016
+ '<li><a data-event="lineHeight" data-value="2.0"><i class="fa fa-check"></i> 2.0</a></li>' +
2017
+ '<li><a data-event="lineHeight" data-value="3.0"><i class="fa fa-check"></i> 3.0</a></li>' +
2018
+ '</ul>';
2019
+ },
2020
+ help: function (lang) {
2021
+ return '<button type="button" class="btn btn-small" title="' + lang.options.help + '" data-shortcut="Ctrl+/" data-mac-shortcut="⌘+/" data-event="showHelpDialog" tabindex="-1"><i class="fa fa-question"></i></button>';
2022
+ },
2023
+ fullscreen: function (lang) {
2024
+ return '<button type="button" class="btn btn-small" title="' + lang.options.fullscreen + '" data-event="fullscreen" tabindex="-1"><i class="fa fa-arrows-alt"></i></button>';
2025
+ },
2026
+ codeview: function (lang) {
2027
+ return '<button type="button" class="btn btn-small" title="' + lang.options.codeview + '" data-event="codeview" tabindex="-1"><i class="fa fa-code"></i></button>';
2028
+ }
2029
+ };
2030
+ tplPopover = function (lang) {
2031
+ return '<div class="editor-popover">' +
2032
+ '<div class="editor-link-popover popover bottom in" style="display: none;">' +
2033
+ '<div class="arrow"></div>' +
2034
+ '<div class="popover-content editor-link-content">' +
2035
+ '<a href="http://www.google.com" target="_blank">www.google.com</a>&nbsp;&nbsp;' +
2036
+ '<div class="editor-insert btn-group">' +
2037
+ '<button type="button" class="btn btn-mini" title="' + lang.link.edit + '" data-event="showLinkDialog" tabindex="-1"><i class="fa fa-edit"></i></button>' +
2038
+ '<button type="button" class="btn btn-mini" title="' + lang.link.unlink + '" data-event="unlink" tabindex="-1"><i class="fa fa-unlink"></i></button>' +
2039
+ '<button type="button" class="btn btn-mini" title="' + lang.video.videoLink + '" data-event="showVideoDialog" tabindex="-1"><i class="fa fa-video-camera"></i></button>' +
2040
+ '</div>' +
2041
+ '</div>' +
2042
+ '</div>' +
2043
+ '<div class="editor-image-popover popover bottom in" style="display: none;">' +
2044
+ '<div class="arrow"></div>' +
2045
+ '<div class="popover-content editor-image-content">' +
2046
+ '<div class="btn-group">' +
2047
+ '<button type="button" class="btn btn-mini" title="' + lang.image.resizeFull + '" data-event="resize" data-value="1" tabindex="-1"><span class="editor-fontsize-10">100%</span> </button>' +
2048
+ '<button type="button" class="btn btn-mini" title="' + lang.image.resizeHalf + '" data-event="resize" data-value="0.5" tabindex="-1"><span class="editor-fontsize-10">50%</span> </button>' +
2049
+ '<button type="button" class="btn btn-mini" title="' + lang.image.resizeQuarter + '" data-event="resize" data-value="0.25" tabindex="-1"><span class="editor-fontsize-10">25%</span> </button>' +
2050
+ '</div>' +
2051
+ '<div class="btn-group">' +
2052
+ '<button type="button" class="btn btn-mini" title="' + lang.image.floatLeft + '" data-event="floatMe" data-value="left" tabindex="-1"><i class="fa fa-align-left"></i></button>' +
2053
+ '<button type="button" class="btn btn-mini" title="' + lang.image.floatRight + '" data-event="floatMe" data-value="right" tabindex="-1"><i class="fa fa-align-right"></i></button>' +
2054
+ '<button type="button" class="btn btn-mini" title="' + lang.image.floatNone + '" data-event="floatMe" data-value="none" tabindex="-1"><i class="fa fa-align-justify"></i></button>' +
2055
+ '</div>' +
2056
+ '</div>' +
2057
+ '</div>' +
2058
+ '</div>';
2059
+ };
2060
+
2061
+ tplhandle = '<div class="editor-handle">' +
2062
+ '<div class="editor-control-selection">' +
2063
+ '<div class="editor-control-selection-bg"></div>' +
2064
+ '<div class="editor-control-holder editor-control-nw"></div>' +
2065
+ '<div class="editor-control-holder editor-control-ne"></div>' +
2066
+ '<div class="editor-control-holder editor-control-sw"></div>' +
2067
+ '<div class="editor-control-sizing editor-control-se"></div>' +
2068
+ '<div class="editor-control-selection-info"></div>' +
2069
+ '</div>' +
2070
+ '</div>';
2071
+
2072
+ var tplShortcutText = function (lang) {
2073
+ return '<table class="editor-shortcut">' +
2074
+ '<thead>' +
2075
+ '<tr><th></th><th>' + lang.shortcut.textFormatting + '</th></tr>' +
2076
+ '</thead>' +
2077
+ '<tbody>' +
2078
+ '<tr><td>⌘ + B</td><td>' + lang.font.bold + '</td></tr>' +
2079
+ '<tr><td>⌘ + I</td><td>' + lang.font.italic + '</td></tr>' +
2080
+ '<tr><td>⌘ + U</td><td>' + lang.font.underline + '</td></tr>' +
2081
+ '<tr><td>⌘ + ⇧ + S</td><td>' + lang.font.strike + '</td></tr>' +
2082
+ '<tr><td>⌘ + \\</td><td>' + lang.font.clear + '</td></tr>' +
2083
+ '</tr>' +
2084
+ '</tbody>' +
2085
+ '</table>';
2086
+ };
2087
+
2088
+ var tplShortcutAction = function (lang) {
2089
+ return '<table class="editor-shortcut">' +
2090
+ '<thead>' +
2091
+ '<tr><th></th><th>' + lang.shortcut.action + '</th></tr>' +
2092
+ '</thead>' +
2093
+ '<tbody>' +
2094
+ '<tr><td>⌘ + Z</td><td>' + lang.history.undo + '</td></tr>' +
2095
+ '<tr><td>⌘ + ⇧ + Z</td><td>' + lang.history.redo + '</td></tr>' +
2096
+ '<tr><td>⌘ + ]</td><td>' + lang.paragraph.indent + '</td></tr>' +
2097
+ '<tr><td>⌘ + [</td><td>' + lang.paragraph.outdent + '</td></tr>' +
2098
+ '<tr><td>⌘ + K</td><td>' + lang.link.insert + '</td></tr>' +
2099
+ '<tr><td>⌘ + ENTER</td><td>' + lang.hr.insert + '</td></tr>' +
2100
+ '</tbody>' +
2101
+ '</table>';
2102
+ };
2103
+
2104
+ var tplShortcutPara = function (lang) {
2105
+ return '<table class="editor-shortcut">' +
2106
+ '<thead>' +
2107
+ '<tr><th></th><th>' + lang.shortcut.paragraphFormatting + '</th></tr>' +
2108
+ '</thead>' +
2109
+ '<tbody>' +
2110
+ '<tr><td>⌘ + ⇧ + L</td><td>' + lang.paragraph.left + '</td></tr>' +
2111
+ '<tr><td>⌘ + ⇧ + E</td><td>' + lang.paragraph.center + '</td></tr>' +
2112
+ '<tr><td>⌘ + ⇧ + R</td><td>' + lang.paragraph.right + '</td></tr>' +
2113
+ '<tr><td>⌘ + ⇧ + J</td><td>' + lang.paragraph.justify + '</td></tr>' +
2114
+ '<tr><td>⌘ + ⇧ + NUM7</td><td>' + lang.lists.ordered + '</td></tr>' +
2115
+ '<tr><td>⌘ + ⇧ + NUM8</td><td>' + lang.lists.unordered + '</td></tr>' +
2116
+ '</tbody>' +
2117
+ '</table>';
2118
+ };
2119
+
2120
+ var tplShortcutStyle = function (lang) {
2121
+ return '<table class="editor-shortcut">' +
2122
+ '<thead>' +
2123
+ '<tr><th></th><th>' + lang.shortcut.documentStyle + '</th></tr>' +
2124
+ '</thead>' +
2125
+ '<tbody>' +
2126
+ '<tr><td>⌘ + NUM0</td><td>' + lang.style.normal + '</td></tr>' +
2127
+ '<tr><td>⌘ + NUM1</td><td>' + lang.style.h1 + '</td></tr>' +
2128
+ '<tr><td>⌘ + NUM2</td><td>' + lang.style.h2 + '</td></tr>' +
2129
+ '<tr><td>⌘ + NUM3</td><td>' + lang.style.h3 + '</td></tr>' +
2130
+ '<tr><td>⌘ + NUM4</td><td>' + lang.style.h4 + '</td></tr>' +
2131
+ '<tr><td>⌘ + NUM5</td><td>' + lang.style.h5 + '</td></tr>' +
2132
+ '<tr><td>⌘ + NUM6</td><td>' + lang.style.h6 + '</td></tr>' +
2133
+ '</tbody>' +
2134
+ '</table>';
2135
+ };
2136
+
2137
+ var tplShortcutTable = function (lang) {
2138
+ return '<table class="editor-shortcut-layout">' +
2139
+ '<tbody>' +
2140
+ '<tr><td>' + tplShortcutAction(lang) + '</td><td>' + tplShortcutText(lang) + '</td></tr>' +
2141
+ '<tr><td>' + tplShortcutStyle(lang) + '</td><td>' + tplShortcutPara(lang) + '</td></tr>' +
2142
+ '</tbody>' +
2143
+ '</table>';
2144
+ };
2145
+
2146
+ var replaceMacKeys = function (sHtml) {
2147
+ return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
2148
+ };
2149
+
2150
+ tplDialog = function (lang) {
2151
+ return '<div class="editor-dialog">' +
2152
+ '<div class="editor-image-dialog modal fade" aria-hidden="false">' +
2153
+ '<div class="modal-dialog">' +
2154
+ '<div class="modal-header">' +
2155
+ '<h3>' + lang.image.insert + '</h3>' +
2156
+ '</div>' +
2157
+ '<div class="modal-body">' +
2158
+ '<label>' + lang.image.selectFromFiles + '</label>' +
2159
+ '<input class="editor-image-input" type="file" name="files" accept="image/*" />' +
2160
+ '<label>' + lang.image.url + '</label>' +
2161
+ '<input class="editor-image-url" type="text" />' +
2162
+ '</div>' +
2163
+ '<div class="modal-footer">' +
2164
+ '<a data-dismiss="modal" href="#" class="modal-footer-btn modal-footer-btn-left">' + lang.shortcut.close + '</a>' +
2165
+ '<button href="#" class="modal-footer-btn modal-footer-btn-right editor-image-btn" disabled="disabled">' + lang.image.insert + '</button>' +
2166
+ '</div>' +
2167
+ '</div>' +
2168
+ '</div>' +
2169
+ // Link Modal
2170
+ '<div class="editor-link-dialog modal fade" aria-hidden="false">' +
2171
+ '<div class="modal-dialog">' +
2172
+ '<div class="modal-header">' +
2173
+ '<h3>' + lang.link.insert + '</h3>' +
2174
+ '</div>' +
2175
+ '<div class="modal-body">' +
2176
+ '<div class="form-group">' +
2177
+ '<label>' + lang.link.textToDisplay + '</label>' +
2178
+ '<span class="editor-link-text uneditable-input" disabled="disabled"/>' +
2179
+ '</div>' +
2180
+ '<div class="form-group">' +
2181
+ '<label>' + lang.link.url + '</label>' +
2182
+ '<input class="editor-link-url" type="text" />' +
2183
+ '</div>' +
2184
+ '</div>' +
2185
+ '<div class="modal-footer">' +
2186
+ '<a data-dismiss="modal" href="#" class="modal-footer-btn modal-footer-btn-left">' + lang.shortcut.close + '</a>' +
2187
+ '<button href="#" class="modal-footer-btn modal-footer-btn-right editor-link-btn" disabled="disabled">' + lang.link.insert + '</button>' +
2188
+ '</div>' +
2189
+ '</div>' +
2190
+ '</div>' +
2191
+ // Video Modal
2192
+ '<div class="editor-video-dialog modal fade" aria-hidden="false">' +
2193
+ '<div class="modal-dialog">' +
2194
+ '<div class="modal-header">' +
2195
+ '<h3>' + lang.video.insert + '</h3>' +
2196
+ '</div>' +
2197
+ '<div class="modal-body">' +
2198
+ '<div class="form-group">' +
2199
+ '<label>' + lang.video.url + '</label>' +
2200
+ '<input class="editor-video-url" type="text" />' +
2201
+ '<span class="form-help-block">' + lang.video.providers + '</span>' +
2202
+ '</div>' +
2203
+ '</div>' +
2204
+ '<div class="modal-footer">' +
2205
+ '<a data-dismiss="modal" href="#" class="modal-footer-btn modal-footer-btn-left">' + lang.shortcut.close + '</a>' +
2206
+ '<button href="#" class="modal-footer-btn modal-footer-btn-right editor-video-btn" disabled="disabled">' + lang.video.insert + '</button>' +
2207
+ '</div>' +
2208
+ '</div>' +
2209
+ '</div>' +
2210
+ // Help Modal
2211
+ '<div class="editor-help-dialog modal fade" aria-hidden="false">' +
2212
+ '<div class="modal-dialog">' +
2213
+ '<div class="modal-header">' +
2214
+ '<h3>' + lang.shortcut.shortcuts + '</h3>' +
2215
+ '</div>' +
2216
+ '<div class="modal-body">' +
2217
+ (agent.bMac ? tplShortcutTable(lang) : replaceMacKeys(tplShortcutTable(lang))) +
2218
+ '</div>' +
2219
+ '<div class="modal-footer">' +
2220
+ '<a data-dismiss="modal" class="modal-footer-btn" href="#">' + lang.shortcut.close + '</a>' +
2221
+ '</div>' +
2222
+ '</div>' +
2223
+ '</div>' +
2224
+ '</div>';
2225
+ };
2226
+
2227
+ tplStatusbar = '<div class="editor-resizebar"><div class="editor-icon-bar"></div><div class="editor-icon-bar"></div><div class="editor-icon-bar"></div></div>';
2228
+ /* jshint ignore:end */
2229
+
2230
+ // createTooltip
2231
+ var createTooltip = function ($container, sPlacement) {
2232
+ $container.find('button').each(function (i, elBtn) {
2233
+ var $btn = $(elBtn);
2234
+ var tplShortcut = $btn.attr(agent.bMac ? 'data-mac-shortcut': 'data-shortcut');
2235
+ if (tplShortcut) { $btn.attr('title', function (i, v) { return v + ' (' + tplShortcut + ')'; }); }
2236
+ // bootstrap tooltip on btn-group bug: https://github.com/twitter/bootstrap/issues/5687
2237
+ }).tooltip({container: 'body', trigger: 'hover', placement: sPlacement || 'top'})
2238
+ .on('click', function () {$(this).tooltip('hide'); });
2239
+ };
2240
+
2241
+ // pallete colors
2242
+ var aaColor = [
2243
+ ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
2244
+ ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
2245
+ ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
2246
+ ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
2247
+ ['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
2248
+ ['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
2249
+ ['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
2250
+ ['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
2251
+ ];
2252
+
2253
+ // createPalette
2254
+ var createPalette = function ($container) {
2255
+ $container.find('.editor-color-palette').each(function () {
2256
+ var $palette = $(this), sEvent = $palette.attr('data-target-event');
2257
+ var aPaletteContents = [];
2258
+ for (var row = 0, szRow = aaColor.length; row < szRow; row++) {
2259
+ var aColor = aaColor[row];
2260
+ var aButton = [];
2261
+ for (var col = 0, szCol = aColor.length; col < szCol; col++) {
2262
+ var sColor = aColor[col];
2263
+ aButton.push(['<button type="button" class="editor-color-btn" style="background-color:', sColor,
2264
+ ';" data-event="', sEvent,
2265
+ '" data-value="', sColor,
2266
+ '" title="', sColor,
2267
+ '" data-toggle="button" tabindex="-1"></button>'].join(''));
2268
+ }
2269
+ aPaletteContents.push('<div>' + aButton.join('') + '</div>');
2270
+ }
2271
+ $palette.html(aPaletteContents.join(''));
2272
+ });
2273
+ };
2274
+
2275
+ // createLayout
2276
+ this.createLayout = function ($holder, options) {
2277
+ var nHeight = options.height,
2278
+ nTabsize = options.tabsize,
2279
+ sDirection = options.direction,
2280
+ aToolbarSetting = options.toolbar,
2281
+ langInfo = $.editor.lang[options.lang];
2282
+
2283
+ //already created
2284
+ if ($holder.next().hasClass('editor-canvas')) { return; }
2285
+
2286
+ //01. create Editor
2287
+ var $editor = $('<div class="editor-canvas"></div>');
2288
+ $editor.data('options', options);
2289
+
2290
+ //02. statusbar
2291
+ if (nHeight > 0) {
2292
+ $('<div class="editor-statusbar">' + tplStatusbar + '</div>').prependTo($editor);
2293
+ }
2294
+
2295
+ //03. create Editable
2296
+ var $editable = $('<div class="editor-editable" contentEditable="true"></div>').prependTo($editor);
2297
+ if (nHeight) {
2298
+ $editable.height(nHeight);
2299
+ $editable.data('optionHeight', nHeight);
2300
+ }
2301
+ if (nTabsize) {
2302
+ $editable.data('tabsize', nTabsize);
2303
+ }
2304
+ if (sDirection) {
2305
+ $editable.attr('dir', sDirection);
2306
+ }
2307
+
2308
+ $editable.html(dom.html($holder) || dom.emptyPara);
2309
+ $editable.data('NoteHistory', new History());
2310
+
2311
+ //031. create codable
2312
+ $('<textarea class="editor-codable"></textarea>').prependTo($editor);
2313
+
2314
+ //032. set styleWithCSS for backColor / foreColor clearing with 'inherit'.
2315
+ setTimeout(function () { // protect FF Error: NS_ERROR_FAILURE: Failure
2316
+ document.execCommand('styleWithCSS', 0, true);
2317
+ });
2318
+
2319
+ //04. create Toolbar
2320
+ var sToolbar = '';
2321
+ for (var idx = 0, sz = aToolbarSetting.length; idx < sz; idx ++) {
2322
+ var group = aToolbarSetting[idx];
2323
+ sToolbar += '<div class="editor-' + group[0] + ' btn-group">';
2324
+ for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
2325
+ sToolbar += tplToolbarInfo[group[1][i]](langInfo);
2326
+ }
2327
+ sToolbar += '</div>';
2328
+ }
2329
+
2330
+ sToolbar = '<div class="editor-toolbar btn-toolbar">' + sToolbar + '</div>';
2331
+
2332
+ var $toolbar = $(sToolbar).prependTo($editor);
2333
+ createPalette($toolbar);
2334
+ createTooltip($toolbar, 'bottom');
2335
+
2336
+ //05. create Popover
2337
+ var $popover = $(tplPopover(langInfo)).prependTo($editor);
2338
+ createTooltip($popover);
2339
+
2340
+ //06. handle(control selection, ...)
2341
+ $(tplhandle).prependTo($editor);
2342
+
2343
+ //07. create Dialog
2344
+ var $dialog = $(tplDialog(langInfo)).prependTo($editor);
2345
+ $dialog.find('button.close, a.modal-close').click(function () {
2346
+ $(this).closest('.modal').modal('hide');
2347
+ });
2348
+
2349
+ //08. create Dropzone
2350
+ $('<div class="editor-dropzone"><div class="editor-dropzone-message"></div></div>').prependTo($editor);
2351
+
2352
+ //09. Editor/Holder switch
2353
+ $editor.insertAfter($holder);
2354
+ $holder.hide();
2355
+ };
2356
+
2357
+ // layoutInfoFromHolder
2358
+ var layoutInfoFromHolder = this.layoutInfoFromHolder = function ($holder) {
2359
+ var $editor = $holder.next();
2360
+ if (!$editor.hasClass('editor-canvas')) { return; }
2361
+
2362
+ return {
2363
+ editor: $editor,
2364
+ dropzone: $editor.find('.editor-dropzone'),
2365
+ toolbar: $editor.find('.editor-toolbar'),
2366
+ editable: $editor.find('.editor-editable'),
2367
+ codable: $editor.find('.editor-codable'),
2368
+ statusbar: $editor.find('.editor-statusbar'),
2369
+ popover: $editor.find('.editor-popover'),
2370
+ handle: $editor.find('.editor-handle'),
2371
+ dialog: $editor.find('.editor-dialog')
2372
+ };
2373
+ };
2374
+
2375
+ // removeLayout
2376
+ this.removeLayout = function ($holder) {
2377
+ var info = layoutInfoFromHolder($holder);
2378
+ if (!info) { return; }
2379
+ $holder.html(info.editable.html());
2380
+
2381
+ info.editor.remove();
2382
+ $holder.show();
2383
+ };
2384
+ };
2385
+
2386
+ var renderer = new Renderer();
2387
+ var eventHandler = new EventHandler();
2388
+
2389
+ $.editor = $.editor || {};
2390
+
2391
+ $.extend($.editor, {
2392
+ version: '0.5.1',
2393
+ lang: {
2394
+ 'en-US': {
2395
+ font: {
2396
+ bold: 'Bold',
2397
+ italic: 'Italic',
2398
+ underline: 'Underline',
2399
+ strike: 'Strike',
2400
+ clear: 'Remove Font Style',
2401
+ height: 'Line Height',
2402
+ size: 'Font Size'
2403
+ },
2404
+ image: {
2405
+ image: 'Picture',
2406
+ insert: 'Insert Image',
2407
+ resizeFull: 'Resize Full',
2408
+ resizeHalf: 'Resize Half',
2409
+ resizeQuarter: 'Resize Quarter',
2410
+ floatLeft: 'Float Left',
2411
+ floatRight: 'Float Right',
2412
+ floatNone: 'Float None',
2413
+ dragImageHere: 'Drag an image here',
2414
+ selectFromFiles: 'Select from files',
2415
+ url: 'Image URL'
2416
+ },
2417
+ link: {
2418
+ link: 'Link',
2419
+ insert: 'Insert Link',
2420
+ unlink: 'Unlink',
2421
+ edit: 'Edit',
2422
+ textToDisplay: 'Text to display',
2423
+ url: 'To what URL should this link go?'
2424
+ },
2425
+ video: {
2426
+ video: 'Video',
2427
+ videoLink: 'Video Link',
2428
+ insert: 'Insert Video',
2429
+ url: 'Video URL?',
2430
+ providers: '(YouTube, Vimeo, Vine, Instagram, or DailyMotion)'
2431
+ },
2432
+ table: {
2433
+ table: 'Table'
2434
+ },
2435
+ hr: {
2436
+ insert: 'Insert Horizontal Rule'
2437
+ },
2438
+ style: {
2439
+ style: 'Style',
2440
+ normal: 'Normal',
2441
+ blockquote: 'Quote',
2442
+ pre: 'Code',
2443
+ h1: 'Header 1',
2444
+ h2: 'Header 2',
2445
+ h3: 'Header 3',
2446
+ h4: 'Header 4',
2447
+ h5: 'Header 5',
2448
+ h6: 'Header 6'
2449
+ },
2450
+ lists: {
2451
+ unordered: 'Unordered list',
2452
+ ordered: 'Ordered list'
2453
+ },
2454
+ options: {
2455
+ help: 'Help',
2456
+ fullscreen: 'Full Screen',
2457
+ codeview: 'Code View'
2458
+ },
2459
+ paragraph: {
2460
+ paragraph: 'Paragraph',
2461
+ outdent: 'Outdent',
2462
+ indent: 'Indent',
2463
+ left: 'Align left',
2464
+ center: 'Align center',
2465
+ right: 'Align right',
2466
+ justify: 'Justify full'
2467
+ },
2468
+ color: {
2469
+ recent: 'Recent Color',
2470
+ more: 'More Color',
2471
+ background: 'BackColor',
2472
+ foreground: 'FontColor',
2473
+ transparent: 'Transparent',
2474
+ setTransparent: 'Set transparent',
2475
+ reset: 'Reset',
2476
+ resetToDefault: 'Reset to default'
2477
+ },
2478
+ shortcut: {
2479
+ shortcuts: 'Keyboard shortcuts',
2480
+ close: 'Close',
2481
+ textFormatting: 'Text formatting',
2482
+ action: 'Action',
2483
+ paragraphFormatting: 'Paragraph formatting',
2484
+ documentStyle: 'Document Style'
2485
+ },
2486
+ history: {
2487
+ undo: 'Undo',
2488
+ redo: 'Redo'
2489
+ }
2490
+ }
2491
+ }
2492
+ });
2493
+
2494
+ /**
2495
+ * extend jquery fn
2496
+ */
2497
+ $.fn.extend({
2498
+ // create Editor Layout and attach Key and Mouse Event
2499
+ editor: function (options) {
2500
+ options = $.extend({
2501
+ toolbar: [
2502
+ ['style', ['style']],
2503
+ ['font', ['bold', 'italic', 'underline', 'clear']],
2504
+ ['fontsize', ['fontsize']],
2505
+ ['color', ['color']],
2506
+ ['para', ['ul', 'ol', 'paragraph']],
2507
+ ['height', ['height']],
2508
+ ['table', ['table']],
2509
+ ['insert', ['link', 'picture', 'video']],
2510
+ ['view', ['fullscreen', 'codeview']],
2511
+ ['help', ['help']]
2512
+ ],
2513
+ lang: 'en-US'
2514
+ }, options);
2515
+
2516
+ this.each(function (idx, elHolder) {
2517
+ var $holder = $(elHolder);
2518
+
2519
+ // createLayout with options
2520
+ renderer.createLayout($holder, options);
2521
+
2522
+ var info = renderer.layoutInfoFromHolder($holder);
2523
+ eventHandler.attach(info, options);
2524
+
2525
+ // Textarea auto filling the code before form submit.
2526
+ if (dom.isTextarea($holder[0])) {
2527
+ $holder.closest('form').submit(function () {
2528
+ $holder.html($holder.code());
2529
+ });
2530
+ }
2531
+ });
2532
+
2533
+ if (this.first() && options.focus) { // focus on first editable element
2534
+ var info = renderer.layoutInfoFromHolder(this.first());
2535
+ info.editable.focus();
2536
+ }
2537
+ if (this.length > 0 && options.oninit) { // callback on init
2538
+ options.oninit();
2539
+ }
2540
+ },
2541
+ // get the HTML contents of editor or set the HTML contents of editor.
2542
+ code: function (sHTML) {
2543
+ // get the HTML contents
2544
+ if (sHTML === undefined) {
2545
+ var $holder = this.first();
2546
+ if ($holder.length === 0) { return; }
2547
+ var info = renderer.layoutInfoFromHolder($holder);
2548
+ if (!!(info && info.editable)) {
2549
+ var bCodeview = info.editor.hasClass('codeview');
2550
+ if (bCodeview && agent.bCodeMirror) {
2551
+ info.codable.data('cmEditor').save();
2552
+ }
2553
+ return bCodeview ? info.codable.val() : info.editable.html();
2554
+ }
2555
+ return $holder.html();
2556
+ }
2557
+
2558
+ // set the HTML contents
2559
+ this.each(function (i, elHolder) {
2560
+ var info = renderer.layoutInfoFromHolder($(elHolder));
2561
+ if (info && info.editable) { info.editable.html(sHTML); }
2562
+ });
2563
+ },
2564
+ // destroy Editor Layout and dettach Key and Mouse Event
2565
+ destroy: function () {
2566
+ this.each(function (idx, elHolder) {
2567
+ var $holder = $(elHolder);
2568
+
2569
+ var info = renderer.layoutInfoFromHolder($holder);
2570
+ if (!info || !info.editable) { return; }
2571
+ eventHandler.dettach(info);
2572
+ renderer.removeLayout($holder);
2573
+ });
2574
+ },
2575
+ // inner object for test
2576
+ editorInner: function () {
2577
+ return { dom: dom, list: list, func: func, range: range };
2578
+ }
2579
+ });
2580
+ }));