summernote-rails 0.3.0 → 0.4.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.
@@ -3,12 +3,15 @@
3
3
  * (c) 2013~ Alan Hong
4
4
  * summernote may be freely distributed under the MIT license./
5
5
  */
6
- (function($) {
7
- "use strict";
8
-
9
- //Check Platform/Agent
10
- var bMac = navigator.appVersion.indexOf('Mac') > -1;
11
- var bMSIE = navigator.userAgent.indexOf('MSIE') > -1;
6
+ (function($) { "use strict";
7
+ /**
8
+ * object which check platform/agent
9
+ */
10
+ var agent = {
11
+ bMac: navigator.appVersion.indexOf('Mac') > -1,
12
+ bMSIE: navigator.userAgent.indexOf('MSIE') > -1,
13
+ bFF: navigator.userAgent.indexOf('Firefox') > -1
14
+ };
12
15
 
13
16
  /**
14
17
  * func utils (for high-order func's arg)
@@ -17,8 +20,8 @@
17
20
  var eq = function(elA) { return function(elB) { return elA === elB; }; };
18
21
  var eq2 = function(elA, elB) { return elA === elB; };
19
22
  var fail = function() { return false; };
20
- var not = function(f) { return function() { return !f.apply(f, arguments); }};
21
- var self = function(a) { return a; }
23
+ var not = function(f) { return function() { return !f.apply(f, arguments); }; };
24
+ var self = function(a) { return a; };
22
25
  return { eq: eq, eq2: eq2, fail: fail, not: not, self: self };
23
26
  }();
24
27
 
@@ -31,6 +34,11 @@
31
34
  var initial = function(array) { return array.slice(0, array.length - 1); };
32
35
  var tail = function(array) { return array.slice(1); };
33
36
 
37
+ /**
38
+ * get sum from a list
39
+ * @param {array} array - array
40
+ * @param {function} fn - iterator
41
+ */
34
42
  var sum = function(array, fn) {
35
43
  fn = fn || func.self;
36
44
  return array.reduce(function(memo, v) {
@@ -38,6 +46,10 @@
38
46
  }, 0);
39
47
  };
40
48
 
49
+ /**
50
+ * returns a copy of the collection with array type.
51
+ * @param {collection} collection - collection eg) node.childNodes, ...
52
+ */
41
53
  var from = function(collection) {
42
54
  var result = [], idx = -1, length = collection.length;
43
55
  while (++idx < length) {
@@ -46,6 +58,11 @@
46
58
  return result;
47
59
  };
48
60
 
61
+ /**
62
+ * cluster item by second function
63
+ * @param {array} array - array
64
+ * @param {function} fn - predicate function for cluster rule
65
+ */
49
66
  var clusterBy = function(array, fn) {
50
67
  if (array.length === 0) { return []; }
51
68
  var aTail = tail(array);
@@ -60,25 +77,81 @@
60
77
  }, [[head(array)]]);
61
78
  };
62
79
 
80
+ /**
81
+ * returns a copy of the array with all falsy values removed
82
+ * @param {array} array - array
83
+ * @param {function} fn - predicate function for cluster rule
84
+ */
63
85
  var compact = function(array) {
64
86
  var aResult = [];
65
87
  for (var idx = 0, sz = array.length; idx < sz; idx ++) {
66
- if (array[idx]) { aResult.push(array[idx]); };
67
- };
88
+ if (array[idx]) { aResult.push(array[idx]); }
89
+ }
68
90
  return aResult;
69
91
  };
70
92
 
71
93
  return { head: head, last: last, initial: initial, tail: tail,
72
94
  sum: sum, from: from, compact: compact, clusterBy: clusterBy };
73
95
  }();
96
+
97
+ /**
98
+ * aysnc functions which returns deferred object
99
+ */
100
+ var async = function() {
101
+ /**
102
+ * readFile
103
+ * @param {file} file - file object
104
+ */
105
+ var readFile = function(file) {
106
+ return $.Deferred(function(deferred) {
107
+ var reader = new FileReader();
108
+ reader.onload = function(e) { deferred.resolve(e.target.result); }
109
+ reader.onerror = function(e) { deferred.reject(this); }
110
+ reader.readAsDataURL(file);
111
+ }).promise();
112
+ };
113
+
114
+ /**
115
+ * loadImage
116
+ * @param {string} sUrl
117
+ */
118
+ var loadImage = function(sUrl) {
119
+ return $.Deferred(function(deferred) {
120
+   var image = new Image();
121
+   image.onload = loaded;
122
+   image.onerror = errored; // URL returns 404, etc
123
+   image.onabort = errored; // IE may call this if user clicks "Stop"
124
+   image.src = sUrl;
125
+    
126
+   function loaded() {
127
+     unbindEvents(); deferred.resolve(image);
128
+   }
129
+   function errored() {
130
+     unbindEvents(); deferred.reject(image);
131
+   }
132
+   function unbindEvents() {
133
+     image.onload = null;
134
+     image.onerror = null;
135
+     image.onabort = null;
136
+   }
137
+ }).promise();
138
+ };
139
+
140
+ return { readFile: readFile, loadImage: loadImage };
141
+ }();
74
142
 
75
143
  /**
76
144
  * dom utils
77
145
  */
78
146
  var dom = function() {
147
+ /**
148
+ * returns predicate which judge whether nodeName is same
149
+ */
79
150
  var makePredByNodeName = function(sNodeName) {
80
151
  // nodeName of element is always uppercase.
81
- return function(node) { return node && node.nodeName === sNodeName; };
152
+ return function(node) {
153
+ return node && node.nodeName === sNodeName;
154
+ };
82
155
  };
83
156
 
84
157
  var isPara = function(node) {
@@ -97,7 +170,11 @@
97
170
  return node && $(node).hasClass('note-control-sizing');
98
171
  };
99
172
 
100
- // ancestor: find nearest ancestor predicate hit
173
+ /**
174
+ * find nearest ancestor predicate hit
175
+ * @param {element} node
176
+ * @param {function} pred - predicate function
177
+ */
101
178
  var ancestor = function(node, pred) {
102
179
  while (node) {
103
180
  if (pred(node)) { return node; }
@@ -106,7 +183,11 @@
106
183
  return null;
107
184
  };
108
185
 
109
- // listAncestor: listing ancestor nodes (until predicate hit: optional)
186
+ /**
187
+ * returns new array of ancestor nodes (until predicate hit).
188
+ * @param {element} node
189
+ * @param {function} [optional] pred - predicate function
190
+ */
110
191
  var listAncestor = function(node, pred) {
111
192
  pred = pred || func.fail;
112
193
 
@@ -118,7 +199,11 @@
118
199
  return aAncestor;
119
200
  };
120
201
 
121
- // commonAncestor: find commonAncestor
202
+ /**
203
+ * returns common ancestor node between two nodes.
204
+ * @param {element} nodeA
205
+ * @param {element} nodeB
206
+ */
122
207
  var commonAncestor = function(nodeA, nodeB) {
123
208
  var aAncestor = listAncestor(nodeA);
124
209
  for (var n = nodeB; n; n = n.parentNode) {
@@ -127,8 +212,12 @@
127
212
  return null; // difference document area
128
213
  };
129
214
 
130
- // listBetween: listing all Nodes between nodeA and nodeB
131
- // FIXME: nodeA and nodeB must be sorted, use comparePoints later.
215
+ /**
216
+ * listing all Nodes between two nodes.
217
+ * FIXME: nodeA and nodeB must be sorted, use comparePoints later.
218
+ * @param {element} nodeA
219
+ * @param {element} nodeB
220
+ */
132
221
  var listBetween = function(nodeA, nodeB) {
133
222
  var aNode = [];
134
223
 
@@ -136,19 +225,23 @@
136
225
  var fnWalk = function(node) {
137
226
  if (!node) { return; } // traverse fisnish
138
227
  if (node === nodeA) { bStart = true; } // start point
139
- if (bStart && !bEnd) { aNode.push(node) } // between
228
+ if (bStart && !bEnd) { aNode.push(node); } // between
140
229
  if (node === nodeB) { bEnd = true; return; } // end point
141
230
 
142
- for (var idx = 0, sz=node.childNodes.length; idx < sz; idx++) {
231
+ for (var idx = 0, sz = node.childNodes.length; idx < sz; idx++) {
143
232
  fnWalk(node.childNodes[idx]);
144
233
  }
145
- }
234
+ };
146
235
 
147
236
  fnWalk(commonAncestor(nodeA, nodeB)); // DFS with commonAcestor.
148
237
  return aNode;
149
238
  };
150
239
 
151
- // listPrev: listing prevSiblings (until predicate hit: optional)
240
+ /**
241
+ * listing all prevSiblings (until predicate hit).
242
+ * @param {element} node
243
+ * @param {function} [optional] pred - predicate function
244
+ */
152
245
  var listPrev = function(node, pred) {
153
246
  pred = pred || func.fail;
154
247
 
@@ -157,11 +250,15 @@
157
250
  aNext.push(node);
158
251
  if (pred(node)) { break; }
159
252
  node = node.previousSibling;
160
- };
253
+ }
161
254
  return aNext;
162
255
  };
163
256
 
164
- // listNext: listing nextSiblings (until predicate hit: optional)
257
+ /**
258
+ * listing nextSiblings (until predicate hit).
259
+ * @param {element} node
260
+ * @param {function} pred [optional] - predicate function
261
+ */
165
262
  var listNext = function(node, pred) {
166
263
  pred = pred || func.fail;
167
264
 
@@ -170,11 +267,15 @@
170
267
  aNext.push(node);
171
268
  if (pred(node)) { break; }
172
269
  node = node.nextSibling;
173
- };
270
+ }
174
271
  return aNext;
175
272
  };
176
273
 
177
- // insertAfter: insert node after preceding
274
+ /**
275
+ * insert node after preceding
276
+ * @param {element} node
277
+ * @param {element} preceding - predicate function
278
+ */
178
279
  var insertAfter = function(node, preceding) {
179
280
  var next = preceding.nextSibling, parent = preceding.parentNode;
180
281
  if (next) {
@@ -185,7 +286,11 @@
185
286
  return node;
186
287
  };
187
288
 
188
- // appends: append children
289
+ /**
290
+ * append children
291
+ * @param {element} node
292
+ * @param {collection} aChild
293
+ */
189
294
  var appends = function(node, aChild) {
190
295
  $.each(aChild, function(idx, child) {
191
296
  node.appendChild(child);
@@ -195,26 +300,40 @@
195
300
 
196
301
  var isText = makePredByNodeName('#text');
197
302
 
198
- // length: size of element.
303
+ /**
304
+ * returns #text's text size or element's childNodes size
305
+ * @param {element} node
306
+ */
199
307
  var length = function(node) {
200
308
  if (isText(node)) { return node.nodeValue.length; }
201
309
  return node.childNodes.length;
202
310
  };
203
311
 
204
- // position: offset from parent.
312
+ /**
313
+ * returns offset from parent.
314
+ * @param {element} node
315
+ */
205
316
  var position = function(node) {
206
317
  var offset = 0;
207
318
  while (node = node.previousSibling) { offset += 1; }
208
319
  return offset;
209
320
  };
210
321
 
211
- // makeOffsetPath: return offsetPath(offset list) from ancestor
322
+ /**
323
+ * return offsetPath(array of offset) from ancestor
324
+ * @param {element} ancestor - ancestor node
325
+ * @param {element} node
326
+ */
212
327
  var makeOffsetPath = function(ancestor, node) {
213
328
  var aAncestor = list.initial(listAncestor(node, func.eq(ancestor)));
214
329
  return $.map(aAncestor, position).reverse();
215
330
  };
216
331
 
217
- // fromtOffsetPath: return element from offsetPath(offset list)
332
+ /**
333
+ * return element from offsetPath(array of offset)
334
+ * @param {element} ancestor - ancestor node
335
+ * @param {array} aOffset - offsetPath
336
+ */
218
337
  var fromOffsetPath = function(ancestor, aOffset) {
219
338
  var current = ancestor;
220
339
  for (var i = 0, sz = aOffset.length; i < sz; i++) {
@@ -223,7 +342,11 @@
223
342
  return current;
224
343
  };
225
344
 
226
- // splitData: split element or #text
345
+ /**
346
+ * split element or #text
347
+ * @param {element} node
348
+ * @param {number} offset
349
+ */
227
350
  var splitData = function(node, offset) {
228
351
  if (offset === 0) { return node; }
229
352
  if (offset >= length(node)) { return node.nextSibling; }
@@ -237,7 +360,12 @@
237
360
  return appends(node, listNext(child));
238
361
  };
239
362
 
240
- // split: split dom tree by boundaryPoint(pivot and offset)
363
+ /**
364
+ * split dom tree by boundaryPoint(pivot and offset)
365
+ * @param {element} root
366
+ * @param {element} pivot - this will be boundaryPoint's node
367
+ * @param {number} offset - this will be boundaryPoint's offset
368
+ */
241
369
  var split = function(root, pivot, offset) {
242
370
  var aAncestor = listAncestor(pivot, func.eq(root));
243
371
  if (aAncestor.length === 1) { return splitData(pivot, offset); }
@@ -251,6 +379,37 @@
251
379
  return clone;
252
380
  });
253
381
  };
382
+
383
+ /**
384
+ * remove node, (bRemoveChild: remove child or not)
385
+ * @param {element} node
386
+ * @param {boolean} bRemoveChild
387
+ */
388
+ var remove = function(node, bRemoveChild) {
389
+ if (!node || !node.parentNode) { return; }
390
+ if (node.removeNode) { return node.removeNode(bRemoveChild); }
391
+
392
+ var elParent = node.parentNode;
393
+ if (!bRemoveChild) {
394
+ var aNode = [];
395
+ for (var i = 0, sz = node.childNodes.length; i < sz; i++) {
396
+ aNode.push(node.childNodes[i]);
397
+ }
398
+
399
+ for (var i = 0, sz = aNode.length; i < sz; i++) {
400
+ elParent.insertBefore(aNode[i], node);
401
+ }
402
+ }
403
+
404
+ elParent.removeChild(node);
405
+ };
406
+
407
+ var unescape = function(str) {
408
+ return $("<div/>").html(str).text();
409
+ };
410
+ var html = function($node) {
411
+ return dom.isTextarea($node[0]) ? unescape($node.val()) : $node.html();
412
+ };
254
413
 
255
414
  return {
256
415
  isText: isText,
@@ -260,209 +419,215 @@
260
419
  isDiv: makePredByNodeName('DIV'), isSpan: makePredByNodeName('SPAN'),
261
420
  isB: makePredByNodeName('B'), isU: makePredByNodeName('U'),
262
421
  isS: makePredByNodeName('S'), isI: makePredByNodeName('I'),
263
- isImg: makePredByNodeName('IMG'),
422
+ isImg: makePredByNodeName('IMG'), isTextarea: makePredByNodeName('TEXTAREA'),
264
423
  ancestor: ancestor, listAncestor: listAncestor,
265
424
  listNext: listNext, listPrev: listPrev,
266
425
  commonAncestor: commonAncestor, listBetween: listBetween,
267
426
  insertAfter: insertAfter, position: position,
268
427
  makeOffsetPath: makeOffsetPath, fromOffsetPath: fromOffsetPath,
269
- split: split
428
+ split: split, remove: remove, html: html
270
429
  };
271
430
  }();
272
431
 
273
432
  /**
274
- * Range
275
- * {startContainer, startOffset, endContainer, endOffset}
276
- * create Range Object From arguments or Browser Selection
433
+ * range module
277
434
  */
278
- var bW3CRangeSupport = !!document.createRange;
279
-
280
- // return boundary point from TextRange(ie8)
281
- // inspired by Andy Na's HuskyRange.js
282
- var textRange2bp = function(textRange, bStart) {
283
- var elCont = textRange.parentElement(), nOffset;
284
-
285
- var tester = document.body.createTextRange(), elPrevCont;
286
- var aChild = list.from(elCont.childNodes);
287
- for (nOffset = 0; nOffset < aChild.length; nOffset++) {
288
- if (dom.isText(aChild[nOffset])) { continue; }
289
- tester.moveToElementText(aChild[nOffset]);
290
- if (tester.compareEndPoints("StartToStart", textRange) >= 0) { break; }
291
- elPrevCont = aChild[nOffset];
292
- }
435
+ var range = function() {
436
+ var bW3CRangeSupport = !!document.createRange;
437
+
438
+ // return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
439
+ var textRange2bp = function(textRange, bStart) {
440
+ var elCont = textRange.parentElement(), nOffset;
441
+
442
+ var tester = document.body.createTextRange(), elPrevCont;
443
+ var aChild = list.from(elCont.childNodes);
444
+ for (nOffset = 0; nOffset < aChild.length; nOffset++) {
445
+ if (dom.isText(aChild[nOffset])) { continue; }
446
+ tester.moveToElementText(aChild[nOffset]);
447
+ if (tester.compareEndPoints('StartToStart', textRange) >= 0) { break; }
448
+ elPrevCont = aChild[nOffset];
449
+ }
293
450
 
294
- if (nOffset != 0 && dom.isText(aChild[nOffset - 1])) {
295
- var textRangeStart = document.body.createTextRange(), elCurText = null;
296
- textRangeStart.moveToElementText(elPrevCont || elCont);
297
- textRangeStart.collapse(!elPrevCont);
298
- elCurText = elPrevCont ? elPrevCont.nextSibling : elCont.firstChild;
451
+ if (nOffset !== 0 && dom.isText(aChild[nOffset - 1])) {
452
+ var textRangeStart = document.body.createTextRange(), elCurText = null;
453
+ textRangeStart.moveToElementText(elPrevCont || elCont);
454
+ textRangeStart.collapse(!elPrevCont);
455
+ elCurText = elPrevCont ? elPrevCont.nextSibling : elCont.firstChild;
299
456
 
300
- var pointTester = textRange.duplicate();
301
- pointTester.setEndPoint("StartToStart", textRangeStart);
302
- var nTextCount = pointTester.text.replace(/[\r\n]/g, "").length;
457
+ var pointTester = textRange.duplicate();
458
+ pointTester.setEndPoint('StartToStart', textRangeStart);
459
+ var nTextCount = pointTester.text.replace(/[\r\n]/g, '').length;
303
460
 
304
- while (nTextCount > elCurText.nodeValue.length && elCurText.nextSibling) {
305
- nTextCount -= elCurText.nodeValue.length;
306
- elCurText = elCurText.nextSibling;
307
- }
308
- var sDummy = elCurText.nodeValue; //enforce IE to re-reference elCurText
461
+ while (nTextCount > elCurText.nodeValue.length && elCurText.nextSibling) {
462
+ nTextCount -= elCurText.nodeValue.length;
463
+ elCurText = elCurText.nextSibling;
464
+ }
465
+ var sDummy = elCurText.nodeValue; //enforce IE to re-reference elCurText
309
466
 
310
- if (bStart && elCurText.nextSibling && dom.isText(elCurText.nextSibling) &&
311
- nTextCount == elCurText.nodeValue.length) {
312
- nTextCount -= elCurText.nodeValue.length;
467
+ if (bStart && elCurText.nextSibling && dom.isText(elCurText.nextSibling) &&
468
+ nTextCount == elCurText.nodeValue.length) {
469
+ nTextCount -= elCurText.nodeValue.length;
313
470
  elCurText = elCurText.nextSibling;
471
+ }
472
+
473
+ elCont = elCurText;
474
+ nOffset = nTextCount;
314
475
  }
315
476
 
316
- elCont = elCurText;
317
- nOffset = nTextCount;
318
- }
477
+ return {cont: elCont, offset: nOffset};
478
+ };
319
479
 
320
- return {cont: elCont, offset: nOffset};
321
- };
480
+ // return TextRange from boundary point (inspired by google closure-library)
481
+ var bp2textRange = function(bp) {
482
+ var textRangeInfo = function(elCont, nOffset) {
483
+ var elNode, bCollapseToStart;
322
484
 
323
- // return TextRange(ie8) from boundary point
324
- // (inspired by google closure-library)
325
- var bp2textRange = function(bp) {
326
- var textRangeInfo = function(elCont, nOffset) {
327
- var elNode, bCollapseToStart;
328
-
329
- if (dom.isText(elCont)) {
330
- var aPrevText = dom.listPrev(elCont, func.not(dom.isText));
331
- var elPrevCont = list.last(aPrevText).previousSibling;
332
- elNode = elPrevCont || elCont.parentNode;
333
- nOffset += list.sum(list.tail(aPrevText), dom.length);
334
- bCollapseToStart = !elPrevCont;
335
- } else {
336
- elNode = elCont.childNodes[nOffset] || elCont;
337
- if (dom.isText(elNode)) {
338
- return textRangeInfo(elNode, nOffset);
485
+ if (dom.isText(elCont)) {
486
+ var aPrevText = dom.listPrev(elCont, func.not(dom.isText));
487
+ var elPrevCont = list.last(aPrevText).previousSibling;
488
+ elNode = elPrevCont || elCont.parentNode;
489
+ nOffset += list.sum(list.tail(aPrevText), dom.length);
490
+ bCollapseToStart = !elPrevCont;
491
+ } else {
492
+ elNode = elCont.childNodes[nOffset] || elCont;
493
+ if (dom.isText(elNode)) {
494
+ return textRangeInfo(elNode, nOffset);
495
+ }
496
+
497
+ nOffset = 0;
498
+ bCollapseToStart = false;
339
499
  }
340
500
 
341
- nOffset = 0;
342
- bCollapseToStart = false;
343
- }
501
+ return {cont: elNode, collapseToStart: bCollapseToStart, offset: nOffset};
502
+ };
344
503
 
345
- return {cont: elNode, collapseToStart: bCollapseToStart, offset: nOffset};
346
- }
504
+ var textRange = document.body.createTextRange();
505
+ var info = textRangeInfo(bp.cont, bp.offset);
347
506
 
348
- var textRange = document.body.createTextRange();
349
- var info = textRangeInfo(bp.cont, bp.offset);
507
+ textRange.moveToElementText(info.cont);
508
+ textRange.collapse(info.collapseToStart);
509
+ textRange.moveStart('character', info.offset);
510
+ return textRange;
511
+ };
350
512
 
351
- textRange.moveToElementText(info.cont);
352
- textRange.collapse(info.collapseToStart);
353
- textRange.moveStart("character", info.offset);
354
- return textRange;
355
- };
513
+ // {startContainer, startOffset, endContainer, endOffset}
514
+ var WrappedRange = function(sc, so, ec, eo) {
515
+ this.sc = sc; this.so = so;
516
+ this.ec = ec; this.eo = eo;
356
517
 
357
- var Range = function(sc, so, ec, eo) {
358
- if (arguments.length === 0) { // from Browser Selection
359
- if (bW3CRangeSupport) { // webkit, firefox
360
- var nativeRng = document.getSelection().getRangeAt(0);
361
- sc = nativeRng.startContainer, so = nativeRng.startOffset,
362
- ec = nativeRng.endContainer, eo = nativeRng.endOffset;
363
- } else { //TextRange
364
- var textRange = document.selection.createRange();
365
- var textRangeEnd = textRange.duplicate(); textRangeEnd.collapse(false);
366
- var textRangeStart = textRange; textRangeStart.collapse(true);
367
-
368
- var bpStart = textRange2bp(textRangeStart, true),
369
- bpEnd = textRange2bp(textRangeEnd, false);
518
+ // nativeRange: get nativeRange from sc, so, ec, eo
519
+ var nativeRange = function() {
520
+ if (bW3CRangeSupport) {
521
+ var w3cRange = document.createRange();
522
+ w3cRange.setStart(sc, so);
523
+ w3cRange.setEnd(ec, eo);
524
+ return w3cRange;
525
+ } else {
526
+ var textRange = bp2textRange({cont:sc, offset:so});
527
+ textRange.setEndPoint('EndToEnd', bp2textRange({cont:ec, offset:eo}));
528
+ return textRange;
529
+ }
530
+ };
370
531
 
371
- sc = bpStart.cont, so = bpStart.offset;
372
- ec = bpEnd.cont, eo = bpEnd.offset;
373
- }
374
- }
375
-
376
- this.sc = sc; this.so = so;
377
- this.ec = ec; this.eo = eo;
378
-
379
- // nativeRange: get nativeRange from sc, so, ec, eo
380
- var nativeRange = function() {
381
- if (bW3CRangeSupport) {
382
- var range = document.createRange();
383
- range.setStart(sc, so);
384
- range.setEnd(ec, eo);
385
- return range;
386
- } else {
387
- var textRange = bp2textRange({cont:sc, offset:so});
388
- textRange.setEndPoint('EndToEnd', bp2textRange({cont:ec, offset:eo}));
389
- return textRange;
390
- }
391
- };
392
-
393
- // select: update visible range
394
- this.select = function() {
395
- var nativeRng = nativeRange();
396
- if (bW3CRangeSupport) {
397
- var selection = document.getSelection();
398
- if (selection.rangeCount > 0) { selection.removeAllRanges(); }
399
- selection.addRange(nativeRng);
400
- } else {
401
- nativeRng.select();
402
- }
403
- };
404
-
405
- // listPara: listing paragraphs on range
406
- this.listPara = function() {
407
- var aNode = dom.listBetween(sc, ec);
408
- var aPara = list.compact($.map(aNode, function(node) {
409
- return dom.ancestor(node, dom.isPara);
410
- }));
411
- return $.map(list.clusterBy(aPara, func.eq2), list.head);
412
- };
413
-
414
- // isOnList: judge whether range is on list node or not
415
- this.isOnList = function() {
416
- var elStart = dom.ancestor(sc, dom.isList),
417
- elEnd = dom.ancestor(ec, dom.isList);
418
- return elStart && (elStart === elEnd);
419
- };
532
+ // select: update visible range
533
+ this.select = function() {
534
+ var nativeRng = nativeRange();
535
+ if (bW3CRangeSupport) {
536
+ var selection = document.getSelection();
537
+ if (selection.rangeCount > 0) { selection.removeAllRanges(); }
538
+ selection.addRange(nativeRng);
539
+ } else {
540
+ nativeRng.select();
541
+ }
542
+ };
420
543
 
421
- // isOnAnchor: judge whether range is on anchor node or not
422
- this.isOnAnchor = function() {
423
- var elStart = dom.ancestor(sc, dom.isAnchor),
424
- elEnd = dom.ancestor(ec, dom.isAnchor);
425
- return elStart && (elStart === elEnd);
426
- };
544
+ // listPara: listing paragraphs on range
545
+ this.listPara = function() {
546
+ var aNode = dom.listBetween(sc, ec);
547
+ var aPara = list.compact($.map(aNode, function(node) {
548
+ return dom.ancestor(node, dom.isPara);
549
+ }));
550
+ return $.map(list.clusterBy(aPara, func.eq2), list.head);
551
+ };
427
552
 
428
- // isCollapsed: judge whether range was collapsed
429
- this.isCollapsed = function() { return sc === ec && so === eo; };
430
-
431
- // insertNode
432
- this.insertNode = function(node) {
433
- var nativeRng = nativeRange();
434
- if (bW3CRangeSupport) {
435
- nativeRng.insertNode(node);
436
- } else {
437
- nativeRng.pasteHTML(node.outerHTML); // NOTE: missing node reference.
438
- }
439
- };
553
+ // makeIsOn: return isOn(pred) function
554
+ var makeIsOn = function(pred) {
555
+ return function() {
556
+ var elAncestor = dom.ancestor(sc, pred);
557
+ return elAncestor && (elAncestor === dom.ancestor(ec, pred));
558
+ };
559
+ };
440
560
 
441
- this.toString = function() {
442
- var nativeRng = nativeRange();
443
- if (bW3CRangeSupport) {
444
- return nativeRng.toString();
445
- } else {
446
- return nativeRng.text;
447
- }
448
- };
561
+ // isOnEditable: judge whether range is on editable or not
562
+ this.isOnEditable = makeIsOn(dom.isEditable);
563
+ // isOnList: judge whether range is on list node or not
564
+ this.isOnList = makeIsOn(dom.isList);
565
+ // isOnAnchor: judge whether range is on anchor node or not
566
+ this.isOnAnchor = makeIsOn(dom.isAnchor);
567
+ // isCollapsed: judge whether range was collapsed
568
+ this.isCollapsed = function() { return sc === ec && so === eo; };
569
+
570
+ // insertNode
571
+ this.insertNode = function(node) {
572
+ var nativeRng = nativeRange();
573
+ if (bW3CRangeSupport) {
574
+ nativeRng.insertNode(node);
575
+ } else {
576
+ nativeRng.pasteHTML(node.outerHTML); // NOTE: missing node reference.
577
+ }
578
+ };
449
579
 
450
- //bookmark: offsetPath bookmark
451
- this.bookmark = function(elEditable) {
452
- return {
453
- s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
454
- e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
580
+ this.toString = function() {
581
+ var nativeRng = nativeRange();
582
+ return bW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
583
+ };
584
+
585
+ //bookmark: offsetPath bookmark
586
+ this.bookmark = function(elEditable) {
587
+ return {
588
+ s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
589
+ e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
590
+ };
455
591
  };
456
592
  };
457
- };
458
593
 
459
- // createRangeFromBookmark
460
- var createRangeFromBookmark = function(elEditable, bookmark) {
461
- return new Range(dom.fromOffsetPath(elEditable, bookmark.s.path),
462
- bookmark.s.offset,
463
- dom.fromOffsetPath(elEditable, bookmark.e.path),
464
- bookmark.e.offset);
465
- };
594
+ return { // Range Object
595
+ // create Range Object From arguments or Browser Selection
596
+ create : function(sc, so, ec, eo) {
597
+ if (arguments.length === 0) { // from Browser Selection
598
+ if (bW3CRangeSupport) { // webkit, firefox
599
+ var selection = document.getSelection();
600
+ if (selection.rangeCount === 0) { return null; }
601
+
602
+ var nativeRng = selection.getRangeAt(0);
603
+ sc = nativeRng.startContainer, so = nativeRng.startOffset,
604
+ ec = nativeRng.endContainer, eo = nativeRng.endOffset;
605
+ } else { // IE8: TextRange
606
+ var textRange = document.selection.createRange();
607
+ var textRangeEnd = textRange.duplicate(); textRangeEnd.collapse(false);
608
+ var textRangeStart = textRange; textRangeStart.collapse(true);
609
+
610
+ var bpStart = textRange2bp(textRangeStart, true),
611
+ bpEnd = textRange2bp(textRangeEnd, false);
612
+
613
+ sc = bpStart.cont, so = bpStart.offset;
614
+ ec = bpEnd.cont, eo = bpEnd.offset;
615
+ }
616
+ } else if (arguments.length === 2) { //collapsed
617
+ ec = sc; eo = so;
618
+ }
619
+ return new WrappedRange(sc, so, ec, eo);
620
+ },
621
+ // createFromBookmark
622
+ createFromBookmark : function(elEditable, bookmark) {
623
+ var sc = dom.fromOffsetPath(elEditable, bookmark.s.path);
624
+ var so = bookmark.s.offset;
625
+ var ec = dom.fromOffsetPath(elEditable, bookmark.e.path);
626
+ var eo = bookmark.e.offset;
627
+ return new WrappedRange(sc, so, ec, eo);
628
+ }
629
+ };
630
+ }();
466
631
 
467
632
  /**
468
633
  * Style
@@ -513,7 +678,7 @@
513
678
  oStyle.aAncestor = dom.listAncestor(rng.sc, dom.isEditable);
514
679
 
515
680
  return oStyle;
516
- }
681
+ };
517
682
  };
518
683
 
519
684
  /**
@@ -523,7 +688,7 @@
523
688
  var aUndo = [], aRedo = [];
524
689
 
525
690
  var makeSnap = function(welEditable) {
526
- var elEditable = welEditable[0], rng = new Range();
691
+ var elEditable = welEditable[0], rng = range.create();
527
692
  return {
528
693
  contents: welEditable.html(), bookmark: rng.bookmark(elEditable),
529
694
  scrollTop: welEditable.scrollTop()
@@ -532,7 +697,7 @@
532
697
 
533
698
  var applySnap = function(welEditable, oSnap) {
534
699
  welEditable.html(oSnap.contents).scrollTop(oSnap.scrollTop);
535
- createRangeFromBookmark(welEditable[0], oSnap.bookmark).select();
700
+ range.createFromBookmark(welEditable[0], oSnap.bookmark).select();
536
701
  };
537
702
 
538
703
  this.undo = function(welEditable) {
@@ -556,11 +721,33 @@
556
721
  * Editor
557
722
  */
558
723
  var Editor = function() {
724
+ // save current range
725
+ this.saveRange = function(welEditable) {
726
+ welEditable.data('range', range.create());
727
+ }
728
+
729
+ // restore lately range
730
+ this.restoreRange = function(welEditable) {
731
+ var rng = welEditable.data('range');
732
+ if (rng) { rng.select(); }
733
+ }
734
+
559
735
  //currentStyle
560
736
  var style = new Style();
561
737
  this.currentStyle = function(elTarget) {
562
- if (document.getSelection && document.getSelection().rangeCount == 0) { return null; }
563
- return style.current((new Range()), elTarget);
738
+ var rng = range.create();
739
+ return rng.isOnEditable() && style.current(rng, elTarget);
740
+ };
741
+
742
+ this.tab = function(welEditable) {
743
+ recordUndo(welEditable);
744
+ var rng = range.create();
745
+ var sNbsp = new Array(welEditable.data('tabsize') + 1).join('&nbsp;');
746
+ rng.insertNode($('<span id="noteTab">' + sNbsp + '</span>')[0]);
747
+ var welTab = $('#noteTab').removeAttr('id');
748
+ rng = range.create(welTab[0], 1);
749
+ rng.select();
750
+ dom.remove(welTab[0]);
564
751
  };
565
752
 
566
753
  // undo
@@ -583,7 +770,7 @@
583
770
  'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
584
771
  'insertOrderedList', 'insertUnorderedList',
585
772
  'indent', 'outdent', 'formatBlock', 'removeFormat',
586
- 'backColor', 'foreColor', 'insertImage', 'insertHorizontalRule'];
773
+ 'backColor', 'foreColor', 'insertHorizontalRule'];
587
774
 
588
775
  for (var idx = 0, len=aCmd.length; idx < len; idx ++) {
589
776
  this[aCmd[idx]] = function(sCmd) {
@@ -594,57 +781,78 @@
594
781
  }(aCmd[idx]);
595
782
  }
596
783
 
784
+ this.insertImage = function(welEditable, sUrl) {
785
+ async.loadImage(sUrl).done(function(image) {
786
+ recordUndo(welEditable);
787
+ var welImage = $('<img>').attr('src', sUrl);
788
+ welImage.css('width', Math.min(welEditable.width(), image.width));
789
+ range.create().insertNode(welImage[0]);
790
+ }).fail(function(image) {
791
+ var callbacks = welEditable.data('callbacks');
792
+ if (callbacks.onImageUploadError) {
793
+ callbacks.onImageUploadError();
794
+ }
795
+ });
796
+ };
797
+
597
798
  this.formatBlock = function(welEditable, sValue) {
598
- sValue = bMSIE ? "<" + sValue + ">" : sValue;
599
- document.execCommand("FormatBlock", false, sValue);
799
+ recordUndo(welEditable);
800
+ sValue = agent.bMSIE ? '<' + sValue + '>' : sValue;
801
+ document.execCommand('FormatBlock', false, sValue);
600
802
  };
601
803
 
602
804
  this.fontSize = function(welEditable, sValue) {
603
805
  recordUndo(welEditable);
604
806
  document.execCommand('fontSize', false, 3);
605
- // <font size='3'> to <font style='font-size={sValue}px;'>
606
- var welFont = welEditable.find('font[size=3]');
607
- welFont.removeAttr('size').css('font-size', sValue + 'px');
807
+ if (agent.bFF) {
808
+ // firefox: <font size="3"> to <span style='font-size={sValue}px;'>, buggy
809
+ welEditable.find('font[size=3]').removeAttr('size').css('font-size', sValue + 'px');
810
+ } else {
811
+ // chrome: <span style="font-size: medium"> to <span style='font-size={sValue}px;'>
812
+ welEditable.find('span').filter(function() {
813
+ return this.style.fontSize == 'medium';
814
+ }).css('font-size', sValue + 'px');
815
+ }
608
816
  };
609
817
 
610
818
  this.lineHeight = function(welEditable, sValue) {
611
819
  recordUndo(welEditable);
612
- style.stylePara(new Range(), {lineHeight: sValue});
820
+ style.stylePara(range.create(), {lineHeight: sValue});
613
821
  };
614
822
 
615
823
  this.unlink = function(welEditable) {
616
- var rng = new Range();
824
+ var rng = range.create();
617
825
  if (rng.isOnAnchor()) {
618
826
  recordUndo(welEditable);
619
827
  var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
620
- rng = new Range(elAnchor, 0, elAnchor, 1);
828
+ rng = range.create(elAnchor, 0, elAnchor, 1);
621
829
  rng.select();
622
830
  document.execCommand('unlink');
623
831
  }
624
832
  };
625
833
 
626
834
  this.setLinkDialog = function(welEditable, fnShowDialog) {
627
- var rng = new Range();
835
+ var rng = range.create();
628
836
  if (rng.isOnAnchor()) {
629
837
  var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
630
- rng = new Range(elAnchor, 0, elAnchor, 1);
838
+ rng = range.create(elAnchor, 0, elAnchor, 1);
631
839
  }
632
840
  fnShowDialog({
633
841
  range: rng,
634
842
  text: rng.toString(),
635
- url: rng.isOnAnchor() ? dom.ancestor(rng.sc, dom.isAnchor).href : ""
843
+ url: rng.isOnAnchor() ? dom.ancestor(rng.sc, dom.isAnchor).href : ''
636
844
  }, function(sLinkUrl) {
637
845
  rng.select(); recordUndo(welEditable);
638
846
 
639
- var bProtocol = sLinkUrl.toLowerCase().indexOf("://") !== -1;
640
- var sLinkUrlWithProtocol = bProtocol ? sLinkUrl : "http://" + sLinkUrl;
847
+ var bProtocol = sLinkUrl.toLowerCase().indexOf('://') !== -1;
848
+ var sLinkUrlWithProtocol = bProtocol ? sLinkUrl : 'http://' + sLinkUrl;
641
849
 
642
850
  //IE: createLink when range collapsed.
643
- if (bMSIE && rng.isCollapsed()) {
851
+ if (agent.bMSIE && rng.isCollapsed()) {
644
852
  rng.insertNode($('<A id="linkAnchor">' + sLinkUrl + '</A>')[0]);
645
- var welAnchor = $('#linkAnchor').removeAttr("id")
853
+ var welAnchor = $('#linkAnchor').removeAttr('id')
646
854
  .attr('href', sLinkUrlWithProtocol);
647
- rng = new Range(welAnchor[0], 0, welAnchor[0], 1);
855
+ rng = range.create(welAnchor[0], 0, welAnchor[0], 1);
648
856
  rng.select();
649
857
  } else {
650
858
  document.execCommand('createlink', false, sLinkUrlWithProtocol);
@@ -654,10 +862,11 @@
654
862
 
655
863
  this.color = function(welEditable, sObjColor) {
656
864
  var oColor = JSON.parse(sObjColor);
865
+ var foreColor = oColor.foreColor, backColor = oColor.backColor;
657
866
 
658
867
  recordUndo(welEditable);
659
- document.execCommand('foreColor', false, oColor.foreColor);
660
- document.execCommand('backColor', false, oColor.backColor);
868
+ if (foreColor) { document.execCommand('foreColor', false, foreColor); }
869
+ if (backColor) { document.execCommand('backColor', false, backColor); }
661
870
  };
662
871
 
663
872
  this.insertTable = function(welEditable, sDim) {
@@ -666,7 +875,7 @@
666
875
  var nCol = aDim[0], nRow = aDim[1];
667
876
 
668
877
  var aTD = [], sTD;
669
- var sWhitespace = bMSIE ? '&nbsp;' : '<br/>';
878
+ var sWhitespace = agent.bMSIE ? '&nbsp;' : '<br/>';
670
879
  for (var idxCol = 0; idxCol < nCol; idxCol++) {
671
880
  aTD.push('<td>' + sWhitespace + '</td>');
672
881
  }
@@ -678,10 +887,10 @@
678
887
  }
679
888
  sTR = aTR.join('');
680
889
  var sTable = '<table class="table table-bordered">' + sTR + '</table>';
681
- (new Range()).insertNode($(sTable)[0]);
890
+ range.create().insertNode($(sTable)[0]);
682
891
  };
683
892
 
684
- this.float = function(welEditable, sValue, elTarget) {
893
+ this.floatMe = function(welEditable, sValue, elTarget) {
685
894
  recordUndo(welEditable);
686
895
  elTarget.style.cssFloat = sValue;
687
896
  };
@@ -689,12 +898,17 @@
689
898
  this.resize = function(welEditable, sValue, elTarget) {
690
899
  recordUndo(welEditable);
691
900
  elTarget.style.width = welEditable.width() * sValue + 'px';
692
- elTarget.style.height = "";
901
+ elTarget.style.height = '';
693
902
  };
694
903
 
695
- this.resizeTo = function(pos, elTarget) {
696
- elTarget.style.width = pos.x + 'px';
697
- elTarget.style.height = pos.y + 'px';
904
+ this.resizeTo = function(pos, welTarget) {
905
+ var newRatio = pos.y / pos.x;
906
+ var ratio = welTarget.data('ratio');
907
+
908
+ welTarget.css({
909
+ width: ratio > newRatio ? pos.x : pos.y / ratio,
910
+ height: ratio > newRatio ? pos.x * ratio : pos.y
911
+ });
698
912
  };
699
913
  };
700
914
 
@@ -759,39 +973,55 @@
759
973
  var oColor = JSON.parse(welRecentColor.attr('data-value'));
760
974
  oColor[sEvent] = sValue;
761
975
  welRecentColor.attr('data-value', JSON.stringify(oColor));
762
- var sKey = sEvent === "backColor" ? 'background-color' : 'color';
976
+ var sKey = sEvent === 'backColor' ? 'background-color' : 'color';
763
977
  welRecentColor.find('i').css(sKey, sValue);
764
978
  };
979
+
980
+ this.updateFullscreen = function(welToolbar, bFullscreen) {
981
+ var welBtn = welToolbar.find('button[data-event="fullscreen"]');
982
+ welBtn[bFullscreen ? 'addClass' : 'removeClass']('active');
983
+ };
984
+ this.updateCodeview = function(welToolbar, bCodeview) {
985
+ var welBtn = welToolbar.find('button[data-event="codeview"]');
986
+ welBtn[bCodeview ? 'addClass' : 'removeClass']('active');
987
+ };
988
+
989
+ this.enable = function(welToolbar) {
990
+ welToolbar.find('button').not('button[data-event="codeview"]').removeClass('disabled');
991
+ };
992
+
993
+ this.disable = function(welToolbar) {
994
+ welToolbar.find('button').not('button[data-event="codeview"]').addClass('disabled');
995
+ };
765
996
  };
766
997
 
767
998
  /**
768
- * Popover
999
+ * Popover (http://getbootstrap.com/javascript/#popovers)
769
1000
  */
770
1001
  var Popover = function() {
1002
+ var showPopover = function(welPopover, elPlaceholder) {
1003
+ var welPlaceHolder = $(elPlaceholder);
1004
+ var pos = welPlaceHolder.position(), height = welPlaceHolder.height();
1005
+ welPopover.css({
1006
+ display: 'block',
1007
+ left: pos.left,
1008
+ top: pos.top + height
1009
+ });
1010
+ };
1011
+
771
1012
  this.update = function(welPopover, oStyle) {
772
1013
  var welLinkPopover = welPopover.find('.note-link-popover'),
773
1014
  welImagePopover = welPopover.find('.note-image-popover');
774
1015
  if (oStyle.anchor) {
775
1016
  var welAnchor = welLinkPopover.find('a');
776
1017
  welAnchor.attr('href', oStyle.anchor.href).html(oStyle.anchor.href);
777
-
778
- var rect = oStyle.anchor.getBoundingClientRect();
779
- welLinkPopover.css({
780
- display: 'block',
781
- left: rect.left,
782
- top: $(document).scrollTop() + rect.bottom
783
- });
1018
+ showPopover(welLinkPopover, oStyle.anchor);
784
1019
  } else {
785
1020
  welLinkPopover.hide();
786
1021
  }
787
1022
 
788
1023
  if (oStyle.image) {
789
- var rect = oStyle.image.getBoundingClientRect();
790
- welImagePopover.css({
791
- display: 'block',
792
- left: rect.left,
793
- top: $(document).scrollTop() + rect.bottom
794
- });
1024
+ showPopover(welImagePopover, oStyle.image);
795
1025
  } else {
796
1026
  welImagePopover.hide();
797
1027
  }
@@ -809,15 +1039,15 @@
809
1039
  this.update = function(welHandle, oStyle) {
810
1040
  var welSelection = welHandle.find('.note-control-selection');
811
1041
  if (oStyle.image) {
812
- var rect = oStyle.image.getBoundingClientRect();
1042
+ var welImage = $(oStyle.image);
1043
+ var pos = welImage.position();
1044
+ var szImage = {w: welImage.width(), h: welImage.height()};
813
1045
  welSelection.css({
814
1046
  display: 'block',
815
- left: rect.left + 'px',
816
- top: $(document).scrollTop() + rect.top + 'px',
817
- width: rect.width + 'px',
818
- height: rect.height + 'px'
1047
+ left: pos.left, top: pos.top,
1048
+ width: szImage.w, height: szImage.h
819
1049
  }).data('target', oStyle.image); // save current image element.
820
- var sSizing = rect.width + 'x' + rect.height;
1050
+ var sSizing = szImage.w + 'x' + szImage.h;
821
1051
  welSelection.find('.note-control-selection-info').text(sSizing);
822
1052
  } else {
823
1053
  welSelection.hide();
@@ -833,23 +1063,40 @@
833
1063
  * Dialog
834
1064
  */
835
1065
  var Dialog = function() {
836
- this.showImageDialog = function(welDialog, hDropImage, fnInsertImages) {
1066
+ this.showImageDialog = function(welDialog, hDropImage, fnInsertImages, fnInsertImage) {
837
1067
  var welImageDialog = welDialog.find('.note-image-dialog');
838
1068
  var welDropzone = welDialog.find('.note-dropzone'),
839
- welImageInput = welDialog.find('.note-image-input');
1069
+ welImageInput = welDialog.find('.note-image-input'),
1070
+ welImageUrl = welDialog.find('.note-image-url'),
1071
+ welImageBtn = welDialog.find('.note-image-btn');
840
1072
 
841
1073
  welImageDialog.on('shown.bs.modal', function(e) {
842
1074
  welDropzone.on('dragenter dragover dragleave', false);
843
1075
  welDropzone.on('drop', function(e) {
844
1076
  hDropImage(e); welImageDialog.modal('hide');
845
1077
  });
846
- welImageInput.on('change', function() {
1078
+ welImageInput.on('change', function(event) {
847
1079
  fnInsertImages(this.files); $(this).val('');
848
1080
  welImageDialog.modal('hide');
849
1081
  });
1082
+ welImageUrl.val('').keyup(function(event) {
1083
+ if (welImageUrl.val()) {
1084
+ welImageBtn.removeClass('disabled').attr('disabled', false);
1085
+ } else {
1086
+ welImageBtn.addClass('disabled').attr('disabled', true);
1087
+ }
1088
+ }).trigger('focus');
1089
+ welImageBtn.click(function(event) {
1090
+ welImageDialog.modal('hide');
1091
+ fnInsertImage(welImageUrl.val());
1092
+ event.preventDefault();
1093
+ });
850
1094
  }).on('hidden.bs.modal', function(e) {
851
1095
  welDropzone.off('dragenter dragover dragleave drop');
852
1096
  welImageInput.off('change');
1097
+ welImageDialog.off('shown.bs.modal hidden.bs.modal');
1098
+ welImageUrl.off('keyup');
1099
+ welImageBtn.off('click');
853
1100
  }).modal('show');
854
1101
  };
855
1102
 
@@ -868,7 +1115,7 @@
868
1115
  welLinkBtn.addClass('disabled').attr('disabled', true);
869
1116
  }
870
1117
 
871
- if (!linkInfo.text) { welLinkText.html(welLinkUrl.val()); };
1118
+ if (!linkInfo.text) { welLinkText.html(welLinkUrl.val()); }
872
1119
  }).trigger('focus');
873
1120
  welLinkBtn.click(function(event) {
874
1121
  welLinkDialog.modal('hide'); //hide and createLink (ie9+)
@@ -877,8 +1124,8 @@
877
1124
  });
878
1125
  }).on('hidden.bs.modal', function(e) {
879
1126
  welLinkUrl.off('keyup');
880
- welLinkDialog.off('shown.bs.modal hidden.bs.modal');
881
1127
  welLinkBtn.off('click');
1128
+ welLinkDialog.off('shown.bs.modal hidden.bs.modal');
882
1129
  }).modal('show');
883
1130
  };
884
1131
 
@@ -910,6 +1157,7 @@
910
1157
  editor: function() { return welEditor; },
911
1158
  toolbar: function() { return welEditor.find('.note-toolbar'); },
912
1159
  editable: function() { return welEditor.find('.note-editable'); },
1160
+ codable: function() { return welEditor.find('.note-codable'); },
913
1161
  statusbar: function() { return welEditor.find('.note-statusbar'); },
914
1162
  popover: function() { return welEditor.find('.note-popover'); },
915
1163
  handle: function() { return welEditor.find('.note-handle'); },
@@ -918,14 +1166,16 @@
918
1166
  };
919
1167
 
920
1168
  var hKeydown = function(event) {
921
- var bCmd = bMac ? event.metaKey : event.ctrlKey,
1169
+ var bCmd = agent.bMac ? event.metaKey : event.ctrlKey,
922
1170
  bShift = event.shiftKey, keyCode = event.keyCode;
923
1171
 
924
1172
  // optimize
925
1173
  var bExecCmd = (bCmd || bShift || keyCode === key.TAB);
926
1174
  var oLayoutInfo = (bExecCmd) ? makeLayoutInfo(event.target) : null;
927
1175
 
928
- if (bCmd && ((bShift && keyCode === key.Z) || keyCode === key.Y)) {
1176
+ if (keyCode === key.TAB && oLayoutInfo.editable().data('tabsize')) {
1177
+ editor.tab(oLayoutInfo.editable());
1178
+ } else if (bCmd && ((bShift && keyCode === key.Z) || keyCode === key.Y)) {
929
1179
  editor.redo(oLayoutInfo.editable());
930
1180
  } else if (bCmd && keyCode === key.Z) {
931
1181
  editor.undo(oLayoutInfo.editable());
@@ -940,6 +1190,7 @@
940
1190
  } else if (bCmd && keyCode === key.BACKSLACH) {
941
1191
  editor.removeFormat(oLayoutInfo.editable());
942
1192
  } else if (bCmd && keyCode === key.K) {
1193
+ oLayoutInfo.editable();
943
1194
  editor.setLinkDialog(oLayoutInfo.editable(), function(linkInfo, cb) {
944
1195
  dialog.showLinkDialog(oLayoutInfo.dialog(), linkInfo, cb);
945
1196
  });
@@ -979,14 +1230,21 @@
979
1230
  };
980
1231
 
981
1232
  var insertImages = function(welEditable, files) {
982
- welEditable.trigger('focus');
983
- $.each(files, function(idx, file) {
984
- var fileReader = new FileReader;
985
- fileReader.onload = function(event) {
986
- editor.insertImage(welEditable, event.target.result); // sURL
987
- };
988
- fileReader.readAsDataURL(file);
989
- });
1233
+ var callbacks = welEditable.data('callbacks');
1234
+ editor.restoreRange(welEditable);
1235
+ if (callbacks.onImageUpload) { // call custom handler
1236
+ callbacks.onImageUpload(files, editor, welEditable);
1237
+ } else {
1238
+ $.each(files, function(idx, file) {
1239
+ async.readFile(file).done(function(sURL) {
1240
+ editor.insertImage(welEditable, sURL);
1241
+ }).fail(function() {
1242
+ if (callbacks.onImageUploadError) {
1243
+ callbacks.onImageUploadError();
1244
+ }
1245
+ });
1246
+ });
1247
+ }
990
1248
  };
991
1249
 
992
1250
  var hDropImage = function(event) {
@@ -1001,12 +1259,11 @@
1001
1259
 
1002
1260
  var hMousedown = function(event) {
1003
1261
  //preventDefault Selection for FF, IE8+
1004
- if (dom.isImg(event.target)) { event.preventDefault(); };
1262
+ if (dom.isImg(event.target)) { event.preventDefault(); }
1005
1263
  };
1006
1264
 
1007
1265
  var hToolbarAndPopoverUpdate = function(event) {
1008
1266
  var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
1009
-
1010
1267
  var oStyle = editor.currentStyle(event.target);
1011
1268
  if (!oStyle) { return; }
1012
1269
  toolbar.update(oLayoutInfo.toolbar(), oStyle);
@@ -1027,19 +1284,25 @@
1027
1284
  welHandle = oLayoutInfo.handle(), welPopover = oLayoutInfo.popover(),
1028
1285
  welEditable = oLayoutInfo.editable(), welEditor = oLayoutInfo.editor();
1029
1286
 
1030
- var elTarget = welHandle.find('.note-control-selection').data('target');
1031
- var posStart = $(elTarget).offset(),
1287
+ var elTarget = welHandle.find('.note-control-selection').data('target'),
1288
+ welTarget = $(elTarget);
1289
+ var posStart = welTarget.offset(),
1032
1290
  scrollTop = $(document).scrollTop(), posDistance;
1291
+
1033
1292
  welEditor.on('mousemove', function(event) {
1034
1293
  posDistance = {x: event.clientX - posStart.left,
1035
1294
  y: event.clientY - (posStart.top - scrollTop)};
1036
- editor.resizeTo(posDistance, elTarget);
1295
+ editor.resizeTo(posDistance, welTarget);
1037
1296
  handle.update(welHandle, {image: elTarget});
1038
1297
  popover.update(welPopover, {image: elTarget});
1039
1298
  }).on('mouseup', function() {
1040
1299
  welEditor.off('mousemove').off('mouseup');
1041
1300
  });
1042
1301
 
1302
+ if (!welTarget.data('ratio')) { // original ratio.
1303
+ welTarget.data('ratio', welTarget.height() / welTarget.width());
1304
+ }
1305
+
1043
1306
  editor.recordUndo(welEditable);
1044
1307
  event.stopPropagation(); event.preventDefault();
1045
1308
  }
@@ -1060,11 +1323,12 @@
1060
1323
 
1061
1324
  var oLayoutInfo = makeLayoutInfo(event.target);
1062
1325
  var welDialog = oLayoutInfo.dialog(),
1063
- welEditable = oLayoutInfo.editable();
1326
+ welEditable = oLayoutInfo.editable(),
1327
+ welCodable = oLayoutInfo.codable();
1064
1328
 
1065
1329
  // before command
1066
1330
  var elTarget;
1067
- if ($.inArray(sEvent, ['resize', 'float']) !== -1) {
1331
+ if ($.inArray(sEvent, ['resize', 'floatMe']) !== -1) {
1068
1332
  var welHandle = oLayoutInfo.handle();
1069
1333
  var welSelection = welHandle.find('.note-control-selection');
1070
1334
  elTarget = welSelection.data('target');
@@ -1076,18 +1340,62 @@
1076
1340
  }
1077
1341
 
1078
1342
  // after command
1079
- if ($.inArray(sEvent, ["backColor", "foreColor"]) !== -1) {
1343
+ if ($.inArray(sEvent, ['backColor', 'foreColor']) !== -1) {
1080
1344
  toolbar.updateRecentColor(welBtn[0], sEvent, sValue);
1081
- } else if (sEvent === "showLinkDialog") { // popover to dialog
1345
+ } else if (sEvent === 'showLinkDialog') { // popover to dialog
1346
+ welEditable.focus();
1082
1347
  editor.setLinkDialog(welEditable, function(linkInfo, cb) {
1083
1348
  dialog.showLinkDialog(welDialog, linkInfo, cb);
1084
1349
  });
1085
- } else if (sEvent === "showImageDialog") {
1350
+ } else if (sEvent === 'showImageDialog') {
1351
+ welEditable.focus();
1086
1352
  dialog.showImageDialog(welDialog, hDropImage, function(files) {
1087
1353
  insertImages(welEditable, files);
1354
+ }, function(sUrl) {
1355
+ editor.restoreRange(welEditable);
1356
+ editor.insertImage(welEditable, sUrl);
1088
1357
  });
1089
- } else if (sEvent === "showHelpDialog") {
1358
+ } else if (sEvent === 'showHelpDialog') {
1090
1359
  dialog.showHelpDialog(welDialog);
1360
+ } else if (sEvent === 'fullscreen') {
1361
+ var welEditor = oLayoutInfo.editor();
1362
+ welEditor.toggleClass('fullscreen');
1363
+
1364
+ var welToolbar = oLayoutInfo.toolbar();
1365
+ var hResizeFullscreen = function() {
1366
+ var nHeight = $(window).height() - welToolbar.outerHeight();
1367
+ welEditable.css('height', nHeight);
1368
+ };
1369
+
1370
+ var bFullscreen = welEditor.hasClass('fullscreen');
1371
+ if (bFullscreen) {
1372
+ welEditable.data('orgHeight', welEditable.css('height'));
1373
+ $(window).resize(hResizeFullscreen).trigger('resize');
1374
+ } else {
1375
+ welEditable.css('height', welEditable.data('orgHeight'));
1376
+ $(window).off('resize');
1377
+ }
1378
+
1379
+ toolbar.updateFullscreen(welToolbar, bFullscreen);
1380
+ } else if (sEvent === 'codeview') {
1381
+ var welEditor = oLayoutInfo.editor(),
1382
+ welToolbar = oLayoutInfo.toolbar();
1383
+ welEditor.toggleClass('codeview');
1384
+
1385
+ var bCodeview = welEditor.hasClass('codeview');
1386
+ if (bCodeview) {
1387
+ welCodable.val(welEditable.html());
1388
+ welCodable.height(welEditable.height());
1389
+ toolbar.disable(welToolbar);
1390
+ welCodable.focus();
1391
+ } else {
1392
+ welEditable.html(welCodable.val());
1393
+ welEditable.height(welCodable.height());
1394
+ toolbar.enable(welToolbar);
1395
+ welEditable.focus();
1396
+ }
1397
+
1398
+ toolbar.updateCodeview(oLayoutInfo.toolbar(), bCodeview);
1091
1399
  }
1092
1400
 
1093
1401
  hToolbarAndPopoverUpdate(event);
@@ -1097,7 +1405,9 @@
1097
1405
  var EDITABLE_PADDING = 24;
1098
1406
  var hStatusbarMousedown = function(event) {
1099
1407
  var welDocument = $(document);
1100
- var welEditable = makeLayoutInfo(event.target).editable();
1408
+ var oLayoutInfo = makeLayoutInfo(event.target);
1409
+ var welEditable = oLayoutInfo.editable(),
1410
+ welCodable = oLayoutInfo.codable();
1101
1411
 
1102
1412
  var nEditableTop = welEditable.offset().top - welDocument.scrollTop();
1103
1413
  var hMousemove = function(event) {
@@ -1106,7 +1416,7 @@
1106
1416
  var hMouseup = function() {
1107
1417
  welDocument.unbind('mousemove', hMousemove)
1108
1418
  .unbind('mouseup', hMouseup);
1109
- }
1419
+ };
1110
1420
  welDocument.mousemove(hMousemove).mouseup(hMouseup);
1111
1421
  event.stopPropagation(); event.preventDefault();
1112
1422
  };
@@ -1145,6 +1455,12 @@
1145
1455
  welDimensionDisplay.html(dim.c + ' x ' + dim.r);
1146
1456
  };
1147
1457
 
1458
+ /**
1459
+ * Attach eventhandler
1460
+ * @param {object} oLayoutInfo - layout Informations
1461
+ * @param {object} options - user options include custom event handlers
1462
+ * @param {function} options.enter - enter key handler
1463
+ */
1148
1464
  this.attach = function(oLayoutInfo, options) {
1149
1465
  oLayoutInfo.editable.on('keydown', hKeydown);
1150
1466
  oLayoutInfo.editable.on('mousedown', hMousedown);
@@ -1168,8 +1484,13 @@
1168
1484
  var welCatcher = welToolbar.find('.note-dimension-picker-mousecatcher');
1169
1485
  welCatcher.on('mousemove', hDimensionPickerMove);
1170
1486
 
1171
- // callback
1172
- // init, enter, !change, !pasteBefore, !pasteAfter, focus, blur, keyup, keydown
1487
+ // save selection when focusout
1488
+ oLayoutInfo.editable.on('blur', function() {
1489
+ editor.saveRange(oLayoutInfo.editable);
1490
+ });
1491
+
1492
+ // basic event callbacks (lowercase)
1493
+ // enter, focus, blur, keyup, keydown
1173
1494
  if (options.onenter) {
1174
1495
  oLayoutInfo.editable.keypress(function(event) {
1175
1496
  if (event.keyCode === key.ENTER) { options.onenter(event);}
@@ -1177,11 +1498,18 @@
1177
1498
  }
1178
1499
  if (options.onfocus) { oLayoutInfo.editable.focus(options.onfocus); }
1179
1500
  if (options.onblur) { oLayoutInfo.editable.blur(options.onblur); }
1180
- if (options.onkeyup) { oLayoutInfo.editable.blur(options.onkeyup); }
1181
- if (options.onkeydown) { oLayoutInfo.editable.blur(options.onkeydown); }
1182
-
1183
- // TODO: callback for advanced features
1184
- // autosave, impageUpload, imageUploadError, fileUpload, fileUploadError
1501
+ if (options.onkeyup) { oLayoutInfo.editable.keyup(options.onkeyup); }
1502
+ if (options.onkeydown) { oLayoutInfo.editable.keydown(options.onkeydown); }
1503
+
1504
+ // callbacks for advanced features (camel)
1505
+ // All editor status will be saved on editable with jquery's data
1506
+ // for support multiple editor with singleton object.
1507
+ oLayoutInfo.editable.data('callbacks', {
1508
+ onChange: options.onChange, onAutoSave: options.onAutoSave,
1509
+ onPasteBefore: options.onPasteBefore, onPasteAfter: options.onPasteAfter,
1510
+ onImageUpload: options.onImageUpload, onImageUploadError: options.onImageUpload,
1511
+ onFileUpload: options.onFileUpload, onFileUploadError: options.onFileUpload
1512
+ });
1185
1513
  };
1186
1514
 
1187
1515
  this.dettach = function(oLayoutInfo) {
@@ -1200,11 +1528,11 @@
1200
1528
  var Renderer = function() {
1201
1529
  var aToolbarItem = {
1202
1530
  picture:
1203
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Picture" data-event="showImageDialog" tabindex="-1"><i class="icon-picture"></i></button>',
1531
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Picture" data-event="showImageDialog" tabindex="-1"><i class="fa fa-picture-o icon-picture"></i></button>',
1204
1532
  link:
1205
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Link" data-event="showLinkDialog" data-shortcut="Ctrl+K" data-mac-shortcut="⌘+K" tabindex="-1"><i class="icon-link"></i></button>',
1533
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Link" data-event="showLinkDialog" data-shortcut="Ctrl+K" data-mac-shortcut="⌘+K" tabindex="-1"><i class="fa fa-link icon-link"></i></button>',
1206
1534
  table:
1207
- '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" title="Table" data-toggle="dropdown" tabindex="-1"><i class="icon-table"></i> <span class="caret"></span></button>' +
1535
+ '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" title="Table" data-toggle="dropdown" tabindex="-1"><i class="fa fa-table icon-table"></i> <span class="caret"></span></button>' +
1208
1536
  '<ul class="dropdown-menu">' +
1209
1537
  '<div class="note-dimension-picker">' +
1210
1538
  '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
@@ -1214,7 +1542,7 @@
1214
1542
  '<div class="note-dimension-display"> 1 x 1 </div>' +
1215
1543
  '</ul>',
1216
1544
  style:
1217
- '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" title="Style" data-toggle="dropdown" tabindex="-1"><i class="icon-magic"></i> <span class="caret"></span></button>' +
1545
+ '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" title="Style" data-toggle="dropdown" tabindex="-1"><i class="fa fa-magic icon-magic"></i> <span class="caret"></span></button>' +
1218
1546
  '<ul class="dropdown-menu">' +
1219
1547
  '<li><a data-event="formatBlock" data-value="p">Normal</a></li>' +
1220
1548
  '<li><a data-event="formatBlock" data-value="blockquote"><blockquote>Quote</blockquote></a></li>' +
@@ -1229,18 +1557,18 @@
1229
1557
  fontsize:
1230
1558
  '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" data-toggle="dropdown" title="Font Size" tabindex="-1"><span class="note-current-fontsize">11</span> <b class="caret"></b></button>' +
1231
1559
  '<ul class="dropdown-menu">' +
1232
- '<li><a data-event="fontSize" data-value="8"><i class="icon-ok"></i> 8</a></li>' +
1233
- '<li><a data-event="fontSize" data-value="9"><i class="icon-ok"></i> 9</a></li>' +
1234
- '<li><a data-event="fontSize" data-value="10"><i class="icon-ok"></i> 10</a></li>' +
1235
- '<li><a data-event="fontSize" data-value="11"><i class="icon-ok"></i> 11</a></li>' +
1236
- '<li><a data-event="fontSize" data-value="12"><i class="icon-ok"></i> 12</a></li>' +
1237
- '<li><a data-event="fontSize" data-value="14"><i class="icon-ok"></i> 14</a></li>' +
1238
- '<li><a data-event="fontSize" data-value="18"><i class="icon-ok"></i> 18</a></li>' +
1239
- '<li><a data-event="fontSize" data-value="24"><i class="icon-ok"></i> 24</a></li>' +
1240
- '<li><a data-event="fontSize" data-value="36"><i class="icon-ok"></i> 36</a></li>' +
1560
+ '<li><a data-event="fontSize" data-value="8"><i class="fa fa-check icon-ok"></i> 8</a></li>' +
1561
+ '<li><a data-event="fontSize" data-value="9"><i class="fa fa-check icon-ok"></i> 9</a></li>' +
1562
+ '<li><a data-event="fontSize" data-value="10"><i class="fa fa-check icon-ok"></i> 10</a></li>' +
1563
+ '<li><a data-event="fontSize" data-value="11"><i class="fa fa-check icon-ok"></i> 11</a></li>' +
1564
+ '<li><a data-event="fontSize" data-value="12"><i class="fa fa-check icon-ok"></i> 12</a></li>' +
1565
+ '<li><a data-event="fontSize" data-value="14"><i class="fa fa-check icon-ok"></i> 14</a></li>' +
1566
+ '<li><a data-event="fontSize" data-value="18"><i class="fa fa-check icon-ok"></i> 18</a></li>' +
1567
+ '<li><a data-event="fontSize" data-value="24"><i class="fa fa-check icon-ok"></i> 24</a></li>' +
1568
+ '<li><a data-event="fontSize" data-value="36"><i class="fa fa-check icon-ok"></i> 36</a></li>' +
1241
1569
  '</ul>',
1242
1570
  color:
1243
- '<button type="button" class="btn btn-default btn-sm btn-small note-recent-color" title="Recent Color" data-event="color" data-value=\'{"foreColor":"black","backColor":"yellow"}\' tabindex="-1"><i class="icon-font" style="color:black;background-color:yellow;"></i></button>' +
1571
+ '<button type="button" class="btn btn-default btn-sm btn-small note-recent-color" title="Recent Color" data-event="color" data-value=\'{"backColor":"yellow"}\' tabindex="-1"><i class="fa fa-font icon-font" style="color:black;background-color:yellow;"></i></button>' +
1244
1572
  '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" title="More Color" data-toggle="dropdown" tabindex="-1">' +
1245
1573
  '<span class="caret"></span>' +
1246
1574
  '</button>' +
@@ -1248,82 +1576,87 @@
1248
1576
  '<li>' +
1249
1577
  '<div class="btn-group">' +
1250
1578
  '<div class="note-palette-title">BackColor</div>' +
1579
+ '<div class="note-color-reset" data-event="backColor" data-value="inherit" title="Transparent">Set transparent</div>' +
1251
1580
  '<div class="note-color-palette" data-target-event="backColor"></div>' +
1252
1581
  '</div>' +
1253
1582
  '<div class="btn-group">' +
1254
1583
  '<div class="note-palette-title">FontColor</div>' +
1584
+ '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="Reset">Reset to default</div>' +
1255
1585
  '<div class="note-color-palette" data-target-event="foreColor"></div>' +
1256
1586
  '</div>' +
1257
1587
  '</li>' +
1258
1588
  '</ul>',
1259
1589
  bold:
1260
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Bold" data-shortcut="Ctrl+B" data-mac-shortcut="⌘+B" data-event="bold" tabindex="-1"><i class="icon-bold"></i></button>',
1590
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Bold" data-shortcut="Ctrl+B" data-mac-shortcut="⌘+B" data-event="bold" tabindex="-1"><i class="fa fa-bold icon-bold"></i></button>',
1261
1591
  italic:
1262
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Italic" data-shortcut="Ctrl+I" data-mac-shortcut="⌘+I" data-event="italic" tabindex="-1"><i class="icon-italic"></i></button>',
1592
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Italic" data-shortcut="Ctrl+I" data-mac-shortcut="⌘+I" data-event="italic" tabindex="-1"><i class="fa fa-italic icon-italic"></i></button>',
1263
1593
  underline:
1264
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Underline" data-shortcut="Ctrl+U" data-mac-shortcut="⌘+U" data-event="underline" tabindex="-1"><i class="icon-underline"></i></button>',
1594
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Underline" data-shortcut="Ctrl+U" data-mac-shortcut="⌘+U" data-event="underline" tabindex="-1"><i class="fa fa-underline icon-underline"></i></button>',
1265
1595
  clear:
1266
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Remove Font Style" data-shortcut="Ctrl+\\" data-mac-shortcut="⌘+\\" data-event="removeFormat" tabindex="-1"><i class="icon-eraser"></i></button>',
1596
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Remove Font Style" data-shortcut="Ctrl+\\" data-mac-shortcut="⌘+\\" data-event="removeFormat" tabindex="-1"><i class="fa fa-eraser icon-eraser"></i></button>',
1267
1597
  ul:
1268
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Unordered list" data-shortcut="Ctrl+Shift+8" data-mac-shortcut="⌘+⇧+7" data-event="insertUnorderedList" tabindex="-1"><i class="icon-list-ul"></i></button>',
1598
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Unordered list" data-shortcut="Ctrl+Shift+8" data-mac-shortcut="⌘+⇧+7" data-event="insertUnorderedList" tabindex="-1"><i class="fa fa-list-ul icon-list-ul"></i></button>',
1269
1599
  ol:
1270
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Ordered list" data-shortcut="Ctrl+Shift+7" data-mac-shortcut="⌘+⇧+8" data-event="insertOrderedList" tabindex="-1"><i class="icon-list-ol"></i></button>',
1600
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Ordered list" data-shortcut="Ctrl+Shift+7" data-mac-shortcut="⌘+⇧+8" data-event="insertOrderedList" tabindex="-1"><i class="fa fa-list-ol icon-list-ol"></i></button>',
1271
1601
  paragraph:
1272
- '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" title="Paragraph" data-toggle="dropdown" tabindex="-1"><i class="icon-align-left"></i> <span class="caret"></span></button>' +
1602
+ '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" title="Paragraph" data-toggle="dropdown" tabindex="-1"><i class="fa fa-align-left icon-align-left"></i> <span class="caret"></span></button>' +
1273
1603
  '<ul class="dropdown-menu">' +
1274
1604
  '<li>' +
1275
1605
  '<div class="note-align btn-group">' +
1276
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Align left" data-shortcut="Ctrl+Shift+L" data-mac-shortcut="⌘+⇧+L" data-event="justifyLeft" tabindex="-1"><i class="icon-align-left"></i></button>' +
1277
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Align center" data-shortcut="Ctrl+Shift+E" data-mac-shortcut="⌘+⇧+E" data-event="justifyCenter" tabindex="-1"><i class="icon-align-center"></i></button>' +
1278
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Align right" data-shortcut="Ctrl+Shift+R" data-mac-shortcut="⌘+⇧+R" data-event="justifyRight" tabindex="-1"><i class="icon-align-right"></i></button>' +
1279
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Justify full" data-shortcut="Ctrl+Shift+J" data-mac-shortcut="⌘+⇧+J" data-event="justifyFull" tabindex="-1"><i class="icon-align-justify"></i></button>' +
1606
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Align left" data-shortcut="Ctrl+Shift+L" data-mac-shortcut="⌘+⇧+L" data-event="justifyLeft" tabindex="-1"><i class="fa fa-align-left icon-align-left"></i></button>' +
1607
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Align center" data-shortcut="Ctrl+Shift+E" data-mac-shortcut="⌘+⇧+E" data-event="justifyCenter" tabindex="-1"><i class="fa fa-align-center icon-align-center"></i></button>' +
1608
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Align right" data-shortcut="Ctrl+Shift+R" data-mac-shortcut="⌘+⇧+R" data-event="justifyRight" tabindex="-1"><i class="fa fa-align-right icon-align-right"></i></button>' +
1609
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Justify full" data-shortcut="Ctrl+Shift+J" data-mac-shortcut="⌘+⇧+J" data-event="justifyFull" tabindex="-1"><i class="fa fa-align-justify icon-align-justify"></i></button>' +
1280
1610
  '</div>' +
1281
1611
  '</li>' +
1282
1612
  '<li>' +
1283
1613
  '<div class="note-list btn-group">' +
1284
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Outdent" data-shortcut="Ctrl+[" data-mac-shortcut="⌘+[" data-event="outdent" tabindex="-1"><i class="icon-indent-left"></i></button>' +
1285
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Indent" data-shortcut="Ctrl+]" data-mac-shortcut="⌘+]" data-event="indent" tabindex="-1"><i class="icon-indent-right"></i></button>' +
1614
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Outdent" data-shortcut="Ctrl+[" data-mac-shortcut="⌘+[" data-event="outdent" tabindex="-1"><i class="fa fa-outdent icon-indent-left"></i></button>' +
1615
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Indent" data-shortcut="Ctrl+]" data-mac-shortcut="⌘+]" data-event="indent" tabindex="-1"><i class="fa fa-indent icon-indent-right"></i></button>' +
1286
1616
  '</li>' +
1287
1617
  '</ul>',
1288
1618
  height:
1289
- '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" data-toggle="dropdown" title="Line Height" tabindex="-1"><i class="icon-text-height"></i>&nbsp; <b class="caret"></b></button>' +
1619
+ '<button type="button" class="btn btn-default btn-sm btn-small dropdown-toggle" data-toggle="dropdown" title="Line Height" tabindex="-1"><i class="fa fa-text-height icon-text-height"></i>&nbsp; <b class="caret"></b></button>' +
1290
1620
  '<ul class="dropdown-menu">' +
1291
- '<li><a data-event="lineHeight" data-value="1.0"><i class="icon-ok"></i> 1.0</a></li>' +
1292
- '<li><a data-event="lineHeight" data-value="1.2"><i class="icon-ok"></i> 1.2</a></li>' +
1293
- '<li><a data-event="lineHeight" data-value="1.4"><i class="icon-ok"></i> 1.4</a></li>' +
1294
- '<li><a data-event="lineHeight" data-value="1.5"><i class="icon-ok"></i> 1.5</a></li>' +
1295
- '<li><a data-event="lineHeight" data-value="1.6"><i class="icon-ok"></i> 1.6</a></li>' +
1296
- '<li><a data-event="lineHeight" data-value="1.8"><i class="icon-ok"></i> 1.8</a></li>' +
1297
- '<li><a data-event="lineHeight" data-value="2.0"><i class="icon-ok"></i> 2.0</a></li>' +
1298
- '<li><a data-event="lineHeight" data-value="3.0"><i class="icon-ok"></i> 3.0</a></li>' +
1621
+ '<li><a data-event="lineHeight" data-value="1.0"><i class="fa fa-check icon-ok"></i> 1.0</a></li>' +
1622
+ '<li><a data-event="lineHeight" data-value="1.2"><i class="fa fa-check icon-ok"></i> 1.2</a></li>' +
1623
+ '<li><a data-event="lineHeight" data-value="1.4"><i class="fa fa-check icon-ok"></i> 1.4</a></li>' +
1624
+ '<li><a data-event="lineHeight" data-value="1.5"><i class="fa fa-check icon-ok"></i> 1.5</a></li>' +
1625
+ '<li><a data-event="lineHeight" data-value="1.6"><i class="fa fa-check icon-ok"></i> 1.6</a></li>' +
1626
+ '<li><a data-event="lineHeight" data-value="1.8"><i class="fa fa-check icon-ok"></i> 1.8</a></li>' +
1627
+ '<li><a data-event="lineHeight" data-value="2.0"><i class="fa fa-check icon-ok"></i> 2.0</a></li>' +
1628
+ '<li><a data-event="lineHeight" data-value="3.0"><i class="fa fa-check icon-ok"></i> 3.0</a></li>' +
1299
1629
  '</ul>',
1300
1630
  help:
1301
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Help" data-shortcut="Ctrl+/" data-mac-shortcut="⌘+/" data-event="showHelpDialog" tabindex="-1"><i class="icon-question"></i></button>'
1631
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Help" data-shortcut="Ctrl+/" data-mac-shortcut="⌘+/" data-event="showHelpDialog" tabindex="-1"><i class="fa fa-question icon-question"></i></button>',
1632
+ fullscreen:
1633
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Full Screen" data-event="fullscreen" tabindex="-1"><i class="fa fa-arrows-alt icon-fullscreen"></i></button>',
1634
+ codeview:
1635
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Code View" data-event="codeview" tabindex="-1"><i class="fa fa-code icon-code"></i></button>'
1302
1636
  };
1303
1637
  var sPopover = '<div class="note-popover">' +
1304
- '<div class="note-link-popover popover fade bottom in" style="display: none;">' +
1638
+ '<div class="note-link-popover popover bottom in" style="display: none;">' +
1305
1639
  '<div class="arrow"></div>' +
1306
1640
  '<div class="popover-content note-link-content">' +
1307
1641
  '<a href="http://www.google.com" target="_blank">www.google.com</a>&nbsp;&nbsp;' +
1308
1642
  '<div class="note-insert btn-group">' +
1309
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Edit" data-event="showLinkDialog" tabindex="-1"><i class="icon-edit"></i></button>' +
1310
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Unlink" data-event="unlink" tabindex="-1"><i class="icon-unlink"></i></button>' +
1643
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Edit" data-event="showLinkDialog" tabindex="-1"><i class="fa fa-edit icon-edit"></i></button>' +
1644
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Unlink" data-event="unlink" tabindex="-1"><i class="fa fa-unlink icon-unlink"></i></button>' +
1311
1645
  '</div>' +
1312
1646
  '</div>' +
1313
1647
  '</div>' +
1314
- '<div class="note-image-popover popover fade bottom in" style="display: none;">' +
1648
+ '<div class="note-image-popover popover bottom in" style="display: none;">' +
1315
1649
  '<div class="arrow"></div>' +
1316
1650
  '<div class="popover-content note-image-content">' +
1317
1651
  '<div class="btn-group">' +
1318
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Resize Full" data-event="resize" data-value="1" tabindex="-1"><i class="icon-resize-full"></i></button>' +
1319
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Resize Half" data-event="resize" data-value="0.5" tabindex="-1">½</button>' +
1320
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Resize Thrid" data-event="resize" data-value="0.33" tabindex="-1">⅓</button>' +
1321
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Resize Quarter" data-event="resize" data-value="0.25" tabindex="-1">¼</button>' +
1652
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Resize Full" data-event="resize" data-value="1" tabindex="-1"><span class="note-fontsize-10">100%</span> </button>' +
1653
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Resize Half" data-event="resize" data-value="0.5" tabindex="-1"><span class="note-fontsize-10">50%</span> </button>' +
1654
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Resize Quarter" data-event="resize" data-value="0.25" tabindex="-1"><span class="note-fontsize-10">25%</span> </button>' +
1322
1655
  '</div>' +
1323
1656
  '<div class="btn-group">' +
1324
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Float Left" data-event="float" data-value="left" tabindex="-1"><i class="icon-align-left"></i></button>' +
1325
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Float Right" data-event="float" data-value="right" tabindex="-1"><i class="icon-align-right"></i></button>' +
1326
- '<button type="button" class="btn btn-default btn-sm btn-small" title="Float None" data-event="float" data-value="none" tabindex="-1"><i class="icon-reorder"></i></button>' +
1657
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Float Left" data-event="floatMe" data-value="left" tabindex="-1"><i class="fa fa-align-left icon-align-left"></i></button>' +
1658
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Float Right" data-event="floatMe" data-value="right" tabindex="-1"><i class="fa fa-align-right icon-align-right"></i></button>' +
1659
+ '<button type="button" class="btn btn-default btn-sm btn-small" title="Float None" data-event="floatMe" data-value="none" tabindex="-1"><i class="fa fa-align-justify icon-align-justify"></i></button>' +
1327
1660
  '</div>' +
1328
1661
  '</div>' +
1329
1662
  '</div>' +
@@ -1404,21 +1737,30 @@
1404
1737
  '</tbody>' +
1405
1738
  '</table>';
1406
1739
 
1740
+ if (!agent.bMac) { // shortcut modifier for windows
1741
+ sShortcutTable = sShortcutTable.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
1742
+ }
1743
+
1407
1744
  var sDialog = '<div class="note-dialog">' +
1408
1745
  '<div class="note-image-dialog modal" aria-hidden="false">' +
1409
1746
  '<div class="modal-dialog">' +
1410
1747
  '<div class="modal-content">' +
1411
1748
  '<div class="modal-header">' +
1412
- '<button type="button" class="close" data-dismiss="modal" aria-hidden="true" tabindex="-1">×</button>' +
1749
+ '<button type="button" class="close" aria-hidden="true" tabindex="-1">×</button>' +
1413
1750
  '<h4>Insert Image</h4>' +
1414
1751
  '</div>' +
1415
1752
  '<div class="modal-body">' +
1416
1753
  '<div class="row-fluid">' +
1417
1754
  '<div class="note-dropzone span12">Drag an image here</div>' +
1418
- '<div>or if you prefer...</div>' +
1419
- '<input class="note-image-input" type="file" class="note-link-url" type="text" />' +
1755
+ '<h5>Select from files</h5>' +
1756
+ '<input class="note-image-input" type="file" name="files" accept="image/*" capture="camera" />' +
1757
+ '<h5>Image URL</h5>' +
1758
+ '<input class="note-image-url form-control span12" type="text" />' +
1420
1759
  '</div>' +
1421
1760
  '</div>' +
1761
+ '<div class="modal-footer">' +
1762
+ '<button href="#" class="btn btn-primary note-image-btn disabled" disabled="disabled">Insert</button>' +
1763
+ '</div>' +
1422
1764
  '</div>' +
1423
1765
  '</div>' +
1424
1766
  '</div>' +
@@ -1426,7 +1768,7 @@
1426
1768
  '<div class="modal-dialog">' +
1427
1769
  '<div class="modal-content">' +
1428
1770
  '<div class="modal-header">' +
1429
- '<button type="button" class="close" data-dismiss="modal" aria-hidden="true" tabindex="-1">×</button>' +
1771
+ '<button type="button" class="close" aria-hidden="true" tabindex="-1">×</button>' +
1430
1772
  '<h4>Edit Link</h4>' +
1431
1773
  '</div>' +
1432
1774
  '<div class="modal-body">' +
@@ -1443,20 +1785,20 @@
1443
1785
  '</div>' +
1444
1786
  '</div>' +
1445
1787
  '<div class="modal-footer">' +
1446
- '<a href="#" class="btn disabled note-link-btn" disabled="disabled">Link</a>' +
1788
+ '<button href="#" class="btn btn-primary note-link-btn disabled" disabled="disabled">Link</button>' +
1447
1789
  '</div>' +
1448
1790
  '</div>' +
1449
1791
  '</div>' +
1450
1792
  '</div>' +
1451
- '<div class="note-help-dialog modal fade" aria-hidden="false">' +
1793
+ '<div class="note-help-dialog modal" aria-hidden="false">' +
1452
1794
  '<div class="modal-dialog">' +
1453
1795
  '<div class="modal-content">' +
1454
1796
  '<div class="modal-body">' +
1455
1797
  '<div class="modal-background">' +
1456
- '<a class="modal-close pull-right" data-dismiss="modal" aria-hidden="true" tabindex="-1">Close</a>' +
1798
+ '<a class="modal-close pull-right" aria-hidden="true" tabindex="-1">Close</a>' +
1457
1799
  '<div class="title">Keyboard shortcuts</div>' +
1458
1800
  sShortcutTable +
1459
- '<p class="text-center"><a href="//hackerwins.github.io/summernote/" target="_blank">Summernote v0.3</a> · <a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · <a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a></p>' +
1801
+ '<p class="text-center"><a href="//hackerwins.github.io/summernote/" target="_blank">Summernote v0.4</a> · <a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · <a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a></p>' +
1460
1802
  '</div>' +
1461
1803
  '</div>' +
1462
1804
  '</div>' +
@@ -1467,15 +1809,15 @@
1467
1809
  var createTooltip = function(welContainer, sPlacement) {
1468
1810
  welContainer.find('button').each(function(i, elBtn) {
1469
1811
  var welBtn = $(elBtn);
1470
- var sShortcut = welBtn.attr(bMac ? 'data-mac-shortcut':'data-shortcut');
1471
- if (sShortcut) { welBtn.attr('title', function(i, v) { return v + ' (' + sShortcut + ')'}); }
1812
+ var sShortcut = welBtn.attr(agent.bMac ? 'data-mac-shortcut': 'data-shortcut');
1813
+ if (sShortcut) { welBtn.attr('title', function(i, v) { return v + ' (' + sShortcut + ')'; }); }
1472
1814
  //bootstrap tooltip on btn-group bug: https://github.com/twitter/bootstrap/issues/5687
1473
1815
  }).tooltip({container: 'body', placement: sPlacement || 'top'});
1474
1816
  };
1475
1817
 
1476
1818
  // pallete colors
1477
1819
  var aaColor = [
1478
- ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#EFF7F7', '#FFFFFF'],
1820
+ ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
1479
1821
  ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
1480
1822
  ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
1481
1823
  ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
@@ -1510,7 +1852,7 @@
1510
1852
  };
1511
1853
 
1512
1854
  // createLayout
1513
- var createLayout = this.createLayout = function(welHolder, nHeight, nTabIndex, aToolbarSetting) {
1855
+ var createLayout = this.createLayout = function(welHolder, nHeight, nTabsize, aToolbarSetting) {
1514
1856
  //already created
1515
1857
  if (welHolder.next().hasClass('note-editor')) { return; }
1516
1858
 
@@ -1524,11 +1866,21 @@
1524
1866
 
1525
1867
  //03. create Editable
1526
1868
  var welEditable = $('<div class="note-editable" contentEditable="true"></div>').prependTo(welEditor);
1527
- if (nTabIndex) { welEditable.attr('tabIndex', nTabIndex); }
1528
1869
  if (nHeight) { welEditable.height(nHeight); }
1870
+ if (nTabsize) {
1871
+ welEditable.data('tabsize', nTabsize);
1872
+ }
1529
1873
 
1530
- welEditable.html(welHolder.html());
1874
+ welEditable.html(dom.html(welHolder));
1531
1875
  welEditable.data('NoteHistory', new History());
1876
+
1877
+ //031. create codable
1878
+ var welCodable = $('<textarea class="note-codable"></textarea>').prependTo(welEditor);
1879
+
1880
+ //032. set styleWithCSS for backColor / foreColor clearing with 'inherit'.
1881
+ setTimeout(function() { // protect FF Error: NS_ERROR_FAILURE: Failure
1882
+ document.execCommand('styleWithCSS', 0, true);
1883
+ });
1532
1884
 
1533
1885
  //04. create Toolbar
1534
1886
  var sToolbar = '';
@@ -1539,7 +1891,7 @@
1539
1891
  sToolbar += aToolbarItem[group[1][i]];
1540
1892
  }
1541
1893
  sToolbar += '</div>';
1542
- };
1894
+ }
1543
1895
 
1544
1896
  sToolbar = '<div class="note-toolbar btn-toolbar">' + sToolbar + '</div>';
1545
1897
 
@@ -1555,8 +1907,11 @@
1555
1907
  $(sHandle).prependTo(welEditor);
1556
1908
 
1557
1909
  //07. create Dialog
1558
- $(sDialog).prependTo(welEditor);
1559
-
1910
+ var welDialog = $(sDialog).prependTo(welEditor);
1911
+ welDialog.find('button.close, a.modal-close').click(function(event) {
1912
+ $(this).closest('.modal').modal('hide');
1913
+ });
1914
+
1560
1915
  //08. Editor/Holder switch
1561
1916
  welEditor.insertAfter(welHolder);
1562
1917
  welHolder.hide();
@@ -1571,6 +1926,7 @@
1571
1926
  editor: welEditor,
1572
1927
  toolbar: welEditor.find('.note-toolbar'),
1573
1928
  editable: welEditor.find('.note-editable'),
1929
+ codable: welEditor.find('.note-codable'),
1574
1930
  statusbar: welEditor.find('.note-statusbar'),
1575
1931
  popover: welEditor.find('.note-popover'),
1576
1932
  handle: welEditor.find('.note-handle'),
@@ -1608,6 +1964,7 @@
1608
1964
  ['height', ['height']],
1609
1965
  ['table', ['table']],
1610
1966
  ['insert', ['link', 'picture']],
1967
+ ['view', ['fullscreen', 'codeview']],
1611
1968
  ['help', ['help']]
1612
1969
  ]
1613
1970
  }, options );
@@ -1616,7 +1973,7 @@
1616
1973
  var welHolder = $(elHolder);
1617
1974
 
1618
1975
  // createLayout with options
1619
- renderer.createLayout(welHolder, options.height, options.tabIndex, options.toolbar);
1976
+ renderer.createLayout(welHolder, options.height, options.tabsize, options.toolbar);
1620
1977
 
1621
1978
  var info = renderer.layoutInfoFromHolder(welHolder);
1622
1979
  eventHandler.attach(info, options);
@@ -1628,17 +1985,20 @@
1628
1985
  }
1629
1986
  if (this.length > 0 && options.oninit) { // callback on init
1630
1987
  options.oninit();
1631
- };
1988
+ }
1632
1989
  },
1633
1990
  // get the HTML contents of note or set the HTML contents of note.
1634
1991
  code: function(sHTML) {
1635
1992
  //get the HTML contents
1636
1993
  if (sHTML === undefined) {
1637
- return this.map(function(idx, elHolder) {
1638
- var info = renderer.layoutInfoFromHolder($(elHolder));
1639
- var bEditable = !!(info && info.editable);
1640
- return bEditable ? info.editable.html() : $(elHolder).html();
1641
- });
1994
+ var welHolder = this.first();
1995
+ if (welHolder.length === 0) { return; }
1996
+ var info = renderer.layoutInfoFromHolder(welHolder);
1997
+ if (!!(info && info.editable)) {
1998
+ var bCodeview = info.editor.hasClass('codeview');
1999
+ return bCodeview ? info.codable.val() : info.editable.html();
2000
+ }
2001
+ return welHolder.html();
1642
2002
  }
1643
2003
 
1644
2004
  // set the HTML contents
@@ -1660,13 +2020,13 @@
1660
2020
  },
1661
2021
  // inner object for test
1662
2022
  summernoteInner: function() {
1663
- return { dom: dom, list: list, func: func, Range: Range };
2023
+ return { dom: dom, list: list, func: func, range: range };
1664
2024
  }
1665
2025
  });
1666
- })(jQuery); // jQuery
2026
+ })(window.jQuery); // jQuery
1667
2027
 
1668
- //Array.prototype.reduce fallback
1669
- //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
2028
+ // Array.prototype.reduce fallback
2029
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
1670
2030
  if ('function' !== typeof Array.prototype.reduce) {
1671
2031
  Array.prototype.reduce = function(callback, opt_initialValue) {
1672
2032
  'use strict';