bitsnote-assets 0.0.3 → 0.0.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 99c7af7842f9483f2ea9dd9e1a75a0990584edf8
4
- data.tar.gz: 25a9e17f68ad62fe6d90d8c6424dc07730a5289c
3
+ metadata.gz: 4a8e47c3f21a2dee68fa6f6c563851a79f832287
4
+ data.tar.gz: da87e01b26d2ff9fe93afa509936bf2bf408cf72
5
5
  SHA512:
6
- metadata.gz: e4b0f1ed7668fb59c0538bd56a10dfba097e575c5aa2ca6f38bdfbc6ce790c581f2ba40ee0cb12d7afa0ad6d1ce8b76404b89219f428dcba7808688766c7e330
7
- data.tar.gz: 05e7b1b064c83605d824907a0caad16db1670637db3e14021c5d0babce60ffc4fb1e15100078bc7bf990cfc10814a6c5d799983d19c128426c3851cbd7129609
6
+ metadata.gz: fc6be01770abe01b4a329bf76ab7f99c82357a33963c135c5328f21b983f9c65b84bf305a66a96e5d68eb8e1ed87b5ab4ad302c308c8ba97e919c8c9a8788b82
7
+ data.tar.gz: 663ebdea058b3586c592695ff02cc2b272b3054d9677da5694c7242acab6e47a7fdf267a854d9e6f5bd28c06b9ddf6877502fc146fbd830131a35bebbe681472
@@ -1,15 +1,3 @@
1
1
  = BitsnoteAssets
2
2
 
3
3
  This project rocks and uses MIT-LICENSE.
4
-
5
- == Assets ==
6
- Angular.js 1.2.23
7
- Ionic 1.0.0-beta.11 (including angular-ui router)
8
- Bootstrap 3.2
9
- jQuery-UI 1.11.1
10
-
11
- == CDN ==
12
-
13
- jQuery-ui
14
- Angular
15
- Bootstrap
@@ -0,0 +1,4765 @@
1
+ /**
2
+ * Super simple wysiwyg editor on Bootstrap v0.5.6
3
+ * http://hackerwins.github.io/summernote/
4
+ *
5
+ * summernote.js
6
+ * Copyright 2013 Alan Hong. and outher contributors
7
+ * summernote may be freely distributed under the MIT license./
8
+ *
9
+ * Date: 2014-08-27T07:06Z
10
+ */
11
+ (function (factory) {
12
+ /* global define */
13
+ if (typeof define === 'function' && define.amd) {
14
+ // AMD. Register as an anonymous module.
15
+ define(['jquery'], factory);
16
+ } else {
17
+ // Browser globals: jQuery
18
+ factory(window.jQuery);
19
+ }
20
+ }(function ($) {
21
+
22
+
23
+
24
+ if ('function' !== typeof Array.prototype.reduce) {
25
+ /**
26
+ * Array.prototype.reduce fallback
27
+ *
28
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
29
+ */
30
+ Array.prototype.reduce = function (callback, optInitialValue) {
31
+ var idx, value, length = this.length >>> 0, isValueSet = false;
32
+ if (1 < arguments.length) {
33
+ value = optInitialValue;
34
+ isValueSet = true;
35
+ }
36
+ for (idx = 0; length > idx; ++idx) {
37
+ if (this.hasOwnProperty(idx)) {
38
+ if (isValueSet) {
39
+ value = callback(value, this[idx], idx, this);
40
+ } else {
41
+ value = this[idx];
42
+ isValueSet = true;
43
+ }
44
+ }
45
+ }
46
+ if (!isValueSet) {
47
+ throw new TypeError('Reduce of empty array with no initial value');
48
+ }
49
+ return value;
50
+ };
51
+ }
52
+
53
+ var isSupportAmd = typeof define === 'function' && define.amd;
54
+
55
+ /**
56
+ * returns whether font is installed or not.
57
+ * @param {String} fontName
58
+ * @return {Boolean}
59
+ */
60
+ var isFontInstalled = function (fontName) {
61
+ var testFontName = fontName === 'Comic Sans MS' ? 'Courier New' : 'Comic Sans MS';
62
+ var $tester = $('<div>').css({
63
+ position: 'absolute',
64
+ left: '-9999px',
65
+ top: '-9999px',
66
+ fontSize: '200px'
67
+ }).text('mmmmmmmmmwwwwwww').appendTo(document.body);
68
+
69
+ var originalWidth = $tester.css('fontFamily', testFontName).width();
70
+ var width = $tester.css('fontFamily', fontName + ',' + testFontName).width();
71
+
72
+ $tester.remove();
73
+
74
+ return originalWidth !== width;
75
+ };
76
+
77
+ /**
78
+ * Object which check platform and agent
79
+ */
80
+ var agent = {
81
+ isMac: navigator.appVersion.indexOf('Mac') > -1,
82
+ isMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1,
83
+ isFF: navigator.userAgent.indexOf('Firefox') > -1,
84
+ jqueryVersion: parseFloat($.fn.jquery),
85
+ isSupportAmd: isSupportAmd,
86
+ hasCodeMirror: isSupportAmd ? require.specified('CodeMirror') : !!window.CodeMirror,
87
+ isFontInstalled: isFontInstalled,
88
+ isW3CRangeSupport: !!document.createRange
89
+ };
90
+
91
+ /**
92
+ * func utils (for high-order func's arg)
93
+ */
94
+ var func = (function () {
95
+ var eq = function (elA) {
96
+ return function (elB) {
97
+ return elA === elB;
98
+ };
99
+ };
100
+
101
+ var eq2 = function (elA, elB) {
102
+ return elA === elB;
103
+ };
104
+
105
+ var ok = function () {
106
+ return true;
107
+ };
108
+
109
+ var fail = function () {
110
+ return false;
111
+ };
112
+
113
+ var not = function (f) {
114
+ return function () {
115
+ return !f.apply(f, arguments);
116
+ };
117
+ };
118
+
119
+ var self = function (a) {
120
+ return a;
121
+ };
122
+
123
+ var idCounter = 0;
124
+
125
+ /**
126
+ * generate a globally-unique id
127
+ *
128
+ * @param {String} [prefix]
129
+ */
130
+ var uniqueId = function (prefix) {
131
+ var id = ++idCounter + '';
132
+ return prefix ? prefix + id : id;
133
+ };
134
+
135
+ /**
136
+ * returns bnd (bounds) from rect
137
+ *
138
+ * - IE Compatability Issue: http://goo.gl/sRLOAo
139
+ * - Scroll Issue: http://goo.gl/sNjUc
140
+ *
141
+ * @param {Rect} rect
142
+ * @return {Object} bounds
143
+ * @return {Number} bounds.top
144
+ * @return {Number} bounds.left
145
+ * @return {Number} bounds.width
146
+ * @return {Number} bounds.height
147
+ */
148
+ var rect2bnd = function (rect) {
149
+ var $document = $(document);
150
+ return {
151
+ top: rect.top + $document.scrollTop(),
152
+ left: rect.left + $document.scrollLeft(),
153
+ width: rect.right - rect.left,
154
+ height: rect.bottom - rect.top
155
+ };
156
+ };
157
+
158
+ /**
159
+ * returns a copy of the object where the keys have become the values and the values the keys.
160
+ * @param {Object} obj
161
+ * @return {Object}
162
+ */
163
+ var invertObject = function (obj) {
164
+ var inverted = {};
165
+ for (var key in obj) {
166
+ if (obj.hasOwnProperty(key)) {
167
+ inverted[obj[key]] = key;
168
+ }
169
+ }
170
+ return inverted;
171
+ };
172
+
173
+ return {
174
+ eq: eq,
175
+ eq2: eq2,
176
+ ok: ok,
177
+ fail: fail,
178
+ not: not,
179
+ self: self,
180
+ uniqueId: uniqueId,
181
+ rect2bnd: rect2bnd,
182
+ invertObject: invertObject
183
+ };
184
+ })();
185
+
186
+ /**
187
+ * list utils
188
+ */
189
+ var list = (function () {
190
+ /**
191
+ * returns the first element of an array.
192
+ * @param {Array} array
193
+ */
194
+ var head = function (array) {
195
+ return array[0];
196
+ };
197
+
198
+ /**
199
+ * returns the last element of an array.
200
+ * @param {Array} array
201
+ */
202
+ var last = function (array) {
203
+ return array[array.length - 1];
204
+ };
205
+
206
+ /**
207
+ * returns everything but the last entry of the array.
208
+ * @param {Array} array
209
+ */
210
+ var initial = function (array) {
211
+ return array.slice(0, array.length - 1);
212
+ };
213
+
214
+ /**
215
+ * returns the rest of the elements in an array.
216
+ * @param {Array} array
217
+ */
218
+ var tail = function (array) {
219
+ return array.slice(1);
220
+ };
221
+
222
+ /**
223
+ * returns next item.
224
+ * @param {Array} array
225
+ */
226
+ var next = function (array, item) {
227
+ var idx = array.indexOf(item);
228
+ if (idx === -1) { return null; }
229
+
230
+ return array[idx + 1];
231
+ };
232
+
233
+ /**
234
+ * returns prev item.
235
+ * @param {Array} array
236
+ */
237
+ var prev = function (array, item) {
238
+ var idx = array.indexOf(item);
239
+ if (idx === -1) { return null; }
240
+
241
+ return array[idx - 1];
242
+ };
243
+
244
+ var all = function (array, pred) {
245
+ for (var idx = 0, sz = array.length; idx < sz; idx ++) {
246
+ if (!pred(array[idx])) {
247
+ return false;
248
+ }
249
+ }
250
+ return true;
251
+ };
252
+
253
+ var contains = function (array, item) {
254
+ return array.indexOf(item) !== -1;
255
+ };
256
+
257
+ /**
258
+ * get sum from a list
259
+ * @param {Array} array - array
260
+ * @param {Function} fn - iterator
261
+ */
262
+ var sum = function (array, fn) {
263
+ fn = fn || func.self;
264
+ return array.reduce(function (memo, v) {
265
+ return memo + fn(v);
266
+ }, 0);
267
+ };
268
+
269
+ /**
270
+ * returns a copy of the collection with array type.
271
+ * @param {Collection} collection - collection eg) node.childNodes, ...
272
+ */
273
+ var from = function (collection) {
274
+ var result = [], idx = -1, length = collection.length;
275
+ while (++idx < length) {
276
+ result[idx] = collection[idx];
277
+ }
278
+ return result;
279
+ };
280
+
281
+ /**
282
+ * cluster elements by predicate function.
283
+ * @param {Array} array - array
284
+ * @param {Function} fn - predicate function for cluster rule
285
+ * @param {Array[]}
286
+ */
287
+ var clusterBy = function (array, fn) {
288
+ if (!array.length) { return []; }
289
+ var aTail = tail(array);
290
+ return aTail.reduce(function (memo, v) {
291
+ var aLast = last(memo);
292
+ if (fn(last(aLast), v)) {
293
+ aLast[aLast.length] = v;
294
+ } else {
295
+ memo[memo.length] = [v];
296
+ }
297
+ return memo;
298
+ }, [[head(array)]]);
299
+ };
300
+
301
+ /**
302
+ * returns a copy of the array with all falsy values removed
303
+ * @param {Array} array - array
304
+ * @param {Function} fn - predicate function for cluster rule
305
+ */
306
+ var compact = function (array) {
307
+ var aResult = [];
308
+ for (var idx = 0, sz = array.length; idx < sz; idx ++) {
309
+ if (array[idx]) { aResult.push(array[idx]); }
310
+ }
311
+ return aResult;
312
+ };
313
+
314
+ var unique = function (array) {
315
+ var results = [];
316
+
317
+ for (var idx = 0, sz = array.length; idx < sz; idx ++) {
318
+ if (results.indexOf(array[idx]) === -1) {
319
+ results.push(array[idx]);
320
+ }
321
+ }
322
+
323
+ return results;
324
+ };
325
+
326
+ return { head: head, last: last, initial: initial, tail: tail,
327
+ prev: prev, next: next, contains: contains,
328
+ all: all, sum: sum, from: from,
329
+ clusterBy: clusterBy, compact: compact, unique: unique };
330
+ })();
331
+
332
+
333
+ var NBSP_CHAR = String.fromCharCode(160);
334
+ var ZERO_WIDTH_NBSP_CHAR = '\ufeff';
335
+
336
+ /**
337
+ * Dom functions
338
+ */
339
+ var dom = (function () {
340
+ /**
341
+ * returns whether node is `note-editable` or not.
342
+ *
343
+ * @param {Node} node
344
+ * @return {Boolean}
345
+ */
346
+ var isEditable = function (node) {
347
+ return node && $(node).hasClass('note-editable');
348
+ };
349
+
350
+ var isControlSizing = function (node) {
351
+ return node && $(node).hasClass('note-control-sizing');
352
+ };
353
+
354
+ /**
355
+ * build layoutInfo from $editor(.note-editor)
356
+ *
357
+ * @param {jQuery} $editor
358
+ * @return {Object}
359
+ */
360
+ var buildLayoutInfo = function ($editor) {
361
+ var makeFinder;
362
+
363
+ // air mode
364
+ if ($editor.hasClass('note-air-editor')) {
365
+ var id = list.last($editor.attr('id').split('-'));
366
+ makeFinder = function (sIdPrefix) {
367
+ return function () { return $(sIdPrefix + id); };
368
+ };
369
+
370
+ return {
371
+ editor: function () { return $editor; },
372
+ editable: function () { return $editor; },
373
+ popover: makeFinder('#note-popover-'),
374
+ handle: makeFinder('#note-handle-'),
375
+ dialog: makeFinder('#note-dialog-')
376
+ };
377
+
378
+ // frame mode
379
+ } else {
380
+ makeFinder = function (sClassName) {
381
+ return function () { return $editor.find(sClassName); };
382
+ };
383
+ return {
384
+ editor: function () { return $editor; },
385
+ dropzone: makeFinder('.note-dropzone'),
386
+ toolbar: makeFinder('.note-toolbar'),
387
+ editable: makeFinder('.note-editable'),
388
+ codable: makeFinder('.note-codable'),
389
+ statusbar: makeFinder('.note-statusbar'),
390
+ popover: makeFinder('.note-popover'),
391
+ handle: makeFinder('.note-handle'),
392
+ dialog: makeFinder('.note-dialog')
393
+ };
394
+ }
395
+ };
396
+
397
+ /**
398
+ * returns predicate which judge whether nodeName is same
399
+ * @param {String} sNodeName
400
+ */
401
+ var makePredByNodeName = function (sNodeName) {
402
+ sNodeName = sNodeName.toUpperCase();
403
+ return function (node) {
404
+ return node && node.nodeName.toUpperCase() === sNodeName;
405
+ };
406
+ };
407
+
408
+ var isText = function (node) {
409
+ return node && node.nodeType === 3;
410
+ };
411
+
412
+ /**
413
+ * ex) br, col, embed, hr, img, input, ...
414
+ * @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
415
+ */
416
+ var isVoid = function (node) {
417
+ return node && /^BR|^IMG|^HR/.test(node.nodeName.toUpperCase());
418
+ };
419
+
420
+ var isPara = function (node) {
421
+ if (isEditable(node)) {
422
+ return false;
423
+ }
424
+
425
+ // Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
426
+ return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
427
+ };
428
+
429
+ var isInline = function (node) {
430
+ return !isBodyContainer(node) && !isPara(node);
431
+ };
432
+
433
+ var isList = function (node) {
434
+ return node && /^UL|^OL/.test(node.nodeName.toUpperCase());
435
+ };
436
+
437
+ var isCell = function (node) {
438
+ return node && /^TD|^TH/.test(node.nodeName.toUpperCase());
439
+ };
440
+
441
+ var isBodyContainer = function (node) {
442
+ return isCell(node) || isEditable(node);
443
+ };
444
+
445
+ /**
446
+ * returns whether node is textNode on bodyContainer or not.
447
+ *
448
+ * @param {Node} node
449
+ */
450
+ var isBodyText = function (node) {
451
+ return isText(node) && isBodyContainer(node.parentNode);
452
+ };
453
+
454
+ var isParaInline = function (node) {
455
+ return isInline(node) && !!ancestor(node, isPara);
456
+ };
457
+
458
+ var isBodyInline = function (node) {
459
+ return isInline(node) && !ancestor(node, isPara);
460
+ };
461
+
462
+ /**
463
+ * blank HTML for cursor position
464
+ */
465
+ var blankHTML = agent.isMSIE ? '&nbsp;' : '<br>';
466
+
467
+ /**
468
+ * returns #text's text size or element's childNodes size
469
+ *
470
+ * @param {Node} node
471
+ */
472
+ var nodeLength = function (node) {
473
+ if (isText(node)) {
474
+ return node.nodeValue.length;
475
+ }
476
+
477
+ return node.childNodes.length;
478
+ };
479
+
480
+ /**
481
+ * padding blankHTML if node is empty (for cursor position)
482
+ */
483
+ var paddingBlankHTML = function (node) {
484
+ if (!isVoid(node) && !nodeLength(node)) {
485
+ node.innerHTML = blankHTML;
486
+ }
487
+ };
488
+
489
+ /**
490
+ * find nearest ancestor predicate hit
491
+ *
492
+ * @param {Node} node
493
+ * @param {Function} pred - predicate function
494
+ */
495
+ var ancestor = function (node, pred) {
496
+ while (node) {
497
+ if (pred(node)) { return node; }
498
+ if (isEditable(node)) { break; }
499
+
500
+ node = node.parentNode;
501
+ }
502
+ return null;
503
+ };
504
+
505
+ /**
506
+ * returns new array of ancestor nodes (until predicate hit).
507
+ *
508
+ * @param {Node} node
509
+ * @param {Function} [optional] pred - predicate function
510
+ */
511
+ var listAncestor = function (node, pred) {
512
+ pred = pred || func.fail;
513
+
514
+ var ancestors = [];
515
+ ancestor(node, function (el) {
516
+ if (!isEditable(el)) {
517
+ ancestors.push(el);
518
+ }
519
+
520
+ return pred(el);
521
+ });
522
+ return ancestors;
523
+ };
524
+
525
+ /**
526
+ * returns common ancestor node between two nodes.
527
+ *
528
+ * @param {Node} nodeA
529
+ * @param {Node} nodeB
530
+ */
531
+ var commonAncestor = function (nodeA, nodeB) {
532
+ var ancestors = listAncestor(nodeA);
533
+ for (var n = nodeB; n; n = n.parentNode) {
534
+ if ($.inArray(n, ancestors) > -1) { return n; }
535
+ }
536
+ return null; // difference document area
537
+ };
538
+
539
+ /**
540
+ * listing all Nodes between two nodes.
541
+ * FIXME: nodeA and nodeB must be sorted, use comparePoints later.
542
+ *
543
+ * @param {Node} nodeA
544
+ * @param {Node} nodeB
545
+ */
546
+ var listBetween = function (nodeA, nodeB) {
547
+ var nodes = [];
548
+
549
+ var isStart = false, isEnd = false;
550
+
551
+ // DFS(depth first search) with commonAcestor.
552
+ (function fnWalk(node) {
553
+ if (!node) { return; } // traverse fisnish
554
+ if (node === nodeA) { isStart = true; } // start point
555
+ if (isStart && !isEnd) { nodes.push(node); } // between
556
+ if (node === nodeB) { isEnd = true; return; } // end point
557
+
558
+ for (var idx = 0, sz = node.childNodes.length; idx < sz; idx++) {
559
+ fnWalk(node.childNodes[idx]);
560
+ }
561
+ })(commonAncestor(nodeA, nodeB));
562
+
563
+ return nodes;
564
+ };
565
+
566
+ /**
567
+ * listing all previous siblings (until predicate hit).
568
+ *
569
+ * @param {Node} node
570
+ * @param {Function} [optional] pred - predicate function
571
+ */
572
+ var listPrev = function (node, pred) {
573
+ pred = pred || func.fail;
574
+
575
+ var nodes = [];
576
+ while (node) {
577
+ if (pred(node)) { break; }
578
+ nodes.push(node);
579
+ node = node.previousSibling;
580
+ }
581
+ return nodes;
582
+ };
583
+
584
+ /**
585
+ * listing next siblings (until predicate hit).
586
+ *
587
+ * @param {Node} node
588
+ * @param {Function} [pred] - predicate function
589
+ */
590
+ var listNext = function (node, pred) {
591
+ pred = pred || func.fail;
592
+
593
+ var nodes = [];
594
+ while (node) {
595
+ if (pred(node)) { break; }
596
+ nodes.push(node);
597
+ node = node.nextSibling;
598
+ }
599
+ return nodes;
600
+ };
601
+
602
+ /**
603
+ * listing descendant nodes
604
+ *
605
+ * @param {Node} node
606
+ * @param {Function} [pred] - predicate function
607
+ */
608
+ var listDescendant = function (node, pred) {
609
+ var descendents = [];
610
+ pred = pred || func.ok;
611
+
612
+ // start DFS(depth first search) with node
613
+ (function fnWalk(current) {
614
+ if (node !== current && pred(current)) {
615
+ descendents.push(current);
616
+ }
617
+ for (var idx = 0, sz = current.childNodes.length; idx < sz; idx++) {
618
+ fnWalk(current.childNodes[idx]);
619
+ }
620
+ })(node);
621
+
622
+ return descendents;
623
+ };
624
+
625
+ /**
626
+ * wrap node with new tag.
627
+ *
628
+ * @param {Node} node
629
+ * @param {Node} tagName of wrapper
630
+ * @return {Node} - wrapper
631
+ */
632
+ var wrap = function (node, wrapperName) {
633
+ var parent = node.parentNode;
634
+ var wrapper = $('<' + wrapperName + '>')[0];
635
+
636
+ parent.insertBefore(wrapper, node);
637
+ wrapper.appendChild(node);
638
+
639
+ return wrapper;
640
+ };
641
+
642
+ /**
643
+ * insert node after preceding
644
+ *
645
+ * @param {Node} node
646
+ * @param {Node} preceding - predicate function
647
+ */
648
+ var insertAfter = function (node, preceding) {
649
+ var next = preceding.nextSibling, parent = preceding.parentNode;
650
+ if (next) {
651
+ parent.insertBefore(node, next);
652
+ } else {
653
+ parent.appendChild(node);
654
+ }
655
+ return node;
656
+ };
657
+
658
+ /**
659
+ * append elements.
660
+ *
661
+ * @param {Node} node
662
+ * @param {Collection} aChild
663
+ */
664
+ var appendChildNodes = function (node, aChild) {
665
+ $.each(aChild, function (idx, child) {
666
+ node.appendChild(child);
667
+ });
668
+ return node;
669
+ };
670
+
671
+ var isLeftEdgePoint = function (boundaryPoint) {
672
+ return boundaryPoint.offset === 0;
673
+ };
674
+
675
+ var isRightEdgePoint = function (boundaryPoint) {
676
+ return boundaryPoint.offset === nodeLength(boundaryPoint.node);
677
+ };
678
+
679
+ /**
680
+ * returns whether boundaryPoint is edge or not.
681
+ *
682
+ * @param {BoundaryPoint} boundaryPoitn
683
+ * @return {Boolean}
684
+ */
685
+ var isEdgePoint = function (boundaryPoint) {
686
+ return boundaryPoint.offset === 0 || isRightEdgePoint(boundaryPoint);
687
+ };
688
+
689
+ /**
690
+ * returns whether node is right edge of ancestor or not.
691
+ *
692
+ * @param {Node} node
693
+ * @param {Node} ancestor
694
+ * @return {Boolean}
695
+ */
696
+ var isRightEdgeOf = function (node, ancestor) {
697
+ while (node && node !== ancestor) {
698
+ if (position(node) !== nodeLength(node.parentNode) - 1) {
699
+ return false;
700
+ }
701
+ node = node.parentNode;
702
+ }
703
+
704
+ return true;
705
+ };
706
+
707
+ /**
708
+ * returns offset from parent.
709
+ *
710
+ * @param {Node} node
711
+ */
712
+ var position = function (node) {
713
+ var offset = 0;
714
+ while ((node = node.previousSibling)) { offset += 1; }
715
+ return offset;
716
+ };
717
+
718
+ var hasChildren = function (node) {
719
+ return node && node.childNodes && node.childNodes.length;
720
+ };
721
+
722
+ /**
723
+ * returns previous boundaryPoint
724
+ *
725
+ * @param {BoundaryPoint} point
726
+ * @param {Boolean} isSkipInnerOffset
727
+ * @return {BoundaryPoint}
728
+ */
729
+ var prevPoint = function (point, isSkipInnerOffset) {
730
+ var node, offset;
731
+
732
+ if (point.offset === 0) {
733
+ if (isEditable(point.node)) {
734
+ return null;
735
+ }
736
+
737
+ node = point.node.parentNode;
738
+ offset = position(point.node);
739
+ } else if (hasChildren(point.node)) {
740
+ node = point.node.childNodes[offset - 1];
741
+ offset = nodeLength(node);
742
+ } else {
743
+ node = point.node;
744
+ offset = isSkipInnerOffset ? 0 : point.offset - 1;
745
+ }
746
+
747
+ return {
748
+ node: node,
749
+ offset: offset
750
+ };
751
+ };
752
+
753
+ /**
754
+ * returns next boundaryPoint
755
+ *
756
+ * @param {BoundaryPoint} point
757
+ * @param {Boolean} isSkipInnerOffset
758
+ * @return {BoundaryPoint}
759
+ */
760
+ var nextPoint = function (point, isSkipInnerOffset) {
761
+ var node, offset;
762
+
763
+ if (nodeLength(point.node) === point.offset) {
764
+ if (isEditable(point.node)) {
765
+ return null;
766
+ }
767
+
768
+ node = point.node.parentNode;
769
+ offset = position(point.node) + 1;
770
+ } else if (hasChildren(point.node)) {
771
+ node = point.node.childNodes[point.offset];
772
+ offset = 0;
773
+ } else {
774
+ node = point.node;
775
+ offset = isSkipInnerOffset ? nodeLength(point.node) : point.offset + 1;
776
+ }
777
+
778
+ return {
779
+ node: node,
780
+ offset: offset
781
+ };
782
+ };
783
+
784
+ /**
785
+ * returns whether pointA and pointB is same or not.
786
+ *
787
+ * @param {BoundaryPoint} pointA
788
+ * @param {BoundaryPoint} pointB
789
+ */
790
+ var isSamePoint = function (pointA, pointB) {
791
+ return pointA.node === pointB.node && pointA.offset === pointB.offset;
792
+ };
793
+
794
+ /**
795
+ * @param {BoundaryPoint} point
796
+ * @param {Function} pred
797
+ * @return {BoundaryPoint}
798
+ */
799
+ var prevPointUntil = function (point, pred) {
800
+ while (point) {
801
+ if (pred(point)) {
802
+ return point;
803
+ }
804
+
805
+ point = prevPoint(point);
806
+ }
807
+
808
+ return null;
809
+ };
810
+
811
+ /**
812
+ * @param {BoundaryPoint} startPoint
813
+ * @param {BoundaryPoint} endPoint
814
+ * @param {Function} handler
815
+ * @param {Boolean} isSkipInnerOffset
816
+ */
817
+ var walkPoint = function (startPoint, endPoint, handler, isSkipInnerOffset) {
818
+ var point = startPoint;
819
+
820
+ while (point) {
821
+ handler(point);
822
+
823
+ if (isSamePoint(point, endPoint)) {
824
+ break;
825
+ }
826
+
827
+ var isSkipOffset = isSkipInnerOffset && startPoint.node !== point.node;
828
+ point = nextPoint(point, isSkipOffset);
829
+ }
830
+ };
831
+
832
+ /**
833
+ * return offsetPath(array of offset) from ancestor
834
+ *
835
+ * @param {Node} ancestor - ancestor node
836
+ * @param {Node} node
837
+ */
838
+ var makeOffsetPath = function (ancestor, node) {
839
+ var ancestors = list.initial(listAncestor(node, func.eq(ancestor)));
840
+ return $.map(ancestors, position).reverse();
841
+ };
842
+
843
+ /**
844
+ * return element from offsetPath(array of offset)
845
+ *
846
+ * @param {Node} ancestor - ancestor node
847
+ * @param {array} aOffset - offsetPath
848
+ */
849
+ var fromOffsetPath = function (ancestor, aOffset) {
850
+ var current = ancestor;
851
+ for (var i = 0, sz = aOffset.length; i < sz; i++) {
852
+ current = current.childNodes[aOffset[i]];
853
+ }
854
+ return current;
855
+ };
856
+
857
+ /**
858
+ * split element or #text
859
+ *
860
+ * @param {BoundaryPoint} point
861
+ * @return {Node} right node of boundaryPoint
862
+ */
863
+ var splitNode = function (point) {
864
+ // split #text
865
+ if (isText(point.node)) {
866
+ // edge case
867
+ if (isLeftEdgePoint(point)) {
868
+ return point.node;
869
+ } else if (isRightEdgePoint(point)) {
870
+ return point.node.nextSibling;
871
+ }
872
+
873
+ return point.node.splitText(point.offset);
874
+ }
875
+
876
+ // split element
877
+ var childNode = point.node.childNodes[point.offset];
878
+ var clone = insertAfter(point.node.cloneNode(false), point.node);
879
+ appendChildNodes(clone, listNext(childNode));
880
+
881
+ paddingBlankHTML(point.node);
882
+ paddingBlankHTML(clone);
883
+
884
+ return clone;
885
+ };
886
+
887
+ /**
888
+ * split tree by point
889
+ *
890
+ * @param {Node} root - split root
891
+ * @param {BoundaryPoint} point
892
+ * @return {Node} right node of boundaryPoint
893
+ */
894
+ var splitTree = function (root, point) {
895
+ // ex) [#text, <span>, <p>]
896
+ var ancestors = listAncestor(point.node, func.eq(root));
897
+
898
+ if (!ancestors.length) {
899
+ return null;
900
+ } else if (ancestors.length === 1) {
901
+ return splitNode(point);
902
+ }
903
+
904
+ return ancestors.reduce(function (node, parent) {
905
+ var clone = insertAfter(parent.cloneNode(false), parent);
906
+
907
+ if (node === point.node) {
908
+ node = splitNode(point);
909
+ }
910
+
911
+ appendChildNodes(clone, listNext(node));
912
+
913
+ paddingBlankHTML(parent);
914
+ paddingBlankHTML(clone);
915
+ return clone;
916
+ });
917
+ };
918
+
919
+ var createText = function (text) {
920
+ return document.createTextNode(text);
921
+ };
922
+
923
+ /**
924
+ * remove node, (isRemoveChild: remove child or not)
925
+ * @param {Node} node
926
+ * @param {Boolean} isRemoveChild
927
+ */
928
+ var remove = function (node, isRemoveChild) {
929
+ if (!node || !node.parentNode) { return; }
930
+ if (node.removeNode) { return node.removeNode(isRemoveChild); }
931
+
932
+ var parent = node.parentNode;
933
+ if (!isRemoveChild) {
934
+ var nodes = [];
935
+ var i, sz;
936
+ for (i = 0, sz = node.childNodes.length; i < sz; i++) {
937
+ nodes.push(node.childNodes[i]);
938
+ }
939
+
940
+ for (i = 0, sz = nodes.length; i < sz; i++) {
941
+ parent.insertBefore(nodes[i], node);
942
+ }
943
+ }
944
+
945
+ parent.removeChild(node);
946
+ };
947
+
948
+ var isTextarea = makePredByNodeName('TEXTAREA');
949
+
950
+ var html = function ($node) {
951
+ return isTextarea($node[0]) ? $node.val() : $node.html();
952
+ };
953
+
954
+ return {
955
+ NBSP_CHAR: NBSP_CHAR,
956
+ ZERO_WIDTH_NBSP_CHAR: ZERO_WIDTH_NBSP_CHAR,
957
+ blank: blankHTML,
958
+ emptyPara: '<p>' + blankHTML + '</p>',
959
+ isEditable: isEditable,
960
+ isControlSizing: isControlSizing,
961
+ buildLayoutInfo: buildLayoutInfo,
962
+ isText: isText,
963
+ isPara: isPara,
964
+ isInline: isInline,
965
+ isBodyText: isBodyText,
966
+ isBodyInline: isBodyInline,
967
+ isParaInline: isParaInline,
968
+ isList: isList,
969
+ isTable: makePredByNodeName('TABLE'),
970
+ isCell: isCell,
971
+ isBodyContainer: isBodyContainer,
972
+ isAnchor: makePredByNodeName('A'),
973
+ isDiv: makePredByNodeName('DIV'),
974
+ isLi: makePredByNodeName('LI'),
975
+ isSpan: makePredByNodeName('SPAN'),
976
+ isB: makePredByNodeName('B'),
977
+ isU: makePredByNodeName('U'),
978
+ isS: makePredByNodeName('S'),
979
+ isI: makePredByNodeName('I'),
980
+ isImg: makePredByNodeName('IMG'),
981
+ isTextarea: isTextarea,
982
+ nodeLength: nodeLength,
983
+ isLeftEdgePoint: isLeftEdgePoint,
984
+ isRightEdgePoint: isRightEdgePoint,
985
+ isEdgePoint: isEdgePoint,
986
+ isRightEdgeOf: isRightEdgeOf,
987
+ prevPoint: prevPoint,
988
+ nextPoint: nextPoint,
989
+ isSamePoint: isSamePoint,
990
+ prevPointUntil: prevPointUntil,
991
+ walkPoint: walkPoint,
992
+ ancestor: ancestor,
993
+ listAncestor: listAncestor,
994
+ listNext: listNext,
995
+ listPrev: listPrev,
996
+ listDescendant: listDescendant,
997
+ commonAncestor: commonAncestor,
998
+ listBetween: listBetween,
999
+ wrap: wrap,
1000
+ insertAfter: insertAfter,
1001
+ appendChildNodes: appendChildNodes,
1002
+ position: position,
1003
+ makeOffsetPath: makeOffsetPath,
1004
+ fromOffsetPath: fromOffsetPath,
1005
+ splitTree: splitTree,
1006
+ createText: createText,
1007
+ remove: remove,
1008
+ html: html
1009
+ };
1010
+ })();
1011
+
1012
+ var settings = {
1013
+ // version
1014
+ version: '0.5.6',
1015
+
1016
+ /**
1017
+ * options
1018
+ */
1019
+ options: {
1020
+ width: null, // set editor width
1021
+ height: null, // set editor height, ex) 300
1022
+
1023
+ minHeight: null, // set minimum height of editor
1024
+ maxHeight: null, // set maximum height of editor
1025
+
1026
+ focus: false, // set focus to editable area after initializing summernote
1027
+
1028
+ tabsize: 4, // size of tab ex) 2 or 4
1029
+ styleWithSpan: true, // style with span (Chrome and FF only)
1030
+
1031
+ disableLinkTarget: false, // hide link Target Checkbox
1032
+ disableDragAndDrop: false, // disable drag and drop event
1033
+ disableResizeEditor: false, // disable resizing editor
1034
+
1035
+ codemirror: { // codemirror options
1036
+ mode: 'text/html',
1037
+ htmlMode: true,
1038
+ lineNumbers: true,
1039
+ autoFormatOnStart: false
1040
+ },
1041
+
1042
+ // language
1043
+ lang: 'en-US', // language 'en-US', 'ko-KR', ...
1044
+ direction: null, // text direction, ex) 'rtl'
1045
+
1046
+ // toolbar
1047
+ toolbar: [
1048
+ ['style', ['style']],
1049
+ ['font', ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'clear']],
1050
+ ['fontname', ['fontname']],
1051
+ // ['fontsize', ['fontsize']], // Still buggy
1052
+ ['color', ['color']],
1053
+ ['para', ['ul', 'ol', 'paragraph']],
1054
+ ['height', ['height']],
1055
+ ['table', ['table']],
1056
+ ['insert', ['link', 'picture', 'video', 'hr']],
1057
+ ['view', ['fullscreen', 'codeview']],
1058
+ ['help', ['help']]
1059
+ ],
1060
+
1061
+ // air mode: inline editor
1062
+ airMode: false,
1063
+ // airPopover: [
1064
+ // ['style', ['style']],
1065
+ // ['font', ['bold', 'italic', 'underline', 'clear']],
1066
+ // ['fontname', ['fontname']],
1067
+ // ['fontsize', ['fontsize']], // Still buggy
1068
+ // ['color', ['color']],
1069
+ // ['para', ['ul', 'ol', 'paragraph']],
1070
+ // ['height', ['height']],
1071
+ // ['table', ['table']],
1072
+ // ['insert', ['link', 'picture', 'video']],
1073
+ // ['help', ['help']]
1074
+ // ],
1075
+ airPopover: [
1076
+ ['color', ['color']],
1077
+ ['font', ['bold', 'underline', 'clear']],
1078
+ ['para', ['ul', 'paragraph']],
1079
+ ['table', ['table']],
1080
+ ['insert', ['link', 'picture']]
1081
+ ],
1082
+
1083
+ // style tag
1084
+ styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
1085
+
1086
+ // default fontName
1087
+ defaultFontName: 'Helvetica Neue',
1088
+
1089
+ // fontName
1090
+ fontNames: [
1091
+ 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New',
1092
+ 'Helvetica Neue', 'Impact', 'Lucida Grande',
1093
+ 'Tahoma', 'Times New Roman', 'Verdana'
1094
+ ],
1095
+
1096
+ // pallete colors(n x n)
1097
+ colors: [
1098
+ ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
1099
+ ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
1100
+ ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
1101
+ ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
1102
+ ['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
1103
+ ['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
1104
+ ['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
1105
+ ['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
1106
+ ],
1107
+
1108
+ // fontSize
1109
+ fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],
1110
+
1111
+ // lineHeight
1112
+ lineHeights: ['1.0', '1.2', '1.4', '1.5', '1.6', '1.8', '2.0', '3.0'],
1113
+
1114
+ // insertTable max size
1115
+ insertTableMaxSize: {
1116
+ col: 10,
1117
+ row: 10
1118
+ },
1119
+
1120
+ // callbacks
1121
+ oninit: null, // initialize
1122
+ onfocus: null, // editable has focus
1123
+ onblur: null, // editable out of focus
1124
+ onenter: null, // enter key pressed
1125
+ onkeyup: null, // keyup
1126
+ onkeydown: null, // keydown
1127
+ onImageUpload: null, // imageUpload
1128
+ onImageUploadError: null, // imageUploadError
1129
+ onToolbarClick: null,
1130
+
1131
+ /**
1132
+ * manipulate link address when user create link
1133
+ * @param {String} sLinkUrl
1134
+ * @return {String}
1135
+ */
1136
+ onCreateLink: function (sLinkUrl) {
1137
+ if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
1138
+ sLinkUrl = 'mailto:' + sLinkUrl;
1139
+ } else if (sLinkUrl.indexOf('://') === -1) {
1140
+ sLinkUrl = 'http://' + sLinkUrl;
1141
+ }
1142
+
1143
+ return sLinkUrl;
1144
+ },
1145
+
1146
+ keyMap: {
1147
+ pc: {
1148
+ 'ENTER': 'insertParagraph',
1149
+ 'CTRL+Z': 'undo',
1150
+ 'CTRL+Y': 'redo',
1151
+ 'TAB': 'tab',
1152
+ 'SHIFT+TAB': 'untab',
1153
+ 'CTRL+B': 'bold',
1154
+ 'CTRL+I': 'italic',
1155
+ 'CTRL+U': 'underline',
1156
+ 'CTRL+SHIFT+S': 'strikethrough',
1157
+ 'CTRL+BACKSLASH': 'removeFormat',
1158
+ 'CTRL+SHIFT+L': 'justifyLeft',
1159
+ 'CTRL+SHIFT+E': 'justifyCenter',
1160
+ 'CTRL+SHIFT+R': 'justifyRight',
1161
+ 'CTRL+SHIFT+J': 'justifyFull',
1162
+ 'CTRL+SHIFT+NUM7': 'insertUnorderedList',
1163
+ 'CTRL+SHIFT+NUM8': 'insertOrderedList',
1164
+ 'CTRL+LEFTBRACKET': 'outdent',
1165
+ 'CTRL+RIGHTBRACKET': 'indent',
1166
+ 'CTRL+NUM0': 'formatPara',
1167
+ 'CTRL+NUM1': 'formatH1',
1168
+ 'CTRL+NUM2': 'formatH2',
1169
+ 'CTRL+NUM3': 'formatH3',
1170
+ 'CTRL+NUM4': 'formatH4',
1171
+ 'CTRL+NUM5': 'formatH5',
1172
+ 'CTRL+NUM6': 'formatH6',
1173
+ 'CTRL+ENTER': 'insertHorizontalRule',
1174
+ 'CTRL+K': 'showLinkDialog'
1175
+ },
1176
+
1177
+ mac: {
1178
+ 'ENTER': 'insertParagraph',
1179
+ 'CMD+Z': 'undo',
1180
+ 'CMD+SHIFT+Z': 'redo',
1181
+ 'TAB': 'tab',
1182
+ 'SHIFT+TAB': 'untab',
1183
+ 'CMD+B': 'bold',
1184
+ 'CMD+I': 'italic',
1185
+ 'CMD+U': 'underline',
1186
+ 'CMD+SHIFT+S': 'strikethrough',
1187
+ 'CMD+BACKSLASH': 'removeFormat',
1188
+ 'CMD+SHIFT+L': 'justifyLeft',
1189
+ 'CMD+SHIFT+E': 'justifyCenter',
1190
+ 'CMD+SHIFT+R': 'justifyRight',
1191
+ 'CMD+SHIFT+J': 'justifyFull',
1192
+ 'CMD+SHIFT+NUM7': 'insertUnorderedList',
1193
+ 'CMD+SHIFT+NUM8': 'insertOrderedList',
1194
+ 'CMD+LEFTBRACKET': 'outdent',
1195
+ 'CMD+RIGHTBRACKET': 'indent',
1196
+ 'CMD+NUM0': 'formatPara',
1197
+ 'CMD+NUM1': 'formatH1',
1198
+ 'CMD+NUM2': 'formatH2',
1199
+ 'CMD+NUM3': 'formatH3',
1200
+ 'CMD+NUM4': 'formatH4',
1201
+ 'CMD+NUM5': 'formatH5',
1202
+ 'CMD+NUM6': 'formatH6',
1203
+ 'CMD+ENTER': 'insertHorizontalRule',
1204
+ 'CMD+K': 'showLinkDialog'
1205
+ }
1206
+ }
1207
+ },
1208
+
1209
+ // default language: en-US
1210
+ lang: {
1211
+ 'en-US': {
1212
+ font: {
1213
+ bold: 'Bold',
1214
+ italic: 'Italic',
1215
+ underline: 'Underline',
1216
+ strikethrough: 'Strikethrough',
1217
+ subscript: 'Subscript',
1218
+ superscript: 'Superscript',
1219
+ clear: 'Remove Font Style',
1220
+ height: 'Line Height',
1221
+ name: 'Font Family',
1222
+ size: 'Font Size'
1223
+ },
1224
+ image: {
1225
+ image: 'Picture',
1226
+ insert: 'Insert Image',
1227
+ resizeFull: 'Resize Full',
1228
+ resizeHalf: 'Resize Half',
1229
+ resizeQuarter: 'Resize Quarter',
1230
+ floatLeft: 'Float Left',
1231
+ floatRight: 'Float Right',
1232
+ floatNone: 'Float None',
1233
+ dragImageHere: 'Drag an image here',
1234
+ selectFromFiles: 'Select from files',
1235
+ url: 'Image URL',
1236
+ remove: 'Remove Image'
1237
+ },
1238
+ link: {
1239
+ link: 'Link',
1240
+ insert: 'Insert Link',
1241
+ unlink: 'Unlink',
1242
+ edit: 'Edit',
1243
+ textToDisplay: 'Text to display',
1244
+ url: 'To what URL should this link go?',
1245
+ openInNewWindow: 'Open in new window'
1246
+ },
1247
+ video: {
1248
+ video: 'Video',
1249
+ videoLink: 'Video Link',
1250
+ insert: 'Insert Video',
1251
+ url: 'Video URL?',
1252
+ providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion or Youku)'
1253
+ },
1254
+ table: {
1255
+ table: 'Table'
1256
+ },
1257
+ hr: {
1258
+ insert: 'Insert Horizontal Rule'
1259
+ },
1260
+ style: {
1261
+ style: 'Style',
1262
+ normal: 'Normal',
1263
+ blockquote: 'Quote',
1264
+ pre: 'Code',
1265
+ h1: 'Header 1',
1266
+ h2: 'Header 2',
1267
+ h3: 'Header 3',
1268
+ h4: 'Header 4',
1269
+ h5: 'Header 5',
1270
+ h6: 'Header 6'
1271
+ },
1272
+ lists: {
1273
+ unordered: 'Unordered list',
1274
+ ordered: 'Ordered list'
1275
+ },
1276
+ options: {
1277
+ help: 'Help',
1278
+ fullscreen: 'Full Screen',
1279
+ codeview: 'Code View'
1280
+ },
1281
+ paragraph: {
1282
+ paragraph: 'Paragraph',
1283
+ outdent: 'Outdent',
1284
+ indent: 'Indent',
1285
+ left: 'Align left',
1286
+ center: 'Align center',
1287
+ right: 'Align right',
1288
+ justify: 'Justify full'
1289
+ },
1290
+ color: {
1291
+ recent: 'Recent Color',
1292
+ more: 'More Color',
1293
+ background: 'Background Color',
1294
+ foreground: 'Foreground Color',
1295
+ transparent: 'Transparent',
1296
+ setTransparent: 'Set transparent',
1297
+ reset: 'Reset',
1298
+ resetToDefault: 'Reset to default'
1299
+ },
1300
+ shortcut: {
1301
+ shortcuts: 'Keyboard shortcuts',
1302
+ close: 'Close',
1303
+ textFormatting: 'Text formatting',
1304
+ action: 'Action',
1305
+ paragraphFormatting: 'Paragraph formatting',
1306
+ documentStyle: 'Document Style'
1307
+ },
1308
+ history: {
1309
+ undo: 'Undo',
1310
+ redo: 'Redo'
1311
+ }
1312
+ }
1313
+ }
1314
+ };
1315
+
1316
+ /**
1317
+ * Async functions which returns `Promise`
1318
+ */
1319
+ var async = (function () {
1320
+ /**
1321
+ * read contents of file as representing URL
1322
+ *
1323
+ * @param {File} file
1324
+ * @return {Promise} - then: sDataUrl
1325
+ */
1326
+ var readFileAsDataURL = function (file) {
1327
+ return $.Deferred(function (deferred) {
1328
+ $.extend(new FileReader(), {
1329
+ onload: function (e) {
1330
+ var sDataURL = e.target.result;
1331
+ deferred.resolve(sDataURL);
1332
+ },
1333
+ onerror: function () {
1334
+ deferred.reject(this);
1335
+ }
1336
+ }).readAsDataURL(file);
1337
+ }).promise();
1338
+ };
1339
+
1340
+ /**
1341
+ * create `<image>` from url string
1342
+ *
1343
+ * @param {String} sUrl
1344
+ * @return {Promise} - then: $image
1345
+ */
1346
+ var createImage = function (sUrl, filename) {
1347
+ return $.Deferred(function (deferred) {
1348
+ $('<img>').one('load', function () {
1349
+ deferred.resolve($(this));
1350
+ }).one('error abort', function () {
1351
+ deferred.reject($(this));
1352
+ }).css({
1353
+ display: 'none'
1354
+ }).appendTo(document.body)
1355
+ .attr('src', sUrl)
1356
+ .attr('data-filename', filename);
1357
+ }).promise();
1358
+ };
1359
+
1360
+ return {
1361
+ readFileAsDataURL: readFileAsDataURL,
1362
+ createImage: createImage
1363
+ };
1364
+ })();
1365
+
1366
+ /**
1367
+ * Object for keycodes.
1368
+ */
1369
+ var key = {
1370
+ isEdit: function (keyCode) {
1371
+ return [8, 9, 13, 32].indexOf(keyCode) !== -1;
1372
+ },
1373
+ nameFromCode: {
1374
+ '8': 'BACKSPACE',
1375
+ '9': 'TAB',
1376
+ '13': 'ENTER',
1377
+ '32': 'SPACE',
1378
+
1379
+ // Number: 0-9
1380
+ '48': 'NUM0',
1381
+ '49': 'NUM1',
1382
+ '50': 'NUM2',
1383
+ '51': 'NUM3',
1384
+ '52': 'NUM4',
1385
+ '53': 'NUM5',
1386
+ '54': 'NUM6',
1387
+ '55': 'NUM7',
1388
+ '56': 'NUM8',
1389
+
1390
+ // Alphabet: a-z
1391
+ '66': 'B',
1392
+ '69': 'E',
1393
+ '73': 'I',
1394
+ '74': 'J',
1395
+ '75': 'K',
1396
+ '76': 'L',
1397
+ '82': 'R',
1398
+ '83': 'S',
1399
+ '85': 'U',
1400
+ '89': 'Y',
1401
+ '90': 'Z',
1402
+
1403
+ '191': 'SLASH',
1404
+ '219': 'LEFTBRACKET',
1405
+ '220': 'BACKSLASH',
1406
+ '221': 'RIGHTBRACKET'
1407
+ }
1408
+ };
1409
+
1410
+ /**
1411
+ * Style
1412
+ * @class
1413
+ */
1414
+ var Style = function () {
1415
+ /**
1416
+ * passing an array of style properties to .css()
1417
+ * will result in an object of property-value pairs.
1418
+ * (compability with version < 1.9)
1419
+ *
1420
+ * @param {jQuery} $obj
1421
+ * @param {Array} propertyNames - An array of one or more CSS properties.
1422
+ * @returns {Object}
1423
+ */
1424
+ var jQueryCSS = function ($obj, propertyNames) {
1425
+ if (agent.jqueryVersion < 1.9) {
1426
+ var result = {};
1427
+ $.each(propertyNames, function (idx, propertyName) {
1428
+ result[propertyName] = $obj.css(propertyName);
1429
+ });
1430
+ return result;
1431
+ }
1432
+ return $obj.css.call($obj, propertyNames);
1433
+ };
1434
+
1435
+ /**
1436
+ * paragraph level style
1437
+ *
1438
+ * @param {WrappedRange} rng
1439
+ * @param {Object} styleInfo
1440
+ */
1441
+ this.stylePara = function (rng, styleInfo) {
1442
+ $.each(rng.nodes(dom.isPara, {
1443
+ includeAncestor: true
1444
+ }), function (idx, para) {
1445
+ $(para).css(styleInfo);
1446
+ });
1447
+ };
1448
+
1449
+ /**
1450
+ * get current style on cursor
1451
+ *
1452
+ * @param {WrappedRange} rng
1453
+ * @param {Node} target - target element on event
1454
+ * @return {Object} - object contains style properties.
1455
+ */
1456
+ this.current = function (rng, target) {
1457
+ var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
1458
+ var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height'];
1459
+ var styleInfo = jQueryCSS($cont, properties) || {};
1460
+
1461
+ styleInfo['font-size'] = parseInt(styleInfo['font-size'], 10);
1462
+
1463
+ // document.queryCommandState for toggle state
1464
+ styleInfo['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal';
1465
+ styleInfo['font-italic'] = document.queryCommandState('italic') ? 'italic' : 'normal';
1466
+ styleInfo['font-underline'] = document.queryCommandState('underline') ? 'underline' : 'normal';
1467
+ styleInfo['font-strikethrough'] = document.queryCommandState('strikeThrough') ? 'strikethrough' : 'normal';
1468
+ styleInfo['font-superscript'] = document.queryCommandState('superscript') ? 'superscript' : 'normal';
1469
+ styleInfo['font-subscript'] = document.queryCommandState('subscript') ? 'subscript' : 'normal';
1470
+
1471
+ // list-style-type to list-style(unordered, ordered)
1472
+ if (!rng.isOnList()) {
1473
+ styleInfo['list-style'] = 'none';
1474
+ } else {
1475
+ var aOrderedType = ['circle', 'disc', 'disc-leading-zero', 'square'];
1476
+ var isUnordered = $.inArray(styleInfo['list-style-type'], aOrderedType) > -1;
1477
+ styleInfo['list-style'] = isUnordered ? 'unordered' : 'ordered';
1478
+ }
1479
+
1480
+ var para = dom.ancestor(rng.sc, dom.isPara);
1481
+ if (para && para.style['line-height']) {
1482
+ styleInfo['line-height'] = para.style.lineHeight;
1483
+ } else {
1484
+ var lineHeight = parseInt(styleInfo['line-height'], 10) / parseInt(styleInfo['font-size'], 10);
1485
+ styleInfo['line-height'] = lineHeight.toFixed(1);
1486
+ }
1487
+
1488
+ styleInfo.image = dom.isImg(target) && target;
1489
+ styleInfo.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
1490
+ styleInfo.ancestors = dom.listAncestor(rng.sc, dom.isEditable);
1491
+ styleInfo.range = rng;
1492
+
1493
+ return styleInfo;
1494
+ };
1495
+ };
1496
+
1497
+
1498
+ /**
1499
+ * related data structure
1500
+ * - {BoundaryPoint}: a point of dom tree
1501
+ * - {BoundaryPoints}: two boundaryPoints corresponding to the start and the end of the Range
1502
+ *
1503
+ * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Position
1504
+ */
1505
+ var range = (function () {
1506
+ /**
1507
+ * return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
1508
+ *
1509
+ * @param {TextRange} textRange
1510
+ * @param {Boolean} isStart
1511
+ * @return {BoundaryPoint}
1512
+ */
1513
+ var textRangeToPoint = function (textRange, isStart) {
1514
+ var container = textRange.parentElement(), offset;
1515
+
1516
+ var tester = document.body.createTextRange(), prevContainer;
1517
+ var childNodes = list.from(container.childNodes);
1518
+ for (offset = 0; offset < childNodes.length; offset++) {
1519
+ if (dom.isText(childNodes[offset])) {
1520
+ continue;
1521
+ }
1522
+ tester.moveToElementText(childNodes[offset]);
1523
+ if (tester.compareEndPoints('StartToStart', textRange) >= 0) {
1524
+ break;
1525
+ }
1526
+ prevContainer = childNodes[offset];
1527
+ }
1528
+
1529
+ if (offset !== 0 && dom.isText(childNodes[offset - 1])) {
1530
+ var textRangeStart = document.body.createTextRange(), curTextNode = null;
1531
+ textRangeStart.moveToElementText(prevContainer || container);
1532
+ textRangeStart.collapse(!prevContainer);
1533
+ curTextNode = prevContainer ? prevContainer.nextSibling : container.firstChild;
1534
+
1535
+ var pointTester = textRange.duplicate();
1536
+ pointTester.setEndPoint('StartToStart', textRangeStart);
1537
+ var textCount = pointTester.text.replace(/[\r\n]/g, '').length;
1538
+
1539
+ while (textCount > curTextNode.nodeValue.length && curTextNode.nextSibling) {
1540
+ textCount -= curTextNode.nodeValue.length;
1541
+ curTextNode = curTextNode.nextSibling;
1542
+ }
1543
+
1544
+ /* jshint ignore:start */
1545
+ var dummy = curTextNode.nodeValue; // enforce IE to re-reference curTextNode, hack
1546
+ /* jshint ignore:end */
1547
+
1548
+ if (isStart && curTextNode.nextSibling && dom.isText(curTextNode.nextSibling) &&
1549
+ textCount === curTextNode.nodeValue.length) {
1550
+ textCount -= curTextNode.nodeValue.length;
1551
+ curTextNode = curTextNode.nextSibling;
1552
+ }
1553
+
1554
+ container = curTextNode;
1555
+ offset = textCount;
1556
+ }
1557
+
1558
+ return {
1559
+ cont: container,
1560
+ offset: offset
1561
+ };
1562
+ };
1563
+
1564
+ /**
1565
+ * return TextRange from boundary point (inspired by google closure-library)
1566
+ * @param {BoundaryPoint} point
1567
+ * @return {TextRange}
1568
+ */
1569
+ var pointToTextRange = function (point) {
1570
+ var textRangeInfo = function (container, offset) {
1571
+ var node, isCollapseToStart;
1572
+
1573
+ if (dom.isText(container)) {
1574
+ var prevTextNodes = dom.listPrev(container, func.not(dom.isText));
1575
+ var prevContainer = list.last(prevTextNodes).previousSibling;
1576
+ node = prevContainer || container.parentNode;
1577
+ offset += list.sum(list.tail(prevTextNodes), dom.nodeLength);
1578
+ isCollapseToStart = !prevContainer;
1579
+ } else {
1580
+ node = container.childNodes[offset] || container;
1581
+ if (dom.isText(node)) {
1582
+ return textRangeInfo(node, 0);
1583
+ }
1584
+
1585
+ offset = 0;
1586
+ isCollapseToStart = false;
1587
+ }
1588
+
1589
+ return {
1590
+ node: node,
1591
+ collapseToStart: isCollapseToStart,
1592
+ offset: offset
1593
+ };
1594
+ };
1595
+
1596
+ var textRange = document.body.createTextRange();
1597
+ var info = textRangeInfo(point.node, point.offset);
1598
+
1599
+ textRange.moveToElementText(info.node);
1600
+ textRange.collapse(info.collapseToStart);
1601
+ textRange.moveStart('character', info.offset);
1602
+ return textRange;
1603
+ };
1604
+
1605
+ /**
1606
+ * Wrapped Range
1607
+ *
1608
+ * @param {Node} sc - start container
1609
+ * @param {Number} so - start offset
1610
+ * @param {Node} ec - end container
1611
+ * @param {Number} eo - end offset
1612
+ */
1613
+ var WrappedRange = function (sc, so, ec, eo) {
1614
+ this.sc = sc;
1615
+ this.so = so;
1616
+ this.ec = ec;
1617
+ this.eo = eo;
1618
+
1619
+ // nativeRange: get nativeRange from sc, so, ec, eo
1620
+ var nativeRange = function () {
1621
+ if (agent.isW3CRangeSupport) {
1622
+ var w3cRange = document.createRange();
1623
+ w3cRange.setStart(sc, so);
1624
+ w3cRange.setEnd(ec, eo);
1625
+
1626
+ return w3cRange;
1627
+ } else {
1628
+ var textRange = pointToTextRange({
1629
+ node: sc,
1630
+ offset: so
1631
+ });
1632
+
1633
+ textRange.setEndPoint('EndToEnd', pointToTextRange({
1634
+ node: ec,
1635
+ offset: eo
1636
+ }));
1637
+
1638
+ return textRange;
1639
+ }
1640
+ };
1641
+
1642
+ this.getPoints = function () {
1643
+ return {
1644
+ sc: sc,
1645
+ so: so,
1646
+ ec: ec,
1647
+ eo: eo
1648
+ };
1649
+ };
1650
+
1651
+ this.getStartPoint = function () {
1652
+ return {
1653
+ node: sc,
1654
+ offset: so
1655
+ };
1656
+ };
1657
+
1658
+ this.getEndPoint = function () {
1659
+ return {
1660
+ node: ec,
1661
+ offset: eo
1662
+ };
1663
+ };
1664
+
1665
+ /**
1666
+ * select update visible range
1667
+ */
1668
+ this.select = function () {
1669
+ var nativeRng = nativeRange();
1670
+ if (agent.isW3CRangeSupport) {
1671
+ var selection = document.getSelection();
1672
+ if (selection.rangeCount > 0) {
1673
+ selection.removeAllRanges();
1674
+ }
1675
+ selection.addRange(nativeRng);
1676
+ } else {
1677
+ nativeRng.select();
1678
+ }
1679
+ };
1680
+
1681
+ /**
1682
+ * returns matched nodes on range
1683
+ *
1684
+ * @param {Function} [pred] - predicate function
1685
+ * @param {Object} [options]
1686
+ * @param {Boolean} [options.includeAncestor]
1687
+ * @param {Boolean} [options.fullyContains]
1688
+ * @return {Node[]}
1689
+ */
1690
+ this.nodes = function (pred, options) {
1691
+ pred = pred || func.ok;
1692
+
1693
+ var includeAncestor = options && options.includeAncestor;
1694
+ var fullyContains = options && options.fullyContains;
1695
+
1696
+ // TODO compare points and sort
1697
+ var startPoint = this.getStartPoint();
1698
+ var endPoint = this.getEndPoint();
1699
+
1700
+ var nodes = [];
1701
+ var leftEdgeNodes = [];
1702
+
1703
+ dom.walkPoint(startPoint, endPoint, function (point) {
1704
+ if (dom.isEditable(point.node)) {
1705
+ return;
1706
+ }
1707
+
1708
+ var node;
1709
+ if (fullyContains) {
1710
+ if (dom.isLeftEdgePoint(point)) {
1711
+ leftEdgeNodes.push(point.node);
1712
+ }
1713
+ if (dom.isRightEdgePoint(point) &&
1714
+ list.contains(leftEdgeNodes, point.node)) {
1715
+ node = point.node;
1716
+ }
1717
+ } else if (includeAncestor) {
1718
+ node = dom.ancestor(point.node, pred);
1719
+ } else {
1720
+ node = point.node;
1721
+ }
1722
+
1723
+ if (node && pred(node)) {
1724
+ nodes.push(node);
1725
+ }
1726
+ }, true);
1727
+
1728
+ return list.unique(nodes);
1729
+ };
1730
+
1731
+ /**
1732
+ * returns commonAncestor of range
1733
+ * @return {Element} - commonAncestor
1734
+ */
1735
+ this.commonAncestor = function () {
1736
+ return dom.commonAncestor(sc, ec);
1737
+ };
1738
+
1739
+ /**
1740
+ * returns expanded range by pred
1741
+ *
1742
+ * @param {Function} pred - predicate function
1743
+ * @return {WrappedRange}
1744
+ */
1745
+ this.expand = function (pred) {
1746
+ var startAncestor = dom.ancestor(sc, pred);
1747
+ var endAncestor = dom.ancestor(ec, pred);
1748
+
1749
+ if (!startAncestor && !endAncestor) {
1750
+ return new WrappedRange(sc, so, ec, eo);
1751
+ }
1752
+
1753
+ var boundaryPoints = this.getPoints();
1754
+
1755
+ if (startAncestor) {
1756
+ boundaryPoints.sc = startAncestor;
1757
+ boundaryPoints.so = 0;
1758
+ }
1759
+
1760
+ if (endAncestor) {
1761
+ boundaryPoints.ec = endAncestor;
1762
+ boundaryPoints.eo = dom.nodeLength(endAncestor);
1763
+ }
1764
+
1765
+ return new WrappedRange(
1766
+ boundaryPoints.sc,
1767
+ boundaryPoints.so,
1768
+ boundaryPoints.ec,
1769
+ boundaryPoints.eo
1770
+ );
1771
+ };
1772
+
1773
+ /**
1774
+ * @param {Boolean} isCollapseToStart
1775
+ * @return {WrappedRange}
1776
+ */
1777
+ this.collapse = function (isCollapseToStart) {
1778
+ if (isCollapseToStart) {
1779
+ return new WrappedRange(sc, so, sc, so);
1780
+ } else {
1781
+ return new WrappedRange(ec, eo, ec, eo);
1782
+ }
1783
+ };
1784
+
1785
+ /**
1786
+ * splitText on range
1787
+ */
1788
+ this.splitText = function () {
1789
+ var isSameContainer = sc === ec;
1790
+ var boundaryPoints = this.getPoints();
1791
+
1792
+ if (dom.isText(ec) && !dom.isEdgePoint(this.getEndPoint())) {
1793
+ ec.splitText(eo);
1794
+ }
1795
+
1796
+ if (dom.isText(sc) && !dom.isEdgePoint(this.getStartPoint())) {
1797
+ boundaryPoints.sc = sc.splitText(so);
1798
+ boundaryPoints.so = 0;
1799
+
1800
+ if (isSameContainer) {
1801
+ boundaryPoints.ec = boundaryPoints.sc;
1802
+ boundaryPoints.eo = eo - so;
1803
+ }
1804
+ }
1805
+
1806
+ return new WrappedRange(
1807
+ boundaryPoints.sc,
1808
+ boundaryPoints.so,
1809
+ boundaryPoints.ec,
1810
+ boundaryPoints.eo
1811
+ );
1812
+ };
1813
+
1814
+ /**
1815
+ * delete contents on range
1816
+ * @return {WrappedRange}
1817
+ */
1818
+ this.deleteContents = function () {
1819
+ if (this.isCollapsed()) {
1820
+ return this;
1821
+ }
1822
+
1823
+ var rng = this.splitText();
1824
+ var nodes = rng.nodes(null, {
1825
+ fullyContains: true
1826
+ });
1827
+
1828
+ var point = dom.prevPointUntil(rng.getStartPoint(), function (point) {
1829
+ return !list.contains(nodes, point.node);
1830
+ });
1831
+
1832
+ $.each(nodes, function (idx, node) {
1833
+ dom.remove(node, false);
1834
+ });
1835
+
1836
+ return new WrappedRange(
1837
+ point.node,
1838
+ point.offset,
1839
+ point.node,
1840
+ point.offset
1841
+ );
1842
+ };
1843
+
1844
+ /**
1845
+ * makeIsOn: return isOn(pred) function
1846
+ */
1847
+ var makeIsOn = function (pred) {
1848
+ return function () {
1849
+ var ancestor = dom.ancestor(sc, pred);
1850
+ return !!ancestor && (ancestor === dom.ancestor(ec, pred));
1851
+ };
1852
+ };
1853
+
1854
+ // isOnEditable: judge whether range is on editable or not
1855
+ this.isOnEditable = makeIsOn(dom.isEditable);
1856
+ // isOnList: judge whether range is on list node or not
1857
+ this.isOnList = makeIsOn(dom.isList);
1858
+ // isOnAnchor: judge whether range is on anchor node or not
1859
+ this.isOnAnchor = makeIsOn(dom.isAnchor);
1860
+ // isOnAnchor: judge whether range is on cell node or not
1861
+ this.isOnCell = makeIsOn(dom.isCell);
1862
+
1863
+ /**
1864
+ * returns whether range was collapsed or not
1865
+ */
1866
+ this.isCollapsed = function () {
1867
+ return sc === ec && so === eo;
1868
+ };
1869
+
1870
+ /**
1871
+ * wrap inline nodes which children of body with paragraph
1872
+ *
1873
+ * @return {WrappedRange}
1874
+ */
1875
+ this.wrapBodyInlineWithPara = function () {
1876
+ if (dom.isEditable(sc) && !sc.childNodes[so]) {
1877
+ return new WrappedRange(sc.appendChild($(dom.emptyPara)[0]), 0);
1878
+ } else if (!dom.isInline(sc) || dom.isParaInline(sc)) {
1879
+ return this;
1880
+ }
1881
+
1882
+ // find inline top ancestor
1883
+ var ancestors = dom.listAncestor(sc, func.not(dom.isInline));
1884
+ var topAncestor = list.last(ancestors);
1885
+ if (!dom.isInline(topAncestor)) {
1886
+ topAncestor = ancestors[ancestors.length - 2] || sc.childNodes[so];
1887
+ }
1888
+
1889
+ // siblings not in paragraph
1890
+ var inlineSiblings = dom.listPrev(topAncestor, dom.isParaInline).reverse();
1891
+ inlineSiblings = inlineSiblings.concat(dom.listNext(topAncestor.nextSibling, dom.isParaInline));
1892
+
1893
+ // wrap with paragraph
1894
+ if (inlineSiblings.length) {
1895
+ var para = dom.wrap(list.head(inlineSiblings), 'p');
1896
+ dom.appendChildNodes(para, list.tail(inlineSiblings));
1897
+ }
1898
+
1899
+ return this;
1900
+ };
1901
+
1902
+ /**
1903
+ * insert node at current cursor
1904
+ *
1905
+ * @param {Node} node
1906
+ * @param {Boolean} [isInline]
1907
+ * @return {Node}
1908
+ */
1909
+ this.insertNode = function (node, isInline) {
1910
+ var rng = this.wrapBodyInlineWithPara();
1911
+ var point = rng.getStartPoint();
1912
+
1913
+ var splitRoot, container, pivot;
1914
+ if (isInline) {
1915
+ container = dom.isPara(point.node) ? point.node : point.node.parentNode;
1916
+ if (dom.isPara(point.node)) {
1917
+ pivot = point.node.childNodes[point.offset];
1918
+ } else {
1919
+ pivot = dom.splitTree(point.node, point);
1920
+ }
1921
+ } else {
1922
+ // splitRoot will be childNode of container
1923
+ var ancestors = dom.listAncestor(point.node, dom.isBodyContainer);
1924
+ var topAncestor = list.last(ancestors) || point.node;
1925
+
1926
+ if (dom.isBodyContainer(topAncestor)) {
1927
+ splitRoot = ancestors[ancestors.length - 2];
1928
+ container = topAncestor;
1929
+ } else {
1930
+ splitRoot = topAncestor;
1931
+ container = splitRoot.parentNode;
1932
+ }
1933
+ pivot = splitRoot && dom.splitTree(splitRoot, point);
1934
+ }
1935
+
1936
+ if (pivot) {
1937
+ pivot.parentNode.insertBefore(node, pivot);
1938
+ } else {
1939
+ container.appendChild(node);
1940
+ }
1941
+
1942
+ return node;
1943
+ };
1944
+
1945
+ this.toString = function () {
1946
+ var nativeRng = nativeRange();
1947
+ return agent.isW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
1948
+ };
1949
+
1950
+ /**
1951
+ * create offsetPath bookmark
1952
+ * @param {Node} editable
1953
+ */
1954
+ this.bookmark = function (editable) {
1955
+ return {
1956
+ s: {
1957
+ path: dom.makeOffsetPath(editable, sc),
1958
+ offset: so
1959
+ },
1960
+ e: {
1961
+ path: dom.makeOffsetPath(editable, ec),
1962
+ offset: eo
1963
+ }
1964
+ };
1965
+ };
1966
+
1967
+ /**
1968
+ * getClientRects
1969
+ * @return {Rect[]}
1970
+ */
1971
+ this.getClientRects = function () {
1972
+ var nativeRng = nativeRange();
1973
+ return nativeRng.getClientRects();
1974
+ };
1975
+ };
1976
+
1977
+ return {
1978
+ /**
1979
+ * create Range Object From arguments or Browser Selection
1980
+ *
1981
+ * @param {Node} sc - start container
1982
+ * @param {Number} so - start offset
1983
+ * @param {Node} ec - end container
1984
+ * @param {Number} eo - end offset
1985
+ */
1986
+ create : function (sc, so, ec, eo) {
1987
+ if (!arguments.length) { // from Browser Selection
1988
+ if (agent.isW3CRangeSupport) {
1989
+ var selection = document.getSelection();
1990
+ if (selection.rangeCount === 0) {
1991
+ return null;
1992
+ }
1993
+
1994
+ var nativeRng = selection.getRangeAt(0);
1995
+ sc = nativeRng.startContainer;
1996
+ so = nativeRng.startOffset;
1997
+ ec = nativeRng.endContainer;
1998
+ eo = nativeRng.endOffset;
1999
+ } else { // IE8: TextRange
2000
+ var textRange = document.selection.createRange();
2001
+ var textRangeEnd = textRange.duplicate();
2002
+ textRangeEnd.collapse(false);
2003
+ var textRangeStart = textRange;
2004
+ textRangeStart.collapse(true);
2005
+
2006
+ var startPoint = textRangeToPoint(textRangeStart, true),
2007
+ endPoint = textRangeToPoint(textRangeEnd, false);
2008
+
2009
+ sc = startPoint.cont;
2010
+ so = startPoint.offset;
2011
+ ec = endPoint.cont;
2012
+ eo = endPoint.offset;
2013
+ }
2014
+ } else if (arguments.length === 2) { //collapsed
2015
+ ec = sc;
2016
+ eo = so;
2017
+ }
2018
+ return new WrappedRange(sc, so, ec, eo);
2019
+ },
2020
+
2021
+ /**
2022
+ * create WrappedRange from node
2023
+ *
2024
+ * @param {Node} node
2025
+ * @return {WrappedRange}
2026
+ */
2027
+ createFromNode: function (node) {
2028
+ return this.create(node, 0, node, 1);
2029
+ },
2030
+
2031
+ /**
2032
+ * create WrappedRange from Bookmark
2033
+ *
2034
+ * @param {Node} editable
2035
+ * @param {Obkect} bookmark
2036
+ * @return {WrappedRange}
2037
+ */
2038
+ createFromBookmark : function (editable, bookmark) {
2039
+ var sc = dom.fromOffsetPath(editable, bookmark.s.path);
2040
+ var so = bookmark.s.offset;
2041
+ var ec = dom.fromOffsetPath(editable, bookmark.e.path);
2042
+ var eo = bookmark.e.offset;
2043
+ return new WrappedRange(sc, so, ec, eo);
2044
+ }
2045
+ };
2046
+ })();
2047
+
2048
+ /**
2049
+ * Table
2050
+ * @class
2051
+ */
2052
+ var Table = function () {
2053
+ /**
2054
+ * handle tab key
2055
+ *
2056
+ * @param {WrappedRange} rng
2057
+ * @param {Boolean} isShift
2058
+ */
2059
+ this.tab = function (rng, isShift) {
2060
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
2061
+ var table = dom.ancestor(cell, dom.isTable);
2062
+ var cells = dom.listDescendant(table, dom.isCell);
2063
+
2064
+ var nextCell = list[isShift ? 'prev' : 'next'](cells, cell);
2065
+ if (nextCell) {
2066
+ range.create(nextCell, 0).select();
2067
+ }
2068
+ };
2069
+
2070
+ /**
2071
+ * create empty table element
2072
+ *
2073
+ * @param {Number} rowCount
2074
+ * @param {Number} colCount
2075
+ * @return {Node}
2076
+ */
2077
+ this.createTable = function (colCount, rowCount) {
2078
+ var tds = [], tdHTML;
2079
+ for (var idxCol = 0; idxCol < colCount; idxCol++) {
2080
+ tds.push('<td>' + dom.blank + '</td>');
2081
+ }
2082
+ tdHTML = tds.join('');
2083
+
2084
+ var trs = [], trHTML;
2085
+ for (var idxRow = 0; idxRow < rowCount; idxRow++) {
2086
+ trs.push('<tr>' + tdHTML + '</tr>');
2087
+ }
2088
+ trHTML = trs.join('');
2089
+ return $('<table class="table table-bordered">' + trHTML + '</table>')[0];
2090
+ };
2091
+ };
2092
+
2093
+ /**
2094
+ * Editor
2095
+ * @class
2096
+ */
2097
+ var Editor = function () {
2098
+
2099
+ var style = new Style();
2100
+ var table = new Table();
2101
+
2102
+ /**
2103
+ * save current range
2104
+ *
2105
+ * @param {jQuery} $editable
2106
+ */
2107
+ this.saveRange = function ($editable) {
2108
+ $editable.focus();
2109
+ $editable.data('range', range.create());
2110
+ };
2111
+
2112
+ /**
2113
+ * restore lately range
2114
+ *
2115
+ * @param {jQuery} $editable
2116
+ */
2117
+ this.restoreRange = function ($editable) {
2118
+ var rng = $editable.data('range');
2119
+ if (rng) {
2120
+ rng.select();
2121
+ $editable.focus();
2122
+ }
2123
+ };
2124
+
2125
+ /**
2126
+ * current style
2127
+ * @param {Node} target
2128
+ */
2129
+ this.currentStyle = function (target) {
2130
+ var rng = range.create();
2131
+ return rng ? rng.isOnEditable() && style.current(rng, target) : false;
2132
+ };
2133
+
2134
+ /**
2135
+ * undo
2136
+ * @param {jQuery} $editable
2137
+ */
2138
+ this.undo = function ($editable) {
2139
+ $editable.data('NoteHistory').undo($editable);
2140
+ };
2141
+
2142
+ /**
2143
+ * redo
2144
+ * @param {jQuery} $editable
2145
+ */
2146
+ this.redo = function ($editable) {
2147
+ $editable.data('NoteHistory').redo($editable);
2148
+ };
2149
+
2150
+ /**
2151
+ * record Undo
2152
+ * @param {jQuery} $editable
2153
+ */
2154
+ var recordUndo = this.recordUndo = function ($editable) {
2155
+ $editable.data('NoteHistory').recordUndo($editable);
2156
+ };
2157
+
2158
+ /* jshint ignore:start */
2159
+ // native commands(with execCommand), generate function for execCommand
2160
+ var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
2161
+ 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
2162
+ 'insertOrderedList', 'insertUnorderedList',
2163
+ 'indent', 'outdent', 'formatBlock', 'removeFormat',
2164
+ 'backColor', 'foreColor', 'insertHorizontalRule', 'fontName'];
2165
+
2166
+ for (var idx = 0, len = commands.length; idx < len; idx ++) {
2167
+ this[commands[idx]] = (function (sCmd) {
2168
+ return function ($editable, value) {
2169
+ recordUndo($editable);
2170
+
2171
+ document.execCommand(sCmd, false, value);
2172
+ };
2173
+ })(commands[idx]);
2174
+ }
2175
+ /* jshint ignore:end */
2176
+
2177
+ /**
2178
+ * @param {jQuery} $editable
2179
+ * @param {WrappedRange} rng
2180
+ * @param {Number} tabsize
2181
+ */
2182
+ var insertTab = function ($editable, rng, tabsize) {
2183
+ recordUndo($editable);
2184
+
2185
+ var tab = dom.createText(new Array(tabsize + 1).join(dom.NBSP_CHAR));
2186
+ rng = rng.deleteContents();
2187
+ rng.insertNode(tab, true);
2188
+
2189
+ rng = range.create(tab, tabsize);
2190
+ rng.select();
2191
+ };
2192
+
2193
+ /**
2194
+ * handle tab key
2195
+ * @param {jQuery} $editable
2196
+ * @param {Object} options
2197
+ */
2198
+ this.tab = function ($editable, options) {
2199
+ var rng = range.create();
2200
+ if (rng.isCollapsed() && rng.isOnCell()) {
2201
+ table.tab(rng);
2202
+ } else {
2203
+ insertTab($editable, rng, options.tabsize);
2204
+ }
2205
+ };
2206
+
2207
+ /**
2208
+ * handle shift+tab key
2209
+ */
2210
+ this.untab = function () {
2211
+ var rng = range.create();
2212
+ if (rng.isCollapsed() && rng.isOnCell()) {
2213
+ table.tab(rng, true);
2214
+ }
2215
+ };
2216
+
2217
+ /**
2218
+ * insert paragraph
2219
+ *
2220
+ * @param {Node} $editable
2221
+ */
2222
+ this.insertParagraph = function ($editable) {
2223
+ recordUndo($editable);
2224
+
2225
+ var rng = range.create();
2226
+
2227
+ // deleteContents on range.
2228
+ rng = rng.deleteContents();
2229
+
2230
+ rng = rng.wrapBodyInlineWithPara();
2231
+
2232
+ // find split root node: block level node
2233
+ var splitRoot = dom.ancestor(rng.sc, dom.isPara);
2234
+ var nextPara = dom.splitTree(splitRoot, rng.getStartPoint());
2235
+ range.create(nextPara, 0).select();
2236
+ };
2237
+
2238
+ /**
2239
+ * insert image
2240
+ *
2241
+ * @param {jQuery} $editable
2242
+ * @param {String} sUrl
2243
+ */
2244
+ this.insertImage = function ($editable, sUrl, filename) {
2245
+ async.createImage(sUrl, filename).then(function ($image) {
2246
+ recordUndo($editable);
2247
+
2248
+ $image.css({
2249
+ display: '',
2250
+ width: Math.min($editable.width(), $image.width())
2251
+ });
2252
+ range.create().insertNode($image[0]);
2253
+ }).fail(function () {
2254
+ var callbacks = $editable.data('callbacks');
2255
+ if (callbacks.onImageUploadError) {
2256
+ callbacks.onImageUploadError();
2257
+ }
2258
+ });
2259
+ };
2260
+
2261
+ /**
2262
+ * insert video
2263
+ * @param {jQuery} $editable
2264
+ * @param {String} sUrl
2265
+ */
2266
+ this.insertVideo = function ($editable, sUrl) {
2267
+ recordUndo($editable);
2268
+
2269
+ // video url patterns(youtube, instagram, vimeo, dailymotion, youku)
2270
+ var ytRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
2271
+ var ytMatch = sUrl.match(ytRegExp);
2272
+
2273
+ var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/;
2274
+ var igMatch = sUrl.match(igRegExp);
2275
+
2276
+ var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
2277
+ var vMatch = sUrl.match(vRegExp);
2278
+
2279
+ var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
2280
+ var vimMatch = sUrl.match(vimRegExp);
2281
+
2282
+ var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
2283
+ var dmMatch = sUrl.match(dmRegExp);
2284
+
2285
+ var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)\.html/;
2286
+ var youkuMatch = sUrl.match(youkuRegExp);
2287
+
2288
+ var $video;
2289
+ if (ytMatch && ytMatch[2].length === 11) {
2290
+ var youtubeId = ytMatch[2];
2291
+ $video = $('<iframe>')
2292
+ .attr('src', '//www.youtube.com/embed/' + youtubeId)
2293
+ .attr('width', '640').attr('height', '360');
2294
+ } else if (igMatch && igMatch[0].length) {
2295
+ $video = $('<iframe>')
2296
+ .attr('src', igMatch[0] + '/embed/')
2297
+ .attr('width', '612').attr('height', '710')
2298
+ .attr('scrolling', 'no')
2299
+ .attr('allowtransparency', 'true');
2300
+ } else if (vMatch && vMatch[0].length) {
2301
+ $video = $('<iframe>')
2302
+ .attr('src', vMatch[0] + '/embed/simple')
2303
+ .attr('width', '600').attr('height', '600')
2304
+ .attr('class', 'vine-embed');
2305
+ } else if (vimMatch && vimMatch[3].length) {
2306
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
2307
+ .attr('src', '//player.vimeo.com/video/' + vimMatch[3])
2308
+ .attr('width', '640').attr('height', '360');
2309
+ } else if (dmMatch && dmMatch[2].length) {
2310
+ $video = $('<iframe>')
2311
+ .attr('src', '//www.dailymotion.com/embed/video/' + dmMatch[2])
2312
+ .attr('width', '640').attr('height', '360');
2313
+ } else if (youkuMatch && youkuMatch[1].length) {
2314
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
2315
+ .attr('height', '498')
2316
+ .attr('width', '510')
2317
+ .attr('src', '//player.youku.com/embed/' + youkuMatch[1]);
2318
+ } else {
2319
+ // this is not a known video link. Now what, Cat? Now what?
2320
+ }
2321
+
2322
+ if ($video) {
2323
+ $video.attr('frameborder', 0);
2324
+ range.create().insertNode($video[0]);
2325
+ }
2326
+ };
2327
+
2328
+ /**
2329
+ * formatBlock
2330
+ *
2331
+ * @param {jQuery} $editable
2332
+ * @param {String} tagName
2333
+ */
2334
+ this.formatBlock = function ($editable, tagName) {
2335
+ recordUndo($editable);
2336
+
2337
+ tagName = agent.isMSIE ? '<' + tagName + '>' : tagName;
2338
+ document.execCommand('FormatBlock', false, tagName);
2339
+ };
2340
+
2341
+ this.formatPara = function ($editable) {
2342
+ this.formatBlock($editable, 'P');
2343
+ };
2344
+
2345
+ /* jshint ignore:start */
2346
+ for (var idx = 1; idx <= 6; idx ++) {
2347
+ this['formatH' + idx] = function (idx) {
2348
+ return function ($editable) {
2349
+ this.formatBlock($editable, 'H' + idx);
2350
+ };
2351
+ }(idx);
2352
+ };
2353
+ /* jshint ignore:end */
2354
+
2355
+ /**
2356
+ * fontsize
2357
+ * FIXME: Still buggy
2358
+ *
2359
+ * @param {jQuery} $editable
2360
+ * @param {String} value - px
2361
+ */
2362
+ this.fontSize = function ($editable, value) {
2363
+ recordUndo($editable);
2364
+
2365
+ document.execCommand('fontSize', false, 3);
2366
+ if (agent.isFF) {
2367
+ // firefox: <font size="3"> to <span style='font-size={value}px;'>, buggy
2368
+ $editable.find('font[size=3]').removeAttr('size').css('font-size', value + 'px');
2369
+ } else {
2370
+ // chrome: <span style="font-size: medium"> to <span style='font-size={value}px;'>
2371
+ $editable.find('span').filter(function () {
2372
+ return this.style.fontSize === 'medium';
2373
+ }).css('font-size', value + 'px');
2374
+ }
2375
+ };
2376
+
2377
+ /**
2378
+ * lineHeight
2379
+ * @param {jQuery} $editable
2380
+ * @param {String} value
2381
+ */
2382
+ this.lineHeight = function ($editable, value) {
2383
+ recordUndo($editable);
2384
+
2385
+ style.stylePara(range.create(), {
2386
+ lineHeight: value
2387
+ });
2388
+ };
2389
+
2390
+ /**
2391
+ * unlink
2392
+ * @param {jQuery} $editable
2393
+ */
2394
+ this.unlink = function ($editable) {
2395
+ var rng = range.create();
2396
+ if (rng.isOnAnchor()) {
2397
+ recordUndo($editable);
2398
+
2399
+ var anchor = dom.ancestor(rng.sc, dom.isAnchor);
2400
+ rng = range.createFromNode(anchor);
2401
+ rng.select();
2402
+ document.execCommand('unlink');
2403
+ }
2404
+ };
2405
+
2406
+ /**
2407
+ * create link
2408
+ *
2409
+ * @param {jQuery} $editable
2410
+ * @param {Object} linkInfo
2411
+ * @param {Object} options
2412
+ */
2413
+ this.createLink = function ($editable, linkInfo, options) {
2414
+ var linkUrl = linkInfo.url;
2415
+ var linkText = linkInfo.text;
2416
+ var isNewWindow = linkInfo.newWindow;
2417
+ var rng = linkInfo.range;
2418
+
2419
+ recordUndo($editable);
2420
+
2421
+ if (options.onCreateLink) {
2422
+ linkUrl = options.onCreateLink(linkUrl);
2423
+ }
2424
+
2425
+ rng = rng.deleteContents();
2426
+
2427
+ // Create a new link when there is no anchor on range.
2428
+ var anchor = rng.insertNode($('<A>' + linkText + '</A>')[0], true);
2429
+ $(anchor).attr({
2430
+ href: linkUrl,
2431
+ target: isNewWindow ? '_blank' : ''
2432
+ });
2433
+
2434
+ rng = range.createFromNode(anchor);
2435
+ rng.select();
2436
+ };
2437
+
2438
+ /**
2439
+ * returns link info
2440
+ *
2441
+ * @return {Object}
2442
+ */
2443
+ this.getLinkInfo = function ($editable) {
2444
+ $editable.focus();
2445
+
2446
+ var rng = range.create().expand(dom.isAnchor);
2447
+
2448
+ // Get the first anchor on range(for edit).
2449
+ var $anchor = $(list.head(rng.nodes(dom.isAnchor)));
2450
+
2451
+ return {
2452
+ range: rng,
2453
+ text: rng.toString(),
2454
+ isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : true,
2455
+ url: $anchor.length ? $anchor.attr('href') : ''
2456
+ };
2457
+ };
2458
+
2459
+ /**
2460
+ * get video info
2461
+ *
2462
+ * @param {jQuery} $editable
2463
+ * @return {Object}
2464
+ */
2465
+ this.getVideoInfo = function ($editable) {
2466
+ $editable.focus();
2467
+
2468
+ var rng = range.create();
2469
+
2470
+ if (rng.isOnAnchor()) {
2471
+ var anchor = dom.ancestor(rng.sc, dom.isAnchor);
2472
+ rng = range.createFromNode(anchor);
2473
+ }
2474
+
2475
+ return {
2476
+ text: rng.toString()
2477
+ };
2478
+ };
2479
+
2480
+ this.color = function ($editable, sObjColor) {
2481
+ var oColor = JSON.parse(sObjColor);
2482
+ var foreColor = oColor.foreColor, backColor = oColor.backColor;
2483
+
2484
+ recordUndo($editable);
2485
+
2486
+ if (foreColor) { document.execCommand('foreColor', false, foreColor); }
2487
+ if (backColor) { document.execCommand('backColor', false, backColor); }
2488
+ };
2489
+
2490
+ this.insertTable = function ($editable, sDim) {
2491
+ recordUndo($editable);
2492
+
2493
+ var dimension = sDim.split('x');
2494
+ var rng = range.create();
2495
+ rng = rng.deleteContents();
2496
+ rng.insertNode(table.createTable(dimension[0], dimension[1]));
2497
+ };
2498
+
2499
+ /**
2500
+ * @param {jQuery} $editable
2501
+ * @param {String} value
2502
+ * @param {jQuery} $target
2503
+ */
2504
+ this.floatMe = function ($editable, value, $target) {
2505
+ recordUndo($editable);
2506
+
2507
+ $target.css('float', value);
2508
+ };
2509
+
2510
+ /**
2511
+ * resize overlay element
2512
+ * @param {jQuery} $editable
2513
+ * @param {String} value
2514
+ * @param {jQuery} $target - target element
2515
+ */
2516
+ this.resize = function ($editable, value, $target) {
2517
+ recordUndo($editable);
2518
+
2519
+ $target.css({
2520
+ width: $editable.width() * value + 'px',
2521
+ height: ''
2522
+ });
2523
+ };
2524
+
2525
+ /**
2526
+ * @param {Position} pos
2527
+ * @param {jQuery} $target - target element
2528
+ * @param {Boolean} [bKeepRatio] - keep ratio
2529
+ */
2530
+ this.resizeTo = function (pos, $target, bKeepRatio) {
2531
+ var imageSize;
2532
+ if (bKeepRatio) {
2533
+ var newRatio = pos.y / pos.x;
2534
+ var ratio = $target.data('ratio');
2535
+ imageSize = {
2536
+ width: ratio > newRatio ? pos.x : pos.y / ratio,
2537
+ height: ratio > newRatio ? pos.x * ratio : pos.y
2538
+ };
2539
+ } else {
2540
+ imageSize = {
2541
+ width: pos.x,
2542
+ height: pos.y
2543
+ };
2544
+ }
2545
+
2546
+ $target.css(imageSize);
2547
+ };
2548
+
2549
+ /**
2550
+ * remove media object
2551
+ *
2552
+ * @param {jQuery} $editable
2553
+ * @param {String} value - dummy argument (for keep interface)
2554
+ * @param {jQuery} $target - target element
2555
+ */
2556
+ this.removeMedia = function ($editable, value, $target) {
2557
+ recordUndo($editable);
2558
+
2559
+ $target.detach();
2560
+ };
2561
+ };
2562
+
2563
+ /**
2564
+ * History
2565
+ * @class
2566
+ */
2567
+ var History = function () {
2568
+ var undoStack = [], redoStack = [];
2569
+
2570
+ var makeSnapshot = function ($editable) {
2571
+ var editable = $editable[0];
2572
+ var rng = range.create();
2573
+
2574
+ return {
2575
+ contents: $editable.html(),
2576
+ bookmark: rng.bookmark(editable),
2577
+ scrollTop: $editable.scrollTop()
2578
+ };
2579
+ };
2580
+
2581
+ var applySnapshot = function ($editable, snapshot) {
2582
+ $editable.html(snapshot.contents).scrollTop(snapshot.scrollTop);
2583
+ range.createFromBookmark($editable[0], snapshot.bookmark).select();
2584
+ };
2585
+
2586
+ this.undo = function ($editable) {
2587
+ var snapshot = makeSnapshot($editable);
2588
+ if (!undoStack.length) {
2589
+ return;
2590
+ }
2591
+ applySnapshot($editable, undoStack.pop());
2592
+ redoStack.push(snapshot);
2593
+ };
2594
+
2595
+ this.redo = function ($editable) {
2596
+ var snapshot = makeSnapshot($editable);
2597
+ if (!redoStack.length) {
2598
+ return;
2599
+ }
2600
+ applySnapshot($editable, redoStack.pop());
2601
+ undoStack.push(snapshot);
2602
+ };
2603
+
2604
+ this.recordUndo = function ($editable) {
2605
+ redoStack = [];
2606
+ undoStack.push(makeSnapshot($editable));
2607
+ };
2608
+ };
2609
+
2610
+ /**
2611
+ * Button
2612
+ */
2613
+ var Button = function () {
2614
+ /**
2615
+ * update button status
2616
+ *
2617
+ * @param {jQuery} $container
2618
+ * @param {Object} styleInfo
2619
+ */
2620
+ this.update = function ($container, styleInfo) {
2621
+ /**
2622
+ * handle dropdown's check mark (for fontname, fontsize, lineHeight).
2623
+ * @param {jQuery} $btn
2624
+ * @param {Number} value
2625
+ */
2626
+ var checkDropdownMenu = function ($btn, value) {
2627
+ $btn.find('.dropdown-menu li a').each(function () {
2628
+ // always compare string to avoid creating another func.
2629
+ var isChecked = ($(this).data('value') + '') === (value + '');
2630
+ this.className = isChecked ? 'checked' : '';
2631
+ });
2632
+ };
2633
+
2634
+ /**
2635
+ * update button state(active or not).
2636
+ *
2637
+ * @param {String} selector
2638
+ * @param {Function} pred
2639
+ */
2640
+ var btnState = function (selector, pred) {
2641
+ var $btn = $container.find(selector);
2642
+ $btn.toggleClass('active', pred());
2643
+ };
2644
+
2645
+ // fontname
2646
+ var $fontname = $container.find('.note-fontname');
2647
+ if ($fontname.length) {
2648
+ var selectedFont = styleInfo['font-family'];
2649
+ if (!!selectedFont) {
2650
+ selectedFont = list.head(selectedFont.split(','));
2651
+ selectedFont = selectedFont.replace(/\'/g, '');
2652
+ $fontname.find('.note-current-fontname').text(selectedFont);
2653
+ checkDropdownMenu($fontname, selectedFont);
2654
+ }
2655
+ }
2656
+
2657
+ // fontsize
2658
+ var $fontsize = $container.find('.note-fontsize');
2659
+ $fontsize.find('.note-current-fontsize').text(styleInfo['font-size']);
2660
+ checkDropdownMenu($fontsize, parseFloat(styleInfo['font-size']));
2661
+
2662
+ // lineheight
2663
+ var $lineHeight = $container.find('.note-height');
2664
+ checkDropdownMenu($lineHeight, parseFloat(styleInfo['line-height']));
2665
+
2666
+ btnState('button[data-event="bold"]', function () {
2667
+ return styleInfo['font-bold'] === 'bold';
2668
+ });
2669
+ btnState('button[data-event="italic"]', function () {
2670
+ return styleInfo['font-italic'] === 'italic';
2671
+ });
2672
+ btnState('button[data-event="underline"]', function () {
2673
+ return styleInfo['font-underline'] === 'underline';
2674
+ });
2675
+ btnState('button[data-event="strikethrough"]', function () {
2676
+ return styleInfo['font-strikethrough'] === 'strikethrough';
2677
+ });
2678
+ btnState('button[data-event="superscript"]', function () {
2679
+ return styleInfo['font-superscript'] === 'superscript';
2680
+ });
2681
+ btnState('button[data-event="subscript"]', function () {
2682
+ return styleInfo['font-subscript'] === 'subscript';
2683
+ });
2684
+ btnState('button[data-event="justifyLeft"]', function () {
2685
+ return styleInfo['text-align'] === 'left' || styleInfo['text-align'] === 'start';
2686
+ });
2687
+ btnState('button[data-event="justifyCenter"]', function () {
2688
+ return styleInfo['text-align'] === 'center';
2689
+ });
2690
+ btnState('button[data-event="justifyRight"]', function () {
2691
+ return styleInfo['text-align'] === 'right';
2692
+ });
2693
+ btnState('button[data-event="justifyFull"]', function () {
2694
+ return styleInfo['text-align'] === 'justify';
2695
+ });
2696
+ btnState('button[data-event="insertUnorderedList"]', function () {
2697
+ return styleInfo['list-style'] === 'unordered';
2698
+ });
2699
+ btnState('button[data-event="insertOrderedList"]', function () {
2700
+ return styleInfo['list-style'] === 'ordered';
2701
+ });
2702
+ };
2703
+
2704
+ /**
2705
+ * update recent color
2706
+ *
2707
+ * @param {Node} button
2708
+ * @param {String} eventName
2709
+ * @param {value} value
2710
+ */
2711
+ this.updateRecentColor = function (button, eventName, value) {
2712
+ var $color = $(button).closest('.note-color');
2713
+ var $recentColor = $color.find('.note-recent-color');
2714
+ var colorInfo = JSON.parse($recentColor.attr('data-value'));
2715
+ colorInfo[eventName] = value;
2716
+ $recentColor.attr('data-value', JSON.stringify(colorInfo));
2717
+ var sKey = eventName === 'backColor' ? 'background-color' : 'color';
2718
+ $recentColor.find('i').css(sKey, value);
2719
+ };
2720
+ };
2721
+
2722
+ /**
2723
+ * Toolbar
2724
+ */
2725
+ var Toolbar = function () {
2726
+ var button = new Button();
2727
+
2728
+ this.update = function ($toolbar, styleInfo) {
2729
+ button.update($toolbar, styleInfo);
2730
+ };
2731
+
2732
+ /**
2733
+ * @param {Node} button
2734
+ * @param {String} eventName
2735
+ * @param {String} value
2736
+ */
2737
+ this.updateRecentColor = function (buttonNode, eventName, value) {
2738
+ button.updateRecentColor(buttonNode, eventName, value);
2739
+ };
2740
+
2741
+ /**
2742
+ * activate buttons exclude codeview
2743
+ * @param {jQuery} $toolbar
2744
+ */
2745
+ this.activate = function ($toolbar) {
2746
+ $toolbar.find('button')
2747
+ .not('button[data-event="codeview"]')
2748
+ .removeClass('disabled');
2749
+ };
2750
+
2751
+ /**
2752
+ * deactivate buttons exclude codeview
2753
+ * @param {jQuery} $toolbar
2754
+ */
2755
+ this.deactivate = function ($toolbar) {
2756
+ $toolbar.find('button')
2757
+ .not('button[data-event="codeview"]')
2758
+ .addClass('disabled');
2759
+ };
2760
+
2761
+ this.updateFullscreen = function ($container, bFullscreen) {
2762
+ var $btn = $container.find('button[data-event="fullscreen"]');
2763
+ $btn.toggleClass('active', bFullscreen);
2764
+ };
2765
+
2766
+ this.updateCodeview = function ($container, isCodeview) {
2767
+ var $btn = $container.find('button[data-event="codeview"]');
2768
+ $btn.toggleClass('active', isCodeview);
2769
+ };
2770
+ };
2771
+
2772
+ /**
2773
+ * Popover (http://getbootstrap.com/javascript/#popovers)
2774
+ */
2775
+ var Popover = function () {
2776
+ var button = new Button();
2777
+
2778
+ /**
2779
+ * returns position from placeholder
2780
+ * @param {Node} placeholder
2781
+ * @param {Boolean} isAirMode
2782
+ */
2783
+ var posFromPlaceholder = function (placeholder, isAirMode) {
2784
+ var $placeholder = $(placeholder);
2785
+ var pos = isAirMode ? $placeholder.offset() : $placeholder.position();
2786
+ var height = $placeholder.outerHeight(true); // include margin
2787
+
2788
+ // popover below placeholder.
2789
+ return {
2790
+ left: pos.left,
2791
+ top: pos.top + height
2792
+ };
2793
+ };
2794
+
2795
+ /**
2796
+ * show popover
2797
+ * @param {jQuery} popover
2798
+ * @param {Position} pos
2799
+ */
2800
+ var showPopover = function ($popover, pos) {
2801
+ $popover.css({
2802
+ display: 'block',
2803
+ left: pos.left,
2804
+ top: pos.top
2805
+ });
2806
+ };
2807
+
2808
+ var PX_POPOVER_ARROW_OFFSET_X = 20;
2809
+
2810
+ /**
2811
+ * update current state
2812
+ * @param {jQuery} $popover - popover container
2813
+ * @param {Object} styleInfo - style object
2814
+ * @param {Boolean} isAirMode
2815
+ */
2816
+ this.update = function ($popover, styleInfo, isAirMode) {
2817
+ button.update($popover, styleInfo);
2818
+
2819
+ var $linkPopover = $popover.find('.note-link-popover');
2820
+ if (styleInfo.anchor) {
2821
+ var $anchor = $linkPopover.find('a');
2822
+ var href = $(styleInfo.anchor).attr('href');
2823
+ $anchor.attr('href', href).html(href);
2824
+ showPopover($linkPopover, posFromPlaceholder(styleInfo.anchor, isAirMode));
2825
+ } else {
2826
+ $linkPopover.hide();
2827
+ }
2828
+
2829
+ var $imagePopover = $popover.find('.note-image-popover');
2830
+ if (styleInfo.image) {
2831
+ showPopover($imagePopover, posFromPlaceholder(styleInfo.image, isAirMode));
2832
+ } else {
2833
+ $imagePopover.hide();
2834
+ }
2835
+
2836
+ var $airPopover = $popover.find('.note-air-popover');
2837
+ if (isAirMode && !styleInfo.range.isCollapsed()) {
2838
+ var bnd = func.rect2bnd(list.last(styleInfo.range.getClientRects()));
2839
+ showPopover($airPopover, {
2840
+ left: Math.max(bnd.left + bnd.width / 2 - PX_POPOVER_ARROW_OFFSET_X, 0),
2841
+ top: bnd.top + bnd.height
2842
+ });
2843
+ } else {
2844
+ $airPopover.hide();
2845
+ }
2846
+ };
2847
+
2848
+ /**
2849
+ * @param {Node} button
2850
+ * @param {String} eventName
2851
+ * @param {String} value
2852
+ */
2853
+ this.updateRecentColor = function (button, eventName, value) {
2854
+ button.updateRecentColor(button, eventName, value);
2855
+ };
2856
+
2857
+ /**
2858
+ * hide all popovers
2859
+ * @param {jQuery} $popover - popover contaienr
2860
+ */
2861
+ this.hide = function ($popover) {
2862
+ $popover.children().hide();
2863
+ };
2864
+ };
2865
+
2866
+ /**
2867
+ * Handle
2868
+ */
2869
+ var Handle = function () {
2870
+ /**
2871
+ * update handle
2872
+ * @param {jQuery} $handle
2873
+ * @param {Object} styleInfo
2874
+ * @param {Boolean} isAirMode
2875
+ */
2876
+ this.update = function ($handle, styleInfo, isAirMode) {
2877
+ var $selection = $handle.find('.note-control-selection');
2878
+ if (styleInfo.image) {
2879
+ var $image = $(styleInfo.image);
2880
+ var pos = isAirMode ? $image.offset() : $image.position();
2881
+
2882
+ // include margin
2883
+ var imageSize = {
2884
+ w: $image.outerWidth(true),
2885
+ h: $image.outerHeight(true)
2886
+ };
2887
+
2888
+ $selection.css({
2889
+ display: 'block',
2890
+ left: pos.left,
2891
+ top: pos.top,
2892
+ width: imageSize.w,
2893
+ height: imageSize.h
2894
+ }).data('target', styleInfo.image); // save current image element.
2895
+ var sizingText = imageSize.w + 'x' + imageSize.h;
2896
+ $selection.find('.note-control-selection-info').text(sizingText);
2897
+ } else {
2898
+ $selection.hide();
2899
+ }
2900
+ };
2901
+
2902
+ this.hide = function ($handle) {
2903
+ $handle.children().hide();
2904
+ };
2905
+ };
2906
+
2907
+ /**
2908
+ * Dialog
2909
+ *
2910
+ * @class
2911
+ */
2912
+ var Dialog = function () {
2913
+
2914
+ /**
2915
+ * toggle button status
2916
+ *
2917
+ * @param {jQuery} $btn
2918
+ * @param {Boolean} isEnable
2919
+ */
2920
+ var toggleBtn = function ($btn, isEnable) {
2921
+ $btn.toggleClass('disabled', !isEnable);
2922
+ $btn.attr('disabled', !isEnable);
2923
+ };
2924
+
2925
+ /**
2926
+ * show image dialog
2927
+ *
2928
+ * @param {jQuery} $editable
2929
+ * @param {jQuery} $dialog
2930
+ * @return {Promise}
2931
+ */
2932
+ this.showImageDialog = function ($editable, $dialog) {
2933
+ return $.Deferred(function (deferred) {
2934
+ var $imageDialog = $dialog.find('.note-image-dialog');
2935
+
2936
+ var $imageInput = $dialog.find('.note-image-input'),
2937
+ $imageUrl = $dialog.find('.note-image-url'),
2938
+ $imageBtn = $dialog.find('.note-image-btn');
2939
+
2940
+ $imageDialog.one('shown.bs.modal', function () {
2941
+ // Cloning imageInput to clear element.
2942
+ $imageInput.replaceWith($imageInput.clone()
2943
+ .on('change', function () {
2944
+ deferred.resolve(this.files);
2945
+ $imageDialog.modal('hide');
2946
+ })
2947
+ .val('')
2948
+ );
2949
+
2950
+ $imageBtn.click(function (event) {
2951
+ event.preventDefault();
2952
+
2953
+ deferred.resolve($imageUrl.val());
2954
+ $imageDialog.modal('hide');
2955
+ });
2956
+
2957
+ $imageUrl.on('keyup paste', function (event) {
2958
+ var url;
2959
+
2960
+ if (event.type === 'paste') {
2961
+ url = event.originalEvent.clipboardData.getData('text');
2962
+ } else {
2963
+ url = $imageUrl.val();
2964
+ }
2965
+
2966
+ toggleBtn($imageBtn, url);
2967
+ }).val('').trigger('focus');
2968
+ }).one('hidden.bs.modal', function () {
2969
+ $imageInput.off('change');
2970
+ $imageUrl.off('keyup paste');
2971
+ $imageBtn.off('click');
2972
+
2973
+ if (deferred.state() === 'pending') {
2974
+ deferred.reject();
2975
+ }
2976
+ }).modal('show');
2977
+ });
2978
+ };
2979
+
2980
+ /**
2981
+ * Show video dialog and set event handlers on dialog controls.
2982
+ *
2983
+ * @param {jQuery} $dialog
2984
+ * @param {Object} videoInfo
2985
+ * @return {Promise}
2986
+ */
2987
+ this.showVideoDialog = function ($editable, $dialog, videoInfo) {
2988
+ return $.Deferred(function (deferred) {
2989
+ var $videoDialog = $dialog.find('.note-video-dialog');
2990
+ var $videoUrl = $videoDialog.find('.note-video-url'),
2991
+ $videoBtn = $videoDialog.find('.note-video-btn');
2992
+
2993
+ $videoDialog.one('shown.bs.modal', function () {
2994
+ $videoUrl.val(videoInfo.text).keyup(function () {
2995
+ toggleBtn($videoBtn, $videoUrl.val());
2996
+ }).trigger('keyup').trigger('focus');
2997
+
2998
+ $videoBtn.click(function (event) {
2999
+ event.preventDefault();
3000
+
3001
+ deferred.resolve($videoUrl.val());
3002
+ $videoDialog.modal('hide');
3003
+ });
3004
+ }).one('hidden.bs.modal', function () {
3005
+ // dettach events
3006
+ $videoUrl.off('keyup');
3007
+ $videoBtn.off('click');
3008
+
3009
+ if (deferred.state() === 'pending') {
3010
+ deferred.reject();
3011
+ }
3012
+ }).modal('show');
3013
+ });
3014
+ };
3015
+
3016
+ /**
3017
+ * Show link dialog and set event handlers on dialog controls.
3018
+ *
3019
+ * @param {jQuery} $dialog
3020
+ * @param {Object} linkInfo
3021
+ * @return {Promise}
3022
+ */
3023
+ this.showLinkDialog = function ($editable, $dialog, linkInfo) {
3024
+ return $.Deferred(function (deferred) {
3025
+ var $linkDialog = $dialog.find('.note-link-dialog');
3026
+
3027
+ var $linkText = $linkDialog.find('.note-link-text'),
3028
+ $linkUrl = $linkDialog.find('.note-link-url'),
3029
+ $linkBtn = $linkDialog.find('.note-link-btn'),
3030
+ $openInNewWindow = $linkDialog.find('input[type=checkbox]');
3031
+
3032
+ $linkDialog.one('shown.bs.modal', function () {
3033
+ $linkText.val(linkInfo.text);
3034
+
3035
+ $linkText.keyup(function () {
3036
+ // if linktext was modified by keyup,
3037
+ // stop cloning text from linkUrl
3038
+ linkInfo.text = $linkText.val();
3039
+ });
3040
+
3041
+ // if no url was given, copy text to url
3042
+ if (!linkInfo.url) {
3043
+ linkInfo.url = linkInfo.text;
3044
+ toggleBtn($linkBtn, linkInfo.text);
3045
+ }
3046
+
3047
+ $linkUrl.keyup(function () {
3048
+ toggleBtn($linkBtn, $linkUrl.val());
3049
+ // display same link on `Text to display` input
3050
+ // when create a new link
3051
+ if (!linkInfo.text) {
3052
+ $linkText.val($linkUrl.val());
3053
+ }
3054
+ }).val(linkInfo.url).trigger('focus').trigger('select');
3055
+
3056
+ $openInNewWindow.prop('checked', linkInfo.newWindow);
3057
+
3058
+ $linkBtn.one('click', function (event) {
3059
+ event.preventDefault();
3060
+
3061
+ deferred.resolve({
3062
+ range: linkInfo.range,
3063
+ url: $linkUrl.val(),
3064
+ text: $linkText.val(),
3065
+ newWindow: $openInNewWindow.is(':checked')
3066
+ });
3067
+ $linkDialog.modal('hide');
3068
+ });
3069
+ }).one('hidden.bs.modal', function () {
3070
+ // dettach events
3071
+ $linkText.off('keyup');
3072
+ $linkUrl.off('keyup');
3073
+ $linkBtn.off('click');
3074
+
3075
+ if (deferred.state() === 'pending') {
3076
+ deferred.reject();
3077
+ }
3078
+ }).modal('show');
3079
+ }).promise();
3080
+ };
3081
+
3082
+ /**
3083
+ * show help dialog
3084
+ *
3085
+ * @param {jQuery} $dialog
3086
+ */
3087
+ this.showHelpDialog = function ($editable, $dialog) {
3088
+ return $.Deferred(function (deferred) {
3089
+ var $helpDialog = $dialog.find('.note-help-dialog');
3090
+
3091
+ $helpDialog.one('hidden.bs.modal', function () {
3092
+ deferred.resolve();
3093
+ }).modal('show');
3094
+ }).promise();
3095
+ };
3096
+ };
3097
+
3098
+
3099
+ var CodeMirror;
3100
+ if (agent.hasCodeMirror) {
3101
+ if (agent.isSupportAmd) {
3102
+ require(['CodeMirror'], function (cm) {
3103
+ CodeMirror = cm;
3104
+ });
3105
+ } else {
3106
+ CodeMirror = window.CodeMirror;
3107
+ }
3108
+ }
3109
+
3110
+ /**
3111
+ * EventHandler
3112
+ */
3113
+ var EventHandler = function () {
3114
+ var $window = $(window);
3115
+ var $document = $(document);
3116
+ var $scrollbar = $('html, body');
3117
+
3118
+ var editor = new Editor();
3119
+ var toolbar = new Toolbar(), popover = new Popover();
3120
+ var handle = new Handle(), dialog = new Dialog();
3121
+
3122
+ /**
3123
+ * returns makeLayoutInfo from editor's descendant node.
3124
+ *
3125
+ * @param {Node} descendant
3126
+ * @returns {Object}
3127
+ */
3128
+ var makeLayoutInfo = function (descendant) {
3129
+ var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
3130
+
3131
+ if (!$target.length) { return null; }
3132
+
3133
+ var $editor;
3134
+ if ($target.is('.note-editor, .note-air-editor')) {
3135
+ $editor = $target;
3136
+ } else {
3137
+ $editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
3138
+ }
3139
+
3140
+ return dom.buildLayoutInfo($editor);
3141
+ };
3142
+
3143
+ /**
3144
+ * insert Images from file array.
3145
+ *
3146
+ * @param {jQuery} $editable
3147
+ * @param {File[]} files
3148
+ */
3149
+ var insertImages = function ($editable, files) {
3150
+ editor.restoreRange($editable);
3151
+ var callbacks = $editable.data('callbacks');
3152
+
3153
+ // If onImageUpload options setted
3154
+ if (callbacks.onImageUpload) {
3155
+ callbacks.onImageUpload(files, editor, $editable);
3156
+ // else insert Image as dataURL
3157
+ } else {
3158
+ $.each(files, function (idx, file) {
3159
+ var filename = file.name;
3160
+ async.readFileAsDataURL(file).then(function (sDataURL) {
3161
+ editor.insertImage($editable, sDataURL, filename);
3162
+ }).fail(function () {
3163
+ if (callbacks.onImageUploadError) {
3164
+ callbacks.onImageUploadError();
3165
+ }
3166
+ });
3167
+ });
3168
+ }
3169
+ };
3170
+
3171
+ var commands = {
3172
+ /**
3173
+ * @param {Object} layoutInfo
3174
+ */
3175
+ showLinkDialog: function (layoutInfo) {
3176
+ var $editor = layoutInfo.editor(),
3177
+ $dialog = layoutInfo.dialog(),
3178
+ $editable = layoutInfo.editable(),
3179
+ linkInfo = editor.getLinkInfo($editable);
3180
+
3181
+ var options = $editor.data('options');
3182
+
3183
+ editor.saveRange($editable);
3184
+ dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) {
3185
+ editor.restoreRange($editable);
3186
+ editor.createLink($editable, linkInfo, options);
3187
+ // hide popover after creating link
3188
+ popover.hide(layoutInfo.popover());
3189
+ }).fail(function () {
3190
+ editor.restoreRange($editable);
3191
+ });
3192
+ },
3193
+
3194
+ /**
3195
+ * @param {Object} layoutInfo
3196
+ */
3197
+ showImageDialog: function (layoutInfo) {
3198
+ var $dialog = layoutInfo.dialog(),
3199
+ $editable = layoutInfo.editable();
3200
+
3201
+ editor.saveRange($editable);
3202
+ dialog.showImageDialog($editable, $dialog).then(function (data) {
3203
+ editor.restoreRange($editable);
3204
+
3205
+ if (typeof data === 'string') {
3206
+ // image url
3207
+ editor.insertImage($editable, data);
3208
+ } else {
3209
+ // array of files
3210
+ insertImages($editable, data);
3211
+ }
3212
+ }).fail(function () {
3213
+ editor.restoreRange($editable);
3214
+ });
3215
+ },
3216
+
3217
+ /**
3218
+ * @param {Object} layoutInfo
3219
+ */
3220
+ showVideoDialog: function (layoutInfo) {
3221
+ var $dialog = layoutInfo.dialog(),
3222
+ $editable = layoutInfo.editable(),
3223
+ videoInfo = editor.getVideoInfo($editable);
3224
+
3225
+ editor.saveRange($editable);
3226
+ dialog.showVideoDialog($editable, $dialog, videoInfo).then(function (sUrl) {
3227
+ editor.restoreRange($editable);
3228
+ editor.insertVideo($editable, sUrl);
3229
+ }).fail(function () {
3230
+ editor.restoreRange($editable);
3231
+ });
3232
+ },
3233
+
3234
+ /**
3235
+ * @param {Object} layoutInfo
3236
+ */
3237
+ showHelpDialog: function (layoutInfo) {
3238
+ var $dialog = layoutInfo.dialog(),
3239
+ $editable = layoutInfo.editable();
3240
+
3241
+ editor.saveRange($editable);
3242
+ dialog.showHelpDialog($editable, $dialog).then(function () {
3243
+ editor.restoreRange($editable);
3244
+ });
3245
+ },
3246
+
3247
+ fullscreen: function (layoutInfo) {
3248
+ var $editor = layoutInfo.editor(),
3249
+ $toolbar = layoutInfo.toolbar(),
3250
+ $editable = layoutInfo.editable(),
3251
+ $codable = layoutInfo.codable();
3252
+
3253
+ var options = $editor.data('options');
3254
+
3255
+ var resize = function (size) {
3256
+ $editor.css('width', size.w);
3257
+ $editable.css('height', size.h);
3258
+ $codable.css('height', size.h);
3259
+ if ($codable.data('cmeditor')) {
3260
+ $codable.data('cmeditor').setsize(null, size.h);
3261
+ }
3262
+ };
3263
+
3264
+ $editor.toggleClass('fullscreen');
3265
+ var isFullscreen = $editor.hasClass('fullscreen');
3266
+ if (isFullscreen) {
3267
+ $editable.data('orgheight', $editable.css('height'));
3268
+
3269
+ $window.on('resize', function () {
3270
+ resize({
3271
+ w: $window.width(),
3272
+ h: $window.height() - $toolbar.outerHeight()
3273
+ });
3274
+ }).trigger('resize');
3275
+
3276
+ $scrollbar.css('overflow', 'hidden');
3277
+ } else {
3278
+ $window.off('resize');
3279
+ resize({
3280
+ w: options.width || '',
3281
+ h: $editable.data('orgheight')
3282
+ });
3283
+ $scrollbar.css('overflow', 'visible');
3284
+ }
3285
+
3286
+ toolbar.updateFullscreen($toolbar, isFullscreen);
3287
+ },
3288
+
3289
+ codeview: function (layoutInfo) {
3290
+ var $editor = layoutInfo.editor(),
3291
+ $toolbar = layoutInfo.toolbar(),
3292
+ $editable = layoutInfo.editable(),
3293
+ $codable = layoutInfo.codable(),
3294
+ $popover = layoutInfo.popover();
3295
+
3296
+ var options = $editor.data('options');
3297
+
3298
+ var cmEditor, server;
3299
+
3300
+ $editor.toggleClass('codeview');
3301
+
3302
+ var isCodeview = $editor.hasClass('codeview');
3303
+ if (isCodeview) {
3304
+ $codable.val($editable.html());
3305
+ $codable.height($editable.height());
3306
+ toolbar.deactivate($toolbar);
3307
+ popover.hide($popover);
3308
+ $codable.focus();
3309
+
3310
+ // activate CodeMirror as codable
3311
+ if (agent.hasCodeMirror) {
3312
+ cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
3313
+
3314
+ // CodeMirror TernServer
3315
+ if (options.codemirror.tern) {
3316
+ server = new CodeMirror.TernServer(options.codemirror.tern);
3317
+ cmEditor.ternServer = server;
3318
+ cmEditor.on('cursorActivity', function (cm) {
3319
+ server.updateArgHints(cm);
3320
+ });
3321
+ }
3322
+
3323
+ // CodeMirror hasn't Padding.
3324
+ cmEditor.setSize(null, $editable.outerHeight());
3325
+ // autoFormatRange If formatting included
3326
+ if (options.codemirror.autoFormatOnStart && cmEditor.autoFormatRange) {
3327
+ cmEditor.autoFormatRange({line: 0, ch: 0}, {
3328
+ line: cmEditor.lineCount(),
3329
+ ch: cmEditor.getTextArea().value.length
3330
+ });
3331
+ }
3332
+ $codable.data('cmEditor', cmEditor);
3333
+ }
3334
+ } else {
3335
+ // deactivate CodeMirror as codable
3336
+ if (agent.hasCodeMirror) {
3337
+ cmEditor = $codable.data('cmEditor');
3338
+ $codable.val(cmEditor.getValue());
3339
+ cmEditor.toTextArea();
3340
+ }
3341
+
3342
+ $editable.html($codable.val() || dom.emptyPara);
3343
+ $editable.height(options.height ? $codable.height() : 'auto');
3344
+
3345
+ toolbar.activate($toolbar);
3346
+ $editable.focus();
3347
+ }
3348
+
3349
+ toolbar.updateCodeview(layoutInfo.toolbar(), isCodeview);
3350
+ }
3351
+ };
3352
+
3353
+ var hMousedown = function (event) {
3354
+ //preventDefault Selection for FF, IE8+
3355
+ if (dom.isImg(event.target)) {
3356
+ event.preventDefault();
3357
+ }
3358
+ };
3359
+
3360
+ var hToolbarAndPopoverUpdate = function (event) {
3361
+ // delay for range after mouseup
3362
+ setTimeout(function () {
3363
+ var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
3364
+ var styleInfo = editor.currentStyle(event.target);
3365
+ if (!styleInfo) { return; }
3366
+
3367
+ var isAirMode = layoutInfo.editor().data('options').airMode;
3368
+ if (!isAirMode) {
3369
+ toolbar.update(layoutInfo.toolbar(), styleInfo);
3370
+ }
3371
+
3372
+ popover.update(layoutInfo.popover(), styleInfo, isAirMode);
3373
+ handle.update(layoutInfo.handle(), styleInfo, isAirMode);
3374
+ }, 0);
3375
+ };
3376
+
3377
+ var hScroll = function (event) {
3378
+ var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
3379
+ //hide popover and handle when scrolled
3380
+ popover.hide(layoutInfo.popover());
3381
+ handle.hide(layoutInfo.handle());
3382
+ };
3383
+
3384
+ /**
3385
+ * paste clipboard image
3386
+ *
3387
+ * @param {Event} event
3388
+ */
3389
+ var hPasteClipboardImage = function (event) {
3390
+ var clipboardData = event.originalEvent.clipboardData;
3391
+ if (!clipboardData || !clipboardData.items || !clipboardData.items.length) {
3392
+ return;
3393
+ }
3394
+
3395
+ var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
3396
+ var item = list.head(clipboardData.items);
3397
+ var isClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
3398
+
3399
+ if (isClipboardImage) {
3400
+ insertImages(layoutInfo.editable(), [item.getAsFile()]);
3401
+ }
3402
+ };
3403
+
3404
+ /**
3405
+ * `mousedown` event handler on $handle
3406
+ * - controlSizing: resize image
3407
+ *
3408
+ * @param {MouseEvent} event
3409
+ */
3410
+ var hHandleMousedown = function (event) {
3411
+ if (dom.isControlSizing(event.target)) {
3412
+ event.preventDefault();
3413
+ event.stopPropagation();
3414
+
3415
+ var layoutInfo = makeLayoutInfo(event.target),
3416
+ $handle = layoutInfo.handle(), $popover = layoutInfo.popover(),
3417
+ $editable = layoutInfo.editable(),
3418
+ $editor = layoutInfo.editor();
3419
+
3420
+ var target = $handle.find('.note-control-selection').data('target'),
3421
+ $target = $(target), posStart = $target.offset(),
3422
+ scrollTop = $document.scrollTop();
3423
+
3424
+ var isAirMode = $editor.data('options').airMode;
3425
+
3426
+ $document.on('mousemove', function (event) {
3427
+ editor.resizeTo({
3428
+ x: event.clientX - posStart.left,
3429
+ y: event.clientY - (posStart.top - scrollTop)
3430
+ }, $target, !event.shiftKey);
3431
+
3432
+ handle.update($handle, {image: target}, isAirMode);
3433
+ popover.update($popover, {image: target}, isAirMode);
3434
+ }).one('mouseup', function () {
3435
+ $document.off('mousemove');
3436
+ });
3437
+
3438
+ if (!$target.data('ratio')) { // original ratio.
3439
+ $target.data('ratio', $target.height() / $target.width());
3440
+ }
3441
+
3442
+ editor.recordUndo($editable);
3443
+ }
3444
+ };
3445
+
3446
+ var hToolbarAndPopoverMousedown = function (event) {
3447
+ // prevent default event when insertTable (FF, Webkit)
3448
+ var $btn = $(event.target).closest('[data-event]');
3449
+ if ($btn.length) {
3450
+ event.preventDefault();
3451
+ }
3452
+ };
3453
+
3454
+ var hToolbarAndPopoverClick = function (event) {
3455
+ var $btn = $(event.target).closest('[data-event]');
3456
+
3457
+ if ($btn.length) {
3458
+ var eventName = $btn.attr('data-event'),
3459
+ value = $btn.attr('data-value');
3460
+
3461
+ var layoutInfo = makeLayoutInfo(event.target);
3462
+
3463
+ event.preventDefault();
3464
+
3465
+ // before command: detect control selection element($target)
3466
+ var $target;
3467
+ if ($.inArray(eventName, ['resize', 'floatMe', 'removeMedia']) !== -1) {
3468
+ var $selection = layoutInfo.handle().find('.note-control-selection');
3469
+ $target = $($selection.data('target'));
3470
+ }
3471
+
3472
+ if (editor[eventName]) { // on command
3473
+ var $editable = layoutInfo.editable();
3474
+ $editable.trigger('focus');
3475
+ editor[eventName]($editable, value, $target);
3476
+ } else if (commands[eventName]) {
3477
+ commands[eventName].call(this, layoutInfo);
3478
+ }
3479
+
3480
+ // after command
3481
+ if ($.inArray(eventName, ['backColor', 'foreColor']) !== -1) {
3482
+ var options = layoutInfo.editor().data('options', options);
3483
+ var module = options.airMode ? popover : toolbar;
3484
+ module.updateRecentColor(list.head($btn), eventName, value);
3485
+ }
3486
+
3487
+ hToolbarAndPopoverUpdate(event);
3488
+ }
3489
+ };
3490
+
3491
+ var EDITABLE_PADDING = 24;
3492
+ /**
3493
+ * `mousedown` event handler on statusbar
3494
+ *
3495
+ * @param {MouseEvent} event
3496
+ */
3497
+ var hStatusbarMousedown = function (event) {
3498
+ event.preventDefault();
3499
+ event.stopPropagation();
3500
+
3501
+ var $editable = makeLayoutInfo(event.target).editable();
3502
+ var nEditableTop = $editable.offset().top - $document.scrollTop();
3503
+
3504
+ var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
3505
+ var options = layoutInfo.editor().data('options');
3506
+
3507
+ $document.on('mousemove', function (event) {
3508
+ var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING);
3509
+
3510
+ nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight;
3511
+ nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight;
3512
+
3513
+ $editable.height(nHeight);
3514
+ }).one('mouseup', function () {
3515
+ $document.off('mousemove');
3516
+ });
3517
+ };
3518
+
3519
+ var PX_PER_EM = 18;
3520
+ var hDimensionPickerMove = function (event, options) {
3521
+ var $picker = $(event.target.parentNode); // target is mousecatcher
3522
+ var $dimensionDisplay = $picker.next();
3523
+ var $catcher = $picker.find('.note-dimension-picker-mousecatcher');
3524
+ var $highlighted = $picker.find('.note-dimension-picker-highlighted');
3525
+ var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');
3526
+
3527
+ var posOffset;
3528
+ // HTML5 with jQuery - e.offsetX is undefined in Firefox
3529
+ if (event.offsetX === undefined) {
3530
+ var posCatcher = $(event.target).offset();
3531
+ posOffset = {
3532
+ x: event.pageX - posCatcher.left,
3533
+ y: event.pageY - posCatcher.top
3534
+ };
3535
+ } else {
3536
+ posOffset = {
3537
+ x: event.offsetX,
3538
+ y: event.offsetY
3539
+ };
3540
+ }
3541
+
3542
+ var dim = {
3543
+ c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
3544
+ r: Math.ceil(posOffset.y / PX_PER_EM) || 1
3545
+ };
3546
+
3547
+ $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });
3548
+ $catcher.attr('data-value', dim.c + 'x' + dim.r);
3549
+
3550
+ if (3 < dim.c && dim.c < options.insertTableMaxSize.col) {
3551
+ $unhighlighted.css({ width: dim.c + 1 + 'em'});
3552
+ }
3553
+
3554
+ if (3 < dim.r && dim.r < options.insertTableMaxSize.row) {
3555
+ $unhighlighted.css({ height: dim.r + 1 + 'em'});
3556
+ }
3557
+
3558
+ $dimensionDisplay.html(dim.c + ' x ' + dim.r);
3559
+ };
3560
+
3561
+ /**
3562
+ * Drag and Drop Events
3563
+ *
3564
+ * @param {Object} layoutInfo - layout Informations
3565
+ * @param {Boolean} disableDragAndDrop
3566
+ */
3567
+ var handleDragAndDropEvent = function (layoutInfo, disableDragAndDrop) {
3568
+ if (disableDragAndDrop) {
3569
+ // prevent default drop event
3570
+ $document.on('drop', function (e) {
3571
+ e.preventDefault();
3572
+ });
3573
+ } else {
3574
+ attachDragAndDropEvent(layoutInfo);
3575
+ }
3576
+ };
3577
+
3578
+ /**
3579
+ * attach Drag and Drop Events
3580
+ *
3581
+ * @param {Object} layoutInfo - layout Informations
3582
+ */
3583
+ var attachDragAndDropEvent = function (layoutInfo) {
3584
+ var collection = $(),
3585
+ $dropzone = layoutInfo.dropzone,
3586
+ $dropzoneMessage = layoutInfo.dropzone.find('.note-dropzone-message');
3587
+
3588
+ // show dropzone on dragenter when dragging a object to document.
3589
+ $document.on('dragenter', function (e) {
3590
+ var isCodeview = layoutInfo.editor.hasClass('codeview');
3591
+ if (!isCodeview && !collection.length) {
3592
+ layoutInfo.editor.addClass('dragover');
3593
+ $dropzone.width(layoutInfo.editor.width());
3594
+ $dropzone.height(layoutInfo.editor.height());
3595
+ $dropzoneMessage.text('Drag Image Here');
3596
+ }
3597
+ collection = collection.add(e.target);
3598
+ }).on('dragleave', function (e) {
3599
+ collection = collection.not(e.target);
3600
+ if (!collection.length) {
3601
+ layoutInfo.editor.removeClass('dragover');
3602
+ }
3603
+ }).on('drop', function () {
3604
+ collection = $();
3605
+ layoutInfo.editor.removeClass('dragover');
3606
+ });
3607
+
3608
+ // change dropzone's message on hover.
3609
+ $dropzone.on('dragenter', function () {
3610
+ $dropzone.addClass('hover');
3611
+ $dropzoneMessage.text('Drop Image');
3612
+ }).on('dragleave', function () {
3613
+ $dropzone.removeClass('hover');
3614
+ $dropzoneMessage.text('Drag Image Here');
3615
+ });
3616
+
3617
+ // attach dropImage
3618
+ $dropzone.on('drop', function (event) {
3619
+ event.preventDefault();
3620
+
3621
+ var dataTransfer = event.originalEvent.dataTransfer;
3622
+ if (dataTransfer && dataTransfer.files) {
3623
+ var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
3624
+ layoutInfo.editable().focus();
3625
+ insertImages(layoutInfo.editable(), dataTransfer.files);
3626
+ }
3627
+ }).on('dragover', false); // prevent default dragover event
3628
+ };
3629
+
3630
+
3631
+ /**
3632
+ * bind KeyMap on keydown
3633
+ *
3634
+ * @param {Object} layoutInfo
3635
+ * @param {Object} keyMap
3636
+ */
3637
+ this.bindKeyMap = function (layoutInfo, keyMap) {
3638
+ var $editor = layoutInfo.editor;
3639
+ var $editable = layoutInfo.editable;
3640
+
3641
+ layoutInfo = makeLayoutInfo($editable);
3642
+
3643
+ $editable.on('keydown', function (event) {
3644
+ var aKey = [];
3645
+
3646
+ // modifier
3647
+ if (event.metaKey) { aKey.push('CMD'); }
3648
+ if (event.ctrlKey && !event.altKey) { aKey.push('CTRL'); }
3649
+ if (event.shiftKey) { aKey.push('SHIFT'); }
3650
+
3651
+ // keycode
3652
+ var keyName = key.nameFromCode[event.keyCode];
3653
+ if (keyName) { aKey.push(keyName); }
3654
+
3655
+ var eventName = keyMap[aKey.join('+')];
3656
+ if (eventName) {
3657
+ event.preventDefault();
3658
+
3659
+ if (editor[eventName]) {
3660
+ editor[eventName]($editable, $editor.data('options'));
3661
+ } else if (commands[eventName]) {
3662
+ commands[eventName].call(this, layoutInfo);
3663
+ }
3664
+ } else if (key.isEdit(event.keyCode)) {
3665
+ editor.recordUndo($editable);
3666
+ }
3667
+ });
3668
+ };
3669
+
3670
+ /**
3671
+ * attach eventhandler
3672
+ *
3673
+ * @param {Object} layoutInfo - layout Informations
3674
+ * @param {Object} options - user options include custom event handlers
3675
+ * @param {Function} options.enter - enter key handler
3676
+ */
3677
+ this.attach = function (layoutInfo, options) {
3678
+ // handlers for editable
3679
+ this.bindKeyMap(layoutInfo, options.keyMap[agent.isMac ? 'mac' : 'pc']);
3680
+ layoutInfo.editable.on('mousedown', hMousedown);
3681
+ layoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
3682
+ layoutInfo.editable.on('scroll', hScroll);
3683
+ layoutInfo.editable.on('paste', hPasteClipboardImage);
3684
+
3685
+ // handler for handle and popover
3686
+ layoutInfo.handle.on('mousedown', hHandleMousedown);
3687
+ layoutInfo.popover.on('click', hToolbarAndPopoverClick);
3688
+ layoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
3689
+
3690
+ // handlers for frame mode (toolbar, statusbar)
3691
+ if (!options.airMode) {
3692
+ // handler for drag and drop
3693
+ handleDragAndDropEvent(layoutInfo, options.disableDragAndDrop);
3694
+
3695
+ // handler for toolbar
3696
+ layoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
3697
+ layoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
3698
+
3699
+ // handler for statusbar
3700
+ if (!options.disableResizeEditor) {
3701
+ layoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
3702
+ }
3703
+ }
3704
+
3705
+ // handler for table dimension
3706
+ var $catcherContainer = options.airMode ? layoutInfo.popover :
3707
+ layoutInfo.toolbar;
3708
+ var $catcher = $catcherContainer.find('.note-dimension-picker-mousecatcher');
3709
+ $catcher.css({
3710
+ width: options.insertTableMaxSize.col + 'em',
3711
+ height: options.insertTableMaxSize.row + 'em'
3712
+ }).on('mousemove', function (event) {
3713
+ hDimensionPickerMove(event, options);
3714
+ });
3715
+
3716
+ // save options on editor
3717
+ layoutInfo.editor.data('options', options);
3718
+
3719
+ // ret styleWithCSS for backColor / foreColor clearing with 'inherit'.
3720
+ if (options.styleWithSpan && !agent.isMSIE) {
3721
+ // protect FF Error: NS_ERROR_FAILURE: Failure
3722
+ setTimeout(function () {
3723
+ document.execCommand('styleWithCSS', 0, true);
3724
+ }, 0);
3725
+ }
3726
+
3727
+ // History
3728
+ layoutInfo.editable.data('NoteHistory', new History());
3729
+
3730
+ // basic event callbacks (lowercase)
3731
+ // enter, focus, blur, keyup, keydown
3732
+ if (options.onenter) {
3733
+ layoutInfo.editable.keypress(function (event) {
3734
+ if (event.keyCode === key.ENTER) { options.onenter(event); }
3735
+ });
3736
+ }
3737
+
3738
+ if (options.onfocus) { layoutInfo.editable.focus(options.onfocus); }
3739
+ if (options.onblur) { layoutInfo.editable.blur(options.onblur); }
3740
+ if (options.onkeyup) { layoutInfo.editable.keyup(options.onkeyup); }
3741
+ if (options.onkeydown) { layoutInfo.editable.keydown(options.onkeydown); }
3742
+ if (options.onpaste) { layoutInfo.editable.on('paste', options.onpaste); }
3743
+
3744
+ // callbacks for advanced features (camel)
3745
+ if (options.onToolbarClick) { layoutInfo.toolbar.click(options.onToolbarClick); }
3746
+ if (options.onChange) {
3747
+ var hChange = function () {
3748
+ options.onChange(layoutInfo.editable, layoutInfo.editable.html());
3749
+ };
3750
+
3751
+ if (agent.isMSIE) {
3752
+ var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted';
3753
+ layoutInfo.editable.on(sDomEvents, hChange);
3754
+ } else {
3755
+ layoutInfo.editable.on('input', hChange);
3756
+ }
3757
+ }
3758
+
3759
+ // All editor status will be saved on editable with jquery's data
3760
+ // for support multiple editor with singleton object.
3761
+ layoutInfo.editable.data('callbacks', {
3762
+ onAutoSave: options.onAutoSave,
3763
+ onImageUpload: options.onImageUpload,
3764
+ onImageUploadError: options.onImageUploadError,
3765
+ onFileUpload: options.onFileUpload,
3766
+ onFileUploadError: options.onFileUpload
3767
+ });
3768
+ };
3769
+
3770
+ this.dettach = function (layoutInfo, options) {
3771
+ layoutInfo.editable.off();
3772
+
3773
+ layoutInfo.popover.off();
3774
+ layoutInfo.handle.off();
3775
+ layoutInfo.dialog.off();
3776
+
3777
+ if (!options.airMode) {
3778
+ layoutInfo.dropzone.off();
3779
+ layoutInfo.toolbar.off();
3780
+ layoutInfo.statusbar.off();
3781
+ }
3782
+ };
3783
+ };
3784
+
3785
+ /**
3786
+ * renderer
3787
+ *
3788
+ * rendering toolbar and editable
3789
+ */
3790
+ var Renderer = function () {
3791
+
3792
+ /**
3793
+ * bootstrap button template
3794
+ *
3795
+ * @param {String} label
3796
+ * @param {Object} [options]
3797
+ * @param {String} [options.event]
3798
+ * @param {String} [options.value]
3799
+ * @param {String} [options.title]
3800
+ * @param {String} [options.dropdown]
3801
+ */
3802
+ var tplButton = function (label, options) {
3803
+ var event = options.event;
3804
+ var value = options.value;
3805
+ var title = options.title;
3806
+ var className = options.className;
3807
+ var dropdown = options.dropdown;
3808
+
3809
+ return '<button type="button"' +
3810
+ ' class="btn btn-default btn-sm btn-small' +
3811
+ (className ? ' ' + className : '') +
3812
+ (dropdown ? ' dropdown-toggle' : '') +
3813
+ '"' +
3814
+ (dropdown ? ' data-toggle="dropdown"' : '') +
3815
+ (title ? ' title="' + title + '"' : '') +
3816
+ (event ? ' data-event="' + event + '"' : '') +
3817
+ (value ? ' data-value=\'' + value + '\'' : '') +
3818
+ ' tabindex="-1">' +
3819
+ label +
3820
+ (dropdown ? ' <span class="caret"></span>' : '') +
3821
+ '</button>' +
3822
+ (dropdown || '');
3823
+ };
3824
+
3825
+ /**
3826
+ * bootstrap icon button template
3827
+ *
3828
+ * @param {String} iconClassName
3829
+ * @param {Object} [options]
3830
+ * @param {String} [options.event]
3831
+ * @param {String} [options.value]
3832
+ * @param {String} [options.title]
3833
+ * @param {String} [options.dropdown]
3834
+ */
3835
+ var tplIconButton = function (iconClassName, options) {
3836
+ var label = '<i class="' + iconClassName + '"></i>';
3837
+ return tplButton(label, options);
3838
+ };
3839
+
3840
+ /**
3841
+ * bootstrap popover template
3842
+ *
3843
+ * @param {String} className
3844
+ * @param {String} content
3845
+ */
3846
+ var tplPopover = function (className, content) {
3847
+ return '<div class="' + className + ' popover bottom in" style="display: none;">' +
3848
+ '<div class="arrow"></div>' +
3849
+ '<div class="popover-content">' +
3850
+ content +
3851
+ '</div>' +
3852
+ '</div>';
3853
+ };
3854
+
3855
+ /**
3856
+ * bootstrap dialog template
3857
+ *
3858
+ * @param {String} className
3859
+ * @param {String} [title]
3860
+ * @param {String} body
3861
+ * @param {String} [footer]
3862
+ */
3863
+ var tplDialog = function (className, title, body, footer) {
3864
+ return '<div class="' + className + ' modal" aria-hidden="false">' +
3865
+ '<div class="modal-dialog">' +
3866
+ '<div class="modal-content">' +
3867
+ (title ?
3868
+ '<div class="modal-header">' +
3869
+ '<button type="button" class="close" aria-hidden="true" tabindex="-1">&times;</button>' +
3870
+ '<h4 class="modal-title">' + title + '</h4>' +
3871
+ '</div>' : ''
3872
+ ) +
3873
+ '<form class="note-modal-form">' +
3874
+ '<div class="modal-body">' +
3875
+ '<div class="row-fluid">' + body + '</div>' +
3876
+ '</div>' +
3877
+ (footer ?
3878
+ '<div class="modal-footer">' + footer + '</div>' : ''
3879
+ ) +
3880
+ '</form>' +
3881
+ '</div>' +
3882
+ '</div>' +
3883
+ '</div>';
3884
+ };
3885
+
3886
+ var tplButtonInfo = {
3887
+ picture: function (lang) {
3888
+ return tplIconButton('fa fa-picture-o icon-picture', {
3889
+ event: 'showImageDialog',
3890
+ title: lang.image.image
3891
+ });
3892
+ },
3893
+ link: function (lang) {
3894
+ return tplIconButton('fa fa-link icon-link', {
3895
+ event: 'showLinkDialog',
3896
+ title: lang.link.link
3897
+ });
3898
+ },
3899
+ video: function (lang) {
3900
+ return tplIconButton('fa fa-youtube-play icon-play', {
3901
+ event: 'showVideoDialog',
3902
+ title: lang.video.video
3903
+ });
3904
+ },
3905
+ table: function (lang) {
3906
+ var dropdown = '<ul class="dropdown-menu">' +
3907
+ '<div class="note-dimension-picker">' +
3908
+ '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
3909
+ '<div class="note-dimension-picker-highlighted"></div>' +
3910
+ '<div class="note-dimension-picker-unhighlighted"></div>' +
3911
+ '</div>' +
3912
+ '<div class="note-dimension-display"> 1 x 1 </div>' +
3913
+ '</ul>';
3914
+ return tplIconButton('fa fa-table icon-table', {
3915
+ title: lang.table.table,
3916
+ dropdown: dropdown
3917
+ });
3918
+ },
3919
+ style: function (lang, options) {
3920
+ var items = options.styleTags.reduce(function (memo, v) {
3921
+ var label = lang.style[v === 'p' ? 'normal' : v];
3922
+ return memo + '<li><a data-event="formatBlock" href="#" data-value="' + v + '">' +
3923
+ (
3924
+ (v === 'p' || v === 'pre') ? label :
3925
+ '<' + v + '>' + label + '</' + v + '>'
3926
+ ) +
3927
+ '</a></li>';
3928
+ }, '');
3929
+
3930
+ return tplIconButton('fa fa-magic icon-magic', {
3931
+ title: lang.style.style,
3932
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3933
+ });
3934
+ },
3935
+ fontname: function (lang, options) {
3936
+ var items = options.fontNames.reduce(function (memo, v) {
3937
+ if (!agent.isFontInstalled(v)) { return memo; }
3938
+ return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '">' +
3939
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3940
+ '</a></li>';
3941
+ }, '');
3942
+ var label = '<span class="note-current-fontname">' +
3943
+ options.defaultFontName +
3944
+ '</span>';
3945
+ return tplButton(label, {
3946
+ title: lang.font.name,
3947
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3948
+ });
3949
+ },
3950
+ fontsize: function (lang, options) {
3951
+ var items = options.fontSizes.reduce(function (memo, v) {
3952
+ return memo + '<li><a data-event="fontSize" href="#" data-value="' + v + '">' +
3953
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3954
+ '</a></li>';
3955
+ }, '');
3956
+
3957
+ var label = '<span class="note-current-fontsize">11</span>';
3958
+ return tplButton(label, {
3959
+ title: lang.font.size,
3960
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3961
+ });
3962
+ },
3963
+
3964
+ color: function (lang) {
3965
+ var colorButtonLabel = '<i class="fa fa-font icon-font" style="color:black;background-color:yellow;"></i>';
3966
+ var colorButton = tplButton(colorButtonLabel, {
3967
+ className: 'note-recent-color',
3968
+ title: lang.color.recent,
3969
+ event: 'color',
3970
+ value: '{"backColor":"yellow"}'
3971
+ });
3972
+
3973
+ var dropdown = '<ul class="dropdown-menu">' +
3974
+ '<li>' +
3975
+ '<div class="btn-group">' +
3976
+ '<div class="note-palette-title">' + lang.color.background + '</div>' +
3977
+ '<div class="note-color-reset" data-event="backColor"' +
3978
+ ' data-value="inherit" title="' + lang.color.transparent + '">' +
3979
+ lang.color.setTransparent +
3980
+ '</div>' +
3981
+ '<div class="note-color-palette" data-target-event="backColor"></div>' +
3982
+ '</div>' +
3983
+ '<div class="btn-group">' +
3984
+ '<div class="note-palette-title">' + lang.color.foreground + '</div>' +
3985
+ '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' +
3986
+ lang.color.resetToDefault +
3987
+ '</div>' +
3988
+ '<div class="note-color-palette" data-target-event="foreColor"></div>' +
3989
+ '</div>' +
3990
+ '</li>' +
3991
+ '</ul>';
3992
+
3993
+ var moreButton = tplButton('', {
3994
+ title: lang.color.more,
3995
+ dropdown: dropdown
3996
+ });
3997
+
3998
+ return colorButton + moreButton;
3999
+ },
4000
+ bold: function (lang) {
4001
+ return tplIconButton('fa fa-bold icon-bold', {
4002
+ event: 'bold',
4003
+ title: lang.font.bold
4004
+ });
4005
+ },
4006
+ italic: function (lang) {
4007
+ return tplIconButton('fa fa-italic icon-italic', {
4008
+ event: 'italic',
4009
+ title: lang.font.italic
4010
+ });
4011
+ },
4012
+ underline: function (lang) {
4013
+ return tplIconButton('fa fa-underline icon-underline', {
4014
+ event: 'underline',
4015
+ title: lang.font.underline
4016
+ });
4017
+ },
4018
+ strikethrough: function (lang) {
4019
+ return tplIconButton('fa fa-strikethrough icon-strikethrough', {
4020
+ event: 'strikethrough',
4021
+ title: lang.font.strikethrough
4022
+ });
4023
+ },
4024
+ superscript: function (lang) {
4025
+ return tplIconButton('fa fa-superscript icon-superscript', {
4026
+ event: 'superscript',
4027
+ title: lang.font.superscript
4028
+ });
4029
+ },
4030
+ subscript: function (lang) {
4031
+ return tplIconButton('fa fa-subscript icon-subscript', {
4032
+ event: 'subscript',
4033
+ title: lang.font.subscript
4034
+ });
4035
+ },
4036
+ clear: function (lang) {
4037
+ return tplIconButton('fa fa-eraser icon-eraser', {
4038
+ event: 'removeFormat',
4039
+ title: lang.font.clear
4040
+ });
4041
+ },
4042
+ ul: function (lang) {
4043
+ return tplIconButton('fa fa-list-ul icon-list-ul', {
4044
+ event: 'insertUnorderedList',
4045
+ title: lang.lists.unordered
4046
+ });
4047
+ },
4048
+ ol: function (lang) {
4049
+ return tplIconButton('fa fa-list-ol icon-list-ol', {
4050
+ event: 'insertOrderedList',
4051
+ title: lang.lists.ordered
4052
+ });
4053
+ },
4054
+ paragraph: function (lang) {
4055
+ var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
4056
+ title: lang.paragraph.left,
4057
+ event: 'justifyLeft'
4058
+ });
4059
+ var centerButton = tplIconButton('fa fa-align-center icon-align-center', {
4060
+ title: lang.paragraph.center,
4061
+ event: 'justifyCenter'
4062
+ });
4063
+ var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
4064
+ title: lang.paragraph.right,
4065
+ event: 'justifyRight'
4066
+ });
4067
+ var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
4068
+ title: lang.paragraph.justify,
4069
+ event: 'justifyFull'
4070
+ });
4071
+
4072
+ var outdentButton = tplIconButton('fa fa-outdent icon-indent-left', {
4073
+ title: lang.paragraph.outdent,
4074
+ event: 'outdent'
4075
+ });
4076
+ var indentButton = tplIconButton('fa fa-indent icon-indent-right', {
4077
+ title: lang.paragraph.indent,
4078
+ event: 'indent'
4079
+ });
4080
+
4081
+ var dropdown = '<div class="dropdown-menu">' +
4082
+ '<div class="note-align btn-group">' +
4083
+ leftButton + centerButton + rightButton + justifyButton +
4084
+ '</div>' +
4085
+ '<div class="note-list btn-group">' +
4086
+ indentButton + outdentButton +
4087
+ '</div>' +
4088
+ '</div>';
4089
+
4090
+ return tplIconButton('fa fa-align-left icon-align-left', {
4091
+ title: lang.paragraph.paragraph,
4092
+ dropdown: dropdown
4093
+ });
4094
+ },
4095
+ height: function (lang, options) {
4096
+ var items = options.lineHeights.reduce(function (memo, v) {
4097
+ return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' +
4098
+ '<i class="fa fa-check icon-ok"></i> ' + v +
4099
+ '</a></li>';
4100
+ }, '');
4101
+
4102
+ return tplIconButton('fa fa-text-height icon-text-height', {
4103
+ title: lang.font.height,
4104
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
4105
+ });
4106
+
4107
+ },
4108
+ help: function (lang) {
4109
+ return tplIconButton('fa fa-question icon-question', {
4110
+ event: 'showHelpDialog',
4111
+ title: lang.options.help
4112
+ });
4113
+ },
4114
+ fullscreen: function (lang) {
4115
+ return tplIconButton('fa fa-arrows-alt icon-fullscreen', {
4116
+ event: 'fullscreen',
4117
+ title: lang.options.fullscreen
4118
+ });
4119
+ },
4120
+ codeview: function (lang) {
4121
+ return tplIconButton('fa fa-code icon-code', {
4122
+ event: 'codeview',
4123
+ title: lang.options.codeview
4124
+ });
4125
+ },
4126
+ undo: function (lang) {
4127
+ return tplIconButton('fa fa-undo icon-undo', {
4128
+ event: 'undo',
4129
+ title: lang.history.undo
4130
+ });
4131
+ },
4132
+ redo: function (lang) {
4133
+ return tplIconButton('fa fa-repeat icon-repeat', {
4134
+ event: 'redo',
4135
+ title: lang.history.redo
4136
+ });
4137
+ },
4138
+ hr: function (lang) {
4139
+ return tplIconButton('fa fa-minus icon-hr', {
4140
+ event: 'insertHorizontalRule',
4141
+ title: lang.hr.insert
4142
+ });
4143
+ }
4144
+ };
4145
+
4146
+ var tplPopovers = function (lang, options) {
4147
+ var tplLinkPopover = function () {
4148
+ var linkButton = tplIconButton('fa fa-edit icon-edit', {
4149
+ title: lang.link.edit,
4150
+ event: 'showLinkDialog'
4151
+ });
4152
+ var unlinkButton = tplIconButton('fa fa-unlink icon-unlink', {
4153
+ title: lang.link.unlink,
4154
+ event: 'unlink'
4155
+ });
4156
+ var content = '<a href="http://www.google.com" target="_blank">www.google.com</a>&nbsp;&nbsp;' +
4157
+ '<div class="note-insert btn-group">' +
4158
+ linkButton + unlinkButton +
4159
+ '</div>';
4160
+ return tplPopover('note-link-popover', content);
4161
+ };
4162
+
4163
+ var tplImagePopover = function () {
4164
+ var fullButton = tplButton('<span class="note-fontsize-10">100%</span>', {
4165
+ title: lang.image.resizeFull,
4166
+ event: 'resize',
4167
+ value: '1'
4168
+ });
4169
+ var halfButton = tplButton('<span class="note-fontsize-10">50%</span>', {
4170
+ title: lang.image.resizeHalf,
4171
+ event: 'resize',
4172
+ value: '0.5'
4173
+ });
4174
+ var quarterButton = tplButton('<span class="note-fontsize-10">25%</span>', {
4175
+ title: lang.image.resizeQuarter,
4176
+ event: 'resize',
4177
+ value: '0.25'
4178
+ });
4179
+
4180
+ var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
4181
+ title: lang.image.floatLeft,
4182
+ event: 'floatMe',
4183
+ value: 'left'
4184
+ });
4185
+ var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
4186
+ title: lang.image.floatRight,
4187
+ event: 'floatMe',
4188
+ value: 'right'
4189
+ });
4190
+ var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
4191
+ title: lang.image.floatNone,
4192
+ event: 'floatMe',
4193
+ value: 'none'
4194
+ });
4195
+
4196
+ var removeButton = tplIconButton('fa fa-trash-o icon-trash', {
4197
+ title: lang.image.remove,
4198
+ event: 'removeMedia',
4199
+ value: 'none'
4200
+ });
4201
+
4202
+ var content = '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>' +
4203
+ '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' +
4204
+ '<div class="btn-group">' + removeButton + '</div>';
4205
+ return tplPopover('note-image-popover', content);
4206
+ };
4207
+
4208
+ var tplAirPopover = function () {
4209
+ var content = '';
4210
+ for (var idx = 0, sz = options.airPopover.length; idx < sz; idx ++) {
4211
+ var group = options.airPopover[idx];
4212
+ content += '<div class="note-' + group[0] + ' btn-group">';
4213
+ for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
4214
+ content += tplButtonInfo[group[1][i]](lang, options);
4215
+ }
4216
+ content += '</div>';
4217
+ }
4218
+
4219
+ return tplPopover('note-air-popover', content);
4220
+ };
4221
+
4222
+ return '<div class="note-popover">' +
4223
+ tplLinkPopover() +
4224
+ tplImagePopover() +
4225
+ (options.airMode ? tplAirPopover() : '') +
4226
+ '</div>';
4227
+ };
4228
+
4229
+ var tplHandles = function () {
4230
+ return '<div class="note-handle">' +
4231
+ '<div class="note-control-selection">' +
4232
+ '<div class="note-control-selection-bg"></div>' +
4233
+ '<div class="note-control-holder note-control-nw"></div>' +
4234
+ '<div class="note-control-holder note-control-ne"></div>' +
4235
+ '<div class="note-control-holder note-control-sw"></div>' +
4236
+ '<div class="note-control-sizing note-control-se"></div>' +
4237
+ '<div class="note-control-selection-info"></div>' +
4238
+ '</div>' +
4239
+ '</div>';
4240
+ };
4241
+
4242
+ /**
4243
+ * shortcut table template
4244
+ * @param {String} title
4245
+ * @param {String} body
4246
+ */
4247
+ var tplShortcut = function (title, body) {
4248
+ return '<table class="note-shortcut">' +
4249
+ '<thead>' +
4250
+ '<tr><th></th><th>' + title + '</th></tr>' +
4251
+ '</thead>' +
4252
+ '<tbody>' + body + '</tbody>' +
4253
+ '</table>';
4254
+ };
4255
+
4256
+ var tplShortcutText = function (lang) {
4257
+ var body = '<tr><td>⌘ + B</td><td>' + lang.font.bold + '</td></tr>' +
4258
+ '<tr><td>⌘ + I</td><td>' + lang.font.italic + '</td></tr>' +
4259
+ '<tr><td>⌘ + U</td><td>' + lang.font.underline + '</td></tr>' +
4260
+ '<tr><td>⌘ + ⇧ + S</td><td>' + lang.font.strikethrough + '</td></tr>' +
4261
+ '<tr><td>⌘ + \\</td><td>' + lang.font.clear + '</td></tr>';
4262
+
4263
+ return tplShortcut(lang.shortcut.textFormatting, body);
4264
+ };
4265
+
4266
+ var tplShortcutAction = function (lang) {
4267
+ var body = '<tr><td>⌘ + Z</td><td>' + lang.history.undo + '</td></tr>' +
4268
+ '<tr><td>⌘ + ⇧ + Z</td><td>' + lang.history.redo + '</td></tr>' +
4269
+ '<tr><td>⌘ + ]</td><td>' + lang.paragraph.indent + '</td></tr>' +
4270
+ '<tr><td>⌘ + [</td><td>' + lang.paragraph.outdent + '</td></tr>' +
4271
+ '<tr><td>⌘ + ENTER</td><td>' + lang.hr.insert + '</td></tr>';
4272
+
4273
+ return tplShortcut(lang.shortcut.action, body);
4274
+ };
4275
+
4276
+ var tplShortcutPara = function (lang) {
4277
+ var body = '<tr><td>⌘ + ⇧ + L</td><td>' + lang.paragraph.left + '</td></tr>' +
4278
+ '<tr><td>⌘ + ⇧ + E</td><td>' + lang.paragraph.center + '</td></tr>' +
4279
+ '<tr><td>⌘ + ⇧ + R</td><td>' + lang.paragraph.right + '</td></tr>' +
4280
+ '<tr><td>⌘ + ⇧ + J</td><td>' + lang.paragraph.justify + '</td></tr>' +
4281
+ '<tr><td>⌘ + ⇧ + NUM7</td><td>' + lang.lists.ordered + '</td></tr>' +
4282
+ '<tr><td>⌘ + ⇧ + NUM8</td><td>' + lang.lists.unordered + '</td></tr>';
4283
+
4284
+ return tplShortcut(lang.shortcut.paragraphFormatting, body);
4285
+ };
4286
+
4287
+ var tplShortcutStyle = function (lang) {
4288
+ var body = '<tr><td>⌘ + NUM0</td><td>' + lang.style.normal + '</td></tr>' +
4289
+ '<tr><td>⌘ + NUM1</td><td>' + lang.style.h1 + '</td></tr>' +
4290
+ '<tr><td>⌘ + NUM2</td><td>' + lang.style.h2 + '</td></tr>' +
4291
+ '<tr><td>⌘ + NUM3</td><td>' + lang.style.h3 + '</td></tr>' +
4292
+ '<tr><td>⌘ + NUM4</td><td>' + lang.style.h4 + '</td></tr>' +
4293
+ '<tr><td>⌘ + NUM5</td><td>' + lang.style.h5 + '</td></tr>' +
4294
+ '<tr><td>⌘ + NUM6</td><td>' + lang.style.h6 + '</td></tr>';
4295
+
4296
+ return tplShortcut(lang.shortcut.documentStyle, body);
4297
+ };
4298
+
4299
+ var tplExtraShortcuts = function (lang, options) {
4300
+ var extraKeys = options.extraKeys;
4301
+ var body = '';
4302
+ for (var key in extraKeys) {
4303
+ if (extraKeys.hasOwnProperty(key)) {
4304
+ body += '<tr><td>' + key + '</td><td>' + extraKeys[key] + '</td></tr>';
4305
+ }
4306
+ }
4307
+
4308
+ return tplShortcut(lang.shortcut.extraKeys, body);
4309
+ };
4310
+
4311
+ var tplShortcutTable = function (lang, options) {
4312
+ var template = '<table class="note-shortcut-layout">' +
4313
+ '<tbody>' +
4314
+ '<tr><td>' + tplShortcutAction(lang, options) + '</td><td>' + tplShortcutText(lang, options) + '</td></tr>' +
4315
+ '<tr><td>' + tplShortcutStyle(lang, options) + '</td><td>' + tplShortcutPara(lang, options) + '</td></tr>';
4316
+ if (options.extraKeys) {
4317
+ template += '<tr><td colspan="2">' + tplExtraShortcuts(lang, options) + '</td></tr>';
4318
+ }
4319
+ template += '</tbody</table>';
4320
+ return template;
4321
+ };
4322
+
4323
+ var replaceMacKeys = function (sHtml) {
4324
+ return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
4325
+ };
4326
+
4327
+ var tplDialogs = function (lang, options) {
4328
+ var tplImageDialog = function () {
4329
+ var body =
4330
+ '<div class="note-group-select-from-files">' +
4331
+ '<h5>' + lang.image.selectFromFiles + '</h5>' +
4332
+ '<input class="note-image-input" type="file" name="files" accept="image/*" />' +
4333
+ '</div>' +
4334
+ '<h5>' + lang.image.url + '</h5>' +
4335
+ '<input class="note-image-url form-control span12" type="text" />';
4336
+ var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
4337
+ return tplDialog('note-image-dialog', lang.image.insert, body, footer);
4338
+ };
4339
+
4340
+ var tplLinkDialog = function () {
4341
+ var body = '<div class="form-group">' +
4342
+ '<label>' + lang.link.textToDisplay + '</label>' +
4343
+ '<input class="note-link-text form-control span12" type="text" />' +
4344
+ '</div>' +
4345
+ '<div class="form-group">' +
4346
+ '<label>' + lang.link.url + '</label>' +
4347
+ '<input class="note-link-url form-control span12" type="text" />' +
4348
+ '</div>' +
4349
+ (!options.disableLinkTarget ?
4350
+ '<div class="checkbox">' +
4351
+ '<label>' + '<input type="checkbox" checked> ' +
4352
+ lang.link.openInNewWindow +
4353
+ '</label>' +
4354
+ '</div>' : ''
4355
+ );
4356
+ var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
4357
+ return tplDialog('note-link-dialog', lang.link.insert, body, footer);
4358
+ };
4359
+
4360
+ var tplVideoDialog = function () {
4361
+ var body = '<div class="form-group">' +
4362
+ '<label>' + lang.video.url + '</label>&nbsp;<small class="text-muted">' + lang.video.providers + '</small>' +
4363
+ '<input class="note-video-url form-control span12" type="text" />' +
4364
+ '</div>';
4365
+ var footer = '<button href="#" class="btn btn-primary note-video-btn disabled" disabled>' + lang.video.insert + '</button>';
4366
+ return tplDialog('note-video-dialog', lang.video.insert, body, footer);
4367
+ };
4368
+
4369
+ var tplHelpDialog = function () {
4370
+ var body = '<a class="modal-close pull-right" aria-hidden="true" tabindex="-1">' + lang.shortcut.close + '</a>' +
4371
+ '<div class="title">' + lang.shortcut.shortcuts + '</div>' +
4372
+ (agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
4373
+ '<p class="text-center">' +
4374
+ '<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote 0.5.6</a> · ' +
4375
+ '<a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · ' +
4376
+ '<a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a>' +
4377
+ '</p>';
4378
+ return tplDialog('note-help-dialog', '', body, '');
4379
+ };
4380
+
4381
+ return '<div class="note-dialog">' +
4382
+ tplImageDialog() +
4383
+ tplLinkDialog() +
4384
+ tplVideoDialog() +
4385
+ tplHelpDialog() +
4386
+ '</div>';
4387
+ };
4388
+
4389
+ var tplStatusbar = function () {
4390
+ return '<div class="note-resizebar">' +
4391
+ '<div class="note-icon-bar"></div>' +
4392
+ '<div class="note-icon-bar"></div>' +
4393
+ '<div class="note-icon-bar"></div>' +
4394
+ '</div>';
4395
+ };
4396
+
4397
+ var representShortcut = function (str) {
4398
+ if (agent.isMac) {
4399
+ str = str.replace('CMD', '⌘').replace('SHIFT', '⇧');
4400
+ }
4401
+
4402
+ return str.replace('BACKSLASH', '\\')
4403
+ .replace('SLASH', '/')
4404
+ .replace('LEFTBRACKET', '[')
4405
+ .replace('RIGHTBRACKET', ']');
4406
+ };
4407
+
4408
+ /**
4409
+ * createTooltip
4410
+ *
4411
+ * @param {jQuery} $container
4412
+ * @param {Object} keyMap
4413
+ * @param {String} [sPlacement]
4414
+ */
4415
+ var createTooltip = function ($container, keyMap, sPlacement) {
4416
+ var invertedKeyMap = func.invertObject(keyMap);
4417
+ var $buttons = $container.find('button');
4418
+
4419
+ $buttons.each(function (i, elBtn) {
4420
+ var $btn = $(elBtn);
4421
+ var sShortcut = invertedKeyMap[$btn.data('event')];
4422
+ if (sShortcut) {
4423
+ $btn.attr('title', function (i, v) {
4424
+ return v + ' (' + representShortcut(sShortcut) + ')';
4425
+ });
4426
+ }
4427
+ // bootstrap tooltip on btn-group bug
4428
+ // https://github.com/twbs/bootstrap/issues/5687
4429
+ }).tooltip({
4430
+ container: 'body',
4431
+ trigger: 'hover',
4432
+ placement: sPlacement || 'top'
4433
+ }).on('click', function () {
4434
+ $(this).tooltip('hide');
4435
+ });
4436
+ };
4437
+
4438
+ // createPalette
4439
+ var createPalette = function ($container, options) {
4440
+ var colorInfo = options.colors;
4441
+ $container.find('.note-color-palette').each(function () {
4442
+ var $palette = $(this), eventName = $palette.attr('data-target-event');
4443
+ var paletteContents = [];
4444
+ for (var row = 0, szRow = colorInfo.length; row < szRow; row++) {
4445
+ var colors = colorInfo[row];
4446
+ var buttons = [];
4447
+ for (var col = 0, szCol = colors.length; col < szCol; col++) {
4448
+ var color = colors[col];
4449
+ buttons.push(['<button type="button" class="note-color-btn" style="background-color:', color,
4450
+ ';" data-event="', eventName,
4451
+ '" data-value="', color,
4452
+ '" title="', color,
4453
+ '" data-toggle="button" tabindex="-1"></button>'].join(''));
4454
+ }
4455
+ paletteContents.push('<div class="note-color-row">' + buttons.join('') + '</div>');
4456
+ }
4457
+ $palette.html(paletteContents.join(''));
4458
+ });
4459
+ };
4460
+
4461
+ /**
4462
+ * create summernote layout (air mode)
4463
+ *
4464
+ * @param {jQuery} $holder
4465
+ * @param {Object} options
4466
+ */
4467
+ this.createLayoutByAirMode = function ($holder, options) {
4468
+ var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
4469
+ var langInfo = $.summernote.lang[options.lang];
4470
+
4471
+ var id = func.uniqueId();
4472
+
4473
+ $holder.addClass('note-air-editor note-editable');
4474
+ $holder.attr({
4475
+ 'id': 'note-editor-' + id,
4476
+ 'contentEditable': true
4477
+ });
4478
+
4479
+ var body = document.body;
4480
+
4481
+ // create Popover
4482
+ var $popover = $(tplPopovers(langInfo, options));
4483
+ $popover.addClass('note-air-layout');
4484
+ $popover.attr('id', 'note-popover-' + id);
4485
+ $popover.appendTo(body);
4486
+ createTooltip($popover, keyMap);
4487
+ createPalette($popover, options);
4488
+
4489
+ // create Handle
4490
+ var $handle = $(tplHandles());
4491
+ $handle.addClass('note-air-layout');
4492
+ $handle.attr('id', 'note-handle-' + id);
4493
+ $handle.appendTo(body);
4494
+
4495
+ // create Dialog
4496
+ var $dialog = $(tplDialogs(langInfo, options));
4497
+ $dialog.addClass('note-air-layout');
4498
+ $dialog.attr('id', 'note-dialog-' + id);
4499
+ $dialog.find('button.close, a.modal-close').click(function () {
4500
+ $(this).closest('.modal').modal('hide');
4501
+ });
4502
+ $dialog.appendTo(body);
4503
+ };
4504
+
4505
+ /**
4506
+ * create summernote layout (normal mode)
4507
+ *
4508
+ * @param {jQuery} $holder
4509
+ * @param {Object} options
4510
+ */
4511
+ this.createLayoutByFrame = function ($holder, options) {
4512
+ //01. create Editor
4513
+ var $editor = $('<div class="note-editor"></div>');
4514
+ if (options.width) {
4515
+ $editor.width(options.width);
4516
+ }
4517
+
4518
+ //02. statusbar (resizebar)
4519
+ if (options.height > 0) {
4520
+ $('<div class="note-statusbar">' + (options.disableResizeEditor ? '' : tplStatusbar()) + '</div>').prependTo($editor);
4521
+ }
4522
+
4523
+ //03. create Editable
4524
+ var isContentEditable = !$holder.is(':disabled');
4525
+ var $editable = $('<div class="note-editable" contentEditable="' + isContentEditable + '"></div>')
4526
+ .prependTo($editor);
4527
+ if (options.height) {
4528
+ $editable.height(options.height);
4529
+ }
4530
+ if (options.direction) {
4531
+ $editable.attr('dir', options.direction);
4532
+ }
4533
+
4534
+ $editable.html(dom.html($holder) || dom.emptyPara);
4535
+
4536
+ //031. create codable
4537
+ $('<textarea class="note-codable"></textarea>').prependTo($editor);
4538
+
4539
+ var langInfo = $.summernote.lang[options.lang];
4540
+
4541
+ //04. create Toolbar
4542
+ var toolbarHTML = '';
4543
+ for (var idx = 0, sz = options.toolbar.length; idx < sz; idx ++) {
4544
+ var groupName = options.toolbar[idx][0];
4545
+ var groupButtons = options.toolbar[idx][1];
4546
+
4547
+ toolbarHTML += '<div class="note-' + groupName + ' btn-group">';
4548
+ for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) {
4549
+ // continue creating toolbar even if a button doesn't exist
4550
+ if (!$.isFunction(tplButtonInfo[groupButtons[i]])) { continue; }
4551
+ toolbarHTML += tplButtonInfo[groupButtons[i]](langInfo, options);
4552
+ }
4553
+ toolbarHTML += '</div>';
4554
+ }
4555
+
4556
+ toolbarHTML = '<div class="note-toolbar btn-toolbar">' + toolbarHTML + '</div>';
4557
+
4558
+ var $toolbar = $(toolbarHTML).prependTo($editor);
4559
+ var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
4560
+ createPalette($toolbar, options);
4561
+ createTooltip($toolbar, keyMap, 'bottom');
4562
+
4563
+ //05. create Popover
4564
+ var $popover = $(tplPopovers(langInfo, options)).prependTo($editor);
4565
+ createPalette($popover, options);
4566
+ createTooltip($popover, keyMap);
4567
+
4568
+ //06. handle(control selection, ...)
4569
+ $(tplHandles()).prependTo($editor);
4570
+
4571
+ //07. create Dialog
4572
+ var $dialog = $(tplDialogs(langInfo, options)).prependTo($editor);
4573
+ $dialog.find('button.close, a.modal-close').click(function () {
4574
+ $(this).closest('.modal').modal('hide');
4575
+ });
4576
+
4577
+ //08. create Dropzone
4578
+ $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
4579
+
4580
+ //09. Editor/Holder switch
4581
+ $editor.insertAfter($holder);
4582
+ $holder.hide();
4583
+ };
4584
+
4585
+ this.noteEditorFromHolder = function ($holder) {
4586
+ if ($holder.hasClass('note-air-editor')) {
4587
+ return $holder;
4588
+ } else if ($holder.next().hasClass('note-editor')) {
4589
+ return $holder.next();
4590
+ } else {
4591
+ return $();
4592
+ }
4593
+ };
4594
+
4595
+ /**
4596
+ * create summernote layout
4597
+ *
4598
+ * @param {jQuery} $holder
4599
+ * @param {Object} options
4600
+ */
4601
+ this.createLayout = function ($holder, options) {
4602
+ if (this.noteEditorFromHolder($holder).length) {
4603
+ return;
4604
+ }
4605
+
4606
+ if (options.airMode) {
4607
+ this.createLayoutByAirMode($holder, options);
4608
+ } else {
4609
+ this.createLayoutByFrame($holder, options);
4610
+ }
4611
+ };
4612
+
4613
+ /**
4614
+ * returns layoutInfo from holder
4615
+ *
4616
+ * @param {jQuery} $holder - placeholder
4617
+ * @returns {Object}
4618
+ */
4619
+ this.layoutInfoFromHolder = function ($holder) {
4620
+ var $editor = this.noteEditorFromHolder($holder);
4621
+ if (!$editor.length) { return; }
4622
+
4623
+ var layoutInfo = dom.buildLayoutInfo($editor);
4624
+ // cache all properties.
4625
+ for (var key in layoutInfo) {
4626
+ if (layoutInfo.hasOwnProperty(key)) {
4627
+ layoutInfo[key] = layoutInfo[key].call();
4628
+ }
4629
+ }
4630
+ return layoutInfo;
4631
+ };
4632
+
4633
+ /**
4634
+ * removeLayout
4635
+ *
4636
+ * @param {jQuery} $holder - placeholder
4637
+ * @param {Object} layoutInfo
4638
+ * @param {Object} options
4639
+ *
4640
+ */
4641
+ this.removeLayout = function ($holder, layoutInfo, options) {
4642
+ if (options.airMode) {
4643
+ $holder.removeClass('note-air-editor note-editable')
4644
+ .removeAttr('id contentEditable');
4645
+
4646
+ layoutInfo.popover.remove();
4647
+ layoutInfo.handle.remove();
4648
+ layoutInfo.dialog.remove();
4649
+ } else {
4650
+ $holder.html(layoutInfo.editable.html());
4651
+
4652
+ layoutInfo.editor.remove();
4653
+ $holder.show();
4654
+ }
4655
+ };
4656
+ };
4657
+
4658
+ // jQuery namespace for summernote
4659
+ $.summernote = $.summernote || {};
4660
+
4661
+ // extends default `settings`
4662
+ $.extend($.summernote, settings);
4663
+
4664
+ var renderer = new Renderer();
4665
+ var eventHandler = new EventHandler();
4666
+
4667
+ /**
4668
+ * extend jquery fn
4669
+ */
4670
+ $.fn.extend({
4671
+ /**
4672
+ * initialize summernote
4673
+ * - create editor layout and attach Mouse and keyboard events.
4674
+ *
4675
+ * @param {Object} options
4676
+ * @returns {this}
4677
+ */
4678
+ summernote: function (options) {
4679
+ // extend default options
4680
+ options = $.extend({}, $.summernote.options, options);
4681
+
4682
+ this.each(function (idx, elHolder) {
4683
+ var $holder = $(elHolder);
4684
+
4685
+ // createLayout with options
4686
+ renderer.createLayout($holder, options);
4687
+
4688
+ var info = renderer.layoutInfoFromHolder($holder);
4689
+ eventHandler.attach(info, options);
4690
+
4691
+ // Textarea: auto filling the code before form submit.
4692
+ if (dom.isTextarea($holder[0])) {
4693
+ $holder.closest('form').submit(function () {
4694
+ $holder.val($holder.code());
4695
+ });
4696
+ }
4697
+ });
4698
+
4699
+ // focus on first editable element
4700
+ if (this.first().length && options.focus) {
4701
+ var info = renderer.layoutInfoFromHolder(this.first());
4702
+ info.editable.focus();
4703
+ }
4704
+
4705
+ // callback on init
4706
+ if (this.length && options.oninit) {
4707
+ options.oninit();
4708
+ }
4709
+
4710
+ return this;
4711
+ },
4712
+ //
4713
+
4714
+ /**
4715
+ * get the HTML contents of note or set the HTML contents of note.
4716
+ *
4717
+ * @param {String} [sHTML] - HTML contents(optional, set)
4718
+ * @returns {this|String} - context(set) or HTML contents of note(get).
4719
+ */
4720
+ code: function (sHTML) {
4721
+ // get the HTML contents of note
4722
+ if (sHTML === undefined) {
4723
+ var $holder = this.first();
4724
+ if (!$holder.length) { return; }
4725
+ var info = renderer.layoutInfoFromHolder($holder);
4726
+ if (!!(info && info.editable)) {
4727
+ var isCodeview = info.editor.hasClass('codeview');
4728
+ if (isCodeview && agent.hasCodeMirror) {
4729
+ info.codable.data('cmEditor').save();
4730
+ }
4731
+ return isCodeview ? info.codable.val() : info.editable.html();
4732
+ }
4733
+ return dom.isTextarea($holder[0]) ? $holder.val() : $holder.html();
4734
+ }
4735
+
4736
+ // set the HTML contents of note
4737
+ this.each(function (i, elHolder) {
4738
+ var info = renderer.layoutInfoFromHolder($(elHolder));
4739
+ if (info && info.editable) { info.editable.html(sHTML); }
4740
+ });
4741
+
4742
+ return this;
4743
+ },
4744
+
4745
+ /**
4746
+ * destroy Editor Layout and dettach Key and Mouse Event
4747
+ * @returns {this}
4748
+ */
4749
+ destroy: function () {
4750
+ this.each(function (idx, elHolder) {
4751
+ var $holder = $(elHolder);
4752
+
4753
+ var info = renderer.layoutInfoFromHolder($holder);
4754
+ if (!info || !info.editable) { return; }
4755
+
4756
+ var options = info.editor.data('options');
4757
+
4758
+ eventHandler.dettach(info, options);
4759
+ renderer.removeLayout($holder, info, options);
4760
+ });
4761
+
4762
+ return this;
4763
+ }
4764
+ });
4765
+ }));