summernote-rails 0.3.0 → 0.4.0

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