summernote-rails 0.6.2.1 → 0.6.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -0
  3. data/lib/summernote-rails/version.rb +1 -1
  4. data/vendor/assets/javascripts/summernote/locales/ar-AR.js +3 -1
  5. data/vendor/assets/javascripts/summernote/locales/ca-ES.js +3 -1
  6. data/vendor/assets/javascripts/summernote/locales/cs-CZ.js +3 -1
  7. data/vendor/assets/javascripts/summernote/locales/da-DK.js +5 -1
  8. data/vendor/assets/javascripts/summernote/locales/de-DE.js +3 -1
  9. data/vendor/assets/javascripts/summernote/locales/es-ES.js +5 -1
  10. data/vendor/assets/javascripts/summernote/locales/es-EU.js +3 -1
  11. data/vendor/assets/javascripts/summernote/locales/fa-IR.js +3 -1
  12. data/vendor/assets/javascripts/summernote/locales/fi-FI.js +3 -1
  13. data/vendor/assets/javascripts/summernote/locales/fr-FR.js +5 -1
  14. data/vendor/assets/javascripts/summernote/locales/he-IL.js +5 -1
  15. data/vendor/assets/javascripts/summernote/locales/hu-HU.js +3 -1
  16. data/vendor/assets/javascripts/summernote/locales/id-ID.js +3 -1
  17. data/vendor/assets/javascripts/summernote/locales/it-IT.js +3 -1
  18. data/vendor/assets/javascripts/summernote/locales/ja-JP.js +3 -1
  19. data/vendor/assets/javascripts/summernote/locales/ko-KR.js +6 -1
  20. data/vendor/assets/javascripts/summernote/locales/nb-NO.js +3 -1
  21. data/vendor/assets/javascripts/summernote/locales/nl-NL.js +3 -1
  22. data/vendor/assets/javascripts/summernote/locales/pl-PL.js +17 -7
  23. data/vendor/assets/javascripts/summernote/locales/pt-BR.js +3 -1
  24. data/vendor/assets/javascripts/summernote/locales/ro-RO.js +3 -1
  25. data/vendor/assets/javascripts/summernote/locales/ru-RU.js +5 -1
  26. data/vendor/assets/javascripts/summernote/locales/sk-SK.js +3 -1
  27. data/vendor/assets/javascripts/summernote/locales/sl-SI.js +5 -1
  28. data/vendor/assets/javascripts/summernote/locales/sr-RS-Latin.js +3 -1
  29. data/vendor/assets/javascripts/summernote/locales/sr-RS.js +3 -1
  30. data/vendor/assets/javascripts/summernote/locales/sv-SE.js +3 -1
  31. data/vendor/assets/javascripts/summernote/locales/th-TH.js +5 -1
  32. data/vendor/assets/javascripts/summernote/locales/tr-TR.js +5 -1
  33. data/vendor/assets/javascripts/summernote/locales/uk-UA.js +5 -1
  34. data/vendor/assets/javascripts/summernote/locales/vi-VN.js +3 -1
  35. data/vendor/assets/javascripts/summernote/locales/zh-CN.js +3 -1
  36. data/vendor/assets/javascripts/summernote/locales/zh-TW.js +3 -1
  37. data/vendor/assets/javascripts/summernote/plugin/summernote-ext-hello.js +89 -0
  38. data/vendor/assets/javascripts/summernote/plugin/summernote-ext-video.js +573 -0
  39. data/vendor/assets/javascripts/summernote/summernote.js +1648 -1099
  40. metadata +4 -2
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Super simple wysiwyg editor on Bootstrap v0.6.2
2
+ * Super simple wysiwyg editor on Bootstrap v0.6.5
3
3
  * http://summernote.org/
4
4
  *
5
5
  * summernote.js
6
6
  * Copyright 2013-2015 Alan Hong. and other contributors
7
7
  * summernote may be freely distributed under the MIT license./
8
8
  *
9
- * Date: 2015-03-14T03:32Z
9
+ * Date: 2015-04-26T03:15Z
10
10
  */
11
11
  (function (factory) {
12
12
  /* global define */
@@ -227,6 +227,18 @@
227
227
  return inverted;
228
228
  };
229
229
 
230
+ /**
231
+ * @param {String} namespace
232
+ * @param {String} [prefix]
233
+ * @return {String}
234
+ */
235
+ var namespaceToCamel = function (namespace, prefix) {
236
+ prefix = prefix || '';
237
+ return prefix + namespace.split('.').map(function (name) {
238
+ return name.substring(0, 1).toUpperCase() + name.substring(1);
239
+ }).join('');
240
+ };
241
+
230
242
  return {
231
243
  eq: eq,
232
244
  eq2: eq2,
@@ -238,7 +250,8 @@
238
250
  and: and,
239
251
  uniqueId: uniqueId,
240
252
  rect2bnd: rect2bnd,
241
- invertObject: invertObject
253
+ invertObject: invertObject,
254
+ namespaceToCamel: namespaceToCamel
242
255
  };
243
256
  })();
244
257
 
@@ -489,6 +502,7 @@
489
502
 
490
503
  return {
491
504
  editor: function () { return $editor; },
505
+ holder : function () { return $editor.data('holder'); },
492
506
  editable: function () { return $editor; },
493
507
  popover: makeFinder('#note-popover-'),
494
508
  handle: makeFinder('#note-handle-'),
@@ -502,6 +516,7 @@
502
516
  };
503
517
  return {
504
518
  editor: function () { return $editor; },
519
+ holder : function () { return $editor.data('holder'); },
505
520
  dropzone: makeFinder('.note-dropzone'),
506
521
  toolbar: makeFinder('.note-toolbar'),
507
522
  editable: makeFinder('.note-editable'),
@@ -514,6 +529,30 @@
514
529
  }
515
530
  };
516
531
 
532
+ /**
533
+ * returns makeLayoutInfo from editor's descendant node.
534
+ *
535
+ * @private
536
+ * @param {Node} descendant
537
+ * @return {Object}
538
+ */
539
+ var makeLayoutInfo = function (descendant) {
540
+ var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
541
+
542
+ if (!$target.length) {
543
+ return null;
544
+ }
545
+
546
+ var $editor;
547
+ if ($target.is('.note-editor, .note-air-editor')) {
548
+ $editor = $target;
549
+ } else {
550
+ $editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
551
+ }
552
+
553
+ return buildLayoutInfo($editor);
554
+ };
555
+
517
556
  /**
518
557
  * @method makePredByNodeName
519
558
  *
@@ -635,6 +674,7 @@
635
674
 
636
675
  /**
637
676
  * blank HTML for cursor position
677
+ * - [workaround] for MSIE IE doesn't works with bogus br
638
678
  */
639
679
  var blankHTML = agent.isMSIE ? '&nbsp;' : '<br>';
640
680
 
@@ -1077,6 +1117,21 @@
1077
1117
  return null;
1078
1118
  };
1079
1119
 
1120
+ /**
1121
+ * returns whether point has character or not.
1122
+ *
1123
+ * @param {Point} point
1124
+ * @return {Boolean}
1125
+ */
1126
+ var isCharPoint = function (point) {
1127
+ if (!isText(point.node)) {
1128
+ return false;
1129
+ }
1130
+
1131
+ var ch = point.node.nodeValue.charAt(point.offset - 1);
1132
+ return ch && (ch !== ' ' && ch !== NBSP_CHAR);
1133
+ };
1134
+
1080
1135
  /**
1081
1136
  * @method walkPoint
1082
1137
  *
@@ -1141,33 +1196,39 @@
1141
1196
  * split element or #text
1142
1197
  *
1143
1198
  * @param {BoundaryPoint} point
1144
- * @param {Boolean} [isSkipPaddingBlankHTML]
1199
+ * @param {Object} [options]
1200
+ * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
1201
+ * @param {Boolean} [options.isNotSplitEdgePoint] - default: false
1145
1202
  * @return {Node} right node of boundaryPoint
1146
1203
  */
1147
- var splitNode = function (point, isSkipPaddingBlankHTML) {
1148
- // split #text
1149
- if (isText(point.node)) {
1150
- // edge case
1204
+ var splitNode = function (point, options) {
1205
+ var isSkipPaddingBlankHTML = options && options.isSkipPaddingBlankHTML;
1206
+ var isNotSplitEdgePoint = options && options.isNotSplitEdgePoint;
1207
+
1208
+ // edge case
1209
+ if (isEdgePoint(point) && (isText(point.node) || isNotSplitEdgePoint)) {
1151
1210
  if (isLeftEdgePoint(point)) {
1152
1211
  return point.node;
1153
1212
  } else if (isRightEdgePoint(point)) {
1154
1213
  return point.node.nextSibling;
1155
1214
  }
1215
+ }
1156
1216
 
1217
+ // split #text
1218
+ if (isText(point.node)) {
1157
1219
  return point.node.splitText(point.offset);
1158
- }
1220
+ } else {
1221
+ var childNode = point.node.childNodes[point.offset];
1222
+ var clone = insertAfter(point.node.cloneNode(false), point.node);
1223
+ appendChildNodes(clone, listNext(childNode));
1159
1224
 
1160
- // split element
1161
- var childNode = point.node.childNodes[point.offset];
1162
- var clone = insertAfter(point.node.cloneNode(false), point.node);
1163
- appendChildNodes(clone, listNext(childNode));
1225
+ if (!isSkipPaddingBlankHTML) {
1226
+ paddingBlankHTML(point.node);
1227
+ paddingBlankHTML(clone);
1228
+ }
1164
1229
 
1165
- if (!isSkipPaddingBlankHTML) {
1166
- paddingBlankHTML(point.node);
1167
- paddingBlankHTML(clone);
1230
+ return clone;
1168
1231
  }
1169
-
1170
- return clone;
1171
1232
  };
1172
1233
 
1173
1234
  /**
@@ -1177,33 +1238,30 @@
1177
1238
  *
1178
1239
  * @param {Node} root - split root
1179
1240
  * @param {BoundaryPoint} point
1180
- * @param {Boolean} [isSkipPaddingBlankHTML]
1241
+ * @param {Object} [options]
1242
+ * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
1243
+ * @param {Boolean} [options.isNotSplitEdgePoint] - default: false
1181
1244
  * @return {Node} right node of boundaryPoint
1182
1245
  */
1183
- var splitTree = function (root, point, isSkipPaddingBlankHTML) {
1246
+ var splitTree = function (root, point, options) {
1184
1247
  // ex) [#text, <span>, <p>]
1185
1248
  var ancestors = listAncestor(point.node, func.eq(root));
1186
1249
 
1187
1250
  if (!ancestors.length) {
1188
1251
  return null;
1189
1252
  } else if (ancestors.length === 1) {
1190
- return splitNode(point, isSkipPaddingBlankHTML);
1253
+ return splitNode(point, options);
1191
1254
  }
1192
1255
 
1193
1256
  return ancestors.reduce(function (node, parent) {
1194
- var clone = insertAfter(parent.cloneNode(false), parent);
1195
-
1196
1257
  if (node === point.node) {
1197
- node = splitNode(point, isSkipPaddingBlankHTML);
1258
+ node = splitNode(point, options);
1198
1259
  }
1199
1260
 
1200
- appendChildNodes(clone, listNext(node));
1201
-
1202
- if (!isSkipPaddingBlankHTML) {
1203
- paddingBlankHTML(parent);
1204
- paddingBlankHTML(clone);
1205
- }
1206
- return clone;
1261
+ return splitNode({
1262
+ node: parent,
1263
+ offset: node ? dom.position(node) : nodeLength(parent)
1264
+ }, options);
1207
1265
  });
1208
1266
  };
1209
1267
 
@@ -1231,8 +1289,16 @@
1231
1289
  container = splitRoot.parentNode;
1232
1290
  }
1233
1291
 
1234
- // split with splitTree
1235
- var pivot = splitRoot && splitTree(splitRoot, point, isInline);
1292
+ // if splitRoot is exists, split with splitTree
1293
+ var pivot = splitRoot && splitTree(splitRoot, point, {
1294
+ isSkipPaddingBlankHTML: isInline,
1295
+ isNotSplitEdgePoint: isInline
1296
+ });
1297
+
1298
+ // if container is point.node, find pivot with point.offset
1299
+ if (!pivot && container === point.node) {
1300
+ pivot = point.node.childNodes[point.offset];
1301
+ }
1236
1302
 
1237
1303
  return {
1238
1304
  rightNode: pivot,
@@ -1323,6 +1389,18 @@
1323
1389
 
1324
1390
  var isTextarea = makePredByNodeName('TEXTAREA');
1325
1391
 
1392
+ /**
1393
+ * @param {jQuery} $node
1394
+ * @param {Boolean} [stripLinebreaks] - default: false
1395
+ */
1396
+ var value = function ($node, stripLinebreaks) {
1397
+ var val = isTextarea($node[0]) ? $node.val() : $node.html();
1398
+ if (stripLinebreaks) {
1399
+ return val.replace(/[\n\r]/g, '');
1400
+ }
1401
+ return val;
1402
+ };
1403
+
1326
1404
  /**
1327
1405
  * @method html
1328
1406
  *
@@ -1332,7 +1410,7 @@
1332
1410
  * @param {Boolean} [isNewlineOnBlock]
1333
1411
  */
1334
1412
  var html = function ($node, isNewlineOnBlock) {
1335
- var markup = isTextarea($node[0]) ? $node.val() : $node.html();
1413
+ var markup = value($node);
1336
1414
 
1337
1415
  if (isNewlineOnBlock) {
1338
1416
  var regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g;
@@ -1350,14 +1428,6 @@
1350
1428
  return markup;
1351
1429
  };
1352
1430
 
1353
- var value = function ($textarea, stripLinebreaks) {
1354
- var val = $textarea.val();
1355
- if (stripLinebreaks) {
1356
- return val.replace(/[\n\r]/g, '');
1357
- }
1358
- return val;
1359
- };
1360
-
1361
1431
  return {
1362
1432
  /** @property {String} NBSP_CHAR */
1363
1433
  NBSP_CHAR: NBSP_CHAR,
@@ -1371,11 +1441,13 @@
1371
1441
  isEditable: isEditable,
1372
1442
  isControlSizing: isControlSizing,
1373
1443
  buildLayoutInfo: buildLayoutInfo,
1444
+ makeLayoutInfo: makeLayoutInfo,
1374
1445
  isText: isText,
1375
1446
  isVoid: isVoid,
1376
1447
  isPara: isPara,
1377
1448
  isPurePara: isPurePara,
1378
1449
  isInline: isInline,
1450
+ isBlock: func.not(isInline),
1379
1451
  isBodyInline: isBodyInline,
1380
1452
  isBody: isBody,
1381
1453
  isParaInline: isParaInline,
@@ -1411,6 +1483,7 @@
1411
1483
  isVisiblePoint: isVisiblePoint,
1412
1484
  prevPointUntil: prevPointUntil,
1413
1485
  nextPointUntil: nextPointUntil,
1486
+ isCharPoint: isCharPoint,
1414
1487
  walkPoint: walkPoint,
1415
1488
  ancestor: ancestor,
1416
1489
  singleChildAncestor: singleChildAncestor,
@@ -1618,6 +1691,32 @@
1618
1691
  } else {
1619
1692
  nativeRng.select();
1620
1693
  }
1694
+
1695
+ return this;
1696
+ };
1697
+
1698
+ /**
1699
+ * Moves the scrollbar to start container(sc) of current range
1700
+ *
1701
+ * @return {WrappedRange}
1702
+ */
1703
+ this.scrollIntoView = function () {
1704
+ if (this.sc.scrollIntoView) {
1705
+ this.sc.scrollIntoView(false);
1706
+ }
1707
+
1708
+ return this;
1709
+ };
1710
+
1711
+ /**
1712
+ * set a focus into start container of current range
1713
+ *
1714
+ * @return {WrappedRange}
1715
+ */
1716
+ this.focus = function () {
1717
+ this.sc.focus();
1718
+
1719
+ return this;
1621
1720
  };
1622
1721
 
1623
1722
  /**
@@ -1922,6 +2021,21 @@
1922
2021
 
1923
2022
  return node;
1924
2023
  };
2024
+
2025
+ /**
2026
+ * insert html at current cursor
2027
+ */
2028
+ this.pasteHTML = function (markup) {
2029
+ var self = this;
2030
+ var contentsContainer = $('<div></div>').html(markup)[0];
2031
+ var childNodes = list.from(contentsContainer.childNodes);
2032
+
2033
+ this.wrapBodyInlineWithPara().deleteContents();
2034
+
2035
+ return $.map(childNodes.reverse(), function (childNode) {
2036
+ return self.insertNode(childNode);
2037
+ }).reverse();
2038
+ };
1925
2039
 
1926
2040
  /**
1927
2041
  * returns text in range
@@ -1932,6 +2046,37 @@
1932
2046
  var nativeRng = nativeRange();
1933
2047
  return agent.isW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
1934
2048
  };
2049
+
2050
+ /**
2051
+ * returns range for word before cursor
2052
+ *
2053
+ * @param {Boolean} [findAfter] - find after cursor, default: false
2054
+ * @return {WrappedRange}
2055
+ */
2056
+ this.getWordRange = function (findAfter) {
2057
+ var endPoint = this.getEndPoint();
2058
+
2059
+ if (!dom.isCharPoint(endPoint)) {
2060
+ return this;
2061
+ }
2062
+
2063
+ var startPoint = dom.prevPointUntil(endPoint, function (point) {
2064
+ return !dom.isCharPoint(point);
2065
+ });
2066
+
2067
+ if (findAfter) {
2068
+ endPoint = dom.nextPointUntil(endPoint, function (point) {
2069
+ return !dom.isCharPoint(point);
2070
+ });
2071
+ }
2072
+
2073
+ return new WrappedRange(
2074
+ startPoint.node,
2075
+ startPoint.offset,
2076
+ endPoint.node,
2077
+ endPoint.offset
2078
+ );
2079
+ };
1935
2080
 
1936
2081
  /**
1937
2082
  * create offsetPath bookmark
@@ -2116,13 +2261,13 @@
2116
2261
  })();
2117
2262
 
2118
2263
  /**
2119
- * @class settings
2264
+ * @class defaults
2120
2265
  *
2121
2266
  * @singleton
2122
2267
  */
2123
- var settings = {
2268
+ var defaults = {
2124
2269
  /** @property */
2125
- version: '0.6.2',
2270
+ version: '0.6.5',
2126
2271
 
2127
2272
  /**
2128
2273
  *
@@ -2169,6 +2314,8 @@
2169
2314
  placeholder: false, // enable placeholder text
2170
2315
  prettifyHtml: true, // enable prettifying html while toggling codeview
2171
2316
 
2317
+ iconPrefix: 'fa fa-', // prefix for css icon classes
2318
+
2172
2319
  codemirror: { // codemirror options
2173
2320
  mode: 'text/html',
2174
2321
  htmlMode: true,
@@ -2183,7 +2330,9 @@
2183
2330
  toolbar: [
2184
2331
  ['style', ['style']],
2185
2332
  ['font', ['bold', 'italic', 'underline', 'clear']],
2333
+ // ['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear']],
2186
2334
  ['fontname', ['fontname']],
2335
+ // ['fontsize', ['fontsize']],
2187
2336
  ['color', ['color']],
2188
2337
  ['para', ['ul', 'ol', 'paragraph']],
2189
2338
  ['height', ['height']],
@@ -2223,11 +2372,13 @@
2223
2372
  // fontName
2224
2373
  fontNames: [
2225
2374
  'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New',
2226
- 'Helvetica Neue', 'Impact', 'Lucida Grande',
2375
+ 'Helvetica Neue', 'Helvetica', 'Impact', 'Lucida Grande',
2227
2376
  'Tahoma', 'Times New Roman', 'Verdana'
2228
2377
  ],
2229
2378
  fontNamesIgnoreCheck: [],
2230
2379
 
2380
+ fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],
2381
+
2231
2382
  // pallete colors(n x n)
2232
2383
  colors: [
2233
2384
  ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
@@ -2352,7 +2503,11 @@
2352
2503
  underline: 'Underline',
2353
2504
  clear: 'Remove Font Style',
2354
2505
  height: 'Line Height',
2355
- name: 'Font Family'
2506
+ name: 'Font Family',
2507
+ strikethrough: 'Strikethrough',
2508
+ subscript: 'Subscript',
2509
+ superscript: 'Superscript',
2510
+ size: 'Font Size'
2356
2511
  },
2357
2512
  image: {
2358
2513
  image: 'Picture',
@@ -2520,55 +2675,128 @@
2520
2675
  * @singleton
2521
2676
  * @alternateClassName key
2522
2677
  */
2523
- var key = {
2678
+ var key = (function () {
2679
+ var keyMap = {
2680
+ 'BACKSPACE': 8,
2681
+ 'TAB': 9,
2682
+ 'ENTER': 13,
2683
+ 'SPACE': 32,
2684
+
2685
+ // Number: 0-9
2686
+ 'NUM0': 48,
2687
+ 'NUM1': 49,
2688
+ 'NUM2': 50,
2689
+ 'NUM3': 51,
2690
+ 'NUM4': 52,
2691
+ 'NUM5': 53,
2692
+ 'NUM6': 54,
2693
+ 'NUM7': 55,
2694
+ 'NUM8': 56,
2695
+
2696
+ // Alphabet: a-z
2697
+ 'B': 66,
2698
+ 'E': 69,
2699
+ 'I': 73,
2700
+ 'J': 74,
2701
+ 'K': 75,
2702
+ 'L': 76,
2703
+ 'R': 82,
2704
+ 'S': 83,
2705
+ 'U': 85,
2706
+ 'Y': 89,
2707
+ 'Z': 90,
2708
+
2709
+ 'SLASH': 191,
2710
+ 'LEFTBRACKET': 219,
2711
+ 'BACKSLASH': 220,
2712
+ 'RIGHTBRACKET': 221
2713
+ };
2714
+
2715
+ return {
2716
+ /**
2717
+ * @method isEdit
2718
+ *
2719
+ * @param {Number} keyCode
2720
+ * @return {Boolean}
2721
+ */
2722
+ isEdit: function (keyCode) {
2723
+ return list.contains([8, 9, 13, 32], keyCode);
2724
+ },
2725
+ /**
2726
+ * @property {Object} nameFromCode
2727
+ * @property {String} nameFromCode.8 "BACKSPACE"
2728
+ */
2729
+ nameFromCode: func.invertObject(keyMap),
2730
+ code: keyMap
2731
+ };
2732
+ })();
2733
+
2734
+ /**
2735
+ * @class editing.History
2736
+ *
2737
+ * Editor History
2738
+ *
2739
+ */
2740
+ var History = function ($editable) {
2741
+ var stack = [], stackOffset = -1;
2742
+ var editable = $editable[0];
2743
+
2744
+ var makeSnapshot = function () {
2745
+ var rng = range.create();
2746
+ var emptyBookmark = {s: {path: [], offset: 0}, e: {path: [], offset: 0}};
2747
+
2748
+ return {
2749
+ contents: $editable.html(),
2750
+ bookmark: (rng ? rng.bookmark(editable) : emptyBookmark)
2751
+ };
2752
+ };
2753
+
2754
+ var applySnapshot = function (snapshot) {
2755
+ if (snapshot.contents !== null) {
2756
+ $editable.html(snapshot.contents);
2757
+ }
2758
+ if (snapshot.bookmark !== null) {
2759
+ range.createFromBookmark(editable, snapshot.bookmark).select();
2760
+ }
2761
+ };
2762
+
2524
2763
  /**
2525
- * @method isEdit
2526
- *
2527
- * @param {Number} keyCode
2528
- * @return {Boolean}
2764
+ * undo
2529
2765
  */
2530
- isEdit: function (keyCode) {
2531
- return list.contains([8, 9, 13, 32], keyCode);
2532
- },
2766
+ this.undo = function () {
2767
+ if (0 < stackOffset) {
2768
+ stackOffset--;
2769
+ applySnapshot(stack[stackOffset]);
2770
+ }
2771
+ };
2772
+
2533
2773
  /**
2534
- * @property {Object} nameFromCode
2535
- * @property {String} nameFromCode.8 "BACKSPACE"
2774
+ * redo
2536
2775
  */
2537
- nameFromCode: {
2538
- '8': 'BACKSPACE',
2539
- '9': 'TAB',
2540
- '13': 'ENTER',
2541
- '32': 'SPACE',
2776
+ this.redo = function () {
2777
+ if (stack.length - 1 > stackOffset) {
2778
+ stackOffset++;
2779
+ applySnapshot(stack[stackOffset]);
2780
+ }
2781
+ };
2542
2782
 
2543
- // Number: 0-9
2544
- '48': 'NUM0',
2545
- '49': 'NUM1',
2546
- '50': 'NUM2',
2547
- '51': 'NUM3',
2548
- '52': 'NUM4',
2549
- '53': 'NUM5',
2550
- '54': 'NUM6',
2551
- '55': 'NUM7',
2552
- '56': 'NUM8',
2783
+ /**
2784
+ * recorded undo
2785
+ */
2786
+ this.recordUndo = function () {
2787
+ stackOffset++;
2553
2788
 
2554
- // Alphabet: a-z
2555
- '66': 'B',
2556
- '69': 'E',
2557
- '73': 'I',
2558
- '74': 'J',
2559
- '75': 'K',
2560
- '76': 'L',
2561
- '82': 'R',
2562
- '83': 'S',
2563
- '85': 'U',
2564
- '89': 'Y',
2565
- '90': 'Z',
2566
-
2567
- '191': 'SLASH',
2568
- '219': 'LEFTBRACKET',
2569
- '220': 'BACKSLASH',
2570
- '221': 'RIGHTBRACKET'
2571
- }
2789
+ // Wash out stack after stackOffset
2790
+ if (stack.length > stackOffset) {
2791
+ stack = stack.slice(0, stackOffset);
2792
+ }
2793
+
2794
+ // Create new snapshot and push it to the end
2795
+ stack.push(makeSnapshot());
2796
+ };
2797
+
2798
+ // Create first undo stack
2799
+ this.recordUndo();
2572
2800
  };
2573
2801
 
2574
2802
  /**
@@ -2581,6 +2809,7 @@
2581
2809
  /**
2582
2810
  * @method jQueryCSS
2583
2811
  *
2812
+ * [workaround] for old jQuery
2584
2813
  * passing an array of style properties to .css()
2585
2814
  * will result in an object of property-value pairs.
2586
2815
  * (compability with version < 1.9)
@@ -2717,160 +2946,46 @@
2717
2946
 
2718
2947
 
2719
2948
  /**
2720
- * @class editing.Typing
2721
- *
2722
- * Typing
2949
+ * @class editing.Bullet
2723
2950
  *
2951
+ * @alternateClassName Bullet
2724
2952
  */
2725
- var Typing = function () {
2726
-
2953
+ var Bullet = function () {
2727
2954
  /**
2728
- * insert tab
2955
+ * @method insertOrderedList
2729
2956
  *
2730
- * @param {jQuery} $editable
2731
- * @param {WrappedRange} rng
2732
- * @param {Number} tabsize
2957
+ * toggle ordered list
2958
+ *
2959
+ * @type command
2733
2960
  */
2734
- this.insertTab = function ($editable, rng, tabsize) {
2735
- var tab = dom.createText(new Array(tabsize + 1).join(dom.NBSP_CHAR));
2736
- rng = rng.deleteContents();
2737
- rng.insertNode(tab, true);
2738
-
2739
- rng = range.create(tab, tabsize);
2740
- rng.select();
2961
+ this.insertOrderedList = function () {
2962
+ this.toggleList('OL');
2741
2963
  };
2742
2964
 
2743
2965
  /**
2744
- * insert paragraph
2966
+ * @method insertUnorderedList
2967
+ *
2968
+ * toggle unordered list
2969
+ *
2970
+ * @type command
2745
2971
  */
2746
- this.insertParagraph = function () {
2747
- var rng = range.create();
2748
-
2749
- // deleteContents on range.
2750
- rng = rng.deleteContents();
2972
+ this.insertUnorderedList = function () {
2973
+ this.toggleList('UL');
2974
+ };
2751
2975
 
2752
- // Wrap range if it needs to be wrapped by paragraph
2753
- rng = rng.wrapBodyInlineWithPara();
2976
+ /**
2977
+ * @method indent
2978
+ *
2979
+ * indent
2980
+ *
2981
+ * @type command
2982
+ */
2983
+ this.indent = function () {
2984
+ var self = this;
2985
+ var rng = range.create().wrapBodyInlineWithPara();
2754
2986
 
2755
- // finding paragraph
2756
- var splitRoot = dom.ancestor(rng.sc, dom.isPara);
2757
-
2758
- var nextPara;
2759
- // on paragraph: split paragraph
2760
- if (splitRoot) {
2761
- nextPara = dom.splitTree(splitRoot, rng.getStartPoint());
2762
-
2763
- var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor);
2764
- emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor));
2765
-
2766
- $.each(emptyAnchors, function (idx, anchor) {
2767
- dom.remove(anchor);
2768
- });
2769
- // no paragraph: insert empty paragraph
2770
- } else {
2771
- var next = rng.sc.childNodes[rng.so];
2772
- nextPara = $(dom.emptyPara)[0];
2773
- if (next) {
2774
- rng.sc.insertBefore(nextPara, next);
2775
- } else {
2776
- rng.sc.appendChild(nextPara);
2777
- }
2778
- }
2779
-
2780
- range.create(nextPara, 0).normalize().select();
2781
- };
2782
-
2783
- };
2784
-
2785
- /**
2786
- * @class editing.Table
2787
- *
2788
- * Table
2789
- *
2790
- */
2791
- var Table = function () {
2792
- /**
2793
- * handle tab key
2794
- *
2795
- * @param {WrappedRange} rng
2796
- * @param {Boolean} isShift
2797
- */
2798
- this.tab = function (rng, isShift) {
2799
- var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
2800
- var table = dom.ancestor(cell, dom.isTable);
2801
- var cells = dom.listDescendant(table, dom.isCell);
2802
-
2803
- var nextCell = list[isShift ? 'prev' : 'next'](cells, cell);
2804
- if (nextCell) {
2805
- range.create(nextCell, 0).select();
2806
- }
2807
- };
2808
-
2809
- /**
2810
- * create empty table element
2811
- *
2812
- * @param {Number} rowCount
2813
- * @param {Number} colCount
2814
- * @return {Node}
2815
- */
2816
- this.createTable = function (colCount, rowCount) {
2817
- var tds = [], tdHTML;
2818
- for (var idxCol = 0; idxCol < colCount; idxCol++) {
2819
- tds.push('<td>' + dom.blank + '</td>');
2820
- }
2821
- tdHTML = tds.join('');
2822
-
2823
- var trs = [], trHTML;
2824
- for (var idxRow = 0; idxRow < rowCount; idxRow++) {
2825
- trs.push('<tr>' + tdHTML + '</tr>');
2826
- }
2827
- trHTML = trs.join('');
2828
- return $('<table class="table table-bordered">' + trHTML + '</table>')[0];
2829
- };
2830
- };
2831
-
2832
-
2833
- /**
2834
- * @class editing.Bullet
2835
- *
2836
- * @alternateClassName Bullet
2837
- */
2838
- var Bullet = function () {
2839
- /**
2840
- * @method insertOrderedList
2841
- *
2842
- * toggle ordered list
2843
- *
2844
- * @type command
2845
- */
2846
- this.insertOrderedList = function () {
2847
- this.toggleList('OL');
2848
- };
2849
-
2850
- /**
2851
- * @method insertUnorderedList
2852
- *
2853
- * toggle unordered list
2854
- *
2855
- * @type command
2856
- */
2857
- this.insertUnorderedList = function () {
2858
- this.toggleList('UL');
2859
- };
2860
-
2861
- /**
2862
- * @method indent
2863
- *
2864
- * indent
2865
- *
2866
- * @type command
2867
- */
2868
- this.indent = function () {
2869
- var self = this;
2870
- var rng = range.create().wrapBodyInlineWithPara();
2871
-
2872
- var paras = rng.nodes(dom.isPara, { includeAncestor: true });
2873
- var clustereds = list.clusterBy(paras, func.peq2('parentNode'));
2987
+ var paras = rng.nodes(dom.isPara, { includeAncestor: true });
2988
+ var clustereds = list.clusterBy(paras, func.peq2('parentNode'));
2874
2989
 
2875
2990
  $.each(clustereds, function (idx, paras) {
2876
2991
  var head = list.head(paras);
@@ -3012,12 +3127,16 @@
3012
3127
  var lastList = headList.childNodes.length > 1 ? dom.splitTree(headList, {
3013
3128
  node: last.parentNode,
3014
3129
  offset: dom.position(last) + 1
3015
- }, true) : null;
3130
+ }, {
3131
+ isSkipPaddingBlankHTML: true
3132
+ }) : null;
3016
3133
 
3017
3134
  var middleList = dom.splitTree(headList, {
3018
3135
  node: head.parentNode,
3019
3136
  offset: dom.position(head)
3020
- }, true);
3137
+ }, {
3138
+ isSkipPaddingBlankHTML: true
3139
+ });
3021
3140
 
3022
3141
  paras = isEscapseToBody ? dom.listDescendant(middleList, dom.isLi) :
3023
3142
  list.from(middleList.childNodes).filter(dom.isLi);
@@ -3051,13 +3170,139 @@
3051
3170
  };
3052
3171
  };
3053
3172
 
3173
+
3174
+ /**
3175
+ * @class editing.Typing
3176
+ *
3177
+ * Typing
3178
+ *
3179
+ */
3180
+ var Typing = function () {
3181
+
3182
+ // a Bullet instance to toggle lists off
3183
+ var bullet = new Bullet();
3184
+
3185
+ /**
3186
+ * insert tab
3187
+ *
3188
+ * @param {jQuery} $editable
3189
+ * @param {WrappedRange} rng
3190
+ * @param {Number} tabsize
3191
+ */
3192
+ this.insertTab = function ($editable, rng, tabsize) {
3193
+ var tab = dom.createText(new Array(tabsize + 1).join(dom.NBSP_CHAR));
3194
+ rng = rng.deleteContents();
3195
+ rng.insertNode(tab, true);
3196
+
3197
+ rng = range.create(tab, tabsize);
3198
+ rng.select();
3199
+ };
3200
+
3201
+ /**
3202
+ * insert paragraph
3203
+ */
3204
+ this.insertParagraph = function () {
3205
+ var rng = range.create();
3206
+
3207
+ // deleteContents on range.
3208
+ rng = rng.deleteContents();
3209
+
3210
+ // Wrap range if it needs to be wrapped by paragraph
3211
+ rng = rng.wrapBodyInlineWithPara();
3212
+
3213
+ // finding paragraph
3214
+ var splitRoot = dom.ancestor(rng.sc, dom.isPara);
3215
+
3216
+ var nextPara;
3217
+ // on paragraph: split paragraph
3218
+ if (splitRoot) {
3219
+ // if it is an empty line with li
3220
+ if (dom.isEmpty(splitRoot) && dom.isLi(splitRoot)) {
3221
+ // disable UL/OL and escape!
3222
+ bullet.toggleList(splitRoot.parentNode.nodeName);
3223
+ return;
3224
+ // if new line has content (not a line break)
3225
+ } else {
3226
+ nextPara = dom.splitTree(splitRoot, rng.getStartPoint());
3227
+
3228
+ var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor);
3229
+ emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor));
3230
+
3231
+ $.each(emptyAnchors, function (idx, anchor) {
3232
+ dom.remove(anchor);
3233
+ });
3234
+ }
3235
+ // no paragraph: insert empty paragraph
3236
+ } else {
3237
+ var next = rng.sc.childNodes[rng.so];
3238
+ nextPara = $(dom.emptyPara)[0];
3239
+ if (next) {
3240
+ rng.sc.insertBefore(nextPara, next);
3241
+ } else {
3242
+ rng.sc.appendChild(nextPara);
3243
+ }
3244
+ }
3245
+
3246
+ range.create(nextPara, 0).normalize().select().focus().scrollIntoView();
3247
+
3248
+ };
3249
+
3250
+ };
3251
+
3252
+ /**
3253
+ * @class editing.Table
3254
+ *
3255
+ * Table
3256
+ *
3257
+ */
3258
+ var Table = function () {
3259
+ /**
3260
+ * handle tab key
3261
+ *
3262
+ * @param {WrappedRange} rng
3263
+ * @param {Boolean} isShift
3264
+ */
3265
+ this.tab = function (rng, isShift) {
3266
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
3267
+ var table = dom.ancestor(cell, dom.isTable);
3268
+ var cells = dom.listDescendant(table, dom.isCell);
3269
+
3270
+ var nextCell = list[isShift ? 'prev' : 'next'](cells, cell);
3271
+ if (nextCell) {
3272
+ range.create(nextCell, 0).select();
3273
+ }
3274
+ };
3275
+
3276
+ /**
3277
+ * create empty table element
3278
+ *
3279
+ * @param {Number} rowCount
3280
+ * @param {Number} colCount
3281
+ * @return {Node}
3282
+ */
3283
+ this.createTable = function (colCount, rowCount) {
3284
+ var tds = [], tdHTML;
3285
+ for (var idxCol = 0; idxCol < colCount; idxCol++) {
3286
+ tds.push('<td>' + dom.blank + '</td>');
3287
+ }
3288
+ tdHTML = tds.join('');
3289
+
3290
+ var trs = [], trHTML;
3291
+ for (var idxRow = 0; idxRow < rowCount; idxRow++) {
3292
+ trs.push('<tr>' + tdHTML + '</tr>');
3293
+ }
3294
+ trHTML = trs.join('');
3295
+ return $('<table class="table table-bordered">' + trHTML + '</table>')[0];
3296
+ };
3297
+ };
3298
+
3054
3299
  /**
3055
3300
  * @class editing.Editor
3056
3301
  *
3057
3302
  * Editor
3058
3303
  *
3059
3304
  */
3060
- var Editor = function () {
3305
+ var Editor = function (handler) {
3061
3306
 
3062
3307
  var style = new Style();
3063
3308
  var table = new Table();
@@ -3151,18 +3396,18 @@
3151
3396
  return rng ? rng.isOnEditable() && style.current(rng, target) : false;
3152
3397
  };
3153
3398
 
3154
- var triggerOnBeforeChange = this.triggerOnBeforeChange = function ($editable) {
3155
- var onBeforeChange = $editable.data('callbacks').onBeforeChange;
3156
- if (onBeforeChange) {
3157
- onBeforeChange($editable.html(), $editable);
3158
- }
3399
+ var triggerOnBeforeChange = function ($editable) {
3400
+ // TODO find holder
3401
+ handler.bindCustomEvent(
3402
+ $(), $editable.data('callbacks'), 'before.command'
3403
+ ).call($editable.html(), $editable);
3159
3404
  };
3160
3405
 
3161
- var triggerOnChange = this.triggerOnChange = function ($editable) {
3162
- var onChange = $editable.data('callbacks').onChange;
3163
- if (onChange) {
3164
- onChange($editable.html(), $editable);
3165
- }
3406
+ var triggerOnChange = function ($editable) {
3407
+ // TODO find holder
3408
+ handler.bindCustomEvent(
3409
+ $(), $editable.data('callbacks'), 'change'
3410
+ ).call($editable.html(), $editable);
3166
3411
  };
3167
3412
 
3168
3413
  /**
@@ -3200,10 +3445,13 @@
3200
3445
  * @method afterCommand
3201
3446
  * after command
3202
3447
  * @param {jQuery} $editable
3448
+ * @param {Boolean} isPreventTrigger
3203
3449
  */
3204
- var afterCommand = this.afterCommand = function ($editable) {
3450
+ var afterCommand = this.afterCommand = function ($editable, isPreventTrigger) {
3205
3451
  $editable.data('NoteHistory').recordUndo();
3206
- triggerOnChange($editable);
3452
+ if (!isPreventTrigger) {
3453
+ triggerOnChange($editable);
3454
+ }
3207
3455
  };
3208
3456
 
3209
3457
  /**
@@ -3304,6 +3552,9 @@
3304
3552
 
3305
3553
  /**
3306
3554
  * @method fontName
3555
+ *
3556
+ * change font name
3557
+ *
3307
3558
  * @param {jQuery} $editable
3308
3559
  * @param {Mixed} value
3309
3560
  */
@@ -3322,7 +3573,7 @@
3322
3573
 
3323
3574
  document.execCommand(sCmd, false, value);
3324
3575
 
3325
- afterCommand($editable);
3576
+ afterCommand($editable, true);
3326
3577
  };
3327
3578
  })(commands[idx]);
3328
3579
  }
@@ -3462,6 +3713,19 @@
3462
3713
  afterCommand($editable);
3463
3714
  };
3464
3715
 
3716
+ /**
3717
+ * paste HTML
3718
+ * @param {Node} $editable
3719
+ * @param {String} markup
3720
+ */
3721
+ this.pasteHTML = function ($editable, markup) {
3722
+ beforeCommand($editable);
3723
+ var rng = this.createRange($editable);
3724
+ var contents = rng.pasteHTML(markup);
3725
+ range.createFromNode(list.last(contents)).collapse().select();
3726
+ afterCommand($editable);
3727
+ };
3728
+
3465
3729
  /**
3466
3730
  * formatBlock
3467
3731
  *
@@ -3470,6 +3734,7 @@
3470
3734
  */
3471
3735
  this.formatBlock = function ($editable, tagName) {
3472
3736
  beforeCommand($editable);
3737
+ // [workaround] for MSIE, IE need `<`
3473
3738
  tagName = agent.isMSIE ? '<' + tagName + '>' : tagName;
3474
3739
  document.execCommand('FormatBlock', false, tagName);
3475
3740
  afterCommand($editable);
@@ -3492,8 +3757,7 @@
3492
3757
  /* jshint ignore:end */
3493
3758
 
3494
3759
  /**
3495
- * fontsize
3496
- * FIXME: Still buggy
3760
+ * fontSize
3497
3761
  *
3498
3762
  * @param {jQuery} $editable
3499
3763
  * @param {String} value - px
@@ -3501,16 +3765,13 @@
3501
3765
  this.fontSize = function ($editable, value) {
3502
3766
  beforeCommand($editable);
3503
3767
 
3504
- document.execCommand('fontSize', false, 3);
3505
- if (agent.isFF) {
3506
- // firefox: <font size="3"> to <span style='font-size={value}px;'>, buggy
3507
- $editable.find('font[size=3]').removeAttr('size').css('font-size', value + 'px');
3508
- } else {
3509
- // chrome: <span style="font-size: medium"> to <span style='font-size={value}px;'>
3510
- $editable.find('span').filter(function () {
3511
- return this.style.fontSize === 'medium';
3512
- }).css('font-size', value + 'px');
3513
- }
3768
+ var rng = this.createRange($editable);
3769
+ var spans = style.styleNodes(rng);
3770
+ $.each(spans, function (idx, span) {
3771
+ $(span).css({
3772
+ 'font-size': value + 'px'
3773
+ });
3774
+ });
3514
3775
 
3515
3776
  afterCommand($editable);
3516
3777
  };
@@ -3582,10 +3843,12 @@
3582
3843
  }
3583
3844
 
3584
3845
  $.each(anchors, function (idx, anchor) {
3585
- $(anchor).attr({
3586
- href: linkUrl,
3587
- target: isNewWindow ? '_blank' : ''
3588
- });
3846
+ $(anchor).attr('href', linkUrl);
3847
+ if (isNewWindow) {
3848
+ $(anchor).attr('target', '_blank');
3849
+ } else {
3850
+ $(anchor).removeAttr('target');
3851
+ }
3589
3852
  });
3590
3853
 
3591
3854
  var startRange = range.createFromNode(list.head(anchors)).collapse(true);
@@ -3623,7 +3886,7 @@
3623
3886
  return {
3624
3887
  range: rng,
3625
3888
  text: rng.toString(),
3626
- isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : true,
3889
+ isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : false,
3627
3890
  url: $anchor.length ? $anchor.attr('href') : ''
3628
3891
  };
3629
3892
  };
@@ -3748,81 +4011,26 @@
3748
4011
  beforeCommand($editable);
3749
4012
  $target.detach();
3750
4013
 
3751
- var callbacks = $editable.data('callbacks');
3752
- if (callbacks && callbacks.onMediaDelete) {
3753
- callbacks.onMediaDelete($target, this, $editable);
3754
- }
4014
+ handler.bindCustomEvent(
4015
+ $(), $editable.data('callbacks'), 'media.delete'
4016
+ ).call($target, this.$editable);
3755
4017
 
3756
4018
  afterCommand($editable);
3757
4019
  };
3758
- };
3759
-
3760
- /**
3761
- * @class editing.History
3762
- *
3763
- * Editor History
3764
- *
3765
- */
3766
- var History = function ($editable) {
3767
- var stack = [], stackOffset = -1;
3768
- var editable = $editable[0];
3769
-
3770
- var makeSnapshot = function () {
3771
- var rng = range.create();
3772
- var emptyBookmark = {s: {path: [0], offset: 0}, e: {path: [0], offset: 0}};
3773
-
3774
- return {
3775
- contents: $editable.html(),
3776
- bookmark: (rng ? rng.bookmark(editable) : emptyBookmark)
3777
- };
3778
- };
3779
-
3780
- var applySnapshot = function (snapshot) {
3781
- if (snapshot.contents !== null) {
3782
- $editable.html(snapshot.contents);
3783
- }
3784
- if (snapshot.bookmark !== null) {
3785
- range.createFromBookmark(editable, snapshot.bookmark).select();
3786
- }
3787
- };
3788
-
3789
- /**
3790
- * undo
3791
- */
3792
- this.undo = function () {
3793
- if (0 < stackOffset) {
3794
- stackOffset--;
3795
- applySnapshot(stack[stackOffset]);
3796
- }
3797
- };
3798
-
3799
- /**
3800
- * redo
3801
- */
3802
- this.redo = function () {
3803
- if (stack.length - 1 > stackOffset) {
3804
- stackOffset++;
3805
- applySnapshot(stack[stackOffset]);
3806
- }
3807
- };
3808
4020
 
3809
4021
  /**
3810
- * recorded undo
4022
+ * set focus
4023
+ *
4024
+ * @param $editable
3811
4025
  */
3812
- this.recordUndo = function () {
3813
- stackOffset++;
4026
+ this.focus = function ($editable) {
4027
+ $editable.focus();
3814
4028
 
3815
- // Wash out stack after stackOffset
3816
- if (stack.length > stackOffset) {
3817
- stack = stack.slice(0, stackOffset);
4029
+ // [workaround] for firefox bug http://goo.gl/lVfAaI
4030
+ if (agent.isFF) {
4031
+ range.createFromNode($editable[0].firstChild || $editable[0]).collapse().select();
3818
4032
  }
3819
-
3820
- // Create new snapshot and push it to the end
3821
- stack.push(makeSnapshot());
3822
4033
  };
3823
-
3824
- // Create first undo stack
3825
- this.recordUndo();
3826
4034
  };
3827
4035
 
3828
4036
  /**
@@ -3908,10 +4116,18 @@
3908
4116
  if ($fontname.length) {
3909
4117
  var selectedFont = styleInfo['font-family'];
3910
4118
  if (!!selectedFont) {
3911
- selectedFont = list.head(selectedFont.split(','));
3912
- selectedFont = selectedFont.replace(/\'/g, '');
4119
+
4120
+ var list = selectedFont.split(',');
4121
+ for (var i = 0, len = list.length; i < len; i++) {
4122
+ selectedFont = list[i].replace(/[\'\"]/g, '').replace(/\s+$/, '').replace(/^\s+/, '');
4123
+ if (agent.isFontInstalled(selectedFont)) {
4124
+ break;
4125
+ }
4126
+ }
4127
+
3913
4128
  $fontname.find('.note-current-fontname').text(selectedFont);
3914
4129
  checkDropdownMenu($fontname, selectedFont);
4130
+
3915
4131
  }
3916
4132
  }
3917
4133
 
@@ -4022,7 +4238,6 @@
4022
4238
  };
4023
4239
 
4024
4240
  /**
4025
- *
4026
4241
  * @param {jQuery} $container
4027
4242
  * @param {Boolean} [bFullscreen=false]
4028
4243
  */
@@ -4032,13 +4247,83 @@
4032
4247
  };
4033
4248
 
4034
4249
  /**
4035
- *
4036
4250
  * @param {jQuery} $container
4037
4251
  * @param {Boolean} [isCodeview=false]
4038
4252
  */
4039
4253
  this.updateCodeview = function ($container, isCodeview) {
4040
4254
  var $btn = $container.find('button[data-event="codeview"]');
4041
4255
  $btn.toggleClass('active', isCodeview);
4256
+
4257
+ if (isCodeview) {
4258
+ this.deactivate($container);
4259
+ } else {
4260
+ this.activate($container);
4261
+ }
4262
+ };
4263
+
4264
+ /**
4265
+ * get button in toolbar
4266
+ *
4267
+ * @param {jQuery} $editable
4268
+ * @param {String} name
4269
+ * @return {jQuery}
4270
+ */
4271
+ this.get = function ($editable, name) {
4272
+ var $toolbar = dom.makeLayoutInfo($editable).toolbar();
4273
+
4274
+ return $toolbar.find('[data-name=' + name + ']');
4275
+ };
4276
+
4277
+ /**
4278
+ * set button state
4279
+ * @param {jQuery} $editable
4280
+ * @param {String} name
4281
+ * @param {Boolean} [isActive=true]
4282
+ */
4283
+ this.setButtonState = function ($editable, name, isActive) {
4284
+ isActive = (isActive === false) ? false : true;
4285
+
4286
+ var $button = this.get($editable, name);
4287
+ $button.toggleClass('active', isActive);
4288
+ };
4289
+ };
4290
+
4291
+ var EDITABLE_PADDING = 24;
4292
+
4293
+ var Statusbar = function () {
4294
+ var $document = $(document);
4295
+
4296
+ this.attach = function (layoutInfo, options) {
4297
+ if (!options.disableResizeEditor) {
4298
+ layoutInfo.statusbar().on('mousedown', hStatusbarMousedown);
4299
+ }
4300
+ };
4301
+
4302
+ /**
4303
+ * `mousedown` event handler on statusbar
4304
+ *
4305
+ * @param {MouseEvent} event
4306
+ */
4307
+ var hStatusbarMousedown = function (event) {
4308
+ event.preventDefault();
4309
+ event.stopPropagation();
4310
+
4311
+ var $editable = dom.makeLayoutInfo(event.target).editable();
4312
+ var editableTop = $editable.offset().top - $document.scrollTop();
4313
+
4314
+ var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
4315
+ var options = layoutInfo.editor().data('options');
4316
+
4317
+ $document.on('mousemove', function (event) {
4318
+ var nHeight = event.clientY - (editableTop + EDITABLE_PADDING);
4319
+
4320
+ nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight;
4321
+ nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight;
4322
+
4323
+ $editable.height(nHeight);
4324
+ }).one('mouseup', function () {
4325
+ $document.off('mousemove');
4326
+ });
4042
4327
  };
4043
4328
  };
4044
4329
 
@@ -4103,7 +4388,13 @@
4103
4388
  if (styleInfo.anchor) {
4104
4389
  var $anchor = $linkPopover.find('a');
4105
4390
  var href = $(styleInfo.anchor).attr('href');
4391
+ var target = $(styleInfo.anchor).attr('target');
4106
4392
  $anchor.attr('href', href).html(href);
4393
+ if (!target) {
4394
+ $anchor.removeAttr('target');
4395
+ } else {
4396
+ $anchor.attr('target', '_blank');
4397
+ }
4107
4398
  showPopover($linkPopover, posFromPlaceholder(styleInfo.anchor, isAirMode));
4108
4399
  } else {
4109
4400
  $linkPopover.hide();
@@ -4154,7 +4445,55 @@
4154
4445
  *
4155
4446
  * Handle
4156
4447
  */
4157
- var Handle = function () {
4448
+ var Handle = function (handler) {
4449
+ var $document = $(document);
4450
+
4451
+ /**
4452
+ * `mousedown` event handler on $handle
4453
+ * - controlSizing: resize image
4454
+ *
4455
+ * @param {MouseEvent} event
4456
+ */
4457
+ var hHandleMousedown = function (event) {
4458
+ if (dom.isControlSizing(event.target)) {
4459
+ event.preventDefault();
4460
+ event.stopPropagation();
4461
+
4462
+ var layoutInfo = dom.makeLayoutInfo(event.target),
4463
+ $handle = layoutInfo.handle(),
4464
+ $popover = layoutInfo.popover(),
4465
+ $editable = layoutInfo.editable(),
4466
+ $editor = layoutInfo.editor();
4467
+
4468
+ var target = $handle.find('.note-control-selection').data('target'),
4469
+ $target = $(target), posStart = $target.offset(),
4470
+ scrollTop = $document.scrollTop();
4471
+
4472
+ var isAirMode = $editor.data('options').airMode;
4473
+
4474
+ $document.on('mousemove', function (event) {
4475
+ handler.invoke('editor.resizeTo', {
4476
+ x: event.clientX - posStart.left,
4477
+ y: event.clientY - (posStart.top - scrollTop)
4478
+ }, $target, !event.shiftKey);
4479
+
4480
+ handler.invoke('handle.update', $handle, {image: target}, isAirMode);
4481
+ handler.invoke('popover.update', $popover, {image: target}, isAirMode);
4482
+ }).one('mouseup', function () {
4483
+ $document.off('mousemove');
4484
+ handler.invoke('editor.afterCommand', $editable);
4485
+ });
4486
+
4487
+ if (!$target.data('ratio')) { // original ratio.
4488
+ $target.data('ratio', $target.height() / $target.width());
4489
+ }
4490
+ }
4491
+ };
4492
+
4493
+ this.attach = function (layoutInfo) {
4494
+ layoutInfo.handle().on('mousedown', hHandleMousedown);
4495
+ };
4496
+
4158
4497
  /**
4159
4498
  * update handle
4160
4499
  * @param {jQuery} $handle
@@ -4197,97 +4536,389 @@
4197
4536
  };
4198
4537
  };
4199
4538
 
4200
- /**
4201
- * @class module.Dialog
4202
- *
4203
- * Dialog
4204
- *
4205
- */
4206
- var Dialog = function () {
4539
+ var Fullscreen = function (handler) {
4540
+ var $window = $(window);
4541
+ var $scrollbar = $('html, body');
4207
4542
 
4208
4543
  /**
4209
- * toggle button status
4544
+ * toggle fullscreen
4210
4545
  *
4211
- * @private
4212
- * @param {jQuery} $btn
4213
- * @param {Boolean} isEnable
4546
+ * @param {Object} layoutInfo
4214
4547
  */
4215
- var toggleBtn = function ($btn, isEnable) {
4216
- $btn.toggleClass('disabled', !isEnable);
4217
- $btn.attr('disabled', !isEnable);
4218
- };
4548
+ this.toggle = function (layoutInfo) {
4219
4549
 
4220
- /**
4221
- * show image dialog
4222
- *
4223
- * @param {jQuery} $editable
4224
- * @param {jQuery} $dialog
4225
- * @return {Promise}
4226
- */
4227
- this.showImageDialog = function ($editable, $dialog) {
4228
- return $.Deferred(function (deferred) {
4229
- var $imageDialog = $dialog.find('.note-image-dialog');
4550
+ var $editor = layoutInfo.editor(),
4551
+ $toolbar = layoutInfo.toolbar(),
4552
+ $editable = layoutInfo.editable(),
4553
+ $codable = layoutInfo.codable();
4554
+
4555
+ var resize = function (size) {
4556
+ $editable.css('height', size.h);
4557
+ $codable.css('height', size.h);
4558
+ if ($codable.data('cmeditor')) {
4559
+ $codable.data('cmeditor').setsize(null, size.h);
4560
+ }
4561
+ };
4230
4562
 
4231
- var $imageInput = $dialog.find('.note-image-input'),
4232
- $imageUrl = $dialog.find('.note-image-url'),
4233
- $imageBtn = $dialog.find('.note-image-btn');
4563
+ $editor.toggleClass('fullscreen');
4564
+ var isFullscreen = $editor.hasClass('fullscreen');
4565
+ if (isFullscreen) {
4566
+ $editable.data('orgheight', $editable.css('height'));
4234
4567
 
4235
- $imageDialog.one('shown.bs.modal', function () {
4236
- // Cloning imageInput to clear element.
4237
- $imageInput.replaceWith($imageInput.clone()
4238
- .on('change', function () {
4239
- deferred.resolve(this.files || this.value);
4240
- $imageDialog.modal('hide');
4241
- })
4242
- .val('')
4243
- );
4568
+ $window.on('resize', function () {
4569
+ resize({
4570
+ h: $window.height() - $toolbar.outerHeight()
4571
+ });
4572
+ }).trigger('resize');
4244
4573
 
4245
- $imageBtn.click(function (event) {
4246
- event.preventDefault();
4574
+ $scrollbar.css('overflow', 'hidden');
4575
+ } else {
4576
+ $window.off('resize');
4577
+ resize({
4578
+ h: $editable.data('orgheight')
4579
+ });
4580
+ $scrollbar.css('overflow', 'visible');
4581
+ }
4247
4582
 
4248
- deferred.resolve($imageUrl.val());
4249
- $imageDialog.modal('hide');
4250
- });
4583
+ handler.invoke('toolbar.updateFullscreen', $toolbar, isFullscreen);
4584
+ };
4585
+ };
4251
4586
 
4252
- $imageUrl.on('keyup paste', function (event) {
4253
- var url;
4254
-
4255
- if (event.type === 'paste') {
4256
- url = event.originalEvent.clipboardData.getData('text');
4257
- } else {
4258
- url = $imageUrl.val();
4259
- }
4260
-
4261
- toggleBtn($imageBtn, url);
4262
- }).val('').trigger('focus');
4263
- }).one('hidden.bs.modal', function () {
4264
- $imageInput.off('change');
4265
- $imageUrl.off('keyup paste');
4266
- $imageBtn.off('click');
4267
4587
 
4268
- if (deferred.state() === 'pending') {
4269
- deferred.reject();
4270
- }
4271
- }).modal('show');
4588
+ var CodeMirror;
4589
+ if (agent.hasCodeMirror) {
4590
+ if (agent.isSupportAmd) {
4591
+ require(['CodeMirror'], function (cm) {
4592
+ CodeMirror = cm;
4272
4593
  });
4594
+ } else {
4595
+ CodeMirror = window.CodeMirror;
4596
+ }
4597
+ }
4598
+
4599
+ /**
4600
+ * @class Codeview
4601
+ */
4602
+ var Codeview = function (handler) {
4603
+
4604
+ this.sync = function (layoutInfo) {
4605
+ var isCodeview = handler.invoke('codeview.isActivated', layoutInfo);
4606
+ if (isCodeview && agent.hasCodeMirror) {
4607
+ layoutInfo.codable().data('cmEditor').save();
4608
+ }
4273
4609
  };
4274
4610
 
4275
4611
  /**
4276
- * Show link dialog and set event handlers on dialog controls.
4277
- *
4278
- * @param {jQuery} $editable
4279
- * @param {jQuery} $dialog
4280
- * @param {Object} linkInfo
4281
- * @return {Promise}
4612
+ * @param {Object} layoutInfo
4613
+ * @return {Boolean}
4282
4614
  */
4283
- this.showLinkDialog = function ($editable, $dialog, linkInfo) {
4284
- return $.Deferred(function (deferred) {
4285
- var $linkDialog = $dialog.find('.note-link-dialog');
4615
+ this.isActivated = function (layoutInfo) {
4616
+ var $editor = layoutInfo.editor();
4617
+ return $editor.hasClass('codeview');
4618
+ };
4286
4619
 
4287
- var $linkText = $linkDialog.find('.note-link-text'),
4288
- $linkUrl = $linkDialog.find('.note-link-url'),
4289
- $linkBtn = $linkDialog.find('.note-link-btn'),
4290
- $openInNewWindow = $linkDialog.find('input[type=checkbox]');
4620
+ /**
4621
+ * toggle codeview
4622
+ *
4623
+ * @param {Object} layoutInfo
4624
+ */
4625
+ this.toggle = function (layoutInfo) {
4626
+ if (this.isActivated(layoutInfo)) {
4627
+ this.deactivate(layoutInfo);
4628
+ } else {
4629
+ this.activate(layoutInfo);
4630
+ }
4631
+ };
4632
+
4633
+ /**
4634
+ * activate code view
4635
+ *
4636
+ * @param {Object} layoutInfo
4637
+ */
4638
+ this.activate = function (layoutInfo) {
4639
+ var $editor = layoutInfo.editor(),
4640
+ $toolbar = layoutInfo.toolbar(),
4641
+ $editable = layoutInfo.editable(),
4642
+ $codable = layoutInfo.codable(),
4643
+ $popover = layoutInfo.popover(),
4644
+ $handle = layoutInfo.handle();
4645
+
4646
+ var options = $editor.data('options');
4647
+
4648
+ $codable.val(dom.html($editable, options.prettifyHtml));
4649
+ $codable.height($editable.height());
4650
+
4651
+ handler.invoke('toolbar.updateCodeview', $toolbar, true);
4652
+ handler.invoke('popover.hide', $popover);
4653
+ handler.invoke('handle.hide', $handle);
4654
+
4655
+ $editor.addClass('codeview');
4656
+
4657
+ $codable.focus();
4658
+
4659
+ // activate CodeMirror as codable
4660
+ if (agent.hasCodeMirror) {
4661
+ var cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
4662
+
4663
+ // CodeMirror TernServer
4664
+ if (options.codemirror.tern) {
4665
+ var server = new CodeMirror.TernServer(options.codemirror.tern);
4666
+ cmEditor.ternServer = server;
4667
+ cmEditor.on('cursorActivity', function (cm) {
4668
+ server.updateArgHints(cm);
4669
+ });
4670
+ }
4671
+
4672
+ // CodeMirror hasn't Padding.
4673
+ cmEditor.setSize(null, $editable.outerHeight());
4674
+ $codable.data('cmEditor', cmEditor);
4675
+ }
4676
+ };
4677
+
4678
+ /**
4679
+ * deactivate code view
4680
+ *
4681
+ * @param {Object} layoutInfo
4682
+ */
4683
+ this.deactivate = function (layoutInfo) {
4684
+ var $editor = layoutInfo.editor(),
4685
+ $toolbar = layoutInfo.toolbar(),
4686
+ $editable = layoutInfo.editable(),
4687
+ $codable = layoutInfo.codable();
4688
+
4689
+ var options = $editor.data('options');
4690
+
4691
+ // deactivate CodeMirror as codable
4692
+ if (agent.hasCodeMirror) {
4693
+ var cmEditor = $codable.data('cmEditor');
4694
+ $codable.val(cmEditor.getValue());
4695
+ cmEditor.toTextArea();
4696
+ }
4697
+
4698
+ $editable.html(dom.value($codable, options.prettifyHtml) || dom.emptyPara);
4699
+ $editable.height(options.height ? $codable.height() : 'auto');
4700
+ $editor.removeClass('codeview');
4701
+
4702
+ $editable.focus();
4703
+
4704
+ handler.invoke('toolbar.updateCodeview', $toolbar, false);
4705
+ };
4706
+ };
4707
+
4708
+ var DragAndDrop = function (handler) {
4709
+ var $document = $(document);
4710
+
4711
+ /**
4712
+ * attach Drag and Drop Events
4713
+ *
4714
+ * @param {Object} layoutInfo - layout Informations
4715
+ * @param {Object} options
4716
+ */
4717
+ this.attach = function (layoutInfo, options) {
4718
+ if (options.airMode || options.disableDragAndDrop) {
4719
+ // prevent default drop event
4720
+ $document.on('drop', function (e) {
4721
+ e.preventDefault();
4722
+ });
4723
+ } else {
4724
+ this.attachDragAndDropEvent(layoutInfo, options);
4725
+ }
4726
+ };
4727
+
4728
+ /**
4729
+ * attach Drag and Drop Events
4730
+ *
4731
+ * @param {Object} layoutInfo - layout Informations
4732
+ * @param {Object} options
4733
+ */
4734
+ this.attachDragAndDropEvent = function (layoutInfo, options) {
4735
+ var collection = $(),
4736
+ $editor = layoutInfo.editor(),
4737
+ $dropzone = layoutInfo.dropzone(),
4738
+ $dropzoneMessage = $dropzone.find('.note-dropzone-message');
4739
+
4740
+ // show dropzone on dragenter when dragging a object to document
4741
+ // -but only if the editor is visible, i.e. has a positive width and height
4742
+ $document.on('dragenter', function (e) {
4743
+ var isCodeview = handler.invoke('codeview.isActivated', layoutInfo);
4744
+ var hasEditorSize = $editor.width() > 0 && $editor.height() > 0;
4745
+ if (!isCodeview && !collection.length && hasEditorSize) {
4746
+ $editor.addClass('dragover');
4747
+ $dropzone.width($editor.width());
4748
+ $dropzone.height($editor.height());
4749
+ $dropzoneMessage.text(options.langInfo.image.dragImageHere);
4750
+ }
4751
+ collection = collection.add(e.target);
4752
+ }).on('dragleave', function (e) {
4753
+ collection = collection.not(e.target);
4754
+ if (!collection.length) {
4755
+ $editor.removeClass('dragover');
4756
+ }
4757
+ }).on('drop', function () {
4758
+ collection = $();
4759
+ $editor.removeClass('dragover');
4760
+ });
4761
+
4762
+ // change dropzone's message on hover.
4763
+ $dropzone.on('dragenter', function () {
4764
+ $dropzone.addClass('hover');
4765
+ $dropzoneMessage.text(options.langInfo.image.dropImage);
4766
+ }).on('dragleave', function () {
4767
+ $dropzone.removeClass('hover');
4768
+ $dropzoneMessage.text(options.langInfo.image.dragImageHere);
4769
+ });
4770
+
4771
+ // attach dropImage
4772
+ $dropzone.on('drop', function (event) {
4773
+ event.preventDefault();
4774
+
4775
+ var dataTransfer = event.originalEvent.dataTransfer;
4776
+ var html = dataTransfer.getData('text/html');
4777
+ var text = dataTransfer.getData('text/plain');
4778
+
4779
+ var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
4780
+
4781
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
4782
+ layoutInfo.editable().focus();
4783
+ handler.insertImages(layoutInfo, dataTransfer.files);
4784
+ } else if (html) {
4785
+ $(html).each(function () {
4786
+ layoutInfo.editable().focus();
4787
+ handler.invoke('editor.insertNode', layoutInfo.editable(), this);
4788
+ });
4789
+ } else if (text) {
4790
+ layoutInfo.editable().focus();
4791
+ handler.invoke('editor.insertText', layoutInfo.editable(), text);
4792
+ }
4793
+ }).on('dragover', false); // prevent default dragover event
4794
+ };
4795
+ };
4796
+
4797
+ var Clipboard = function (handler) {
4798
+
4799
+ this.attach = function (layoutInfo) {
4800
+ layoutInfo.editable().on('paste', hPasteClipboardImage);
4801
+ };
4802
+
4803
+ /**
4804
+ * paste clipboard image
4805
+ *
4806
+ * @param {Event} event
4807
+ */
4808
+ var hPasteClipboardImage = function (event) {
4809
+ var clipboardData = event.originalEvent.clipboardData;
4810
+ var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
4811
+ var $editable = layoutInfo.editable();
4812
+
4813
+ if (!clipboardData || !clipboardData.items || !clipboardData.items.length) {
4814
+ var callbacks = $editable.data('callbacks');
4815
+ // only can run if it has onImageUpload method
4816
+ if (!callbacks.onImageUpload) {
4817
+ return;
4818
+ }
4819
+
4820
+ // save cursor
4821
+ handler.invoke('editor.saveNode', $editable);
4822
+ handler.invoke('editor.saveRange', $editable);
4823
+
4824
+ $editable.html('');
4825
+
4826
+ setTimeout(function () {
4827
+ var $img = $editable.find('img');
4828
+
4829
+ // if img is no in clipboard, insert text or dom
4830
+ if (!$img.length || $img[0].src.indexOf('data:') === -1) {
4831
+ var html = $editable.html();
4832
+
4833
+ handler.invoke('editor.restoreNode', $editable);
4834
+ handler.invoke('editor.restoreRange', $editable);
4835
+
4836
+ handler.invoke('editor.focus', $editable);
4837
+ try {
4838
+ handler.invoke('editor.pasteHTML', $editable, html);
4839
+ } catch (ex) {
4840
+ handler.invoke('editor.insertText', $editable, html);
4841
+ }
4842
+ return;
4843
+ }
4844
+
4845
+ var datauri = $img[0].src;
4846
+
4847
+ var data = atob(datauri.split(',')[1]);
4848
+ var array = new Uint8Array(data.length);
4849
+ for (var i = 0; i < data.length; i++) {
4850
+ array[i] = data.charCodeAt(i);
4851
+ }
4852
+
4853
+ var blob = new Blob([array], { type : 'image/png' });
4854
+ blob.name = 'clipboard.png';
4855
+
4856
+ handler.invoke('editor.restoreNode', $editable);
4857
+ handler.invoke('editor.restoreRange', $editable);
4858
+ handler.insertImages(layoutInfo, [blob]);
4859
+
4860
+ handler.invoke('editor.afterCommand', $editable);
4861
+ }, 0);
4862
+
4863
+ return;
4864
+ }
4865
+
4866
+ var item = list.head(clipboardData.items);
4867
+ var isClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
4868
+
4869
+ if (isClipboardImage) {
4870
+ handler.insertImages(layoutInfo, [item.getAsFile()]);
4871
+ }
4872
+
4873
+ handler.invoke('editor.afterCommand', $editable);
4874
+ };
4875
+ };
4876
+
4877
+ var LinkDialog = function (handler) {
4878
+
4879
+ /**
4880
+ * toggle button status
4881
+ *
4882
+ * @private
4883
+ * @param {jQuery} $btn
4884
+ * @param {Boolean} isEnable
4885
+ */
4886
+ var toggleBtn = function ($btn, isEnable) {
4887
+ $btn.toggleClass('disabled', !isEnable);
4888
+ $btn.attr('disabled', !isEnable);
4889
+ };
4890
+
4891
+ /**
4892
+ * bind enter key
4893
+ *
4894
+ * @private
4895
+ * @param {jQuery} $input
4896
+ * @param {jQuery} $btn
4897
+ */
4898
+ var bindEnterKey = function ($input, $btn) {
4899
+ $input.on('keypress', function (event) {
4900
+ if (event.keyCode === key.code.ENTER) {
4901
+ $btn.trigger('click');
4902
+ }
4903
+ });
4904
+ };
4905
+
4906
+ /**
4907
+ * Show link dialog and set event handlers on dialog controls.
4908
+ *
4909
+ * @param {jQuery} $editable
4910
+ * @param {jQuery} $dialog
4911
+ * @param {Object} linkInfo
4912
+ * @return {Promise}
4913
+ */
4914
+ this.showLinkDialog = function ($editable, $dialog, linkInfo) {
4915
+ return $.Deferred(function (deferred) {
4916
+ var $linkDialog = $dialog.find('.note-link-dialog');
4917
+
4918
+ var $linkText = $linkDialog.find('.note-link-text'),
4919
+ $linkUrl = $linkDialog.find('.note-link-url'),
4920
+ $linkBtn = $linkDialog.find('.note-link-btn'),
4921
+ $openInNewWindow = $linkDialog.find('input[type=checkbox]');
4291
4922
 
4292
4923
  $linkDialog.one('shown.bs.modal', function () {
4293
4924
  $linkText.val(linkInfo.text);
@@ -4313,6 +4944,9 @@
4313
4944
  }
4314
4945
  }).val(linkInfo.url).trigger('focus').trigger('select');
4315
4946
 
4947
+ bindEnterKey($linkUrl, $linkBtn);
4948
+ bindEnterKey($linkText, $linkBtn);
4949
+
4316
4950
  $openInNewWindow.prop('checked', linkInfo.newWindow);
4317
4951
 
4318
4952
  $linkBtn.one('click', function (event) {
@@ -4328,8 +4962,8 @@
4328
4962
  });
4329
4963
  }).one('hidden.bs.modal', function () {
4330
4964
  // detach events
4331
- $linkText.off('input');
4332
- $linkUrl.off('input');
4965
+ $linkText.off('input keypress');
4966
+ $linkUrl.off('input keypress');
4333
4967
  $linkBtn.off('click');
4334
4968
 
4335
4969
  if (deferred.state() === 'pending') {
@@ -4339,6 +4973,136 @@
4339
4973
  }).promise();
4340
4974
  };
4341
4975
 
4976
+ /**
4977
+ * @param {Object} layoutInfo
4978
+ */
4979
+ this.show = function (layoutInfo) {
4980
+ var $editor = layoutInfo.editor(),
4981
+ $dialog = layoutInfo.dialog(),
4982
+ $editable = layoutInfo.editable(),
4983
+ $popover = layoutInfo.popover(),
4984
+ linkInfo = handler.invoke('editor.getLinkInfo', $editable);
4985
+
4986
+ var options = $editor.data('options');
4987
+
4988
+ handler.invoke('editor.saveRange', $editable);
4989
+ this.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) {
4990
+ handler.invoke('editor.restoreRange', $editable);
4991
+ handler.invoke('editor.createLink', $editable, linkInfo, options);
4992
+ // hide popover after creating link
4993
+ handler.invoke('popover.hide', $popover);
4994
+ }).fail(function () {
4995
+ handler.invoke('editor.restoreRange', $editable);
4996
+ });
4997
+ };
4998
+ };
4999
+
5000
+ var ImageDialog = function (handler) {
5001
+ /**
5002
+ * toggle button status
5003
+ *
5004
+ * @private
5005
+ * @param {jQuery} $btn
5006
+ * @param {Boolean} isEnable
5007
+ */
5008
+ var toggleBtn = function ($btn, isEnable) {
5009
+ $btn.toggleClass('disabled', !isEnable);
5010
+ $btn.attr('disabled', !isEnable);
5011
+ };
5012
+
5013
+ /**
5014
+ * bind enter key
5015
+ *
5016
+ * @private
5017
+ * @param {jQuery} $input
5018
+ * @param {jQuery} $btn
5019
+ */
5020
+ var bindEnterKey = function ($input, $btn) {
5021
+ $input.on('keypress', function (event) {
5022
+ if (event.keyCode === key.code.ENTER) {
5023
+ $btn.trigger('click');
5024
+ }
5025
+ });
5026
+ };
5027
+
5028
+ this.show = function (layoutInfo) {
5029
+ var $dialog = layoutInfo.dialog(),
5030
+ $editable = layoutInfo.editable();
5031
+
5032
+ handler.invoke('editor.saveRange', $editable);
5033
+ this.showImageDialog($editable, $dialog).then(function (data) {
5034
+ handler.invoke('editor.restoreRange', $editable);
5035
+
5036
+ if (typeof data === 'string') {
5037
+ // image url
5038
+ handler.invoke('editor.insertImage', $editable, data);
5039
+ } else {
5040
+ // array of files
5041
+ handler.insertImages(layoutInfo, data);
5042
+ }
5043
+ }).fail(function () {
5044
+ handler.invoke('editor.restoreRange', $editable);
5045
+ });
5046
+ };
5047
+
5048
+ /**
5049
+ * show image dialog
5050
+ *
5051
+ * @param {jQuery} $editable
5052
+ * @param {jQuery} $dialog
5053
+ * @return {Promise}
5054
+ */
5055
+ this.showImageDialog = function ($editable, $dialog) {
5056
+ return $.Deferred(function (deferred) {
5057
+ var $imageDialog = $dialog.find('.note-image-dialog');
5058
+
5059
+ var $imageInput = $dialog.find('.note-image-input'),
5060
+ $imageUrl = $dialog.find('.note-image-url'),
5061
+ $imageBtn = $dialog.find('.note-image-btn');
5062
+
5063
+ $imageDialog.one('shown.bs.modal', function () {
5064
+ // Cloning imageInput to clear element.
5065
+ $imageInput.replaceWith($imageInput.clone()
5066
+ .on('change', function () {
5067
+ deferred.resolve(this.files || this.value);
5068
+ $imageDialog.modal('hide');
5069
+ })
5070
+ .val('')
5071
+ );
5072
+
5073
+ $imageBtn.click(function (event) {
5074
+ event.preventDefault();
5075
+
5076
+ deferred.resolve($imageUrl.val());
5077
+ $imageDialog.modal('hide');
5078
+ });
5079
+
5080
+ $imageUrl.on('keyup paste', function (event) {
5081
+ var url;
5082
+
5083
+ if (event.type === 'paste') {
5084
+ url = event.originalEvent.clipboardData.getData('text');
5085
+ } else {
5086
+ url = $imageUrl.val();
5087
+ }
5088
+
5089
+ toggleBtn($imageBtn, url);
5090
+ }).val('').trigger('focus');
5091
+ bindEnterKey($imageUrl, $imageBtn);
5092
+ }).one('hidden.bs.modal', function () {
5093
+ $imageInput.off('change');
5094
+ $imageUrl.off('keyup paste keypress');
5095
+ $imageBtn.off('click');
5096
+
5097
+ if (deferred.state() === 'pending') {
5098
+ deferred.reject();
5099
+ }
5100
+ }).modal('show');
5101
+ });
5102
+ };
5103
+ };
5104
+
5105
+ var HelpDialog = function (handler) {
4342
5106
  /**
4343
5107
  * show help dialog
4344
5108
  *
@@ -4355,62 +5119,89 @@
4355
5119
  }).modal('show');
4356
5120
  }).promise();
4357
5121
  };
4358
- };
4359
5122
 
5123
+ /**
5124
+ * @param {Object} layoutInfo
5125
+ */
5126
+ this.show = function (layoutInfo) {
5127
+ var $dialog = layoutInfo.dialog(),
5128
+ $editable = layoutInfo.editable();
4360
5129
 
4361
- var CodeMirror;
4362
- if (agent.hasCodeMirror) {
4363
- if (agent.isSupportAmd) {
4364
- require(['CodeMirror'], function (cm) {
4365
- CodeMirror = cm;
5130
+ handler.invoke('editor.saveRange', $editable, true);
5131
+ this.showHelpDialog($editable, $dialog).then(function () {
5132
+ handler.invoke('editor.restoreRange', $editable);
4366
5133
  });
4367
- } else {
4368
- CodeMirror = window.CodeMirror;
4369
- }
4370
- }
5134
+ };
5135
+ };
5136
+
4371
5137
 
4372
5138
  /**
4373
5139
  * @class EventHandler
4374
5140
  *
4375
5141
  * EventHandler
5142
+ * - TODO: new instance per a editor
5143
+ * - TODO: rename EventHandler
4376
5144
  */
4377
5145
  var EventHandler = function () {
4378
- var $window = $(window);
4379
- var $document = $(document);
4380
- var $scrollbar = $('html, body');
4381
-
4382
- var editor = new Editor();
4383
- var toolbar = new Toolbar(), popover = new Popover();
4384
- var handle = new Handle(), dialog = new Dialog();
4385
-
4386
5146
  /**
4387
- * get editor
4388
- * @returns {editing.Editor}
5147
+ * Modules
4389
5148
  */
4390
- this.getEditor = function () {
4391
- return editor;
5149
+ var modules = this.modules = {
5150
+ editor: new Editor(this),
5151
+ toolbar: new Toolbar(this),
5152
+ statusbar: new Statusbar(this),
5153
+ popover: new Popover(this),
5154
+ handle: new Handle(this),
5155
+ fullscreen: new Fullscreen(this),
5156
+ codeview: new Codeview(this),
5157
+ dragAndDrop: new DragAndDrop(this),
5158
+ clipboard: new Clipboard(this),
5159
+ linkDialog: new LinkDialog(this),
5160
+ imageDialog: new ImageDialog(this),
5161
+ helpDialog: new HelpDialog(this)
5162
+ };
5163
+
5164
+ // TODO refactor modules and eventHandler
5165
+ // - remove this method and use custom event from $holder instead
5166
+ this.invoke = function () {
5167
+ var moduleAndMethod = list.head(list.from(arguments));
5168
+ var args = list.tail(list.from(arguments));
5169
+
5170
+ var splits = moduleAndMethod.split('.');
5171
+ var hasSeparator = splits.length > 1;
5172
+ var moduleName = hasSeparator && list.head(splits);
5173
+ var methodName = hasSeparator ? list.last(splits) : list.head(splits);
5174
+
5175
+ var module = this.getModule(moduleName);
5176
+ var method = module[methodName];
5177
+
5178
+ return method && method.apply(module, args);
4392
5179
  };
4393
5180
 
4394
5181
  /**
4395
- * returns makeLayoutInfo from editor's descendant node.
5182
+ * returns module
4396
5183
  *
4397
- * @private
4398
- * @param {Node} descendant
4399
- * @return {Object}
5184
+ * @param {String} moduleName - name of module
5185
+ * @return {Module} - defaults is editor
4400
5186
  */
4401
- var makeLayoutInfo = function (descendant) {
4402
- var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
4403
-
4404
- if (!$target.length) { return null; }
4405
-
4406
- var $editor;
4407
- if ($target.is('.note-editor, .note-air-editor')) {
4408
- $editor = $target;
4409
- } else {
4410
- $editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
4411
- }
5187
+ this.getModule = function (moduleName) {
5188
+ return this.modules[moduleName] || this.modules.editor;
5189
+ };
4412
5190
 
4413
- return dom.buildLayoutInfo($editor);
5191
+ /**
5192
+ * @param {jQuery} $holder
5193
+ * @param {Object} callbacks
5194
+ * @param {String} eventNamespace
5195
+ * @returns {Function}
5196
+ */
5197
+ var bindCustomEvent = this.bindCustomEvent = function ($holder, callbacks, eventNamespace) {
5198
+ return function () {
5199
+ var callback = callbacks[func.namespaceToCamel(eventNamespace, 'on')];
5200
+ if (callback) {
5201
+ callback(arguments);
5202
+ }
5203
+ return $holder.trigger('summernote.' + eventNamespace, arguments);
5204
+ };
4414
5205
  };
4415
5206
 
4416
5207
  /**
@@ -4420,33 +5211,28 @@
4420
5211
  * @param {Object} layoutInfo
4421
5212
  * @param {File[]} files
4422
5213
  */
4423
- var insertImages = function (layoutInfo, files) {
5214
+ this.insertImages = function (layoutInfo, files) {
4424
5215
  var $editor = layoutInfo.editor(),
4425
- $editable = layoutInfo.editable();
5216
+ $editable = layoutInfo.editable(),
5217
+ $holder = layoutInfo.holder();
4426
5218
 
4427
5219
  var callbacks = $editable.data('callbacks');
4428
5220
  var options = $editor.data('options');
4429
5221
 
4430
5222
  // If onImageUpload options setted
4431
5223
  if (callbacks.onImageUpload) {
4432
- callbacks.onImageUpload(files, editor, $editable);
5224
+ bindCustomEvent($holder, callbacks, 'image.upload')([files]);
4433
5225
  // else insert Image as dataURL
4434
5226
  } else {
4435
5227
  $.each(files, function (idx, file) {
4436
5228
  var filename = file.name;
4437
5229
  if (options.maximumImageFileSize && options.maximumImageFileSize < file.size) {
4438
- if (callbacks.onImageUploadError) {
4439
- callbacks.onImageUploadError(options.langInfo.image.maximumFileSizeError);
4440
- } else {
4441
- alert(options.langInfo.image.maximumFileSizeError);
4442
- }
5230
+ bindCustomEvent($holder, callbacks, 'image.upload.error')(options.langInfo.image.maximumFileSizeError);
4443
5231
  } else {
4444
5232
  async.readFileAsDataURL(file).then(function (sDataURL) {
4445
- editor.insertImage($editable, sDataURL, filename);
5233
+ modules.editor.insertImage($editable, sDataURL, filename);
4446
5234
  }).fail(function () {
4447
- if (callbacks.onImageUploadError) {
4448
- callbacks.onImageUploadError();
4449
- }
5235
+ bindCustomEvent($holder, callbacks, 'image.upload.error')(options.langInfo.image.maximumFileSizeError);
4450
5236
  });
4451
5237
  }
4452
5238
  });
@@ -4458,303 +5244,67 @@
4458
5244
  * @param {Object} layoutInfo
4459
5245
  */
4460
5246
  showLinkDialog: function (layoutInfo) {
4461
- var $editor = layoutInfo.editor(),
4462
- $dialog = layoutInfo.dialog(),
4463
- $editable = layoutInfo.editable(),
4464
- linkInfo = editor.getLinkInfo($editable);
4465
-
4466
- var options = $editor.data('options');
4467
-
4468
- editor.saveRange($editable);
4469
- dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) {
4470
- editor.restoreRange($editable);
4471
- editor.createLink($editable, linkInfo, options);
4472
- // hide popover after creating link
4473
- popover.hide(layoutInfo.popover());
4474
- }).fail(function () {
4475
- editor.restoreRange($editable);
4476
- });
5247
+ modules.linkDialog.show(layoutInfo);
4477
5248
  },
4478
5249
 
4479
5250
  /**
4480
5251
  * @param {Object} layoutInfo
4481
5252
  */
4482
5253
  showImageDialog: function (layoutInfo) {
4483
- var $dialog = layoutInfo.dialog(),
4484
- $editable = layoutInfo.editable();
4485
-
4486
- editor.saveRange($editable);
4487
- dialog.showImageDialog($editable, $dialog).then(function (data) {
4488
- editor.restoreRange($editable);
4489
-
4490
- if (typeof data === 'string') {
4491
- // image url
4492
- editor.insertImage($editable, data);
4493
- } else {
4494
- // array of files
4495
- insertImages(layoutInfo, data);
4496
- }
4497
- }).fail(function () {
4498
- editor.restoreRange($editable);
4499
- });
5254
+ modules.imageDialog.show(layoutInfo);
4500
5255
  },
4501
5256
 
4502
5257
  /**
4503
5258
  * @param {Object} layoutInfo
4504
5259
  */
4505
5260
  showHelpDialog: function (layoutInfo) {
4506
- var $dialog = layoutInfo.dialog(),
4507
- $editable = layoutInfo.editable();
4508
-
4509
- editor.saveRange($editable, true);
4510
- dialog.showHelpDialog($editable, $dialog).then(function () {
4511
- editor.restoreRange($editable);
4512
- });
4513
- },
4514
-
4515
- fullscreen: function (layoutInfo) {
4516
- var $editor = layoutInfo.editor(),
4517
- $toolbar = layoutInfo.toolbar(),
4518
- $editable = layoutInfo.editable(),
4519
- $codable = layoutInfo.codable();
4520
-
4521
- var resize = function (size) {
4522
- $editable.css('height', size.h);
4523
- $codable.css('height', size.h);
4524
- if ($codable.data('cmeditor')) {
4525
- $codable.data('cmeditor').setsize(null, size.h);
4526
- }
4527
- };
4528
-
4529
- $editor.toggleClass('fullscreen');
4530
- var isFullscreen = $editor.hasClass('fullscreen');
4531
- if (isFullscreen) {
4532
- $editable.data('orgheight', $editable.css('height'));
4533
-
4534
- $window.on('resize', function () {
4535
- resize({
4536
- h: $window.height() - $toolbar.outerHeight()
4537
- });
4538
- }).trigger('resize');
4539
-
4540
- $scrollbar.css('overflow', 'hidden');
4541
- } else {
4542
- $window.off('resize');
4543
- resize({
4544
- h: $editable.data('orgheight')
4545
- });
4546
- $scrollbar.css('overflow', 'visible');
4547
- }
4548
-
4549
- toolbar.updateFullscreen($toolbar, isFullscreen);
5261
+ modules.helpDialog.show(layoutInfo);
4550
5262
  },
4551
5263
 
4552
- codeview: function (layoutInfo) {
4553
- var $editor = layoutInfo.editor(),
4554
- $toolbar = layoutInfo.toolbar(),
4555
- $editable = layoutInfo.editable(),
4556
- $codable = layoutInfo.codable(),
4557
- $popover = layoutInfo.popover(),
4558
- $handle = layoutInfo.handle();
4559
-
4560
- var options = $editor.data('options');
4561
-
4562
- var cmEditor, server;
4563
-
4564
- $editor.toggleClass('codeview');
4565
-
4566
- var isCodeview = $editor.hasClass('codeview');
4567
- if (isCodeview) {
4568
- $codable.val(dom.html($editable, options.prettifyHtml));
4569
- $codable.height($editable.height());
4570
- toolbar.deactivate($toolbar);
4571
- popover.hide($popover);
4572
- handle.hide($handle);
4573
- $codable.focus();
4574
-
4575
- // activate CodeMirror as codable
4576
- if (agent.hasCodeMirror) {
4577
- cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
4578
-
4579
- // CodeMirror TernServer
4580
- if (options.codemirror.tern) {
4581
- server = new CodeMirror.TernServer(options.codemirror.tern);
4582
- cmEditor.ternServer = server;
4583
- cmEditor.on('cursorActivity', function (cm) {
4584
- server.updateArgHints(cm);
4585
- });
4586
- }
4587
-
4588
- // CodeMirror hasn't Padding.
4589
- cmEditor.setSize(null, $editable.outerHeight());
4590
- $codable.data('cmEditor', cmEditor);
4591
- }
4592
- } else {
4593
- // deactivate CodeMirror as codable
4594
- if (agent.hasCodeMirror) {
4595
- cmEditor = $codable.data('cmEditor');
4596
- $codable.val(cmEditor.getValue());
4597
- cmEditor.toTextArea();
4598
- }
4599
-
4600
- $editable.html(dom.value($codable, options.prettifyHtml) || dom.emptyPara);
4601
- $editable.height(options.height ? $codable.height() : 'auto');
4602
-
4603
- toolbar.activate($toolbar);
4604
- $editable.focus();
4605
- }
4606
-
4607
- toolbar.updateCodeview(layoutInfo.toolbar(), isCodeview);
4608
- }
4609
- };
4610
-
4611
- var hMousedown = function (event) {
4612
- //preventDefault Selection for FF, IE8+
4613
- if (dom.isImg(event.target)) {
4614
- event.preventDefault();
4615
- }
4616
- };
4617
-
4618
- var hToolbarAndPopoverUpdate = function (event) {
4619
- // delay for range after mouseup
4620
- setTimeout(function () {
4621
- var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
4622
- var styleInfo = editor.currentStyle(event.target);
4623
- if (!styleInfo) { return; }
4624
-
4625
- var isAirMode = layoutInfo.editor().data('options').airMode;
4626
- if (!isAirMode) {
4627
- toolbar.update(layoutInfo.toolbar(), styleInfo);
4628
- }
4629
-
4630
- popover.update(layoutInfo.popover(), styleInfo, isAirMode);
4631
- handle.update(layoutInfo.handle(), styleInfo, isAirMode);
4632
- }, 0);
4633
- };
4634
-
4635
- var hScroll = function (event) {
4636
- var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
4637
- //hide popover and handle when scrolled
4638
- popover.hide(layoutInfo.popover());
4639
- handle.hide(layoutInfo.handle());
4640
- };
4641
-
4642
- /**
4643
- * paste clipboard image
4644
- *
4645
- * @param {Event} event
4646
- */
4647
- var hPasteClipboardImage = function (event) {
4648
- var clipboardData = event.originalEvent.clipboardData;
4649
- var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
4650
- var $editable = layoutInfo.editable();
4651
-
4652
- if (!clipboardData || !clipboardData.items || !clipboardData.items.length) {
4653
- var callbacks = $editable.data('callbacks');
4654
- // only can run if it has onImageUpload method
4655
- if (!callbacks.onImageUpload) {
4656
- return;
4657
- }
4658
-
4659
- // save cursor
4660
- editor.saveNode($editable);
4661
- editor.saveRange($editable);
4662
-
4663
- $editable.html('');
4664
-
4665
- setTimeout(function () {
4666
- var $img = $editable.find('img');
4667
-
4668
- if (!$img.length || $img[0].src.indexOf('data:') === -1) {
4669
- // pasted content
4670
- var html = $editable.html();
4671
-
4672
- editor.restoreNode($editable);
4673
- editor.restoreRange($editable);
4674
-
4675
- try {
4676
- // insert normal dom code
4677
- $(html).each(function () {
4678
- $editable.focus();
4679
- editor.insertNode($editable, this);
4680
- });
4681
- } catch (ex) {
4682
- // insert text
4683
- $editable.focus();
4684
- editor.insertText($editable, html);
4685
- }
4686
- return;
4687
- }
4688
-
4689
- var datauri = $img[0].src;
4690
- var data = atob(datauri.split(',')[1]);
4691
- var array = new Uint8Array(data.length);
4692
- for (var i = 0; i < data.length; i++) {
4693
- array[i] = data.charCodeAt(i);
4694
- }
4695
-
4696
- var blob = new Blob([array], { type : 'image/png'});
4697
- blob.name = 'clipboard.png';
4698
-
4699
- editor.restoreNode($editable);
4700
- editor.restoreRange($editable);
4701
- insertImages(layoutInfo, [blob]);
4702
-
4703
- editor.afterCommand($editable);
4704
- }, 0);
4705
-
4706
- return;
4707
- }
4708
-
4709
- var item = list.head(clipboardData.items);
4710
- var isClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
4711
-
4712
- if (isClipboardImage) {
4713
- insertImages(layoutInfo, [item.getAsFile()]);
4714
- }
4715
-
4716
- editor.afterCommand($editable);
4717
- };
4718
-
4719
- /**
4720
- * `mousedown` event handler on $handle
4721
- * - controlSizing: resize image
4722
- *
4723
- * @param {MouseEvent} event
4724
- */
4725
- var hHandleMousedown = function (event) {
4726
- if (dom.isControlSizing(event.target)) {
4727
- event.preventDefault();
4728
- event.stopPropagation();
5264
+ /**
5265
+ * @param {Object} layoutInfo
5266
+ */
5267
+ fullscreen: function (layoutInfo) {
5268
+ modules.fullscreen.toggle(layoutInfo);
5269
+ },
4729
5270
 
4730
- var layoutInfo = makeLayoutInfo(event.target),
4731
- $handle = layoutInfo.handle(), $popover = layoutInfo.popover(),
4732
- $editable = layoutInfo.editable(),
4733
- $editor = layoutInfo.editor();
5271
+ /**
5272
+ * @param {Object} layoutInfo
5273
+ */
5274
+ codeview: function (layoutInfo) {
5275
+ modules.codeview.toggle(layoutInfo);
5276
+ }
5277
+ };
4734
5278
 
4735
- var target = $handle.find('.note-control-selection').data('target'),
4736
- $target = $(target), posStart = $target.offset(),
4737
- scrollTop = $document.scrollTop();
5279
+ var hMousedown = function (event) {
5280
+ //preventDefault Selection for FF, IE8+
5281
+ if (dom.isImg(event.target)) {
5282
+ event.preventDefault();
5283
+ }
5284
+ };
4738
5285
 
4739
- var isAirMode = $editor.data('options').airMode;
5286
+ var hToolbarAndPopoverUpdate = function (event) {
5287
+ // delay for range after mouseup
5288
+ setTimeout(function () {
5289
+ var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
5290
+ var styleInfo = modules.editor.currentStyle(event.target);
5291
+ if (!styleInfo) { return; }
4740
5292
 
4741
- $document.on('mousemove', function (event) {
4742
- editor.resizeTo({
4743
- x: event.clientX - posStart.left,
4744
- y: event.clientY - (posStart.top - scrollTop)
4745
- }, $target, !event.shiftKey);
5293
+ var isAirMode = layoutInfo.editor().data('options').airMode;
5294
+ if (!isAirMode) {
5295
+ modules.toolbar.update(layoutInfo.toolbar(), styleInfo);
5296
+ }
4746
5297
 
4747
- handle.update($handle, {image: target}, isAirMode);
4748
- popover.update($popover, {image: target}, isAirMode);
4749
- }).one('mouseup', function () {
4750
- $document.off('mousemove');
4751
- editor.afterCommand($editable);
4752
- });
5298
+ modules.popover.update(layoutInfo.popover(), styleInfo, isAirMode);
5299
+ modules.handle.update(layoutInfo.handle(), styleInfo, isAirMode);
5300
+ }, 0);
5301
+ };
4753
5302
 
4754
- if (!$target.data('ratio')) { // original ratio.
4755
- $target.data('ratio', $target.height() / $target.width());
4756
- }
4757
- }
5303
+ var hScroll = function (event) {
5304
+ var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
5305
+ //hide popover and handle when scrolled
5306
+ modules.popover.hide(layoutInfo.popover());
5307
+ modules.handle.hide(layoutInfo.handle());
4758
5308
  };
4759
5309
 
4760
5310
  var hToolbarAndPopoverMousedown = function (event) {
@@ -4773,7 +5323,7 @@
4773
5323
  value = $btn.attr('data-value'),
4774
5324
  hide = $btn.attr('data-hide');
4775
5325
 
4776
- var layoutInfo = makeLayoutInfo(event.target);
5326
+ var layoutInfo = dom.makeLayoutInfo(event.target);
4777
5327
 
4778
5328
  // before command: detect control selection element($target)
4779
5329
  var $target;
@@ -4789,11 +5339,11 @@
4789
5339
  }
4790
5340
 
4791
5341
  if ($.isFunction($.summernote.pluginEvents[eventName])) {
4792
- $.summernote.pluginEvents[eventName](event, editor, layoutInfo, value);
4793
- } else if (editor[eventName]) { // on command
5342
+ $.summernote.pluginEvents[eventName](event, modules.editor, layoutInfo, value);
5343
+ } else if (modules.editor[eventName]) { // on command
4794
5344
  var $editable = layoutInfo.editable();
4795
- $editable.trigger('focus');
4796
- editor[eventName]($editable, value, $target);
5345
+ $editable.focus();
5346
+ modules.editor[eventName]($editable, value, $target);
4797
5347
  event.preventDefault();
4798
5348
  } else if (commands[eventName]) {
4799
5349
  commands[eventName].call(this, layoutInfo);
@@ -4803,7 +5353,7 @@
4803
5353
  // after command
4804
5354
  if ($.inArray(eventName, ['backColor', 'foreColor']) !== -1) {
4805
5355
  var options = layoutInfo.editor().data('options', options);
4806
- var module = options.airMode ? popover : toolbar;
5356
+ var module = options.airMode ? modules.popover : modules.toolbar;
4807
5357
  module.updateRecentColor(list.head($btn), eventName, value);
4808
5358
  }
4809
5359
 
@@ -4811,34 +5361,6 @@
4811
5361
  }
4812
5362
  };
4813
5363
 
4814
- var EDITABLE_PADDING = 24;
4815
- /**
4816
- * `mousedown` event handler on statusbar
4817
- *
4818
- * @param {MouseEvent} event
4819
- */
4820
- var hStatusbarMousedown = function (event) {
4821
- event.preventDefault();
4822
- event.stopPropagation();
4823
-
4824
- var $editable = makeLayoutInfo(event.target).editable();
4825
- var nEditableTop = $editable.offset().top - $document.scrollTop();
4826
-
4827
- var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
4828
- var options = layoutInfo.editor().data('options');
4829
-
4830
- $document.on('mousemove', function (event) {
4831
- var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING);
4832
-
4833
- nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight;
4834
- nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight;
4835
-
4836
- $editable.height(nHeight);
4837
- }).one('mouseup', function () {
4838
- $document.off('mousemove');
4839
- });
4840
- };
4841
-
4842
5364
  var PX_PER_EM = 18;
4843
5365
  var hDimensionPickerMove = function (event, options) {
4844
5366
  var $picker = $(event.target.parentNode); // target is mousecatcher
@@ -4880,95 +5402,7 @@
4880
5402
 
4881
5403
  $dimensionDisplay.html(dim.c + ' x ' + dim.r);
4882
5404
  };
4883
-
4884
- /**
4885
- * Drag and Drop Events
4886
- *
4887
- * @param {Object} layoutInfo - layout Informations
4888
- * @param {Object} options
4889
- */
4890
- var handleDragAndDropEvent = function (layoutInfo, options) {
4891
- if (options.disableDragAndDrop) {
4892
- // prevent default drop event
4893
- $document.on('drop', function (e) {
4894
- e.preventDefault();
4895
- });
4896
- } else {
4897
- attachDragAndDropEvent(layoutInfo, options);
4898
- }
4899
- };
4900
-
4901
- /**
4902
- * attach Drag and Drop Events
4903
- *
4904
- * @param {Object} layoutInfo - layout Informations
4905
- * @param {Object} options
4906
- */
4907
- var attachDragAndDropEvent = function (layoutInfo, options) {
4908
- var collection = $(),
4909
- $dropzone = layoutInfo.dropzone,
4910
- $dropzoneMessage = layoutInfo.dropzone.find('.note-dropzone-message');
4911
-
4912
- // show dropzone on dragenter when dragging a object to document.
4913
- $document.on('dragenter', function (e) {
4914
- var isCodeview = layoutInfo.editor.hasClass('codeview');
4915
- if (!isCodeview && !collection.length) {
4916
- layoutInfo.editor.addClass('dragover');
4917
- $dropzone.width(layoutInfo.editor.width());
4918
- $dropzone.height(layoutInfo.editor.height());
4919
- $dropzoneMessage.text(options.langInfo.image.dragImageHere);
4920
- }
4921
- collection = collection.add(e.target);
4922
- }).on('dragleave', function (e) {
4923
- collection = collection.not(e.target);
4924
- if (!collection.length) {
4925
- layoutInfo.editor.removeClass('dragover');
4926
- }
4927
- }).on('drop', function () {
4928
- collection = $();
4929
- layoutInfo.editor.removeClass('dragover');
4930
- }).on('mouseout', function (e) {
4931
- collection = collection.not(e.target);
4932
- if (!collection.length) {
4933
- layoutInfo.editor.removeClass('dragover');
4934
- }
4935
- });
4936
-
4937
- // change dropzone's message on hover.
4938
- $dropzone.on('dragenter', function () {
4939
- $dropzone.addClass('hover');
4940
- $dropzoneMessage.text(options.langInfo.image.dropImage);
4941
- }).on('dragleave', function () {
4942
- $dropzone.removeClass('hover');
4943
- $dropzoneMessage.text(options.langInfo.image.dragImageHere);
4944
- });
4945
-
4946
- // attach dropImage
4947
- $dropzone.on('drop', function (event) {
4948
- event.preventDefault();
4949
-
4950
- var dataTransfer = event.originalEvent.dataTransfer;
4951
- var html = dataTransfer.getData('text/html');
4952
- var text = dataTransfer.getData('text/plain');
4953
-
4954
- var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
4955
-
4956
- if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
4957
- layoutInfo.editable().focus();
4958
- insertImages(layoutInfo, dataTransfer.files);
4959
- } else if (html) {
4960
- $(html).each(function () {
4961
- layoutInfo.editable().focus();
4962
- editor.insertNode(layoutInfo.editable(), this);
4963
- });
4964
- } else if (text) {
4965
- layoutInfo.editable().focus();
4966
- editor.insertText(layoutInfo.editable(), text);
4967
- }
4968
- }).on('dragover', false); // prevent default dragover event
4969
- };
4970
-
4971
-
5405
+
4972
5406
  /**
4973
5407
  * bind KeyMap on keydown
4974
5408
  *
@@ -4976,39 +5410,39 @@
4976
5410
  * @param {Object} keyMap
4977
5411
  */
4978
5412
  this.bindKeyMap = function (layoutInfo, keyMap) {
4979
- var $editor = layoutInfo.editor;
4980
- var $editable = layoutInfo.editable;
4981
-
4982
- layoutInfo = makeLayoutInfo($editable);
5413
+ var $editor = layoutInfo.editor();
5414
+ var $editable = layoutInfo.editable();
4983
5415
 
4984
5416
  $editable.on('keydown', function (event) {
4985
- var aKey = [];
5417
+ var keys = [];
4986
5418
 
4987
5419
  // modifier
4988
- if (event.metaKey) { aKey.push('CMD'); }
4989
- if (event.ctrlKey && !event.altKey) { aKey.push('CTRL'); }
4990
- if (event.shiftKey) { aKey.push('SHIFT'); }
5420
+ if (event.metaKey) { keys.push('CMD'); }
5421
+ if (event.ctrlKey && !event.altKey) { keys.push('CTRL'); }
5422
+ if (event.shiftKey) { keys.push('SHIFT'); }
4991
5423
 
4992
5424
  // keycode
4993
5425
  var keyName = key.nameFromCode[event.keyCode];
4994
- if (keyName) { aKey.push(keyName); }
5426
+ if (keyName) {
5427
+ keys.push(keyName);
5428
+ }
4995
5429
 
4996
- var eventName = keyMap[aKey.join('+')];
5430
+ var eventName = keyMap[keys.join('+')];
4997
5431
  if (eventName) {
4998
5432
  if ($.summernote.pluginEvents[eventName]) {
4999
5433
  var plugin = $.summernote.pluginEvents[eventName];
5000
5434
  if ($.isFunction(plugin)) {
5001
- plugin(event, editor, layoutInfo);
5435
+ plugin(event, modules.editor, layoutInfo);
5002
5436
  }
5003
- } else if (editor[eventName]) {
5004
- editor[eventName]($editable, $editor.data('options'));
5437
+ } else if (modules.editor[eventName]) {
5438
+ modules.editor[eventName]($editable, $editor.data('options'));
5005
5439
  event.preventDefault();
5006
5440
  } else if (commands[eventName]) {
5007
5441
  commands[eventName].call(this, layoutInfo);
5008
5442
  event.preventDefault();
5009
5443
  }
5010
5444
  } else if (key.isEdit(event.keyCode)) {
5011
- editor.afterCommand($editable);
5445
+ modules.editor.afterCommand($editable);
5012
5446
  }
5013
5447
  });
5014
5448
  };
@@ -5018,48 +5452,38 @@
5018
5452
  *
5019
5453
  * @param {Object} layoutInfo - layout Informations
5020
5454
  * @param {Object} options - user options include custom event handlers
5021
- * @param {function(event)} [options.onenter] - enter key handler
5022
- * @param {function(event)} [options.onfocus]
5023
- * @param {function(event)} [options.onblur]
5024
- * @param {function(event)} [options.onkeyup]
5025
- * @param {function(event)} [options.onkeydown]
5026
- * @param {function(event)} [options.onpaste]
5027
- * @param {function(event)} [options.onToolBarclick]
5028
- * @param {function(event)} [options.onChange]
5029
5455
  */
5030
5456
  this.attach = function (layoutInfo, options) {
5031
5457
  // handlers for editable
5032
5458
  if (options.shortcuts) {
5033
5459
  this.bindKeyMap(layoutInfo, options.keyMap[agent.isMac ? 'mac' : 'pc']);
5034
5460
  }
5035
- layoutInfo.editable.on('mousedown', hMousedown);
5036
- layoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
5037
- layoutInfo.editable.on('scroll', hScroll);
5038
- layoutInfo.editable.on('paste', hPasteClipboardImage);
5461
+ layoutInfo.editable().on('mousedown', hMousedown);
5462
+ layoutInfo.editable().on('keyup mouseup', hToolbarAndPopoverUpdate);
5463
+ layoutInfo.editable().on('scroll', hScroll);
5464
+ modules.clipboard.attach(layoutInfo, options);
5039
5465
 
5040
5466
  // handler for handle and popover
5041
- layoutInfo.handle.on('mousedown', hHandleMousedown);
5042
- layoutInfo.popover.on('click', hToolbarAndPopoverClick);
5043
- layoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
5467
+ modules.handle.attach(layoutInfo, options);
5468
+ layoutInfo.popover().on('click', hToolbarAndPopoverClick);
5469
+ layoutInfo.popover().on('mousedown', hToolbarAndPopoverMousedown);
5470
+
5471
+ // handler for drag and drop
5472
+ modules.dragAndDrop.attach(layoutInfo, options);
5044
5473
 
5045
5474
  // handlers for frame mode (toolbar, statusbar)
5046
5475
  if (!options.airMode) {
5047
- // handler for drag and drop
5048
- handleDragAndDropEvent(layoutInfo, options);
5049
-
5050
5476
  // handler for toolbar
5051
- layoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
5052
- layoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
5477
+ layoutInfo.toolbar().on('click', hToolbarAndPopoverClick);
5478
+ layoutInfo.toolbar().on('mousedown', hToolbarAndPopoverMousedown);
5053
5479
 
5054
5480
  // handler for statusbar
5055
- if (!options.disableResizeEditor) {
5056
- layoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
5057
- }
5481
+ modules.statusbar.attach(layoutInfo, options);
5058
5482
  }
5059
5483
 
5060
5484
  // handler for table dimension
5061
- var $catcherContainer = options.airMode ? layoutInfo.popover :
5062
- layoutInfo.toolbar;
5485
+ var $catcherContainer = options.airMode ? layoutInfo.popover() :
5486
+ layoutInfo.toolbar();
5063
5487
  var $catcher = $catcherContainer.find('.note-dimension-picker-mousecatcher');
5064
5488
  $catcher.css({
5065
5489
  width: options.insertTableMaxSize.col + 'em',
@@ -5069,74 +5493,124 @@
5069
5493
  });
5070
5494
 
5071
5495
  // save options on editor
5072
- layoutInfo.editor.data('options', options);
5496
+ layoutInfo.editor().data('options', options);
5073
5497
 
5074
5498
  // ret styleWithCSS for backColor / foreColor clearing with 'inherit'.
5075
5499
  if (!agent.isMSIE) {
5076
- // protect FF Error: NS_ERROR_FAILURE: Failure
5500
+ // [workaround] for Firefox
5501
+ // - protect FF Error: NS_ERROR_FAILURE: Failure
5077
5502
  setTimeout(function () {
5078
5503
  document.execCommand('styleWithCSS', 0, options.styleWithSpan);
5079
5504
  }, 0);
5080
5505
  }
5081
5506
 
5082
5507
  // History
5083
- var history = new History(layoutInfo.editable);
5084
- layoutInfo.editable.data('NoteHistory', history);
5085
-
5086
- // basic event callbacks (lowercase)
5087
- // enter, focus, blur, keyup, keydown
5088
- if (options.onenter) {
5089
- layoutInfo.editable.keypress(function (event) {
5090
- if (event.keyCode === key.ENTER) { options.onenter(event); }
5091
- });
5092
- }
5093
-
5094
- if (options.onfocus) { layoutInfo.editable.focus(options.onfocus); }
5095
- if (options.onblur) { layoutInfo.editable.blur(options.onblur); }
5096
- if (options.onkeyup) { layoutInfo.editable.keyup(options.onkeyup); }
5097
- if (options.onkeydown) { layoutInfo.editable.keydown(options.onkeydown); }
5098
- if (options.onpaste) { layoutInfo.editable.on('paste', options.onpaste); }
5099
-
5100
- // callbacks for advanced features (camel)
5101
- if (options.onToolbarClick) { layoutInfo.toolbar.click(options.onToolbarClick); }
5102
- if (options.onChange) {
5103
- var hChange = function () {
5104
- editor.triggerOnChange(layoutInfo.editable);
5105
- };
5106
-
5107
- if (agent.isMSIE) {
5108
- var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted';
5109
- layoutInfo.editable.on(sDomEvents, hChange);
5110
- } else {
5111
- layoutInfo.editable.on('input', hChange);
5112
- }
5113
- }
5508
+ var history = new History(layoutInfo.editable());
5509
+ layoutInfo.editable().data('NoteHistory', history);
5114
5510
 
5115
5511
  // All editor status will be saved on editable with jquery's data
5116
5512
  // for support multiple editor with singleton object.
5117
- layoutInfo.editable.data('callbacks', {
5118
- onBeforeChange: options.onBeforeChange,
5513
+ layoutInfo.editable().data('callbacks', {
5514
+ onInit: options.onInit,
5515
+ onFocus: options.onFocus,
5516
+ onBlur: options.onBlur,
5517
+ onKeydown: options.onKeydown,
5518
+ onKeyup: options.onKeyup,
5519
+ onMousedown: options.onMousedown,
5520
+ onEnter: options.onEnter,
5521
+ onPaste: options.onPaste,
5522
+ onBeforeCommand: options.onBeforeCommand,
5119
5523
  onChange: options.onChange,
5120
- onAutoSave: options.onAutoSave,
5121
5524
  onImageUpload: options.onImageUpload,
5122
5525
  onImageUploadError: options.onImageUploadError,
5123
- onFileUpload: options.onFileUpload,
5124
- onFileUploadError: options.onFileUpload,
5125
5526
  onMediaDelete : options.onMediaDelete
5126
5527
  });
5528
+
5529
+ // Textarea: auto filling the code before form submit.
5530
+ if (dom.isTextarea(list.head(layoutInfo.holder()))) {
5531
+ layoutInfo.holder().closest('form').submit(function () {
5532
+ var contents = layoutInfo.holder().code();
5533
+ layoutInfo.holder().val(contents);
5534
+
5535
+ // callback on submit
5536
+ if (options.onsubmit) {
5537
+ options.onsubmit(contents);
5538
+ }
5539
+ });
5540
+ }
5127
5541
  };
5128
5542
 
5543
+ /**
5544
+ * attach jquery custom event
5545
+ *
5546
+ * @param {Object} layoutInfo - layout Informations
5547
+ */
5548
+ this.attachCustomEvent = function (layoutInfo, options) {
5549
+ var $holder = layoutInfo.holder();
5550
+ var $editable = layoutInfo.editable();
5551
+ var callbacks = $editable.data('callbacks');
5552
+
5553
+ $editable.focus(bindCustomEvent($holder, callbacks, 'focus'));
5554
+ $editable.blur(bindCustomEvent($holder, callbacks, 'blur'));
5555
+
5556
+ $editable.keydown(function (event) {
5557
+ if (event.keyCode === key.code.ENTER) {
5558
+ bindCustomEvent($holder, callbacks, 'enter').call(this, event);
5559
+ }
5560
+ bindCustomEvent($holder, callbacks, 'keydown').call(this, event);
5561
+ });
5562
+ $editable.keyup(bindCustomEvent($holder, callbacks, 'keyup'));
5563
+
5564
+ $editable.on('mousedown', bindCustomEvent($holder, callbacks, 'mousedown'));
5565
+ $editable.on('mouseup', bindCustomEvent($holder, callbacks, 'mouseup'));
5566
+ $editable.on('scroll', bindCustomEvent($holder, callbacks, 'scroll'));
5567
+
5568
+ $editable.on('paste', bindCustomEvent($holder, callbacks, 'paste'));
5569
+
5570
+ // [workaround] for old IE - IE8 don't have input events
5571
+ if (agent.isMSIE) {
5572
+ var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted';
5573
+ $editable.on(sDomEvents, bindCustomEvent($holder, callbacks, 'change'));
5574
+ } else {
5575
+ $editable.on('input', bindCustomEvent($holder, callbacks, 'change'));
5576
+ }
5577
+
5578
+ // callbacks for advanced features (camel)
5579
+ if (!options.airMode) {
5580
+ layoutInfo.toolbar().click(bindCustomEvent($holder, callbacks, 'toolbar.click'));
5581
+ layoutInfo.popover().click(bindCustomEvent($holder, callbacks, 'popover.click'));
5582
+ }
5583
+
5584
+ // Textarea: auto filling the code before form submit.
5585
+ if (dom.isTextarea(list.head($holder))) {
5586
+ $holder.closest('form').submit(function (e) {
5587
+ bindCustomEvent($holder, callbacks, 'submit').call(this, e, $holder.code());
5588
+ });
5589
+ }
5590
+
5591
+ // fire init event
5592
+ bindCustomEvent($holder, callbacks, 'init')(layoutInfo);
5593
+
5594
+ // fire plugin init event
5595
+ for (var i = 0, len = $.summernote.plugins.length; i < len; i++) {
5596
+ if ($.isFunction($.summernote.plugins[i].init)) {
5597
+ $.summernote.plugins[i].init(layoutInfo);
5598
+ }
5599
+ }
5600
+ };
5601
+
5129
5602
  this.detach = function (layoutInfo, options) {
5130
- layoutInfo.editable.off();
5603
+ layoutInfo.holder().off();
5604
+ layoutInfo.editable().off();
5131
5605
 
5132
- layoutInfo.popover.off();
5133
- layoutInfo.handle.off();
5134
- layoutInfo.dialog.off();
5606
+ layoutInfo.popover().off();
5607
+ layoutInfo.handle().off();
5608
+ layoutInfo.dialog().off();
5135
5609
 
5136
5610
  if (!options.airMode) {
5137
- layoutInfo.dropzone.off();
5138
- layoutInfo.toolbar.off();
5139
- layoutInfo.statusbar.off();
5611
+ layoutInfo.dropzone().off();
5612
+ layoutInfo.toolbar().off();
5613
+ layoutInfo.statusbar().off();
5140
5614
  }
5141
5615
  };
5142
5616
  };
@@ -5209,12 +5683,14 @@
5209
5683
  * @param {String} content
5210
5684
  */
5211
5685
  var tplPopover = function (className, content) {
5212
- return '<div class="' + className + ' popover bottom in" style="display: none;">' +
5686
+ var $popover = $('<div class="' + className + ' popover bottom in" style="display: none;">' +
5213
5687
  '<div class="arrow"></div>' +
5214
5688
  '<div class="popover-content">' +
5215
- content +
5216
5689
  '</div>' +
5217
- '</div>';
5690
+ '</div>');
5691
+
5692
+ $popover.find('.popover-content').append(content);
5693
+ return $popover;
5218
5694
  };
5219
5695
 
5220
5696
  /**
@@ -5235,33 +5711,31 @@
5235
5711
  '<h4 class="modal-title">' + title + '</h4>' +
5236
5712
  '</div>' : ''
5237
5713
  ) +
5238
- '<form class="note-modal-form">' +
5239
- '<div class="modal-body">' + body + '</div>' +
5240
- (footer ?
5241
- '<div class="modal-footer">' + footer + '</div>' : ''
5242
- ) +
5243
- '</form>' +
5714
+ '<div class="modal-body">' + body + '</div>' +
5715
+ (footer ?
5716
+ '<div class="modal-footer">' + footer + '</div>' : ''
5717
+ ) +
5244
5718
  '</div>' +
5245
5719
  '</div>' +
5246
5720
  '</div>';
5247
5721
  };
5248
5722
 
5249
5723
  var tplButtonInfo = {
5250
- picture: function (lang) {
5251
- return tplIconButton('fa fa-picture-o', {
5724
+ picture: function (lang, options) {
5725
+ return tplIconButton(options.iconPrefix + 'picture-o', {
5252
5726
  event: 'showImageDialog',
5253
5727
  title: lang.image.image,
5254
5728
  hide: true
5255
5729
  });
5256
5730
  },
5257
- link: function (lang) {
5258
- return tplIconButton('fa fa-link', {
5731
+ link: function (lang, options) {
5732
+ return tplIconButton(options.iconPrefix + 'link', {
5259
5733
  event: 'showLinkDialog',
5260
5734
  title: lang.link.link,
5261
5735
  hide: true
5262
5736
  });
5263
5737
  },
5264
- table: function (lang) {
5738
+ table: function (lang, options) {
5265
5739
  var dropdown = '<ul class="note-table dropdown-menu">' +
5266
5740
  '<div class="note-dimension-picker">' +
5267
5741
  '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
@@ -5270,7 +5744,7 @@
5270
5744
  '</div>' +
5271
5745
  '<div class="note-dimension-display"> 1 x 1 </div>' +
5272
5746
  '</ul>';
5273
- return tplIconButton('fa fa-table', {
5747
+ return tplIconButton(options.iconPrefix + 'table', {
5274
5748
  title: lang.table.table,
5275
5749
  dropdown: dropdown
5276
5750
  });
@@ -5286,30 +5760,49 @@
5286
5760
  '</a></li>';
5287
5761
  }, '');
5288
5762
 
5289
- return tplIconButton('fa fa-magic', {
5763
+ return tplIconButton(options.iconPrefix + 'magic', {
5290
5764
  title: lang.style.style,
5291
5765
  dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
5292
5766
  });
5293
5767
  },
5294
5768
  fontname: function (lang, options) {
5769
+ var realFontList = [];
5295
5770
  var items = options.fontNames.reduce(function (memo, v) {
5296
5771
  if (!agent.isFontInstalled(v) && options.fontNamesIgnoreCheck.indexOf(v) === -1) {
5297
5772
  return memo;
5298
5773
  }
5774
+ realFontList.push(v);
5299
5775
  return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '" style="font-family:\'' + v + '\'">' +
5300
- '<i class="fa fa-check"></i> ' + v +
5776
+ '<i class="' + options.iconPrefix + 'check"></i> ' + v +
5301
5777
  '</a></li>';
5302
5778
  }, '');
5779
+
5780
+ var hasDefaultFont = agent.isFontInstalled(options.defaultFontName);
5781
+ var defaultFontName = (hasDefaultFont) ? options.defaultFontName : realFontList[0];
5782
+
5303
5783
  var label = '<span class="note-current-fontname">' +
5304
- options.defaultFontName +
5784
+ defaultFontName +
5305
5785
  '</span>';
5306
5786
  return tplButton(label, {
5307
5787
  title: lang.font.name,
5308
5788
  dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
5309
5789
  });
5310
5790
  },
5311
- color: function (lang) {
5312
- var colorButtonLabel = '<i class="fa fa-font" style="color:black;background-color:yellow;"></i>';
5791
+ fontsize: function (lang, options) {
5792
+ var items = options.fontSizes.reduce(function (memo, v) {
5793
+ return memo + '<li><a data-event="fontSize" href="#" data-value="' + v + '">' +
5794
+ '<i class="fa fa-check"></i> ' + v +
5795
+ '</a></li>';
5796
+ }, '');
5797
+
5798
+ var label = '<span class="note-current-fontsize">11</span>';
5799
+ return tplButton(label, {
5800
+ title: lang.font.size,
5801
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
5802
+ });
5803
+ },
5804
+ color: function (lang, options) {
5805
+ var colorButtonLabel = '<i class="' + options.iconPrefix + 'font" style="color:black;background-color:yellow;"></i>';
5313
5806
  var colorButton = tplButton(colorButtonLabel, {
5314
5807
  className: 'note-recent-color',
5315
5808
  title: lang.color.recent,
@@ -5344,65 +5837,83 @@
5344
5837
 
5345
5838
  return colorButton + moreButton;
5346
5839
  },
5347
- bold: function (lang) {
5348
- return tplIconButton('fa fa-bold', {
5840
+ bold: function (lang, options) {
5841
+ return tplIconButton(options.iconPrefix + 'bold', {
5349
5842
  event: 'bold',
5350
5843
  title: lang.font.bold
5351
5844
  });
5352
5845
  },
5353
- italic: function (lang) {
5354
- return tplIconButton('fa fa-italic', {
5846
+ italic: function (lang, options) {
5847
+ return tplIconButton(options.iconPrefix + 'italic', {
5355
5848
  event: 'italic',
5356
5849
  title: lang.font.italic
5357
5850
  });
5358
5851
  },
5359
- underline: function (lang) {
5360
- return tplIconButton('fa fa-underline', {
5852
+ underline: function (lang, options) {
5853
+ return tplIconButton(options.iconPrefix + 'underline', {
5361
5854
  event: 'underline',
5362
5855
  title: lang.font.underline
5363
5856
  });
5364
5857
  },
5365
- clear: function (lang) {
5366
- return tplIconButton('fa fa-eraser', {
5858
+ strikethrough: function (lang) {
5859
+ return tplIconButton('fa fa-strikethrough', {
5860
+ event: 'strikethrough',
5861
+ title: lang.font.strikethrough
5862
+ });
5863
+ },
5864
+ superscript: function (lang) {
5865
+ return tplIconButton('fa fa-superscript', {
5866
+ event: 'superscript',
5867
+ title: lang.font.superscript
5868
+ });
5869
+ },
5870
+ subscript: function (lang) {
5871
+ return tplIconButton('fa fa-subscript', {
5872
+ event: 'subscript',
5873
+ title: lang.font.subscript
5874
+ });
5875
+ },
5876
+ clear: function (lang, options) {
5877
+ return tplIconButton(options.iconPrefix + 'eraser', {
5367
5878
  event: 'removeFormat',
5368
5879
  title: lang.font.clear
5369
5880
  });
5370
5881
  },
5371
- ul: function (lang) {
5372
- return tplIconButton('fa fa-list-ul', {
5882
+ ul: function (lang, options) {
5883
+ return tplIconButton(options.iconPrefix + 'list-ul', {
5373
5884
  event: 'insertUnorderedList',
5374
5885
  title: lang.lists.unordered
5375
5886
  });
5376
5887
  },
5377
- ol: function (lang) {
5378
- return tplIconButton('fa fa-list-ol', {
5888
+ ol: function (lang, options) {
5889
+ return tplIconButton(options.iconPrefix + 'list-ol', {
5379
5890
  event: 'insertOrderedList',
5380
5891
  title: lang.lists.ordered
5381
5892
  });
5382
5893
  },
5383
- paragraph: function (lang) {
5384
- var leftButton = tplIconButton('fa fa-align-left', {
5894
+ paragraph: function (lang, options) {
5895
+ var leftButton = tplIconButton(options.iconPrefix + 'align-left', {
5385
5896
  title: lang.paragraph.left,
5386
5897
  event: 'justifyLeft'
5387
5898
  });
5388
- var centerButton = tplIconButton('fa fa-align-center', {
5899
+ var centerButton = tplIconButton(options.iconPrefix + 'align-center', {
5389
5900
  title: lang.paragraph.center,
5390
5901
  event: 'justifyCenter'
5391
5902
  });
5392
- var rightButton = tplIconButton('fa fa-align-right', {
5903
+ var rightButton = tplIconButton(options.iconPrefix + 'align-right', {
5393
5904
  title: lang.paragraph.right,
5394
5905
  event: 'justifyRight'
5395
5906
  });
5396
- var justifyButton = tplIconButton('fa fa-align-justify', {
5907
+ var justifyButton = tplIconButton(options.iconPrefix + 'align-justify', {
5397
5908
  title: lang.paragraph.justify,
5398
5909
  event: 'justifyFull'
5399
5910
  });
5400
5911
 
5401
- var outdentButton = tplIconButton('fa fa-outdent', {
5912
+ var outdentButton = tplIconButton(options.iconPrefix + 'outdent', {
5402
5913
  title: lang.paragraph.outdent,
5403
5914
  event: 'outdent'
5404
5915
  });
5405
- var indentButton = tplIconButton('fa fa-indent', {
5916
+ var indentButton = tplIconButton(options.iconPrefix + 'indent', {
5406
5917
  title: lang.paragraph.indent,
5407
5918
  event: 'indent'
5408
5919
  });
@@ -5416,7 +5927,7 @@
5416
5927
  '</div>' +
5417
5928
  '</div>';
5418
5929
 
5419
- return tplIconButton('fa fa-align-left', {
5930
+ return tplIconButton(options.iconPrefix + 'align-left', {
5420
5931
  title: lang.paragraph.paragraph,
5421
5932
  dropdown: dropdown
5422
5933
  });
@@ -5424,49 +5935,49 @@
5424
5935
  height: function (lang, options) {
5425
5936
  var items = options.lineHeights.reduce(function (memo, v) {
5426
5937
  return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' +
5427
- '<i class="fa fa-check"></i> ' + v +
5938
+ '<i class="' + options.iconPrefix + 'check"></i> ' + v +
5428
5939
  '</a></li>';
5429
5940
  }, '');
5430
5941
 
5431
- return tplIconButton('fa fa-text-height', {
5942
+ return tplIconButton(options.iconPrefix + 'text-height', {
5432
5943
  title: lang.font.height,
5433
5944
  dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
5434
5945
  });
5435
5946
 
5436
5947
  },
5437
- help: function (lang) {
5438
- return tplIconButton('fa fa-question', {
5948
+ help: function (lang, options) {
5949
+ return tplIconButton(options.iconPrefix + 'question', {
5439
5950
  event: 'showHelpDialog',
5440
5951
  title: lang.options.help,
5441
5952
  hide: true
5442
5953
  });
5443
5954
  },
5444
- fullscreen: function (lang) {
5445
- return tplIconButton('fa fa-arrows-alt', {
5955
+ fullscreen: function (lang, options) {
5956
+ return tplIconButton(options.iconPrefix + 'arrows-alt', {
5446
5957
  event: 'fullscreen',
5447
5958
  title: lang.options.fullscreen
5448
5959
  });
5449
5960
  },
5450
- codeview: function (lang) {
5451
- return tplIconButton('fa fa-code', {
5961
+ codeview: function (lang, options) {
5962
+ return tplIconButton(options.iconPrefix + 'code', {
5452
5963
  event: 'codeview',
5453
5964
  title: lang.options.codeview
5454
5965
  });
5455
5966
  },
5456
- undo: function (lang) {
5457
- return tplIconButton('fa fa-undo', {
5967
+ undo: function (lang, options) {
5968
+ return tplIconButton(options.iconPrefix + 'undo', {
5458
5969
  event: 'undo',
5459
5970
  title: lang.history.undo
5460
5971
  });
5461
5972
  },
5462
- redo: function (lang) {
5463
- return tplIconButton('fa fa-repeat', {
5973
+ redo: function (lang, options) {
5974
+ return tplIconButton(options.iconPrefix + 'repeat', {
5464
5975
  event: 'redo',
5465
5976
  title: lang.history.redo
5466
5977
  });
5467
5978
  },
5468
- hr: function (lang) {
5469
- return tplIconButton('fa fa-minus', {
5979
+ hr: function (lang, options) {
5980
+ return tplIconButton(options.iconPrefix + 'minus', {
5470
5981
  event: 'insertHorizontalRule',
5471
5982
  title: lang.hr.insert
5472
5983
  });
@@ -5475,12 +5986,12 @@
5475
5986
 
5476
5987
  var tplPopovers = function (lang, options) {
5477
5988
  var tplLinkPopover = function () {
5478
- var linkButton = tplIconButton('fa fa-edit', {
5989
+ var linkButton = tplIconButton(options.iconPrefix + 'edit', {
5479
5990
  title: lang.link.edit,
5480
5991
  event: 'showLinkDialog',
5481
5992
  hide: true
5482
5993
  });
5483
- var unlinkButton = tplIconButton('fa fa-unlink', {
5994
+ var unlinkButton = tplIconButton(options.iconPrefix + 'unlink', {
5484
5995
  title: lang.link.unlink,
5485
5996
  event: 'unlink'
5486
5997
  });
@@ -5508,44 +6019,44 @@
5508
6019
  value: '0.25'
5509
6020
  });
5510
6021
 
5511
- var leftButton = tplIconButton('fa fa-align-left', {
6022
+ var leftButton = tplIconButton(options.iconPrefix + 'align-left', {
5512
6023
  title: lang.image.floatLeft,
5513
6024
  event: 'floatMe',
5514
6025
  value: 'left'
5515
6026
  });
5516
- var rightButton = tplIconButton('fa fa-align-right', {
6027
+ var rightButton = tplIconButton(options.iconPrefix + 'align-right', {
5517
6028
  title: lang.image.floatRight,
5518
6029
  event: 'floatMe',
5519
6030
  value: 'right'
5520
6031
  });
5521
- var justifyButton = tplIconButton('fa fa-align-justify', {
6032
+ var justifyButton = tplIconButton(options.iconPrefix + 'align-justify', {
5522
6033
  title: lang.image.floatNone,
5523
6034
  event: 'floatMe',
5524
6035
  value: 'none'
5525
6036
  });
5526
6037
 
5527
- var roundedButton = tplIconButton('fa fa-square', {
6038
+ var roundedButton = tplIconButton(options.iconPrefix + 'square', {
5528
6039
  title: lang.image.shapeRounded,
5529
6040
  event: 'imageShape',
5530
6041
  value: 'img-rounded'
5531
6042
  });
5532
- var circleButton = tplIconButton('fa fa-circle-o', {
6043
+ var circleButton = tplIconButton(options.iconPrefix + 'circle-o', {
5533
6044
  title: lang.image.shapeCircle,
5534
6045
  event: 'imageShape',
5535
6046
  value: 'img-circle'
5536
6047
  });
5537
- var thumbnailButton = tplIconButton('fa fa-picture-o', {
6048
+ var thumbnailButton = tplIconButton(options.iconPrefix + 'picture-o', {
5538
6049
  title: lang.image.shapeThumbnail,
5539
6050
  event: 'imageShape',
5540
6051
  value: 'img-thumbnail'
5541
6052
  });
5542
- var noneButton = tplIconButton('fa fa-times', {
6053
+ var noneButton = tplIconButton(options.iconPrefix + 'times', {
5543
6054
  title: lang.image.shapeNone,
5544
6055
  event: 'imageShape',
5545
6056
  value: ''
5546
6057
  });
5547
6058
 
5548
- var removeButton = tplIconButton('fa fa-trash-o', {
6059
+ var removeButton = tplIconButton(options.iconPrefix + 'trash-o', {
5549
6060
  title: lang.image.remove,
5550
6061
  event: 'removeMedia',
5551
6062
  value: 'none'
@@ -5559,24 +6070,34 @@
5559
6070
  };
5560
6071
 
5561
6072
  var tplAirPopover = function () {
5562
- var content = '';
6073
+ var $content = $('<div />');
5563
6074
  for (var idx = 0, len = options.airPopover.length; idx < len; idx ++) {
5564
6075
  var group = options.airPopover[idx];
5565
- content += '<div class="note-' + group[0] + ' btn-group">';
6076
+
6077
+ var $group = $('<div class="note-' + group[0] + ' btn-group">');
5566
6078
  for (var i = 0, lenGroup = group[1].length; i < lenGroup; i++) {
5567
- content += tplButtonInfo[group[1][i]](lang, options);
6079
+ var $button = $(tplButtonInfo[group[1][i]](lang, options));
6080
+
6081
+ $button.attr('data-name', group[1][i]);
6082
+
6083
+ $group.append($button);
5568
6084
  }
5569
- content += '</div>';
6085
+ $content.append($group);
5570
6086
  }
5571
6087
 
5572
- return tplPopover('note-air-popover', content);
6088
+ return tplPopover('note-air-popover', $content.children());
5573
6089
  };
5574
6090
 
5575
- return '<div class="note-popover">' +
5576
- tplLinkPopover() +
5577
- tplImagePopover() +
5578
- (options.airMode ? tplAirPopover() : '') +
5579
- '</div>';
6091
+ var $notePopover = $('<div class="note-popover" />');
6092
+
6093
+ $notePopover.append(tplLinkPopover());
6094
+ $notePopover.append(tplImagePopover());
6095
+
6096
+ if (options.airMode) {
6097
+ $notePopover.append(tplAirPopover());
6098
+ }
6099
+
6100
+ return $notePopover;
5580
6101
  };
5581
6102
 
5582
6103
  var tplHandles = function () {
@@ -5747,7 +6268,7 @@
5747
6268
  '<div class="title">' + lang.shortcut.shortcuts + '</div>' +
5748
6269
  (agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
5749
6270
  '<p class="text-center">' +
5750
- '<a href="//summernote.org/" target="_blank">Summernote 0.6.2</a> · ' +
6271
+ '<a href="//summernote.org/" target="_blank">Summernote 0.6.5</a> · ' +
5751
6272
  '<a href="//github.com/summernote/summernote" target="_blank">Project</a> · ' +
5752
6273
  '<a href="//github.com/summernote/summernote/issues" target="_blank">Issues</a>' +
5753
6274
  '</p>';
@@ -5921,24 +6442,25 @@
5921
6442
  $('<textarea class="note-codable"></textarea>').prependTo($editor);
5922
6443
 
5923
6444
  //04. create Toolbar
5924
- var toolbarHTML = '';
6445
+ var $toolbar = $('<div class="note-toolbar btn-toolbar" />');
5925
6446
  for (var idx = 0, len = options.toolbar.length; idx < len; idx ++) {
5926
6447
  var groupName = options.toolbar[idx][0];
5927
6448
  var groupButtons = options.toolbar[idx][1];
5928
6449
 
5929
- toolbarHTML += '<div class="note-' + groupName + ' btn-group">';
6450
+ var $group = $('<div class="note-' + groupName + ' btn-group" />');
5930
6451
  for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) {
5931
6452
  var buttonInfo = tplButtonInfo[groupButtons[i]];
5932
6453
  // continue creating toolbar even if a button doesn't exist
5933
6454
  if (!$.isFunction(buttonInfo)) { continue; }
5934
- toolbarHTML += buttonInfo(langInfo, options);
6455
+
6456
+ var $button = $(buttonInfo(langInfo, options));
6457
+ $button.attr('data-name', groupButtons[i]); // set button's alias, becuase to get button element from $toolbar
6458
+ $group.append($button);
5935
6459
  }
5936
- toolbarHTML += '</div>';
6460
+ $toolbar.append($group);
5937
6461
  }
5938
-
5939
- toolbarHTML = '<div class="note-toolbar btn-toolbar">' + toolbarHTML + '</div>';
5940
-
5941
- var $toolbar = $(toolbarHTML).prependTo($editor);
6462
+
6463
+ $toolbar.prependTo($editor);
5942
6464
  var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
5943
6465
  createPalette($toolbar, options);
5944
6466
  createTooltip($toolbar, keyMap, 'bottom');
@@ -5965,6 +6487,10 @@
5965
6487
  $holder.hide();
5966
6488
  };
5967
6489
 
6490
+ this.hasNoteEditor = function ($holder) {
6491
+ return this.noteEditorFromHolder($holder).length > 0;
6492
+ };
6493
+
5968
6494
  this.noteEditorFromHolder = function ($holder) {
5969
6495
  if ($holder.hasClass('note-air-editor')) {
5970
6496
  return $holder;
@@ -5982,10 +6508,6 @@
5982
6508
  * @param {Object} options
5983
6509
  */
5984
6510
  this.createLayout = function ($holder, options) {
5985
- if (this.noteEditorFromHolder($holder).length) {
5986
- return;
5987
- }
5988
-
5989
6511
  if (options.airMode) {
5990
6512
  this.createLayoutByAirMode($holder, options);
5991
6513
  } else {
@@ -5997,20 +6519,18 @@
5997
6519
  * returns layoutInfo from holder
5998
6520
  *
5999
6521
  * @param {jQuery} $holder - placeholder
6000
- * @returns {Object}
6522
+ * @return {Object}
6001
6523
  */
6002
6524
  this.layoutInfoFromHolder = function ($holder) {
6003
6525
  var $editor = this.noteEditorFromHolder($holder);
6004
- if (!$editor.length) { return; }
6005
-
6006
- var layoutInfo = dom.buildLayoutInfo($editor);
6007
- // cache all properties.
6008
- for (var key in layoutInfo) {
6009
- if (layoutInfo.hasOwnProperty(key)) {
6010
- layoutInfo[key] = layoutInfo[key].call();
6011
- }
6526
+ if (!$editor.length) {
6527
+ return;
6012
6528
  }
6013
- return layoutInfo;
6529
+
6530
+ // connect $holder to $editor
6531
+ $editor.data('holder', $holder);
6532
+
6533
+ return dom.buildLayoutInfo($editor);
6014
6534
  };
6015
6535
 
6016
6536
  /**
@@ -6026,13 +6546,13 @@
6026
6546
  $holder.removeClass('note-air-editor note-editable')
6027
6547
  .removeAttr('id contentEditable');
6028
6548
 
6029
- layoutInfo.popover.remove();
6030
- layoutInfo.handle.remove();
6031
- layoutInfo.dialog.remove();
6549
+ layoutInfo.popover().remove();
6550
+ layoutInfo.handle().remove();
6551
+ layoutInfo.dialog().remove();
6032
6552
  } else {
6033
- $holder.html(layoutInfo.editable.html());
6553
+ $holder.html(layoutInfo.editable().html());
6034
6554
 
6035
- layoutInfo.editor.remove();
6555
+ layoutInfo.editor().remove();
6036
6556
  $holder.show();
6037
6557
  }
6038
6558
  };
@@ -6079,14 +6599,17 @@
6079
6599
  *
6080
6600
  * summernote attribute
6081
6601
  *
6082
- * @mixin settings
6602
+ * @mixin defaults
6083
6603
  * @singleton
6084
6604
  *
6085
6605
  */
6086
6606
  $.summernote = $.summernote || {};
6087
6607
 
6088
- // extends default `settings`
6089
- $.extend($.summernote, settings);
6608
+ // extends default settings
6609
+ // - $.summernote.version
6610
+ // - $.summernote.options
6611
+ // - $.summernote.lang
6612
+ $.extend($.summernote, defaults);
6090
6613
 
6091
6614
  var renderer = new Renderer();
6092
6615
  var eventHandler = new EventHandler();
@@ -6126,7 +6649,9 @@
6126
6649
  * * layoutInfo is a summernote layout information.
6127
6650
  * * value is data-value property.
6128
6651
  */
6129
- pluginEvents: {}
6652
+ pluginEvents: {},
6653
+
6654
+ plugins : []
6130
6655
  });
6131
6656
 
6132
6657
  /**
@@ -6187,6 +6712,10 @@
6187
6712
  * @param {Object} [plugin.options] update $.summernote.options
6188
6713
  */
6189
6714
  $.summernote.addPlugin = function (plugin) {
6715
+
6716
+ // save plugin list
6717
+ $.summernote.plugins.push(plugin);
6718
+
6190
6719
  if (plugin.buttons) {
6191
6720
  $.each(plugin.buttons, function (name, button) {
6192
6721
  renderer.addButtonInfo(name, button);
@@ -6232,11 +6761,19 @@
6232
6761
  * ```
6233
6762
  *
6234
6763
  * @member $.fn
6235
- * @param {Object} options reference to $.summernote.options
6236
- * @returns {this}
6237
- */
6238
- summernote: function (options) {
6239
- // extend default options
6764
+ * @param {Object|String} options reference to $.summernote.options
6765
+ * @return {this}
6766
+ */
6767
+ summernote: function () {
6768
+ // check first argument's type
6769
+ // - {String}: External API call {{module}}.{{method}}
6770
+ // - {Object}: init options
6771
+ var type = $.type(list.head(arguments));
6772
+ var isExternalAPICalled = type === 'string';
6773
+ var isInitOptions = type === 'object';
6774
+
6775
+ // extend default options with custom user options
6776
+ var options = isInitOptions ? list.head(arguments) : {};
6240
6777
  options = $.extend({}, $.summernote.options, options);
6241
6778
 
6242
6779
  // Include langInfo in options for later use, e.g. for image drag-n-drop
@@ -6246,40 +6783,43 @@
6246
6783
  this.each(function (idx, holder) {
6247
6784
  var $holder = $(holder);
6248
6785
 
6249
- // createLayout with options
6250
- renderer.createLayout($holder, options);
6786
+ // if layout isn't created yet, createLayout and attach events
6787
+ if (!renderer.hasNoteEditor($holder)) {
6788
+ renderer.createLayout($holder, options);
6251
6789
 
6252
- var info = renderer.layoutInfoFromHolder($holder);
6253
- eventHandler.attach(info, options);
6790
+ var layoutInfo = renderer.layoutInfoFromHolder($holder);
6254
6791
 
6255
- // Textarea: auto filling the code before form submit.
6256
- if (dom.isTextarea($holder[0])) {
6257
- $holder.closest('form').submit(function () {
6258
- var contents = $holder.code();
6259
- $holder.val(contents);
6792
+ eventHandler.attach(layoutInfo, options);
6793
+ eventHandler.attachCustomEvent(layoutInfo, options);
6260
6794
 
6261
- // callback on submit
6262
- if (options.onsubmit) {
6263
- options.onsubmit(contents);
6264
- }
6265
- });
6266
6795
  }
6267
6796
  });
6268
6797
 
6269
- // focus on first editable element
6270
- if (this.first().length && options.focus) {
6271
- var info = renderer.layoutInfoFromHolder(this.first());
6272
- info.editable.focus();
6273
- }
6274
-
6275
6798
  // callback on init
6276
- if (this.length && options.oninit) {
6799
+ if (!isExternalAPICalled && this.length && options.oninit) {
6277
6800
  options.oninit();
6278
6801
  }
6279
6802
 
6803
+ var $first = this.first();
6804
+ if ($first.length) {
6805
+ var layoutInfo = renderer.layoutInfoFromHolder($first);
6806
+
6807
+ // external API
6808
+ if (isExternalAPICalled) {
6809
+ var moduleAndMethod = list.head(list.from(arguments));
6810
+ var args = list.tail(list.from(arguments));
6811
+
6812
+ // TODO now external API only works for editor
6813
+ var params = [moduleAndMethod, layoutInfo.editable()].concat(args);
6814
+ return eventHandler.invoke.apply(eventHandler, params);
6815
+ } else if (options.focus) {
6816
+ // focus on first editable element for initialize editor
6817
+ layoutInfo.editable().focus();
6818
+ }
6819
+ }
6820
+
6280
6821
  return this;
6281
6822
  },
6282
- //
6283
6823
 
6284
6824
  /**
6285
6825
  * @method
@@ -6297,29 +6837,36 @@
6297
6837
  * ```
6298
6838
  *
6299
6839
  * @member $.fn
6300
- * @param {String} [sHTML] - HTML contents(optional, set)
6301
- * @returns {this|String} - context(set) or HTML contents of note(get).
6840
+ * @param {String} [html] - HTML contents(optional, set)
6841
+ * @return {this|String} - context(set) or HTML contents of note(get).
6302
6842
  */
6303
- code: function (sHTML) {
6843
+ code: function (html) {
6304
6844
  // get the HTML contents of note
6305
- if (sHTML === undefined) {
6845
+ if (html === undefined) {
6306
6846
  var $holder = this.first();
6307
- if (!$holder.length) { return; }
6308
- var info = renderer.layoutInfoFromHolder($holder);
6309
- if (!!(info && info.editable)) {
6310
- var isCodeview = info.editor.hasClass('codeview');
6311
- if (isCodeview && agent.hasCodeMirror) {
6312
- info.codable.data('cmEditor').save();
6313
- }
6314
- return isCodeview ? info.codable.val() : info.editable.html();
6847
+ if (!$holder.length) {
6848
+ return;
6315
6849
  }
6316
- return dom.isTextarea($holder[0]) ? $holder.val() : $holder.html();
6850
+
6851
+ var layoutInfo = renderer.layoutInfoFromHolder($holder);
6852
+ var $editable = layoutInfo && layoutInfo.editable();
6853
+
6854
+ if ($editable && $editable.length) {
6855
+ var isCodeview = eventHandler.invoke('codeview.isActivated', layoutInfo);
6856
+ eventHandler.invoke('codeview.sync', layoutInfo);
6857
+ return isCodeview ? layoutInfo.codable().val() :
6858
+ layoutInfo.editable().html();
6859
+ }
6860
+ return dom.value($holder);
6317
6861
  }
6318
6862
 
6319
6863
  // set the HTML contents of note
6320
6864
  this.each(function (i, holder) {
6321
- var info = renderer.layoutInfoFromHolder($(holder));
6322
- if (info && info.editable) { info.editable.html(sHTML); }
6865
+ var layoutInfo = renderer.layoutInfoFromHolder($(holder));
6866
+ var $editable = layoutInfo && layoutInfo.editable();
6867
+ if ($editable) {
6868
+ $editable.html(html);
6869
+ }
6323
6870
  });
6324
6871
 
6325
6872
  return this;
@@ -6331,16 +6878,18 @@
6331
6878
  * destroy Editor Layout and detach Key and Mouse Event
6332
6879
  *
6333
6880
  * @member $.fn
6334
- * @returns {this}
6881
+ * @return {this}
6335
6882
  */
6336
6883
  destroy: function () {
6337
6884
  this.each(function (idx, holder) {
6338
6885
  var $holder = $(holder);
6339
6886
 
6340
- var info = renderer.layoutInfoFromHolder($holder);
6341
- if (!info || !info.editable) { return; }
6887
+ if (!renderer.hasNoteEditor($holder)) {
6888
+ return;
6889
+ }
6342
6890
 
6343
- var options = info.editor.data('options');
6891
+ var info = renderer.layoutInfoFromHolder($holder);
6892
+ var options = info.editor().data('options');
6344
6893
 
6345
6894
  eventHandler.detach(info, options);
6346
6895
  renderer.removeLayout($holder, info, options);