simditor-rails 1.0.1 → 1.0.5
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/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)
|