summernote-rails 0.2.1.1 → 0.2.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ac7b0b4ade4d7fa3a4ecf578f5ffd7a55b9b4fa
4
- data.tar.gz: 973bac1f0f30d5325fcabcc0bfad30cbc9525988
3
+ metadata.gz: 66f224d348d0825289874d387f9e713702c3fc50
4
+ data.tar.gz: 2bd808d70a67c2eb3aebbde46c8eb8c992d455e4
5
5
  SHA512:
6
- metadata.gz: d41497e55e2cb506c8a4dccbafa96815a48592cc24215a96c32be083ef6a7350e9b7cc7759dfdbc348f8bbd75e48fc9c17f98d17553429d7126aea5240bf32cc
7
- data.tar.gz: cc43c11d474a7b5b751c3ff4b2472530e95dfd1e0bc70303876a9fd50aad9a15ed26f6f6a0251913e8201400ad01fb994e1decb21b6dc9caf9ec30a62ff1605f
6
+ metadata.gz: ad521e2aa228d8e545cc3c457c50fdea2f3a18184ef7afe9fc244c39d978543190e610b3a14c1d7fe8664b4b8b06908c209542f4e1daa152db72a7d0dcf18eb1
7
+ data.tar.gz: 343f0fa8fc0298feefc4d7db5086831bd3fde65c6a1e4ed12833a7eaf7fe642ca68798fc5f1abf3a8c3442d8df440722f724a84469f65fd36e2f977701e48b42
data/README.md CHANGED
@@ -6,6 +6,8 @@ Website of Summernote, https://github.com/hackerwins/summernote
6
6
 
7
7
  The version of summernote-rails is matched for the summernote editor.
8
8
 
9
+ [![Gem Version](https://badge.fury.io/rb/summernote-rails.png)](http://badge.fury.io/rb/summernote-rails)
10
+
9
11
  ## Installation
10
12
 
11
13
  Add the following gems to your application's Gemfile:
@@ -14,10 +16,10 @@ Add the following gems to your application's Gemfile:
14
16
  gem 'simple_form', github: 'plataformatec/simple_form', tag: 'v3.0.0.beta1'
15
17
  # for Rails 3.x
16
18
  gem 'simple_form'
17
-
19
+
18
20
  gem 'bootstrap-sass'
19
21
  gem 'font-awesome-rails'
20
- gem 'summernote-rails', github: 'rorlab/summernote-rails'
22
+ gem 'summernote-rails'
21
23
 
22
24
  And then execute:
23
25
 
@@ -83,6 +85,16 @@ In app/views/posts/_form.html.erb, you should add 'summernote' class name to the
83
85
 
84
86
  That's it.
85
87
 
88
+
89
+ ## Changelogs
90
+
91
+ - v0.2.1.2 : Added InsertHorizontalRule(Cmd+Enter) in summernote editor
92
+ Added summernote.js
93
+ - v0.2.1.1 : available for Rails >= 3.1
94
+ - v0.2.1 : bugfixed file uploading
95
+ - v0.2.0 : available for Rails v4.0
96
+
97
+
86
98
  ## Contributing
87
99
 
88
100
  1. Fork it
@@ -1,5 +1,5 @@
1
1
  module Summernote
2
2
  module Rails
3
- VERSION = "0.2.1.1"
3
+ VERSION = "0.2.1.2"
4
4
  end
5
5
  end
@@ -0,0 +1,1365 @@
1
+ /**
2
+ * summernote.js
3
+ * (c) 2013~ Alan Hong
4
+ * summernote may be freely distributed under the MIT license./
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;
12
+
13
+ /**
14
+ * func utils (for high-order func's arg)
15
+ */
16
+ var func = function() {
17
+ var eq = function(nodeA) {
18
+ return function(nodeB) { return nodeA === nodeB; };
19
+ };
20
+ var eq2 = function(nodeA, nodeB) { return nodeA === nodeB; };
21
+ var fail = function() { return false; };
22
+ return { eq: eq, eq2: eq2, fail: fail };
23
+ }();
24
+
25
+ /**
26
+ * list utils
27
+ */
28
+ var list = function() {
29
+ var head = function(array) { return array[0]; };
30
+ var last = function(array) { return array[array.length - 1]; };
31
+ var initial = function(array) { return array.slice(0, array.length - 1); };
32
+ var tail = function(array) { return array.slice(1); };
33
+
34
+ var clusterBy = function(array, fn) {
35
+ if (array.length === 0) { return []; }
36
+ var aTail = tail(array);
37
+ return aTail.reduce(function (memo, v) {
38
+ var aLast = last(memo);
39
+ if (fn(last(aLast), v)) {
40
+ aLast[aLast.length] = v;
41
+ } else {
42
+ memo[memo.length] = [v];
43
+ }
44
+ return memo;
45
+ }, [[head(array)]]);
46
+ };
47
+
48
+ var compact = function(array) {
49
+ var aResult = [];
50
+ for (var idx = 0, sz = array.length; idx < sz; idx ++) {
51
+ if (array[idx]) { aResult.push(array[idx]); };
52
+ };
53
+ return aResult;
54
+ };
55
+
56
+ return { head: head, last: last, initial: initial, tail: tail,
57
+ compact: compact, clusterBy: clusterBy };
58
+ }();
59
+
60
+ /**
61
+ * dom utils
62
+ */
63
+ var dom = function() {
64
+ var makePredByNodeName = function(sNodeName) {
65
+ // nodeName of element is always uppercase.
66
+ return function(node) { return node && node.nodeName === sNodeName; };
67
+ };
68
+
69
+ var isPara = function(node) {
70
+ return node && /^P|^LI|^H[1-7]/.test(node.nodeName);
71
+ };
72
+
73
+ var isList = function(node) {
74
+ return node && /^UL|^OL/.test(node.nodeName);
75
+ };
76
+
77
+ var isEditable = function(node) {
78
+ return node && $(node).hasClass('note-editable');
79
+ };
80
+
81
+ var isControlSizing = function(node) {
82
+ return node && $(node).hasClass('note-control-sizing');
83
+ };
84
+
85
+ // ancestor: find nearest ancestor predicate hit
86
+ var ancestor = function(node, pred) {
87
+ while (node) {
88
+ if (pred(node)) { return node; }
89
+ node = node.parentNode;
90
+ }
91
+ return null;
92
+ };
93
+
94
+ // listAncestor: listing ancestor nodes (until predicate hit: optional)
95
+ var listAncestor = function(node, pred) {
96
+ pred = pred || func.fail;
97
+
98
+ var aAncestor = [];
99
+ ancestor(node, function(el) {
100
+ aAncestor.push(el);
101
+ return pred(el);
102
+ });
103
+ return aAncestor;
104
+ };
105
+
106
+ // commonAncestor: find commonAncestor
107
+ var commonAncestor = function(nodeA, nodeB) {
108
+ var aAncestor = listAncestor(nodeA);
109
+ for (var n = nodeB; n; n = n.parentNode) {
110
+ if ($.inArray(n, aAncestor) > -1) { return n; }
111
+ }
112
+ return null; // difference document area
113
+ };
114
+
115
+ // listBetween: listing all Nodes between nodeA and nodeB
116
+ // FIXME: nodeA and nodeB must be sorted, use comparePoints later.
117
+ var listBetween = function(nodeA, nodeB) {
118
+ var aNode = [];
119
+ var elAncestor = commonAncestor(nodeA, nodeB);
120
+ //TODO: IE8, createNodeIterator
121
+ var iterator = document.createNodeIterator(elAncestor,
122
+ NodeFilter.SHOW_ALL, null,
123
+ false);
124
+ var node, bStart = false;
125
+ while (node = iterator.nextNode()) {
126
+ if (nodeA === node) { bStart = true; }
127
+ if (bStart) { aNode.push(node); }
128
+ if (nodeB === node) { break; }
129
+ }
130
+ return aNode;
131
+ };
132
+
133
+ // listNext: listing nextSiblings (until predicate hit: optional)
134
+ var listNext = function(node, pred) {
135
+ pred = pred || func.fail;
136
+
137
+ var aNext = [];
138
+ while (node) {
139
+ aNext.push(node);
140
+ if (node === pred) { break; }
141
+ node = node.nextSibling;
142
+ };
143
+ return aNext;
144
+ };
145
+
146
+ // insertAfter: insert node after preceding
147
+ var insertAfter = function(node, preceding) {
148
+ var next = preceding.nextSibling, parent = preceding.parentNode;
149
+ if (next) {
150
+ parent.insertBefore(node, next);
151
+ } else {
152
+ parent.appendChild(node);
153
+ }
154
+ return node;
155
+ };
156
+
157
+ // appends: append children
158
+ var appends = function(node, aChild) {
159
+ $.each(aChild, function(idx, child) {
160
+ node.appendChild(child);
161
+ });
162
+ return node;
163
+ };
164
+
165
+ var isText = makePredByNodeName('#text');
166
+
167
+ // length: size of element.
168
+ var length = function(node) {
169
+ if (isText(node)) { return node.nodeValue.length; }
170
+ return node.childNodes.length;
171
+ };
172
+
173
+ // position: offset from parent.
174
+ var position = function(node) {
175
+ var offset = 0;
176
+ while (node = node.previousSibling) { offset += 1; }
177
+ return offset;
178
+ };
179
+
180
+ // makeOffsetPath: return offsetPath(offset list) from ancestor
181
+ var makeOffsetPath = function(ancestor, node) {
182
+ var aAncestor = list.initial(listAncestor(node, func.eq(ancestor)));
183
+ return $.map(aAncestor, position).reverse();
184
+ };
185
+
186
+ // fromtOffsetPath: return element from offsetPath(offset list)
187
+ var fromOffsetPath = function(ancestor, aOffset) {
188
+ var current = ancestor;
189
+ for (var i = 0, sz = aOffset.length; i < sz; i++) {
190
+ current = current.childNodes[aOffset[i]];
191
+ }
192
+ return current;
193
+ };
194
+
195
+ // splitData: split element or #text
196
+ var splitData = function(node, offset) {
197
+ if (offset === 0) { return node; }
198
+ if (offset >= length(node)) { return node.nextSibling; }
199
+
200
+ // splitText
201
+ if (isText(node)) { return node.splitText(offset); }
202
+
203
+ // splitElement
204
+ var child = node.childNodes[offset];
205
+ node = insertAfter(node.cloneNode(false), node);
206
+ return appends(node, listNext(child));
207
+ };
208
+
209
+ // split: split dom tree by boundaryPoint(pivot and offset)
210
+ var split = function(root, pivot, offset) {
211
+ var aAncestor = listAncestor(pivot, func.eq(root));
212
+ if (aAncestor.length === 1) {
213
+ return splitData(pivot, offset);
214
+ }
215
+ return aAncestor.reduce(function(node, parent) {
216
+ var clone = parent.cloneNode(false);
217
+ insertAfter(clone, parent);
218
+ if (node === pivot) {
219
+ node = splitData(node, offset);
220
+ }
221
+ appends(clone, listNext(node));
222
+ return clone;
223
+ });
224
+ };
225
+
226
+ return {
227
+ isText: isText,
228
+ isPara: isPara, isList: isList,
229
+ isEditable: isEditable, isControlSizing: isControlSizing,
230
+ isAnchor: makePredByNodeName('A'),
231
+ isDiv: makePredByNodeName('DIV'), isSpan: makePredByNodeName('SPAN'),
232
+ isB: makePredByNodeName('B'), isU: makePredByNodeName('U'),
233
+ isS: makePredByNodeName('S'), isI: makePredByNodeName('I'),
234
+ isImg: makePredByNodeName('IMG'),
235
+ ancestor: ancestor, listAncestor: listAncestor, listNext: listNext,
236
+ commonAncestor: commonAncestor, listBetween: listBetween,
237
+ insertAfter: insertAfter, position: position,
238
+ makeOffsetPath: makeOffsetPath, fromOffsetPath: fromOffsetPath,
239
+ split: split
240
+ };
241
+ }();
242
+
243
+ /**
244
+ * Range
245
+ * {startContainer, startOffset, endContainer, endOffset}
246
+ * create Range Object From arguments or Browser Selection
247
+ */
248
+ var bW3CRangeSupport = !!document.createRange;
249
+ var Range = function(sc, so, ec, eo) {
250
+ if (arguments.length === 0) { // from Browser Selection
251
+ if (document.getSelection) { // webkit, firefox
252
+ var nativeRng = document.getSelection().getRangeAt(0);
253
+ sc = nativeRng.startContainer, so = nativeRng.startOffset,
254
+ ec = nativeRng.endContainer, eo = nativeRng.endOffset;
255
+ } // TODO: handle IE8+ TextRange
256
+ }
257
+
258
+ this.sc = sc; this.so = so;
259
+ this.ec = ec; this.eo = eo;
260
+
261
+ // nativeRange: get nativeRange from sc, so, ec, eo
262
+ var nativeRange = function() {
263
+ if (bW3CRangeSupport) {
264
+ var range = document.createRange();
265
+ range.setStart(sc, so);
266
+ range.setEnd(ec, eo);
267
+ return range;
268
+ } // TODO: handle IE8+ TextRange
269
+ };
270
+
271
+ // select: update visible range
272
+ this.select = function() {
273
+ var nativeRng = nativeRange();
274
+ if (bW3CRangeSupport) {
275
+ var selection = document.getSelection();
276
+ if (selection.rangeCount > 0) { selection.removeAllRanges(); }
277
+ selection.addRange(nativeRng);
278
+ } // TODO: handle IE8+ TextRange
279
+ };
280
+
281
+ // listPara: listing paragraphs on range
282
+ this.listPara = function() {
283
+ var aNode = dom.listBetween(sc, ec);
284
+ var aPara = list.compact($.map(aNode, function(node) {
285
+ return dom.ancestor(node, dom.isPara);
286
+ }));
287
+ return $.map(list.clusterBy(aPara, func.eq2), list.head);
288
+ };
289
+
290
+ // isOnList: judge whether range is on list node or not
291
+ this.isOnList = function() {
292
+ var elStart = dom.ancestor(sc, dom.isList),
293
+ elEnd = dom.ancestor(ec, dom.isList);
294
+ return elStart && (elStart === elEnd);
295
+ };
296
+
297
+ // isOnAnchor: judge whether range is on anchor node or not
298
+ this.isOnAnchor = function() {
299
+ var elStart = dom.ancestor(sc, dom.isAnchor),
300
+ elEnd = dom.ancestor(ec, dom.isAnchor);
301
+ return elStart && (elStart === elEnd);
302
+ };
303
+
304
+ // isCollapsed: judge whether range was collapsed
305
+ this.isCollapsed = function() { return sc === ec && so === eo; };
306
+
307
+ // insertNode
308
+ this.insertNode = function(node) {
309
+ var nativeRng = nativeRange();
310
+ if (bW3CRangeSupport) {
311
+ nativeRng.insertNode(node);
312
+ } // TODO: IE8
313
+ };
314
+
315
+ // surroundContents
316
+ this.surroundContents = function(sNodeName) {
317
+ var node = $('<' + sNodeName + ' />')[0];
318
+ var nativeRng = nativeRange();
319
+ if (bW3CRangeSupport) {
320
+ nativeRng.surroundContents(node);
321
+ } // TODO: IE8
322
+
323
+ return node;
324
+ };
325
+
326
+ this.toString = function() {
327
+ var nativeRng = nativeRange();
328
+ if (bW3CRangeSupport) {
329
+ return nativeRng.toString();
330
+ } // TODO: IE8
331
+ };
332
+
333
+ //bookmark: offsetPath bookmark
334
+ this.bookmark = function(elEditable) {
335
+ return {
336
+ s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
337
+ e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
338
+ };
339
+ };
340
+ };
341
+
342
+ // createRangeFromBookmark
343
+ var createRangeFromBookmark = function(elEditable, bookmark) {
344
+ return new Range(dom.fromOffsetPath(elEditable, bookmark.s.path),
345
+ bookmark.s.offset,
346
+ dom.fromOffsetPath(elEditable, bookmark.e.path),
347
+ bookmark.e.offset);
348
+ };
349
+
350
+ /**
351
+ * Style
352
+ */
353
+ var Style = function() {
354
+ // font level style
355
+ this.styleFont = function(rng, oStyle) {
356
+ //TODO: complete styleFont later only works for webkit
357
+ //rng.splitInline();
358
+ var elSpan = rng.surroundContents('span');
359
+ $.each(oStyle, function(sKey, sValue) {
360
+ elSpan.style[sKey] = sValue;
361
+ });
362
+ };
363
+
364
+ // para level style
365
+ this.stylePara = function(rng, oStyle) {
366
+ var aPara = rng.listPara();
367
+ $.each(aPara, function(idx, elPara) {
368
+ $.each(oStyle, function(sKey, sValue) {
369
+ elPara.style[sKey] = sValue;
370
+ });
371
+ });
372
+ };
373
+
374
+ // get current style, elTarget: target element on event.
375
+ this.current = function(rng, elTarget) {
376
+ var welCont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
377
+ var oStyle = welCont.css(['font-size', 'font-weight', 'font-style',
378
+ 'text-decoration', 'text-align',
379
+ 'list-style-type', 'line-height']) || {};
380
+
381
+ oStyle['font-size'] = parseInt(oStyle['font-size']);
382
+
383
+ // FF font-weight patch(number to 'bold' or 'normal')
384
+ if (!isNaN(parseInt(oStyle['font-weight']))) {
385
+ oStyle['font-weight'] = oStyle['font-weight'] > 400 ? 'bold' : 'normal';
386
+ }
387
+
388
+ // list-style-type to list-style(unordered, ordered)
389
+ if (!rng.isOnList()) {
390
+ oStyle['list-style'] = 'none';
391
+ } else {
392
+ var aOrderedType = ['circle', 'disc', 'disc-leading-zero', 'square'];
393
+ var bUnordered = $.inArray(oStyle['list-style-type'], aOrderedType) > -1;
394
+ oStyle['list-style'] = bUnordered ? 'unordered' : 'ordered';
395
+ }
396
+
397
+ var elPara = dom.ancestor(rng.sc, dom.isPara);
398
+ if (elPara && elPara.style['line-height']) {
399
+ oStyle['line-height'] = elPara.style.lineHeight;
400
+ } else {
401
+ var lineHeight = parseInt(oStyle['line-height']) / parseInt(oStyle['font-size']);
402
+ oStyle['line-height'] = lineHeight.toFixed(1);
403
+ }
404
+
405
+ oStyle.image = dom.isImg(elTarget) && elTarget;
406
+ oStyle.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
407
+ oStyle.aAncestor = dom.listAncestor(rng.sc, dom.isEditable);
408
+
409
+ return oStyle;
410
+ }
411
+ };
412
+
413
+ /**
414
+ * History
415
+ */
416
+ var History = function() {
417
+ var aUndo = [], aRedo = [];
418
+
419
+ var makeSnap = function(welEditable) {
420
+ var elEditable = welEditable[0], rng = new Range();
421
+ return {
422
+ contents: welEditable.html(), bookmark: rng.bookmark(elEditable),
423
+ scrollTop: welEditable.scrollTop()
424
+ };
425
+ };
426
+
427
+ var applySnap = function(welEditable, oSnap) {
428
+ welEditable.html(oSnap.contents).scrollTop(oSnap.scrollTop);
429
+ createRangeFromBookmark(welEditable[0], oSnap.bookmark).select();
430
+ };
431
+
432
+ this.undo = function(welEditable) {
433
+ var oSnap = makeSnap(welEditable);
434
+ if (aUndo.length === 0) { return; }
435
+ applySnap(welEditable, aUndo.pop()), aRedo.push(oSnap);
436
+ };
437
+
438
+ this.redo = function(welEditable) {
439
+ var oSnap = makeSnap(welEditable);
440
+ if (aRedo.length === 0) { return; }
441
+ applySnap(welEditable, aRedo.pop()), aUndo.push(oSnap);
442
+ };
443
+
444
+ this.recordUndo = function(welEditable) {
445
+ aRedo = [], aUndo.push(makeSnap(welEditable));
446
+ };
447
+ };
448
+
449
+ /**
450
+ * Editor
451
+ */
452
+ var Editor = function() {
453
+ //currentStyle
454
+ var style = new Style();
455
+ this.currentStyle = function(elTarget) {
456
+ if (document.getSelection().rangeCount == 0) { return null; }
457
+ return style.current((new Range()), elTarget);
458
+ };
459
+
460
+ // undo
461
+ this.undo = function(welEditable) {
462
+ welEditable.data('NoteHistory').undo(welEditable);
463
+ };
464
+
465
+ // redo
466
+ this.redo = function(welEditable) {
467
+ welEditable.data('NoteHistory').redo(welEditable);
468
+ };
469
+
470
+ // recordUndo
471
+ var recordUndo = this.recordUndo = function(welEditable) {
472
+ welEditable.data('NoteHistory').recordUndo(welEditable);
473
+ };
474
+
475
+ // native commands(with execCommand)
476
+ var aCmd = ['bold', 'italic', 'underline', 'justifyLeft', 'justifyCenter',
477
+ 'justifyRight', 'justifyFull', 'insertOrderedList',
478
+ 'insertUnorderedList', 'indent', 'outdent', 'formatBlock',
479
+ 'removeFormat', 'backColor', 'foreColor', 'insertImage',
480
+ 'insertHorizontalRule'];
481
+
482
+ for (var idx = 0, len=aCmd.length; idx < len; idx ++) {
483
+ this[aCmd[idx]] = function(sCmd) {
484
+ return function(welEditable, sValue) {
485
+ recordUndo(welEditable);
486
+ document.execCommand(sCmd, false, sValue);
487
+ };
488
+ }(aCmd[idx]);
489
+ }
490
+
491
+ this.fontSize = function(welEditable, sValue) {
492
+ recordUndo(welEditable);
493
+ document.execCommand('fontSize', false, 3);
494
+ // <font size='3'> to <font style='font-size={sValue}px;'>
495
+ var welFont = welEditable.find('font[size=3]');
496
+ welFont.removeAttr('size').css('font-size', sValue + 'px');
497
+ };
498
+
499
+ this.lineHeight = function(welEditable, sValue) {
500
+ recordUndo(welEditable);
501
+ style.stylePara(new Range(), {lineHeight: sValue});
502
+ };
503
+
504
+ this.unlink = function(welEditable) {
505
+ var rng = new Range();
506
+ if (rng.isOnAnchor()) {
507
+ recordUndo(welEditable);
508
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
509
+ rng = new Range(elAnchor, 0, elAnchor, 1);
510
+ rng.select();
511
+ document.execCommand('unlink');
512
+ }
513
+ };
514
+
515
+ this.setLinkDialog = function(welEditable, fnShowDialog) {
516
+ var rng = new Range();
517
+ if (rng.isOnAnchor()) {
518
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
519
+ rng = new Range(elAnchor, 0, elAnchor, 1);
520
+ }
521
+ fnShowDialog({
522
+ range: rng,
523
+ text: rng.toString(),
524
+ url: rng.isOnAnchor() ? dom.ancestor(rng.sc, dom.isAnchor).href : ""
525
+ }, function(sLinkUrl) {
526
+ rng.select();
527
+ recordUndo(welEditable);
528
+ if (sLinkUrl.toLowerCase().indexOf("http://") !== 0) {
529
+ sLinkUrl = "http://" + sLinkUrl;
530
+ }
531
+ document.execCommand('createlink', false, sLinkUrl);
532
+ });
533
+ };
534
+
535
+ this.color = function(welEditable, sObjColor) {
536
+ var oColor = JSON.parse(sObjColor);
537
+ this.foreColor(welEditable, oColor.foreColor);
538
+ this.backColor(welEditable, oColor.backColor);
539
+ };
540
+
541
+ this.insertTable = function(welEditable, sDim) {
542
+ recordUndo(welEditable);
543
+ var aDim = sDim.split('x');
544
+ var nCol = aDim[0], nRow = aDim[1];
545
+
546
+ var aTD = [], sTD;
547
+ var sWhitespace = bMSIE ? '&nbsp;' : '<br/>';
548
+ for (var idxCol = 0; idxCol < nCol; idxCol++) {
549
+ aTD.push('<td>' + sWhitespace + '</td>');
550
+ }
551
+ sTD = aTD.join('');
552
+
553
+ var aTR = [], sTR;
554
+ for (var idxRow = 0; idxRow < nRow; idxRow++) {
555
+ aTR.push('<tr>' + sTD + '</tr>');
556
+ }
557
+ sTR = aTR.join('');
558
+ var sTable = '<table class="table table-bordered">' + sTR + '</table>';
559
+ (new Range()).insertNode($(sTable)[0]);
560
+ };
561
+
562
+ this.float = function(welEditable, sValue, elTarget) {
563
+ recordUndo(welEditable);
564
+ elTarget.style.cssFloat = sValue;
565
+ };
566
+
567
+ this.resize = function(welEditable, sValue, elTarget) {
568
+ recordUndo(welEditable);
569
+ elTarget.style.width = welEditable.width() * sValue + 'px';
570
+ elTarget.style.height = "";
571
+ };
572
+
573
+ this.resizeTo = function(pos, elTarget) {
574
+ elTarget.style.width = pos.x + 'px';
575
+ elTarget.style.height = pos.y + 'px';
576
+ };
577
+ };
578
+
579
+ /**
580
+ * Toolbar
581
+ */
582
+ var Toolbar = function() {
583
+ this.update = function(welToolbar, oStyle) {
584
+ //handle selectbox for fontsize, lineHeight
585
+ var checkDropdownMenu = function(welBtn, nValue) {
586
+ welBtn.find('.dropdown-menu li a').each(function() {
587
+ var bChecked = $(this).attr('data-value') == nValue;
588
+ this.className = bChecked ? 'checked' : '';
589
+ });
590
+ };
591
+
592
+ var welFontsize = welToolbar.find('.note-fontsize');
593
+ welFontsize.find('.note-current-fontsize').html(oStyle['font-size']);
594
+ checkDropdownMenu(welFontsize, parseFloat(oStyle['font-size']));
595
+
596
+ var welLineHeight = welToolbar.find('.note-line-height');
597
+ checkDropdownMenu(welLineHeight, parseFloat(oStyle['line-height']));
598
+
599
+ //check button state
600
+ var btnState = function(sSelector, pred) {
601
+ var welBtn = welToolbar.find(sSelector);
602
+ welBtn[pred() ? 'addClass' : 'removeClass']('active');
603
+ };
604
+
605
+ btnState('button[data-event="bold"]', function() {
606
+ return oStyle['font-weight'] === 'bold';
607
+ });
608
+ btnState('button[data-event="italic"]', function() {
609
+ return oStyle['font-style'] === 'italic';
610
+ });
611
+ btnState('button[data-event="underline"]', function() {
612
+ return oStyle['text-decoration'] === 'underline';
613
+ });
614
+ btnState('button[data-event="justifyLeft"]', function() {
615
+ return oStyle['text-align'] === 'left' || oStyle['text-align'] === 'start';
616
+ });
617
+ btnState('button[data-event="justifyCenter"]', function() {
618
+ return oStyle['text-align'] === 'center';
619
+ });
620
+ btnState('button[data-event="justifyRight"]', function() {
621
+ return oStyle['text-align'] === 'right';
622
+ });
623
+ btnState('button[data-event="justifyFull"]', function() {
624
+ return oStyle['text-align'] === 'justify';
625
+ });
626
+ btnState('button[data-event="insertUnorderedList"]', function() {
627
+ return oStyle['list-style'] === 'unordered';
628
+ });
629
+ btnState('button[data-event="insertOrderedList"]', function() {
630
+ return oStyle['list-style'] === 'ordered';
631
+ });
632
+ };
633
+
634
+ this.updateRecentColor = function(elBtn, sEvent, sValue) {
635
+ var welColor = $(elBtn).closest('.note-color');
636
+ var welRecentColor = welColor.find('.note-recent-color');
637
+ var oColor = JSON.parse(welRecentColor.attr('data-value'));
638
+ oColor[sEvent] = sValue;
639
+ welRecentColor.attr('data-value', JSON.stringify(oColor));
640
+ var sKey = sEvent === "backColor" ? 'background-color' : 'color';
641
+ welRecentColor.find('i').css(sKey, sValue);
642
+ };
643
+ };
644
+
645
+ /**
646
+ * Popover
647
+ */
648
+ var Popover = function() {
649
+ this.update = function(welPopover, oStyle) {
650
+ var welLinkPopover = welPopover.find('.note-link-popover'),
651
+ welImagePopover = welPopover.find('.note-image-popover');
652
+ if (oStyle.anchor) {
653
+ var welAnchor = welLinkPopover.find('a');
654
+ welAnchor.attr('href', oStyle.anchor.href).html(oStyle.anchor.href);
655
+
656
+ var rect = oStyle.anchor.getBoundingClientRect();
657
+ welLinkPopover.css({
658
+ display: 'block',
659
+ left: rect.left,
660
+ top: $(document).scrollTop() + rect.bottom
661
+ });
662
+ } else {
663
+ welLinkPopover.hide();
664
+ }
665
+
666
+ if (oStyle.image) {
667
+ var rect = oStyle.image.getBoundingClientRect();
668
+ welImagePopover.css({
669
+ display: 'block',
670
+ left: rect.left,
671
+ top: $(document).scrollTop() + rect.bottom
672
+ });
673
+ } else {
674
+ welImagePopover.hide();
675
+ }
676
+ };
677
+
678
+ this.hide = function(welPopover) {
679
+ welPopover.children().hide();
680
+ };
681
+ };
682
+
683
+ /**
684
+ * Handle
685
+ */
686
+ var Handle = function() {
687
+ this.update = function(welHandle, oStyle) {
688
+ var welSelection = welHandle.find('.note-control-selection');
689
+ if (oStyle.image) {
690
+ var rect = oStyle.image.getBoundingClientRect();
691
+ welSelection.css({
692
+ display: 'block',
693
+ left: rect.left + 'px',
694
+ top: $(document).scrollTop() + rect.top + 'px',
695
+ width: rect.width + 'px',
696
+ height: rect.height + 'px'
697
+ }).data('target', oStyle.image); // save current image element.
698
+ var sSizing = rect.width + 'x' + rect.height;
699
+ welSelection.find('.note-control-selection-info').text(sSizing);
700
+ } else {
701
+ welSelection.hide();
702
+ }
703
+ };
704
+
705
+ this.hide = function(welHandle) {
706
+ welHandle.children().hide();
707
+ };
708
+ };
709
+
710
+ /**
711
+ * Dialog
712
+ */
713
+ var Dialog = function() {
714
+ this.showImageDialog = function(welDialog, hDropImage, fnInsertImages) {
715
+ var welImageDialog = welDialog.find('.note-image-dialog');
716
+ var welDropzone = welDialog.find('.note-dropzone'),
717
+ welImageInput = welDialog.find('.note-image-input');
718
+
719
+ welImageDialog.on('shown', function(e) {
720
+ welDropzone.on('dragenter dragover dragleave', false);
721
+ welDropzone.on('drop', function(e) {
722
+ hDropImage(e); welImageDialog.modal('hide');
723
+ });
724
+ welImageInput.on('change', function() {
725
+ fnInsertImages(this.files); $(this).val('');
726
+ welImageDialog.modal('hide');
727
+ });
728
+ }).on('hidden', function(e) {
729
+ welDropzone.off('dragenter dragover dragleave drop');
730
+ welImageInput.off('change');
731
+ }).modal('show');
732
+ };
733
+
734
+ this.showLinkDialog = function(welDialog, linkInfo, callback) {
735
+ var welLinkDialog = welDialog.find('.note-link-dialog');
736
+ var welLinkText = welLinkDialog.find('.note-link-text'),
737
+ welLinkUrl = welLinkDialog.find('.note-link-url'),
738
+ welLinkBtn = welLinkDialog.find('.note-link-btn');
739
+
740
+ welLinkDialog.on('shown', function(e) {
741
+ welLinkText.html(linkInfo.text);
742
+ welLinkUrl.val(linkInfo.url).keyup(function(event) {
743
+ if (welLinkUrl.val()) {
744
+ welLinkBtn.removeClass('disabled').attr('disabled', false);
745
+ } else {
746
+ welLinkBtn.addClass('disabled').attr('disabled', true);
747
+ }
748
+
749
+ if (!linkInfo.text) { welLinkText.html(welLinkUrl.val()); };
750
+ }).trigger('focus');
751
+ welLinkBtn.click(function(event) {
752
+ welLinkDialog.modal('hide'); //hide and createLink (ie9+)
753
+ callback(welLinkUrl.val());
754
+ event.preventDefault();
755
+ });
756
+ }).on('hidden', function(e) {
757
+ welLinkUrl.off('keyup');
758
+ welLinkDialog.off('shown hidden');
759
+ welLinkBtn.off('click');
760
+ }).modal('show');
761
+ };
762
+ };
763
+
764
+ /**
765
+ * EventHandler
766
+ *
767
+ * handle mouse & key event on note
768
+ */
769
+ var EventHandler = function() {
770
+ var editor = new Editor();
771
+ var toolbar = new Toolbar(), popover = new Popover();
772
+ var handle = new Handle(), dialog = new Dialog();
773
+
774
+ var key = { BACKSPACE: 8, TAB: 9, ENTER: 13, SPACE: 32,
775
+ NUM0: 48, NUM1: 49, NUM4: 52, NUM7: 55, NUM8: 56,
776
+ B: 66, E: 69, I: 73, J: 74, K: 75, L: 76, R: 82,
777
+ U: 85, Y: 89, Z: 90, BACKSLACH: 220 };
778
+
779
+ // makeLayoutInfo from editor's descendant node.
780
+ var makeLayoutInfo = function(descendant) {
781
+ var welEditor = $(descendant).closest('.note-editor');
782
+ return {
783
+ editor: function() { return welEditor; },
784
+ editable: function() { return welEditor.find('.note-editable'); },
785
+ toolbar: function() { return welEditor.find('.note-toolbar'); },
786
+ popover: function() { return welEditor.find('.note-popover'); },
787
+ handle: function() { return welEditor.find('.note-handle'); },
788
+ dialog: function() { return welEditor.find('.note-dialog'); }
789
+ };
790
+ };
791
+
792
+ var hKeydown = function(event) {
793
+ var bCmd = bMac ? event.metaKey : event.ctrlKey,
794
+ bShift = event.shiftKey, keyCode = event.keyCode;
795
+
796
+ // optimize
797
+ var oLayoutInfo = (bCmd || bShift) ? makeLayoutInfo(event.target) : null;
798
+
799
+ if (bCmd && ((bShift && keyCode === key.Z) || keyCode === key.Y)) {
800
+ editor.redo(oLayoutInfo.editable());
801
+ } else if (bCmd && keyCode === key.Z) {
802
+ editor.undo(oLayoutInfo.editable());
803
+ } else if (bCmd && keyCode === key.B) {
804
+ editor.bold(oLayoutInfo.editable());
805
+ } else if (bCmd && keyCode === key.I) {
806
+ editor.italic(oLayoutInfo.editable());
807
+ } else if (bCmd && keyCode === key.U) {
808
+ editor.underline(oLayoutInfo.editable());
809
+ } else if (bCmd && keyCode === key.BACKSLACH) {
810
+ editor.removeFormat(oLayoutInfo.editable());
811
+ } else if (bCmd && keyCode === key.K) {
812
+ editor.setLinkDialog(oLayoutInfo.editable(), function(linkInfo, cb) {
813
+ dialog.showLinkDialog(oLayoutInfo.dialog(), linkInfo, cb);
814
+ });
815
+ } else if (bCmd && bShift && keyCode === key.L) {
816
+ editor.justifyLeft(oLayoutInfo.editable());
817
+ } else if (bCmd && bShift && keyCode === key.E) {
818
+ editor.justifyCenter(oLayoutInfo.editable());
819
+ } else if (bCmd && bShift && keyCode === key.R) {
820
+ editor.justifyRight(oLayoutInfo.editable());
821
+ } else if (bCmd && bShift && keyCode === key.J) {
822
+ editor.justifyFull(oLayoutInfo.editable());
823
+ } else if (bCmd && bShift && keyCode === key.NUM7) {
824
+ editor.insertUnorderedList(oLayoutInfo.editable());
825
+ } else if (bCmd && bShift && keyCode === key.NUM8) {
826
+ editor.insertOrderedList(oLayoutInfo.editable());
827
+ } else if (bShift && keyCode === key.TAB) { // shift + tab
828
+ editor.outdent(oLayoutInfo.editable());
829
+ } else if (keyCode === key.TAB) { // tab
830
+ editor.indent(oLayoutInfo.editable());
831
+ } else if (bCmd && keyCode === key.NUM0) { // formatBlock Paragraph
832
+ editor.formatBlock(oLayoutInfo.editable(), 'P');
833
+ } else if (bCmd && (key.NUM1 <= keyCode && keyCode <= key.NUM4)) {
834
+ var sHeading = 'H' + String.fromCharCode(keyCode); // H1~H4
835
+ editor.formatBlock(oLayoutInfo.editable(), sHeading);
836
+ } else if (bCmd && keyCode === key.ENTER) {
837
+ editor.insertHorizontalRule(oLayoutInfo.editable());
838
+ } else {
839
+ if (keyCode === key.BACKSPACE || keyCode === key.ENTER ||
840
+ keyCode === key.SPACE) {
841
+ editor.recordUndo(makeLayoutInfo(event.target).editable());
842
+ }
843
+ return; // not matched
844
+ }
845
+ event.preventDefault(); //prevent default event for FF
846
+ };
847
+
848
+ var insertImages = function(welEditable, files) {
849
+ $.each(files, function(idx, file) {
850
+ var fileReader = new FileReader;
851
+ fileReader.onload = function(event) {
852
+ editor.insertImage(welEditable, event.target.result); // sURL
853
+ };
854
+ fileReader.readAsDataURL(file);
855
+ });
856
+ };
857
+
858
+ var hDropImage = function(event) {
859
+ var dataTransfer = event.originalEvent.dataTransfer;
860
+ if (dataTransfer && dataTransfer.files) {
861
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
862
+ insertImages(oLayoutInfo.editable(), dataTransfer.files);
863
+ }
864
+ event.stopPropagation();
865
+ event.preventDefault();
866
+ };
867
+
868
+ var hMousedown = function(event) {
869
+ //preventDefault Selection for FF, IE8+
870
+ if (dom.isImg(event.target)) { event.preventDefault(); };
871
+ };
872
+
873
+ var hToolbarAndPopoverUpdate = function(event) {
874
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
875
+
876
+ var oStyle = editor.currentStyle(event.target);
877
+ if (!oStyle) { return; }
878
+ toolbar.update(oLayoutInfo.toolbar(), oStyle);
879
+ popover.update(oLayoutInfo.popover(), oStyle);
880
+ handle.update(oLayoutInfo.handle(), oStyle);
881
+ };
882
+
883
+ var hScroll = function(event) {
884
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
885
+ //hide popover and handle when scrolled
886
+ popover.hide(oLayoutInfo.popover());
887
+ handle.hide(oLayoutInfo.handle());
888
+ };
889
+
890
+ var hHandleMousedown = function(event) {
891
+ if (dom.isControlSizing(event.target)) {
892
+ var oLayoutInfo = makeLayoutInfo(event.target),
893
+ welHandle = oLayoutInfo.handle(), welPopover = oLayoutInfo.popover(),
894
+ welEditable = oLayoutInfo.editable(), welEditor = oLayoutInfo.editor();
895
+
896
+ var elTarget = welHandle.find('.note-control-selection').data('target');
897
+ var posStart = $(elTarget).offset(),
898
+ scrollTop = $(document).scrollTop(), posDistance;
899
+ welEditor.on('mousemove', function(event) {
900
+ posDistance = {x: event.clientX - posStart.left,
901
+ y: event.clientY - (posStart.top - scrollTop)};
902
+ editor.resizeTo(posDistance, elTarget);
903
+ handle.update(welHandle, {image: elTarget});
904
+ popover.update(welPopover, {image: elTarget});
905
+ }).on('mouseup', function() {
906
+ welEditor.off('mousemove').off('mouseup');
907
+ });
908
+
909
+ editor.recordUndo(welEditable);
910
+ event.stopPropagation(); event.preventDefault();
911
+ }
912
+ };
913
+
914
+ var hToolbarAndPopoverMousedown = function(event) {
915
+ // prevent default event when insertTable (FF, Webkit)
916
+ var welBtn = $(event.target).closest('[data-event]');
917
+ if (welBtn.length > 0) { event.preventDefault(); }
918
+ };
919
+
920
+ var hToolbarAndPopoverClick = function(event) {
921
+ var welBtn = $(event.target).closest('[data-event]');
922
+
923
+ if (welBtn.length > 0) {
924
+ var sEvent = welBtn.attr('data-event'),
925
+ sValue = welBtn.attr('data-value');
926
+
927
+ var oLayoutInfo = makeLayoutInfo(event.target);
928
+ var welDialog = oLayoutInfo.dialog(),
929
+ welEditable = oLayoutInfo.editable();
930
+
931
+ // before command
932
+ var elTarget;
933
+ if ($.inArray(sEvent, ['resize', 'float']) !== -1) {
934
+ var welHandle = oLayoutInfo.handle();
935
+ var welSelection = welHandle.find('.note-control-selection');
936
+ elTarget = welSelection.data('target');
937
+ }
938
+
939
+ if (editor[sEvent]) { // on command
940
+ welEditable.trigger('focus');
941
+ editor[sEvent](welEditable, sValue, elTarget);
942
+ }
943
+
944
+ // after command
945
+ if ($.inArray(sEvent, ["backColor", "foreColor"]) !== -1) {
946
+ toolbar.updateRecentColor(welBtn[0], sEvent, sValue);
947
+ } else if (sEvent === "showLinkDialog") { // popover to dialog
948
+ editor.setLinkDialog(welEditable, function(linkInfo, cb) {
949
+ dialog.showLinkDialog(welDialog, linkInfo, cb);
950
+ });
951
+ } else if (sEvent === "showImageDialog") {
952
+ dialog.showImageDialog(welDialog, hDropImage, function(files) {
953
+ insertImages(welEditable, files);
954
+ });
955
+ }
956
+
957
+ hToolbarAndPopoverUpdate(event);
958
+ }
959
+ };
960
+
961
+ var PX_PER_EM = 18;
962
+ var hDimensionPickerMove = function(event) {
963
+ var welPicker = $(event.target.parentNode); // target is mousecatcher
964
+ var welDimensionDisplay = welPicker.next();
965
+ var welCatcher = welPicker.find('.note-dimension-picker-mousecatcher');
966
+ var welHighlighted = welPicker.find('.note-dimension-picker-highlighted');
967
+ var welUnhighlighted = welPicker.find('.note-dimension-picker-unhighlighted');
968
+ var posOffset;
969
+ if (event.offsetX === undefined) {
970
+ // HTML5 with jQuery - e.offsetX is undefined in Firefox
971
+ var posCatcher = $(event.target).offset();
972
+ posOffset = {x: event.pageX - posCatcher.left,
973
+ y: event.pageY - posCatcher.top};
974
+ } else {
975
+ posOffset = {x: event.offsetX, y: event.offsetY};
976
+ }
977
+
978
+ var dim = {c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
979
+ r: Math.ceil(posOffset.y / PX_PER_EM) || 1};
980
+
981
+ welHighlighted.css({ width: dim.c +'em', height: dim.r + 'em' });
982
+ welCatcher.attr('data-value', dim.c + 'x' + dim.r);
983
+
984
+ if (3 < dim.c && dim.c < 20) { // 5~20
985
+ welUnhighlighted.css({ width: dim.c + 1 + 'em'});
986
+ }
987
+
988
+ if (3 < dim.r && dim.r < 20) { // 5~20
989
+ welUnhighlighted.css({ height: dim.r + 1 + 'em'});
990
+ }
991
+
992
+ welDimensionDisplay.html(dim.c + ' x ' + dim.r);
993
+ };
994
+
995
+ this.attach = function(oLayoutInfo) {
996
+ oLayoutInfo.editable.on('keydown', hKeydown);
997
+ oLayoutInfo.editable.on('mousedown', hMousedown);
998
+ oLayoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
999
+ oLayoutInfo.editable.on('scroll', hScroll);
1000
+ //TODO: handle Drag point
1001
+ oLayoutInfo.editable.on('dragenter dragover dragleave', false);
1002
+ oLayoutInfo.editable.on('drop', hDropImage);
1003
+
1004
+ oLayoutInfo.handle.on('mousedown', hHandleMousedown);
1005
+
1006
+ oLayoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
1007
+ oLayoutInfo.popover.on('click', hToolbarAndPopoverClick);
1008
+ oLayoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
1009
+ oLayoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
1010
+
1011
+ //toolbar table dimension
1012
+ var welToolbar = oLayoutInfo.toolbar;
1013
+ var welCatcher = welToolbar.find('.note-dimension-picker-mousecatcher');
1014
+ welCatcher.on('mousemove', hDimensionPickerMove);
1015
+ };
1016
+
1017
+ this.dettach = function(oLayoutInfo) {
1018
+ oLayoutInfo.editable.off();
1019
+ oLayoutInfo.toolbar.off();
1020
+ oLayoutInfo.handle.off();
1021
+ oLayoutInfo.popover.off();
1022
+ };
1023
+ };
1024
+
1025
+ /**
1026
+ * Renderer
1027
+ *
1028
+ * rendering toolbar and editable
1029
+ */
1030
+ var Renderer = function() {
1031
+ var sToolbar = '<div class="note-toolbar btn-toolbar">' +
1032
+ '<div class="note-insert btn-group">' +
1033
+ '<button type="button" class="btn btn-small" title="Picture" data-event="showImageDialog"><i class="icon-picture"></i></button>' +
1034
+ '<button type="button" class="btn btn-small" title="Link" data-event="showLinkDialog" data-shortcut="Ctrl+K" data-mac-shortcut="⌘+K" ><i class="icon-link"></i></button>' +
1035
+ '</div>' +
1036
+ '<div class="note-table btn-group">' +
1037
+ '<button type="button" class="btn btn-small dropdown-toggle" title="Table" data-toggle="dropdown"><i class="icon-table"></i> <span class="caret"></span></button>' +
1038
+ '<ul class="dropdown-menu">' +
1039
+ '<div class="note-dimension-picker">' +
1040
+ '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
1041
+ '<div class="note-dimension-picker-highlighted"></div>' +
1042
+ '<div class="note-dimension-picker-unhighlighted"></div>' +
1043
+ '</div>' +
1044
+ '<div class="note-dimension-display"> 1 x 1 </div>' +
1045
+ '</ul>' +
1046
+ '</div>' +
1047
+ '<div class="note-style btn-group">' +
1048
+ '<button type="button" class="btn btn-small dropdown-toggle" title="Style" data-toggle="dropdown"><i class="icon-magic"></i> <span class="caret"></span></button>' +
1049
+ '<ul class="dropdown-menu">' +
1050
+ '<li><a data-event="formatBlock" data-value="p">Paragraph</a></li>' +
1051
+ '<li><a data-event="formatBlock" data-value="blockquote"><blockquote>Quote</blockquote></a></li>' +
1052
+ '<li><a data-event="formatBlock" data-value="pre">Code</a></li>' +
1053
+ '<li><a data-event="formatBlock" data-value="h1"><h1>Header 1</h1></a></li>' +
1054
+ '<li><a data-event="formatBlock" data-value="h2"><h2>Header 2</h2></a></li>' +
1055
+ '<li><a data-event="formatBlock" data-value="h3"><h3>Header 3</h3></a></li>' +
1056
+ '<li><a data-event="formatBlock" data-value="h4"><h4>Header 4</h4></a></li>' +
1057
+ '</ul>' +
1058
+ '</div>' +
1059
+ '<div class="note-fontsize btn-group">' +
1060
+ '<button type="button" class="btn btn-small dropdown-toggle" data-toggle="dropdown" title="Font Size"><span class="note-current-fontsize">11</span> <b class="caret"></b></button>' +
1061
+ '<ul class="dropdown-menu">' +
1062
+ '<li><a data-event="fontSize" data-value="8"><i class="icon-ok"></i> 8</a></li>' +
1063
+ '<li><a data-event="fontSize" data-value="9"><i class="icon-ok"></i> 9</a></li>' +
1064
+ '<li><a data-event="fontSize" data-value="10"><i class="icon-ok"></i> 10</a></li>' +
1065
+ '<li><a data-event="fontSize" data-value="11"><i class="icon-ok"></i> 11</a></li>' +
1066
+ '<li><a data-event="fontSize" data-value="12"><i class="icon-ok"></i> 12</a></li>' +
1067
+ '<li><a data-event="fontSize" data-value="14"><i class="icon-ok"></i> 14</a></li>' +
1068
+ '<li><a data-event="fontSize" data-value="18"><i class="icon-ok"></i> 18</a></li>' +
1069
+ '<li><a data-event="fontSize" data-value="24"><i class="icon-ok"></i> 24</a></li>' +
1070
+ '<li><a data-event="fontSize" data-value="36"><i class="icon-ok"></i> 36</a></li>' +
1071
+ '</ul>' +
1072
+ '</div>' +
1073
+ '<div class="note-color btn-group">' +
1074
+ '<button type="button" class="btn btn-small note-recent-color" title="Recent Color" data-event="color" data-value=\'{"foreColor":"black","backColor":"yellow"}\'><i class="icon-font" style="color:black;background-color:yellow;"></i></button>' +
1075
+ '<button type="button" class="btn btn-small dropdown-toggle" title="More Color" data-toggle="dropdown">' +
1076
+ '<span class="caret"></span>' +
1077
+ '</button>' +
1078
+ '<ul class="dropdown-menu">' +
1079
+ '<li>' +
1080
+ '<div class="btn-group">' +
1081
+ '<div class="note-palette-title">BackColor</div>' +
1082
+ '<div class="note-color-palette" data-target-event="backColor"></div>' +
1083
+ '</div>' +
1084
+ '<div class="btn-group">' +
1085
+ '<div class="note-palette-title">FontColor</div>' +
1086
+ '<div class="note-color-palette" data-target-event="foreColor"></div>' +
1087
+ '</div>' +
1088
+ '</li>' +
1089
+ '</ul>' +
1090
+ '</div>' +
1091
+ '<div class="note-style btn-group">' +
1092
+ '<button type="button" class="btn btn-small" title="Bold" data-shortcut="Ctrl+B" data-mac-shortcut="⌘+B" data-event="bold"><i class="icon-bold"></i></button>' +
1093
+ '<button type="button" class="btn btn-small" title="Italic" data-shortcut="Ctrl+I" data-mac-shortcut="⌘+I" data-event="italic"><i class="icon-italic"></i></button>' +
1094
+ '<button type="button" class="btn btn-small" title="Underline" data-shortcut="Ctrl+U" data-mac-shortcut="⌘+U" data-event="underline"><i class="icon-underline"></i></button>' +
1095
+ '<button type="button" class="btn btn-small" title="Remove Font Style" data-shortcut="Ctrl+\\" data-mac-shortcut="⌘+\\" data-event="removeFormat"><i class="icon-eraser"></i></button>' +
1096
+ '</div>' +
1097
+ '<div class="note-para btn-group">' +
1098
+ '<button type="button" class="btn btn-small" title="Unordered list" data-shortcut="Ctrl+Shift+8" data-mac-shortcut="⌘+⇧+7" data-event="insertUnorderedList"><i class="icon-list-ul"></i></button>' +
1099
+ '<button type="button" class="btn btn-small" title="Ordered list" data-shortcut="Ctrl+Shift+7" data-mac-shortcut="⌘+⇧+8" data-event="insertOrderedList"><i class="icon-list-ol"></i></button>' +
1100
+ '<button type="button" class="btn btn-small dropdown-toggle" title="Paragraph" data-toggle="dropdown"><i class="icon-align-left"></i> <span class="caret"></span></button>' +
1101
+ '<ul class="dropdown-menu right">' +
1102
+ '<li>' +
1103
+ '<div class="note-align btn-group">' +
1104
+ '<button type="button" class="btn btn-small" title="Align left" data-shortcut="Ctrl+Shift+L" data-mac-shortcut="⌘+⇧+L" data-event="justifyLeft"><i class="icon-align-left"></i></button>' +
1105
+ '<button type="button" class="btn btn-small" title="Align center" data-shortcut="Ctrl+Shift+E" data-mac-shortcut="⌘+⇧+E" data-event="justifyCenter"><i class="icon-align-center"></i></button>' +
1106
+ '<button type="button" class="btn btn-small" title="Align right" data-shortcut="Ctrl+Shift+R" data-mac-shortcut="⌘+⇧+R" data-event="justifyRight"><i class="icon-align-right"></i></button>' +
1107
+ '<button type="button" class="btn btn-small" title="Justify full" data-shortcut="Ctrl+Shift+J" data-mac-shortcut="⌘+⇧+J" data-event="justifyFull"><i class="icon-align-justify"></i></button>' +
1108
+ '</div>' +
1109
+ '</li>' +
1110
+ '<li>' +
1111
+ '<div class="note-list btn-group">' +
1112
+ '<button type="button" class="btn btn-small" title="Outdent" data-shortcut="Shift+TAB" data-mac-shortcut="⇧+TAB" data-event="outdent"><i class="icon-indent-left"></i></button>' +
1113
+ '<button type="button" class="btn btn-small" title="Indent" data-shortcut="TAB" data-mac-shortcut="TAB" data-event="indent"><i class="icon-indent-right"></i></button>' +
1114
+ '</li>' +
1115
+ '</ul>' +
1116
+ '</div>' +
1117
+ '<div class="note-line-height btn-group">' +
1118
+ '<button type="button" class="btn btn-small dropdown-toggle" data-toggle="dropdown" title="Line Height"><i class="icon-text-height"></i>&nbsp; <b class="caret"></b></button>' +
1119
+ '<ul class="dropdown-menu right">' +
1120
+ '<li><a data-event="lineHeight" data-value="1.0"><i class="icon-ok"></i> 1.0</a></li>' +
1121
+ '<li><a data-event="lineHeight" data-value="1.2"><i class="icon-ok"></i> 1.2</a></li>' +
1122
+ '<li><a data-event="lineHeight" data-value="1.4"><i class="icon-ok"></i> 1.4</a></li>' +
1123
+ '<li><a data-event="lineHeight" data-value="1.5"><i class="icon-ok"></i> 1.5</a></li>' +
1124
+ '<li><a data-event="lineHeight" data-value="1.6"><i class="icon-ok"></i> 1.6</a></li>' +
1125
+ '<li><a data-event="lineHeight" data-value="1.8"><i class="icon-ok"></i> 1.8</a></li>' +
1126
+ '<li><a data-event="lineHeight" data-value="2.0"><i class="icon-ok"></i> 2.0</a></li>' +
1127
+ '<li><a data-event="lineHeight" data-value="3.0"><i class="icon-ok"></i> 3.0</a></li>' +
1128
+ '</ul>' +
1129
+ '</div>' +
1130
+ '</div>';
1131
+ var sPopover = '<div class="note-popover">' +
1132
+ '<div class="note-link-popover popover fade bottom in" style="display: none;">' +
1133
+ '<div class="arrow"></div>' +
1134
+ '<div class="popover-content note-link-content">' +
1135
+ '<a href="http://www.google.com" target="_blank">www.google.com</a>&nbsp;&nbsp;' +
1136
+ '<div class="note-insert btn-group">' +
1137
+ '<button type="button" class="btn btn-small" title="Edit" data-event="showLinkDialog"><i class="icon-edit"></i></button>' +
1138
+ '<button type="button" class="btn btn-small" title="Unlink" data-event="unlink"><i class="icon-unlink"></i></button>' +
1139
+ '</div>' +
1140
+ '</div>' +
1141
+ '</div>' +
1142
+ '<div class="note-image-popover popover fade bottom in" style="display: none;">' +
1143
+ '<div class="arrow"></div>' +
1144
+ '<div class="popover-content note-image-content">' +
1145
+ '<div class="btn-group">' +
1146
+ '<button type="button" class="btn btn-small" title="Resize Full" data-event="resize" data-value="1"><i class="icon-resize-full"></i></button>' +
1147
+ '<button type="button" class="btn btn-small" title="Resize Half" data-event="resize" data-value="0.5">½</button>' +
1148
+ '<button type="button" class="btn btn-small" title="Resize Thrid" data-event="resize" data-value="0.33">⅓</button>' +
1149
+ '<button type="button" class="btn btn-small" title="Resize Quarter" data-event="resize" data-value="0.25">¼</button>' +
1150
+ '</div>' +
1151
+ '<div class="btn-group">' +
1152
+ '<button type="button" class="btn btn-small" title="Float Left" data-event="float" data-value="left"><i class="icon-align-left"></i></button>' +
1153
+ '<button type="button" class="btn btn-small" title="Float Right" data-event="float" data-value="right"><i class="icon-align-right"></i></button>' +
1154
+ '<button type="button" class="btn btn-small" title="Float None" data-event="float" data-value="none"><i class="icon-reorder"></i></button>' +
1155
+ '</div>' +
1156
+ '</div>' +
1157
+ '</div>' +
1158
+ '</div>';
1159
+
1160
+ var sHandle = '<div class="note-handle">' +
1161
+ '<div class="note-control-selection">' +
1162
+ '<div class="note-control-selection-bg"></div>' +
1163
+ '<div class="note-control-holder note-control-nw"></div>' +
1164
+ '<div class="note-control-holder note-control-ne"></div>' +
1165
+ '<div class="note-control-holder note-control-sw"></div>' +
1166
+ '<div class="note-control-sizing note-control-se"></div>' +
1167
+ '<div class="note-control-selection-info"></div>' +
1168
+ '</div>' +
1169
+ '</div>';
1170
+
1171
+ var sDialog = '<div class="note-dialog">' +
1172
+ '<div class="note-image-dialog modal hide in" aria-hidden="false">' +
1173
+ '<div class="modal-header">' +
1174
+ '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>' +
1175
+ '<h4>Insert Image</h4>' +
1176
+ '</div>' +
1177
+ '<div class="modal-body">' +
1178
+ '<div class="row-fluid">' +
1179
+ '<div class="note-dropzone span12">Drag an image here</div>' +
1180
+ '<div>or if you prefer...</div>' +
1181
+ '<input class="note-image-input" type="file" class="note-link-url" type="text" />' +
1182
+ '</div>' +
1183
+ '</div>' +
1184
+ '</div>' +
1185
+ '<div class="note-link-dialog modal hide in" aria-hidden="false">' +
1186
+ '<div class="modal-header">' +
1187
+ '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>' +
1188
+ '<h4>Edit Link</h4>' +
1189
+ '</div>' +
1190
+ '<div class="modal-body">' +
1191
+ '<div class="row-fluid">' +
1192
+ '<label>Text to display</label>' +
1193
+ '<span class="note-link-text input-xlarge uneditable-input" />' +
1194
+ '<label>To what URL should this link go?</label>' +
1195
+ '<input class="note-link-url span12" type="text" />' +
1196
+ '</div>' +
1197
+ '</div>' +
1198
+ '<div class="modal-footer">' +
1199
+ '<a href="#" class="btn disabled note-link-btn" disabled="disabled">Link</a>' +
1200
+ '</div>' +
1201
+ '</div>' +
1202
+ '</div>';
1203
+
1204
+ // createTooltip
1205
+ var createTooltip = function(welContainer, sPlacement) {
1206
+ welContainer.find('button').each(function(i, elBtn) {
1207
+ var welBtn = $(elBtn);
1208
+ var sShortcut = welBtn.attr(bMac ? 'data-mac-shortcut':'data-shortcut');
1209
+ if (sShortcut) { welBtn.attr('title', function(i, v) { return v + ' (' + sShortcut + ')'}); }
1210
+ //bootstrap tooltip on btn-group bug: https://github.com/twitter/bootstrap/issues/5687
1211
+ }).tooltip({container: 'body', placement: sPlacement || 'top'});
1212
+ };
1213
+
1214
+ // pallete colors
1215
+ var aaColor = [
1216
+ ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#EFF7F7', '#FFFFFF'],
1217
+ ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
1218
+ ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
1219
+ ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
1220
+ ['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
1221
+ ['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
1222
+ ['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
1223
+ ['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
1224
+ ];
1225
+
1226
+ // createPalette
1227
+ var createPalette = function(welContainer) {
1228
+ welContainer.find('.note-color-palette').each(function() {
1229
+ var welPalette = $(this), sEvent = welPalette.attr('data-target-event');
1230
+ var sPaletteContents = '';
1231
+ for (var row = 0, szRow = aaColor.length; row < szRow; row++) {
1232
+ var aColor = aaColor[row];
1233
+ var sLine = '<div>';
1234
+ for (var col = 0, szCol = aColor.length; col < szCol; col++) {
1235
+ var sColor = aColor[col];
1236
+ var sButton = ['<button type="button" class="note-color-btn" style="background-color:', sColor,
1237
+ ';" data-event="', sEvent,
1238
+ '" data-value="', sColor,
1239
+ '" title="', sColor,
1240
+ '" data-toggle="button"></button>'].join('');
1241
+ sLine += sButton;
1242
+ }
1243
+ sLine += '</div>';
1244
+ sPaletteContents += sLine;
1245
+ }
1246
+ welPalette.html(sPaletteContents);
1247
+ });
1248
+ };
1249
+
1250
+ // createLayout
1251
+ var createLayout = this.createLayout = function(welHolder, nHeight) {
1252
+ //already created
1253
+ if (welHolder.next().hasClass('note-editor')) { return; }
1254
+
1255
+ //01. create Editor
1256
+ var welEditor = $('<div class="note-editor"></div>');
1257
+
1258
+ //02. create Editable
1259
+ var welEditable = $('<div class="note-editable" contentEditable="true"></div>').prependTo(welEditor);
1260
+ if (nHeight) { welEditable.height(nHeight); }
1261
+
1262
+ welEditable.html(welHolder.html());
1263
+ welEditable.data('NoteHistory', new History());
1264
+
1265
+ //03. create Toolbar
1266
+ var welToolbar = $(sToolbar).prependTo(welEditor);
1267
+ createPalette(welToolbar);
1268
+ createTooltip(welToolbar, 'bottom');
1269
+
1270
+ //04. create Popover
1271
+ var welPopover = $(sPopover).prependTo(welEditor);
1272
+ createTooltip(welPopover);
1273
+
1274
+ //05. handle(control selection, ...)
1275
+ $(sHandle).prependTo(welEditor);
1276
+
1277
+ //06. create Dialog
1278
+ $(sDialog).prependTo(welEditor);
1279
+
1280
+ //05. Editor/Holder switch
1281
+ welEditor.insertAfter(welHolder);
1282
+ welHolder.hide();
1283
+ };
1284
+
1285
+ // layoutInfoFromHolder
1286
+ var layoutInfoFromHolder = this.layoutInfoFromHolder = function(welHolder) {
1287
+ var welEditor = welHolder.next();
1288
+ if (!welEditor.hasClass('note-editor')) { return; }
1289
+
1290
+ return {
1291
+ editor: welEditor,
1292
+ editable: welEditor.find('.note-editable'),
1293
+ toolbar: welEditor.find('.note-toolbar'),
1294
+ popover: welEditor.find('.note-popover'),
1295
+ handle: welEditor.find('.note-handle'),
1296
+ dialog: welEditor.find('.note-dialog')
1297
+ };
1298
+ };
1299
+
1300
+ // removeLayout
1301
+ var removeLayout = this.removeLayout = function(welHolder) {
1302
+ var info = layoutInfoFromHolder(welHolder);
1303
+ if (!info) { return; }
1304
+ welHolder.html(info.editable.html());
1305
+
1306
+ info.editor.remove();
1307
+ welHolder.show();
1308
+ };
1309
+ };
1310
+
1311
+ var renderer = new Renderer();
1312
+ var eventHandler = new EventHandler();
1313
+
1314
+ /**
1315
+ * extend jquery fn
1316
+ */
1317
+ $.fn.extend({
1318
+ // create Editor Layout and attach Key and Mouse Event
1319
+ summernote: function(options) {
1320
+ options = options || {};
1321
+
1322
+ this.each(function(idx, elHolder) {
1323
+ var welHolder = $(elHolder);
1324
+
1325
+ // createLayout
1326
+ renderer.createLayout(welHolder, options.height);
1327
+
1328
+ var info = renderer.layoutInfoFromHolder(welHolder);
1329
+ eventHandler.attach(info);
1330
+
1331
+ if (options.focus) { info.editable.focus(); } // options focus
1332
+ });
1333
+ },
1334
+ // get the HTML contents of note or set the HTML contents of note.
1335
+ code: function(sHTML) {
1336
+ //get the HTML contents
1337
+ if (sHTML === undefined) {
1338
+ return this.map(function(idx, elHolder) {
1339
+ var info = renderer.layoutInfoFromHolder($(elHolder));
1340
+ return info.editable.html();
1341
+ });
1342
+ }
1343
+
1344
+ // set the HTML contents
1345
+ this.each(function(i, elHolder) {
1346
+ var info = renderer.layoutInfoFromHolder($(elHolder));
1347
+ info.editable.html(sHTML);
1348
+ });
1349
+ },
1350
+ // destroy Editor Layout and dettach Key and Mouse Event
1351
+ destroy: function() {
1352
+ this.each(function(idx, elHolder) {
1353
+ var welHolder = $(elHolder);
1354
+
1355
+ var info = renderer.layoutInfoFromHolder(welHolder);
1356
+ eventHandler.dettach(info);
1357
+ renderer.removeLayout(welHolder);
1358
+ });
1359
+ },
1360
+ // inner object for test
1361
+ summernoteInner: function() {
1362
+ return { dom: dom, list: list, func: func };
1363
+ }
1364
+ });
1365
+ })(jQuery); // jQuery