bitsnote-assets 0.0.3 → 0.0.4
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 +4 -4
- data/README.rdoc +0 -12
- data/app/assets/javascripts/summernote/summernote.js +4765 -0
- data/app/assets/javascripts/summernote/summernote.min.js +2 -0
- data/app/assets/stylesheets/summernote/summernote-bs3.css +6057 -0
- data/app/assets/stylesheets/summernote/summernote.css +1 -0
- data/lib/bitsnote-assets.rb +26 -8
- data/lib/bitsnote-assets/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a8e47c3f21a2dee68fa6f6c563851a79f832287
|
|
4
|
+
data.tar.gz: da87e01b26d2ff9fe93afa509936bf2bf408cf72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc6be01770abe01b4a329bf76ab7f99c82357a33963c135c5328f21b983f9c65b84bf305a66a96e5d68eb8e1ed87b5ab4ad302c308c8ba97e919c8c9a8788b82
|
|
7
|
+
data.tar.gz: 663ebdea058b3586c592695ff02cc2b272b3054d9677da5694c7242acab6e47a7fdf267a854d9e6f5bd28c06b9ddf6877502fc146fbd830131a35bebbe681472
|
data/README.rdoc
CHANGED
|
@@ -0,0 +1,4765 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Super simple wysiwyg editor on Bootstrap v0.5.6
|
|
3
|
+
* http://hackerwins.github.io/summernote/
|
|
4
|
+
*
|
|
5
|
+
* summernote.js
|
|
6
|
+
* Copyright 2013 Alan Hong. and outher contributors
|
|
7
|
+
* summernote may be freely distributed under the MIT license./
|
|
8
|
+
*
|
|
9
|
+
* Date: 2014-08-27T07:06Z
|
|
10
|
+
*/
|
|
11
|
+
(function (factory) {
|
|
12
|
+
/* global define */
|
|
13
|
+
if (typeof define === 'function' && define.amd) {
|
|
14
|
+
// AMD. Register as an anonymous module.
|
|
15
|
+
define(['jquery'], factory);
|
|
16
|
+
} else {
|
|
17
|
+
// Browser globals: jQuery
|
|
18
|
+
factory(window.jQuery);
|
|
19
|
+
}
|
|
20
|
+
}(function ($) {
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if ('function' !== typeof Array.prototype.reduce) {
|
|
25
|
+
/**
|
|
26
|
+
* Array.prototype.reduce fallback
|
|
27
|
+
*
|
|
28
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
|
|
29
|
+
*/
|
|
30
|
+
Array.prototype.reduce = function (callback, optInitialValue) {
|
|
31
|
+
var idx, value, length = this.length >>> 0, isValueSet = false;
|
|
32
|
+
if (1 < arguments.length) {
|
|
33
|
+
value = optInitialValue;
|
|
34
|
+
isValueSet = true;
|
|
35
|
+
}
|
|
36
|
+
for (idx = 0; length > idx; ++idx) {
|
|
37
|
+
if (this.hasOwnProperty(idx)) {
|
|
38
|
+
if (isValueSet) {
|
|
39
|
+
value = callback(value, this[idx], idx, this);
|
|
40
|
+
} else {
|
|
41
|
+
value = this[idx];
|
|
42
|
+
isValueSet = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!isValueSet) {
|
|
47
|
+
throw new TypeError('Reduce of empty array with no initial value');
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var isSupportAmd = typeof define === 'function' && define.amd;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* returns whether font is installed or not.
|
|
57
|
+
* @param {String} fontName
|
|
58
|
+
* @return {Boolean}
|
|
59
|
+
*/
|
|
60
|
+
var isFontInstalled = function (fontName) {
|
|
61
|
+
var testFontName = fontName === 'Comic Sans MS' ? 'Courier New' : 'Comic Sans MS';
|
|
62
|
+
var $tester = $('<div>').css({
|
|
63
|
+
position: 'absolute',
|
|
64
|
+
left: '-9999px',
|
|
65
|
+
top: '-9999px',
|
|
66
|
+
fontSize: '200px'
|
|
67
|
+
}).text('mmmmmmmmmwwwwwww').appendTo(document.body);
|
|
68
|
+
|
|
69
|
+
var originalWidth = $tester.css('fontFamily', testFontName).width();
|
|
70
|
+
var width = $tester.css('fontFamily', fontName + ',' + testFontName).width();
|
|
71
|
+
|
|
72
|
+
$tester.remove();
|
|
73
|
+
|
|
74
|
+
return originalWidth !== width;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Object which check platform and agent
|
|
79
|
+
*/
|
|
80
|
+
var agent = {
|
|
81
|
+
isMac: navigator.appVersion.indexOf('Mac') > -1,
|
|
82
|
+
isMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1,
|
|
83
|
+
isFF: navigator.userAgent.indexOf('Firefox') > -1,
|
|
84
|
+
jqueryVersion: parseFloat($.fn.jquery),
|
|
85
|
+
isSupportAmd: isSupportAmd,
|
|
86
|
+
hasCodeMirror: isSupportAmd ? require.specified('CodeMirror') : !!window.CodeMirror,
|
|
87
|
+
isFontInstalled: isFontInstalled,
|
|
88
|
+
isW3CRangeSupport: !!document.createRange
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* func utils (for high-order func's arg)
|
|
93
|
+
*/
|
|
94
|
+
var func = (function () {
|
|
95
|
+
var eq = function (elA) {
|
|
96
|
+
return function (elB) {
|
|
97
|
+
return elA === elB;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
var eq2 = function (elA, elB) {
|
|
102
|
+
return elA === elB;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
var ok = function () {
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
var fail = function () {
|
|
110
|
+
return false;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
var not = function (f) {
|
|
114
|
+
return function () {
|
|
115
|
+
return !f.apply(f, arguments);
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
var self = function (a) {
|
|
120
|
+
return a;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
var idCounter = 0;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* generate a globally-unique id
|
|
127
|
+
*
|
|
128
|
+
* @param {String} [prefix]
|
|
129
|
+
*/
|
|
130
|
+
var uniqueId = function (prefix) {
|
|
131
|
+
var id = ++idCounter + '';
|
|
132
|
+
return prefix ? prefix + id : id;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* returns bnd (bounds) from rect
|
|
137
|
+
*
|
|
138
|
+
* - IE Compatability Issue: http://goo.gl/sRLOAo
|
|
139
|
+
* - Scroll Issue: http://goo.gl/sNjUc
|
|
140
|
+
*
|
|
141
|
+
* @param {Rect} rect
|
|
142
|
+
* @return {Object} bounds
|
|
143
|
+
* @return {Number} bounds.top
|
|
144
|
+
* @return {Number} bounds.left
|
|
145
|
+
* @return {Number} bounds.width
|
|
146
|
+
* @return {Number} bounds.height
|
|
147
|
+
*/
|
|
148
|
+
var rect2bnd = function (rect) {
|
|
149
|
+
var $document = $(document);
|
|
150
|
+
return {
|
|
151
|
+
top: rect.top + $document.scrollTop(),
|
|
152
|
+
left: rect.left + $document.scrollLeft(),
|
|
153
|
+
width: rect.right - rect.left,
|
|
154
|
+
height: rect.bottom - rect.top
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* returns a copy of the object where the keys have become the values and the values the keys.
|
|
160
|
+
* @param {Object} obj
|
|
161
|
+
* @return {Object}
|
|
162
|
+
*/
|
|
163
|
+
var invertObject = function (obj) {
|
|
164
|
+
var inverted = {};
|
|
165
|
+
for (var key in obj) {
|
|
166
|
+
if (obj.hasOwnProperty(key)) {
|
|
167
|
+
inverted[obj[key]] = key;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return inverted;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
eq: eq,
|
|
175
|
+
eq2: eq2,
|
|
176
|
+
ok: ok,
|
|
177
|
+
fail: fail,
|
|
178
|
+
not: not,
|
|
179
|
+
self: self,
|
|
180
|
+
uniqueId: uniqueId,
|
|
181
|
+
rect2bnd: rect2bnd,
|
|
182
|
+
invertObject: invertObject
|
|
183
|
+
};
|
|
184
|
+
})();
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* list utils
|
|
188
|
+
*/
|
|
189
|
+
var list = (function () {
|
|
190
|
+
/**
|
|
191
|
+
* returns the first element of an array.
|
|
192
|
+
* @param {Array} array
|
|
193
|
+
*/
|
|
194
|
+
var head = function (array) {
|
|
195
|
+
return array[0];
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* returns the last element of an array.
|
|
200
|
+
* @param {Array} array
|
|
201
|
+
*/
|
|
202
|
+
var last = function (array) {
|
|
203
|
+
return array[array.length - 1];
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* returns everything but the last entry of the array.
|
|
208
|
+
* @param {Array} array
|
|
209
|
+
*/
|
|
210
|
+
var initial = function (array) {
|
|
211
|
+
return array.slice(0, array.length - 1);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* returns the rest of the elements in an array.
|
|
216
|
+
* @param {Array} array
|
|
217
|
+
*/
|
|
218
|
+
var tail = function (array) {
|
|
219
|
+
return array.slice(1);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* returns next item.
|
|
224
|
+
* @param {Array} array
|
|
225
|
+
*/
|
|
226
|
+
var next = function (array, item) {
|
|
227
|
+
var idx = array.indexOf(item);
|
|
228
|
+
if (idx === -1) { return null; }
|
|
229
|
+
|
|
230
|
+
return array[idx + 1];
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* returns prev item.
|
|
235
|
+
* @param {Array} array
|
|
236
|
+
*/
|
|
237
|
+
var prev = function (array, item) {
|
|
238
|
+
var idx = array.indexOf(item);
|
|
239
|
+
if (idx === -1) { return null; }
|
|
240
|
+
|
|
241
|
+
return array[idx - 1];
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
var all = function (array, pred) {
|
|
245
|
+
for (var idx = 0, sz = array.length; idx < sz; idx ++) {
|
|
246
|
+
if (!pred(array[idx])) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
var contains = function (array, item) {
|
|
254
|
+
return array.indexOf(item) !== -1;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* get sum from a list
|
|
259
|
+
* @param {Array} array - array
|
|
260
|
+
* @param {Function} fn - iterator
|
|
261
|
+
*/
|
|
262
|
+
var sum = function (array, fn) {
|
|
263
|
+
fn = fn || func.self;
|
|
264
|
+
return array.reduce(function (memo, v) {
|
|
265
|
+
return memo + fn(v);
|
|
266
|
+
}, 0);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* returns a copy of the collection with array type.
|
|
271
|
+
* @param {Collection} collection - collection eg) node.childNodes, ...
|
|
272
|
+
*/
|
|
273
|
+
var from = function (collection) {
|
|
274
|
+
var result = [], idx = -1, length = collection.length;
|
|
275
|
+
while (++idx < length) {
|
|
276
|
+
result[idx] = collection[idx];
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* cluster elements by predicate function.
|
|
283
|
+
* @param {Array} array - array
|
|
284
|
+
* @param {Function} fn - predicate function for cluster rule
|
|
285
|
+
* @param {Array[]}
|
|
286
|
+
*/
|
|
287
|
+
var clusterBy = function (array, fn) {
|
|
288
|
+
if (!array.length) { return []; }
|
|
289
|
+
var aTail = tail(array);
|
|
290
|
+
return aTail.reduce(function (memo, v) {
|
|
291
|
+
var aLast = last(memo);
|
|
292
|
+
if (fn(last(aLast), v)) {
|
|
293
|
+
aLast[aLast.length] = v;
|
|
294
|
+
} else {
|
|
295
|
+
memo[memo.length] = [v];
|
|
296
|
+
}
|
|
297
|
+
return memo;
|
|
298
|
+
}, [[head(array)]]);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* returns a copy of the array with all falsy values removed
|
|
303
|
+
* @param {Array} array - array
|
|
304
|
+
* @param {Function} fn - predicate function for cluster rule
|
|
305
|
+
*/
|
|
306
|
+
var compact = function (array) {
|
|
307
|
+
var aResult = [];
|
|
308
|
+
for (var idx = 0, sz = array.length; idx < sz; idx ++) {
|
|
309
|
+
if (array[idx]) { aResult.push(array[idx]); }
|
|
310
|
+
}
|
|
311
|
+
return aResult;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
var unique = function (array) {
|
|
315
|
+
var results = [];
|
|
316
|
+
|
|
317
|
+
for (var idx = 0, sz = array.length; idx < sz; idx ++) {
|
|
318
|
+
if (results.indexOf(array[idx]) === -1) {
|
|
319
|
+
results.push(array[idx]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return results;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return { head: head, last: last, initial: initial, tail: tail,
|
|
327
|
+
prev: prev, next: next, contains: contains,
|
|
328
|
+
all: all, sum: sum, from: from,
|
|
329
|
+
clusterBy: clusterBy, compact: compact, unique: unique };
|
|
330
|
+
})();
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
var NBSP_CHAR = String.fromCharCode(160);
|
|
334
|
+
var ZERO_WIDTH_NBSP_CHAR = '\ufeff';
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Dom functions
|
|
338
|
+
*/
|
|
339
|
+
var dom = (function () {
|
|
340
|
+
/**
|
|
341
|
+
* returns whether node is `note-editable` or not.
|
|
342
|
+
*
|
|
343
|
+
* @param {Node} node
|
|
344
|
+
* @return {Boolean}
|
|
345
|
+
*/
|
|
346
|
+
var isEditable = function (node) {
|
|
347
|
+
return node && $(node).hasClass('note-editable');
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
var isControlSizing = function (node) {
|
|
351
|
+
return node && $(node).hasClass('note-control-sizing');
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* build layoutInfo from $editor(.note-editor)
|
|
356
|
+
*
|
|
357
|
+
* @param {jQuery} $editor
|
|
358
|
+
* @return {Object}
|
|
359
|
+
*/
|
|
360
|
+
var buildLayoutInfo = function ($editor) {
|
|
361
|
+
var makeFinder;
|
|
362
|
+
|
|
363
|
+
// air mode
|
|
364
|
+
if ($editor.hasClass('note-air-editor')) {
|
|
365
|
+
var id = list.last($editor.attr('id').split('-'));
|
|
366
|
+
makeFinder = function (sIdPrefix) {
|
|
367
|
+
return function () { return $(sIdPrefix + id); };
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
editor: function () { return $editor; },
|
|
372
|
+
editable: function () { return $editor; },
|
|
373
|
+
popover: makeFinder('#note-popover-'),
|
|
374
|
+
handle: makeFinder('#note-handle-'),
|
|
375
|
+
dialog: makeFinder('#note-dialog-')
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// frame mode
|
|
379
|
+
} else {
|
|
380
|
+
makeFinder = function (sClassName) {
|
|
381
|
+
return function () { return $editor.find(sClassName); };
|
|
382
|
+
};
|
|
383
|
+
return {
|
|
384
|
+
editor: function () { return $editor; },
|
|
385
|
+
dropzone: makeFinder('.note-dropzone'),
|
|
386
|
+
toolbar: makeFinder('.note-toolbar'),
|
|
387
|
+
editable: makeFinder('.note-editable'),
|
|
388
|
+
codable: makeFinder('.note-codable'),
|
|
389
|
+
statusbar: makeFinder('.note-statusbar'),
|
|
390
|
+
popover: makeFinder('.note-popover'),
|
|
391
|
+
handle: makeFinder('.note-handle'),
|
|
392
|
+
dialog: makeFinder('.note-dialog')
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* returns predicate which judge whether nodeName is same
|
|
399
|
+
* @param {String} sNodeName
|
|
400
|
+
*/
|
|
401
|
+
var makePredByNodeName = function (sNodeName) {
|
|
402
|
+
sNodeName = sNodeName.toUpperCase();
|
|
403
|
+
return function (node) {
|
|
404
|
+
return node && node.nodeName.toUpperCase() === sNodeName;
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
var isText = function (node) {
|
|
409
|
+
return node && node.nodeType === 3;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* ex) br, col, embed, hr, img, input, ...
|
|
414
|
+
* @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
|
|
415
|
+
*/
|
|
416
|
+
var isVoid = function (node) {
|
|
417
|
+
return node && /^BR|^IMG|^HR/.test(node.nodeName.toUpperCase());
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
var isPara = function (node) {
|
|
421
|
+
if (isEditable(node)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
|
|
426
|
+
return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
var isInline = function (node) {
|
|
430
|
+
return !isBodyContainer(node) && !isPara(node);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
var isList = function (node) {
|
|
434
|
+
return node && /^UL|^OL/.test(node.nodeName.toUpperCase());
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
var isCell = function (node) {
|
|
438
|
+
return node && /^TD|^TH/.test(node.nodeName.toUpperCase());
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
var isBodyContainer = function (node) {
|
|
442
|
+
return isCell(node) || isEditable(node);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* returns whether node is textNode on bodyContainer or not.
|
|
447
|
+
*
|
|
448
|
+
* @param {Node} node
|
|
449
|
+
*/
|
|
450
|
+
var isBodyText = function (node) {
|
|
451
|
+
return isText(node) && isBodyContainer(node.parentNode);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
var isParaInline = function (node) {
|
|
455
|
+
return isInline(node) && !!ancestor(node, isPara);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
var isBodyInline = function (node) {
|
|
459
|
+
return isInline(node) && !ancestor(node, isPara);
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* blank HTML for cursor position
|
|
464
|
+
*/
|
|
465
|
+
var blankHTML = agent.isMSIE ? ' ' : '<br>';
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* returns #text's text size or element's childNodes size
|
|
469
|
+
*
|
|
470
|
+
* @param {Node} node
|
|
471
|
+
*/
|
|
472
|
+
var nodeLength = function (node) {
|
|
473
|
+
if (isText(node)) {
|
|
474
|
+
return node.nodeValue.length;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return node.childNodes.length;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* padding blankHTML if node is empty (for cursor position)
|
|
482
|
+
*/
|
|
483
|
+
var paddingBlankHTML = function (node) {
|
|
484
|
+
if (!isVoid(node) && !nodeLength(node)) {
|
|
485
|
+
node.innerHTML = blankHTML;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* find nearest ancestor predicate hit
|
|
491
|
+
*
|
|
492
|
+
* @param {Node} node
|
|
493
|
+
* @param {Function} pred - predicate function
|
|
494
|
+
*/
|
|
495
|
+
var ancestor = function (node, pred) {
|
|
496
|
+
while (node) {
|
|
497
|
+
if (pred(node)) { return node; }
|
|
498
|
+
if (isEditable(node)) { break; }
|
|
499
|
+
|
|
500
|
+
node = node.parentNode;
|
|
501
|
+
}
|
|
502
|
+
return null;
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* returns new array of ancestor nodes (until predicate hit).
|
|
507
|
+
*
|
|
508
|
+
* @param {Node} node
|
|
509
|
+
* @param {Function} [optional] pred - predicate function
|
|
510
|
+
*/
|
|
511
|
+
var listAncestor = function (node, pred) {
|
|
512
|
+
pred = pred || func.fail;
|
|
513
|
+
|
|
514
|
+
var ancestors = [];
|
|
515
|
+
ancestor(node, function (el) {
|
|
516
|
+
if (!isEditable(el)) {
|
|
517
|
+
ancestors.push(el);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return pred(el);
|
|
521
|
+
});
|
|
522
|
+
return ancestors;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* returns common ancestor node between two nodes.
|
|
527
|
+
*
|
|
528
|
+
* @param {Node} nodeA
|
|
529
|
+
* @param {Node} nodeB
|
|
530
|
+
*/
|
|
531
|
+
var commonAncestor = function (nodeA, nodeB) {
|
|
532
|
+
var ancestors = listAncestor(nodeA);
|
|
533
|
+
for (var n = nodeB; n; n = n.parentNode) {
|
|
534
|
+
if ($.inArray(n, ancestors) > -1) { return n; }
|
|
535
|
+
}
|
|
536
|
+
return null; // difference document area
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* listing all Nodes between two nodes.
|
|
541
|
+
* FIXME: nodeA and nodeB must be sorted, use comparePoints later.
|
|
542
|
+
*
|
|
543
|
+
* @param {Node} nodeA
|
|
544
|
+
* @param {Node} nodeB
|
|
545
|
+
*/
|
|
546
|
+
var listBetween = function (nodeA, nodeB) {
|
|
547
|
+
var nodes = [];
|
|
548
|
+
|
|
549
|
+
var isStart = false, isEnd = false;
|
|
550
|
+
|
|
551
|
+
// DFS(depth first search) with commonAcestor.
|
|
552
|
+
(function fnWalk(node) {
|
|
553
|
+
if (!node) { return; } // traverse fisnish
|
|
554
|
+
if (node === nodeA) { isStart = true; } // start point
|
|
555
|
+
if (isStart && !isEnd) { nodes.push(node); } // between
|
|
556
|
+
if (node === nodeB) { isEnd = true; return; } // end point
|
|
557
|
+
|
|
558
|
+
for (var idx = 0, sz = node.childNodes.length; idx < sz; idx++) {
|
|
559
|
+
fnWalk(node.childNodes[idx]);
|
|
560
|
+
}
|
|
561
|
+
})(commonAncestor(nodeA, nodeB));
|
|
562
|
+
|
|
563
|
+
return nodes;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* listing all previous siblings (until predicate hit).
|
|
568
|
+
*
|
|
569
|
+
* @param {Node} node
|
|
570
|
+
* @param {Function} [optional] pred - predicate function
|
|
571
|
+
*/
|
|
572
|
+
var listPrev = function (node, pred) {
|
|
573
|
+
pred = pred || func.fail;
|
|
574
|
+
|
|
575
|
+
var nodes = [];
|
|
576
|
+
while (node) {
|
|
577
|
+
if (pred(node)) { break; }
|
|
578
|
+
nodes.push(node);
|
|
579
|
+
node = node.previousSibling;
|
|
580
|
+
}
|
|
581
|
+
return nodes;
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* listing next siblings (until predicate hit).
|
|
586
|
+
*
|
|
587
|
+
* @param {Node} node
|
|
588
|
+
* @param {Function} [pred] - predicate function
|
|
589
|
+
*/
|
|
590
|
+
var listNext = function (node, pred) {
|
|
591
|
+
pred = pred || func.fail;
|
|
592
|
+
|
|
593
|
+
var nodes = [];
|
|
594
|
+
while (node) {
|
|
595
|
+
if (pred(node)) { break; }
|
|
596
|
+
nodes.push(node);
|
|
597
|
+
node = node.nextSibling;
|
|
598
|
+
}
|
|
599
|
+
return nodes;
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* listing descendant nodes
|
|
604
|
+
*
|
|
605
|
+
* @param {Node} node
|
|
606
|
+
* @param {Function} [pred] - predicate function
|
|
607
|
+
*/
|
|
608
|
+
var listDescendant = function (node, pred) {
|
|
609
|
+
var descendents = [];
|
|
610
|
+
pred = pred || func.ok;
|
|
611
|
+
|
|
612
|
+
// start DFS(depth first search) with node
|
|
613
|
+
(function fnWalk(current) {
|
|
614
|
+
if (node !== current && pred(current)) {
|
|
615
|
+
descendents.push(current);
|
|
616
|
+
}
|
|
617
|
+
for (var idx = 0, sz = current.childNodes.length; idx < sz; idx++) {
|
|
618
|
+
fnWalk(current.childNodes[idx]);
|
|
619
|
+
}
|
|
620
|
+
})(node);
|
|
621
|
+
|
|
622
|
+
return descendents;
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* wrap node with new tag.
|
|
627
|
+
*
|
|
628
|
+
* @param {Node} node
|
|
629
|
+
* @param {Node} tagName of wrapper
|
|
630
|
+
* @return {Node} - wrapper
|
|
631
|
+
*/
|
|
632
|
+
var wrap = function (node, wrapperName) {
|
|
633
|
+
var parent = node.parentNode;
|
|
634
|
+
var wrapper = $('<' + wrapperName + '>')[0];
|
|
635
|
+
|
|
636
|
+
parent.insertBefore(wrapper, node);
|
|
637
|
+
wrapper.appendChild(node);
|
|
638
|
+
|
|
639
|
+
return wrapper;
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* insert node after preceding
|
|
644
|
+
*
|
|
645
|
+
* @param {Node} node
|
|
646
|
+
* @param {Node} preceding - predicate function
|
|
647
|
+
*/
|
|
648
|
+
var insertAfter = function (node, preceding) {
|
|
649
|
+
var next = preceding.nextSibling, parent = preceding.parentNode;
|
|
650
|
+
if (next) {
|
|
651
|
+
parent.insertBefore(node, next);
|
|
652
|
+
} else {
|
|
653
|
+
parent.appendChild(node);
|
|
654
|
+
}
|
|
655
|
+
return node;
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* append elements.
|
|
660
|
+
*
|
|
661
|
+
* @param {Node} node
|
|
662
|
+
* @param {Collection} aChild
|
|
663
|
+
*/
|
|
664
|
+
var appendChildNodes = function (node, aChild) {
|
|
665
|
+
$.each(aChild, function (idx, child) {
|
|
666
|
+
node.appendChild(child);
|
|
667
|
+
});
|
|
668
|
+
return node;
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
var isLeftEdgePoint = function (boundaryPoint) {
|
|
672
|
+
return boundaryPoint.offset === 0;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
var isRightEdgePoint = function (boundaryPoint) {
|
|
676
|
+
return boundaryPoint.offset === nodeLength(boundaryPoint.node);
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* returns whether boundaryPoint is edge or not.
|
|
681
|
+
*
|
|
682
|
+
* @param {BoundaryPoint} boundaryPoitn
|
|
683
|
+
* @return {Boolean}
|
|
684
|
+
*/
|
|
685
|
+
var isEdgePoint = function (boundaryPoint) {
|
|
686
|
+
return boundaryPoint.offset === 0 || isRightEdgePoint(boundaryPoint);
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* returns whether node is right edge of ancestor or not.
|
|
691
|
+
*
|
|
692
|
+
* @param {Node} node
|
|
693
|
+
* @param {Node} ancestor
|
|
694
|
+
* @return {Boolean}
|
|
695
|
+
*/
|
|
696
|
+
var isRightEdgeOf = function (node, ancestor) {
|
|
697
|
+
while (node && node !== ancestor) {
|
|
698
|
+
if (position(node) !== nodeLength(node.parentNode) - 1) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
node = node.parentNode;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return true;
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* returns offset from parent.
|
|
709
|
+
*
|
|
710
|
+
* @param {Node} node
|
|
711
|
+
*/
|
|
712
|
+
var position = function (node) {
|
|
713
|
+
var offset = 0;
|
|
714
|
+
while ((node = node.previousSibling)) { offset += 1; }
|
|
715
|
+
return offset;
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
var hasChildren = function (node) {
|
|
719
|
+
return node && node.childNodes && node.childNodes.length;
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* returns previous boundaryPoint
|
|
724
|
+
*
|
|
725
|
+
* @param {BoundaryPoint} point
|
|
726
|
+
* @param {Boolean} isSkipInnerOffset
|
|
727
|
+
* @return {BoundaryPoint}
|
|
728
|
+
*/
|
|
729
|
+
var prevPoint = function (point, isSkipInnerOffset) {
|
|
730
|
+
var node, offset;
|
|
731
|
+
|
|
732
|
+
if (point.offset === 0) {
|
|
733
|
+
if (isEditable(point.node)) {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
node = point.node.parentNode;
|
|
738
|
+
offset = position(point.node);
|
|
739
|
+
} else if (hasChildren(point.node)) {
|
|
740
|
+
node = point.node.childNodes[offset - 1];
|
|
741
|
+
offset = nodeLength(node);
|
|
742
|
+
} else {
|
|
743
|
+
node = point.node;
|
|
744
|
+
offset = isSkipInnerOffset ? 0 : point.offset - 1;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
node: node,
|
|
749
|
+
offset: offset
|
|
750
|
+
};
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* returns next boundaryPoint
|
|
755
|
+
*
|
|
756
|
+
* @param {BoundaryPoint} point
|
|
757
|
+
* @param {Boolean} isSkipInnerOffset
|
|
758
|
+
* @return {BoundaryPoint}
|
|
759
|
+
*/
|
|
760
|
+
var nextPoint = function (point, isSkipInnerOffset) {
|
|
761
|
+
var node, offset;
|
|
762
|
+
|
|
763
|
+
if (nodeLength(point.node) === point.offset) {
|
|
764
|
+
if (isEditable(point.node)) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
node = point.node.parentNode;
|
|
769
|
+
offset = position(point.node) + 1;
|
|
770
|
+
} else if (hasChildren(point.node)) {
|
|
771
|
+
node = point.node.childNodes[point.offset];
|
|
772
|
+
offset = 0;
|
|
773
|
+
} else {
|
|
774
|
+
node = point.node;
|
|
775
|
+
offset = isSkipInnerOffset ? nodeLength(point.node) : point.offset + 1;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return {
|
|
779
|
+
node: node,
|
|
780
|
+
offset: offset
|
|
781
|
+
};
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* returns whether pointA and pointB is same or not.
|
|
786
|
+
*
|
|
787
|
+
* @param {BoundaryPoint} pointA
|
|
788
|
+
* @param {BoundaryPoint} pointB
|
|
789
|
+
*/
|
|
790
|
+
var isSamePoint = function (pointA, pointB) {
|
|
791
|
+
return pointA.node === pointB.node && pointA.offset === pointB.offset;
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* @param {BoundaryPoint} point
|
|
796
|
+
* @param {Function} pred
|
|
797
|
+
* @return {BoundaryPoint}
|
|
798
|
+
*/
|
|
799
|
+
var prevPointUntil = function (point, pred) {
|
|
800
|
+
while (point) {
|
|
801
|
+
if (pred(point)) {
|
|
802
|
+
return point;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
point = prevPoint(point);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return null;
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* @param {BoundaryPoint} startPoint
|
|
813
|
+
* @param {BoundaryPoint} endPoint
|
|
814
|
+
* @param {Function} handler
|
|
815
|
+
* @param {Boolean} isSkipInnerOffset
|
|
816
|
+
*/
|
|
817
|
+
var walkPoint = function (startPoint, endPoint, handler, isSkipInnerOffset) {
|
|
818
|
+
var point = startPoint;
|
|
819
|
+
|
|
820
|
+
while (point) {
|
|
821
|
+
handler(point);
|
|
822
|
+
|
|
823
|
+
if (isSamePoint(point, endPoint)) {
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
var isSkipOffset = isSkipInnerOffset && startPoint.node !== point.node;
|
|
828
|
+
point = nextPoint(point, isSkipOffset);
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* return offsetPath(array of offset) from ancestor
|
|
834
|
+
*
|
|
835
|
+
* @param {Node} ancestor - ancestor node
|
|
836
|
+
* @param {Node} node
|
|
837
|
+
*/
|
|
838
|
+
var makeOffsetPath = function (ancestor, node) {
|
|
839
|
+
var ancestors = list.initial(listAncestor(node, func.eq(ancestor)));
|
|
840
|
+
return $.map(ancestors, position).reverse();
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* return element from offsetPath(array of offset)
|
|
845
|
+
*
|
|
846
|
+
* @param {Node} ancestor - ancestor node
|
|
847
|
+
* @param {array} aOffset - offsetPath
|
|
848
|
+
*/
|
|
849
|
+
var fromOffsetPath = function (ancestor, aOffset) {
|
|
850
|
+
var current = ancestor;
|
|
851
|
+
for (var i = 0, sz = aOffset.length; i < sz; i++) {
|
|
852
|
+
current = current.childNodes[aOffset[i]];
|
|
853
|
+
}
|
|
854
|
+
return current;
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* split element or #text
|
|
859
|
+
*
|
|
860
|
+
* @param {BoundaryPoint} point
|
|
861
|
+
* @return {Node} right node of boundaryPoint
|
|
862
|
+
*/
|
|
863
|
+
var splitNode = function (point) {
|
|
864
|
+
// split #text
|
|
865
|
+
if (isText(point.node)) {
|
|
866
|
+
// edge case
|
|
867
|
+
if (isLeftEdgePoint(point)) {
|
|
868
|
+
return point.node;
|
|
869
|
+
} else if (isRightEdgePoint(point)) {
|
|
870
|
+
return point.node.nextSibling;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return point.node.splitText(point.offset);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// split element
|
|
877
|
+
var childNode = point.node.childNodes[point.offset];
|
|
878
|
+
var clone = insertAfter(point.node.cloneNode(false), point.node);
|
|
879
|
+
appendChildNodes(clone, listNext(childNode));
|
|
880
|
+
|
|
881
|
+
paddingBlankHTML(point.node);
|
|
882
|
+
paddingBlankHTML(clone);
|
|
883
|
+
|
|
884
|
+
return clone;
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* split tree by point
|
|
889
|
+
*
|
|
890
|
+
* @param {Node} root - split root
|
|
891
|
+
* @param {BoundaryPoint} point
|
|
892
|
+
* @return {Node} right node of boundaryPoint
|
|
893
|
+
*/
|
|
894
|
+
var splitTree = function (root, point) {
|
|
895
|
+
// ex) [#text, <span>, <p>]
|
|
896
|
+
var ancestors = listAncestor(point.node, func.eq(root));
|
|
897
|
+
|
|
898
|
+
if (!ancestors.length) {
|
|
899
|
+
return null;
|
|
900
|
+
} else if (ancestors.length === 1) {
|
|
901
|
+
return splitNode(point);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return ancestors.reduce(function (node, parent) {
|
|
905
|
+
var clone = insertAfter(parent.cloneNode(false), parent);
|
|
906
|
+
|
|
907
|
+
if (node === point.node) {
|
|
908
|
+
node = splitNode(point);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
appendChildNodes(clone, listNext(node));
|
|
912
|
+
|
|
913
|
+
paddingBlankHTML(parent);
|
|
914
|
+
paddingBlankHTML(clone);
|
|
915
|
+
return clone;
|
|
916
|
+
});
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
var createText = function (text) {
|
|
920
|
+
return document.createTextNode(text);
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* remove node, (isRemoveChild: remove child or not)
|
|
925
|
+
* @param {Node} node
|
|
926
|
+
* @param {Boolean} isRemoveChild
|
|
927
|
+
*/
|
|
928
|
+
var remove = function (node, isRemoveChild) {
|
|
929
|
+
if (!node || !node.parentNode) { return; }
|
|
930
|
+
if (node.removeNode) { return node.removeNode(isRemoveChild); }
|
|
931
|
+
|
|
932
|
+
var parent = node.parentNode;
|
|
933
|
+
if (!isRemoveChild) {
|
|
934
|
+
var nodes = [];
|
|
935
|
+
var i, sz;
|
|
936
|
+
for (i = 0, sz = node.childNodes.length; i < sz; i++) {
|
|
937
|
+
nodes.push(node.childNodes[i]);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
for (i = 0, sz = nodes.length; i < sz; i++) {
|
|
941
|
+
parent.insertBefore(nodes[i], node);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
parent.removeChild(node);
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
var isTextarea = makePredByNodeName('TEXTAREA');
|
|
949
|
+
|
|
950
|
+
var html = function ($node) {
|
|
951
|
+
return isTextarea($node[0]) ? $node.val() : $node.html();
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
return {
|
|
955
|
+
NBSP_CHAR: NBSP_CHAR,
|
|
956
|
+
ZERO_WIDTH_NBSP_CHAR: ZERO_WIDTH_NBSP_CHAR,
|
|
957
|
+
blank: blankHTML,
|
|
958
|
+
emptyPara: '<p>' + blankHTML + '</p>',
|
|
959
|
+
isEditable: isEditable,
|
|
960
|
+
isControlSizing: isControlSizing,
|
|
961
|
+
buildLayoutInfo: buildLayoutInfo,
|
|
962
|
+
isText: isText,
|
|
963
|
+
isPara: isPara,
|
|
964
|
+
isInline: isInline,
|
|
965
|
+
isBodyText: isBodyText,
|
|
966
|
+
isBodyInline: isBodyInline,
|
|
967
|
+
isParaInline: isParaInline,
|
|
968
|
+
isList: isList,
|
|
969
|
+
isTable: makePredByNodeName('TABLE'),
|
|
970
|
+
isCell: isCell,
|
|
971
|
+
isBodyContainer: isBodyContainer,
|
|
972
|
+
isAnchor: makePredByNodeName('A'),
|
|
973
|
+
isDiv: makePredByNodeName('DIV'),
|
|
974
|
+
isLi: makePredByNodeName('LI'),
|
|
975
|
+
isSpan: makePredByNodeName('SPAN'),
|
|
976
|
+
isB: makePredByNodeName('B'),
|
|
977
|
+
isU: makePredByNodeName('U'),
|
|
978
|
+
isS: makePredByNodeName('S'),
|
|
979
|
+
isI: makePredByNodeName('I'),
|
|
980
|
+
isImg: makePredByNodeName('IMG'),
|
|
981
|
+
isTextarea: isTextarea,
|
|
982
|
+
nodeLength: nodeLength,
|
|
983
|
+
isLeftEdgePoint: isLeftEdgePoint,
|
|
984
|
+
isRightEdgePoint: isRightEdgePoint,
|
|
985
|
+
isEdgePoint: isEdgePoint,
|
|
986
|
+
isRightEdgeOf: isRightEdgeOf,
|
|
987
|
+
prevPoint: prevPoint,
|
|
988
|
+
nextPoint: nextPoint,
|
|
989
|
+
isSamePoint: isSamePoint,
|
|
990
|
+
prevPointUntil: prevPointUntil,
|
|
991
|
+
walkPoint: walkPoint,
|
|
992
|
+
ancestor: ancestor,
|
|
993
|
+
listAncestor: listAncestor,
|
|
994
|
+
listNext: listNext,
|
|
995
|
+
listPrev: listPrev,
|
|
996
|
+
listDescendant: listDescendant,
|
|
997
|
+
commonAncestor: commonAncestor,
|
|
998
|
+
listBetween: listBetween,
|
|
999
|
+
wrap: wrap,
|
|
1000
|
+
insertAfter: insertAfter,
|
|
1001
|
+
appendChildNodes: appendChildNodes,
|
|
1002
|
+
position: position,
|
|
1003
|
+
makeOffsetPath: makeOffsetPath,
|
|
1004
|
+
fromOffsetPath: fromOffsetPath,
|
|
1005
|
+
splitTree: splitTree,
|
|
1006
|
+
createText: createText,
|
|
1007
|
+
remove: remove,
|
|
1008
|
+
html: html
|
|
1009
|
+
};
|
|
1010
|
+
})();
|
|
1011
|
+
|
|
1012
|
+
var settings = {
|
|
1013
|
+
// version
|
|
1014
|
+
version: '0.5.6',
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* options
|
|
1018
|
+
*/
|
|
1019
|
+
options: {
|
|
1020
|
+
width: null, // set editor width
|
|
1021
|
+
height: null, // set editor height, ex) 300
|
|
1022
|
+
|
|
1023
|
+
minHeight: null, // set minimum height of editor
|
|
1024
|
+
maxHeight: null, // set maximum height of editor
|
|
1025
|
+
|
|
1026
|
+
focus: false, // set focus to editable area after initializing summernote
|
|
1027
|
+
|
|
1028
|
+
tabsize: 4, // size of tab ex) 2 or 4
|
|
1029
|
+
styleWithSpan: true, // style with span (Chrome and FF only)
|
|
1030
|
+
|
|
1031
|
+
disableLinkTarget: false, // hide link Target Checkbox
|
|
1032
|
+
disableDragAndDrop: false, // disable drag and drop event
|
|
1033
|
+
disableResizeEditor: false, // disable resizing editor
|
|
1034
|
+
|
|
1035
|
+
codemirror: { // codemirror options
|
|
1036
|
+
mode: 'text/html',
|
|
1037
|
+
htmlMode: true,
|
|
1038
|
+
lineNumbers: true,
|
|
1039
|
+
autoFormatOnStart: false
|
|
1040
|
+
},
|
|
1041
|
+
|
|
1042
|
+
// language
|
|
1043
|
+
lang: 'en-US', // language 'en-US', 'ko-KR', ...
|
|
1044
|
+
direction: null, // text direction, ex) 'rtl'
|
|
1045
|
+
|
|
1046
|
+
// toolbar
|
|
1047
|
+
toolbar: [
|
|
1048
|
+
['style', ['style']],
|
|
1049
|
+
['font', ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'clear']],
|
|
1050
|
+
['fontname', ['fontname']],
|
|
1051
|
+
// ['fontsize', ['fontsize']], // Still buggy
|
|
1052
|
+
['color', ['color']],
|
|
1053
|
+
['para', ['ul', 'ol', 'paragraph']],
|
|
1054
|
+
['height', ['height']],
|
|
1055
|
+
['table', ['table']],
|
|
1056
|
+
['insert', ['link', 'picture', 'video', 'hr']],
|
|
1057
|
+
['view', ['fullscreen', 'codeview']],
|
|
1058
|
+
['help', ['help']]
|
|
1059
|
+
],
|
|
1060
|
+
|
|
1061
|
+
// air mode: inline editor
|
|
1062
|
+
airMode: false,
|
|
1063
|
+
// airPopover: [
|
|
1064
|
+
// ['style', ['style']],
|
|
1065
|
+
// ['font', ['bold', 'italic', 'underline', 'clear']],
|
|
1066
|
+
// ['fontname', ['fontname']],
|
|
1067
|
+
// ['fontsize', ['fontsize']], // Still buggy
|
|
1068
|
+
// ['color', ['color']],
|
|
1069
|
+
// ['para', ['ul', 'ol', 'paragraph']],
|
|
1070
|
+
// ['height', ['height']],
|
|
1071
|
+
// ['table', ['table']],
|
|
1072
|
+
// ['insert', ['link', 'picture', 'video']],
|
|
1073
|
+
// ['help', ['help']]
|
|
1074
|
+
// ],
|
|
1075
|
+
airPopover: [
|
|
1076
|
+
['color', ['color']],
|
|
1077
|
+
['font', ['bold', 'underline', 'clear']],
|
|
1078
|
+
['para', ['ul', 'paragraph']],
|
|
1079
|
+
['table', ['table']],
|
|
1080
|
+
['insert', ['link', 'picture']]
|
|
1081
|
+
],
|
|
1082
|
+
|
|
1083
|
+
// style tag
|
|
1084
|
+
styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
|
1085
|
+
|
|
1086
|
+
// default fontName
|
|
1087
|
+
defaultFontName: 'Helvetica Neue',
|
|
1088
|
+
|
|
1089
|
+
// fontName
|
|
1090
|
+
fontNames: [
|
|
1091
|
+
'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New',
|
|
1092
|
+
'Helvetica Neue', 'Impact', 'Lucida Grande',
|
|
1093
|
+
'Tahoma', 'Times New Roman', 'Verdana'
|
|
1094
|
+
],
|
|
1095
|
+
|
|
1096
|
+
// pallete colors(n x n)
|
|
1097
|
+
colors: [
|
|
1098
|
+
['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
|
|
1099
|
+
['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
|
|
1100
|
+
['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
|
|
1101
|
+
['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
|
|
1102
|
+
['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
|
|
1103
|
+
['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
|
|
1104
|
+
['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
|
|
1105
|
+
['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
|
|
1106
|
+
],
|
|
1107
|
+
|
|
1108
|
+
// fontSize
|
|
1109
|
+
fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],
|
|
1110
|
+
|
|
1111
|
+
// lineHeight
|
|
1112
|
+
lineHeights: ['1.0', '1.2', '1.4', '1.5', '1.6', '1.8', '2.0', '3.0'],
|
|
1113
|
+
|
|
1114
|
+
// insertTable max size
|
|
1115
|
+
insertTableMaxSize: {
|
|
1116
|
+
col: 10,
|
|
1117
|
+
row: 10
|
|
1118
|
+
},
|
|
1119
|
+
|
|
1120
|
+
// callbacks
|
|
1121
|
+
oninit: null, // initialize
|
|
1122
|
+
onfocus: null, // editable has focus
|
|
1123
|
+
onblur: null, // editable out of focus
|
|
1124
|
+
onenter: null, // enter key pressed
|
|
1125
|
+
onkeyup: null, // keyup
|
|
1126
|
+
onkeydown: null, // keydown
|
|
1127
|
+
onImageUpload: null, // imageUpload
|
|
1128
|
+
onImageUploadError: null, // imageUploadError
|
|
1129
|
+
onToolbarClick: null,
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* manipulate link address when user create link
|
|
1133
|
+
* @param {String} sLinkUrl
|
|
1134
|
+
* @return {String}
|
|
1135
|
+
*/
|
|
1136
|
+
onCreateLink: function (sLinkUrl) {
|
|
1137
|
+
if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
|
|
1138
|
+
sLinkUrl = 'mailto:' + sLinkUrl;
|
|
1139
|
+
} else if (sLinkUrl.indexOf('://') === -1) {
|
|
1140
|
+
sLinkUrl = 'http://' + sLinkUrl;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
return sLinkUrl;
|
|
1144
|
+
},
|
|
1145
|
+
|
|
1146
|
+
keyMap: {
|
|
1147
|
+
pc: {
|
|
1148
|
+
'ENTER': 'insertParagraph',
|
|
1149
|
+
'CTRL+Z': 'undo',
|
|
1150
|
+
'CTRL+Y': 'redo',
|
|
1151
|
+
'TAB': 'tab',
|
|
1152
|
+
'SHIFT+TAB': 'untab',
|
|
1153
|
+
'CTRL+B': 'bold',
|
|
1154
|
+
'CTRL+I': 'italic',
|
|
1155
|
+
'CTRL+U': 'underline',
|
|
1156
|
+
'CTRL+SHIFT+S': 'strikethrough',
|
|
1157
|
+
'CTRL+BACKSLASH': 'removeFormat',
|
|
1158
|
+
'CTRL+SHIFT+L': 'justifyLeft',
|
|
1159
|
+
'CTRL+SHIFT+E': 'justifyCenter',
|
|
1160
|
+
'CTRL+SHIFT+R': 'justifyRight',
|
|
1161
|
+
'CTRL+SHIFT+J': 'justifyFull',
|
|
1162
|
+
'CTRL+SHIFT+NUM7': 'insertUnorderedList',
|
|
1163
|
+
'CTRL+SHIFT+NUM8': 'insertOrderedList',
|
|
1164
|
+
'CTRL+LEFTBRACKET': 'outdent',
|
|
1165
|
+
'CTRL+RIGHTBRACKET': 'indent',
|
|
1166
|
+
'CTRL+NUM0': 'formatPara',
|
|
1167
|
+
'CTRL+NUM1': 'formatH1',
|
|
1168
|
+
'CTRL+NUM2': 'formatH2',
|
|
1169
|
+
'CTRL+NUM3': 'formatH3',
|
|
1170
|
+
'CTRL+NUM4': 'formatH4',
|
|
1171
|
+
'CTRL+NUM5': 'formatH5',
|
|
1172
|
+
'CTRL+NUM6': 'formatH6',
|
|
1173
|
+
'CTRL+ENTER': 'insertHorizontalRule',
|
|
1174
|
+
'CTRL+K': 'showLinkDialog'
|
|
1175
|
+
},
|
|
1176
|
+
|
|
1177
|
+
mac: {
|
|
1178
|
+
'ENTER': 'insertParagraph',
|
|
1179
|
+
'CMD+Z': 'undo',
|
|
1180
|
+
'CMD+SHIFT+Z': 'redo',
|
|
1181
|
+
'TAB': 'tab',
|
|
1182
|
+
'SHIFT+TAB': 'untab',
|
|
1183
|
+
'CMD+B': 'bold',
|
|
1184
|
+
'CMD+I': 'italic',
|
|
1185
|
+
'CMD+U': 'underline',
|
|
1186
|
+
'CMD+SHIFT+S': 'strikethrough',
|
|
1187
|
+
'CMD+BACKSLASH': 'removeFormat',
|
|
1188
|
+
'CMD+SHIFT+L': 'justifyLeft',
|
|
1189
|
+
'CMD+SHIFT+E': 'justifyCenter',
|
|
1190
|
+
'CMD+SHIFT+R': 'justifyRight',
|
|
1191
|
+
'CMD+SHIFT+J': 'justifyFull',
|
|
1192
|
+
'CMD+SHIFT+NUM7': 'insertUnorderedList',
|
|
1193
|
+
'CMD+SHIFT+NUM8': 'insertOrderedList',
|
|
1194
|
+
'CMD+LEFTBRACKET': 'outdent',
|
|
1195
|
+
'CMD+RIGHTBRACKET': 'indent',
|
|
1196
|
+
'CMD+NUM0': 'formatPara',
|
|
1197
|
+
'CMD+NUM1': 'formatH1',
|
|
1198
|
+
'CMD+NUM2': 'formatH2',
|
|
1199
|
+
'CMD+NUM3': 'formatH3',
|
|
1200
|
+
'CMD+NUM4': 'formatH4',
|
|
1201
|
+
'CMD+NUM5': 'formatH5',
|
|
1202
|
+
'CMD+NUM6': 'formatH6',
|
|
1203
|
+
'CMD+ENTER': 'insertHorizontalRule',
|
|
1204
|
+
'CMD+K': 'showLinkDialog'
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
|
|
1209
|
+
// default language: en-US
|
|
1210
|
+
lang: {
|
|
1211
|
+
'en-US': {
|
|
1212
|
+
font: {
|
|
1213
|
+
bold: 'Bold',
|
|
1214
|
+
italic: 'Italic',
|
|
1215
|
+
underline: 'Underline',
|
|
1216
|
+
strikethrough: 'Strikethrough',
|
|
1217
|
+
subscript: 'Subscript',
|
|
1218
|
+
superscript: 'Superscript',
|
|
1219
|
+
clear: 'Remove Font Style',
|
|
1220
|
+
height: 'Line Height',
|
|
1221
|
+
name: 'Font Family',
|
|
1222
|
+
size: 'Font Size'
|
|
1223
|
+
},
|
|
1224
|
+
image: {
|
|
1225
|
+
image: 'Picture',
|
|
1226
|
+
insert: 'Insert Image',
|
|
1227
|
+
resizeFull: 'Resize Full',
|
|
1228
|
+
resizeHalf: 'Resize Half',
|
|
1229
|
+
resizeQuarter: 'Resize Quarter',
|
|
1230
|
+
floatLeft: 'Float Left',
|
|
1231
|
+
floatRight: 'Float Right',
|
|
1232
|
+
floatNone: 'Float None',
|
|
1233
|
+
dragImageHere: 'Drag an image here',
|
|
1234
|
+
selectFromFiles: 'Select from files',
|
|
1235
|
+
url: 'Image URL',
|
|
1236
|
+
remove: 'Remove Image'
|
|
1237
|
+
},
|
|
1238
|
+
link: {
|
|
1239
|
+
link: 'Link',
|
|
1240
|
+
insert: 'Insert Link',
|
|
1241
|
+
unlink: 'Unlink',
|
|
1242
|
+
edit: 'Edit',
|
|
1243
|
+
textToDisplay: 'Text to display',
|
|
1244
|
+
url: 'To what URL should this link go?',
|
|
1245
|
+
openInNewWindow: 'Open in new window'
|
|
1246
|
+
},
|
|
1247
|
+
video: {
|
|
1248
|
+
video: 'Video',
|
|
1249
|
+
videoLink: 'Video Link',
|
|
1250
|
+
insert: 'Insert Video',
|
|
1251
|
+
url: 'Video URL?',
|
|
1252
|
+
providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion or Youku)'
|
|
1253
|
+
},
|
|
1254
|
+
table: {
|
|
1255
|
+
table: 'Table'
|
|
1256
|
+
},
|
|
1257
|
+
hr: {
|
|
1258
|
+
insert: 'Insert Horizontal Rule'
|
|
1259
|
+
},
|
|
1260
|
+
style: {
|
|
1261
|
+
style: 'Style',
|
|
1262
|
+
normal: 'Normal',
|
|
1263
|
+
blockquote: 'Quote',
|
|
1264
|
+
pre: 'Code',
|
|
1265
|
+
h1: 'Header 1',
|
|
1266
|
+
h2: 'Header 2',
|
|
1267
|
+
h3: 'Header 3',
|
|
1268
|
+
h4: 'Header 4',
|
|
1269
|
+
h5: 'Header 5',
|
|
1270
|
+
h6: 'Header 6'
|
|
1271
|
+
},
|
|
1272
|
+
lists: {
|
|
1273
|
+
unordered: 'Unordered list',
|
|
1274
|
+
ordered: 'Ordered list'
|
|
1275
|
+
},
|
|
1276
|
+
options: {
|
|
1277
|
+
help: 'Help',
|
|
1278
|
+
fullscreen: 'Full Screen',
|
|
1279
|
+
codeview: 'Code View'
|
|
1280
|
+
},
|
|
1281
|
+
paragraph: {
|
|
1282
|
+
paragraph: 'Paragraph',
|
|
1283
|
+
outdent: 'Outdent',
|
|
1284
|
+
indent: 'Indent',
|
|
1285
|
+
left: 'Align left',
|
|
1286
|
+
center: 'Align center',
|
|
1287
|
+
right: 'Align right',
|
|
1288
|
+
justify: 'Justify full'
|
|
1289
|
+
},
|
|
1290
|
+
color: {
|
|
1291
|
+
recent: 'Recent Color',
|
|
1292
|
+
more: 'More Color',
|
|
1293
|
+
background: 'Background Color',
|
|
1294
|
+
foreground: 'Foreground Color',
|
|
1295
|
+
transparent: 'Transparent',
|
|
1296
|
+
setTransparent: 'Set transparent',
|
|
1297
|
+
reset: 'Reset',
|
|
1298
|
+
resetToDefault: 'Reset to default'
|
|
1299
|
+
},
|
|
1300
|
+
shortcut: {
|
|
1301
|
+
shortcuts: 'Keyboard shortcuts',
|
|
1302
|
+
close: 'Close',
|
|
1303
|
+
textFormatting: 'Text formatting',
|
|
1304
|
+
action: 'Action',
|
|
1305
|
+
paragraphFormatting: 'Paragraph formatting',
|
|
1306
|
+
documentStyle: 'Document Style'
|
|
1307
|
+
},
|
|
1308
|
+
history: {
|
|
1309
|
+
undo: 'Undo',
|
|
1310
|
+
redo: 'Redo'
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* Async functions which returns `Promise`
|
|
1318
|
+
*/
|
|
1319
|
+
var async = (function () {
|
|
1320
|
+
/**
|
|
1321
|
+
* read contents of file as representing URL
|
|
1322
|
+
*
|
|
1323
|
+
* @param {File} file
|
|
1324
|
+
* @return {Promise} - then: sDataUrl
|
|
1325
|
+
*/
|
|
1326
|
+
var readFileAsDataURL = function (file) {
|
|
1327
|
+
return $.Deferred(function (deferred) {
|
|
1328
|
+
$.extend(new FileReader(), {
|
|
1329
|
+
onload: function (e) {
|
|
1330
|
+
var sDataURL = e.target.result;
|
|
1331
|
+
deferred.resolve(sDataURL);
|
|
1332
|
+
},
|
|
1333
|
+
onerror: function () {
|
|
1334
|
+
deferred.reject(this);
|
|
1335
|
+
}
|
|
1336
|
+
}).readAsDataURL(file);
|
|
1337
|
+
}).promise();
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* create `<image>` from url string
|
|
1342
|
+
*
|
|
1343
|
+
* @param {String} sUrl
|
|
1344
|
+
* @return {Promise} - then: $image
|
|
1345
|
+
*/
|
|
1346
|
+
var createImage = function (sUrl, filename) {
|
|
1347
|
+
return $.Deferred(function (deferred) {
|
|
1348
|
+
$('<img>').one('load', function () {
|
|
1349
|
+
deferred.resolve($(this));
|
|
1350
|
+
}).one('error abort', function () {
|
|
1351
|
+
deferred.reject($(this));
|
|
1352
|
+
}).css({
|
|
1353
|
+
display: 'none'
|
|
1354
|
+
}).appendTo(document.body)
|
|
1355
|
+
.attr('src', sUrl)
|
|
1356
|
+
.attr('data-filename', filename);
|
|
1357
|
+
}).promise();
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
return {
|
|
1361
|
+
readFileAsDataURL: readFileAsDataURL,
|
|
1362
|
+
createImage: createImage
|
|
1363
|
+
};
|
|
1364
|
+
})();
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Object for keycodes.
|
|
1368
|
+
*/
|
|
1369
|
+
var key = {
|
|
1370
|
+
isEdit: function (keyCode) {
|
|
1371
|
+
return [8, 9, 13, 32].indexOf(keyCode) !== -1;
|
|
1372
|
+
},
|
|
1373
|
+
nameFromCode: {
|
|
1374
|
+
'8': 'BACKSPACE',
|
|
1375
|
+
'9': 'TAB',
|
|
1376
|
+
'13': 'ENTER',
|
|
1377
|
+
'32': 'SPACE',
|
|
1378
|
+
|
|
1379
|
+
// Number: 0-9
|
|
1380
|
+
'48': 'NUM0',
|
|
1381
|
+
'49': 'NUM1',
|
|
1382
|
+
'50': 'NUM2',
|
|
1383
|
+
'51': 'NUM3',
|
|
1384
|
+
'52': 'NUM4',
|
|
1385
|
+
'53': 'NUM5',
|
|
1386
|
+
'54': 'NUM6',
|
|
1387
|
+
'55': 'NUM7',
|
|
1388
|
+
'56': 'NUM8',
|
|
1389
|
+
|
|
1390
|
+
// Alphabet: a-z
|
|
1391
|
+
'66': 'B',
|
|
1392
|
+
'69': 'E',
|
|
1393
|
+
'73': 'I',
|
|
1394
|
+
'74': 'J',
|
|
1395
|
+
'75': 'K',
|
|
1396
|
+
'76': 'L',
|
|
1397
|
+
'82': 'R',
|
|
1398
|
+
'83': 'S',
|
|
1399
|
+
'85': 'U',
|
|
1400
|
+
'89': 'Y',
|
|
1401
|
+
'90': 'Z',
|
|
1402
|
+
|
|
1403
|
+
'191': 'SLASH',
|
|
1404
|
+
'219': 'LEFTBRACKET',
|
|
1405
|
+
'220': 'BACKSLASH',
|
|
1406
|
+
'221': 'RIGHTBRACKET'
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Style
|
|
1412
|
+
* @class
|
|
1413
|
+
*/
|
|
1414
|
+
var Style = function () {
|
|
1415
|
+
/**
|
|
1416
|
+
* passing an array of style properties to .css()
|
|
1417
|
+
* will result in an object of property-value pairs.
|
|
1418
|
+
* (compability with version < 1.9)
|
|
1419
|
+
*
|
|
1420
|
+
* @param {jQuery} $obj
|
|
1421
|
+
* @param {Array} propertyNames - An array of one or more CSS properties.
|
|
1422
|
+
* @returns {Object}
|
|
1423
|
+
*/
|
|
1424
|
+
var jQueryCSS = function ($obj, propertyNames) {
|
|
1425
|
+
if (agent.jqueryVersion < 1.9) {
|
|
1426
|
+
var result = {};
|
|
1427
|
+
$.each(propertyNames, function (idx, propertyName) {
|
|
1428
|
+
result[propertyName] = $obj.css(propertyName);
|
|
1429
|
+
});
|
|
1430
|
+
return result;
|
|
1431
|
+
}
|
|
1432
|
+
return $obj.css.call($obj, propertyNames);
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* paragraph level style
|
|
1437
|
+
*
|
|
1438
|
+
* @param {WrappedRange} rng
|
|
1439
|
+
* @param {Object} styleInfo
|
|
1440
|
+
*/
|
|
1441
|
+
this.stylePara = function (rng, styleInfo) {
|
|
1442
|
+
$.each(rng.nodes(dom.isPara, {
|
|
1443
|
+
includeAncestor: true
|
|
1444
|
+
}), function (idx, para) {
|
|
1445
|
+
$(para).css(styleInfo);
|
|
1446
|
+
});
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* get current style on cursor
|
|
1451
|
+
*
|
|
1452
|
+
* @param {WrappedRange} rng
|
|
1453
|
+
* @param {Node} target - target element on event
|
|
1454
|
+
* @return {Object} - object contains style properties.
|
|
1455
|
+
*/
|
|
1456
|
+
this.current = function (rng, target) {
|
|
1457
|
+
var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
|
|
1458
|
+
var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height'];
|
|
1459
|
+
var styleInfo = jQueryCSS($cont, properties) || {};
|
|
1460
|
+
|
|
1461
|
+
styleInfo['font-size'] = parseInt(styleInfo['font-size'], 10);
|
|
1462
|
+
|
|
1463
|
+
// document.queryCommandState for toggle state
|
|
1464
|
+
styleInfo['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal';
|
|
1465
|
+
styleInfo['font-italic'] = document.queryCommandState('italic') ? 'italic' : 'normal';
|
|
1466
|
+
styleInfo['font-underline'] = document.queryCommandState('underline') ? 'underline' : 'normal';
|
|
1467
|
+
styleInfo['font-strikethrough'] = document.queryCommandState('strikeThrough') ? 'strikethrough' : 'normal';
|
|
1468
|
+
styleInfo['font-superscript'] = document.queryCommandState('superscript') ? 'superscript' : 'normal';
|
|
1469
|
+
styleInfo['font-subscript'] = document.queryCommandState('subscript') ? 'subscript' : 'normal';
|
|
1470
|
+
|
|
1471
|
+
// list-style-type to list-style(unordered, ordered)
|
|
1472
|
+
if (!rng.isOnList()) {
|
|
1473
|
+
styleInfo['list-style'] = 'none';
|
|
1474
|
+
} else {
|
|
1475
|
+
var aOrderedType = ['circle', 'disc', 'disc-leading-zero', 'square'];
|
|
1476
|
+
var isUnordered = $.inArray(styleInfo['list-style-type'], aOrderedType) > -1;
|
|
1477
|
+
styleInfo['list-style'] = isUnordered ? 'unordered' : 'ordered';
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
var para = dom.ancestor(rng.sc, dom.isPara);
|
|
1481
|
+
if (para && para.style['line-height']) {
|
|
1482
|
+
styleInfo['line-height'] = para.style.lineHeight;
|
|
1483
|
+
} else {
|
|
1484
|
+
var lineHeight = parseInt(styleInfo['line-height'], 10) / parseInt(styleInfo['font-size'], 10);
|
|
1485
|
+
styleInfo['line-height'] = lineHeight.toFixed(1);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
styleInfo.image = dom.isImg(target) && target;
|
|
1489
|
+
styleInfo.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
|
|
1490
|
+
styleInfo.ancestors = dom.listAncestor(rng.sc, dom.isEditable);
|
|
1491
|
+
styleInfo.range = rng;
|
|
1492
|
+
|
|
1493
|
+
return styleInfo;
|
|
1494
|
+
};
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* related data structure
|
|
1500
|
+
* - {BoundaryPoint}: a point of dom tree
|
|
1501
|
+
* - {BoundaryPoints}: two boundaryPoints corresponding to the start and the end of the Range
|
|
1502
|
+
*
|
|
1503
|
+
* @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Position
|
|
1504
|
+
*/
|
|
1505
|
+
var range = (function () {
|
|
1506
|
+
/**
|
|
1507
|
+
* return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
|
|
1508
|
+
*
|
|
1509
|
+
* @param {TextRange} textRange
|
|
1510
|
+
* @param {Boolean} isStart
|
|
1511
|
+
* @return {BoundaryPoint}
|
|
1512
|
+
*/
|
|
1513
|
+
var textRangeToPoint = function (textRange, isStart) {
|
|
1514
|
+
var container = textRange.parentElement(), offset;
|
|
1515
|
+
|
|
1516
|
+
var tester = document.body.createTextRange(), prevContainer;
|
|
1517
|
+
var childNodes = list.from(container.childNodes);
|
|
1518
|
+
for (offset = 0; offset < childNodes.length; offset++) {
|
|
1519
|
+
if (dom.isText(childNodes[offset])) {
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
tester.moveToElementText(childNodes[offset]);
|
|
1523
|
+
if (tester.compareEndPoints('StartToStart', textRange) >= 0) {
|
|
1524
|
+
break;
|
|
1525
|
+
}
|
|
1526
|
+
prevContainer = childNodes[offset];
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
if (offset !== 0 && dom.isText(childNodes[offset - 1])) {
|
|
1530
|
+
var textRangeStart = document.body.createTextRange(), curTextNode = null;
|
|
1531
|
+
textRangeStart.moveToElementText(prevContainer || container);
|
|
1532
|
+
textRangeStart.collapse(!prevContainer);
|
|
1533
|
+
curTextNode = prevContainer ? prevContainer.nextSibling : container.firstChild;
|
|
1534
|
+
|
|
1535
|
+
var pointTester = textRange.duplicate();
|
|
1536
|
+
pointTester.setEndPoint('StartToStart', textRangeStart);
|
|
1537
|
+
var textCount = pointTester.text.replace(/[\r\n]/g, '').length;
|
|
1538
|
+
|
|
1539
|
+
while (textCount > curTextNode.nodeValue.length && curTextNode.nextSibling) {
|
|
1540
|
+
textCount -= curTextNode.nodeValue.length;
|
|
1541
|
+
curTextNode = curTextNode.nextSibling;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/* jshint ignore:start */
|
|
1545
|
+
var dummy = curTextNode.nodeValue; // enforce IE to re-reference curTextNode, hack
|
|
1546
|
+
/* jshint ignore:end */
|
|
1547
|
+
|
|
1548
|
+
if (isStart && curTextNode.nextSibling && dom.isText(curTextNode.nextSibling) &&
|
|
1549
|
+
textCount === curTextNode.nodeValue.length) {
|
|
1550
|
+
textCount -= curTextNode.nodeValue.length;
|
|
1551
|
+
curTextNode = curTextNode.nextSibling;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
container = curTextNode;
|
|
1555
|
+
offset = textCount;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
return {
|
|
1559
|
+
cont: container,
|
|
1560
|
+
offset: offset
|
|
1561
|
+
};
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* return TextRange from boundary point (inspired by google closure-library)
|
|
1566
|
+
* @param {BoundaryPoint} point
|
|
1567
|
+
* @return {TextRange}
|
|
1568
|
+
*/
|
|
1569
|
+
var pointToTextRange = function (point) {
|
|
1570
|
+
var textRangeInfo = function (container, offset) {
|
|
1571
|
+
var node, isCollapseToStart;
|
|
1572
|
+
|
|
1573
|
+
if (dom.isText(container)) {
|
|
1574
|
+
var prevTextNodes = dom.listPrev(container, func.not(dom.isText));
|
|
1575
|
+
var prevContainer = list.last(prevTextNodes).previousSibling;
|
|
1576
|
+
node = prevContainer || container.parentNode;
|
|
1577
|
+
offset += list.sum(list.tail(prevTextNodes), dom.nodeLength);
|
|
1578
|
+
isCollapseToStart = !prevContainer;
|
|
1579
|
+
} else {
|
|
1580
|
+
node = container.childNodes[offset] || container;
|
|
1581
|
+
if (dom.isText(node)) {
|
|
1582
|
+
return textRangeInfo(node, 0);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
offset = 0;
|
|
1586
|
+
isCollapseToStart = false;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
return {
|
|
1590
|
+
node: node,
|
|
1591
|
+
collapseToStart: isCollapseToStart,
|
|
1592
|
+
offset: offset
|
|
1593
|
+
};
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
var textRange = document.body.createTextRange();
|
|
1597
|
+
var info = textRangeInfo(point.node, point.offset);
|
|
1598
|
+
|
|
1599
|
+
textRange.moveToElementText(info.node);
|
|
1600
|
+
textRange.collapse(info.collapseToStart);
|
|
1601
|
+
textRange.moveStart('character', info.offset);
|
|
1602
|
+
return textRange;
|
|
1603
|
+
};
|
|
1604
|
+
|
|
1605
|
+
/**
|
|
1606
|
+
* Wrapped Range
|
|
1607
|
+
*
|
|
1608
|
+
* @param {Node} sc - start container
|
|
1609
|
+
* @param {Number} so - start offset
|
|
1610
|
+
* @param {Node} ec - end container
|
|
1611
|
+
* @param {Number} eo - end offset
|
|
1612
|
+
*/
|
|
1613
|
+
var WrappedRange = function (sc, so, ec, eo) {
|
|
1614
|
+
this.sc = sc;
|
|
1615
|
+
this.so = so;
|
|
1616
|
+
this.ec = ec;
|
|
1617
|
+
this.eo = eo;
|
|
1618
|
+
|
|
1619
|
+
// nativeRange: get nativeRange from sc, so, ec, eo
|
|
1620
|
+
var nativeRange = function () {
|
|
1621
|
+
if (agent.isW3CRangeSupport) {
|
|
1622
|
+
var w3cRange = document.createRange();
|
|
1623
|
+
w3cRange.setStart(sc, so);
|
|
1624
|
+
w3cRange.setEnd(ec, eo);
|
|
1625
|
+
|
|
1626
|
+
return w3cRange;
|
|
1627
|
+
} else {
|
|
1628
|
+
var textRange = pointToTextRange({
|
|
1629
|
+
node: sc,
|
|
1630
|
+
offset: so
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
textRange.setEndPoint('EndToEnd', pointToTextRange({
|
|
1634
|
+
node: ec,
|
|
1635
|
+
offset: eo
|
|
1636
|
+
}));
|
|
1637
|
+
|
|
1638
|
+
return textRange;
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
this.getPoints = function () {
|
|
1643
|
+
return {
|
|
1644
|
+
sc: sc,
|
|
1645
|
+
so: so,
|
|
1646
|
+
ec: ec,
|
|
1647
|
+
eo: eo
|
|
1648
|
+
};
|
|
1649
|
+
};
|
|
1650
|
+
|
|
1651
|
+
this.getStartPoint = function () {
|
|
1652
|
+
return {
|
|
1653
|
+
node: sc,
|
|
1654
|
+
offset: so
|
|
1655
|
+
};
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
this.getEndPoint = function () {
|
|
1659
|
+
return {
|
|
1660
|
+
node: ec,
|
|
1661
|
+
offset: eo
|
|
1662
|
+
};
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* select update visible range
|
|
1667
|
+
*/
|
|
1668
|
+
this.select = function () {
|
|
1669
|
+
var nativeRng = nativeRange();
|
|
1670
|
+
if (agent.isW3CRangeSupport) {
|
|
1671
|
+
var selection = document.getSelection();
|
|
1672
|
+
if (selection.rangeCount > 0) {
|
|
1673
|
+
selection.removeAllRanges();
|
|
1674
|
+
}
|
|
1675
|
+
selection.addRange(nativeRng);
|
|
1676
|
+
} else {
|
|
1677
|
+
nativeRng.select();
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
/**
|
|
1682
|
+
* returns matched nodes on range
|
|
1683
|
+
*
|
|
1684
|
+
* @param {Function} [pred] - predicate function
|
|
1685
|
+
* @param {Object} [options]
|
|
1686
|
+
* @param {Boolean} [options.includeAncestor]
|
|
1687
|
+
* @param {Boolean} [options.fullyContains]
|
|
1688
|
+
* @return {Node[]}
|
|
1689
|
+
*/
|
|
1690
|
+
this.nodes = function (pred, options) {
|
|
1691
|
+
pred = pred || func.ok;
|
|
1692
|
+
|
|
1693
|
+
var includeAncestor = options && options.includeAncestor;
|
|
1694
|
+
var fullyContains = options && options.fullyContains;
|
|
1695
|
+
|
|
1696
|
+
// TODO compare points and sort
|
|
1697
|
+
var startPoint = this.getStartPoint();
|
|
1698
|
+
var endPoint = this.getEndPoint();
|
|
1699
|
+
|
|
1700
|
+
var nodes = [];
|
|
1701
|
+
var leftEdgeNodes = [];
|
|
1702
|
+
|
|
1703
|
+
dom.walkPoint(startPoint, endPoint, function (point) {
|
|
1704
|
+
if (dom.isEditable(point.node)) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
var node;
|
|
1709
|
+
if (fullyContains) {
|
|
1710
|
+
if (dom.isLeftEdgePoint(point)) {
|
|
1711
|
+
leftEdgeNodes.push(point.node);
|
|
1712
|
+
}
|
|
1713
|
+
if (dom.isRightEdgePoint(point) &&
|
|
1714
|
+
list.contains(leftEdgeNodes, point.node)) {
|
|
1715
|
+
node = point.node;
|
|
1716
|
+
}
|
|
1717
|
+
} else if (includeAncestor) {
|
|
1718
|
+
node = dom.ancestor(point.node, pred);
|
|
1719
|
+
} else {
|
|
1720
|
+
node = point.node;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
if (node && pred(node)) {
|
|
1724
|
+
nodes.push(node);
|
|
1725
|
+
}
|
|
1726
|
+
}, true);
|
|
1727
|
+
|
|
1728
|
+
return list.unique(nodes);
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* returns commonAncestor of range
|
|
1733
|
+
* @return {Element} - commonAncestor
|
|
1734
|
+
*/
|
|
1735
|
+
this.commonAncestor = function () {
|
|
1736
|
+
return dom.commonAncestor(sc, ec);
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* returns expanded range by pred
|
|
1741
|
+
*
|
|
1742
|
+
* @param {Function} pred - predicate function
|
|
1743
|
+
* @return {WrappedRange}
|
|
1744
|
+
*/
|
|
1745
|
+
this.expand = function (pred) {
|
|
1746
|
+
var startAncestor = dom.ancestor(sc, pred);
|
|
1747
|
+
var endAncestor = dom.ancestor(ec, pred);
|
|
1748
|
+
|
|
1749
|
+
if (!startAncestor && !endAncestor) {
|
|
1750
|
+
return new WrappedRange(sc, so, ec, eo);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
var boundaryPoints = this.getPoints();
|
|
1754
|
+
|
|
1755
|
+
if (startAncestor) {
|
|
1756
|
+
boundaryPoints.sc = startAncestor;
|
|
1757
|
+
boundaryPoints.so = 0;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
if (endAncestor) {
|
|
1761
|
+
boundaryPoints.ec = endAncestor;
|
|
1762
|
+
boundaryPoints.eo = dom.nodeLength(endAncestor);
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
return new WrappedRange(
|
|
1766
|
+
boundaryPoints.sc,
|
|
1767
|
+
boundaryPoints.so,
|
|
1768
|
+
boundaryPoints.ec,
|
|
1769
|
+
boundaryPoints.eo
|
|
1770
|
+
);
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* @param {Boolean} isCollapseToStart
|
|
1775
|
+
* @return {WrappedRange}
|
|
1776
|
+
*/
|
|
1777
|
+
this.collapse = function (isCollapseToStart) {
|
|
1778
|
+
if (isCollapseToStart) {
|
|
1779
|
+
return new WrappedRange(sc, so, sc, so);
|
|
1780
|
+
} else {
|
|
1781
|
+
return new WrappedRange(ec, eo, ec, eo);
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* splitText on range
|
|
1787
|
+
*/
|
|
1788
|
+
this.splitText = function () {
|
|
1789
|
+
var isSameContainer = sc === ec;
|
|
1790
|
+
var boundaryPoints = this.getPoints();
|
|
1791
|
+
|
|
1792
|
+
if (dom.isText(ec) && !dom.isEdgePoint(this.getEndPoint())) {
|
|
1793
|
+
ec.splitText(eo);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
if (dom.isText(sc) && !dom.isEdgePoint(this.getStartPoint())) {
|
|
1797
|
+
boundaryPoints.sc = sc.splitText(so);
|
|
1798
|
+
boundaryPoints.so = 0;
|
|
1799
|
+
|
|
1800
|
+
if (isSameContainer) {
|
|
1801
|
+
boundaryPoints.ec = boundaryPoints.sc;
|
|
1802
|
+
boundaryPoints.eo = eo - so;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
return new WrappedRange(
|
|
1807
|
+
boundaryPoints.sc,
|
|
1808
|
+
boundaryPoints.so,
|
|
1809
|
+
boundaryPoints.ec,
|
|
1810
|
+
boundaryPoints.eo
|
|
1811
|
+
);
|
|
1812
|
+
};
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* delete contents on range
|
|
1816
|
+
* @return {WrappedRange}
|
|
1817
|
+
*/
|
|
1818
|
+
this.deleteContents = function () {
|
|
1819
|
+
if (this.isCollapsed()) {
|
|
1820
|
+
return this;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
var rng = this.splitText();
|
|
1824
|
+
var nodes = rng.nodes(null, {
|
|
1825
|
+
fullyContains: true
|
|
1826
|
+
});
|
|
1827
|
+
|
|
1828
|
+
var point = dom.prevPointUntil(rng.getStartPoint(), function (point) {
|
|
1829
|
+
return !list.contains(nodes, point.node);
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
$.each(nodes, function (idx, node) {
|
|
1833
|
+
dom.remove(node, false);
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
return new WrappedRange(
|
|
1837
|
+
point.node,
|
|
1838
|
+
point.offset,
|
|
1839
|
+
point.node,
|
|
1840
|
+
point.offset
|
|
1841
|
+
);
|
|
1842
|
+
};
|
|
1843
|
+
|
|
1844
|
+
/**
|
|
1845
|
+
* makeIsOn: return isOn(pred) function
|
|
1846
|
+
*/
|
|
1847
|
+
var makeIsOn = function (pred) {
|
|
1848
|
+
return function () {
|
|
1849
|
+
var ancestor = dom.ancestor(sc, pred);
|
|
1850
|
+
return !!ancestor && (ancestor === dom.ancestor(ec, pred));
|
|
1851
|
+
};
|
|
1852
|
+
};
|
|
1853
|
+
|
|
1854
|
+
// isOnEditable: judge whether range is on editable or not
|
|
1855
|
+
this.isOnEditable = makeIsOn(dom.isEditable);
|
|
1856
|
+
// isOnList: judge whether range is on list node or not
|
|
1857
|
+
this.isOnList = makeIsOn(dom.isList);
|
|
1858
|
+
// isOnAnchor: judge whether range is on anchor node or not
|
|
1859
|
+
this.isOnAnchor = makeIsOn(dom.isAnchor);
|
|
1860
|
+
// isOnAnchor: judge whether range is on cell node or not
|
|
1861
|
+
this.isOnCell = makeIsOn(dom.isCell);
|
|
1862
|
+
|
|
1863
|
+
/**
|
|
1864
|
+
* returns whether range was collapsed or not
|
|
1865
|
+
*/
|
|
1866
|
+
this.isCollapsed = function () {
|
|
1867
|
+
return sc === ec && so === eo;
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* wrap inline nodes which children of body with paragraph
|
|
1872
|
+
*
|
|
1873
|
+
* @return {WrappedRange}
|
|
1874
|
+
*/
|
|
1875
|
+
this.wrapBodyInlineWithPara = function () {
|
|
1876
|
+
if (dom.isEditable(sc) && !sc.childNodes[so]) {
|
|
1877
|
+
return new WrappedRange(sc.appendChild($(dom.emptyPara)[0]), 0);
|
|
1878
|
+
} else if (!dom.isInline(sc) || dom.isParaInline(sc)) {
|
|
1879
|
+
return this;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// find inline top ancestor
|
|
1883
|
+
var ancestors = dom.listAncestor(sc, func.not(dom.isInline));
|
|
1884
|
+
var topAncestor = list.last(ancestors);
|
|
1885
|
+
if (!dom.isInline(topAncestor)) {
|
|
1886
|
+
topAncestor = ancestors[ancestors.length - 2] || sc.childNodes[so];
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// siblings not in paragraph
|
|
1890
|
+
var inlineSiblings = dom.listPrev(topAncestor, dom.isParaInline).reverse();
|
|
1891
|
+
inlineSiblings = inlineSiblings.concat(dom.listNext(topAncestor.nextSibling, dom.isParaInline));
|
|
1892
|
+
|
|
1893
|
+
// wrap with paragraph
|
|
1894
|
+
if (inlineSiblings.length) {
|
|
1895
|
+
var para = dom.wrap(list.head(inlineSiblings), 'p');
|
|
1896
|
+
dom.appendChildNodes(para, list.tail(inlineSiblings));
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
return this;
|
|
1900
|
+
};
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* insert node at current cursor
|
|
1904
|
+
*
|
|
1905
|
+
* @param {Node} node
|
|
1906
|
+
* @param {Boolean} [isInline]
|
|
1907
|
+
* @return {Node}
|
|
1908
|
+
*/
|
|
1909
|
+
this.insertNode = function (node, isInline) {
|
|
1910
|
+
var rng = this.wrapBodyInlineWithPara();
|
|
1911
|
+
var point = rng.getStartPoint();
|
|
1912
|
+
|
|
1913
|
+
var splitRoot, container, pivot;
|
|
1914
|
+
if (isInline) {
|
|
1915
|
+
container = dom.isPara(point.node) ? point.node : point.node.parentNode;
|
|
1916
|
+
if (dom.isPara(point.node)) {
|
|
1917
|
+
pivot = point.node.childNodes[point.offset];
|
|
1918
|
+
} else {
|
|
1919
|
+
pivot = dom.splitTree(point.node, point);
|
|
1920
|
+
}
|
|
1921
|
+
} else {
|
|
1922
|
+
// splitRoot will be childNode of container
|
|
1923
|
+
var ancestors = dom.listAncestor(point.node, dom.isBodyContainer);
|
|
1924
|
+
var topAncestor = list.last(ancestors) || point.node;
|
|
1925
|
+
|
|
1926
|
+
if (dom.isBodyContainer(topAncestor)) {
|
|
1927
|
+
splitRoot = ancestors[ancestors.length - 2];
|
|
1928
|
+
container = topAncestor;
|
|
1929
|
+
} else {
|
|
1930
|
+
splitRoot = topAncestor;
|
|
1931
|
+
container = splitRoot.parentNode;
|
|
1932
|
+
}
|
|
1933
|
+
pivot = splitRoot && dom.splitTree(splitRoot, point);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
if (pivot) {
|
|
1937
|
+
pivot.parentNode.insertBefore(node, pivot);
|
|
1938
|
+
} else {
|
|
1939
|
+
container.appendChild(node);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
return node;
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
this.toString = function () {
|
|
1946
|
+
var nativeRng = nativeRange();
|
|
1947
|
+
return agent.isW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
/**
|
|
1951
|
+
* create offsetPath bookmark
|
|
1952
|
+
* @param {Node} editable
|
|
1953
|
+
*/
|
|
1954
|
+
this.bookmark = function (editable) {
|
|
1955
|
+
return {
|
|
1956
|
+
s: {
|
|
1957
|
+
path: dom.makeOffsetPath(editable, sc),
|
|
1958
|
+
offset: so
|
|
1959
|
+
},
|
|
1960
|
+
e: {
|
|
1961
|
+
path: dom.makeOffsetPath(editable, ec),
|
|
1962
|
+
offset: eo
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
/**
|
|
1968
|
+
* getClientRects
|
|
1969
|
+
* @return {Rect[]}
|
|
1970
|
+
*/
|
|
1971
|
+
this.getClientRects = function () {
|
|
1972
|
+
var nativeRng = nativeRange();
|
|
1973
|
+
return nativeRng.getClientRects();
|
|
1974
|
+
};
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1977
|
+
return {
|
|
1978
|
+
/**
|
|
1979
|
+
* create Range Object From arguments or Browser Selection
|
|
1980
|
+
*
|
|
1981
|
+
* @param {Node} sc - start container
|
|
1982
|
+
* @param {Number} so - start offset
|
|
1983
|
+
* @param {Node} ec - end container
|
|
1984
|
+
* @param {Number} eo - end offset
|
|
1985
|
+
*/
|
|
1986
|
+
create : function (sc, so, ec, eo) {
|
|
1987
|
+
if (!arguments.length) { // from Browser Selection
|
|
1988
|
+
if (agent.isW3CRangeSupport) {
|
|
1989
|
+
var selection = document.getSelection();
|
|
1990
|
+
if (selection.rangeCount === 0) {
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
var nativeRng = selection.getRangeAt(0);
|
|
1995
|
+
sc = nativeRng.startContainer;
|
|
1996
|
+
so = nativeRng.startOffset;
|
|
1997
|
+
ec = nativeRng.endContainer;
|
|
1998
|
+
eo = nativeRng.endOffset;
|
|
1999
|
+
} else { // IE8: TextRange
|
|
2000
|
+
var textRange = document.selection.createRange();
|
|
2001
|
+
var textRangeEnd = textRange.duplicate();
|
|
2002
|
+
textRangeEnd.collapse(false);
|
|
2003
|
+
var textRangeStart = textRange;
|
|
2004
|
+
textRangeStart.collapse(true);
|
|
2005
|
+
|
|
2006
|
+
var startPoint = textRangeToPoint(textRangeStart, true),
|
|
2007
|
+
endPoint = textRangeToPoint(textRangeEnd, false);
|
|
2008
|
+
|
|
2009
|
+
sc = startPoint.cont;
|
|
2010
|
+
so = startPoint.offset;
|
|
2011
|
+
ec = endPoint.cont;
|
|
2012
|
+
eo = endPoint.offset;
|
|
2013
|
+
}
|
|
2014
|
+
} else if (arguments.length === 2) { //collapsed
|
|
2015
|
+
ec = sc;
|
|
2016
|
+
eo = so;
|
|
2017
|
+
}
|
|
2018
|
+
return new WrappedRange(sc, so, ec, eo);
|
|
2019
|
+
},
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* create WrappedRange from node
|
|
2023
|
+
*
|
|
2024
|
+
* @param {Node} node
|
|
2025
|
+
* @return {WrappedRange}
|
|
2026
|
+
*/
|
|
2027
|
+
createFromNode: function (node) {
|
|
2028
|
+
return this.create(node, 0, node, 1);
|
|
2029
|
+
},
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* create WrappedRange from Bookmark
|
|
2033
|
+
*
|
|
2034
|
+
* @param {Node} editable
|
|
2035
|
+
* @param {Obkect} bookmark
|
|
2036
|
+
* @return {WrappedRange}
|
|
2037
|
+
*/
|
|
2038
|
+
createFromBookmark : function (editable, bookmark) {
|
|
2039
|
+
var sc = dom.fromOffsetPath(editable, bookmark.s.path);
|
|
2040
|
+
var so = bookmark.s.offset;
|
|
2041
|
+
var ec = dom.fromOffsetPath(editable, bookmark.e.path);
|
|
2042
|
+
var eo = bookmark.e.offset;
|
|
2043
|
+
return new WrappedRange(sc, so, ec, eo);
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
})();
|
|
2047
|
+
|
|
2048
|
+
/**
|
|
2049
|
+
* Table
|
|
2050
|
+
* @class
|
|
2051
|
+
*/
|
|
2052
|
+
var Table = function () {
|
|
2053
|
+
/**
|
|
2054
|
+
* handle tab key
|
|
2055
|
+
*
|
|
2056
|
+
* @param {WrappedRange} rng
|
|
2057
|
+
* @param {Boolean} isShift
|
|
2058
|
+
*/
|
|
2059
|
+
this.tab = function (rng, isShift) {
|
|
2060
|
+
var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
|
|
2061
|
+
var table = dom.ancestor(cell, dom.isTable);
|
|
2062
|
+
var cells = dom.listDescendant(table, dom.isCell);
|
|
2063
|
+
|
|
2064
|
+
var nextCell = list[isShift ? 'prev' : 'next'](cells, cell);
|
|
2065
|
+
if (nextCell) {
|
|
2066
|
+
range.create(nextCell, 0).select();
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
|
|
2070
|
+
/**
|
|
2071
|
+
* create empty table element
|
|
2072
|
+
*
|
|
2073
|
+
* @param {Number} rowCount
|
|
2074
|
+
* @param {Number} colCount
|
|
2075
|
+
* @return {Node}
|
|
2076
|
+
*/
|
|
2077
|
+
this.createTable = function (colCount, rowCount) {
|
|
2078
|
+
var tds = [], tdHTML;
|
|
2079
|
+
for (var idxCol = 0; idxCol < colCount; idxCol++) {
|
|
2080
|
+
tds.push('<td>' + dom.blank + '</td>');
|
|
2081
|
+
}
|
|
2082
|
+
tdHTML = tds.join('');
|
|
2083
|
+
|
|
2084
|
+
var trs = [], trHTML;
|
|
2085
|
+
for (var idxRow = 0; idxRow < rowCount; idxRow++) {
|
|
2086
|
+
trs.push('<tr>' + tdHTML + '</tr>');
|
|
2087
|
+
}
|
|
2088
|
+
trHTML = trs.join('');
|
|
2089
|
+
return $('<table class="table table-bordered">' + trHTML + '</table>')[0];
|
|
2090
|
+
};
|
|
2091
|
+
};
|
|
2092
|
+
|
|
2093
|
+
/**
|
|
2094
|
+
* Editor
|
|
2095
|
+
* @class
|
|
2096
|
+
*/
|
|
2097
|
+
var Editor = function () {
|
|
2098
|
+
|
|
2099
|
+
var style = new Style();
|
|
2100
|
+
var table = new Table();
|
|
2101
|
+
|
|
2102
|
+
/**
|
|
2103
|
+
* save current range
|
|
2104
|
+
*
|
|
2105
|
+
* @param {jQuery} $editable
|
|
2106
|
+
*/
|
|
2107
|
+
this.saveRange = function ($editable) {
|
|
2108
|
+
$editable.focus();
|
|
2109
|
+
$editable.data('range', range.create());
|
|
2110
|
+
};
|
|
2111
|
+
|
|
2112
|
+
/**
|
|
2113
|
+
* restore lately range
|
|
2114
|
+
*
|
|
2115
|
+
* @param {jQuery} $editable
|
|
2116
|
+
*/
|
|
2117
|
+
this.restoreRange = function ($editable) {
|
|
2118
|
+
var rng = $editable.data('range');
|
|
2119
|
+
if (rng) {
|
|
2120
|
+
rng.select();
|
|
2121
|
+
$editable.focus();
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
|
|
2125
|
+
/**
|
|
2126
|
+
* current style
|
|
2127
|
+
* @param {Node} target
|
|
2128
|
+
*/
|
|
2129
|
+
this.currentStyle = function (target) {
|
|
2130
|
+
var rng = range.create();
|
|
2131
|
+
return rng ? rng.isOnEditable() && style.current(rng, target) : false;
|
|
2132
|
+
};
|
|
2133
|
+
|
|
2134
|
+
/**
|
|
2135
|
+
* undo
|
|
2136
|
+
* @param {jQuery} $editable
|
|
2137
|
+
*/
|
|
2138
|
+
this.undo = function ($editable) {
|
|
2139
|
+
$editable.data('NoteHistory').undo($editable);
|
|
2140
|
+
};
|
|
2141
|
+
|
|
2142
|
+
/**
|
|
2143
|
+
* redo
|
|
2144
|
+
* @param {jQuery} $editable
|
|
2145
|
+
*/
|
|
2146
|
+
this.redo = function ($editable) {
|
|
2147
|
+
$editable.data('NoteHistory').redo($editable);
|
|
2148
|
+
};
|
|
2149
|
+
|
|
2150
|
+
/**
|
|
2151
|
+
* record Undo
|
|
2152
|
+
* @param {jQuery} $editable
|
|
2153
|
+
*/
|
|
2154
|
+
var recordUndo = this.recordUndo = function ($editable) {
|
|
2155
|
+
$editable.data('NoteHistory').recordUndo($editable);
|
|
2156
|
+
};
|
|
2157
|
+
|
|
2158
|
+
/* jshint ignore:start */
|
|
2159
|
+
// native commands(with execCommand), generate function for execCommand
|
|
2160
|
+
var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
|
|
2161
|
+
'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
|
|
2162
|
+
'insertOrderedList', 'insertUnorderedList',
|
|
2163
|
+
'indent', 'outdent', 'formatBlock', 'removeFormat',
|
|
2164
|
+
'backColor', 'foreColor', 'insertHorizontalRule', 'fontName'];
|
|
2165
|
+
|
|
2166
|
+
for (var idx = 0, len = commands.length; idx < len; idx ++) {
|
|
2167
|
+
this[commands[idx]] = (function (sCmd) {
|
|
2168
|
+
return function ($editable, value) {
|
|
2169
|
+
recordUndo($editable);
|
|
2170
|
+
|
|
2171
|
+
document.execCommand(sCmd, false, value);
|
|
2172
|
+
};
|
|
2173
|
+
})(commands[idx]);
|
|
2174
|
+
}
|
|
2175
|
+
/* jshint ignore:end */
|
|
2176
|
+
|
|
2177
|
+
/**
|
|
2178
|
+
* @param {jQuery} $editable
|
|
2179
|
+
* @param {WrappedRange} rng
|
|
2180
|
+
* @param {Number} tabsize
|
|
2181
|
+
*/
|
|
2182
|
+
var insertTab = function ($editable, rng, tabsize) {
|
|
2183
|
+
recordUndo($editable);
|
|
2184
|
+
|
|
2185
|
+
var tab = dom.createText(new Array(tabsize + 1).join(dom.NBSP_CHAR));
|
|
2186
|
+
rng = rng.deleteContents();
|
|
2187
|
+
rng.insertNode(tab, true);
|
|
2188
|
+
|
|
2189
|
+
rng = range.create(tab, tabsize);
|
|
2190
|
+
rng.select();
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
/**
|
|
2194
|
+
* handle tab key
|
|
2195
|
+
* @param {jQuery} $editable
|
|
2196
|
+
* @param {Object} options
|
|
2197
|
+
*/
|
|
2198
|
+
this.tab = function ($editable, options) {
|
|
2199
|
+
var rng = range.create();
|
|
2200
|
+
if (rng.isCollapsed() && rng.isOnCell()) {
|
|
2201
|
+
table.tab(rng);
|
|
2202
|
+
} else {
|
|
2203
|
+
insertTab($editable, rng, options.tabsize);
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
/**
|
|
2208
|
+
* handle shift+tab key
|
|
2209
|
+
*/
|
|
2210
|
+
this.untab = function () {
|
|
2211
|
+
var rng = range.create();
|
|
2212
|
+
if (rng.isCollapsed() && rng.isOnCell()) {
|
|
2213
|
+
table.tab(rng, true);
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
|
|
2217
|
+
/**
|
|
2218
|
+
* insert paragraph
|
|
2219
|
+
*
|
|
2220
|
+
* @param {Node} $editable
|
|
2221
|
+
*/
|
|
2222
|
+
this.insertParagraph = function ($editable) {
|
|
2223
|
+
recordUndo($editable);
|
|
2224
|
+
|
|
2225
|
+
var rng = range.create();
|
|
2226
|
+
|
|
2227
|
+
// deleteContents on range.
|
|
2228
|
+
rng = rng.deleteContents();
|
|
2229
|
+
|
|
2230
|
+
rng = rng.wrapBodyInlineWithPara();
|
|
2231
|
+
|
|
2232
|
+
// find split root node: block level node
|
|
2233
|
+
var splitRoot = dom.ancestor(rng.sc, dom.isPara);
|
|
2234
|
+
var nextPara = dom.splitTree(splitRoot, rng.getStartPoint());
|
|
2235
|
+
range.create(nextPara, 0).select();
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
/**
|
|
2239
|
+
* insert image
|
|
2240
|
+
*
|
|
2241
|
+
* @param {jQuery} $editable
|
|
2242
|
+
* @param {String} sUrl
|
|
2243
|
+
*/
|
|
2244
|
+
this.insertImage = function ($editable, sUrl, filename) {
|
|
2245
|
+
async.createImage(sUrl, filename).then(function ($image) {
|
|
2246
|
+
recordUndo($editable);
|
|
2247
|
+
|
|
2248
|
+
$image.css({
|
|
2249
|
+
display: '',
|
|
2250
|
+
width: Math.min($editable.width(), $image.width())
|
|
2251
|
+
});
|
|
2252
|
+
range.create().insertNode($image[0]);
|
|
2253
|
+
}).fail(function () {
|
|
2254
|
+
var callbacks = $editable.data('callbacks');
|
|
2255
|
+
if (callbacks.onImageUploadError) {
|
|
2256
|
+
callbacks.onImageUploadError();
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
};
|
|
2260
|
+
|
|
2261
|
+
/**
|
|
2262
|
+
* insert video
|
|
2263
|
+
* @param {jQuery} $editable
|
|
2264
|
+
* @param {String} sUrl
|
|
2265
|
+
*/
|
|
2266
|
+
this.insertVideo = function ($editable, sUrl) {
|
|
2267
|
+
recordUndo($editable);
|
|
2268
|
+
|
|
2269
|
+
// video url patterns(youtube, instagram, vimeo, dailymotion, youku)
|
|
2270
|
+
var ytRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
|
2271
|
+
var ytMatch = sUrl.match(ytRegExp);
|
|
2272
|
+
|
|
2273
|
+
var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/;
|
|
2274
|
+
var igMatch = sUrl.match(igRegExp);
|
|
2275
|
+
|
|
2276
|
+
var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
|
|
2277
|
+
var vMatch = sUrl.match(vRegExp);
|
|
2278
|
+
|
|
2279
|
+
var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
|
|
2280
|
+
var vimMatch = sUrl.match(vimRegExp);
|
|
2281
|
+
|
|
2282
|
+
var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
|
|
2283
|
+
var dmMatch = sUrl.match(dmRegExp);
|
|
2284
|
+
|
|
2285
|
+
var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)\.html/;
|
|
2286
|
+
var youkuMatch = sUrl.match(youkuRegExp);
|
|
2287
|
+
|
|
2288
|
+
var $video;
|
|
2289
|
+
if (ytMatch && ytMatch[2].length === 11) {
|
|
2290
|
+
var youtubeId = ytMatch[2];
|
|
2291
|
+
$video = $('<iframe>')
|
|
2292
|
+
.attr('src', '//www.youtube.com/embed/' + youtubeId)
|
|
2293
|
+
.attr('width', '640').attr('height', '360');
|
|
2294
|
+
} else if (igMatch && igMatch[0].length) {
|
|
2295
|
+
$video = $('<iframe>')
|
|
2296
|
+
.attr('src', igMatch[0] + '/embed/')
|
|
2297
|
+
.attr('width', '612').attr('height', '710')
|
|
2298
|
+
.attr('scrolling', 'no')
|
|
2299
|
+
.attr('allowtransparency', 'true');
|
|
2300
|
+
} else if (vMatch && vMatch[0].length) {
|
|
2301
|
+
$video = $('<iframe>')
|
|
2302
|
+
.attr('src', vMatch[0] + '/embed/simple')
|
|
2303
|
+
.attr('width', '600').attr('height', '600')
|
|
2304
|
+
.attr('class', 'vine-embed');
|
|
2305
|
+
} else if (vimMatch && vimMatch[3].length) {
|
|
2306
|
+
$video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
|
|
2307
|
+
.attr('src', '//player.vimeo.com/video/' + vimMatch[3])
|
|
2308
|
+
.attr('width', '640').attr('height', '360');
|
|
2309
|
+
} else if (dmMatch && dmMatch[2].length) {
|
|
2310
|
+
$video = $('<iframe>')
|
|
2311
|
+
.attr('src', '//www.dailymotion.com/embed/video/' + dmMatch[2])
|
|
2312
|
+
.attr('width', '640').attr('height', '360');
|
|
2313
|
+
} else if (youkuMatch && youkuMatch[1].length) {
|
|
2314
|
+
$video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
|
|
2315
|
+
.attr('height', '498')
|
|
2316
|
+
.attr('width', '510')
|
|
2317
|
+
.attr('src', '//player.youku.com/embed/' + youkuMatch[1]);
|
|
2318
|
+
} else {
|
|
2319
|
+
// this is not a known video link. Now what, Cat? Now what?
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
if ($video) {
|
|
2323
|
+
$video.attr('frameborder', 0);
|
|
2324
|
+
range.create().insertNode($video[0]);
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
|
|
2328
|
+
/**
|
|
2329
|
+
* formatBlock
|
|
2330
|
+
*
|
|
2331
|
+
* @param {jQuery} $editable
|
|
2332
|
+
* @param {String} tagName
|
|
2333
|
+
*/
|
|
2334
|
+
this.formatBlock = function ($editable, tagName) {
|
|
2335
|
+
recordUndo($editable);
|
|
2336
|
+
|
|
2337
|
+
tagName = agent.isMSIE ? '<' + tagName + '>' : tagName;
|
|
2338
|
+
document.execCommand('FormatBlock', false, tagName);
|
|
2339
|
+
};
|
|
2340
|
+
|
|
2341
|
+
this.formatPara = function ($editable) {
|
|
2342
|
+
this.formatBlock($editable, 'P');
|
|
2343
|
+
};
|
|
2344
|
+
|
|
2345
|
+
/* jshint ignore:start */
|
|
2346
|
+
for (var idx = 1; idx <= 6; idx ++) {
|
|
2347
|
+
this['formatH' + idx] = function (idx) {
|
|
2348
|
+
return function ($editable) {
|
|
2349
|
+
this.formatBlock($editable, 'H' + idx);
|
|
2350
|
+
};
|
|
2351
|
+
}(idx);
|
|
2352
|
+
};
|
|
2353
|
+
/* jshint ignore:end */
|
|
2354
|
+
|
|
2355
|
+
/**
|
|
2356
|
+
* fontsize
|
|
2357
|
+
* FIXME: Still buggy
|
|
2358
|
+
*
|
|
2359
|
+
* @param {jQuery} $editable
|
|
2360
|
+
* @param {String} value - px
|
|
2361
|
+
*/
|
|
2362
|
+
this.fontSize = function ($editable, value) {
|
|
2363
|
+
recordUndo($editable);
|
|
2364
|
+
|
|
2365
|
+
document.execCommand('fontSize', false, 3);
|
|
2366
|
+
if (agent.isFF) {
|
|
2367
|
+
// firefox: <font size="3"> to <span style='font-size={value}px;'>, buggy
|
|
2368
|
+
$editable.find('font[size=3]').removeAttr('size').css('font-size', value + 'px');
|
|
2369
|
+
} else {
|
|
2370
|
+
// chrome: <span style="font-size: medium"> to <span style='font-size={value}px;'>
|
|
2371
|
+
$editable.find('span').filter(function () {
|
|
2372
|
+
return this.style.fontSize === 'medium';
|
|
2373
|
+
}).css('font-size', value + 'px');
|
|
2374
|
+
}
|
|
2375
|
+
};
|
|
2376
|
+
|
|
2377
|
+
/**
|
|
2378
|
+
* lineHeight
|
|
2379
|
+
* @param {jQuery} $editable
|
|
2380
|
+
* @param {String} value
|
|
2381
|
+
*/
|
|
2382
|
+
this.lineHeight = function ($editable, value) {
|
|
2383
|
+
recordUndo($editable);
|
|
2384
|
+
|
|
2385
|
+
style.stylePara(range.create(), {
|
|
2386
|
+
lineHeight: value
|
|
2387
|
+
});
|
|
2388
|
+
};
|
|
2389
|
+
|
|
2390
|
+
/**
|
|
2391
|
+
* unlink
|
|
2392
|
+
* @param {jQuery} $editable
|
|
2393
|
+
*/
|
|
2394
|
+
this.unlink = function ($editable) {
|
|
2395
|
+
var rng = range.create();
|
|
2396
|
+
if (rng.isOnAnchor()) {
|
|
2397
|
+
recordUndo($editable);
|
|
2398
|
+
|
|
2399
|
+
var anchor = dom.ancestor(rng.sc, dom.isAnchor);
|
|
2400
|
+
rng = range.createFromNode(anchor);
|
|
2401
|
+
rng.select();
|
|
2402
|
+
document.execCommand('unlink');
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2406
|
+
/**
|
|
2407
|
+
* create link
|
|
2408
|
+
*
|
|
2409
|
+
* @param {jQuery} $editable
|
|
2410
|
+
* @param {Object} linkInfo
|
|
2411
|
+
* @param {Object} options
|
|
2412
|
+
*/
|
|
2413
|
+
this.createLink = function ($editable, linkInfo, options) {
|
|
2414
|
+
var linkUrl = linkInfo.url;
|
|
2415
|
+
var linkText = linkInfo.text;
|
|
2416
|
+
var isNewWindow = linkInfo.newWindow;
|
|
2417
|
+
var rng = linkInfo.range;
|
|
2418
|
+
|
|
2419
|
+
recordUndo($editable);
|
|
2420
|
+
|
|
2421
|
+
if (options.onCreateLink) {
|
|
2422
|
+
linkUrl = options.onCreateLink(linkUrl);
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
rng = rng.deleteContents();
|
|
2426
|
+
|
|
2427
|
+
// Create a new link when there is no anchor on range.
|
|
2428
|
+
var anchor = rng.insertNode($('<A>' + linkText + '</A>')[0], true);
|
|
2429
|
+
$(anchor).attr({
|
|
2430
|
+
href: linkUrl,
|
|
2431
|
+
target: isNewWindow ? '_blank' : ''
|
|
2432
|
+
});
|
|
2433
|
+
|
|
2434
|
+
rng = range.createFromNode(anchor);
|
|
2435
|
+
rng.select();
|
|
2436
|
+
};
|
|
2437
|
+
|
|
2438
|
+
/**
|
|
2439
|
+
* returns link info
|
|
2440
|
+
*
|
|
2441
|
+
* @return {Object}
|
|
2442
|
+
*/
|
|
2443
|
+
this.getLinkInfo = function ($editable) {
|
|
2444
|
+
$editable.focus();
|
|
2445
|
+
|
|
2446
|
+
var rng = range.create().expand(dom.isAnchor);
|
|
2447
|
+
|
|
2448
|
+
// Get the first anchor on range(for edit).
|
|
2449
|
+
var $anchor = $(list.head(rng.nodes(dom.isAnchor)));
|
|
2450
|
+
|
|
2451
|
+
return {
|
|
2452
|
+
range: rng,
|
|
2453
|
+
text: rng.toString(),
|
|
2454
|
+
isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : true,
|
|
2455
|
+
url: $anchor.length ? $anchor.attr('href') : ''
|
|
2456
|
+
};
|
|
2457
|
+
};
|
|
2458
|
+
|
|
2459
|
+
/**
|
|
2460
|
+
* get video info
|
|
2461
|
+
*
|
|
2462
|
+
* @param {jQuery} $editable
|
|
2463
|
+
* @return {Object}
|
|
2464
|
+
*/
|
|
2465
|
+
this.getVideoInfo = function ($editable) {
|
|
2466
|
+
$editable.focus();
|
|
2467
|
+
|
|
2468
|
+
var rng = range.create();
|
|
2469
|
+
|
|
2470
|
+
if (rng.isOnAnchor()) {
|
|
2471
|
+
var anchor = dom.ancestor(rng.sc, dom.isAnchor);
|
|
2472
|
+
rng = range.createFromNode(anchor);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
return {
|
|
2476
|
+
text: rng.toString()
|
|
2477
|
+
};
|
|
2478
|
+
};
|
|
2479
|
+
|
|
2480
|
+
this.color = function ($editable, sObjColor) {
|
|
2481
|
+
var oColor = JSON.parse(sObjColor);
|
|
2482
|
+
var foreColor = oColor.foreColor, backColor = oColor.backColor;
|
|
2483
|
+
|
|
2484
|
+
recordUndo($editable);
|
|
2485
|
+
|
|
2486
|
+
if (foreColor) { document.execCommand('foreColor', false, foreColor); }
|
|
2487
|
+
if (backColor) { document.execCommand('backColor', false, backColor); }
|
|
2488
|
+
};
|
|
2489
|
+
|
|
2490
|
+
this.insertTable = function ($editable, sDim) {
|
|
2491
|
+
recordUndo($editable);
|
|
2492
|
+
|
|
2493
|
+
var dimension = sDim.split('x');
|
|
2494
|
+
var rng = range.create();
|
|
2495
|
+
rng = rng.deleteContents();
|
|
2496
|
+
rng.insertNode(table.createTable(dimension[0], dimension[1]));
|
|
2497
|
+
};
|
|
2498
|
+
|
|
2499
|
+
/**
|
|
2500
|
+
* @param {jQuery} $editable
|
|
2501
|
+
* @param {String} value
|
|
2502
|
+
* @param {jQuery} $target
|
|
2503
|
+
*/
|
|
2504
|
+
this.floatMe = function ($editable, value, $target) {
|
|
2505
|
+
recordUndo($editable);
|
|
2506
|
+
|
|
2507
|
+
$target.css('float', value);
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
/**
|
|
2511
|
+
* resize overlay element
|
|
2512
|
+
* @param {jQuery} $editable
|
|
2513
|
+
* @param {String} value
|
|
2514
|
+
* @param {jQuery} $target - target element
|
|
2515
|
+
*/
|
|
2516
|
+
this.resize = function ($editable, value, $target) {
|
|
2517
|
+
recordUndo($editable);
|
|
2518
|
+
|
|
2519
|
+
$target.css({
|
|
2520
|
+
width: $editable.width() * value + 'px',
|
|
2521
|
+
height: ''
|
|
2522
|
+
});
|
|
2523
|
+
};
|
|
2524
|
+
|
|
2525
|
+
/**
|
|
2526
|
+
* @param {Position} pos
|
|
2527
|
+
* @param {jQuery} $target - target element
|
|
2528
|
+
* @param {Boolean} [bKeepRatio] - keep ratio
|
|
2529
|
+
*/
|
|
2530
|
+
this.resizeTo = function (pos, $target, bKeepRatio) {
|
|
2531
|
+
var imageSize;
|
|
2532
|
+
if (bKeepRatio) {
|
|
2533
|
+
var newRatio = pos.y / pos.x;
|
|
2534
|
+
var ratio = $target.data('ratio');
|
|
2535
|
+
imageSize = {
|
|
2536
|
+
width: ratio > newRatio ? pos.x : pos.y / ratio,
|
|
2537
|
+
height: ratio > newRatio ? pos.x * ratio : pos.y
|
|
2538
|
+
};
|
|
2539
|
+
} else {
|
|
2540
|
+
imageSize = {
|
|
2541
|
+
width: pos.x,
|
|
2542
|
+
height: pos.y
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
$target.css(imageSize);
|
|
2547
|
+
};
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* remove media object
|
|
2551
|
+
*
|
|
2552
|
+
* @param {jQuery} $editable
|
|
2553
|
+
* @param {String} value - dummy argument (for keep interface)
|
|
2554
|
+
* @param {jQuery} $target - target element
|
|
2555
|
+
*/
|
|
2556
|
+
this.removeMedia = function ($editable, value, $target) {
|
|
2557
|
+
recordUndo($editable);
|
|
2558
|
+
|
|
2559
|
+
$target.detach();
|
|
2560
|
+
};
|
|
2561
|
+
};
|
|
2562
|
+
|
|
2563
|
+
/**
|
|
2564
|
+
* History
|
|
2565
|
+
* @class
|
|
2566
|
+
*/
|
|
2567
|
+
var History = function () {
|
|
2568
|
+
var undoStack = [], redoStack = [];
|
|
2569
|
+
|
|
2570
|
+
var makeSnapshot = function ($editable) {
|
|
2571
|
+
var editable = $editable[0];
|
|
2572
|
+
var rng = range.create();
|
|
2573
|
+
|
|
2574
|
+
return {
|
|
2575
|
+
contents: $editable.html(),
|
|
2576
|
+
bookmark: rng.bookmark(editable),
|
|
2577
|
+
scrollTop: $editable.scrollTop()
|
|
2578
|
+
};
|
|
2579
|
+
};
|
|
2580
|
+
|
|
2581
|
+
var applySnapshot = function ($editable, snapshot) {
|
|
2582
|
+
$editable.html(snapshot.contents).scrollTop(snapshot.scrollTop);
|
|
2583
|
+
range.createFromBookmark($editable[0], snapshot.bookmark).select();
|
|
2584
|
+
};
|
|
2585
|
+
|
|
2586
|
+
this.undo = function ($editable) {
|
|
2587
|
+
var snapshot = makeSnapshot($editable);
|
|
2588
|
+
if (!undoStack.length) {
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
applySnapshot($editable, undoStack.pop());
|
|
2592
|
+
redoStack.push(snapshot);
|
|
2593
|
+
};
|
|
2594
|
+
|
|
2595
|
+
this.redo = function ($editable) {
|
|
2596
|
+
var snapshot = makeSnapshot($editable);
|
|
2597
|
+
if (!redoStack.length) {
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
applySnapshot($editable, redoStack.pop());
|
|
2601
|
+
undoStack.push(snapshot);
|
|
2602
|
+
};
|
|
2603
|
+
|
|
2604
|
+
this.recordUndo = function ($editable) {
|
|
2605
|
+
redoStack = [];
|
|
2606
|
+
undoStack.push(makeSnapshot($editable));
|
|
2607
|
+
};
|
|
2608
|
+
};
|
|
2609
|
+
|
|
2610
|
+
/**
|
|
2611
|
+
* Button
|
|
2612
|
+
*/
|
|
2613
|
+
var Button = function () {
|
|
2614
|
+
/**
|
|
2615
|
+
* update button status
|
|
2616
|
+
*
|
|
2617
|
+
* @param {jQuery} $container
|
|
2618
|
+
* @param {Object} styleInfo
|
|
2619
|
+
*/
|
|
2620
|
+
this.update = function ($container, styleInfo) {
|
|
2621
|
+
/**
|
|
2622
|
+
* handle dropdown's check mark (for fontname, fontsize, lineHeight).
|
|
2623
|
+
* @param {jQuery} $btn
|
|
2624
|
+
* @param {Number} value
|
|
2625
|
+
*/
|
|
2626
|
+
var checkDropdownMenu = function ($btn, value) {
|
|
2627
|
+
$btn.find('.dropdown-menu li a').each(function () {
|
|
2628
|
+
// always compare string to avoid creating another func.
|
|
2629
|
+
var isChecked = ($(this).data('value') + '') === (value + '');
|
|
2630
|
+
this.className = isChecked ? 'checked' : '';
|
|
2631
|
+
});
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2634
|
+
/**
|
|
2635
|
+
* update button state(active or not).
|
|
2636
|
+
*
|
|
2637
|
+
* @param {String} selector
|
|
2638
|
+
* @param {Function} pred
|
|
2639
|
+
*/
|
|
2640
|
+
var btnState = function (selector, pred) {
|
|
2641
|
+
var $btn = $container.find(selector);
|
|
2642
|
+
$btn.toggleClass('active', pred());
|
|
2643
|
+
};
|
|
2644
|
+
|
|
2645
|
+
// fontname
|
|
2646
|
+
var $fontname = $container.find('.note-fontname');
|
|
2647
|
+
if ($fontname.length) {
|
|
2648
|
+
var selectedFont = styleInfo['font-family'];
|
|
2649
|
+
if (!!selectedFont) {
|
|
2650
|
+
selectedFont = list.head(selectedFont.split(','));
|
|
2651
|
+
selectedFont = selectedFont.replace(/\'/g, '');
|
|
2652
|
+
$fontname.find('.note-current-fontname').text(selectedFont);
|
|
2653
|
+
checkDropdownMenu($fontname, selectedFont);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// fontsize
|
|
2658
|
+
var $fontsize = $container.find('.note-fontsize');
|
|
2659
|
+
$fontsize.find('.note-current-fontsize').text(styleInfo['font-size']);
|
|
2660
|
+
checkDropdownMenu($fontsize, parseFloat(styleInfo['font-size']));
|
|
2661
|
+
|
|
2662
|
+
// lineheight
|
|
2663
|
+
var $lineHeight = $container.find('.note-height');
|
|
2664
|
+
checkDropdownMenu($lineHeight, parseFloat(styleInfo['line-height']));
|
|
2665
|
+
|
|
2666
|
+
btnState('button[data-event="bold"]', function () {
|
|
2667
|
+
return styleInfo['font-bold'] === 'bold';
|
|
2668
|
+
});
|
|
2669
|
+
btnState('button[data-event="italic"]', function () {
|
|
2670
|
+
return styleInfo['font-italic'] === 'italic';
|
|
2671
|
+
});
|
|
2672
|
+
btnState('button[data-event="underline"]', function () {
|
|
2673
|
+
return styleInfo['font-underline'] === 'underline';
|
|
2674
|
+
});
|
|
2675
|
+
btnState('button[data-event="strikethrough"]', function () {
|
|
2676
|
+
return styleInfo['font-strikethrough'] === 'strikethrough';
|
|
2677
|
+
});
|
|
2678
|
+
btnState('button[data-event="superscript"]', function () {
|
|
2679
|
+
return styleInfo['font-superscript'] === 'superscript';
|
|
2680
|
+
});
|
|
2681
|
+
btnState('button[data-event="subscript"]', function () {
|
|
2682
|
+
return styleInfo['font-subscript'] === 'subscript';
|
|
2683
|
+
});
|
|
2684
|
+
btnState('button[data-event="justifyLeft"]', function () {
|
|
2685
|
+
return styleInfo['text-align'] === 'left' || styleInfo['text-align'] === 'start';
|
|
2686
|
+
});
|
|
2687
|
+
btnState('button[data-event="justifyCenter"]', function () {
|
|
2688
|
+
return styleInfo['text-align'] === 'center';
|
|
2689
|
+
});
|
|
2690
|
+
btnState('button[data-event="justifyRight"]', function () {
|
|
2691
|
+
return styleInfo['text-align'] === 'right';
|
|
2692
|
+
});
|
|
2693
|
+
btnState('button[data-event="justifyFull"]', function () {
|
|
2694
|
+
return styleInfo['text-align'] === 'justify';
|
|
2695
|
+
});
|
|
2696
|
+
btnState('button[data-event="insertUnorderedList"]', function () {
|
|
2697
|
+
return styleInfo['list-style'] === 'unordered';
|
|
2698
|
+
});
|
|
2699
|
+
btnState('button[data-event="insertOrderedList"]', function () {
|
|
2700
|
+
return styleInfo['list-style'] === 'ordered';
|
|
2701
|
+
});
|
|
2702
|
+
};
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* update recent color
|
|
2706
|
+
*
|
|
2707
|
+
* @param {Node} button
|
|
2708
|
+
* @param {String} eventName
|
|
2709
|
+
* @param {value} value
|
|
2710
|
+
*/
|
|
2711
|
+
this.updateRecentColor = function (button, eventName, value) {
|
|
2712
|
+
var $color = $(button).closest('.note-color');
|
|
2713
|
+
var $recentColor = $color.find('.note-recent-color');
|
|
2714
|
+
var colorInfo = JSON.parse($recentColor.attr('data-value'));
|
|
2715
|
+
colorInfo[eventName] = value;
|
|
2716
|
+
$recentColor.attr('data-value', JSON.stringify(colorInfo));
|
|
2717
|
+
var sKey = eventName === 'backColor' ? 'background-color' : 'color';
|
|
2718
|
+
$recentColor.find('i').css(sKey, value);
|
|
2719
|
+
};
|
|
2720
|
+
};
|
|
2721
|
+
|
|
2722
|
+
/**
|
|
2723
|
+
* Toolbar
|
|
2724
|
+
*/
|
|
2725
|
+
var Toolbar = function () {
|
|
2726
|
+
var button = new Button();
|
|
2727
|
+
|
|
2728
|
+
this.update = function ($toolbar, styleInfo) {
|
|
2729
|
+
button.update($toolbar, styleInfo);
|
|
2730
|
+
};
|
|
2731
|
+
|
|
2732
|
+
/**
|
|
2733
|
+
* @param {Node} button
|
|
2734
|
+
* @param {String} eventName
|
|
2735
|
+
* @param {String} value
|
|
2736
|
+
*/
|
|
2737
|
+
this.updateRecentColor = function (buttonNode, eventName, value) {
|
|
2738
|
+
button.updateRecentColor(buttonNode, eventName, value);
|
|
2739
|
+
};
|
|
2740
|
+
|
|
2741
|
+
/**
|
|
2742
|
+
* activate buttons exclude codeview
|
|
2743
|
+
* @param {jQuery} $toolbar
|
|
2744
|
+
*/
|
|
2745
|
+
this.activate = function ($toolbar) {
|
|
2746
|
+
$toolbar.find('button')
|
|
2747
|
+
.not('button[data-event="codeview"]')
|
|
2748
|
+
.removeClass('disabled');
|
|
2749
|
+
};
|
|
2750
|
+
|
|
2751
|
+
/**
|
|
2752
|
+
* deactivate buttons exclude codeview
|
|
2753
|
+
* @param {jQuery} $toolbar
|
|
2754
|
+
*/
|
|
2755
|
+
this.deactivate = function ($toolbar) {
|
|
2756
|
+
$toolbar.find('button')
|
|
2757
|
+
.not('button[data-event="codeview"]')
|
|
2758
|
+
.addClass('disabled');
|
|
2759
|
+
};
|
|
2760
|
+
|
|
2761
|
+
this.updateFullscreen = function ($container, bFullscreen) {
|
|
2762
|
+
var $btn = $container.find('button[data-event="fullscreen"]');
|
|
2763
|
+
$btn.toggleClass('active', bFullscreen);
|
|
2764
|
+
};
|
|
2765
|
+
|
|
2766
|
+
this.updateCodeview = function ($container, isCodeview) {
|
|
2767
|
+
var $btn = $container.find('button[data-event="codeview"]');
|
|
2768
|
+
$btn.toggleClass('active', isCodeview);
|
|
2769
|
+
};
|
|
2770
|
+
};
|
|
2771
|
+
|
|
2772
|
+
/**
|
|
2773
|
+
* Popover (http://getbootstrap.com/javascript/#popovers)
|
|
2774
|
+
*/
|
|
2775
|
+
var Popover = function () {
|
|
2776
|
+
var button = new Button();
|
|
2777
|
+
|
|
2778
|
+
/**
|
|
2779
|
+
* returns position from placeholder
|
|
2780
|
+
* @param {Node} placeholder
|
|
2781
|
+
* @param {Boolean} isAirMode
|
|
2782
|
+
*/
|
|
2783
|
+
var posFromPlaceholder = function (placeholder, isAirMode) {
|
|
2784
|
+
var $placeholder = $(placeholder);
|
|
2785
|
+
var pos = isAirMode ? $placeholder.offset() : $placeholder.position();
|
|
2786
|
+
var height = $placeholder.outerHeight(true); // include margin
|
|
2787
|
+
|
|
2788
|
+
// popover below placeholder.
|
|
2789
|
+
return {
|
|
2790
|
+
left: pos.left,
|
|
2791
|
+
top: pos.top + height
|
|
2792
|
+
};
|
|
2793
|
+
};
|
|
2794
|
+
|
|
2795
|
+
/**
|
|
2796
|
+
* show popover
|
|
2797
|
+
* @param {jQuery} popover
|
|
2798
|
+
* @param {Position} pos
|
|
2799
|
+
*/
|
|
2800
|
+
var showPopover = function ($popover, pos) {
|
|
2801
|
+
$popover.css({
|
|
2802
|
+
display: 'block',
|
|
2803
|
+
left: pos.left,
|
|
2804
|
+
top: pos.top
|
|
2805
|
+
});
|
|
2806
|
+
};
|
|
2807
|
+
|
|
2808
|
+
var PX_POPOVER_ARROW_OFFSET_X = 20;
|
|
2809
|
+
|
|
2810
|
+
/**
|
|
2811
|
+
* update current state
|
|
2812
|
+
* @param {jQuery} $popover - popover container
|
|
2813
|
+
* @param {Object} styleInfo - style object
|
|
2814
|
+
* @param {Boolean} isAirMode
|
|
2815
|
+
*/
|
|
2816
|
+
this.update = function ($popover, styleInfo, isAirMode) {
|
|
2817
|
+
button.update($popover, styleInfo);
|
|
2818
|
+
|
|
2819
|
+
var $linkPopover = $popover.find('.note-link-popover');
|
|
2820
|
+
if (styleInfo.anchor) {
|
|
2821
|
+
var $anchor = $linkPopover.find('a');
|
|
2822
|
+
var href = $(styleInfo.anchor).attr('href');
|
|
2823
|
+
$anchor.attr('href', href).html(href);
|
|
2824
|
+
showPopover($linkPopover, posFromPlaceholder(styleInfo.anchor, isAirMode));
|
|
2825
|
+
} else {
|
|
2826
|
+
$linkPopover.hide();
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
var $imagePopover = $popover.find('.note-image-popover');
|
|
2830
|
+
if (styleInfo.image) {
|
|
2831
|
+
showPopover($imagePopover, posFromPlaceholder(styleInfo.image, isAirMode));
|
|
2832
|
+
} else {
|
|
2833
|
+
$imagePopover.hide();
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
var $airPopover = $popover.find('.note-air-popover');
|
|
2837
|
+
if (isAirMode && !styleInfo.range.isCollapsed()) {
|
|
2838
|
+
var bnd = func.rect2bnd(list.last(styleInfo.range.getClientRects()));
|
|
2839
|
+
showPopover($airPopover, {
|
|
2840
|
+
left: Math.max(bnd.left + bnd.width / 2 - PX_POPOVER_ARROW_OFFSET_X, 0),
|
|
2841
|
+
top: bnd.top + bnd.height
|
|
2842
|
+
});
|
|
2843
|
+
} else {
|
|
2844
|
+
$airPopover.hide();
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
|
|
2848
|
+
/**
|
|
2849
|
+
* @param {Node} button
|
|
2850
|
+
* @param {String} eventName
|
|
2851
|
+
* @param {String} value
|
|
2852
|
+
*/
|
|
2853
|
+
this.updateRecentColor = function (button, eventName, value) {
|
|
2854
|
+
button.updateRecentColor(button, eventName, value);
|
|
2855
|
+
};
|
|
2856
|
+
|
|
2857
|
+
/**
|
|
2858
|
+
* hide all popovers
|
|
2859
|
+
* @param {jQuery} $popover - popover contaienr
|
|
2860
|
+
*/
|
|
2861
|
+
this.hide = function ($popover) {
|
|
2862
|
+
$popover.children().hide();
|
|
2863
|
+
};
|
|
2864
|
+
};
|
|
2865
|
+
|
|
2866
|
+
/**
|
|
2867
|
+
* Handle
|
|
2868
|
+
*/
|
|
2869
|
+
var Handle = function () {
|
|
2870
|
+
/**
|
|
2871
|
+
* update handle
|
|
2872
|
+
* @param {jQuery} $handle
|
|
2873
|
+
* @param {Object} styleInfo
|
|
2874
|
+
* @param {Boolean} isAirMode
|
|
2875
|
+
*/
|
|
2876
|
+
this.update = function ($handle, styleInfo, isAirMode) {
|
|
2877
|
+
var $selection = $handle.find('.note-control-selection');
|
|
2878
|
+
if (styleInfo.image) {
|
|
2879
|
+
var $image = $(styleInfo.image);
|
|
2880
|
+
var pos = isAirMode ? $image.offset() : $image.position();
|
|
2881
|
+
|
|
2882
|
+
// include margin
|
|
2883
|
+
var imageSize = {
|
|
2884
|
+
w: $image.outerWidth(true),
|
|
2885
|
+
h: $image.outerHeight(true)
|
|
2886
|
+
};
|
|
2887
|
+
|
|
2888
|
+
$selection.css({
|
|
2889
|
+
display: 'block',
|
|
2890
|
+
left: pos.left,
|
|
2891
|
+
top: pos.top,
|
|
2892
|
+
width: imageSize.w,
|
|
2893
|
+
height: imageSize.h
|
|
2894
|
+
}).data('target', styleInfo.image); // save current image element.
|
|
2895
|
+
var sizingText = imageSize.w + 'x' + imageSize.h;
|
|
2896
|
+
$selection.find('.note-control-selection-info').text(sizingText);
|
|
2897
|
+
} else {
|
|
2898
|
+
$selection.hide();
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
this.hide = function ($handle) {
|
|
2903
|
+
$handle.children().hide();
|
|
2904
|
+
};
|
|
2905
|
+
};
|
|
2906
|
+
|
|
2907
|
+
/**
|
|
2908
|
+
* Dialog
|
|
2909
|
+
*
|
|
2910
|
+
* @class
|
|
2911
|
+
*/
|
|
2912
|
+
var Dialog = function () {
|
|
2913
|
+
|
|
2914
|
+
/**
|
|
2915
|
+
* toggle button status
|
|
2916
|
+
*
|
|
2917
|
+
* @param {jQuery} $btn
|
|
2918
|
+
* @param {Boolean} isEnable
|
|
2919
|
+
*/
|
|
2920
|
+
var toggleBtn = function ($btn, isEnable) {
|
|
2921
|
+
$btn.toggleClass('disabled', !isEnable);
|
|
2922
|
+
$btn.attr('disabled', !isEnable);
|
|
2923
|
+
};
|
|
2924
|
+
|
|
2925
|
+
/**
|
|
2926
|
+
* show image dialog
|
|
2927
|
+
*
|
|
2928
|
+
* @param {jQuery} $editable
|
|
2929
|
+
* @param {jQuery} $dialog
|
|
2930
|
+
* @return {Promise}
|
|
2931
|
+
*/
|
|
2932
|
+
this.showImageDialog = function ($editable, $dialog) {
|
|
2933
|
+
return $.Deferred(function (deferred) {
|
|
2934
|
+
var $imageDialog = $dialog.find('.note-image-dialog');
|
|
2935
|
+
|
|
2936
|
+
var $imageInput = $dialog.find('.note-image-input'),
|
|
2937
|
+
$imageUrl = $dialog.find('.note-image-url'),
|
|
2938
|
+
$imageBtn = $dialog.find('.note-image-btn');
|
|
2939
|
+
|
|
2940
|
+
$imageDialog.one('shown.bs.modal', function () {
|
|
2941
|
+
// Cloning imageInput to clear element.
|
|
2942
|
+
$imageInput.replaceWith($imageInput.clone()
|
|
2943
|
+
.on('change', function () {
|
|
2944
|
+
deferred.resolve(this.files);
|
|
2945
|
+
$imageDialog.modal('hide');
|
|
2946
|
+
})
|
|
2947
|
+
.val('')
|
|
2948
|
+
);
|
|
2949
|
+
|
|
2950
|
+
$imageBtn.click(function (event) {
|
|
2951
|
+
event.preventDefault();
|
|
2952
|
+
|
|
2953
|
+
deferred.resolve($imageUrl.val());
|
|
2954
|
+
$imageDialog.modal('hide');
|
|
2955
|
+
});
|
|
2956
|
+
|
|
2957
|
+
$imageUrl.on('keyup paste', function (event) {
|
|
2958
|
+
var url;
|
|
2959
|
+
|
|
2960
|
+
if (event.type === 'paste') {
|
|
2961
|
+
url = event.originalEvent.clipboardData.getData('text');
|
|
2962
|
+
} else {
|
|
2963
|
+
url = $imageUrl.val();
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
toggleBtn($imageBtn, url);
|
|
2967
|
+
}).val('').trigger('focus');
|
|
2968
|
+
}).one('hidden.bs.modal', function () {
|
|
2969
|
+
$imageInput.off('change');
|
|
2970
|
+
$imageUrl.off('keyup paste');
|
|
2971
|
+
$imageBtn.off('click');
|
|
2972
|
+
|
|
2973
|
+
if (deferred.state() === 'pending') {
|
|
2974
|
+
deferred.reject();
|
|
2975
|
+
}
|
|
2976
|
+
}).modal('show');
|
|
2977
|
+
});
|
|
2978
|
+
};
|
|
2979
|
+
|
|
2980
|
+
/**
|
|
2981
|
+
* Show video dialog and set event handlers on dialog controls.
|
|
2982
|
+
*
|
|
2983
|
+
* @param {jQuery} $dialog
|
|
2984
|
+
* @param {Object} videoInfo
|
|
2985
|
+
* @return {Promise}
|
|
2986
|
+
*/
|
|
2987
|
+
this.showVideoDialog = function ($editable, $dialog, videoInfo) {
|
|
2988
|
+
return $.Deferred(function (deferred) {
|
|
2989
|
+
var $videoDialog = $dialog.find('.note-video-dialog');
|
|
2990
|
+
var $videoUrl = $videoDialog.find('.note-video-url'),
|
|
2991
|
+
$videoBtn = $videoDialog.find('.note-video-btn');
|
|
2992
|
+
|
|
2993
|
+
$videoDialog.one('shown.bs.modal', function () {
|
|
2994
|
+
$videoUrl.val(videoInfo.text).keyup(function () {
|
|
2995
|
+
toggleBtn($videoBtn, $videoUrl.val());
|
|
2996
|
+
}).trigger('keyup').trigger('focus');
|
|
2997
|
+
|
|
2998
|
+
$videoBtn.click(function (event) {
|
|
2999
|
+
event.preventDefault();
|
|
3000
|
+
|
|
3001
|
+
deferred.resolve($videoUrl.val());
|
|
3002
|
+
$videoDialog.modal('hide');
|
|
3003
|
+
});
|
|
3004
|
+
}).one('hidden.bs.modal', function () {
|
|
3005
|
+
// dettach events
|
|
3006
|
+
$videoUrl.off('keyup');
|
|
3007
|
+
$videoBtn.off('click');
|
|
3008
|
+
|
|
3009
|
+
if (deferred.state() === 'pending') {
|
|
3010
|
+
deferred.reject();
|
|
3011
|
+
}
|
|
3012
|
+
}).modal('show');
|
|
3013
|
+
});
|
|
3014
|
+
};
|
|
3015
|
+
|
|
3016
|
+
/**
|
|
3017
|
+
* Show link dialog and set event handlers on dialog controls.
|
|
3018
|
+
*
|
|
3019
|
+
* @param {jQuery} $dialog
|
|
3020
|
+
* @param {Object} linkInfo
|
|
3021
|
+
* @return {Promise}
|
|
3022
|
+
*/
|
|
3023
|
+
this.showLinkDialog = function ($editable, $dialog, linkInfo) {
|
|
3024
|
+
return $.Deferred(function (deferred) {
|
|
3025
|
+
var $linkDialog = $dialog.find('.note-link-dialog');
|
|
3026
|
+
|
|
3027
|
+
var $linkText = $linkDialog.find('.note-link-text'),
|
|
3028
|
+
$linkUrl = $linkDialog.find('.note-link-url'),
|
|
3029
|
+
$linkBtn = $linkDialog.find('.note-link-btn'),
|
|
3030
|
+
$openInNewWindow = $linkDialog.find('input[type=checkbox]');
|
|
3031
|
+
|
|
3032
|
+
$linkDialog.one('shown.bs.modal', function () {
|
|
3033
|
+
$linkText.val(linkInfo.text);
|
|
3034
|
+
|
|
3035
|
+
$linkText.keyup(function () {
|
|
3036
|
+
// if linktext was modified by keyup,
|
|
3037
|
+
// stop cloning text from linkUrl
|
|
3038
|
+
linkInfo.text = $linkText.val();
|
|
3039
|
+
});
|
|
3040
|
+
|
|
3041
|
+
// if no url was given, copy text to url
|
|
3042
|
+
if (!linkInfo.url) {
|
|
3043
|
+
linkInfo.url = linkInfo.text;
|
|
3044
|
+
toggleBtn($linkBtn, linkInfo.text);
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
$linkUrl.keyup(function () {
|
|
3048
|
+
toggleBtn($linkBtn, $linkUrl.val());
|
|
3049
|
+
// display same link on `Text to display` input
|
|
3050
|
+
// when create a new link
|
|
3051
|
+
if (!linkInfo.text) {
|
|
3052
|
+
$linkText.val($linkUrl.val());
|
|
3053
|
+
}
|
|
3054
|
+
}).val(linkInfo.url).trigger('focus').trigger('select');
|
|
3055
|
+
|
|
3056
|
+
$openInNewWindow.prop('checked', linkInfo.newWindow);
|
|
3057
|
+
|
|
3058
|
+
$linkBtn.one('click', function (event) {
|
|
3059
|
+
event.preventDefault();
|
|
3060
|
+
|
|
3061
|
+
deferred.resolve({
|
|
3062
|
+
range: linkInfo.range,
|
|
3063
|
+
url: $linkUrl.val(),
|
|
3064
|
+
text: $linkText.val(),
|
|
3065
|
+
newWindow: $openInNewWindow.is(':checked')
|
|
3066
|
+
});
|
|
3067
|
+
$linkDialog.modal('hide');
|
|
3068
|
+
});
|
|
3069
|
+
}).one('hidden.bs.modal', function () {
|
|
3070
|
+
// dettach events
|
|
3071
|
+
$linkText.off('keyup');
|
|
3072
|
+
$linkUrl.off('keyup');
|
|
3073
|
+
$linkBtn.off('click');
|
|
3074
|
+
|
|
3075
|
+
if (deferred.state() === 'pending') {
|
|
3076
|
+
deferred.reject();
|
|
3077
|
+
}
|
|
3078
|
+
}).modal('show');
|
|
3079
|
+
}).promise();
|
|
3080
|
+
};
|
|
3081
|
+
|
|
3082
|
+
/**
|
|
3083
|
+
* show help dialog
|
|
3084
|
+
*
|
|
3085
|
+
* @param {jQuery} $dialog
|
|
3086
|
+
*/
|
|
3087
|
+
this.showHelpDialog = function ($editable, $dialog) {
|
|
3088
|
+
return $.Deferred(function (deferred) {
|
|
3089
|
+
var $helpDialog = $dialog.find('.note-help-dialog');
|
|
3090
|
+
|
|
3091
|
+
$helpDialog.one('hidden.bs.modal', function () {
|
|
3092
|
+
deferred.resolve();
|
|
3093
|
+
}).modal('show');
|
|
3094
|
+
}).promise();
|
|
3095
|
+
};
|
|
3096
|
+
};
|
|
3097
|
+
|
|
3098
|
+
|
|
3099
|
+
var CodeMirror;
|
|
3100
|
+
if (agent.hasCodeMirror) {
|
|
3101
|
+
if (agent.isSupportAmd) {
|
|
3102
|
+
require(['CodeMirror'], function (cm) {
|
|
3103
|
+
CodeMirror = cm;
|
|
3104
|
+
});
|
|
3105
|
+
} else {
|
|
3106
|
+
CodeMirror = window.CodeMirror;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
/**
|
|
3111
|
+
* EventHandler
|
|
3112
|
+
*/
|
|
3113
|
+
var EventHandler = function () {
|
|
3114
|
+
var $window = $(window);
|
|
3115
|
+
var $document = $(document);
|
|
3116
|
+
var $scrollbar = $('html, body');
|
|
3117
|
+
|
|
3118
|
+
var editor = new Editor();
|
|
3119
|
+
var toolbar = new Toolbar(), popover = new Popover();
|
|
3120
|
+
var handle = new Handle(), dialog = new Dialog();
|
|
3121
|
+
|
|
3122
|
+
/**
|
|
3123
|
+
* returns makeLayoutInfo from editor's descendant node.
|
|
3124
|
+
*
|
|
3125
|
+
* @param {Node} descendant
|
|
3126
|
+
* @returns {Object}
|
|
3127
|
+
*/
|
|
3128
|
+
var makeLayoutInfo = function (descendant) {
|
|
3129
|
+
var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
|
|
3130
|
+
|
|
3131
|
+
if (!$target.length) { return null; }
|
|
3132
|
+
|
|
3133
|
+
var $editor;
|
|
3134
|
+
if ($target.is('.note-editor, .note-air-editor')) {
|
|
3135
|
+
$editor = $target;
|
|
3136
|
+
} else {
|
|
3137
|
+
$editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
return dom.buildLayoutInfo($editor);
|
|
3141
|
+
};
|
|
3142
|
+
|
|
3143
|
+
/**
|
|
3144
|
+
* insert Images from file array.
|
|
3145
|
+
*
|
|
3146
|
+
* @param {jQuery} $editable
|
|
3147
|
+
* @param {File[]} files
|
|
3148
|
+
*/
|
|
3149
|
+
var insertImages = function ($editable, files) {
|
|
3150
|
+
editor.restoreRange($editable);
|
|
3151
|
+
var callbacks = $editable.data('callbacks');
|
|
3152
|
+
|
|
3153
|
+
// If onImageUpload options setted
|
|
3154
|
+
if (callbacks.onImageUpload) {
|
|
3155
|
+
callbacks.onImageUpload(files, editor, $editable);
|
|
3156
|
+
// else insert Image as dataURL
|
|
3157
|
+
} else {
|
|
3158
|
+
$.each(files, function (idx, file) {
|
|
3159
|
+
var filename = file.name;
|
|
3160
|
+
async.readFileAsDataURL(file).then(function (sDataURL) {
|
|
3161
|
+
editor.insertImage($editable, sDataURL, filename);
|
|
3162
|
+
}).fail(function () {
|
|
3163
|
+
if (callbacks.onImageUploadError) {
|
|
3164
|
+
callbacks.onImageUploadError();
|
|
3165
|
+
}
|
|
3166
|
+
});
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
};
|
|
3170
|
+
|
|
3171
|
+
var commands = {
|
|
3172
|
+
/**
|
|
3173
|
+
* @param {Object} layoutInfo
|
|
3174
|
+
*/
|
|
3175
|
+
showLinkDialog: function (layoutInfo) {
|
|
3176
|
+
var $editor = layoutInfo.editor(),
|
|
3177
|
+
$dialog = layoutInfo.dialog(),
|
|
3178
|
+
$editable = layoutInfo.editable(),
|
|
3179
|
+
linkInfo = editor.getLinkInfo($editable);
|
|
3180
|
+
|
|
3181
|
+
var options = $editor.data('options');
|
|
3182
|
+
|
|
3183
|
+
editor.saveRange($editable);
|
|
3184
|
+
dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) {
|
|
3185
|
+
editor.restoreRange($editable);
|
|
3186
|
+
editor.createLink($editable, linkInfo, options);
|
|
3187
|
+
// hide popover after creating link
|
|
3188
|
+
popover.hide(layoutInfo.popover());
|
|
3189
|
+
}).fail(function () {
|
|
3190
|
+
editor.restoreRange($editable);
|
|
3191
|
+
});
|
|
3192
|
+
},
|
|
3193
|
+
|
|
3194
|
+
/**
|
|
3195
|
+
* @param {Object} layoutInfo
|
|
3196
|
+
*/
|
|
3197
|
+
showImageDialog: function (layoutInfo) {
|
|
3198
|
+
var $dialog = layoutInfo.dialog(),
|
|
3199
|
+
$editable = layoutInfo.editable();
|
|
3200
|
+
|
|
3201
|
+
editor.saveRange($editable);
|
|
3202
|
+
dialog.showImageDialog($editable, $dialog).then(function (data) {
|
|
3203
|
+
editor.restoreRange($editable);
|
|
3204
|
+
|
|
3205
|
+
if (typeof data === 'string') {
|
|
3206
|
+
// image url
|
|
3207
|
+
editor.insertImage($editable, data);
|
|
3208
|
+
} else {
|
|
3209
|
+
// array of files
|
|
3210
|
+
insertImages($editable, data);
|
|
3211
|
+
}
|
|
3212
|
+
}).fail(function () {
|
|
3213
|
+
editor.restoreRange($editable);
|
|
3214
|
+
});
|
|
3215
|
+
},
|
|
3216
|
+
|
|
3217
|
+
/**
|
|
3218
|
+
* @param {Object} layoutInfo
|
|
3219
|
+
*/
|
|
3220
|
+
showVideoDialog: function (layoutInfo) {
|
|
3221
|
+
var $dialog = layoutInfo.dialog(),
|
|
3222
|
+
$editable = layoutInfo.editable(),
|
|
3223
|
+
videoInfo = editor.getVideoInfo($editable);
|
|
3224
|
+
|
|
3225
|
+
editor.saveRange($editable);
|
|
3226
|
+
dialog.showVideoDialog($editable, $dialog, videoInfo).then(function (sUrl) {
|
|
3227
|
+
editor.restoreRange($editable);
|
|
3228
|
+
editor.insertVideo($editable, sUrl);
|
|
3229
|
+
}).fail(function () {
|
|
3230
|
+
editor.restoreRange($editable);
|
|
3231
|
+
});
|
|
3232
|
+
},
|
|
3233
|
+
|
|
3234
|
+
/**
|
|
3235
|
+
* @param {Object} layoutInfo
|
|
3236
|
+
*/
|
|
3237
|
+
showHelpDialog: function (layoutInfo) {
|
|
3238
|
+
var $dialog = layoutInfo.dialog(),
|
|
3239
|
+
$editable = layoutInfo.editable();
|
|
3240
|
+
|
|
3241
|
+
editor.saveRange($editable);
|
|
3242
|
+
dialog.showHelpDialog($editable, $dialog).then(function () {
|
|
3243
|
+
editor.restoreRange($editable);
|
|
3244
|
+
});
|
|
3245
|
+
},
|
|
3246
|
+
|
|
3247
|
+
fullscreen: function (layoutInfo) {
|
|
3248
|
+
var $editor = layoutInfo.editor(),
|
|
3249
|
+
$toolbar = layoutInfo.toolbar(),
|
|
3250
|
+
$editable = layoutInfo.editable(),
|
|
3251
|
+
$codable = layoutInfo.codable();
|
|
3252
|
+
|
|
3253
|
+
var options = $editor.data('options');
|
|
3254
|
+
|
|
3255
|
+
var resize = function (size) {
|
|
3256
|
+
$editor.css('width', size.w);
|
|
3257
|
+
$editable.css('height', size.h);
|
|
3258
|
+
$codable.css('height', size.h);
|
|
3259
|
+
if ($codable.data('cmeditor')) {
|
|
3260
|
+
$codable.data('cmeditor').setsize(null, size.h);
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
|
|
3264
|
+
$editor.toggleClass('fullscreen');
|
|
3265
|
+
var isFullscreen = $editor.hasClass('fullscreen');
|
|
3266
|
+
if (isFullscreen) {
|
|
3267
|
+
$editable.data('orgheight', $editable.css('height'));
|
|
3268
|
+
|
|
3269
|
+
$window.on('resize', function () {
|
|
3270
|
+
resize({
|
|
3271
|
+
w: $window.width(),
|
|
3272
|
+
h: $window.height() - $toolbar.outerHeight()
|
|
3273
|
+
});
|
|
3274
|
+
}).trigger('resize');
|
|
3275
|
+
|
|
3276
|
+
$scrollbar.css('overflow', 'hidden');
|
|
3277
|
+
} else {
|
|
3278
|
+
$window.off('resize');
|
|
3279
|
+
resize({
|
|
3280
|
+
w: options.width || '',
|
|
3281
|
+
h: $editable.data('orgheight')
|
|
3282
|
+
});
|
|
3283
|
+
$scrollbar.css('overflow', 'visible');
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
toolbar.updateFullscreen($toolbar, isFullscreen);
|
|
3287
|
+
},
|
|
3288
|
+
|
|
3289
|
+
codeview: function (layoutInfo) {
|
|
3290
|
+
var $editor = layoutInfo.editor(),
|
|
3291
|
+
$toolbar = layoutInfo.toolbar(),
|
|
3292
|
+
$editable = layoutInfo.editable(),
|
|
3293
|
+
$codable = layoutInfo.codable(),
|
|
3294
|
+
$popover = layoutInfo.popover();
|
|
3295
|
+
|
|
3296
|
+
var options = $editor.data('options');
|
|
3297
|
+
|
|
3298
|
+
var cmEditor, server;
|
|
3299
|
+
|
|
3300
|
+
$editor.toggleClass('codeview');
|
|
3301
|
+
|
|
3302
|
+
var isCodeview = $editor.hasClass('codeview');
|
|
3303
|
+
if (isCodeview) {
|
|
3304
|
+
$codable.val($editable.html());
|
|
3305
|
+
$codable.height($editable.height());
|
|
3306
|
+
toolbar.deactivate($toolbar);
|
|
3307
|
+
popover.hide($popover);
|
|
3308
|
+
$codable.focus();
|
|
3309
|
+
|
|
3310
|
+
// activate CodeMirror as codable
|
|
3311
|
+
if (agent.hasCodeMirror) {
|
|
3312
|
+
cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
|
|
3313
|
+
|
|
3314
|
+
// CodeMirror TernServer
|
|
3315
|
+
if (options.codemirror.tern) {
|
|
3316
|
+
server = new CodeMirror.TernServer(options.codemirror.tern);
|
|
3317
|
+
cmEditor.ternServer = server;
|
|
3318
|
+
cmEditor.on('cursorActivity', function (cm) {
|
|
3319
|
+
server.updateArgHints(cm);
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
// CodeMirror hasn't Padding.
|
|
3324
|
+
cmEditor.setSize(null, $editable.outerHeight());
|
|
3325
|
+
// autoFormatRange If formatting included
|
|
3326
|
+
if (options.codemirror.autoFormatOnStart && cmEditor.autoFormatRange) {
|
|
3327
|
+
cmEditor.autoFormatRange({line: 0, ch: 0}, {
|
|
3328
|
+
line: cmEditor.lineCount(),
|
|
3329
|
+
ch: cmEditor.getTextArea().value.length
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
$codable.data('cmEditor', cmEditor);
|
|
3333
|
+
}
|
|
3334
|
+
} else {
|
|
3335
|
+
// deactivate CodeMirror as codable
|
|
3336
|
+
if (agent.hasCodeMirror) {
|
|
3337
|
+
cmEditor = $codable.data('cmEditor');
|
|
3338
|
+
$codable.val(cmEditor.getValue());
|
|
3339
|
+
cmEditor.toTextArea();
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
$editable.html($codable.val() || dom.emptyPara);
|
|
3343
|
+
$editable.height(options.height ? $codable.height() : 'auto');
|
|
3344
|
+
|
|
3345
|
+
toolbar.activate($toolbar);
|
|
3346
|
+
$editable.focus();
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
toolbar.updateCodeview(layoutInfo.toolbar(), isCodeview);
|
|
3350
|
+
}
|
|
3351
|
+
};
|
|
3352
|
+
|
|
3353
|
+
var hMousedown = function (event) {
|
|
3354
|
+
//preventDefault Selection for FF, IE8+
|
|
3355
|
+
if (dom.isImg(event.target)) {
|
|
3356
|
+
event.preventDefault();
|
|
3357
|
+
}
|
|
3358
|
+
};
|
|
3359
|
+
|
|
3360
|
+
var hToolbarAndPopoverUpdate = function (event) {
|
|
3361
|
+
// delay for range after mouseup
|
|
3362
|
+
setTimeout(function () {
|
|
3363
|
+
var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
|
|
3364
|
+
var styleInfo = editor.currentStyle(event.target);
|
|
3365
|
+
if (!styleInfo) { return; }
|
|
3366
|
+
|
|
3367
|
+
var isAirMode = layoutInfo.editor().data('options').airMode;
|
|
3368
|
+
if (!isAirMode) {
|
|
3369
|
+
toolbar.update(layoutInfo.toolbar(), styleInfo);
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
popover.update(layoutInfo.popover(), styleInfo, isAirMode);
|
|
3373
|
+
handle.update(layoutInfo.handle(), styleInfo, isAirMode);
|
|
3374
|
+
}, 0);
|
|
3375
|
+
};
|
|
3376
|
+
|
|
3377
|
+
var hScroll = function (event) {
|
|
3378
|
+
var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
|
|
3379
|
+
//hide popover and handle when scrolled
|
|
3380
|
+
popover.hide(layoutInfo.popover());
|
|
3381
|
+
handle.hide(layoutInfo.handle());
|
|
3382
|
+
};
|
|
3383
|
+
|
|
3384
|
+
/**
|
|
3385
|
+
* paste clipboard image
|
|
3386
|
+
*
|
|
3387
|
+
* @param {Event} event
|
|
3388
|
+
*/
|
|
3389
|
+
var hPasteClipboardImage = function (event) {
|
|
3390
|
+
var clipboardData = event.originalEvent.clipboardData;
|
|
3391
|
+
if (!clipboardData || !clipboardData.items || !clipboardData.items.length) {
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
|
|
3396
|
+
var item = list.head(clipboardData.items);
|
|
3397
|
+
var isClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
|
|
3398
|
+
|
|
3399
|
+
if (isClipboardImage) {
|
|
3400
|
+
insertImages(layoutInfo.editable(), [item.getAsFile()]);
|
|
3401
|
+
}
|
|
3402
|
+
};
|
|
3403
|
+
|
|
3404
|
+
/**
|
|
3405
|
+
* `mousedown` event handler on $handle
|
|
3406
|
+
* - controlSizing: resize image
|
|
3407
|
+
*
|
|
3408
|
+
* @param {MouseEvent} event
|
|
3409
|
+
*/
|
|
3410
|
+
var hHandleMousedown = function (event) {
|
|
3411
|
+
if (dom.isControlSizing(event.target)) {
|
|
3412
|
+
event.preventDefault();
|
|
3413
|
+
event.stopPropagation();
|
|
3414
|
+
|
|
3415
|
+
var layoutInfo = makeLayoutInfo(event.target),
|
|
3416
|
+
$handle = layoutInfo.handle(), $popover = layoutInfo.popover(),
|
|
3417
|
+
$editable = layoutInfo.editable(),
|
|
3418
|
+
$editor = layoutInfo.editor();
|
|
3419
|
+
|
|
3420
|
+
var target = $handle.find('.note-control-selection').data('target'),
|
|
3421
|
+
$target = $(target), posStart = $target.offset(),
|
|
3422
|
+
scrollTop = $document.scrollTop();
|
|
3423
|
+
|
|
3424
|
+
var isAirMode = $editor.data('options').airMode;
|
|
3425
|
+
|
|
3426
|
+
$document.on('mousemove', function (event) {
|
|
3427
|
+
editor.resizeTo({
|
|
3428
|
+
x: event.clientX - posStart.left,
|
|
3429
|
+
y: event.clientY - (posStart.top - scrollTop)
|
|
3430
|
+
}, $target, !event.shiftKey);
|
|
3431
|
+
|
|
3432
|
+
handle.update($handle, {image: target}, isAirMode);
|
|
3433
|
+
popover.update($popover, {image: target}, isAirMode);
|
|
3434
|
+
}).one('mouseup', function () {
|
|
3435
|
+
$document.off('mousemove');
|
|
3436
|
+
});
|
|
3437
|
+
|
|
3438
|
+
if (!$target.data('ratio')) { // original ratio.
|
|
3439
|
+
$target.data('ratio', $target.height() / $target.width());
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
editor.recordUndo($editable);
|
|
3443
|
+
}
|
|
3444
|
+
};
|
|
3445
|
+
|
|
3446
|
+
var hToolbarAndPopoverMousedown = function (event) {
|
|
3447
|
+
// prevent default event when insertTable (FF, Webkit)
|
|
3448
|
+
var $btn = $(event.target).closest('[data-event]');
|
|
3449
|
+
if ($btn.length) {
|
|
3450
|
+
event.preventDefault();
|
|
3451
|
+
}
|
|
3452
|
+
};
|
|
3453
|
+
|
|
3454
|
+
var hToolbarAndPopoverClick = function (event) {
|
|
3455
|
+
var $btn = $(event.target).closest('[data-event]');
|
|
3456
|
+
|
|
3457
|
+
if ($btn.length) {
|
|
3458
|
+
var eventName = $btn.attr('data-event'),
|
|
3459
|
+
value = $btn.attr('data-value');
|
|
3460
|
+
|
|
3461
|
+
var layoutInfo = makeLayoutInfo(event.target);
|
|
3462
|
+
|
|
3463
|
+
event.preventDefault();
|
|
3464
|
+
|
|
3465
|
+
// before command: detect control selection element($target)
|
|
3466
|
+
var $target;
|
|
3467
|
+
if ($.inArray(eventName, ['resize', 'floatMe', 'removeMedia']) !== -1) {
|
|
3468
|
+
var $selection = layoutInfo.handle().find('.note-control-selection');
|
|
3469
|
+
$target = $($selection.data('target'));
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
if (editor[eventName]) { // on command
|
|
3473
|
+
var $editable = layoutInfo.editable();
|
|
3474
|
+
$editable.trigger('focus');
|
|
3475
|
+
editor[eventName]($editable, value, $target);
|
|
3476
|
+
} else if (commands[eventName]) {
|
|
3477
|
+
commands[eventName].call(this, layoutInfo);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// after command
|
|
3481
|
+
if ($.inArray(eventName, ['backColor', 'foreColor']) !== -1) {
|
|
3482
|
+
var options = layoutInfo.editor().data('options', options);
|
|
3483
|
+
var module = options.airMode ? popover : toolbar;
|
|
3484
|
+
module.updateRecentColor(list.head($btn), eventName, value);
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
hToolbarAndPopoverUpdate(event);
|
|
3488
|
+
}
|
|
3489
|
+
};
|
|
3490
|
+
|
|
3491
|
+
var EDITABLE_PADDING = 24;
|
|
3492
|
+
/**
|
|
3493
|
+
* `mousedown` event handler on statusbar
|
|
3494
|
+
*
|
|
3495
|
+
* @param {MouseEvent} event
|
|
3496
|
+
*/
|
|
3497
|
+
var hStatusbarMousedown = function (event) {
|
|
3498
|
+
event.preventDefault();
|
|
3499
|
+
event.stopPropagation();
|
|
3500
|
+
|
|
3501
|
+
var $editable = makeLayoutInfo(event.target).editable();
|
|
3502
|
+
var nEditableTop = $editable.offset().top - $document.scrollTop();
|
|
3503
|
+
|
|
3504
|
+
var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
|
|
3505
|
+
var options = layoutInfo.editor().data('options');
|
|
3506
|
+
|
|
3507
|
+
$document.on('mousemove', function (event) {
|
|
3508
|
+
var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING);
|
|
3509
|
+
|
|
3510
|
+
nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight;
|
|
3511
|
+
nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight;
|
|
3512
|
+
|
|
3513
|
+
$editable.height(nHeight);
|
|
3514
|
+
}).one('mouseup', function () {
|
|
3515
|
+
$document.off('mousemove');
|
|
3516
|
+
});
|
|
3517
|
+
};
|
|
3518
|
+
|
|
3519
|
+
var PX_PER_EM = 18;
|
|
3520
|
+
var hDimensionPickerMove = function (event, options) {
|
|
3521
|
+
var $picker = $(event.target.parentNode); // target is mousecatcher
|
|
3522
|
+
var $dimensionDisplay = $picker.next();
|
|
3523
|
+
var $catcher = $picker.find('.note-dimension-picker-mousecatcher');
|
|
3524
|
+
var $highlighted = $picker.find('.note-dimension-picker-highlighted');
|
|
3525
|
+
var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');
|
|
3526
|
+
|
|
3527
|
+
var posOffset;
|
|
3528
|
+
// HTML5 with jQuery - e.offsetX is undefined in Firefox
|
|
3529
|
+
if (event.offsetX === undefined) {
|
|
3530
|
+
var posCatcher = $(event.target).offset();
|
|
3531
|
+
posOffset = {
|
|
3532
|
+
x: event.pageX - posCatcher.left,
|
|
3533
|
+
y: event.pageY - posCatcher.top
|
|
3534
|
+
};
|
|
3535
|
+
} else {
|
|
3536
|
+
posOffset = {
|
|
3537
|
+
x: event.offsetX,
|
|
3538
|
+
y: event.offsetY
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
var dim = {
|
|
3543
|
+
c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
|
|
3544
|
+
r: Math.ceil(posOffset.y / PX_PER_EM) || 1
|
|
3545
|
+
};
|
|
3546
|
+
|
|
3547
|
+
$highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });
|
|
3548
|
+
$catcher.attr('data-value', dim.c + 'x' + dim.r);
|
|
3549
|
+
|
|
3550
|
+
if (3 < dim.c && dim.c < options.insertTableMaxSize.col) {
|
|
3551
|
+
$unhighlighted.css({ width: dim.c + 1 + 'em'});
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
if (3 < dim.r && dim.r < options.insertTableMaxSize.row) {
|
|
3555
|
+
$unhighlighted.css({ height: dim.r + 1 + 'em'});
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
$dimensionDisplay.html(dim.c + ' x ' + dim.r);
|
|
3559
|
+
};
|
|
3560
|
+
|
|
3561
|
+
/**
|
|
3562
|
+
* Drag and Drop Events
|
|
3563
|
+
*
|
|
3564
|
+
* @param {Object} layoutInfo - layout Informations
|
|
3565
|
+
* @param {Boolean} disableDragAndDrop
|
|
3566
|
+
*/
|
|
3567
|
+
var handleDragAndDropEvent = function (layoutInfo, disableDragAndDrop) {
|
|
3568
|
+
if (disableDragAndDrop) {
|
|
3569
|
+
// prevent default drop event
|
|
3570
|
+
$document.on('drop', function (e) {
|
|
3571
|
+
e.preventDefault();
|
|
3572
|
+
});
|
|
3573
|
+
} else {
|
|
3574
|
+
attachDragAndDropEvent(layoutInfo);
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3577
|
+
|
|
3578
|
+
/**
|
|
3579
|
+
* attach Drag and Drop Events
|
|
3580
|
+
*
|
|
3581
|
+
* @param {Object} layoutInfo - layout Informations
|
|
3582
|
+
*/
|
|
3583
|
+
var attachDragAndDropEvent = function (layoutInfo) {
|
|
3584
|
+
var collection = $(),
|
|
3585
|
+
$dropzone = layoutInfo.dropzone,
|
|
3586
|
+
$dropzoneMessage = layoutInfo.dropzone.find('.note-dropzone-message');
|
|
3587
|
+
|
|
3588
|
+
// show dropzone on dragenter when dragging a object to document.
|
|
3589
|
+
$document.on('dragenter', function (e) {
|
|
3590
|
+
var isCodeview = layoutInfo.editor.hasClass('codeview');
|
|
3591
|
+
if (!isCodeview && !collection.length) {
|
|
3592
|
+
layoutInfo.editor.addClass('dragover');
|
|
3593
|
+
$dropzone.width(layoutInfo.editor.width());
|
|
3594
|
+
$dropzone.height(layoutInfo.editor.height());
|
|
3595
|
+
$dropzoneMessage.text('Drag Image Here');
|
|
3596
|
+
}
|
|
3597
|
+
collection = collection.add(e.target);
|
|
3598
|
+
}).on('dragleave', function (e) {
|
|
3599
|
+
collection = collection.not(e.target);
|
|
3600
|
+
if (!collection.length) {
|
|
3601
|
+
layoutInfo.editor.removeClass('dragover');
|
|
3602
|
+
}
|
|
3603
|
+
}).on('drop', function () {
|
|
3604
|
+
collection = $();
|
|
3605
|
+
layoutInfo.editor.removeClass('dragover');
|
|
3606
|
+
});
|
|
3607
|
+
|
|
3608
|
+
// change dropzone's message on hover.
|
|
3609
|
+
$dropzone.on('dragenter', function () {
|
|
3610
|
+
$dropzone.addClass('hover');
|
|
3611
|
+
$dropzoneMessage.text('Drop Image');
|
|
3612
|
+
}).on('dragleave', function () {
|
|
3613
|
+
$dropzone.removeClass('hover');
|
|
3614
|
+
$dropzoneMessage.text('Drag Image Here');
|
|
3615
|
+
});
|
|
3616
|
+
|
|
3617
|
+
// attach dropImage
|
|
3618
|
+
$dropzone.on('drop', function (event) {
|
|
3619
|
+
event.preventDefault();
|
|
3620
|
+
|
|
3621
|
+
var dataTransfer = event.originalEvent.dataTransfer;
|
|
3622
|
+
if (dataTransfer && dataTransfer.files) {
|
|
3623
|
+
var layoutInfo = makeLayoutInfo(event.currentTarget || event.target);
|
|
3624
|
+
layoutInfo.editable().focus();
|
|
3625
|
+
insertImages(layoutInfo.editable(), dataTransfer.files);
|
|
3626
|
+
}
|
|
3627
|
+
}).on('dragover', false); // prevent default dragover event
|
|
3628
|
+
};
|
|
3629
|
+
|
|
3630
|
+
|
|
3631
|
+
/**
|
|
3632
|
+
* bind KeyMap on keydown
|
|
3633
|
+
*
|
|
3634
|
+
* @param {Object} layoutInfo
|
|
3635
|
+
* @param {Object} keyMap
|
|
3636
|
+
*/
|
|
3637
|
+
this.bindKeyMap = function (layoutInfo, keyMap) {
|
|
3638
|
+
var $editor = layoutInfo.editor;
|
|
3639
|
+
var $editable = layoutInfo.editable;
|
|
3640
|
+
|
|
3641
|
+
layoutInfo = makeLayoutInfo($editable);
|
|
3642
|
+
|
|
3643
|
+
$editable.on('keydown', function (event) {
|
|
3644
|
+
var aKey = [];
|
|
3645
|
+
|
|
3646
|
+
// modifier
|
|
3647
|
+
if (event.metaKey) { aKey.push('CMD'); }
|
|
3648
|
+
if (event.ctrlKey && !event.altKey) { aKey.push('CTRL'); }
|
|
3649
|
+
if (event.shiftKey) { aKey.push('SHIFT'); }
|
|
3650
|
+
|
|
3651
|
+
// keycode
|
|
3652
|
+
var keyName = key.nameFromCode[event.keyCode];
|
|
3653
|
+
if (keyName) { aKey.push(keyName); }
|
|
3654
|
+
|
|
3655
|
+
var eventName = keyMap[aKey.join('+')];
|
|
3656
|
+
if (eventName) {
|
|
3657
|
+
event.preventDefault();
|
|
3658
|
+
|
|
3659
|
+
if (editor[eventName]) {
|
|
3660
|
+
editor[eventName]($editable, $editor.data('options'));
|
|
3661
|
+
} else if (commands[eventName]) {
|
|
3662
|
+
commands[eventName].call(this, layoutInfo);
|
|
3663
|
+
}
|
|
3664
|
+
} else if (key.isEdit(event.keyCode)) {
|
|
3665
|
+
editor.recordUndo($editable);
|
|
3666
|
+
}
|
|
3667
|
+
});
|
|
3668
|
+
};
|
|
3669
|
+
|
|
3670
|
+
/**
|
|
3671
|
+
* attach eventhandler
|
|
3672
|
+
*
|
|
3673
|
+
* @param {Object} layoutInfo - layout Informations
|
|
3674
|
+
* @param {Object} options - user options include custom event handlers
|
|
3675
|
+
* @param {Function} options.enter - enter key handler
|
|
3676
|
+
*/
|
|
3677
|
+
this.attach = function (layoutInfo, options) {
|
|
3678
|
+
// handlers for editable
|
|
3679
|
+
this.bindKeyMap(layoutInfo, options.keyMap[agent.isMac ? 'mac' : 'pc']);
|
|
3680
|
+
layoutInfo.editable.on('mousedown', hMousedown);
|
|
3681
|
+
layoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
|
|
3682
|
+
layoutInfo.editable.on('scroll', hScroll);
|
|
3683
|
+
layoutInfo.editable.on('paste', hPasteClipboardImage);
|
|
3684
|
+
|
|
3685
|
+
// handler for handle and popover
|
|
3686
|
+
layoutInfo.handle.on('mousedown', hHandleMousedown);
|
|
3687
|
+
layoutInfo.popover.on('click', hToolbarAndPopoverClick);
|
|
3688
|
+
layoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
|
|
3689
|
+
|
|
3690
|
+
// handlers for frame mode (toolbar, statusbar)
|
|
3691
|
+
if (!options.airMode) {
|
|
3692
|
+
// handler for drag and drop
|
|
3693
|
+
handleDragAndDropEvent(layoutInfo, options.disableDragAndDrop);
|
|
3694
|
+
|
|
3695
|
+
// handler for toolbar
|
|
3696
|
+
layoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
|
|
3697
|
+
layoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
|
|
3698
|
+
|
|
3699
|
+
// handler for statusbar
|
|
3700
|
+
if (!options.disableResizeEditor) {
|
|
3701
|
+
layoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
// handler for table dimension
|
|
3706
|
+
var $catcherContainer = options.airMode ? layoutInfo.popover :
|
|
3707
|
+
layoutInfo.toolbar;
|
|
3708
|
+
var $catcher = $catcherContainer.find('.note-dimension-picker-mousecatcher');
|
|
3709
|
+
$catcher.css({
|
|
3710
|
+
width: options.insertTableMaxSize.col + 'em',
|
|
3711
|
+
height: options.insertTableMaxSize.row + 'em'
|
|
3712
|
+
}).on('mousemove', function (event) {
|
|
3713
|
+
hDimensionPickerMove(event, options);
|
|
3714
|
+
});
|
|
3715
|
+
|
|
3716
|
+
// save options on editor
|
|
3717
|
+
layoutInfo.editor.data('options', options);
|
|
3718
|
+
|
|
3719
|
+
// ret styleWithCSS for backColor / foreColor clearing with 'inherit'.
|
|
3720
|
+
if (options.styleWithSpan && !agent.isMSIE) {
|
|
3721
|
+
// protect FF Error: NS_ERROR_FAILURE: Failure
|
|
3722
|
+
setTimeout(function () {
|
|
3723
|
+
document.execCommand('styleWithCSS', 0, true);
|
|
3724
|
+
}, 0);
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
// History
|
|
3728
|
+
layoutInfo.editable.data('NoteHistory', new History());
|
|
3729
|
+
|
|
3730
|
+
// basic event callbacks (lowercase)
|
|
3731
|
+
// enter, focus, blur, keyup, keydown
|
|
3732
|
+
if (options.onenter) {
|
|
3733
|
+
layoutInfo.editable.keypress(function (event) {
|
|
3734
|
+
if (event.keyCode === key.ENTER) { options.onenter(event); }
|
|
3735
|
+
});
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
if (options.onfocus) { layoutInfo.editable.focus(options.onfocus); }
|
|
3739
|
+
if (options.onblur) { layoutInfo.editable.blur(options.onblur); }
|
|
3740
|
+
if (options.onkeyup) { layoutInfo.editable.keyup(options.onkeyup); }
|
|
3741
|
+
if (options.onkeydown) { layoutInfo.editable.keydown(options.onkeydown); }
|
|
3742
|
+
if (options.onpaste) { layoutInfo.editable.on('paste', options.onpaste); }
|
|
3743
|
+
|
|
3744
|
+
// callbacks for advanced features (camel)
|
|
3745
|
+
if (options.onToolbarClick) { layoutInfo.toolbar.click(options.onToolbarClick); }
|
|
3746
|
+
if (options.onChange) {
|
|
3747
|
+
var hChange = function () {
|
|
3748
|
+
options.onChange(layoutInfo.editable, layoutInfo.editable.html());
|
|
3749
|
+
};
|
|
3750
|
+
|
|
3751
|
+
if (agent.isMSIE) {
|
|
3752
|
+
var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted';
|
|
3753
|
+
layoutInfo.editable.on(sDomEvents, hChange);
|
|
3754
|
+
} else {
|
|
3755
|
+
layoutInfo.editable.on('input', hChange);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
// All editor status will be saved on editable with jquery's data
|
|
3760
|
+
// for support multiple editor with singleton object.
|
|
3761
|
+
layoutInfo.editable.data('callbacks', {
|
|
3762
|
+
onAutoSave: options.onAutoSave,
|
|
3763
|
+
onImageUpload: options.onImageUpload,
|
|
3764
|
+
onImageUploadError: options.onImageUploadError,
|
|
3765
|
+
onFileUpload: options.onFileUpload,
|
|
3766
|
+
onFileUploadError: options.onFileUpload
|
|
3767
|
+
});
|
|
3768
|
+
};
|
|
3769
|
+
|
|
3770
|
+
this.dettach = function (layoutInfo, options) {
|
|
3771
|
+
layoutInfo.editable.off();
|
|
3772
|
+
|
|
3773
|
+
layoutInfo.popover.off();
|
|
3774
|
+
layoutInfo.handle.off();
|
|
3775
|
+
layoutInfo.dialog.off();
|
|
3776
|
+
|
|
3777
|
+
if (!options.airMode) {
|
|
3778
|
+
layoutInfo.dropzone.off();
|
|
3779
|
+
layoutInfo.toolbar.off();
|
|
3780
|
+
layoutInfo.statusbar.off();
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
};
|
|
3784
|
+
|
|
3785
|
+
/**
|
|
3786
|
+
* renderer
|
|
3787
|
+
*
|
|
3788
|
+
* rendering toolbar and editable
|
|
3789
|
+
*/
|
|
3790
|
+
var Renderer = function () {
|
|
3791
|
+
|
|
3792
|
+
/**
|
|
3793
|
+
* bootstrap button template
|
|
3794
|
+
*
|
|
3795
|
+
* @param {String} label
|
|
3796
|
+
* @param {Object} [options]
|
|
3797
|
+
* @param {String} [options.event]
|
|
3798
|
+
* @param {String} [options.value]
|
|
3799
|
+
* @param {String} [options.title]
|
|
3800
|
+
* @param {String} [options.dropdown]
|
|
3801
|
+
*/
|
|
3802
|
+
var tplButton = function (label, options) {
|
|
3803
|
+
var event = options.event;
|
|
3804
|
+
var value = options.value;
|
|
3805
|
+
var title = options.title;
|
|
3806
|
+
var className = options.className;
|
|
3807
|
+
var dropdown = options.dropdown;
|
|
3808
|
+
|
|
3809
|
+
return '<button type="button"' +
|
|
3810
|
+
' class="btn btn-default btn-sm btn-small' +
|
|
3811
|
+
(className ? ' ' + className : '') +
|
|
3812
|
+
(dropdown ? ' dropdown-toggle' : '') +
|
|
3813
|
+
'"' +
|
|
3814
|
+
(dropdown ? ' data-toggle="dropdown"' : '') +
|
|
3815
|
+
(title ? ' title="' + title + '"' : '') +
|
|
3816
|
+
(event ? ' data-event="' + event + '"' : '') +
|
|
3817
|
+
(value ? ' data-value=\'' + value + '\'' : '') +
|
|
3818
|
+
' tabindex="-1">' +
|
|
3819
|
+
label +
|
|
3820
|
+
(dropdown ? ' <span class="caret"></span>' : '') +
|
|
3821
|
+
'</button>' +
|
|
3822
|
+
(dropdown || '');
|
|
3823
|
+
};
|
|
3824
|
+
|
|
3825
|
+
/**
|
|
3826
|
+
* bootstrap icon button template
|
|
3827
|
+
*
|
|
3828
|
+
* @param {String} iconClassName
|
|
3829
|
+
* @param {Object} [options]
|
|
3830
|
+
* @param {String} [options.event]
|
|
3831
|
+
* @param {String} [options.value]
|
|
3832
|
+
* @param {String} [options.title]
|
|
3833
|
+
* @param {String} [options.dropdown]
|
|
3834
|
+
*/
|
|
3835
|
+
var tplIconButton = function (iconClassName, options) {
|
|
3836
|
+
var label = '<i class="' + iconClassName + '"></i>';
|
|
3837
|
+
return tplButton(label, options);
|
|
3838
|
+
};
|
|
3839
|
+
|
|
3840
|
+
/**
|
|
3841
|
+
* bootstrap popover template
|
|
3842
|
+
*
|
|
3843
|
+
* @param {String} className
|
|
3844
|
+
* @param {String} content
|
|
3845
|
+
*/
|
|
3846
|
+
var tplPopover = function (className, content) {
|
|
3847
|
+
return '<div class="' + className + ' popover bottom in" style="display: none;">' +
|
|
3848
|
+
'<div class="arrow"></div>' +
|
|
3849
|
+
'<div class="popover-content">' +
|
|
3850
|
+
content +
|
|
3851
|
+
'</div>' +
|
|
3852
|
+
'</div>';
|
|
3853
|
+
};
|
|
3854
|
+
|
|
3855
|
+
/**
|
|
3856
|
+
* bootstrap dialog template
|
|
3857
|
+
*
|
|
3858
|
+
* @param {String} className
|
|
3859
|
+
* @param {String} [title]
|
|
3860
|
+
* @param {String} body
|
|
3861
|
+
* @param {String} [footer]
|
|
3862
|
+
*/
|
|
3863
|
+
var tplDialog = function (className, title, body, footer) {
|
|
3864
|
+
return '<div class="' + className + ' modal" aria-hidden="false">' +
|
|
3865
|
+
'<div class="modal-dialog">' +
|
|
3866
|
+
'<div class="modal-content">' +
|
|
3867
|
+
(title ?
|
|
3868
|
+
'<div class="modal-header">' +
|
|
3869
|
+
'<button type="button" class="close" aria-hidden="true" tabindex="-1">×</button>' +
|
|
3870
|
+
'<h4 class="modal-title">' + title + '</h4>' +
|
|
3871
|
+
'</div>' : ''
|
|
3872
|
+
) +
|
|
3873
|
+
'<form class="note-modal-form">' +
|
|
3874
|
+
'<div class="modal-body">' +
|
|
3875
|
+
'<div class="row-fluid">' + body + '</div>' +
|
|
3876
|
+
'</div>' +
|
|
3877
|
+
(footer ?
|
|
3878
|
+
'<div class="modal-footer">' + footer + '</div>' : ''
|
|
3879
|
+
) +
|
|
3880
|
+
'</form>' +
|
|
3881
|
+
'</div>' +
|
|
3882
|
+
'</div>' +
|
|
3883
|
+
'</div>';
|
|
3884
|
+
};
|
|
3885
|
+
|
|
3886
|
+
var tplButtonInfo = {
|
|
3887
|
+
picture: function (lang) {
|
|
3888
|
+
return tplIconButton('fa fa-picture-o icon-picture', {
|
|
3889
|
+
event: 'showImageDialog',
|
|
3890
|
+
title: lang.image.image
|
|
3891
|
+
});
|
|
3892
|
+
},
|
|
3893
|
+
link: function (lang) {
|
|
3894
|
+
return tplIconButton('fa fa-link icon-link', {
|
|
3895
|
+
event: 'showLinkDialog',
|
|
3896
|
+
title: lang.link.link
|
|
3897
|
+
});
|
|
3898
|
+
},
|
|
3899
|
+
video: function (lang) {
|
|
3900
|
+
return tplIconButton('fa fa-youtube-play icon-play', {
|
|
3901
|
+
event: 'showVideoDialog',
|
|
3902
|
+
title: lang.video.video
|
|
3903
|
+
});
|
|
3904
|
+
},
|
|
3905
|
+
table: function (lang) {
|
|
3906
|
+
var dropdown = '<ul class="dropdown-menu">' +
|
|
3907
|
+
'<div class="note-dimension-picker">' +
|
|
3908
|
+
'<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
|
|
3909
|
+
'<div class="note-dimension-picker-highlighted"></div>' +
|
|
3910
|
+
'<div class="note-dimension-picker-unhighlighted"></div>' +
|
|
3911
|
+
'</div>' +
|
|
3912
|
+
'<div class="note-dimension-display"> 1 x 1 </div>' +
|
|
3913
|
+
'</ul>';
|
|
3914
|
+
return tplIconButton('fa fa-table icon-table', {
|
|
3915
|
+
title: lang.table.table,
|
|
3916
|
+
dropdown: dropdown
|
|
3917
|
+
});
|
|
3918
|
+
},
|
|
3919
|
+
style: function (lang, options) {
|
|
3920
|
+
var items = options.styleTags.reduce(function (memo, v) {
|
|
3921
|
+
var label = lang.style[v === 'p' ? 'normal' : v];
|
|
3922
|
+
return memo + '<li><a data-event="formatBlock" href="#" data-value="' + v + '">' +
|
|
3923
|
+
(
|
|
3924
|
+
(v === 'p' || v === 'pre') ? label :
|
|
3925
|
+
'<' + v + '>' + label + '</' + v + '>'
|
|
3926
|
+
) +
|
|
3927
|
+
'</a></li>';
|
|
3928
|
+
}, '');
|
|
3929
|
+
|
|
3930
|
+
return tplIconButton('fa fa-magic icon-magic', {
|
|
3931
|
+
title: lang.style.style,
|
|
3932
|
+
dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
|
|
3933
|
+
});
|
|
3934
|
+
},
|
|
3935
|
+
fontname: function (lang, options) {
|
|
3936
|
+
var items = options.fontNames.reduce(function (memo, v) {
|
|
3937
|
+
if (!agent.isFontInstalled(v)) { return memo; }
|
|
3938
|
+
return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '">' +
|
|
3939
|
+
'<i class="fa fa-check icon-ok"></i> ' + v +
|
|
3940
|
+
'</a></li>';
|
|
3941
|
+
}, '');
|
|
3942
|
+
var label = '<span class="note-current-fontname">' +
|
|
3943
|
+
options.defaultFontName +
|
|
3944
|
+
'</span>';
|
|
3945
|
+
return tplButton(label, {
|
|
3946
|
+
title: lang.font.name,
|
|
3947
|
+
dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
|
|
3948
|
+
});
|
|
3949
|
+
},
|
|
3950
|
+
fontsize: function (lang, options) {
|
|
3951
|
+
var items = options.fontSizes.reduce(function (memo, v) {
|
|
3952
|
+
return memo + '<li><a data-event="fontSize" href="#" data-value="' + v + '">' +
|
|
3953
|
+
'<i class="fa fa-check icon-ok"></i> ' + v +
|
|
3954
|
+
'</a></li>';
|
|
3955
|
+
}, '');
|
|
3956
|
+
|
|
3957
|
+
var label = '<span class="note-current-fontsize">11</span>';
|
|
3958
|
+
return tplButton(label, {
|
|
3959
|
+
title: lang.font.size,
|
|
3960
|
+
dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
|
|
3961
|
+
});
|
|
3962
|
+
},
|
|
3963
|
+
|
|
3964
|
+
color: function (lang) {
|
|
3965
|
+
var colorButtonLabel = '<i class="fa fa-font icon-font" style="color:black;background-color:yellow;"></i>';
|
|
3966
|
+
var colorButton = tplButton(colorButtonLabel, {
|
|
3967
|
+
className: 'note-recent-color',
|
|
3968
|
+
title: lang.color.recent,
|
|
3969
|
+
event: 'color',
|
|
3970
|
+
value: '{"backColor":"yellow"}'
|
|
3971
|
+
});
|
|
3972
|
+
|
|
3973
|
+
var dropdown = '<ul class="dropdown-menu">' +
|
|
3974
|
+
'<li>' +
|
|
3975
|
+
'<div class="btn-group">' +
|
|
3976
|
+
'<div class="note-palette-title">' + lang.color.background + '</div>' +
|
|
3977
|
+
'<div class="note-color-reset" data-event="backColor"' +
|
|
3978
|
+
' data-value="inherit" title="' + lang.color.transparent + '">' +
|
|
3979
|
+
lang.color.setTransparent +
|
|
3980
|
+
'</div>' +
|
|
3981
|
+
'<div class="note-color-palette" data-target-event="backColor"></div>' +
|
|
3982
|
+
'</div>' +
|
|
3983
|
+
'<div class="btn-group">' +
|
|
3984
|
+
'<div class="note-palette-title">' + lang.color.foreground + '</div>' +
|
|
3985
|
+
'<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' +
|
|
3986
|
+
lang.color.resetToDefault +
|
|
3987
|
+
'</div>' +
|
|
3988
|
+
'<div class="note-color-palette" data-target-event="foreColor"></div>' +
|
|
3989
|
+
'</div>' +
|
|
3990
|
+
'</li>' +
|
|
3991
|
+
'</ul>';
|
|
3992
|
+
|
|
3993
|
+
var moreButton = tplButton('', {
|
|
3994
|
+
title: lang.color.more,
|
|
3995
|
+
dropdown: dropdown
|
|
3996
|
+
});
|
|
3997
|
+
|
|
3998
|
+
return colorButton + moreButton;
|
|
3999
|
+
},
|
|
4000
|
+
bold: function (lang) {
|
|
4001
|
+
return tplIconButton('fa fa-bold icon-bold', {
|
|
4002
|
+
event: 'bold',
|
|
4003
|
+
title: lang.font.bold
|
|
4004
|
+
});
|
|
4005
|
+
},
|
|
4006
|
+
italic: function (lang) {
|
|
4007
|
+
return tplIconButton('fa fa-italic icon-italic', {
|
|
4008
|
+
event: 'italic',
|
|
4009
|
+
title: lang.font.italic
|
|
4010
|
+
});
|
|
4011
|
+
},
|
|
4012
|
+
underline: function (lang) {
|
|
4013
|
+
return tplIconButton('fa fa-underline icon-underline', {
|
|
4014
|
+
event: 'underline',
|
|
4015
|
+
title: lang.font.underline
|
|
4016
|
+
});
|
|
4017
|
+
},
|
|
4018
|
+
strikethrough: function (lang) {
|
|
4019
|
+
return tplIconButton('fa fa-strikethrough icon-strikethrough', {
|
|
4020
|
+
event: 'strikethrough',
|
|
4021
|
+
title: lang.font.strikethrough
|
|
4022
|
+
});
|
|
4023
|
+
},
|
|
4024
|
+
superscript: function (lang) {
|
|
4025
|
+
return tplIconButton('fa fa-superscript icon-superscript', {
|
|
4026
|
+
event: 'superscript',
|
|
4027
|
+
title: lang.font.superscript
|
|
4028
|
+
});
|
|
4029
|
+
},
|
|
4030
|
+
subscript: function (lang) {
|
|
4031
|
+
return tplIconButton('fa fa-subscript icon-subscript', {
|
|
4032
|
+
event: 'subscript',
|
|
4033
|
+
title: lang.font.subscript
|
|
4034
|
+
});
|
|
4035
|
+
},
|
|
4036
|
+
clear: function (lang) {
|
|
4037
|
+
return tplIconButton('fa fa-eraser icon-eraser', {
|
|
4038
|
+
event: 'removeFormat',
|
|
4039
|
+
title: lang.font.clear
|
|
4040
|
+
});
|
|
4041
|
+
},
|
|
4042
|
+
ul: function (lang) {
|
|
4043
|
+
return tplIconButton('fa fa-list-ul icon-list-ul', {
|
|
4044
|
+
event: 'insertUnorderedList',
|
|
4045
|
+
title: lang.lists.unordered
|
|
4046
|
+
});
|
|
4047
|
+
},
|
|
4048
|
+
ol: function (lang) {
|
|
4049
|
+
return tplIconButton('fa fa-list-ol icon-list-ol', {
|
|
4050
|
+
event: 'insertOrderedList',
|
|
4051
|
+
title: lang.lists.ordered
|
|
4052
|
+
});
|
|
4053
|
+
},
|
|
4054
|
+
paragraph: function (lang) {
|
|
4055
|
+
var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
|
|
4056
|
+
title: lang.paragraph.left,
|
|
4057
|
+
event: 'justifyLeft'
|
|
4058
|
+
});
|
|
4059
|
+
var centerButton = tplIconButton('fa fa-align-center icon-align-center', {
|
|
4060
|
+
title: lang.paragraph.center,
|
|
4061
|
+
event: 'justifyCenter'
|
|
4062
|
+
});
|
|
4063
|
+
var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
|
|
4064
|
+
title: lang.paragraph.right,
|
|
4065
|
+
event: 'justifyRight'
|
|
4066
|
+
});
|
|
4067
|
+
var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
|
|
4068
|
+
title: lang.paragraph.justify,
|
|
4069
|
+
event: 'justifyFull'
|
|
4070
|
+
});
|
|
4071
|
+
|
|
4072
|
+
var outdentButton = tplIconButton('fa fa-outdent icon-indent-left', {
|
|
4073
|
+
title: lang.paragraph.outdent,
|
|
4074
|
+
event: 'outdent'
|
|
4075
|
+
});
|
|
4076
|
+
var indentButton = tplIconButton('fa fa-indent icon-indent-right', {
|
|
4077
|
+
title: lang.paragraph.indent,
|
|
4078
|
+
event: 'indent'
|
|
4079
|
+
});
|
|
4080
|
+
|
|
4081
|
+
var dropdown = '<div class="dropdown-menu">' +
|
|
4082
|
+
'<div class="note-align btn-group">' +
|
|
4083
|
+
leftButton + centerButton + rightButton + justifyButton +
|
|
4084
|
+
'</div>' +
|
|
4085
|
+
'<div class="note-list btn-group">' +
|
|
4086
|
+
indentButton + outdentButton +
|
|
4087
|
+
'</div>' +
|
|
4088
|
+
'</div>';
|
|
4089
|
+
|
|
4090
|
+
return tplIconButton('fa fa-align-left icon-align-left', {
|
|
4091
|
+
title: lang.paragraph.paragraph,
|
|
4092
|
+
dropdown: dropdown
|
|
4093
|
+
});
|
|
4094
|
+
},
|
|
4095
|
+
height: function (lang, options) {
|
|
4096
|
+
var items = options.lineHeights.reduce(function (memo, v) {
|
|
4097
|
+
return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' +
|
|
4098
|
+
'<i class="fa fa-check icon-ok"></i> ' + v +
|
|
4099
|
+
'</a></li>';
|
|
4100
|
+
}, '');
|
|
4101
|
+
|
|
4102
|
+
return tplIconButton('fa fa-text-height icon-text-height', {
|
|
4103
|
+
title: lang.font.height,
|
|
4104
|
+
dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
|
|
4105
|
+
});
|
|
4106
|
+
|
|
4107
|
+
},
|
|
4108
|
+
help: function (lang) {
|
|
4109
|
+
return tplIconButton('fa fa-question icon-question', {
|
|
4110
|
+
event: 'showHelpDialog',
|
|
4111
|
+
title: lang.options.help
|
|
4112
|
+
});
|
|
4113
|
+
},
|
|
4114
|
+
fullscreen: function (lang) {
|
|
4115
|
+
return tplIconButton('fa fa-arrows-alt icon-fullscreen', {
|
|
4116
|
+
event: 'fullscreen',
|
|
4117
|
+
title: lang.options.fullscreen
|
|
4118
|
+
});
|
|
4119
|
+
},
|
|
4120
|
+
codeview: function (lang) {
|
|
4121
|
+
return tplIconButton('fa fa-code icon-code', {
|
|
4122
|
+
event: 'codeview',
|
|
4123
|
+
title: lang.options.codeview
|
|
4124
|
+
});
|
|
4125
|
+
},
|
|
4126
|
+
undo: function (lang) {
|
|
4127
|
+
return tplIconButton('fa fa-undo icon-undo', {
|
|
4128
|
+
event: 'undo',
|
|
4129
|
+
title: lang.history.undo
|
|
4130
|
+
});
|
|
4131
|
+
},
|
|
4132
|
+
redo: function (lang) {
|
|
4133
|
+
return tplIconButton('fa fa-repeat icon-repeat', {
|
|
4134
|
+
event: 'redo',
|
|
4135
|
+
title: lang.history.redo
|
|
4136
|
+
});
|
|
4137
|
+
},
|
|
4138
|
+
hr: function (lang) {
|
|
4139
|
+
return tplIconButton('fa fa-minus icon-hr', {
|
|
4140
|
+
event: 'insertHorizontalRule',
|
|
4141
|
+
title: lang.hr.insert
|
|
4142
|
+
});
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
|
|
4146
|
+
var tplPopovers = function (lang, options) {
|
|
4147
|
+
var tplLinkPopover = function () {
|
|
4148
|
+
var linkButton = tplIconButton('fa fa-edit icon-edit', {
|
|
4149
|
+
title: lang.link.edit,
|
|
4150
|
+
event: 'showLinkDialog'
|
|
4151
|
+
});
|
|
4152
|
+
var unlinkButton = tplIconButton('fa fa-unlink icon-unlink', {
|
|
4153
|
+
title: lang.link.unlink,
|
|
4154
|
+
event: 'unlink'
|
|
4155
|
+
});
|
|
4156
|
+
var content = '<a href="http://www.google.com" target="_blank">www.google.com</a> ' +
|
|
4157
|
+
'<div class="note-insert btn-group">' +
|
|
4158
|
+
linkButton + unlinkButton +
|
|
4159
|
+
'</div>';
|
|
4160
|
+
return tplPopover('note-link-popover', content);
|
|
4161
|
+
};
|
|
4162
|
+
|
|
4163
|
+
var tplImagePopover = function () {
|
|
4164
|
+
var fullButton = tplButton('<span class="note-fontsize-10">100%</span>', {
|
|
4165
|
+
title: lang.image.resizeFull,
|
|
4166
|
+
event: 'resize',
|
|
4167
|
+
value: '1'
|
|
4168
|
+
});
|
|
4169
|
+
var halfButton = tplButton('<span class="note-fontsize-10">50%</span>', {
|
|
4170
|
+
title: lang.image.resizeHalf,
|
|
4171
|
+
event: 'resize',
|
|
4172
|
+
value: '0.5'
|
|
4173
|
+
});
|
|
4174
|
+
var quarterButton = tplButton('<span class="note-fontsize-10">25%</span>', {
|
|
4175
|
+
title: lang.image.resizeQuarter,
|
|
4176
|
+
event: 'resize',
|
|
4177
|
+
value: '0.25'
|
|
4178
|
+
});
|
|
4179
|
+
|
|
4180
|
+
var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
|
|
4181
|
+
title: lang.image.floatLeft,
|
|
4182
|
+
event: 'floatMe',
|
|
4183
|
+
value: 'left'
|
|
4184
|
+
});
|
|
4185
|
+
var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
|
|
4186
|
+
title: lang.image.floatRight,
|
|
4187
|
+
event: 'floatMe',
|
|
4188
|
+
value: 'right'
|
|
4189
|
+
});
|
|
4190
|
+
var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
|
|
4191
|
+
title: lang.image.floatNone,
|
|
4192
|
+
event: 'floatMe',
|
|
4193
|
+
value: 'none'
|
|
4194
|
+
});
|
|
4195
|
+
|
|
4196
|
+
var removeButton = tplIconButton('fa fa-trash-o icon-trash', {
|
|
4197
|
+
title: lang.image.remove,
|
|
4198
|
+
event: 'removeMedia',
|
|
4199
|
+
value: 'none'
|
|
4200
|
+
});
|
|
4201
|
+
|
|
4202
|
+
var content = '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>' +
|
|
4203
|
+
'<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' +
|
|
4204
|
+
'<div class="btn-group">' + removeButton + '</div>';
|
|
4205
|
+
return tplPopover('note-image-popover', content);
|
|
4206
|
+
};
|
|
4207
|
+
|
|
4208
|
+
var tplAirPopover = function () {
|
|
4209
|
+
var content = '';
|
|
4210
|
+
for (var idx = 0, sz = options.airPopover.length; idx < sz; idx ++) {
|
|
4211
|
+
var group = options.airPopover[idx];
|
|
4212
|
+
content += '<div class="note-' + group[0] + ' btn-group">';
|
|
4213
|
+
for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
|
|
4214
|
+
content += tplButtonInfo[group[1][i]](lang, options);
|
|
4215
|
+
}
|
|
4216
|
+
content += '</div>';
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4219
|
+
return tplPopover('note-air-popover', content);
|
|
4220
|
+
};
|
|
4221
|
+
|
|
4222
|
+
return '<div class="note-popover">' +
|
|
4223
|
+
tplLinkPopover() +
|
|
4224
|
+
tplImagePopover() +
|
|
4225
|
+
(options.airMode ? tplAirPopover() : '') +
|
|
4226
|
+
'</div>';
|
|
4227
|
+
};
|
|
4228
|
+
|
|
4229
|
+
var tplHandles = function () {
|
|
4230
|
+
return '<div class="note-handle">' +
|
|
4231
|
+
'<div class="note-control-selection">' +
|
|
4232
|
+
'<div class="note-control-selection-bg"></div>' +
|
|
4233
|
+
'<div class="note-control-holder note-control-nw"></div>' +
|
|
4234
|
+
'<div class="note-control-holder note-control-ne"></div>' +
|
|
4235
|
+
'<div class="note-control-holder note-control-sw"></div>' +
|
|
4236
|
+
'<div class="note-control-sizing note-control-se"></div>' +
|
|
4237
|
+
'<div class="note-control-selection-info"></div>' +
|
|
4238
|
+
'</div>' +
|
|
4239
|
+
'</div>';
|
|
4240
|
+
};
|
|
4241
|
+
|
|
4242
|
+
/**
|
|
4243
|
+
* shortcut table template
|
|
4244
|
+
* @param {String} title
|
|
4245
|
+
* @param {String} body
|
|
4246
|
+
*/
|
|
4247
|
+
var tplShortcut = function (title, body) {
|
|
4248
|
+
return '<table class="note-shortcut">' +
|
|
4249
|
+
'<thead>' +
|
|
4250
|
+
'<tr><th></th><th>' + title + '</th></tr>' +
|
|
4251
|
+
'</thead>' +
|
|
4252
|
+
'<tbody>' + body + '</tbody>' +
|
|
4253
|
+
'</table>';
|
|
4254
|
+
};
|
|
4255
|
+
|
|
4256
|
+
var tplShortcutText = function (lang) {
|
|
4257
|
+
var body = '<tr><td>⌘ + B</td><td>' + lang.font.bold + '</td></tr>' +
|
|
4258
|
+
'<tr><td>⌘ + I</td><td>' + lang.font.italic + '</td></tr>' +
|
|
4259
|
+
'<tr><td>⌘ + U</td><td>' + lang.font.underline + '</td></tr>' +
|
|
4260
|
+
'<tr><td>⌘ + ⇧ + S</td><td>' + lang.font.strikethrough + '</td></tr>' +
|
|
4261
|
+
'<tr><td>⌘ + \\</td><td>' + lang.font.clear + '</td></tr>';
|
|
4262
|
+
|
|
4263
|
+
return tplShortcut(lang.shortcut.textFormatting, body);
|
|
4264
|
+
};
|
|
4265
|
+
|
|
4266
|
+
var tplShortcutAction = function (lang) {
|
|
4267
|
+
var body = '<tr><td>⌘ + Z</td><td>' + lang.history.undo + '</td></tr>' +
|
|
4268
|
+
'<tr><td>⌘ + ⇧ + Z</td><td>' + lang.history.redo + '</td></tr>' +
|
|
4269
|
+
'<tr><td>⌘ + ]</td><td>' + lang.paragraph.indent + '</td></tr>' +
|
|
4270
|
+
'<tr><td>⌘ + [</td><td>' + lang.paragraph.outdent + '</td></tr>' +
|
|
4271
|
+
'<tr><td>⌘ + ENTER</td><td>' + lang.hr.insert + '</td></tr>';
|
|
4272
|
+
|
|
4273
|
+
return tplShortcut(lang.shortcut.action, body);
|
|
4274
|
+
};
|
|
4275
|
+
|
|
4276
|
+
var tplShortcutPara = function (lang) {
|
|
4277
|
+
var body = '<tr><td>⌘ + ⇧ + L</td><td>' + lang.paragraph.left + '</td></tr>' +
|
|
4278
|
+
'<tr><td>⌘ + ⇧ + E</td><td>' + lang.paragraph.center + '</td></tr>' +
|
|
4279
|
+
'<tr><td>⌘ + ⇧ + R</td><td>' + lang.paragraph.right + '</td></tr>' +
|
|
4280
|
+
'<tr><td>⌘ + ⇧ + J</td><td>' + lang.paragraph.justify + '</td></tr>' +
|
|
4281
|
+
'<tr><td>⌘ + ⇧ + NUM7</td><td>' + lang.lists.ordered + '</td></tr>' +
|
|
4282
|
+
'<tr><td>⌘ + ⇧ + NUM8</td><td>' + lang.lists.unordered + '</td></tr>';
|
|
4283
|
+
|
|
4284
|
+
return tplShortcut(lang.shortcut.paragraphFormatting, body);
|
|
4285
|
+
};
|
|
4286
|
+
|
|
4287
|
+
var tplShortcutStyle = function (lang) {
|
|
4288
|
+
var body = '<tr><td>⌘ + NUM0</td><td>' + lang.style.normal + '</td></tr>' +
|
|
4289
|
+
'<tr><td>⌘ + NUM1</td><td>' + lang.style.h1 + '</td></tr>' +
|
|
4290
|
+
'<tr><td>⌘ + NUM2</td><td>' + lang.style.h2 + '</td></tr>' +
|
|
4291
|
+
'<tr><td>⌘ + NUM3</td><td>' + lang.style.h3 + '</td></tr>' +
|
|
4292
|
+
'<tr><td>⌘ + NUM4</td><td>' + lang.style.h4 + '</td></tr>' +
|
|
4293
|
+
'<tr><td>⌘ + NUM5</td><td>' + lang.style.h5 + '</td></tr>' +
|
|
4294
|
+
'<tr><td>⌘ + NUM6</td><td>' + lang.style.h6 + '</td></tr>';
|
|
4295
|
+
|
|
4296
|
+
return tplShortcut(lang.shortcut.documentStyle, body);
|
|
4297
|
+
};
|
|
4298
|
+
|
|
4299
|
+
var tplExtraShortcuts = function (lang, options) {
|
|
4300
|
+
var extraKeys = options.extraKeys;
|
|
4301
|
+
var body = '';
|
|
4302
|
+
for (var key in extraKeys) {
|
|
4303
|
+
if (extraKeys.hasOwnProperty(key)) {
|
|
4304
|
+
body += '<tr><td>' + key + '</td><td>' + extraKeys[key] + '</td></tr>';
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
return tplShortcut(lang.shortcut.extraKeys, body);
|
|
4309
|
+
};
|
|
4310
|
+
|
|
4311
|
+
var tplShortcutTable = function (lang, options) {
|
|
4312
|
+
var template = '<table class="note-shortcut-layout">' +
|
|
4313
|
+
'<tbody>' +
|
|
4314
|
+
'<tr><td>' + tplShortcutAction(lang, options) + '</td><td>' + tplShortcutText(lang, options) + '</td></tr>' +
|
|
4315
|
+
'<tr><td>' + tplShortcutStyle(lang, options) + '</td><td>' + tplShortcutPara(lang, options) + '</td></tr>';
|
|
4316
|
+
if (options.extraKeys) {
|
|
4317
|
+
template += '<tr><td colspan="2">' + tplExtraShortcuts(lang, options) + '</td></tr>';
|
|
4318
|
+
}
|
|
4319
|
+
template += '</tbody</table>';
|
|
4320
|
+
return template;
|
|
4321
|
+
};
|
|
4322
|
+
|
|
4323
|
+
var replaceMacKeys = function (sHtml) {
|
|
4324
|
+
return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
|
|
4325
|
+
};
|
|
4326
|
+
|
|
4327
|
+
var tplDialogs = function (lang, options) {
|
|
4328
|
+
var tplImageDialog = function () {
|
|
4329
|
+
var body =
|
|
4330
|
+
'<div class="note-group-select-from-files">' +
|
|
4331
|
+
'<h5>' + lang.image.selectFromFiles + '</h5>' +
|
|
4332
|
+
'<input class="note-image-input" type="file" name="files" accept="image/*" />' +
|
|
4333
|
+
'</div>' +
|
|
4334
|
+
'<h5>' + lang.image.url + '</h5>' +
|
|
4335
|
+
'<input class="note-image-url form-control span12" type="text" />';
|
|
4336
|
+
var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
|
|
4337
|
+
return tplDialog('note-image-dialog', lang.image.insert, body, footer);
|
|
4338
|
+
};
|
|
4339
|
+
|
|
4340
|
+
var tplLinkDialog = function () {
|
|
4341
|
+
var body = '<div class="form-group">' +
|
|
4342
|
+
'<label>' + lang.link.textToDisplay + '</label>' +
|
|
4343
|
+
'<input class="note-link-text form-control span12" type="text" />' +
|
|
4344
|
+
'</div>' +
|
|
4345
|
+
'<div class="form-group">' +
|
|
4346
|
+
'<label>' + lang.link.url + '</label>' +
|
|
4347
|
+
'<input class="note-link-url form-control span12" type="text" />' +
|
|
4348
|
+
'</div>' +
|
|
4349
|
+
(!options.disableLinkTarget ?
|
|
4350
|
+
'<div class="checkbox">' +
|
|
4351
|
+
'<label>' + '<input type="checkbox" checked> ' +
|
|
4352
|
+
lang.link.openInNewWindow +
|
|
4353
|
+
'</label>' +
|
|
4354
|
+
'</div>' : ''
|
|
4355
|
+
);
|
|
4356
|
+
var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
|
|
4357
|
+
return tplDialog('note-link-dialog', lang.link.insert, body, footer);
|
|
4358
|
+
};
|
|
4359
|
+
|
|
4360
|
+
var tplVideoDialog = function () {
|
|
4361
|
+
var body = '<div class="form-group">' +
|
|
4362
|
+
'<label>' + lang.video.url + '</label> <small class="text-muted">' + lang.video.providers + '</small>' +
|
|
4363
|
+
'<input class="note-video-url form-control span12" type="text" />' +
|
|
4364
|
+
'</div>';
|
|
4365
|
+
var footer = '<button href="#" class="btn btn-primary note-video-btn disabled" disabled>' + lang.video.insert + '</button>';
|
|
4366
|
+
return tplDialog('note-video-dialog', lang.video.insert, body, footer);
|
|
4367
|
+
};
|
|
4368
|
+
|
|
4369
|
+
var tplHelpDialog = function () {
|
|
4370
|
+
var body = '<a class="modal-close pull-right" aria-hidden="true" tabindex="-1">' + lang.shortcut.close + '</a>' +
|
|
4371
|
+
'<div class="title">' + lang.shortcut.shortcuts + '</div>' +
|
|
4372
|
+
(agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
|
|
4373
|
+
'<p class="text-center">' +
|
|
4374
|
+
'<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote 0.5.6</a> · ' +
|
|
4375
|
+
'<a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · ' +
|
|
4376
|
+
'<a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a>' +
|
|
4377
|
+
'</p>';
|
|
4378
|
+
return tplDialog('note-help-dialog', '', body, '');
|
|
4379
|
+
};
|
|
4380
|
+
|
|
4381
|
+
return '<div class="note-dialog">' +
|
|
4382
|
+
tplImageDialog() +
|
|
4383
|
+
tplLinkDialog() +
|
|
4384
|
+
tplVideoDialog() +
|
|
4385
|
+
tplHelpDialog() +
|
|
4386
|
+
'</div>';
|
|
4387
|
+
};
|
|
4388
|
+
|
|
4389
|
+
var tplStatusbar = function () {
|
|
4390
|
+
return '<div class="note-resizebar">' +
|
|
4391
|
+
'<div class="note-icon-bar"></div>' +
|
|
4392
|
+
'<div class="note-icon-bar"></div>' +
|
|
4393
|
+
'<div class="note-icon-bar"></div>' +
|
|
4394
|
+
'</div>';
|
|
4395
|
+
};
|
|
4396
|
+
|
|
4397
|
+
var representShortcut = function (str) {
|
|
4398
|
+
if (agent.isMac) {
|
|
4399
|
+
str = str.replace('CMD', '⌘').replace('SHIFT', '⇧');
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4402
|
+
return str.replace('BACKSLASH', '\\')
|
|
4403
|
+
.replace('SLASH', '/')
|
|
4404
|
+
.replace('LEFTBRACKET', '[')
|
|
4405
|
+
.replace('RIGHTBRACKET', ']');
|
|
4406
|
+
};
|
|
4407
|
+
|
|
4408
|
+
/**
|
|
4409
|
+
* createTooltip
|
|
4410
|
+
*
|
|
4411
|
+
* @param {jQuery} $container
|
|
4412
|
+
* @param {Object} keyMap
|
|
4413
|
+
* @param {String} [sPlacement]
|
|
4414
|
+
*/
|
|
4415
|
+
var createTooltip = function ($container, keyMap, sPlacement) {
|
|
4416
|
+
var invertedKeyMap = func.invertObject(keyMap);
|
|
4417
|
+
var $buttons = $container.find('button');
|
|
4418
|
+
|
|
4419
|
+
$buttons.each(function (i, elBtn) {
|
|
4420
|
+
var $btn = $(elBtn);
|
|
4421
|
+
var sShortcut = invertedKeyMap[$btn.data('event')];
|
|
4422
|
+
if (sShortcut) {
|
|
4423
|
+
$btn.attr('title', function (i, v) {
|
|
4424
|
+
return v + ' (' + representShortcut(sShortcut) + ')';
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
// bootstrap tooltip on btn-group bug
|
|
4428
|
+
// https://github.com/twbs/bootstrap/issues/5687
|
|
4429
|
+
}).tooltip({
|
|
4430
|
+
container: 'body',
|
|
4431
|
+
trigger: 'hover',
|
|
4432
|
+
placement: sPlacement || 'top'
|
|
4433
|
+
}).on('click', function () {
|
|
4434
|
+
$(this).tooltip('hide');
|
|
4435
|
+
});
|
|
4436
|
+
};
|
|
4437
|
+
|
|
4438
|
+
// createPalette
|
|
4439
|
+
var createPalette = function ($container, options) {
|
|
4440
|
+
var colorInfo = options.colors;
|
|
4441
|
+
$container.find('.note-color-palette').each(function () {
|
|
4442
|
+
var $palette = $(this), eventName = $palette.attr('data-target-event');
|
|
4443
|
+
var paletteContents = [];
|
|
4444
|
+
for (var row = 0, szRow = colorInfo.length; row < szRow; row++) {
|
|
4445
|
+
var colors = colorInfo[row];
|
|
4446
|
+
var buttons = [];
|
|
4447
|
+
for (var col = 0, szCol = colors.length; col < szCol; col++) {
|
|
4448
|
+
var color = colors[col];
|
|
4449
|
+
buttons.push(['<button type="button" class="note-color-btn" style="background-color:', color,
|
|
4450
|
+
';" data-event="', eventName,
|
|
4451
|
+
'" data-value="', color,
|
|
4452
|
+
'" title="', color,
|
|
4453
|
+
'" data-toggle="button" tabindex="-1"></button>'].join(''));
|
|
4454
|
+
}
|
|
4455
|
+
paletteContents.push('<div class="note-color-row">' + buttons.join('') + '</div>');
|
|
4456
|
+
}
|
|
4457
|
+
$palette.html(paletteContents.join(''));
|
|
4458
|
+
});
|
|
4459
|
+
};
|
|
4460
|
+
|
|
4461
|
+
/**
|
|
4462
|
+
* create summernote layout (air mode)
|
|
4463
|
+
*
|
|
4464
|
+
* @param {jQuery} $holder
|
|
4465
|
+
* @param {Object} options
|
|
4466
|
+
*/
|
|
4467
|
+
this.createLayoutByAirMode = function ($holder, options) {
|
|
4468
|
+
var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
|
|
4469
|
+
var langInfo = $.summernote.lang[options.lang];
|
|
4470
|
+
|
|
4471
|
+
var id = func.uniqueId();
|
|
4472
|
+
|
|
4473
|
+
$holder.addClass('note-air-editor note-editable');
|
|
4474
|
+
$holder.attr({
|
|
4475
|
+
'id': 'note-editor-' + id,
|
|
4476
|
+
'contentEditable': true
|
|
4477
|
+
});
|
|
4478
|
+
|
|
4479
|
+
var body = document.body;
|
|
4480
|
+
|
|
4481
|
+
// create Popover
|
|
4482
|
+
var $popover = $(tplPopovers(langInfo, options));
|
|
4483
|
+
$popover.addClass('note-air-layout');
|
|
4484
|
+
$popover.attr('id', 'note-popover-' + id);
|
|
4485
|
+
$popover.appendTo(body);
|
|
4486
|
+
createTooltip($popover, keyMap);
|
|
4487
|
+
createPalette($popover, options);
|
|
4488
|
+
|
|
4489
|
+
// create Handle
|
|
4490
|
+
var $handle = $(tplHandles());
|
|
4491
|
+
$handle.addClass('note-air-layout');
|
|
4492
|
+
$handle.attr('id', 'note-handle-' + id);
|
|
4493
|
+
$handle.appendTo(body);
|
|
4494
|
+
|
|
4495
|
+
// create Dialog
|
|
4496
|
+
var $dialog = $(tplDialogs(langInfo, options));
|
|
4497
|
+
$dialog.addClass('note-air-layout');
|
|
4498
|
+
$dialog.attr('id', 'note-dialog-' + id);
|
|
4499
|
+
$dialog.find('button.close, a.modal-close').click(function () {
|
|
4500
|
+
$(this).closest('.modal').modal('hide');
|
|
4501
|
+
});
|
|
4502
|
+
$dialog.appendTo(body);
|
|
4503
|
+
};
|
|
4504
|
+
|
|
4505
|
+
/**
|
|
4506
|
+
* create summernote layout (normal mode)
|
|
4507
|
+
*
|
|
4508
|
+
* @param {jQuery} $holder
|
|
4509
|
+
* @param {Object} options
|
|
4510
|
+
*/
|
|
4511
|
+
this.createLayoutByFrame = function ($holder, options) {
|
|
4512
|
+
//01. create Editor
|
|
4513
|
+
var $editor = $('<div class="note-editor"></div>');
|
|
4514
|
+
if (options.width) {
|
|
4515
|
+
$editor.width(options.width);
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
//02. statusbar (resizebar)
|
|
4519
|
+
if (options.height > 0) {
|
|
4520
|
+
$('<div class="note-statusbar">' + (options.disableResizeEditor ? '' : tplStatusbar()) + '</div>').prependTo($editor);
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
//03. create Editable
|
|
4524
|
+
var isContentEditable = !$holder.is(':disabled');
|
|
4525
|
+
var $editable = $('<div class="note-editable" contentEditable="' + isContentEditable + '"></div>')
|
|
4526
|
+
.prependTo($editor);
|
|
4527
|
+
if (options.height) {
|
|
4528
|
+
$editable.height(options.height);
|
|
4529
|
+
}
|
|
4530
|
+
if (options.direction) {
|
|
4531
|
+
$editable.attr('dir', options.direction);
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
$editable.html(dom.html($holder) || dom.emptyPara);
|
|
4535
|
+
|
|
4536
|
+
//031. create codable
|
|
4537
|
+
$('<textarea class="note-codable"></textarea>').prependTo($editor);
|
|
4538
|
+
|
|
4539
|
+
var langInfo = $.summernote.lang[options.lang];
|
|
4540
|
+
|
|
4541
|
+
//04. create Toolbar
|
|
4542
|
+
var toolbarHTML = '';
|
|
4543
|
+
for (var idx = 0, sz = options.toolbar.length; idx < sz; idx ++) {
|
|
4544
|
+
var groupName = options.toolbar[idx][0];
|
|
4545
|
+
var groupButtons = options.toolbar[idx][1];
|
|
4546
|
+
|
|
4547
|
+
toolbarHTML += '<div class="note-' + groupName + ' btn-group">';
|
|
4548
|
+
for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) {
|
|
4549
|
+
// continue creating toolbar even if a button doesn't exist
|
|
4550
|
+
if (!$.isFunction(tplButtonInfo[groupButtons[i]])) { continue; }
|
|
4551
|
+
toolbarHTML += tplButtonInfo[groupButtons[i]](langInfo, options);
|
|
4552
|
+
}
|
|
4553
|
+
toolbarHTML += '</div>';
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
toolbarHTML = '<div class="note-toolbar btn-toolbar">' + toolbarHTML + '</div>';
|
|
4557
|
+
|
|
4558
|
+
var $toolbar = $(toolbarHTML).prependTo($editor);
|
|
4559
|
+
var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
|
|
4560
|
+
createPalette($toolbar, options);
|
|
4561
|
+
createTooltip($toolbar, keyMap, 'bottom');
|
|
4562
|
+
|
|
4563
|
+
//05. create Popover
|
|
4564
|
+
var $popover = $(tplPopovers(langInfo, options)).prependTo($editor);
|
|
4565
|
+
createPalette($popover, options);
|
|
4566
|
+
createTooltip($popover, keyMap);
|
|
4567
|
+
|
|
4568
|
+
//06. handle(control selection, ...)
|
|
4569
|
+
$(tplHandles()).prependTo($editor);
|
|
4570
|
+
|
|
4571
|
+
//07. create Dialog
|
|
4572
|
+
var $dialog = $(tplDialogs(langInfo, options)).prependTo($editor);
|
|
4573
|
+
$dialog.find('button.close, a.modal-close').click(function () {
|
|
4574
|
+
$(this).closest('.modal').modal('hide');
|
|
4575
|
+
});
|
|
4576
|
+
|
|
4577
|
+
//08. create Dropzone
|
|
4578
|
+
$('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
|
|
4579
|
+
|
|
4580
|
+
//09. Editor/Holder switch
|
|
4581
|
+
$editor.insertAfter($holder);
|
|
4582
|
+
$holder.hide();
|
|
4583
|
+
};
|
|
4584
|
+
|
|
4585
|
+
this.noteEditorFromHolder = function ($holder) {
|
|
4586
|
+
if ($holder.hasClass('note-air-editor')) {
|
|
4587
|
+
return $holder;
|
|
4588
|
+
} else if ($holder.next().hasClass('note-editor')) {
|
|
4589
|
+
return $holder.next();
|
|
4590
|
+
} else {
|
|
4591
|
+
return $();
|
|
4592
|
+
}
|
|
4593
|
+
};
|
|
4594
|
+
|
|
4595
|
+
/**
|
|
4596
|
+
* create summernote layout
|
|
4597
|
+
*
|
|
4598
|
+
* @param {jQuery} $holder
|
|
4599
|
+
* @param {Object} options
|
|
4600
|
+
*/
|
|
4601
|
+
this.createLayout = function ($holder, options) {
|
|
4602
|
+
if (this.noteEditorFromHolder($holder).length) {
|
|
4603
|
+
return;
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
if (options.airMode) {
|
|
4607
|
+
this.createLayoutByAirMode($holder, options);
|
|
4608
|
+
} else {
|
|
4609
|
+
this.createLayoutByFrame($holder, options);
|
|
4610
|
+
}
|
|
4611
|
+
};
|
|
4612
|
+
|
|
4613
|
+
/**
|
|
4614
|
+
* returns layoutInfo from holder
|
|
4615
|
+
*
|
|
4616
|
+
* @param {jQuery} $holder - placeholder
|
|
4617
|
+
* @returns {Object}
|
|
4618
|
+
*/
|
|
4619
|
+
this.layoutInfoFromHolder = function ($holder) {
|
|
4620
|
+
var $editor = this.noteEditorFromHolder($holder);
|
|
4621
|
+
if (!$editor.length) { return; }
|
|
4622
|
+
|
|
4623
|
+
var layoutInfo = dom.buildLayoutInfo($editor);
|
|
4624
|
+
// cache all properties.
|
|
4625
|
+
for (var key in layoutInfo) {
|
|
4626
|
+
if (layoutInfo.hasOwnProperty(key)) {
|
|
4627
|
+
layoutInfo[key] = layoutInfo[key].call();
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
return layoutInfo;
|
|
4631
|
+
};
|
|
4632
|
+
|
|
4633
|
+
/**
|
|
4634
|
+
* removeLayout
|
|
4635
|
+
*
|
|
4636
|
+
* @param {jQuery} $holder - placeholder
|
|
4637
|
+
* @param {Object} layoutInfo
|
|
4638
|
+
* @param {Object} options
|
|
4639
|
+
*
|
|
4640
|
+
*/
|
|
4641
|
+
this.removeLayout = function ($holder, layoutInfo, options) {
|
|
4642
|
+
if (options.airMode) {
|
|
4643
|
+
$holder.removeClass('note-air-editor note-editable')
|
|
4644
|
+
.removeAttr('id contentEditable');
|
|
4645
|
+
|
|
4646
|
+
layoutInfo.popover.remove();
|
|
4647
|
+
layoutInfo.handle.remove();
|
|
4648
|
+
layoutInfo.dialog.remove();
|
|
4649
|
+
} else {
|
|
4650
|
+
$holder.html(layoutInfo.editable.html());
|
|
4651
|
+
|
|
4652
|
+
layoutInfo.editor.remove();
|
|
4653
|
+
$holder.show();
|
|
4654
|
+
}
|
|
4655
|
+
};
|
|
4656
|
+
};
|
|
4657
|
+
|
|
4658
|
+
// jQuery namespace for summernote
|
|
4659
|
+
$.summernote = $.summernote || {};
|
|
4660
|
+
|
|
4661
|
+
// extends default `settings`
|
|
4662
|
+
$.extend($.summernote, settings);
|
|
4663
|
+
|
|
4664
|
+
var renderer = new Renderer();
|
|
4665
|
+
var eventHandler = new EventHandler();
|
|
4666
|
+
|
|
4667
|
+
/**
|
|
4668
|
+
* extend jquery fn
|
|
4669
|
+
*/
|
|
4670
|
+
$.fn.extend({
|
|
4671
|
+
/**
|
|
4672
|
+
* initialize summernote
|
|
4673
|
+
* - create editor layout and attach Mouse and keyboard events.
|
|
4674
|
+
*
|
|
4675
|
+
* @param {Object} options
|
|
4676
|
+
* @returns {this}
|
|
4677
|
+
*/
|
|
4678
|
+
summernote: function (options) {
|
|
4679
|
+
// extend default options
|
|
4680
|
+
options = $.extend({}, $.summernote.options, options);
|
|
4681
|
+
|
|
4682
|
+
this.each(function (idx, elHolder) {
|
|
4683
|
+
var $holder = $(elHolder);
|
|
4684
|
+
|
|
4685
|
+
// createLayout with options
|
|
4686
|
+
renderer.createLayout($holder, options);
|
|
4687
|
+
|
|
4688
|
+
var info = renderer.layoutInfoFromHolder($holder);
|
|
4689
|
+
eventHandler.attach(info, options);
|
|
4690
|
+
|
|
4691
|
+
// Textarea: auto filling the code before form submit.
|
|
4692
|
+
if (dom.isTextarea($holder[0])) {
|
|
4693
|
+
$holder.closest('form').submit(function () {
|
|
4694
|
+
$holder.val($holder.code());
|
|
4695
|
+
});
|
|
4696
|
+
}
|
|
4697
|
+
});
|
|
4698
|
+
|
|
4699
|
+
// focus on first editable element
|
|
4700
|
+
if (this.first().length && options.focus) {
|
|
4701
|
+
var info = renderer.layoutInfoFromHolder(this.first());
|
|
4702
|
+
info.editable.focus();
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
// callback on init
|
|
4706
|
+
if (this.length && options.oninit) {
|
|
4707
|
+
options.oninit();
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
return this;
|
|
4711
|
+
},
|
|
4712
|
+
//
|
|
4713
|
+
|
|
4714
|
+
/**
|
|
4715
|
+
* get the HTML contents of note or set the HTML contents of note.
|
|
4716
|
+
*
|
|
4717
|
+
* @param {String} [sHTML] - HTML contents(optional, set)
|
|
4718
|
+
* @returns {this|String} - context(set) or HTML contents of note(get).
|
|
4719
|
+
*/
|
|
4720
|
+
code: function (sHTML) {
|
|
4721
|
+
// get the HTML contents of note
|
|
4722
|
+
if (sHTML === undefined) {
|
|
4723
|
+
var $holder = this.first();
|
|
4724
|
+
if (!$holder.length) { return; }
|
|
4725
|
+
var info = renderer.layoutInfoFromHolder($holder);
|
|
4726
|
+
if (!!(info && info.editable)) {
|
|
4727
|
+
var isCodeview = info.editor.hasClass('codeview');
|
|
4728
|
+
if (isCodeview && agent.hasCodeMirror) {
|
|
4729
|
+
info.codable.data('cmEditor').save();
|
|
4730
|
+
}
|
|
4731
|
+
return isCodeview ? info.codable.val() : info.editable.html();
|
|
4732
|
+
}
|
|
4733
|
+
return dom.isTextarea($holder[0]) ? $holder.val() : $holder.html();
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
// set the HTML contents of note
|
|
4737
|
+
this.each(function (i, elHolder) {
|
|
4738
|
+
var info = renderer.layoutInfoFromHolder($(elHolder));
|
|
4739
|
+
if (info && info.editable) { info.editable.html(sHTML); }
|
|
4740
|
+
});
|
|
4741
|
+
|
|
4742
|
+
return this;
|
|
4743
|
+
},
|
|
4744
|
+
|
|
4745
|
+
/**
|
|
4746
|
+
* destroy Editor Layout and dettach Key and Mouse Event
|
|
4747
|
+
* @returns {this}
|
|
4748
|
+
*/
|
|
4749
|
+
destroy: function () {
|
|
4750
|
+
this.each(function (idx, elHolder) {
|
|
4751
|
+
var $holder = $(elHolder);
|
|
4752
|
+
|
|
4753
|
+
var info = renderer.layoutInfoFromHolder($holder);
|
|
4754
|
+
if (!info || !info.editable) { return; }
|
|
4755
|
+
|
|
4756
|
+
var options = info.editor.data('options');
|
|
4757
|
+
|
|
4758
|
+
eventHandler.dettach(info, options);
|
|
4759
|
+
renderer.removeLayout($holder, info, options);
|
|
4760
|
+
});
|
|
4761
|
+
|
|
4762
|
+
return this;
|
|
4763
|
+
}
|
|
4764
|
+
});
|
|
4765
|
+
}));
|