summernote-rails 0.6.2.1 → 0.6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);