bitsnote-assets 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
+ }));