simditor-rails 1.0.1 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/simditor-rails.rb +2 -5
- data/lib/simditor-rails/engine.rb +9 -0
- data/lib/simditor-rails/version.rb +1 -1
- data/vendor/assets/images/loading-upload.gif +0 -0
- data/vendor/assets/javascripts/simditor/module.js +151 -0
- data/vendor/assets/javascripts/simditor/simditor.coffee +802 -405
- data/vendor/assets/javascripts/simditor/uploader.js +287 -0
- data/vendor/assets/stylesheets/simditor.scss +667 -567
- metadata +6 -4
- data/vendor/assets/javascripts/simditor/module.coffee +0 -80
- data/vendor/assets/javascripts/simditor/uploader.coffee +0 -228
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 896fba20d92494e0ef2e9bbcfcad1b92b7f17660
|
4
|
+
data.tar.gz: cc3dc37a5e0058a7ad08601421dba33b1aed0c48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a68523ad12395635a6f07e3e6d6b90f250c075709dfd0c1c83fcc341453d42cb4e4e2431d8d8f2f3625a602dd48e05d9a891c1eb86d4f2d076c2d859c50cae6
|
7
|
+
data.tar.gz: ce685882ba6f4002103487975644fb9db37eb1bb68178e7cc8a2098b7d6cbc157c4dc6c52b07ebdc4b8ba86b72b175b509a2bd3b6012470164ef555a3381f249
|
data/lib/simditor-rails.rb
CHANGED
Binary file
|
@@ -0,0 +1,151 @@
|
|
1
|
+
(function() {
|
2
|
+
var Module, Plugin, Widget,
|
3
|
+
__slice = [].slice,
|
4
|
+
__hasProp = {}.hasOwnProperty,
|
5
|
+
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
6
|
+
|
7
|
+
Module = (function() {
|
8
|
+
function Module() {}
|
9
|
+
|
10
|
+
Module.extend = function(obj) {
|
11
|
+
var key, val, _ref;
|
12
|
+
if (!((obj != null) && typeof obj === 'object')) {
|
13
|
+
return;
|
14
|
+
}
|
15
|
+
for (key in obj) {
|
16
|
+
val = obj[key];
|
17
|
+
if (key !== 'included' && key !== 'extended') {
|
18
|
+
this[key] = val;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
return (_ref = obj.extended) != null ? _ref.call(this) : void 0;
|
22
|
+
};
|
23
|
+
|
24
|
+
Module.include = function(obj) {
|
25
|
+
var key, val, _ref;
|
26
|
+
if (!((obj != null) && typeof obj === 'object')) {
|
27
|
+
return;
|
28
|
+
}
|
29
|
+
for (key in obj) {
|
30
|
+
val = obj[key];
|
31
|
+
if (key !== 'included' && key !== 'extended') {
|
32
|
+
this.prototype[key] = val;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
return (_ref = obj.included) != null ? _ref.call(this) : void 0;
|
36
|
+
};
|
37
|
+
|
38
|
+
Module.prototype.on = function() {
|
39
|
+
var args, _ref;
|
40
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
41
|
+
return (_ref = $(this)).on.apply(_ref, args);
|
42
|
+
};
|
43
|
+
|
44
|
+
Module.prototype.one = function() {
|
45
|
+
var args, _ref;
|
46
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
47
|
+
return (_ref = $(this)).one.apply(_ref, args);
|
48
|
+
};
|
49
|
+
|
50
|
+
Module.prototype.off = function() {
|
51
|
+
var args, _ref;
|
52
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
53
|
+
return (_ref = $(this)).off.apply(_ref, args);
|
54
|
+
};
|
55
|
+
|
56
|
+
Module.prototype.trigger = function() {
|
57
|
+
var args, _ref;
|
58
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
59
|
+
return (_ref = $(this)).trigger.apply(_ref, args);
|
60
|
+
};
|
61
|
+
|
62
|
+
Module.prototype.triggerHandler = function() {
|
63
|
+
var args, _ref;
|
64
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
65
|
+
return (_ref = $(this)).triggerHandler.apply(_ref, args);
|
66
|
+
};
|
67
|
+
|
68
|
+
return Module;
|
69
|
+
|
70
|
+
})();
|
71
|
+
|
72
|
+
Widget = (function(_super) {
|
73
|
+
__extends(Widget, _super);
|
74
|
+
|
75
|
+
Widget.connect = function(cls) {
|
76
|
+
if (typeof cls !== 'function') {
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
if (!cls.className) {
|
80
|
+
throw new Error('Widget.connect: lack of class property "className"');
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
if (!this._connectedClasses) {
|
84
|
+
this._connectedClasses = [];
|
85
|
+
}
|
86
|
+
this._connectedClasses.push(cls);
|
87
|
+
if (cls.className) {
|
88
|
+
return this[cls.className] = cls;
|
89
|
+
}
|
90
|
+
};
|
91
|
+
|
92
|
+
Widget.prototype._init = function() {};
|
93
|
+
|
94
|
+
Widget.prototype.opts = {};
|
95
|
+
|
96
|
+
function Widget(opts) {
|
97
|
+
var cls, instance, instances, name, _base, _i, _len;
|
98
|
+
this.opts = $.extend({}, this.opts, opts);
|
99
|
+
(_base = this.constructor)._connectedClasses || (_base._connectedClasses = []);
|
100
|
+
instances = (function() {
|
101
|
+
var _i, _len, _ref, _results;
|
102
|
+
_ref = this.constructor._connectedClasses;
|
103
|
+
_results = [];
|
104
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
105
|
+
cls = _ref[_i];
|
106
|
+
name = cls.className.charAt(0).toLowerCase() + cls.className.slice(1);
|
107
|
+
_results.push(this[name] = new cls(this));
|
108
|
+
}
|
109
|
+
return _results;
|
110
|
+
}).call(this);
|
111
|
+
this._init();
|
112
|
+
for (_i = 0, _len = instances.length; _i < _len; _i++) {
|
113
|
+
instance = instances[_i];
|
114
|
+
if (typeof instance._init === "function") {
|
115
|
+
instance._init();
|
116
|
+
}
|
117
|
+
}
|
118
|
+
this.trigger('pluginconnected');
|
119
|
+
}
|
120
|
+
|
121
|
+
Widget.prototype.destroy = function() {};
|
122
|
+
|
123
|
+
return Widget;
|
124
|
+
|
125
|
+
})(Module);
|
126
|
+
|
127
|
+
Plugin = (function(_super) {
|
128
|
+
__extends(Plugin, _super);
|
129
|
+
|
130
|
+
Plugin.className = 'Plugin';
|
131
|
+
|
132
|
+
Plugin.prototype.opts = {};
|
133
|
+
|
134
|
+
function Plugin(widget) {
|
135
|
+
this.widget = widget;
|
136
|
+
this.opts = $.extend({}, this.opts, this.widget.opts);
|
137
|
+
}
|
138
|
+
|
139
|
+
Plugin.prototype._init = function() {};
|
140
|
+
|
141
|
+
return Plugin;
|
142
|
+
|
143
|
+
})(Module);
|
144
|
+
|
145
|
+
window.Module = Module;
|
146
|
+
|
147
|
+
window.Widget = Widget;
|
148
|
+
|
149
|
+
window.Plugin = Plugin;
|
150
|
+
|
151
|
+
}).call(this);
|
@@ -33,6 +33,9 @@ class Selection extends Plugin
|
|
33
33
|
@clear()
|
34
34
|
@sel.addRange(range)
|
35
35
|
|
36
|
+
# firefox won't auto focus while applying new range
|
37
|
+
@editor.body.focus() if !@editor.inputManager.focused and (@editor.util.browser.firefox or @editor.util.browser.msie)
|
38
|
+
|
36
39
|
rangeAtEndOf: (node, range = @getRange()) ->
|
37
40
|
return unless range? and range.collapsed
|
38
41
|
|
@@ -128,14 +131,36 @@ class Selection extends Plugin
|
|
128
131
|
range.setEnd(node, 0)
|
129
132
|
else
|
130
133
|
nodeLength = @editor.util.getNodeLength node
|
131
|
-
|
134
|
+
if node.nodeType != 3 and nodeLength > 0
|
135
|
+
$lastNode = $(node).contents().last()
|
136
|
+
if $lastNode.is('br')
|
137
|
+
nodeLength -= 1
|
138
|
+
else if $lastNode[0].nodeType != 3 and @editor.util.isEmptyNode($lastNode)
|
139
|
+
$lastNode.append @editor.util.phBr
|
140
|
+
node = $lastNode[0]
|
141
|
+
nodeLength = 0
|
142
|
+
|
132
143
|
range.setEnd(node, nodeLength)
|
133
144
|
|
134
145
|
range.collapse(false)
|
135
146
|
@selectRange range
|
136
147
|
|
137
148
|
deleteRangeContents: (range = @getRange()) ->
|
138
|
-
range.
|
149
|
+
startRange = range.cloneRange()
|
150
|
+
endRange = range.cloneRange()
|
151
|
+
startRange.collapse(true)
|
152
|
+
endRange.collapse(false)
|
153
|
+
|
154
|
+
# the default behavior of cmd+a is buggy
|
155
|
+
if !range.collapsed and @rangeAtStartOf(@editor.body, startRange) and @rangeAtEndOf(@editor.body, endRange)
|
156
|
+
@editor.body.empty()
|
157
|
+
range.setStart @editor.body[0], 0
|
158
|
+
range.collapse true
|
159
|
+
@selectRange range
|
160
|
+
else
|
161
|
+
range.deleteContents()
|
162
|
+
|
163
|
+
range
|
139
164
|
|
140
165
|
breakBlockEl: (el, range = @getRange()) ->
|
141
166
|
$el = $(el)
|
@@ -144,10 +169,9 @@ class Selection extends Plugin
|
|
144
169
|
return $el if range.collapsed
|
145
170
|
$el.before range.extractContents()
|
146
171
|
|
147
|
-
save: () ->
|
172
|
+
save: (range = @getRange()) ->
|
148
173
|
return if @_selectionSaved
|
149
174
|
|
150
|
-
range = @getRange()
|
151
175
|
startCaret = $('<span/>').addClass('simditor-caret-start')
|
152
176
|
endCaret = $('<span/>').addClass('simditor-caret-end')
|
153
177
|
|
@@ -171,7 +195,7 @@ class Selection extends Plugin
|
|
171
195
|
endOffset = endContainer.contents().index(endCaret)
|
172
196
|
|
173
197
|
if startContainer[0] == endContainer[0]
|
174
|
-
endOffset -= 1
|
198
|
+
endOffset -= 1
|
175
199
|
|
176
200
|
range = document.createRange()
|
177
201
|
range.setStart(startContainer.get(0), startOffset)
|
@@ -180,9 +204,6 @@ class Selection extends Plugin
|
|
180
204
|
startCaret.remove()
|
181
205
|
endCaret.remove()
|
182
206
|
@selectRange range
|
183
|
-
|
184
|
-
# firefox won't auto focus while applying new range
|
185
|
-
@editor.body.focus() if @editor.util.browser.firefox
|
186
207
|
else
|
187
208
|
startCaret.remove()
|
188
209
|
endCaret.remove()
|
@@ -201,22 +222,22 @@ class Formatter extends Plugin
|
|
201
222
|
super args...
|
202
223
|
@editor = @widget
|
203
224
|
|
225
|
+
@_allowedTags = ['br', 'a', 'img', 'b', 'strong', 'i', 'u', 'font', 'p', 'ul', 'ol', 'li', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'hr']
|
226
|
+
@_allowedAttributes =
|
227
|
+
img: ['src', 'alt', 'width', 'height', 'data-image-src', 'data-image-size', 'data-image-name', 'data-non-image']
|
228
|
+
a: ['href', 'target']
|
229
|
+
font: ['color']
|
230
|
+
pre: ['data-lang', 'class']
|
231
|
+
p: ['data-indent']
|
232
|
+
h1: ['data-indent']
|
233
|
+
h2: ['data-indent']
|
234
|
+
h3: ['data-indent']
|
235
|
+
h4: ['data-indent']
|
236
|
+
|
204
237
|
_init: ->
|
205
238
|
@editor.body.on 'click', 'a', (e) =>
|
206
239
|
false
|
207
240
|
|
208
|
-
_allowedTags: ['br', 'a', 'img', 'b', 'strong', 'i', 'u', 'p', 'ul', 'ol', 'li', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4']
|
209
|
-
|
210
|
-
_allowedAttributes:
|
211
|
-
img: ['src', 'alt', 'width', 'height', 'data-origin-src', 'data-origin-size', 'data-origin-name']
|
212
|
-
a: ['href', 'target']
|
213
|
-
pre: ['data-lang']
|
214
|
-
p: ['data-indent']
|
215
|
-
h1: ['data-indent']
|
216
|
-
h2: ['data-indent']
|
217
|
-
h3: ['data-indent']
|
218
|
-
h4: ['data-indent']
|
219
|
-
|
220
241
|
decorate: ($el = @editor.body) ->
|
221
242
|
@editor.trigger 'decorate', [$el]
|
222
243
|
|
@@ -230,7 +251,7 @@ class Formatter extends Plugin
|
|
230
251
|
findLinkNode = ($parentNode) ->
|
231
252
|
$parentNode.contents().each (i, node) ->
|
232
253
|
$node = $(node)
|
233
|
-
if $node.is('a') or $node.closest('a', $el).length
|
254
|
+
if $node.is('a') or $node.closest('a, pre', $el).length
|
234
255
|
return
|
235
256
|
|
236
257
|
if $node.contents().length
|
@@ -240,18 +261,18 @@ class Formatter extends Plugin
|
|
240
261
|
|
241
262
|
findLinkNode $el
|
242
263
|
|
243
|
-
re = /(https?:\/\/|www\.)[\w
|
264
|
+
re = /(https?:\/\/|www\.)[\w\-\.\?&=\/#%:,\!\+]+/ig
|
244
265
|
for $node in linkNodes
|
245
|
-
text = $node.text()
|
246
|
-
replaceEls = []
|
247
|
-
match = null
|
248
|
-
lastIndex = 0
|
266
|
+
text = $node.text()
|
267
|
+
replaceEls = []
|
268
|
+
match = null
|
269
|
+
lastIndex = 0
|
249
270
|
|
250
271
|
while (match = re.exec(text)) != null
|
251
272
|
replaceEls.push document.createTextNode(text.substring(lastIndex, match.index))
|
252
273
|
lastIndex = re.lastIndex
|
253
274
|
uri = if /^(http(s)?:\/\/|\/)/.test(match[0]) then match[0] else 'http://' + match[0]
|
254
|
-
replaceEls.push $('<a href="' + uri + '" rel="nofollow">'
|
275
|
+
replaceEls.push $('<a href="' + uri + '" rel="nofollow"></a>').text(match[0])[0]
|
255
276
|
|
256
277
|
replaceEls.push document.createTextNode(text.substring(lastIndex))
|
257
278
|
$node.replaceWith $(replaceEls)
|
@@ -271,7 +292,7 @@ class Formatter extends Plugin
|
|
271
292
|
if $node.is('br')
|
272
293
|
blockNode = null if blockNode?
|
273
294
|
$node.remove()
|
274
|
-
else if @editor.util.isBlockNode(node)
|
295
|
+
else if @editor.util.isBlockNode(node)
|
275
296
|
if $node.is('li')
|
276
297
|
if blockNode and blockNode.is('ul, ol')
|
277
298
|
blockNode.append node
|
@@ -291,8 +312,11 @@ class Formatter extends Plugin
|
|
291
312
|
|
292
313
|
if $node[0].nodeType == 3
|
293
314
|
text = $node.text().replace(/(\r\n|\n|\r)/gm, '')
|
294
|
-
|
295
|
-
|
315
|
+
if text
|
316
|
+
textNode = document.createTextNode text
|
317
|
+
$node.replaceWith textNode
|
318
|
+
else
|
319
|
+
$node.remove()
|
296
320
|
return
|
297
321
|
|
298
322
|
contents = $node.contents()
|
@@ -300,8 +324,14 @@ class Formatter extends Plugin
|
|
300
324
|
|
301
325
|
if $node.is(@_allowedTags.join(',')) or isDecoration
|
302
326
|
# img inside a is not allowed
|
303
|
-
if $node.is('a') and $node.find('img').length > 0
|
304
|
-
|
327
|
+
if $node.is('a') and ($childImg = $node.find('img')).length > 0
|
328
|
+
$node.replaceWith $childImg
|
329
|
+
$node = $childImg
|
330
|
+
contents = null
|
331
|
+
|
332
|
+
# exclude uploading img
|
333
|
+
if $node.is('img') and $node.hasClass('uploading')
|
334
|
+
$node.remove()
|
305
335
|
|
306
336
|
# Clean attributes except `src` `alt` on `img` tag and `href` `target` on `a` tag
|
307
337
|
unless isDecoration
|
@@ -335,16 +365,17 @@ class Formatter extends Plugin
|
|
335
365
|
|
336
366
|
clearHtml: (html, lineBreak = true) ->
|
337
367
|
container = $('<div/>').append(html)
|
368
|
+
contents = container.contents()
|
338
369
|
result = ''
|
339
370
|
|
340
|
-
|
371
|
+
contents.each (i, node) =>
|
341
372
|
if node.nodeType == 3
|
342
373
|
result += node.nodeValue
|
343
374
|
else if node.nodeType == 1
|
344
375
|
$node = $(node)
|
345
|
-
|
346
|
-
result += @clearHtml
|
347
|
-
if lineBreak and $node.is 'br, p, div, li, tr, pre, address, artticle, aside, dl, figcaption, footer, h1, h2, h3, h4, header'
|
376
|
+
children = $node.contents()
|
377
|
+
result += @clearHtml children if children.length > 0
|
378
|
+
if lineBreak and i < contents.length - 1 and $node.is 'br, p, div, li, tr, pre, address, artticle, aside, dl, figcaption, footer, h1, h2, h3, h4, header'
|
348
379
|
result += '\n'
|
349
380
|
|
350
381
|
result
|
@@ -356,9 +387,9 @@ class Formatter extends Plugin
|
|
356
387
|
|
357
388
|
$contents.each (i, el) =>
|
358
389
|
$el = $(el)
|
359
|
-
$el.remove() if $el.is(':not(img, br):empty')
|
390
|
+
$el.remove() if $el.is(':not(img, br, col, td, hr, [class^="simditor-"]):empty')
|
360
391
|
$el.remove() if uselessP($el) #and uselessP($el.prev())
|
361
|
-
$el.find(':not(img, br):empty').remove()
|
392
|
+
$el.find(':not(img, br, col, td, hr, [class^="simditor-"]):empty').remove()
|
362
393
|
|
363
394
|
|
364
395
|
|
@@ -376,11 +407,17 @@ class InputManager extends Plugin
|
|
376
407
|
@editor = @widget
|
377
408
|
@opts.pasteImage = 'inline' if @opts.pasteImage and typeof @opts.pasteImage != 'string'
|
378
409
|
|
410
|
+
# handlers which will be called when specific key is pressed in specific node
|
411
|
+
@_keystrokeHandlers = {}
|
412
|
+
|
413
|
+
@_shortcuts = {}
|
414
|
+
|
379
415
|
_modifierKeys: [16, 17, 18, 91, 93, 224]
|
380
416
|
|
381
417
|
_arrowKeys: [37..40]
|
382
418
|
|
383
419
|
_init: ->
|
420
|
+
|
384
421
|
@_pasteArea = $('<div/>')
|
385
422
|
.css({
|
386
423
|
width: '1px',
|
@@ -397,13 +434,45 @@ class InputManager extends Plugin
|
|
397
434
|
.addClass('simditor-paste-area')
|
398
435
|
.appendTo(@editor.el)
|
399
436
|
|
437
|
+
@_cleanPasteArea = $('<textarea/>')
|
438
|
+
.css({
|
439
|
+
width: '1px',
|
440
|
+
height: '1px',
|
441
|
+
overflow: 'hidden',
|
442
|
+
position: 'fixed',
|
443
|
+
right: '0',
|
444
|
+
bottom: '101px'
|
445
|
+
})
|
446
|
+
.attr({
|
447
|
+
tabIndex: '-1'
|
448
|
+
})
|
449
|
+
.addClass('simditor-clean-paste-area')
|
450
|
+
.appendTo(@editor.el)
|
451
|
+
|
400
452
|
@editor.on 'valuechanged', =>
|
401
|
-
# make sure each code block
|
402
|
-
@editor.body.find('hr, pre, .simditor-
|
453
|
+
# make sure each code block and table has siblings
|
454
|
+
@editor.body.find('hr, pre, .simditor-table').each (i, el) =>
|
403
455
|
$el = $(el)
|
404
|
-
if ($el.parent().is('blockquote') or $el.parent()[0] == @editor.body[0])
|
405
|
-
|
406
|
-
|
456
|
+
if ($el.parent().is('blockquote') or $el.parent()[0] == @editor.body[0])
|
457
|
+
formatted = false
|
458
|
+
|
459
|
+
if $el.next().length == 0
|
460
|
+
$('<p/>').append(@editor.util.phBr)
|
461
|
+
.insertAfter($el)
|
462
|
+
formatted = true
|
463
|
+
|
464
|
+
if $el.prev().length == 0
|
465
|
+
$('<p/>').append(@editor.util.phBr)
|
466
|
+
.insertBefore($el)
|
467
|
+
formatted = true
|
468
|
+
|
469
|
+
if formatted
|
470
|
+
setTimeout =>
|
471
|
+
@editor.trigger 'valuechanged'
|
472
|
+
, 10
|
473
|
+
|
474
|
+
@editor.body.find('pre:empty').append(@editor.util.phBr)
|
475
|
+
|
407
476
|
|
408
477
|
@editor.body.on('keydown', $.proxy(@_onKeyDown, @))
|
409
478
|
.on('keypress', $.proxy(@_onKeyPress, @))
|
@@ -412,28 +481,40 @@ class InputManager extends Plugin
|
|
412
481
|
.on('focus', $.proxy(@_onFocus, @))
|
413
482
|
.on('blur', $.proxy(@_onBlur, @))
|
414
483
|
.on('paste', $.proxy(@_onPaste, @))
|
484
|
+
.on('drop', $.proxy(@_onDrop, @))
|
415
485
|
|
416
486
|
# fix firefox cmd+left/right bug
|
417
487
|
if @editor.util.browser.firefox
|
418
488
|
@addShortcut 'cmd+37', (e) =>
|
419
489
|
e.preventDefault()
|
420
490
|
@editor.selection.sel.modify('move', 'backward', 'lineboundary')
|
491
|
+
false
|
421
492
|
@addShortcut 'cmd+39', (e) =>
|
422
493
|
e.preventDefault()
|
423
494
|
@editor.selection.sel.modify('move', 'forward', 'lineboundary')
|
495
|
+
false
|
496
|
+
|
497
|
+
# meta + enter: submit form
|
498
|
+
submitKey = if @editor.util.os.mac then 'cmd+13' else 'ctrl+13'
|
499
|
+
@addShortcut submitKey, (e) =>
|
500
|
+
@editor.el.closest('form')
|
501
|
+
.find('button:submit')
|
502
|
+
.click()
|
503
|
+
false
|
424
504
|
|
425
505
|
if @editor.textarea.attr 'autofocus'
|
426
506
|
setTimeout =>
|
427
507
|
@editor.focus()
|
428
508
|
, 0
|
429
509
|
|
510
|
+
|
430
511
|
_onFocus: (e) ->
|
431
512
|
@editor.el.addClass('focus')
|
432
513
|
.removeClass('error')
|
433
514
|
@focused = true
|
434
515
|
@lastCaretPosition = null
|
435
516
|
|
436
|
-
|
517
|
+
#@editor.body.find('.selected').removeClass('selected')
|
437
518
|
|
438
519
|
setTimeout =>
|
439
520
|
@editor.triggerHandler 'focus'
|
@@ -449,9 +530,10 @@ class InputManager extends Plugin
|
|
449
530
|
@editor.triggerHandler 'blur'
|
450
531
|
|
451
532
|
_onMouseUp: (e) ->
|
452
|
-
|
453
|
-
|
454
|
-
|
533
|
+
setTimeout =>
|
534
|
+
@editor.trigger 'selectionchanged'
|
535
|
+
@editor.undoManager.update()
|
536
|
+
, 0
|
455
537
|
|
456
538
|
_onKeyDown: (e) ->
|
457
539
|
if @editor.triggerHandler(e) == false
|
@@ -460,8 +542,7 @@ class InputManager extends Plugin
|
|
460
542
|
# handle predefined shortcuts
|
461
543
|
shortcutKey = @editor.util.getShortcutKey e
|
462
544
|
if @_shortcuts[shortcutKey]
|
463
|
-
@_shortcuts[shortcutKey].call(this, e)
|
464
|
-
return false
|
545
|
+
return @_shortcuts[shortcutKey].call(this, e)
|
465
546
|
|
466
547
|
# Check the condictional handlers
|
467
548
|
if e.which of @_keystrokeHandlers
|
@@ -475,7 +556,12 @@ class InputManager extends Plugin
|
|
475
556
|
return unless node.nodeType == 1
|
476
557
|
handler = @_keystrokeHandlers[e.which]?[node.tagName.toLowerCase()]
|
477
558
|
result = handler?(e, $(node))
|
478
|
-
|
559
|
+
|
560
|
+
# different result means:
|
561
|
+
# 1. true, has do everythings, stop browser default action and traverseUp
|
562
|
+
# 2. false, stop traverseUp
|
563
|
+
# 3. undefined, continue traverseUp
|
564
|
+
false if result == true or result == false
|
479
565
|
if result
|
480
566
|
@editor.trigger 'valuechanged'
|
481
567
|
@editor.trigger 'selectionchanged'
|
@@ -490,7 +576,18 @@ class InputManager extends Plugin
|
|
490
576
|
# paste shortcut
|
491
577
|
return if metaKey and e.which == 86
|
492
578
|
|
493
|
-
if @
|
579
|
+
if @editor.util.browser.webkit and e.which == 8 and @editor.selection.rangeAtStartOf $blockEl
|
580
|
+
# fix the span bug in webkit browsers
|
581
|
+
setTimeout =>
|
582
|
+
$newBlockEl = @editor.util.closestBlockEl()
|
583
|
+
@editor.selection.save()
|
584
|
+
@editor.formatter.cleanNode $newBlockEl, true
|
585
|
+
@editor.selection.restore()
|
586
|
+
@editor.trigger 'valuechanged'
|
587
|
+
@editor.trigger 'selectionchanged'
|
588
|
+
, 10
|
589
|
+
@typing = true
|
590
|
+
else if @_typing
|
494
591
|
clearTimeout @_typing if @_typing != true
|
495
592
|
@_typing = setTimeout =>
|
496
593
|
@editor.trigger 'valuechanged'
|
@@ -510,22 +607,6 @@ class InputManager extends Plugin
|
|
510
607
|
if @editor.triggerHandler(e) == false
|
511
608
|
return false
|
512
609
|
|
513
|
-
# input hooks are limited in a single line
|
514
|
-
@_hookStack.length = 0 if e.which == 13
|
515
|
-
|
516
|
-
# check the input hooks
|
517
|
-
if e.which == 32
|
518
|
-
cmd = @_hookStack.join ''
|
519
|
-
@_hookStack.length = 0
|
520
|
-
|
521
|
-
for hook in @_inputHooks
|
522
|
-
if (hook.cmd instanceof RegExp and hook.cmd.test(cmd)) or hook.cmd == cmd
|
523
|
-
hook.callback(e, hook, cmd)
|
524
|
-
break
|
525
|
-
else if @_hookKeyMap[e.which]
|
526
|
-
@_hookStack.push @_hookKeyMap[e.which]
|
527
|
-
@_hookStack.shift() if @_hookStack.length > 10
|
528
|
-
|
529
610
|
_onKeyUp: (e) ->
|
530
611
|
if @editor.triggerHandler(e) == false
|
531
612
|
return false
|
@@ -535,7 +616,7 @@ class InputManager extends Plugin
|
|
535
616
|
@editor.undoManager.update()
|
536
617
|
return
|
537
618
|
|
538
|
-
if e.which == 8 and
|
619
|
+
if e.which == 8 and @editor.util.isEmptyNode(@editor.body)
|
539
620
|
@editor.body.empty()
|
540
621
|
p = $('<p/>').append(@editor.util.phBr)
|
541
622
|
.appendTo(@editor.body)
|
@@ -546,35 +627,54 @@ class InputManager extends Plugin
|
|
546
627
|
if @editor.triggerHandler(e) == false
|
547
628
|
return false
|
548
629
|
|
630
|
+
range = @editor.selection.deleteRangeContents()
|
631
|
+
range.collapse(true) unless range.collapsed
|
632
|
+
$blockEl = @editor.util.closestBlockEl()
|
633
|
+
cleanPaste = $blockEl.is 'pre, table'
|
634
|
+
|
549
635
|
if e.originalEvent.clipboardData && e.originalEvent.clipboardData.items && e.originalEvent.clipboardData.items.length > 0
|
550
636
|
pasteItem = e.originalEvent.clipboardData.items[0]
|
551
637
|
|
552
638
|
# paste file in chrome
|
553
|
-
if /^image\//.test
|
639
|
+
if /^image\//.test(pasteItem.type) and !cleanPaste
|
554
640
|
imageFile = pasteItem.getAsFile()
|
555
641
|
return unless imageFile? and @opts.pasteImage
|
556
642
|
|
557
643
|
unless imageFile.name
|
558
|
-
imageFile.name = "
|
644
|
+
imageFile.name = "Clipboard Image.png"
|
559
645
|
|
560
646
|
uploadOpt = {}
|
561
647
|
uploadOpt[@opts.pasteImage] = true
|
562
648
|
@editor.uploader?.upload(imageFile, uploadOpt)
|
563
649
|
return false
|
564
650
|
|
651
|
+
@editor.selection.save range
|
565
652
|
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
653
|
+
if cleanPaste
|
654
|
+
@_cleanPasteArea.focus()
|
655
|
+
|
656
|
+
# firefox cannot set focus on textarea before pasting
|
657
|
+
if @editor.util.browser.firefox
|
658
|
+
e.preventDefault()
|
659
|
+
@_cleanPasteArea.val e.originalEvent.clipboardData.getData('text/plain')
|
570
660
|
|
571
|
-
|
661
|
+
# IE10 cannot set focus on textarea or editable div before pasting
|
662
|
+
else if @editor.util.browser.msie and @editor.util.browser.version == 10
|
663
|
+
e.preventDefault()
|
664
|
+
@_cleanPasteArea.val window.clipboardData.getData('Text')
|
665
|
+
else
|
666
|
+
@_pasteArea.focus()
|
667
|
+
|
668
|
+
# IE10 cannot set focus on textarea or editable div before pasting
|
669
|
+
if @editor.util.browser.msie and @editor.util.browser.version == 10
|
670
|
+
e.preventDefault()
|
671
|
+
@_pasteArea.html window.clipboardData.getData('Text')
|
572
672
|
|
573
673
|
setTimeout =>
|
574
|
-
if @_pasteArea.is(':empty')
|
674
|
+
if @_pasteArea.is(':empty') and !@_cleanPasteArea.val()
|
575
675
|
pasteContent = null
|
576
|
-
else if
|
577
|
-
pasteContent = @
|
676
|
+
else if cleanPaste
|
677
|
+
pasteContent = @_cleanPasteArea.val()
|
578
678
|
else
|
579
679
|
pasteContent = $('<div/>').append(@_pasteArea.contents())
|
580
680
|
@editor.formatter.format pasteContent
|
@@ -583,6 +683,7 @@ class InputManager extends Plugin
|
|
583
683
|
pasteContent = pasteContent.contents()
|
584
684
|
|
585
685
|
@_pasteArea.empty()
|
686
|
+
@_cleanPasteArea.val('')
|
586
687
|
range = @editor.selection.restore()
|
587
688
|
|
588
689
|
if @editor.triggerHandler('pasting', [pasteContent]) == false
|
@@ -590,34 +691,45 @@ class InputManager extends Plugin
|
|
590
691
|
|
591
692
|
if !pasteContent
|
592
693
|
return
|
593
|
-
else if
|
594
|
-
|
595
|
-
|
694
|
+
else if cleanPaste
|
695
|
+
if $blockEl.is('table')
|
696
|
+
lines = pasteContent.split('\n')
|
697
|
+
lastLine = lines.pop()
|
698
|
+
for line in lines
|
699
|
+
@editor.selection.insertNode document.createTextNode(line)
|
700
|
+
@editor.selection.insertNode $('<br/>')
|
701
|
+
@editor.selection.insertNode document.createTextNode(lastLine)
|
702
|
+
else
|
703
|
+
pasteContent = $('<div/>').text(pasteContent)
|
704
|
+
@editor.selection.insertNode($(node)[0], range) for node in pasteContent.contents()
|
705
|
+
else if $blockEl.is @editor.body
|
706
|
+
@editor.selection.insertNode(node, range) for node in pasteContent
|
596
707
|
else if pasteContent.length < 1
|
597
708
|
return
|
598
709
|
else if pasteContent.length == 1
|
599
710
|
if pasteContent.is('p')
|
600
711
|
children = pasteContent.contents()
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
else
|
620
|
-
|
712
|
+
|
713
|
+
if children.length == 1 and children.is('img')
|
714
|
+
$img = children
|
715
|
+
|
716
|
+
# paste image in firefox and IE 11
|
717
|
+
if /^data:image/.test($img.attr('src'))
|
718
|
+
return unless @opts.pasteImage
|
719
|
+
blob = @editor.util.dataURLtoBlob $img.attr( "src" )
|
720
|
+
blob.name = "Clipboard Image.png"
|
721
|
+
|
722
|
+
uploadOpt = {}
|
723
|
+
uploadOpt[@opts.pasteImage] = true
|
724
|
+
@editor.uploader?.upload(blob, uploadOpt)
|
725
|
+
return
|
726
|
+
|
727
|
+
# cannot paste image in safari
|
728
|
+
else if $img.is('img[src^="webkit-fake-url://"]')
|
729
|
+
return
|
730
|
+
else
|
731
|
+
@editor.selection.insertNode(node, range) for node in children
|
732
|
+
|
621
733
|
else if $blockEl.is('p') and @editor.util.isEmptyNode $blockEl
|
622
734
|
$blockEl.replaceWith pasteContent
|
623
735
|
@editor.selection.setRangeAtEndOf(pasteContent, range)
|
@@ -645,44 +757,25 @@ class InputManager extends Plugin
|
|
645
757
|
@editor.trigger 'selectionchanged'
|
646
758
|
, 10
|
647
759
|
|
760
|
+
_onDrop: (e) ->
|
761
|
+
if @editor.triggerHandler(e) == false
|
762
|
+
return false
|
763
|
+
|
764
|
+
setTimeout =>
|
765
|
+
@editor.trigger 'valuechanged'
|
766
|
+
@editor.trigger 'selectionchanged'
|
767
|
+
, 0
|
648
768
|
|
649
|
-
# handlers which will be called when specific key is pressed in specific node
|
650
|
-
_keystrokeHandlers: {}
|
651
769
|
|
652
770
|
addKeystrokeHandler: (key, node, handler) ->
|
653
771
|
@_keystrokeHandlers[key] = {} unless @_keystrokeHandlers[key]
|
654
772
|
@_keystrokeHandlers[key][node] = handler
|
655
773
|
|
656
774
|
|
657
|
-
# a hook will be triggered when specific string typed
|
658
|
-
_inputHooks: []
|
659
|
-
|
660
|
-
_hookKeyMap: {}
|
661
|
-
|
662
|
-
_hookStack: []
|
663
|
-
|
664
|
-
addInputHook: (hookOpt) ->
|
665
|
-
$.extend(@_hookKeyMap, hookOpt.key)
|
666
|
-
@_inputHooks.push hookOpt
|
667
|
-
|
668
|
-
|
669
|
-
_shortcuts:
|
670
|
-
# meta + enter: submit form
|
671
|
-
'cmd+13': (e) ->
|
672
|
-
@editor.el.closest('form')
|
673
|
-
.find('button:submit')
|
674
|
-
.click()
|
675
|
-
|
676
775
|
addShortcut: (keys, handler) ->
|
677
776
|
@_shortcuts[keys] = $.proxy(handler, this)
|
678
777
|
|
679
778
|
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
779
|
# Standardize keystroke actions across browsers
|
687
780
|
|
688
781
|
class Keystroke extends Plugin
|
@@ -711,11 +804,28 @@ class Keystroke extends Plugin
|
|
711
804
|
true
|
712
805
|
|
713
806
|
|
714
|
-
#
|
807
|
+
# press enter at end of title block in webkit and IE
|
808
|
+
if @editor.util.browser.webkit or @editor.util.browser.msie
|
809
|
+
titleEnterHandler = (e, $node) =>
|
810
|
+
return unless @editor.selection.rangeAtEndOf $node
|
811
|
+
$p = $('<p/>').append(@editor.util.phBr)
|
812
|
+
.insertAfter($node)
|
813
|
+
@editor.selection.setRangeAtStartOf $p
|
814
|
+
true
|
815
|
+
|
816
|
+
@editor.inputManager.addKeystrokeHandler '13', 'h1', titleEnterHandler
|
817
|
+
@editor.inputManager.addKeystrokeHandler '13', 'h2', titleEnterHandler
|
818
|
+
@editor.inputManager.addKeystrokeHandler '13', 'h3', titleEnterHandler
|
819
|
+
@editor.inputManager.addKeystrokeHandler '13', 'h4', titleEnterHandler
|
820
|
+
@editor.inputManager.addKeystrokeHandler '13', 'h5', titleEnterHandler
|
821
|
+
@editor.inputManager.addKeystrokeHandler '13', 'h6', titleEnterHandler
|
822
|
+
|
823
|
+
|
824
|
+
# Remove hr
|
715
825
|
@editor.inputManager.addKeystrokeHandler '8', '*', (e) =>
|
716
826
|
$rootBlock = @editor.util.furthestBlockEl()
|
717
827
|
$prevBlockEl = $rootBlock.prev()
|
718
|
-
if $prevBlockEl.is('hr
|
828
|
+
if $prevBlockEl.is('hr') and @editor.selection.rangeAtStartOf $rootBlock
|
719
829
|
# TODO: need to test on IE
|
720
830
|
@editor.selection.save()
|
721
831
|
$prevBlockEl.remove()
|
@@ -814,21 +924,28 @@ class Keystroke extends Plugin
|
|
814
924
|
@editor.inputManager.addKeystrokeHandler '8', 'li', (e, $node) =>
|
815
925
|
$childList = $node.children('ul, ol')
|
816
926
|
$prevNode = $node.prev('li')
|
817
|
-
return unless $childList.length > 0 and $prevNode.length > 0
|
927
|
+
return false unless $childList.length > 0 and $prevNode.length > 0
|
818
928
|
|
819
929
|
text = ''
|
820
930
|
$textNode = null
|
821
931
|
$node.contents().each (i, n) =>
|
822
|
-
if n.nodeType
|
932
|
+
return false if n.nodeType is 1 and /UL|OL/.test(n.nodeName)
|
933
|
+
return if n.nodeType is 1 and /BR/.test(n.nodeName)
|
934
|
+
|
935
|
+
if n.nodeType is 3 and n.nodeValue
|
823
936
|
text += n.nodeValue
|
824
|
-
|
937
|
+
else if n.nodeType is 1
|
938
|
+
text += $(n).text()
|
939
|
+
|
940
|
+
$textNode= $(n)
|
941
|
+
|
825
942
|
if $textNode and text.length == 1 and @editor.util.browser.firefox and !$textNode.next('br').length
|
826
943
|
$br = $(@editor.util.phBr).insertAfter $textNode
|
827
944
|
$textNode.remove()
|
828
945
|
@editor.selection.setRangeBefore $br
|
829
946
|
return true
|
830
947
|
else if text.length > 0
|
831
|
-
return
|
948
|
+
return false
|
832
949
|
|
833
950
|
range = document.createRange()
|
834
951
|
$prevChildList = $prevNode.children('ul, ol')
|
@@ -868,8 +985,6 @@ class UndoManager extends Plugin
|
|
868
985
|
|
869
986
|
@className: 'UndoManager'
|
870
987
|
|
871
|
-
_stack: []
|
872
|
-
|
873
988
|
_index: -1
|
874
989
|
|
875
990
|
_capacity: 50
|
@@ -879,22 +994,29 @@ class UndoManager extends Plugin
|
|
879
994
|
constructor: (args...) ->
|
880
995
|
super args...
|
881
996
|
@editor = @widget
|
997
|
+
@_stack = []
|
882
998
|
|
883
999
|
_init: ->
|
884
1000
|
if @editor.util.os.mac
|
885
1001
|
undoShortcut = 'cmd+90'
|
886
|
-
redoShortcut = 'shift+cmd+
|
887
|
-
else
|
1002
|
+
redoShortcut = 'shift+cmd+90'
|
1003
|
+
else if @editor.util.os.win
|
888
1004
|
undoShortcut = 'ctrl+90'
|
889
1005
|
redoShortcut = 'ctrl+89'
|
1006
|
+
else
|
1007
|
+
undoShortcut = 'ctrl+90'
|
1008
|
+
redoShortcut = 'shift+ctrl+90'
|
1009
|
+
|
890
1010
|
|
891
1011
|
@editor.inputManager.addShortcut undoShortcut, (e) =>
|
892
1012
|
e.preventDefault()
|
893
1013
|
@undo()
|
1014
|
+
false
|
894
1015
|
|
895
1016
|
@editor.inputManager.addShortcut redoShortcut, (e) =>
|
896
1017
|
e.preventDefault()
|
897
1018
|
@redo()
|
1019
|
+
false
|
898
1020
|
|
899
1021
|
@editor.on 'valuechanged', (e, src) =>
|
900
1022
|
return if src == 'undo'
|
@@ -908,6 +1030,8 @@ class UndoManager extends Plugin
|
|
908
1030
|
, 200
|
909
1031
|
|
910
1032
|
_pushUndoState: ->
|
1033
|
+
return if @editor.triggerHandler('pushundostate') == false
|
1034
|
+
|
911
1035
|
currentState = @currentState()
|
912
1036
|
html = @editor.body.html()
|
913
1037
|
return if currentState and currentState.html == html
|
@@ -939,6 +1063,7 @@ class UndoManager extends Plugin
|
|
939
1063
|
state = @_stack[@_index]
|
940
1064
|
@editor.body.html state.html
|
941
1065
|
@caretPosition state.caret
|
1066
|
+
@editor.body.find('.selected').removeClass('selected')
|
942
1067
|
@editor.sync()
|
943
1068
|
|
944
1069
|
@editor.trigger 'valuechanged', ['undo']
|
@@ -954,6 +1079,7 @@ class UndoManager extends Plugin
|
|
954
1079
|
state = @_stack[@_index]
|
955
1080
|
@editor.body.html state.html
|
956
1081
|
@caretPosition state.caret
|
1082
|
+
@editor.body.find('.selected').removeClass('selected')
|
957
1083
|
@editor.sync()
|
958
1084
|
|
959
1085
|
@editor.trigger 'valuechanged', ['undo']
|
@@ -1093,16 +1219,20 @@ class Util extends Plugin
|
|
1093
1219
|
phBr: '<br/>'
|
1094
1220
|
|
1095
1221
|
os: (->
|
1222
|
+
os = {}
|
1096
1223
|
if /Mac/.test navigator.appVersion
|
1097
|
-
mac
|
1224
|
+
os.mac = true
|
1098
1225
|
else if /Linux/.test navigator.appVersion
|
1099
|
-
linux
|
1226
|
+
os.linux = true
|
1100
1227
|
else if /Win/.test navigator.appVersion
|
1101
|
-
win
|
1228
|
+
os.win = true
|
1102
1229
|
else if /X11/.test navigator.appVersion
|
1103
|
-
unix
|
1104
|
-
|
1105
|
-
|
1230
|
+
os.unix = true
|
1231
|
+
|
1232
|
+
if /Mobi/.test navigator.appVersion
|
1233
|
+
os.mobile = true
|
1234
|
+
|
1235
|
+
os
|
1106
1236
|
)()
|
1107
1237
|
|
1108
1238
|
browser: (->
|
@@ -1114,19 +1244,19 @@ class Util extends Plugin
|
|
1114
1244
|
|
1115
1245
|
if ie
|
1116
1246
|
msie: true
|
1117
|
-
version: ua.match(/(msie |rv:)(\d+(\.\d+)?)/i)[2]
|
1247
|
+
version: ua.match(/(msie |rv:)(\d+(\.\d+)?)/i)[2] * 1
|
1118
1248
|
else if chrome
|
1119
1249
|
webkit: true
|
1120
1250
|
chrome: true
|
1121
|
-
version: ua.match(/(?:chrome|crios)\/(\d+(\.\d+)?)/i)[1]
|
1251
|
+
version: ua.match(/(?:chrome|crios)\/(\d+(\.\d+)?)/i)[1] * 1
|
1122
1252
|
else if safari
|
1123
1253
|
webkit: true
|
1124
1254
|
safari: true
|
1125
|
-
version: ua.match(/version\/(\d+(\.\d+)?)/i)[1]
|
1255
|
+
version: ua.match(/version\/(\d+(\.\d+)?)/i)[1] * 1
|
1126
1256
|
else if firefox
|
1127
1257
|
mozilla: true
|
1128
1258
|
firefox: true
|
1129
|
-
version: ua.match(/firefox\/(\d+(\.\d+)?)/i)[1]
|
1259
|
+
version: ua.match(/firefox\/(\d+(\.\d+)?)/i)[1] * 1
|
1130
1260
|
else
|
1131
1261
|
{}
|
1132
1262
|
)()
|
@@ -1137,7 +1267,7 @@ class Util extends Plugin
|
|
1137
1267
|
|
1138
1268
|
isEmptyNode: (node) ->
|
1139
1269
|
$node = $(node)
|
1140
|
-
!$node.text() and !$node.find(':not(br, span)').length
|
1270
|
+
$node.is(':empty') or (!$node.text() and !$node.find(':not(br, span, div)').length)
|
1141
1271
|
|
1142
1272
|
isBlockNode: (node) ->
|
1143
1273
|
node = $(node)[0]
|
@@ -1315,6 +1445,49 @@ class Util extends Plugin
|
|
1315
1445
|
@editor.trigger 'selectionchanged'
|
1316
1446
|
true
|
1317
1447
|
|
1448
|
+
# convert base64 data url to blob object for pasting images in firefox and IE11
|
1449
|
+
dataURLtoBlob: (dataURL) ->
|
1450
|
+
hasBlobConstructor = window.Blob && (->
|
1451
|
+
try
|
1452
|
+
return Boolean(new Blob())
|
1453
|
+
catch e
|
1454
|
+
return false
|
1455
|
+
)()
|
1456
|
+
|
1457
|
+
hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && (->
|
1458
|
+
try
|
1459
|
+
return new Blob([new Uint8Array(100)]).size == 100
|
1460
|
+
catch e
|
1461
|
+
return false
|
1462
|
+
)()
|
1463
|
+
|
1464
|
+
BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
|
1465
|
+
window.MozBlobBuilder || window.MSBlobBuilder;
|
1466
|
+
|
1467
|
+
return false unless (hasBlobConstructor || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array
|
1468
|
+
|
1469
|
+
if dataURL.split(',')[0].indexOf('base64') >= 0
|
1470
|
+
# Convert base64 to raw binary data held in a string:
|
1471
|
+
byteString = atob(dataURL.split(',')[1])
|
1472
|
+
else
|
1473
|
+
# Convert base64/URLEncoded data component to raw binary data:
|
1474
|
+
byteString = decodeURIComponent(dataURL.split(',')[1])
|
1475
|
+
|
1476
|
+
# Write the bytes of the string to an ArrayBuffer:
|
1477
|
+
arrayBuffer = new ArrayBuffer(byteString.length)
|
1478
|
+
intArray = new Uint8Array(arrayBuffer)
|
1479
|
+
for i in [0..byteString.length]
|
1480
|
+
intArray[i] = byteString.charCodeAt(i)
|
1481
|
+
|
1482
|
+
# Separate out the mime component:
|
1483
|
+
mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]
|
1484
|
+
# Write the ArrayBuffer (or ArrayBufferView) to a blob:
|
1485
|
+
if hasBlobConstructor
|
1486
|
+
return new Blob([if hasArrayBufferViewSupport then intArray else arrayBuffer], {type: mimeString})
|
1487
|
+
bb = new BlobBuilder()
|
1488
|
+
bb.append(arrayBuffer)
|
1489
|
+
bb.getBlob(mimeString)
|
1490
|
+
|
1318
1491
|
|
1319
1492
|
class Toolbar extends Plugin
|
1320
1493
|
|
@@ -1336,7 +1509,7 @@ class Toolbar extends Plugin
|
|
1336
1509
|
return unless @opts.toolbar
|
1337
1510
|
|
1338
1511
|
unless $.isArray @opts.toolbar
|
1339
|
-
@opts.toolbar = ['bold', 'italic', 'underline', '|', 'ol', 'ul', 'blockquote', 'code', '|', 'link', 'image', '|', 'indent', 'outdent']
|
1512
|
+
@opts.toolbar = ['bold', 'italic', 'underline', 'strikethrough', '|', 'ol', 'ul', 'blockquote', 'code', '|', 'link', 'image', '|', 'indent', 'outdent']
|
1340
1513
|
|
1341
1514
|
@_render()
|
1342
1515
|
|
@@ -1351,7 +1524,9 @@ class Toolbar extends Plugin
|
|
1351
1524
|
|
1352
1525
|
if @opts.toolbarFloat
|
1353
1526
|
@wrapper.width @wrapper.outerWidth()
|
1354
|
-
|
1527
|
+
unless @editor.util.os.mobile
|
1528
|
+
@wrapper.css 'left', @wrapper.offset().left
|
1529
|
+
toolbarHeight = @wrapper.outerHeight()
|
1355
1530
|
$(window).on 'scroll.simditor-' + @editor.id, (e) =>
|
1356
1531
|
topEdge = @editor.wrapper.offset().top
|
1357
1532
|
bottomEdge = topEdge + @editor.wrapper.outerHeight() - 80
|
@@ -1359,8 +1534,16 @@ class Toolbar extends Plugin
|
|
1359
1534
|
|
1360
1535
|
if scrollTop <= topEdge or scrollTop >= bottomEdge
|
1361
1536
|
@editor.wrapper.removeClass('toolbar-floating')
|
1537
|
+
.css('padding-top', '')
|
1538
|
+
if @editor.util.os.mobile
|
1539
|
+
@wrapper.css
|
1540
|
+
top: 'auto'
|
1362
1541
|
else
|
1363
1542
|
@editor.wrapper.addClass('toolbar-floating')
|
1543
|
+
.css('padding-top', toolbarHeight)
|
1544
|
+
if @editor.util.os.mobile
|
1545
|
+
@wrapper.css
|
1546
|
+
top: scrollTop - topEdge
|
1364
1547
|
|
1365
1548
|
@editor.on 'selectionchanged focus', =>
|
1366
1549
|
@toolbarStatus()
|
@@ -1387,6 +1570,8 @@ class Toolbar extends Plugin
|
|
1387
1570
|
|
1388
1571
|
@buttons.push new @constructor.buttons[name](@editor)
|
1389
1572
|
|
1573
|
+
@editor.placeholderEl.css 'top', @wrapper.outerHeight()
|
1574
|
+
|
1390
1575
|
toolbarStatus: (name) ->
|
1391
1576
|
return unless @editor.inputManager.focused
|
1392
1577
|
|
@@ -1429,15 +1614,15 @@ class Simditor extends Widget
|
|
1429
1614
|
|
1430
1615
|
opts:
|
1431
1616
|
textarea: null
|
1432
|
-
placeholder:
|
1617
|
+
placeholder: ''
|
1433
1618
|
defaultImage: 'images/image.png'
|
1434
|
-
params:
|
1619
|
+
params: {}
|
1435
1620
|
upload: false
|
1436
1621
|
tabIndent: true
|
1437
1622
|
|
1438
1623
|
_init: ->
|
1439
|
-
@textarea = $(@opts.textarea)
|
1440
|
-
@opts.placeholder = @opts.placeholder
|
1624
|
+
@textarea = $(@opts.textarea)
|
1625
|
+
@opts.placeholder = @opts.placeholder || @textarea.attr('placeholder')
|
1441
1626
|
|
1442
1627
|
unless @textarea.length
|
1443
1628
|
throw new Error 'simditor: param textarea is required.'
|
@@ -1450,9 +1635,9 @@ class Simditor extends Widget
|
|
1450
1635
|
@id = ++ Simditor.count
|
1451
1636
|
@_render()
|
1452
1637
|
|
1453
|
-
if @opts.upload and
|
1638
|
+
if @opts.upload and simple?.uploader
|
1454
1639
|
uploadOpts = if typeof @opts.upload == 'object' then @opts.upload else {}
|
1455
|
-
@uploader =
|
1640
|
+
@uploader = simple.uploader(uploadOpts)
|
1456
1641
|
|
1457
1642
|
form = @textarea.closest 'form'
|
1458
1643
|
if form.length
|
@@ -1463,20 +1648,16 @@ class Simditor extends Widget
|
|
1463
1648
|
|
1464
1649
|
# set default value after all plugins are connected
|
1465
1650
|
@on 'pluginconnected', =>
|
1466
|
-
@setValue @textarea.val() || ''
|
1467
|
-
|
1468
1651
|
if @opts.placeholder
|
1469
1652
|
@on 'valuechanged', =>
|
1470
1653
|
@_placeholder()
|
1471
1654
|
|
1472
|
-
|
1473
|
-
@trigger 'valuechanged'
|
1474
|
-
, 0
|
1655
|
+
@setValue @textarea.val() || ''
|
1475
1656
|
|
1476
1657
|
# Disable the resizing of `img` and `table`
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1658
|
+
if @util.browser.mozilla
|
1659
|
+
document.execCommand "enableObjectResizing", false, false
|
1660
|
+
document.execCommand "enableInlineTableEditing", false, false
|
1480
1661
|
|
1481
1662
|
_tpl:"""
|
1482
1663
|
<div class="simditor">
|
@@ -1506,6 +1687,9 @@ class Simditor extends Widget
|
|
1506
1687
|
else if @util.os.linux
|
1507
1688
|
@el.addClass 'simditor-linux'
|
1508
1689
|
|
1690
|
+
if @util.os.mobile
|
1691
|
+
@el.addClass 'simditor-mobile'
|
1692
|
+
|
1509
1693
|
if @opts.params
|
1510
1694
|
for key, val of @opts.params
|
1511
1695
|
$('<input/>', {
|
@@ -1522,16 +1706,22 @@ class Simditor extends Widget
|
|
1522
1706
|
@placeholderEl.hide()
|
1523
1707
|
|
1524
1708
|
setValue: (val) ->
|
1709
|
+
@hidePopover()
|
1525
1710
|
@textarea.val val
|
1526
1711
|
@body.html val
|
1527
1712
|
|
1528
1713
|
@formatter.format()
|
1529
1714
|
@formatter.decorate()
|
1530
1715
|
|
1716
|
+
setTimeout =>
|
1717
|
+
@trigger 'valuechanged'
|
1718
|
+
, 0
|
1719
|
+
|
1531
1720
|
getValue: () ->
|
1532
1721
|
@sync()
|
1533
1722
|
|
1534
1723
|
sync: ->
|
1724
|
+
@hidePopover
|
1535
1725
|
cloneBody = @body.clone()
|
1536
1726
|
@formatter.undecorate cloneBody
|
1537
1727
|
@formatter.format cloneBody
|
@@ -1539,23 +1729,35 @@ class Simditor extends Widget
|
|
1539
1729
|
# generate `a` tag automatically
|
1540
1730
|
@formatter.autolink cloneBody
|
1541
1731
|
|
1542
|
-
# remove empty `p` tag at the end of content
|
1543
|
-
|
1544
|
-
|
1732
|
+
# remove empty `p` tag at the start/end of content
|
1733
|
+
children = cloneBody.children()
|
1734
|
+
lastP = children.last 'p'
|
1735
|
+
firstP = children.first 'p'
|
1736
|
+
while lastP.is('p') and @util.isEmptyNode(lastP)
|
1545
1737
|
emptyP = lastP
|
1546
1738
|
lastP = lastP.prev 'p'
|
1547
1739
|
emptyP.remove()
|
1740
|
+
while firstP.is('p') and @util.isEmptyNode(firstP)
|
1741
|
+
emptyP = firstP
|
1742
|
+
firstP = lastP.next 'p'
|
1743
|
+
emptyP.remove()
|
1744
|
+
|
1745
|
+
# remove images being uploaded
|
1746
|
+
cloneBody.find('img.uploading').remove()
|
1548
1747
|
|
1549
1748
|
val = $.trim(cloneBody.html())
|
1550
1749
|
@textarea.val val
|
1551
1750
|
val
|
1552
1751
|
|
1553
1752
|
focus: ->
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1753
|
+
if @inputManager.lastCaretPosition
|
1754
|
+
@undoManager.caretPosition @inputManager.lastCaretPosition
|
1755
|
+
else
|
1756
|
+
$blockEl = @body.find('p, li, pre, h1, h2, h3, h4, td').first()
|
1757
|
+
return unless $blockEl.length > 0
|
1758
|
+
range = document.createRange()
|
1759
|
+
@selection.setRangeAtStartOf $blockEl, range
|
1760
|
+
@body.focus()
|
1559
1761
|
|
1560
1762
|
blur: ->
|
1561
1763
|
@body.blur()
|
@@ -1587,11 +1789,6 @@ class Simditor extends Widget
|
|
1587
1789
|
window.Simditor = Simditor
|
1588
1790
|
|
1589
1791
|
|
1590
|
-
class TestPlugin extends Plugin
|
1591
|
-
|
1592
|
-
class Test extends Widget
|
1593
|
-
@connect TestPlugin
|
1594
|
-
|
1595
1792
|
|
1596
1793
|
class Button extends Module
|
1597
1794
|
|
@@ -1628,13 +1825,25 @@ class Button extends Module
|
|
1628
1825
|
|
1629
1826
|
@el.on 'mousedown', (e) =>
|
1630
1827
|
e.preventDefault()
|
1828
|
+
return false if @el.hasClass('disabled') or (@needFocus and !@editor.inputManager.focused)
|
1829
|
+
|
1631
1830
|
if @menu
|
1632
1831
|
@wrapper.toggleClass('menu-on')
|
1633
1832
|
.siblings('li')
|
1634
1833
|
.removeClass('menu-on')
|
1635
|
-
return false
|
1636
1834
|
|
1637
|
-
|
1835
|
+
if @wrapper.is('.menu-on')
|
1836
|
+
exceed = @menuWrapper.offset().left + @menuWrapper.outerWidth() + 5 -
|
1837
|
+
@editor.wrapper.offset().left - @editor.wrapper.outerWidth()
|
1838
|
+
|
1839
|
+
if exceed > 0
|
1840
|
+
@menuWrapper.css
|
1841
|
+
'left': 'auto'
|
1842
|
+
'right': 0
|
1843
|
+
|
1844
|
+
@trigger 'menuexpand'
|
1845
|
+
|
1846
|
+
return false
|
1638
1847
|
|
1639
1848
|
param = @el.data('param')
|
1640
1849
|
@command(param)
|
@@ -1662,6 +1871,7 @@ class Button extends Module
|
|
1662
1871
|
if @shortcut?
|
1663
1872
|
@editor.inputManager.addShortcut @shortcut, (e) =>
|
1664
1873
|
@el.mousedown()
|
1874
|
+
false
|
1665
1875
|
|
1666
1876
|
for tag in @htmlTag.split ','
|
1667
1877
|
tag = $.trim tag
|
@@ -1742,8 +1952,8 @@ class Popover extends Module
|
|
1742
1952
|
.data('popover', @)
|
1743
1953
|
@render()
|
1744
1954
|
|
1745
|
-
|
1746
|
-
|
1955
|
+
#@editor.on 'blur.popover', =>
|
1956
|
+
#@target.addClass('selected') if @active and @target?
|
1747
1957
|
|
1748
1958
|
@el.on 'mouseenter', (e) =>
|
1749
1959
|
@el.addClass 'hover'
|
@@ -1754,11 +1964,9 @@ class Popover extends Module
|
|
1754
1964
|
|
1755
1965
|
show: ($target, position = 'bottom') ->
|
1756
1966
|
return unless $target?
|
1757
|
-
@
|
1967
|
+
@editor.hidePopover()
|
1758
1968
|
|
1759
|
-
@
|
1760
|
-
popover = $(el).data('popover')
|
1761
|
-
popover.hide()
|
1969
|
+
@target = $target.addClass('selected')
|
1762
1970
|
|
1763
1971
|
if @active
|
1764
1972
|
@refresh(position)
|
@@ -1784,6 +1992,7 @@ class Popover extends Module
|
|
1784
1992
|
@trigger 'popoverhide'
|
1785
1993
|
|
1786
1994
|
refresh: (position = 'bottom') ->
|
1995
|
+
return unless @active
|
1787
1996
|
wrapperOffset = @editor.wrapper.offset()
|
1788
1997
|
targetOffset = @target.offset()
|
1789
1998
|
targetH = @target.outerHeight()
|
@@ -1807,11 +2016,14 @@ class Popover extends Module
|
|
1807
2016
|
@el.remove()
|
1808
2017
|
|
1809
2018
|
|
2019
|
+
window.SimditorPopover = Popover
|
2020
|
+
|
2021
|
+
|
1810
2022
|
class TitleButton extends Button
|
1811
2023
|
|
1812
2024
|
name: 'title'
|
1813
2025
|
|
1814
|
-
title: '
|
2026
|
+
title: '标题文字'
|
1815
2027
|
|
1816
2028
|
htmlTag: 'h1, h2, h3, h4'
|
1817
2029
|
|
@@ -1907,6 +2119,14 @@ class BoldButton extends Button
|
|
1907
2119
|
|
1908
2120
|
shortcut: 'cmd+66'
|
1909
2121
|
|
2122
|
+
render: ->
|
2123
|
+
if @editor.util.os.mac
|
2124
|
+
@title = @title + ' ( Cmd + b )'
|
2125
|
+
else
|
2126
|
+
@title = @title + ' ( Ctrl + b )'
|
2127
|
+
@shortcut = 'ctrl+66'
|
2128
|
+
super()
|
2129
|
+
|
1910
2130
|
status: ($node) ->
|
1911
2131
|
@setDisabled $node.is(@disableTag) if $node?
|
1912
2132
|
return true if @disabled
|
@@ -1938,6 +2158,15 @@ class ItalicButton extends Button
|
|
1938
2158
|
|
1939
2159
|
shortcut: 'cmd+73'
|
1940
2160
|
|
2161
|
+
render: ->
|
2162
|
+
if @editor.util.os.mac
|
2163
|
+
@title = @title + ' ( Cmd + i )'
|
2164
|
+
else
|
2165
|
+
@title = @title + ' ( Ctrl + i )'
|
2166
|
+
@shortcut = 'ctrl+73'
|
2167
|
+
|
2168
|
+
super()
|
2169
|
+
|
1941
2170
|
status: ($node) ->
|
1942
2171
|
@setDisabled $node.is(@disableTag) if $node?
|
1943
2172
|
return @disabled if @disabled
|
@@ -1970,6 +2199,14 @@ class UnderlineButton extends Button
|
|
1970
2199
|
|
1971
2200
|
shortcut: 'cmd+85'
|
1972
2201
|
|
2202
|
+
render: ->
|
2203
|
+
if @editor.util.os.mac
|
2204
|
+
@title = @title + ' ( Cmd + u )'
|
2205
|
+
else
|
2206
|
+
@title = @title + ' ( Ctrl + u )'
|
2207
|
+
@shortcut = 'ctrl+85'
|
2208
|
+
super()
|
2209
|
+
|
1973
2210
|
status: ($node) ->
|
1974
2211
|
@setDisabled $node.is(@disableTag) if $node?
|
1975
2212
|
return @disabled if @disabled
|
@@ -1989,6 +2226,80 @@ Simditor.Toolbar.addButton(UnderlineButton)
|
|
1989
2226
|
|
1990
2227
|
|
1991
2228
|
|
2229
|
+
class ColorButton extends Button
|
2230
|
+
|
2231
|
+
name: 'color'
|
2232
|
+
|
2233
|
+
icon: 'font'
|
2234
|
+
|
2235
|
+
title: '文字颜色'
|
2236
|
+
|
2237
|
+
disableTag: 'pre'
|
2238
|
+
|
2239
|
+
menu: true
|
2240
|
+
|
2241
|
+
render: (args...) ->
|
2242
|
+
super args...
|
2243
|
+
|
2244
|
+
renderMenu: ->
|
2245
|
+
$('''
|
2246
|
+
<ul class="color-list">
|
2247
|
+
<li><a href="javascript:;" class="font-color font-color-1" data-color=""></a></li>
|
2248
|
+
<li><a href="javascript:;" class="font-color font-color-2" data-color=""></a></li>
|
2249
|
+
<li><a href="javascript:;" class="font-color font-color-3" data-color=""></a></li>
|
2250
|
+
<li><a href="javascript:;" class="font-color font-color-4" data-color=""></a></li>
|
2251
|
+
<li><a href="javascript:;" class="font-color font-color-5" data-color=""></a></li>
|
2252
|
+
<li><a href="javascript:;" class="font-color font-color-6" data-color=""></a></li>
|
2253
|
+
<li><a href="javascript:;" class="font-color font-color-7" data-color=""></a></li>
|
2254
|
+
<li><a href="javascript:;" class="font-color font-color-8" data-color=""></a></li>
|
2255
|
+
<li class="remove-color"><a href="javascript:;" class="link-remove-color">去掉颜色</a></li>
|
2256
|
+
</ul>
|
2257
|
+
''').appendTo(@menuWrapper)
|
2258
|
+
|
2259
|
+
@menuWrapper.on 'mousedown', '.color-list', (e) ->
|
2260
|
+
false
|
2261
|
+
|
2262
|
+
@menuWrapper.on 'click', '.font-color', (e) =>
|
2263
|
+
@wrapper.removeClass('menu-on')
|
2264
|
+
$link = $(e.currentTarget)
|
2265
|
+
rgb = window.getComputedStyle($link[0], null).getPropertyValue('background-color')
|
2266
|
+
hex = @_convertRgbToHex rgb
|
2267
|
+
return unless hex
|
2268
|
+
document.execCommand 'foreColor', false, hex
|
2269
|
+
@editor.trigger 'valuechanged'
|
2270
|
+
@editor.trigger 'selectionchanged'
|
2271
|
+
|
2272
|
+
@menuWrapper.on 'click', '.link-remove-color', (e) =>
|
2273
|
+
@wrapper.removeClass('menu-on')
|
2274
|
+
$p = @editor.body.find 'p'
|
2275
|
+
return unless $p.length > 0
|
2276
|
+
|
2277
|
+
rgb = window.getComputedStyle($p[0], null).getPropertyValue('color')
|
2278
|
+
hex = @_convertRgbToHex rgb
|
2279
|
+
return unless hex
|
2280
|
+
|
2281
|
+
document.execCommand 'foreColor', false, hex
|
2282
|
+
@editor.trigger 'valuechanged'
|
2283
|
+
@editor.trigger 'selectionchanged'
|
2284
|
+
|
2285
|
+
_convertRgbToHex:(rgb) ->
|
2286
|
+
re = /rgb\((\d+),\s?(\d+),\s?(\d+)\)/g
|
2287
|
+
match = re.exec rgb
|
2288
|
+
return '' unless match
|
2289
|
+
|
2290
|
+
rgbToHex = (r, g, b) ->
|
2291
|
+
componentToHex = (c) ->
|
2292
|
+
hex = c.toString(16)
|
2293
|
+
if hex.length == 1 then '0' + hex else hex
|
2294
|
+
"#" + componentToHex(r) + componentToHex(g) + componentToHex(b)
|
2295
|
+
|
2296
|
+
rgbToHex match[1] * 1, match[2] * 1, match[3] * 1
|
2297
|
+
|
2298
|
+
|
2299
|
+
Simditor.Toolbar.addButton(ColorButton)
|
2300
|
+
|
2301
|
+
|
2302
|
+
|
1992
2303
|
class ListButton extends Button
|
1993
2304
|
|
1994
2305
|
type: ''
|
@@ -2047,20 +2358,6 @@ class ListButton extends Button
|
|
2047
2358
|
|
2048
2359
|
$contents = $(range.extractContents())
|
2049
2360
|
|
2050
|
-
#if $breakedEl?
|
2051
|
-
#$contents.wrapInner('<' + $breakedEl[0].tagName + '/>')
|
2052
|
-
#if @editor.selection.rangeAtStartOf $breakedEl, range
|
2053
|
-
#range.setEndBefore($breakedEl[0])
|
2054
|
-
#range.collapse(false)
|
2055
|
-
#$breakedEl.remove() if $breakedEl.children().length < 1
|
2056
|
-
#else if @editor.selection.rangeAtEndOf $breakedEl, range
|
2057
|
-
#range.setEndAfter($breakedEl[0])
|
2058
|
-
#range.collapse(false)
|
2059
|
-
#else
|
2060
|
-
#$breakedEl = @editor.selection.breakBlockEl($breakedEl, range)
|
2061
|
-
#range.setEndBefore($breakedEl[0])
|
2062
|
-
#range.collapse(false)
|
2063
|
-
|
2064
2361
|
results = []
|
2065
2362
|
$contents.children().each (i, el) =>
|
2066
2363
|
converted = @_convertEl el
|
@@ -2080,7 +2377,7 @@ class ListButton extends Button
|
|
2080
2377
|
$el = $(el)
|
2081
2378
|
results = []
|
2082
2379
|
anotherType = if @type == 'ul' then 'ol' else 'ul'
|
2083
|
-
|
2380
|
+
|
2084
2381
|
if $el.is @type
|
2085
2382
|
$el.children('li').each (i, li) =>
|
2086
2383
|
$li = $(li)
|
@@ -2110,6 +2407,14 @@ class OrderListButton extends ListButton
|
|
2110
2407
|
title: '有序列表'
|
2111
2408
|
icon: 'list-ol'
|
2112
2409
|
htmlTag: 'ol'
|
2410
|
+
shortcut: 'cmd+191'
|
2411
|
+
render: ->
|
2412
|
+
if @editor.util.os.mac
|
2413
|
+
@title = @title + ' ( Cmd + / )'
|
2414
|
+
else
|
2415
|
+
@title = @title + ' ( ctrl + / )'
|
2416
|
+
@shortcut = 'ctrl+191'
|
2417
|
+
super()
|
2113
2418
|
|
2114
2419
|
class UnorderListButton extends ListButton
|
2115
2420
|
type: 'ul'
|
@@ -2117,6 +2422,14 @@ class UnorderListButton extends ListButton
|
|
2117
2422
|
title: '无序列表'
|
2118
2423
|
icon: 'list-ul'
|
2119
2424
|
htmlTag: 'ul'
|
2425
|
+
shortcut: 'cmd+190'
|
2426
|
+
render: ->
|
2427
|
+
if @editor.util.os.mac
|
2428
|
+
@title = @title + ' ( Cmd + . )'
|
2429
|
+
else
|
2430
|
+
@title = @title + ' ( Ctrl + . )'
|
2431
|
+
@shortcut = 'ctrl+190'
|
2432
|
+
super()
|
2120
2433
|
|
2121
2434
|
Simditor.Toolbar.addButton(OrderListButton)
|
2122
2435
|
Simditor.Toolbar.addButton(UnorderListButton)
|
@@ -2195,6 +2508,18 @@ class CodeButton extends Button
|
|
2195
2508
|
|
2196
2509
|
disableTag: 'li, table'
|
2197
2510
|
|
2511
|
+
|
2512
|
+
constructor: (@editor) ->
|
2513
|
+
super @editor
|
2514
|
+
|
2515
|
+
@editor.on 'decorate', (e, $el) =>
|
2516
|
+
$el.find('pre').each (i, pre) =>
|
2517
|
+
@decorate $(pre)
|
2518
|
+
|
2519
|
+
@editor.on 'undecorate', (e, $el) =>
|
2520
|
+
$el.find('pre').each (i, pre) =>
|
2521
|
+
@undecorate $(pre)
|
2522
|
+
|
2198
2523
|
render: (args...) ->
|
2199
2524
|
super args...
|
2200
2525
|
@popover = new CodePopover(@editor)
|
@@ -2209,6 +2534,16 @@ class CodeButton extends Button
|
|
2209
2534
|
|
2210
2535
|
result
|
2211
2536
|
|
2537
|
+
decorate: ($pre) ->
|
2538
|
+
lang = $pre.attr('data-lang')
|
2539
|
+
$pre.removeClass()
|
2540
|
+
$pre.addClass('lang-' + lang) if lang and lang != -1
|
2541
|
+
|
2542
|
+
undecorate: ($pre) ->
|
2543
|
+
lang = $pre.attr('data-lang')
|
2544
|
+
$pre.removeClass()
|
2545
|
+
$pre.addClass('lang-' + lang) if lang and lang != -1
|
2546
|
+
|
2212
2547
|
command: ->
|
2213
2548
|
range = @editor.selection.getRange()
|
2214
2549
|
startNode = range.startContainer
|
@@ -2248,7 +2583,7 @@ class CodeButton extends Button
|
|
2248
2583
|
codeStr = '\n'
|
2249
2584
|
else
|
2250
2585
|
codeStr = @editor.formatter.clearHtml($el)
|
2251
|
-
block = $('<' + @htmlTag + '/>').
|
2586
|
+
block = $('<' + @htmlTag + '/>').text(codeStr)
|
2252
2587
|
results.push(block)
|
2253
2588
|
|
2254
2589
|
results
|
@@ -2286,14 +2621,16 @@ class CodePopover extends Popover
|
|
2286
2621
|
@selectEl = @el.find '.select-lang'
|
2287
2622
|
|
2288
2623
|
@selectEl.on 'change', (e) =>
|
2289
|
-
lang = @selectEl.val()
|
2290
|
-
|
2291
|
-
@target.removeClass(
|
2624
|
+
@lang = @selectEl.val()
|
2625
|
+
selected = @target.hasClass('selected')
|
2626
|
+
@target.removeClass()
|
2292
2627
|
.removeAttr('data-lang')
|
2293
2628
|
|
2294
2629
|
if @lang isnt -1
|
2295
|
-
@target.addClass('lang-' + lang)
|
2296
|
-
@target.attr('data-lang', lang)
|
2630
|
+
@target.addClass('lang-' + @lang)
|
2631
|
+
@target.attr('data-lang', @lang)
|
2632
|
+
|
2633
|
+
@target.addClass('selected') if selected
|
2297
2634
|
|
2298
2635
|
show: (args...) ->
|
2299
2636
|
super args...
|
@@ -2375,16 +2712,15 @@ class LinkButton extends Button
|
|
2375
2712
|
|
2376
2713
|
range.selectNodeContents $link[0]
|
2377
2714
|
|
2378
|
-
|
2379
|
-
|
2380
|
-
|
2381
|
-
|
2382
|
-
|
2383
|
-
|
2384
|
-
|
2385
|
-
@popover.textEl.focus()
|
2386
|
-
@popover.textEl[0].select()
|
2715
|
+
@popover.one 'popovershow', =>
|
2716
|
+
if linkText
|
2717
|
+
@popover.urlEl.focus()
|
2718
|
+
@popover.urlEl[0].select()
|
2719
|
+
else
|
2720
|
+
@popover.textEl.focus()
|
2721
|
+
@popover.textEl[0].select()
|
2387
2722
|
|
2723
|
+
@editor.selection.selectRange range
|
2388
2724
|
@editor.trigger 'valuechanged'
|
2389
2725
|
@editor.trigger 'selectionchanged'
|
2390
2726
|
|
@@ -2426,9 +2762,9 @@ class LinkPopover extends Popover
|
|
2426
2762
|
setTimeout =>
|
2427
2763
|
range = document.createRange()
|
2428
2764
|
@editor.selection.setRangeAfter @target, range
|
2429
|
-
@editor.body.focus() if @editor.util.browser.firefox
|
2430
2765
|
@hide()
|
2431
2766
|
@editor.trigger 'valuechanged'
|
2767
|
+
@editor.trigger 'selectionchanged'
|
2432
2768
|
, 0
|
2433
2769
|
|
2434
2770
|
@unlinkEl.on 'click', (e) =>
|
@@ -2438,7 +2774,8 @@ class LinkPopover extends Popover
|
|
2438
2774
|
|
2439
2775
|
range = document.createRange()
|
2440
2776
|
@editor.selection.setRangeAfter txtNode, range
|
2441
|
-
@editor.
|
2777
|
+
@editor.trigger 'valuechanged'
|
2778
|
+
@editor.trigger 'selectionchanged'
|
2442
2779
|
|
2443
2780
|
show: (args...) ->
|
2444
2781
|
super args...
|
@@ -2453,14 +2790,6 @@ Simditor.Toolbar.addButton(LinkButton)
|
|
2453
2790
|
|
2454
2791
|
class ImageButton extends Button
|
2455
2792
|
|
2456
|
-
_wrapperTpl: """
|
2457
|
-
<div class="simditor-image" contenteditable="false" tabindex="-1">
|
2458
|
-
<div class="simditor-image-resize-handle right"></div>
|
2459
|
-
<div class="simditor-image-resize-handle bottom"></div>
|
2460
|
-
<div class="simditor-image-resize-handle right-bottom"></div>
|
2461
|
-
</div>
|
2462
|
-
"""
|
2463
|
-
|
2464
2793
|
name: 'image'
|
2465
2794
|
|
2466
2795
|
icon: 'picture-o'
|
@@ -2473,9 +2802,11 @@ class ImageButton extends Button
|
|
2473
2802
|
|
2474
2803
|
defaultImage: ''
|
2475
2804
|
|
2476
|
-
|
2805
|
+
needFocus: false
|
2806
|
+
|
2807
|
+
#maxWidth: 0
|
2477
2808
|
|
2478
|
-
maxHeight: 0
|
2809
|
+
#maxHeight: 0
|
2479
2810
|
|
2480
2811
|
menu: [{
|
2481
2812
|
name: 'upload-image',
|
@@ -2490,54 +2821,53 @@ class ImageButton extends Button
|
|
2490
2821
|
super @editor
|
2491
2822
|
|
2492
2823
|
@defaultImage = @editor.opts.defaultImage
|
2493
|
-
|
2494
|
-
|
2824
|
+
#@maxWidth = @editor.opts.maxImageWidth || @editor.body.width()
|
2825
|
+
#@maxHeight = @editor.opts.maxImageHeight || $(window).height()
|
2495
2826
|
|
2496
|
-
@editor.on '
|
2497
|
-
$
|
2498
|
-
@decorate $(img)
|
2499
|
-
|
2500
|
-
@editor.on 'undecorate', (e, $el) =>
|
2501
|
-
$el.find('img').each (i, img) =>
|
2502
|
-
@undecorate $(img)
|
2827
|
+
@editor.body.on 'click', 'img:not([data-non-image])', (e) =>
|
2828
|
+
$img = $(e.currentTarget)
|
2503
2829
|
|
2504
|
-
|
2505
|
-
|
2506
|
-
|
2507
|
-
|
2508
|
-
|
2509
|
-
@
|
2510
|
-
|
2511
|
-
|
2512
|
-
@editor.body.blur()
|
2513
|
-
@editor.body.find('.simditor-image').removeClass('selected')
|
2514
|
-
$imgWrapper.addClass('selected').focus()
|
2515
|
-
$img = $imgWrapper.find('img')
|
2516
|
-
$imgWrapper.width $img.width()
|
2517
|
-
$imgWrapper.height $img.height()
|
2518
|
-
@popover.show $imgWrapper
|
2830
|
+
#@popover.show $img
|
2831
|
+
range = document.createRange()
|
2832
|
+
range.selectNode $img[0]
|
2833
|
+
@editor.selection.selectRange range
|
2834
|
+
setTimeout =>
|
2835
|
+
@editor.body.focus()
|
2836
|
+
@editor.trigger 'selectionchanged'
|
2837
|
+
, 0
|
2519
2838
|
|
2520
2839
|
false
|
2521
2840
|
|
2522
|
-
@editor.body.on '
|
2523
|
-
false
|
2841
|
+
@editor.body.on 'mouseup', 'img:not([data-non-image])', (e) =>
|
2842
|
+
return false
|
2843
|
+
|
2524
2844
|
|
2525
2845
|
@editor.on 'selectionchanged.image', =>
|
2526
|
-
range = @editor.selection.
|
2846
|
+
range = @editor.selection.sel.getRangeAt(0)
|
2527
2847
|
return unless range?
|
2528
|
-
$container = $(range.commonAncestorContainer)
|
2529
2848
|
|
2530
|
-
|
2531
|
-
|
2532
|
-
|
2533
|
-
@popover.
|
2849
|
+
$contents = $(range.cloneContents()).contents()
|
2850
|
+
if $contents.length == 1 and $contents.is('img:not([data-non-image])')
|
2851
|
+
$img = $(range.startContainer).contents().eq(range.startOffset)
|
2852
|
+
@popover.show $img
|
2853
|
+
else
|
2854
|
+
@popover.hide()
|
2855
|
+
|
2856
|
+
@editor.on 'valuechanged.image', =>
|
2857
|
+
$masks = @editor.wrapper.find('.simditor-image-loading')
|
2858
|
+
return unless $masks.length > 0
|
2859
|
+
$masks.each (i, mask) =>
|
2860
|
+
$mask = $(mask)
|
2861
|
+
$img = $mask.data 'img'
|
2862
|
+
unless $img and $img.parent().length > 0
|
2863
|
+
$mask.remove()
|
2864
|
+
if $img
|
2865
|
+
file = $img.data 'file'
|
2866
|
+
if file
|
2867
|
+
@editor.uploader.cancel file
|
2868
|
+
if @editor.body.find('img.uploading').length < 1
|
2869
|
+
@editor.uploader.trigger 'uploadready', [file]
|
2534
2870
|
|
2535
|
-
@editor.body.on 'keydown', '.simditor-image', (e) =>
|
2536
|
-
return unless e.which == 8
|
2537
|
-
@popover.hide()
|
2538
|
-
$(e.currentTarget).remove()
|
2539
|
-
@editor.trigger 'valuechanged'
|
2540
|
-
return false
|
2541
2871
|
|
2542
2872
|
render: (args...) ->
|
2543
2873
|
super args...
|
@@ -2547,25 +2877,31 @@ class ImageButton extends Button
|
|
2547
2877
|
super()
|
2548
2878
|
|
2549
2879
|
$uploadItem = @menuEl.find('.menu-item-upload-image')
|
2550
|
-
$input =
|
2551
|
-
|
2880
|
+
$input = null
|
2881
|
+
|
2882
|
+
createInput = =>
|
2883
|
+
$input.remove() if $input
|
2884
|
+
$input = $('<input type="file" title="上传图片" accept="image/*">')
|
2885
|
+
.appendTo($uploadItem)
|
2886
|
+
|
2887
|
+
createInput()
|
2552
2888
|
|
2553
|
-
$
|
2889
|
+
$uploadItem.on 'click mousedown', 'input[type=file]', (e) =>
|
2554
2890
|
e.stopPropagation()
|
2555
2891
|
|
2556
|
-
$
|
2892
|
+
$uploadItem.on 'change', 'input[type=file]', (e) =>
|
2557
2893
|
if @editor.inputManager.focused
|
2558
2894
|
@editor.uploader.upload($input, {
|
2559
2895
|
inline: true
|
2560
2896
|
})
|
2561
|
-
|
2562
|
-
else
|
2897
|
+
createInput()
|
2898
|
+
else
|
2563
2899
|
@editor.one 'focus', (e) =>
|
2564
2900
|
@editor.uploader.upload($input, {
|
2565
2901
|
inline: true
|
2566
2902
|
})
|
2567
|
-
|
2568
|
-
@editor.
|
2903
|
+
createInput()
|
2904
|
+
@editor.focus()
|
2569
2905
|
@wrapper.removeClass('menu-on')
|
2570
2906
|
|
2571
2907
|
@_initUploader()
|
@@ -2578,49 +2914,71 @@ class ImageButton extends Button
|
|
2578
2914
|
@editor.uploader.on 'beforeupload', (e, file) =>
|
2579
2915
|
return unless file.inline
|
2580
2916
|
|
2581
|
-
if file.
|
2582
|
-
$img = file.
|
2917
|
+
if file.img
|
2918
|
+
$img = $(file.img)
|
2583
2919
|
else
|
2584
|
-
$img = @createImage()
|
2585
|
-
|
2586
|
-
file.
|
2920
|
+
$img = @createImage(file.name)
|
2921
|
+
#$img.click()
|
2922
|
+
file.img = $img
|
2923
|
+
|
2924
|
+
$img.addClass 'uploading'
|
2925
|
+
$img.data 'file', file
|
2587
2926
|
|
2588
2927
|
@editor.uploader.readImageFile file.obj, (img) =>
|
2589
|
-
|
2928
|
+
return unless $img.hasClass('uploading')
|
2929
|
+
src = if img then img.src else @defaultImage
|
2930
|
+
|
2931
|
+
@loadImage $img, src, () =>
|
2932
|
+
@popover.refresh()
|
2590
2933
|
@popover.srcEl.val('正在上传...')
|
2591
|
-
|
2592
|
-
$bar = $('<div class="simditor-image-progress-bar"><div><span></span></div></div>').appendTo file.imgWrapper
|
2593
|
-
$bar.text('正在上传').addClass('hint') unless @editor.uploader.html5
|
2594
|
-
|
2595
|
-
if img
|
2596
|
-
@loadImage $img, img.src, () =>
|
2597
|
-
@popover.refresh()
|
2598
|
-
prepare()
|
2599
|
-
else
|
2600
|
-
prepare()
|
2934
|
+
.prop('disabled', true)
|
2601
2935
|
|
2602
2936
|
@editor.uploader.on 'uploadprogress', (e, file, loaded, total) =>
|
2603
2937
|
return unless file.inline
|
2604
2938
|
|
2605
2939
|
percent = loaded / total
|
2606
|
-
|
2607
|
-
if percent >
|
2608
|
-
|
2609
|
-
|
2610
|
-
|
2611
|
-
|
2612
|
-
|
2940
|
+
percent = (percent * 100).toFixed(0)
|
2941
|
+
percent = 99 if percent > 99
|
2942
|
+
|
2943
|
+
$mask = file.img.data('mask')
|
2944
|
+
if $mask
|
2945
|
+
$img = $mask.data('img')
|
2946
|
+
if $img and $img.parent().length > 0
|
2947
|
+
$mask.find("span").text(percent)
|
2948
|
+
else
|
2949
|
+
$mask.remove()
|
2613
2950
|
|
2614
2951
|
@editor.uploader.on 'uploadsuccess', (e, file, result) =>
|
2615
2952
|
return unless file.inline
|
2616
2953
|
|
2617
|
-
$img = file.
|
2618
|
-
|
2619
|
-
|
2620
|
-
|
2621
|
-
|
2954
|
+
$img = file.img
|
2955
|
+
$img.removeData 'file'
|
2956
|
+
$img.removeClass 'uploading'
|
2957
|
+
|
2958
|
+
$mask = $img.data('mask')
|
2959
|
+
$mask.remove() if $mask
|
2960
|
+
$img.removeData 'mask'
|
2961
|
+
|
2962
|
+
if result.success == false
|
2963
|
+
msg = result.msg || '上传被拒绝了'
|
2964
|
+
if simple? and simple.message?
|
2965
|
+
simple.message
|
2966
|
+
content: msg
|
2967
|
+
else
|
2968
|
+
alert msg
|
2969
|
+
$img.attr 'src', @defaultImage
|
2970
|
+
else
|
2971
|
+
$img.attr 'src', result.file_path
|
2972
|
+
|
2973
|
+
@popover.srcEl.prop('disabled', false)
|
2974
|
+
|
2975
|
+
@editor.trigger 'valuechanged'
|
2976
|
+
if @editor.body.find('img.uploading').length < 1
|
2977
|
+
@editor.uploader.trigger 'uploadready', [file, result]
|
2978
|
+
|
2622
2979
|
|
2623
2980
|
@editor.uploader.on 'uploaderror', (e, file, xhr) =>
|
2981
|
+
return unless file.inline
|
2624
2982
|
return if xhr.statusText == 'abort'
|
2625
2983
|
|
2626
2984
|
if xhr.responseText
|
@@ -2631,109 +2989,113 @@ class ImageButton extends Button
|
|
2631
2989
|
msg = '上传出错了'
|
2632
2990
|
|
2633
2991
|
if simple? and simple.message?
|
2634
|
-
simple.message
|
2992
|
+
simple.message
|
2993
|
+
content: msg
|
2635
2994
|
else
|
2636
|
-
alert
|
2995
|
+
alert msg
|
2637
2996
|
|
2638
|
-
$img = file.
|
2639
|
-
|
2640
|
-
|
2641
|
-
@popover.srcEl.val $img.attr('src')
|
2642
|
-
file.imgWrapper.find(".mask, .simditor-image-progress-bar").remove()
|
2643
|
-
@editor.trigger 'valuechanged'
|
2997
|
+
$img = file.img
|
2998
|
+
$img.removeData 'file'
|
2999
|
+
$img.removeClass 'uploading'
|
2644
3000
|
|
2645
|
-
|
2646
|
-
|
2647
|
-
|
3001
|
+
$mask = $img.data('mask')
|
3002
|
+
$mask.remove() if $mask
|
3003
|
+
$img.removeData 'mask'
|
2648
3004
|
|
2649
|
-
|
2650
|
-
|
2651
|
-
return if $wrapper.length > 0
|
3005
|
+
$img.attr 'src', @defaultImage
|
3006
|
+
@popover.srcEl.prop('disabled', false)
|
2652
3007
|
|
2653
|
-
|
2654
|
-
.
|
2655
|
-
|
3008
|
+
@editor.trigger 'valuechanged'
|
3009
|
+
if @editor.body.find('img.uploading').length < 1
|
3010
|
+
@editor.uploader.trigger 'uploadready', [file, result]
|
2656
3011
|
|
2657
|
-
undecorate: ($img) ->
|
2658
|
-
$wrapper = $img.parent('.simditor-image')
|
2659
|
-
return if $wrapper.length < 1
|
2660
3012
|
|
2661
|
-
|
2662
|
-
$
|
3013
|
+
status: ($node) ->
|
3014
|
+
@setDisabled $node.is(@disableTag) if $node?
|
3015
|
+
return true if @disabled
|
2663
3016
|
|
2664
3017
|
loadImage: ($img, src, callback) ->
|
2665
|
-
$
|
3018
|
+
$mask = $img.data('mask')
|
3019
|
+
if !$mask
|
3020
|
+
$mask = $('<div class="simditor-image-loading"><span></span></div>')
|
3021
|
+
.appendTo(@editor.wrapper)
|
3022
|
+
$mask.addClass('uploading') if $img.hasClass('uploading') and @editor.uploader.html5
|
3023
|
+
$img.data('mask', $mask)
|
3024
|
+
$mask.data('img', $img)
|
3025
|
+
|
3026
|
+
imgPosition = $img.position()
|
3027
|
+
toolbarH = @editor.toolbar.wrapper.outerHeight()
|
3028
|
+
$mask.css({
|
3029
|
+
top: imgPosition.top + toolbarH,
|
3030
|
+
left: imgPosition.left,
|
3031
|
+
width: $img.width(),
|
3032
|
+
height: $img.height()
|
3033
|
+
})
|
3034
|
+
|
2666
3035
|
img = new Image()
|
2667
3036
|
|
2668
3037
|
img.onload = =>
|
2669
3038
|
width = img.width
|
2670
3039
|
height = img.height
|
2671
|
-
if width > @maxWidth
|
2672
|
-
height = @maxWidth * height / width
|
2673
|
-
width = @maxWidth
|
2674
|
-
if height > @maxHeight
|
2675
|
-
width = @maxHeight * width / height
|
2676
|
-
height = @maxHeight
|
3040
|
+
#if width > @maxWidth
|
3041
|
+
#height = @maxWidth * height / width
|
3042
|
+
#width = @maxWidth
|
3043
|
+
#if height > @maxHeight
|
3044
|
+
#width = @maxHeight * width / height
|
3045
|
+
#height = @maxHeight
|
2677
3046
|
|
2678
3047
|
$img.attr({
|
2679
3048
|
src: src,
|
2680
|
-
width: width,
|
2681
|
-
|
2682
|
-
'data-
|
2683
|
-
'data-origin-size': img.width + ',' + img.height
|
3049
|
+
#width: width,
|
3050
|
+
#height: height,
|
3051
|
+
'data-image-size': img.width + ',' + img.height
|
2684
3052
|
})
|
2685
3053
|
|
2686
|
-
$
|
2687
|
-
.
|
3054
|
+
if $img.hasClass 'uploading' # img being uploaded
|
3055
|
+
$mask.css({
|
3056
|
+
width: $img.width(),
|
3057
|
+
height: $img.height()
|
3058
|
+
})
|
3059
|
+
else
|
3060
|
+
$mask.remove()
|
3061
|
+
$img.removeData('mask')
|
2688
3062
|
|
2689
|
-
callback(
|
3063
|
+
callback(img)
|
2690
3064
|
|
2691
3065
|
img.onerror = =>
|
2692
3066
|
callback(false)
|
3067
|
+
$mask.remove()
|
3068
|
+
$img.removeData('mask')
|
2693
3069
|
|
2694
3070
|
img.src = src
|
2695
3071
|
|
2696
|
-
createImage: () ->
|
3072
|
+
createImage: (name = 'Image') ->
|
3073
|
+
@editor.focus() unless @editor.inputManager.focused
|
2697
3074
|
range = @editor.selection.getRange()
|
2698
|
-
startNode = range.startContainer
|
2699
|
-
endNode = range.endContainer
|
2700
|
-
$startBlock = @editor.util.closestBlockEl(startNode)
|
2701
|
-
$endBlock = @editor.util.closestBlockEl(endNode)
|
2702
|
-
|
2703
3075
|
range.deleteContents()
|
2704
3076
|
|
2705
|
-
|
2706
|
-
|
2707
|
-
|
2708
|
-
|
2709
|
-
range.setEndAfter($startBlock[0])
|
2710
|
-
range.collapse(false)
|
2711
|
-
else if $startBlock.is 'p'
|
2712
|
-
if @editor.util.isEmptyNode $startBlock
|
2713
|
-
range.selectNode $startBlock[0]
|
2714
|
-
range.deleteContents()
|
2715
|
-
else if @editor.selection.rangeAtEndOf $startBlock, range
|
2716
|
-
range.setEndAfter($startBlock[0])
|
2717
|
-
range.collapse(false)
|
2718
|
-
else if @editor.selection.rangeAtStartOf $startBlock, range
|
2719
|
-
range.setEndBefore($startBlock[0])
|
2720
|
-
range.collapse(false)
|
2721
|
-
else
|
2722
|
-
$breakedEl = @editor.selection.breakBlockEl($startBlock, range)
|
2723
|
-
range.setEndBefore($breakedEl[0])
|
2724
|
-
range.collapse(false)
|
3077
|
+
$block = @editor.util.closestBlockEl()
|
3078
|
+
if $block.is('p') and !@editor.util.isEmptyNode $block
|
3079
|
+
$block = $('<p/>').append(@editor.util.phBr).insertAfter($block)
|
3080
|
+
@editor.selection.setRangeAtStartOf $block, range
|
2725
3081
|
|
2726
|
-
$img = $('<img/>')
|
3082
|
+
$img = $('<img/>').attr('alt', name)
|
2727
3083
|
range.insertNode $img[0]
|
2728
|
-
|
3084
|
+
|
3085
|
+
$nextBlock = $block.next 'p'
|
3086
|
+
unless $nextBlock.length > 0
|
3087
|
+
$nextBlock = $('<p/>').append(@editor.util.phBr).insertAfter($block)
|
3088
|
+
@editor.selection.setRangeAtStartOf $nextBlock
|
3089
|
+
|
2729
3090
|
$img
|
2730
3091
|
|
2731
|
-
command: () ->
|
3092
|
+
command: (src) ->
|
2732
3093
|
$img = @createImage()
|
2733
3094
|
|
2734
|
-
@loadImage $img, @defaultImage, =>
|
3095
|
+
@loadImage $img, src or @defaultImage, =>
|
2735
3096
|
@editor.trigger 'valuechanged'
|
2736
|
-
$img.
|
3097
|
+
$img[0].offsetHeight
|
3098
|
+
$img.click()
|
2737
3099
|
|
2738
3100
|
@popover.one 'popovershow', =>
|
2739
3101
|
@popover.srcEl.focus()
|
@@ -2749,7 +3111,6 @@ class ImagePopover extends Popover
|
|
2749
3111
|
<input class="image-src" type="text"/>
|
2750
3112
|
<a class="btn-upload" href="javascript:;" title="上传图片" tabindex="-1">
|
2751
3113
|
<span class="fa fa-upload"></span>
|
2752
|
-
<input type="file" title="上传图片" name="upload_file" accept="image/*">
|
2753
3114
|
</a>
|
2754
3115
|
</div>
|
2755
3116
|
</div>
|
@@ -2767,59 +3128,63 @@ class ImagePopover extends Popover
|
|
2767
3128
|
.append(@_tpl)
|
2768
3129
|
@srcEl = @el.find '.image-src'
|
2769
3130
|
|
2770
|
-
@srcEl.on 'keyup', (e) =>
|
2771
|
-
return if e.which == 13
|
2772
|
-
clearTimeout @timer if @timer
|
2773
|
-
|
2774
|
-
@timer = setTimeout =>
|
2775
|
-
src = @srcEl.val()
|
2776
|
-
$img = @target.find('img')
|
2777
|
-
@button.loadImage $img, src, (success) =>
|
2778
|
-
return unless success
|
2779
|
-
@refresh()
|
2780
|
-
@editor.trigger 'valuechanged'
|
2781
|
-
|
2782
|
-
@timer = null
|
2783
|
-
, 200
|
2784
|
-
|
2785
3131
|
@srcEl.on 'keydown', (e) =>
|
2786
3132
|
if e.which == 13 or e.which == 27 or e.which == 9
|
2787
3133
|
e.preventDefault()
|
2788
|
-
|
2789
|
-
|
2790
|
-
|
3134
|
+
|
3135
|
+
if e.which == 13 and !@target.hasClass('uploading')
|
3136
|
+
src = @srcEl.val()
|
3137
|
+
@button.loadImage @target, src, (success) =>
|
3138
|
+
return unless success
|
3139
|
+
@button.editor.body.focus()
|
3140
|
+
@button.editor.selection.setRangeAfter @target
|
3141
|
+
@hide()
|
3142
|
+
@editor.trigger 'valuechanged'
|
3143
|
+
else
|
3144
|
+
@button.editor.body.focus()
|
3145
|
+
@button.editor.selection.setRangeAfter @target
|
3146
|
+
@hide()
|
3147
|
+
|
3148
|
+
@editor.on 'valuechanged', (e) =>
|
3149
|
+
@refresh() if @active
|
2791
3150
|
|
2792
3151
|
@_initUploader()
|
2793
3152
|
|
2794
3153
|
_initUploader: ->
|
3154
|
+
$uploadBtn = @el.find('.btn-upload')
|
2795
3155
|
unless @editor.uploader?
|
2796
|
-
|
3156
|
+
$uploadBtn.remove()
|
2797
3157
|
return
|
2798
3158
|
|
2799
|
-
|
3159
|
+
createInput = =>
|
3160
|
+
@input.remove() if @input
|
3161
|
+
@input = $('<input type="file" title="上传图片" accept="image/*">')
|
3162
|
+
.appendTo($uploadBtn)
|
2800
3163
|
|
2801
|
-
|
3164
|
+
createInput()
|
3165
|
+
|
3166
|
+
@el.on 'click mousedown', 'input[type=file]', (e) =>
|
2802
3167
|
e.stopPropagation()
|
2803
3168
|
|
2804
|
-
@
|
3169
|
+
@el.on 'change', 'input[type=file]', (e) =>
|
2805
3170
|
@editor.uploader.upload(@input, {
|
2806
3171
|
inline: true,
|
2807
|
-
|
3172
|
+
img: @target
|
2808
3173
|
})
|
2809
|
-
|
2810
|
-
@input = @input.clone(true).replaceAll(@input)
|
3174
|
+
createInput()
|
2811
3175
|
|
2812
3176
|
show: (args...) ->
|
2813
3177
|
super args...
|
2814
|
-
$img = @target
|
2815
|
-
|
3178
|
+
$img = @target
|
3179
|
+
if $img.hasClass 'uploading'
|
3180
|
+
@srcEl.val '正在上传'
|
3181
|
+
else
|
3182
|
+
@srcEl.val $img.attr('src')
|
2816
3183
|
|
2817
3184
|
|
2818
3185
|
Simditor.Toolbar.addButton(ImageButton)
|
2819
3186
|
|
2820
3187
|
|
2821
|
-
|
2822
|
-
|
2823
3188
|
class IndentButton extends Button
|
2824
3189
|
|
2825
3190
|
name: 'indent'
|
@@ -2985,15 +3350,16 @@ class TableButton extends Button
|
|
2985
3350
|
$table.find('tr:first td').each (i, td) =>
|
2986
3351
|
$col = $('<col/>').appendTo $colgroup
|
2987
3352
|
|
2988
|
-
|
3353
|
+
@refreshTableWidth $table
|
3354
|
+
|
2989
3355
|
|
2990
|
-
$resizeHandle = $('<div class="resize-handle" contenteditable="false"></div>')
|
3356
|
+
$resizeHandle = $('<div class="simditor-resize-handle" contenteditable="false"></div>')
|
2991
3357
|
.appendTo($wrapper)
|
2992
3358
|
|
2993
3359
|
$wrapper.on 'mousemove', 'td', (e) =>
|
2994
3360
|
return if $wrapper.hasClass('resizing')
|
2995
3361
|
$td = $(e.currentTarget)
|
2996
|
-
x = e.pageX - $(e.currentTarget).offset().left
|
3362
|
+
x = e.pageX - $(e.currentTarget).offset().left
|
2997
3363
|
$td = $td.prev() if x < 5 and $td.prev().length > 0
|
2998
3364
|
|
2999
3365
|
if $td.next('td').length < 1
|
@@ -3020,7 +3386,7 @@ class TableButton extends Button
|
|
3020
3386
|
$wrapper.on 'mouseleave', (e) =>
|
3021
3387
|
$resizeHandle.hide()
|
3022
3388
|
|
3023
|
-
$wrapper.on 'mousedown', '.resize-handle', (e) =>
|
3389
|
+
$wrapper.on 'mousedown', '.simditor-resize-handle', (e) =>
|
3024
3390
|
$handle = $(e.currentTarget)
|
3025
3391
|
$leftTd = $handle.data 'td'
|
3026
3392
|
$leftCol = $handle.data 'col'
|
@@ -3058,7 +3424,9 @@ class TableButton extends Button
|
|
3058
3424
|
false
|
3059
3425
|
|
3060
3426
|
decorate: ($table) ->
|
3061
|
-
|
3427
|
+
if $table.parent('.simditor-table').length > 0
|
3428
|
+
@undecorate $table
|
3429
|
+
|
3062
3430
|
$table.wrap '<div class="simditor-table"></div>'
|
3063
3431
|
@initResize $table
|
3064
3432
|
$table.parent()
|
@@ -3250,3 +3618,32 @@ class TableButton extends Button
|
|
3250
3618
|
|
3251
3619
|
Simditor.Toolbar.addButton TableButton
|
3252
3620
|
|
3621
|
+
|
3622
|
+
|
3623
|
+
class StrikethroughButton extends Button
|
3624
|
+
|
3625
|
+
name: 'strikethrough'
|
3626
|
+
|
3627
|
+
icon: 'strikethrough'
|
3628
|
+
|
3629
|
+
title: '删除线文字'
|
3630
|
+
|
3631
|
+
htmlTag: 'strike'
|
3632
|
+
|
3633
|
+
disableTag: 'pre'
|
3634
|
+
|
3635
|
+
status: ($node) ->
|
3636
|
+
@setDisabled $node.is(@disableTag) if $node?
|
3637
|
+
return true if @disabled
|
3638
|
+
|
3639
|
+
active = document.queryCommandState('strikethrough') is true
|
3640
|
+
@setActive active
|
3641
|
+
active
|
3642
|
+
|
3643
|
+
command: ->
|
3644
|
+
document.execCommand 'strikethrough'
|
3645
|
+
@editor.trigger 'valuechanged'
|
3646
|
+
@editor.trigger 'selectionchanged'
|
3647
|
+
|
3648
|
+
|
3649
|
+
Simditor.Toolbar.addButton(StrikethroughButton)
|