rails_medium_editor_insert_plugin 0.1.0

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.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +86 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +139 -0
  10. data/Rakefile +6 -0
  11. data/app/assets/javascripts/medium-editor-js.js +1 -0
  12. data/app/assets/stylesheets/medium-editor-style.scss +3 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/rails_medium_editor_insert_plugin.rb +7 -0
  16. data/lib/rails_medium_editor_insert_plugin/version.rb +3 -0
  17. data/rails_medium_editor_insert_plugin.gemspec +31 -0
  18. data/vendor/assets/javascripts/medium_editor_js/handlebars.js +29 -0
  19. data/vendor/assets/javascripts/medium_editor_js/jquery-fileupload.js +1477 -0
  20. data/vendor/assets/javascripts/medium_editor_js/jquery-iframe-transport.js +217 -0
  21. data/vendor/assets/javascripts/medium_editor_js/jquery-sortable.js +693 -0
  22. data/vendor/assets/javascripts/medium_editor_js/jquery-ui-widget.js +572 -0
  23. data/vendor/assets/javascripts/medium_editor_js/medium-editor-insert-plugin.js +2091 -0
  24. data/vendor/assets/javascripts/medium_editor_js/medium-editor.js +7054 -0
  25. data/vendor/assets/javascripts/rails-medium-editor-insert-plugin.js +7 -0
  26. data/vendor/assets/stylesheets/medium-editor.scss +3 -0
  27. data/vendor/assets/stylesheets/medium_editor_style/_flat.scss +62 -0
  28. data/vendor/assets/stylesheets/medium_editor_style/_medium-editor.scss +182 -0
  29. data/vendor/assets/stylesheets/medium_editor_style/medium-editor-insert-plugin-frontend.scss +72 -0
  30. data/vendor/assets/stylesheets/medium_editor_style/medium-editor-insert-plugin.scss +209 -0
  31. data/vendor/assets/stylesheets/medium_editor_style/medium-editor-style.scss +4 -0
  32. metadata +130 -0
@@ -0,0 +1,2091 @@
1
+ /*!
2
+ * medium-editor-insert-plugin v2.2.2 - jQuery insert plugin for MediumEditor
3
+ *
4
+ * https://github.com/orthes/medium-editor-insert-plugin
5
+ *
6
+ * Copyright (c) 2014 Pavel Linkesch (http://linkesch.sk)
7
+ * Released under the MIT license
8
+ */
9
+
10
+ this["MediumInsert"] = this["MediumInsert"] || {};
11
+ this["MediumInsert"]["Templates"] = this["MediumInsert"]["Templates"] || {};
12
+
13
+ this["MediumInsert"]["Templates"]["src/js/templates/core-buttons.hbs"] = Handlebars.template({"1":function(container,depth0,helpers,partials,data) {
14
+ var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function";
15
+
16
+ return " <li><a data-addon=\""
17
+ + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"key","hash":{},"data":data}) : helper)))
18
+ + "\" data-action=\"add\" class=\"medium-insert-action\">"
19
+ + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"label","hash":{},"data":data}) : helper))) != null ? stack1 : "")
20
+ + "</a></li>\n";
21
+ },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
22
+ var stack1;
23
+
24
+ return "<div class=\"medium-insert-buttons\" contenteditable=\"false\" style=\"display: none\">\n <a class=\"medium-insert-buttons-show\">+</a>\n <ul class=\"medium-insert-buttons-addons\" style=\"display: none\">\n"
25
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.addons : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
26
+ + " </ul>\n</div>\n";
27
+ },"useData":true});
28
+
29
+ this["MediumInsert"]["Templates"]["src/js/templates/core-caption.hbs"] = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
30
+ var helper;
31
+
32
+ return "<figcaption contenteditable=\"true\" class=\"medium-insert-caption-placeholder\" data-placeholder=\""
33
+ + container.escapeExpression(((helper = (helper = helpers.placeholder || (depth0 != null ? depth0.placeholder : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"placeholder","hash":{},"data":data}) : helper)))
34
+ + "\"></figcaption>";
35
+ },"useData":true});
36
+
37
+ this["MediumInsert"]["Templates"]["src/js/templates/core-empty-line.hbs"] = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
38
+ return "<p><br></p>\n";
39
+ },"useData":true});
40
+
41
+ this["MediumInsert"]["Templates"]["src/js/templates/embeds-toolbar.hbs"] = Handlebars.template({"1":function(container,depth0,helpers,partials,data) {
42
+ var stack1;
43
+
44
+ return " <div class=\"medium-insert-embeds-toolbar medium-editor-toolbar medium-toolbar-arrow-under medium-editor-toolbar-active\">\n <ul class=\"medium-editor-toolbar-actions clearfix\">\n"
45
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.styles : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
46
+ + " </ul>\n </div>\n";
47
+ },"2":function(container,depth0,helpers,partials,data) {
48
+ var stack1;
49
+
50
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.label : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
51
+ },"3":function(container,depth0,helpers,partials,data) {
52
+ var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function";
53
+
54
+ return " <li>\n <button class=\"medium-editor-action\" data-action=\""
55
+ + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"key","hash":{},"data":data}) : helper)))
56
+ + "\">"
57
+ + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"label","hash":{},"data":data}) : helper))) != null ? stack1 : "")
58
+ + "</button>\n </li>\n";
59
+ },"5":function(container,depth0,helpers,partials,data) {
60
+ var stack1;
61
+
62
+ return " <div class=\"medium-insert-embeds-toolbar2 medium-editor-toolbar medium-editor-toolbar-active\">\n <ul class=\"medium-editor-toolbar-actions clearfix\">\n"
63
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.actions : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
64
+ + " </ul>\n </div>\n";
65
+ },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
66
+ var stack1, alias1=depth0 != null ? depth0 : {};
67
+
68
+ return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.styles : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
69
+ + "\n"
70
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.actions : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
71
+ },"useData":true});
72
+
73
+ this["MediumInsert"]["Templates"]["src/js/templates/embeds-wrapper.hbs"] = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
74
+ var stack1, helper;
75
+
76
+ return "<div class=\"medium-insert-embeds\" contenteditable=\"false\">\n <figure>\n <div class=\"medium-insert-embed\">\n "
77
+ + ((stack1 = ((helper = (helper = helpers.html || (depth0 != null ? depth0.html : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"html","hash":{},"data":data}) : helper))) != null ? stack1 : "")
78
+ + "\n </div>\n </figure>\n <div class=\"medium-insert-embeds-overlay\"></div>\n</div>";
79
+ },"useData":true});
80
+
81
+ this["MediumInsert"]["Templates"]["src/js/templates/images-fileupload.hbs"] = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
82
+ return "<input type=\"file\" multiple>";
83
+ },"useData":true});
84
+
85
+ this["MediumInsert"]["Templates"]["src/js/templates/images-image.hbs"] = Handlebars.template({"1":function(container,depth0,helpers,partials,data) {
86
+ return " <div class=\"medium-insert-images-progress\"></div>\n";
87
+ },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
88
+ var stack1, helper, alias1=depth0 != null ? depth0 : {};
89
+
90
+ return "<figure contenteditable=\"false\">\n <img src=\""
91
+ + container.escapeExpression(((helper = (helper = helpers.img || (depth0 != null ? depth0.img : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"img","hash":{},"data":data}) : helper)))
92
+ + "\" alt=\"\">\n"
93
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.progress : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
94
+ + "</figure>";
95
+ },"useData":true});
96
+
97
+ this["MediumInsert"]["Templates"]["src/js/templates/images-progressbar.hbs"] = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
98
+ return "<progress min=\"0\" max=\"100\" value=\"0\">0</progress>";
99
+ },"useData":true});
100
+
101
+ this["MediumInsert"]["Templates"]["src/js/templates/images-toolbar.hbs"] = Handlebars.template({"1":function(container,depth0,helpers,partials,data) {
102
+ var stack1;
103
+
104
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.label : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
105
+ },"2":function(container,depth0,helpers,partials,data) {
106
+ var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function";
107
+
108
+ return " <li>\n <button class=\"medium-editor-action\" data-action=\""
109
+ + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"key","hash":{},"data":data}) : helper)))
110
+ + "\">"
111
+ + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"label","hash":{},"data":data}) : helper))) != null ? stack1 : "")
112
+ + "</button>\n </li>\n";
113
+ },"4":function(container,depth0,helpers,partials,data) {
114
+ var stack1;
115
+
116
+ return " <div class=\"medium-insert-images-toolbar2 medium-editor-toolbar medium-editor-toolbar-active\">\n <ul class=\"medium-editor-toolbar-actions clearfix\">\n"
117
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.actions : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
118
+ + " </ul>\n </div>\n";
119
+ },"5":function(container,depth0,helpers,partials,data) {
120
+ var stack1;
121
+
122
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.label : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
123
+ },"6":function(container,depth0,helpers,partials,data) {
124
+ var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function";
125
+
126
+ return " <li>\n <button class=\"medium-editor-action\" data-action=\""
127
+ + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"key","hash":{},"data":data}) : helper)))
128
+ + "\">"
129
+ + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"label","hash":{},"data":data}) : helper))) != null ? stack1 : "")
130
+ + "</button>\n </li>\n";
131
+ },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
132
+ var stack1, alias1=depth0 != null ? depth0 : {};
133
+
134
+ return "<div class=\"medium-insert-images-toolbar medium-editor-toolbar medium-toolbar-arrow-under medium-editor-toolbar-active\">\n <ul class=\"medium-editor-toolbar-actions clearfix\">\n"
135
+ + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.styles : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
136
+ + " </ul>\n</div>\n\n"
137
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.actions : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
138
+ },"useData":true});
139
+ ;(function ($, window, document, undefined) {
140
+
141
+ 'use strict';
142
+
143
+ /** Default values */
144
+ var pluginName = 'mediumInsert',
145
+ defaults = {
146
+ editor: null,
147
+ enabled: true,
148
+ addons: {
149
+ images: true, // boolean or object containing configuration
150
+ embeds: true
151
+ }
152
+ };
153
+
154
+ /**
155
+ * Capitalize first character
156
+ *
157
+ * @param {string} str
158
+ * @return {string}
159
+ */
160
+
161
+ function ucfirst (str) {
162
+ return str.charAt(0).toUpperCase() + str.slice(1);
163
+ }
164
+
165
+ /**
166
+ * Core plugin's object
167
+ *
168
+ * Sets options, variables and calls init() function
169
+ *
170
+ * @constructor
171
+ * @param {DOM} el - DOM element to init the plugin on
172
+ * @param {object} options - Options to override defaults
173
+ * @return {void}
174
+ */
175
+
176
+ function Core (el, options) {
177
+ var editor;
178
+
179
+ this.el = el;
180
+ this.$el = $(el);
181
+ this.templates = window.MediumInsert.Templates;
182
+
183
+ if (options) {
184
+ // Fix #142
185
+ // Avoid deep copying editor object, because since v2.3.0 it contains circular references which causes jQuery.extend to break
186
+ // Instead copy editor object to this.options manually
187
+ editor = options.editor;
188
+ options.editor = null;
189
+ }
190
+ this.options = $.extend(true, {}, defaults, options);
191
+ this.options.editor = editor;
192
+
193
+ this._defaults = defaults;
194
+ this._name = pluginName;
195
+
196
+ // Extend editor's functions
197
+ if (this.options && this.options.editor) {
198
+ this.options.editor._serialize = this.options.editor.serialize;
199
+ this.options.editor._destroy = this.options.editor.destroy;
200
+ this.options.editor._setup = this.options.editor.setup;
201
+ this.options.editor._hideInsertButtons = this.hideButtons;
202
+
203
+ this.options.editor.serialize = this.editorSerialize;
204
+ this.options.editor.destroy = this.editorDestroy;
205
+ this.options.editor.setup = this.editorSetup;
206
+
207
+ this.options.editor.getExtensionByName('placeholder').updatePlaceholder = this.editorUpdatePlaceholder;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Initialization
213
+ *
214
+ * @return {void}
215
+ */
216
+
217
+ Core.prototype.init = function () {
218
+ this.$el.addClass('medium-editor-insert-plugin');
219
+
220
+ if (typeof this.options.addons !== 'object' || Object.keys(this.options.addons).length === 0) {
221
+ this.disable();
222
+ }
223
+
224
+ this.initAddons();
225
+ this.clean();
226
+ this.events();
227
+ };
228
+
229
+ /**
230
+ * Event listeners
231
+ *
232
+ * @return {void}
233
+ */
234
+
235
+ Core.prototype.events = function () {
236
+ var that = this;
237
+
238
+ this.$el
239
+ .on('dragover drop', function (e) {
240
+ e.preventDefault();
241
+ })
242
+ .on('keyup click', $.proxy(this, 'toggleButtons'))
243
+ .on('selectstart mousedown', '.medium-insert, .medium-insert-buttons', $.proxy(this, 'disableSelection'))
244
+ .on('click', '.medium-insert-buttons-show', $.proxy(this, 'toggleAddons'))
245
+ .on('click', '.medium-insert-action', $.proxy(this, 'addonAction'))
246
+ .on('paste', '.medium-insert-caption-placeholder', function (e) {
247
+ $.proxy(that, 'removeCaptionPlaceholder')($(e.target));
248
+ });
249
+
250
+ $(window).on('resize', $.proxy(this, 'positionButtons', null));
251
+ };
252
+
253
+ /**
254
+ * Return editor instance
255
+ *
256
+ * @return {object} MediumEditor
257
+ */
258
+
259
+ Core.prototype.getEditor = function () {
260
+ return this.options.editor;
261
+ };
262
+
263
+ /**
264
+ * Extend editor's serialize function
265
+ *
266
+ * @return {object} Serialized data
267
+ */
268
+
269
+ Core.prototype.editorSerialize = function () {
270
+ var data = this._serialize();
271
+
272
+ $.each(data, function (key) {
273
+ var $data = $('<div />').html(data[key].value);
274
+
275
+ $data.find('.medium-insert-buttons').remove();
276
+
277
+ // Restore original embed code from embed wrapper attribute value.
278
+ $data.find('[data-embed-code]').each(function() {
279
+ var $this = $(this);
280
+ $this.html($this.attr('data-embed-code'));
281
+ });
282
+
283
+ data[key].value = $data.html();
284
+ });
285
+
286
+ return data;
287
+ };
288
+
289
+ /**
290
+ * Extend editor's destroy function to deactivate this plugin too
291
+ *
292
+ * @return {void}
293
+ */
294
+
295
+ Core.prototype.editorDestroy = function () {
296
+ $.each(this.elements, function (key, el) {
297
+ $(el).data('plugin_' + pluginName).disable();
298
+ });
299
+
300
+ this._destroy();
301
+ };
302
+
303
+ /**
304
+ * Extend editor's setup function to activate this plugin too
305
+ *
306
+ * @return {void}
307
+ */
308
+
309
+ Core.prototype.editorSetup = function () {
310
+ this._setup();
311
+
312
+ $.each(this.elements, function (key, el) {
313
+ $(el).data('plugin_' + pluginName).enable();
314
+ });
315
+ };
316
+
317
+ /**
318
+ * Extend editor's placeholder.updatePlaceholder function to show placeholder dispite of the plugin buttons
319
+ *
320
+ * @return {void}
321
+ */
322
+
323
+ Core.prototype.editorUpdatePlaceholder = function (el) {
324
+ var contents = $(el).children()
325
+ .not('.medium-insert-buttons').contents();
326
+
327
+ if (contents.length === 1 && contents[0].nodeName.toLowerCase() === 'br') {
328
+ this.showPlaceholder(el);
329
+ this.base._hideInsertButtons($(el));
330
+ } else {
331
+ this.hidePlaceholder(el);
332
+ }
333
+ };
334
+
335
+ /**
336
+ * Trigger editableInput on editor
337
+ *
338
+ * @return {void}
339
+ */
340
+
341
+ Core.prototype.triggerInput = function () {
342
+ if (this.getEditor()) {
343
+ this.getEditor().trigger('editableInput', null, this.el);
344
+ }
345
+ };
346
+
347
+ /**
348
+ * Deselects selected text
349
+ *
350
+ * @return {void}
351
+ */
352
+
353
+ Core.prototype.deselect = function () {
354
+ document.getSelection().removeAllRanges();
355
+ };
356
+
357
+ /**
358
+ * Disables the plugin
359
+ *
360
+ * @return {void}
361
+ */
362
+
363
+ Core.prototype.disable = function () {
364
+ this.options.enabled = false;
365
+
366
+ this.$el.find('.medium-insert-buttons').addClass('hide');
367
+ };
368
+
369
+ /**
370
+ * Enables the plugin
371
+ *
372
+ * @return {void}
373
+ */
374
+
375
+ Core.prototype.enable = function () {
376
+ this.options.enabled = true;
377
+
378
+ this.$el.find('.medium-insert-buttons').removeClass('hide');
379
+ };
380
+
381
+ /**
382
+ * Disables selectstart mousedown events on plugin elements except images
383
+ *
384
+ * @return {void}
385
+ */
386
+
387
+ Core.prototype.disableSelection = function (e) {
388
+ var $el = $(e.target);
389
+
390
+ if ($el.is('img') === false || $el.hasClass('medium-insert-buttons-show')) {
391
+ e.preventDefault();
392
+ }
393
+ };
394
+
395
+ /**
396
+ * Initialize addons
397
+ *
398
+ * @return {void}
399
+ */
400
+
401
+ Core.prototype.initAddons = function () {
402
+ var that = this;
403
+
404
+ if (!this.options.addons || this.options.addons.length === 0) {
405
+ return;
406
+ }
407
+
408
+ $.each(this.options.addons, function (addon, options) {
409
+ var addonName = pluginName + ucfirst(addon);
410
+
411
+ if (options === false) {
412
+ delete that.options.addons[addon];
413
+ return;
414
+ }
415
+
416
+ that.$el[addonName](options);
417
+ that.options.addons[addon] = that.$el.data('plugin_'+ addonName).options;
418
+ });
419
+ };
420
+
421
+ /**
422
+ * Cleans a content of the editor
423
+ *
424
+ * @return {void}
425
+ */
426
+
427
+ Core.prototype.clean = function () {
428
+ var that = this,
429
+ $buttons, $lastEl, $text;
430
+
431
+ if (this.options.enabled === false) {
432
+ return;
433
+ }
434
+
435
+ // Fix #39
436
+ // After deleting all content (ctrl+A and delete) in Firefox, all content is deleted and only <br> appears
437
+ // To force placeholder to appear, set <p><br></p> as content of the $el
438
+
439
+ if (this.$el.html().trim() === '' || this.$el.html().trim() === '<br>') {
440
+ this.$el.html(this.templates['src/js/templates/core-empty-line.hbs']().trim());
441
+ }
442
+
443
+ // Fix #29
444
+ // Wrap content text in <p></p> to avoid Firefox problems
445
+ $text = this.$el
446
+ .contents()
447
+ .filter(function () {
448
+ return this.nodeName === '#text' && $.trim($(this).text()) !== '';
449
+ });
450
+
451
+ $text.each(function () {
452
+ $(this).wrap('<p />');
453
+
454
+ // Fix #145
455
+ // Move caret at the end of the element that's being wrapped
456
+ that.moveCaret($(this).parent(), $(this).text().length);
457
+ });
458
+
459
+ this.addButtons();
460
+
461
+ $buttons = this.$el.find('.medium-insert-buttons');
462
+ $lastEl = $buttons.prev();
463
+ if ($lastEl.attr('class') && $lastEl.attr('class').match(/medium\-insert(?!\-active)/)) {
464
+ $buttons.before(this.templates['src/js/templates/core-empty-line.hbs']().trim());
465
+ }
466
+ };
467
+
468
+ /**
469
+ * Returns HTML template of buttons
470
+ *
471
+ * @return {string} HTML template of buttons
472
+ */
473
+
474
+ Core.prototype.getButtons = function () {
475
+ if (this.options.enabled === false) {
476
+ return;
477
+ }
478
+
479
+ return this.templates['src/js/templates/core-buttons.hbs']({
480
+ addons: this.options.addons
481
+ }).trim();
482
+ };
483
+
484
+ /**
485
+ * Appends buttons at the end of the $el
486
+ *
487
+ * @return {void}
488
+ */
489
+
490
+ Core.prototype.addButtons = function () {
491
+ if (this.$el.find('.medium-insert-buttons').length === 0) {
492
+ this.$el.append(this.getButtons());
493
+ }
494
+ };
495
+
496
+ /**
497
+ * Move buttons to current active, empty paragraph and show them
498
+ *
499
+ * @return {void}
500
+ */
501
+
502
+ Core.prototype.toggleButtons = function (e) {
503
+ var $el = $(e.target),
504
+ selection = window.getSelection(),
505
+ that = this,
506
+ range, $current, $p, activeAddon;
507
+
508
+ if (this.options.enabled === false) {
509
+ return;
510
+ }
511
+
512
+ if (!selection || selection.rangeCount === 0) {
513
+ $current = $el;
514
+ } else {
515
+ range = selection.getRangeAt(0);
516
+ $current = $(range.commonAncestorContainer);
517
+ }
518
+
519
+ // When user clicks on editor's placeholder in FF, $current el is editor itself, not the first paragraph as it should
520
+ if ($current.hasClass('medium-editor-insert-plugin')) {
521
+ $current = $current.find('p:first');
522
+ }
523
+
524
+ $p = $current.is('p') ? $current : $current.closest('p');
525
+
526
+ this.clean();
527
+
528
+ if ($el.hasClass('medium-editor-placeholder') === false && $el.closest('.medium-insert-buttons').length === 0 && $current.closest('.medium-insert-buttons').length === 0) {
529
+
530
+ this.$el.find('.medium-insert-active').removeClass('medium-insert-active');
531
+
532
+ $.each(this.options.addons, function (addon) {
533
+ if ($el.closest('.medium-insert-'+ addon).length) {
534
+ $current = $el;
535
+ }
536
+
537
+ if ($current.closest('.medium-insert-'+ addon).length) {
538
+ $p = $current.closest('.medium-insert-'+ addon);
539
+ activeAddon = addon;
540
+ return;
541
+ }
542
+ });
543
+
544
+ if ($p.length && (($p.text().trim() === '' && !activeAddon) || activeAddon === 'images')) {
545
+ $p.addClass('medium-insert-active');
546
+
547
+ // If buttons are displayed on addon paragraph, wait 100ms for possible captions to display
548
+ setTimeout(function () {
549
+ that.positionButtons(activeAddon);
550
+ that.showButtons(activeAddon);
551
+ }, activeAddon ? 100 : 0);
552
+ } else {
553
+ this.hideButtons();
554
+ }
555
+ }
556
+ };
557
+
558
+ /**
559
+ * Show buttons
560
+ *
561
+ * @param {string} activeAddon - Name of active addon
562
+ * @returns {void}
563
+ */
564
+
565
+ Core.prototype.showButtons = function (activeAddon) {
566
+ var $buttons = this.$el.find('.medium-insert-buttons');
567
+
568
+ $buttons.show();
569
+ $buttons.find('li').show();
570
+
571
+ if (activeAddon) {
572
+ $buttons.find('li').hide();
573
+ $buttons.find('a[data-addon="'+ activeAddon +'"]').parent().show();
574
+ }
575
+ };
576
+
577
+ /**
578
+ * Hides buttons
579
+ *
580
+ * @param {jQuery} $el - Editor element
581
+ * @returns {void}
582
+ */
583
+
584
+ Core.prototype.hideButtons = function ($el) {
585
+ $el = $el || this.$el;
586
+
587
+ $el.find('.medium-insert-buttons').hide();
588
+ $el.find('.medium-insert-buttons-addons').hide();
589
+ $el.find('.medium-insert-buttons-show').removeClass('medium-insert-buttons-rotate');
590
+ };
591
+
592
+ /**
593
+ * Position buttons
594
+ *
595
+ * @param {string} activeAddon - Name of active addon
596
+ * @return {void}
597
+ */
598
+
599
+ Core.prototype.positionButtons = function (activeAddon) {
600
+ var $buttons = this.$el.find('.medium-insert-buttons'),
601
+ $p = this.$el.find('.medium-insert-active'),
602
+ $first = $p.find('figure:first').length ? $p.find('figure:first') : $p,
603
+ left, top;
604
+
605
+ if ($p.length) {
606
+
607
+ left = $p.position().left - parseInt($buttons.find('.medium-insert-buttons-addons').css('left'), 10) - parseInt($buttons.find('.medium-insert-buttons-addons a:first').css('margin-left'), 10);
608
+ left = left < 0 ? $p.position().left : left;
609
+ top = $p.position().top + parseInt($p.css('margin-top'), 10);
610
+
611
+ if (activeAddon) {
612
+ if ($p.position().left !== $first.position().left) {
613
+ left = $first.position().left;
614
+ }
615
+
616
+ top += $p.height() + 15; // 15px offset
617
+ }
618
+
619
+ $buttons.css({
620
+ left: left,
621
+ top: top
622
+ });
623
+ }
624
+ };
625
+
626
+ /**
627
+ * Toggles addons buttons
628
+ *
629
+ * @return {void}
630
+ */
631
+
632
+ Core.prototype.toggleAddons = function () {
633
+ this.$el.find('.medium-insert-buttons-addons').fadeToggle();
634
+ this.$el.find('.medium-insert-buttons-show').toggleClass('medium-insert-buttons-rotate');
635
+ };
636
+
637
+ /**
638
+ * Hide addons buttons
639
+ *
640
+ * @return {void}
641
+ */
642
+
643
+ Core.prototype.hideAddons = function () {
644
+ this.$el.find('.medium-insert-buttons-addons').hide();
645
+ this.$el.find('.medium-insert-buttons-show').removeClass('medium-insert-buttons-rotate');
646
+ };
647
+
648
+ /**
649
+ * Call addon's action
650
+ *
651
+ * @param {Event} e
652
+ * @return {void}
653
+ */
654
+
655
+ Core.prototype.addonAction = function (e) {
656
+ var $a = $(e.target).is('a') ? $(e.target) : $(e.target).closest('a'),
657
+ addon = $a.data('addon'),
658
+ action = $a.data('action');
659
+
660
+ this.$el.data('plugin_'+ pluginName + ucfirst(addon))[action]();
661
+ };
662
+
663
+ /**
664
+ * Move caret at the beginning of the empty paragraph
665
+ *
666
+ * @param {jQuery} $el Element where to place the caret
667
+ * @param {integer} position Position where to move caret. Default: 0
668
+ *
669
+ * @return {void}
670
+ */
671
+
672
+ Core.prototype.moveCaret = function ($el, position) {
673
+ var range, sel, el;
674
+
675
+ position = position || 0;
676
+ range = document.createRange();
677
+ sel = window.getSelection();
678
+ el = $el.get(0);
679
+
680
+ if (!el.childNodes.length) {
681
+ var textEl = document.createTextNode(' ');
682
+ el.appendChild(textEl);
683
+ }
684
+
685
+ range.setStart(el.childNodes[0], position);
686
+ range.collapse(true);
687
+ sel.removeAllRanges();
688
+ sel.addRange(range);
689
+ };
690
+
691
+ /**
692
+ * Add caption
693
+ *
694
+ * @param {jQuery Element} $el
695
+ * @param {string} placeholder
696
+ * @return {void}
697
+ */
698
+
699
+ Core.prototype.addCaption = function ($el, placeholder) {
700
+ var $caption = $el.find('figcaption');
701
+
702
+ if ($caption.length === 0) {
703
+ $el.append(this.templates['src/js/templates/core-caption.hbs']({
704
+ placeholder: placeholder
705
+ }));
706
+ }
707
+ };
708
+
709
+ /**
710
+ * Remove captions
711
+ *
712
+ * @param {jQuery Element} $ignore
713
+ * @return {void}
714
+ */
715
+
716
+ Core.prototype.removeCaptions = function ($ignore) {
717
+ var $captions = this.$el.find('figcaption');
718
+
719
+ if ($ignore) {
720
+ $captions = $captions.not($ignore);
721
+ }
722
+
723
+ $captions.each(function () {
724
+ if ($(this).hasClass('medium-insert-caption-placeholder') || $(this).text().trim() === '') {
725
+ $(this).remove();
726
+ }
727
+ });
728
+ };
729
+
730
+ /**
731
+ * Remove caption placeholder
732
+ *
733
+ * @param {jQuery Element} $el
734
+ * @return {void}
735
+ */
736
+
737
+ Core.prototype.removeCaptionPlaceholder = function ($el) {
738
+ var $caption = $el.is('figcaption') ? $el : $el.find('figcaption');
739
+
740
+ if ($caption.length) {
741
+ $caption
742
+ .removeClass('medium-insert-caption-placeholder')
743
+ .removeAttr('data-placeholder');
744
+ }
745
+ };
746
+
747
+ /** Plugin initialization */
748
+
749
+ $.fn[pluginName] = function (options) {
750
+ return this.each(function () {
751
+ var that = this,
752
+ textareaId;
753
+
754
+ if ($(that).is('textarea')) {
755
+ textareaId = $(that).attr('medium-editor-textarea-id');
756
+ that = $(that).siblings('[medium-editor-textarea-id="'+ textareaId +'"]').get(0);
757
+ }
758
+
759
+ if (!$.data(that, 'plugin_' + pluginName)) {
760
+ // Plugin initialization
761
+ $.data(that, 'plugin_' + pluginName, new Core(that, options));
762
+ $.data(that, 'plugin_' + pluginName).init();
763
+ } else if (typeof options === 'string' && $.data(that, 'plugin_' + pluginName)[options]) {
764
+ // Method call
765
+ $.data(that, 'plugin_' + pluginName)[options]();
766
+ }
767
+ });
768
+ };
769
+
770
+ })(jQuery, window, document);
771
+
772
+ ;(function ($, window, document, undefined) {
773
+
774
+ 'use strict';
775
+
776
+ /** Default values */
777
+ var pluginName = 'mediumInsert',
778
+ addonName = 'Embeds', // first char is uppercase
779
+ defaults = {
780
+ label: '<span class="fa fa-youtube-play"></span>',
781
+ placeholder: 'Paste a YouTube, Vimeo, Facebook, Twitter or Instagram link and press Enter',
782
+ oembedProxy: 'http://medium.iframe.ly/api/oembed?iframe=1',
783
+ captions: true,
784
+ captionPlaceholder: 'Type caption (optional)',
785
+ styles: {
786
+ wide: {
787
+ label: '<span class="fa fa-align-justify"></span>',
788
+ // added: function ($el) {},
789
+ // removed: function ($el) {}
790
+ },
791
+ left: {
792
+ label: '<span class="fa fa-align-left"></span>',
793
+ // added: function ($el) {},
794
+ // removed: function ($el) {}
795
+ },
796
+ right: {
797
+ label: '<span class="fa fa-align-right"></span>',
798
+ // added: function ($el) {},
799
+ // removed: function ($el) {}
800
+ }
801
+ },
802
+ actions: {
803
+ remove: {
804
+ label: '<span class="fa fa-times"></span>',
805
+ clicked: function () {
806
+ var $event = $.Event('keydown');
807
+
808
+ $event.which = 8;
809
+ $(document).trigger($event);
810
+ }
811
+ }
812
+ }
813
+ };
814
+
815
+ /**
816
+ * Embeds object
817
+ *
818
+ * Sets options, variables and calls init() function
819
+ *
820
+ * @constructor
821
+ * @param {DOM} el - DOM element to init the plugin on
822
+ * @param {object} options - Options to override defaults
823
+ * @return {void}
824
+ */
825
+
826
+ function Embeds (el, options) {
827
+ this.el = el;
828
+ this.$el = $(el);
829
+ this.templates = window.MediumInsert.Templates;
830
+ this.core = this.$el.data('plugin_'+ pluginName);
831
+
832
+ this.options = $.extend(true, {}, defaults, options);
833
+
834
+ this._defaults = defaults;
835
+ this._name = pluginName;
836
+
837
+ // Extend editor's functions
838
+ if (this.core.getEditor()) {
839
+ this.core.getEditor()._serializePreEmbeds = this.core.getEditor().serialize;
840
+ this.core.getEditor().serialize = this.editorSerialize;
841
+ }
842
+
843
+ this.init();
844
+ }
845
+
846
+ /**
847
+ * Initialization
848
+ *
849
+ * @return {void}
850
+ */
851
+
852
+ Embeds.prototype.init = function () {
853
+ var $embeds = this.$el.find('.medium-insert-embeds');
854
+
855
+ $embeds.attr('contenteditable', false);
856
+ $embeds.each(function () {
857
+ if ($(this).find('.medium-insert-embeds-overlay').length === 0) {
858
+ $(this).append($('<div />').addClass('medium-insert-embeds-overlay'));
859
+ }
860
+ });
861
+
862
+ this.events();
863
+ this.backwardsCompatibility();
864
+ };
865
+
866
+ /**
867
+ * Event listeners
868
+ *
869
+ * @return {void}
870
+ */
871
+
872
+ Embeds.prototype.events = function () {
873
+ $(document)
874
+ .on('click', $.proxy(this, 'unselectEmbed'))
875
+ .on('keydown', $.proxy(this, 'removeEmbed'))
876
+ .on('click', '.medium-insert-embeds-toolbar .medium-editor-action', $.proxy(this, 'toolbarAction'))
877
+ .on('click', '.medium-insert-embeds-toolbar2 .medium-editor-action', $.proxy(this, 'toolbar2Action'));
878
+
879
+ this.$el
880
+ .on('keyup click paste', $.proxy(this, 'togglePlaceholder'))
881
+ .on('keydown', $.proxy(this, 'processLink'))
882
+ .on('click', '.medium-insert-embeds-overlay', $.proxy(this, 'selectEmbed'))
883
+ .on('contextmenu', '.medium-insert-embeds-placeholder', $.proxy(this, 'fixRightClickOnPlaceholder'));
884
+ };
885
+
886
+ /**
887
+ * Replace v0.* class names with new ones, wrap embedded content to new structure
888
+ *
889
+ * @return {void}
890
+ */
891
+
892
+ Embeds.prototype.backwardsCompatibility = function () {
893
+ var that = this;
894
+
895
+ this.$el.find('.mediumInsert-embeds')
896
+ .removeClass('mediumInsert-embeds')
897
+ .addClass('medium-insert-embeds');
898
+
899
+ this.$el.find('.medium-insert-embeds').each(function () {
900
+ if ($(this).find('.medium-insert-embed').length === 0) {
901
+ $(this).after(that.templates['src/js/templates/embeds-wrapper.hbs']({
902
+ html: $(this).html()
903
+ }));
904
+ $(this).remove();
905
+ }
906
+ });
907
+ };
908
+
909
+ /**
910
+ * Extend editor's serialize function
911
+ *
912
+ * @return {object} Serialized data
913
+ */
914
+
915
+ Embeds.prototype.editorSerialize = function () {
916
+ var data = this._serializePreEmbeds();
917
+
918
+ $.each(data, function (key) {
919
+ var $data = $('<div />').html(data[key].value);
920
+
921
+ $data.find('.medium-insert-embeds').removeAttr('contenteditable');
922
+ $data.find('.medium-insert-embeds-overlay').remove();
923
+
924
+ data[key].value = $data.html();
925
+ });
926
+
927
+ return data;
928
+ };
929
+
930
+ /**
931
+ * Add embedded element
932
+ *
933
+ * @return {void}
934
+ */
935
+
936
+ Embeds.prototype.add = function () {
937
+ var $place = this.$el.find('.medium-insert-active');
938
+
939
+ // Fix #132
940
+ // Make sure that the content of the paragraph is empty and <br> is wrapped in <p></p> to avoid Firefox problems
941
+ $place.html(this.templates['src/js/templates/core-empty-line.hbs']().trim());
942
+
943
+ // Replace paragraph with div to prevent #124 issue with pasting in Chrome,
944
+ // because medium editor wraps inserted content into paragraph and paragraphs can't be nested
945
+ if ($place.is('p')) {
946
+ $place.replaceWith('<div class="medium-insert-active">'+ $place.html() +'</div>');
947
+ $place = this.$el.find('.medium-insert-active');
948
+ this.core.moveCaret($place);
949
+ }
950
+
951
+ $place.addClass('medium-insert-embeds medium-insert-embeds-input medium-insert-embeds-active');
952
+
953
+ this.togglePlaceholder({ target: $place.get(0) });
954
+
955
+ $place.click();
956
+ this.core.hideButtons();
957
+ };
958
+
959
+ /**
960
+ * Toggles placeholder
961
+ *
962
+ * @param {Event} e
963
+ * @return {void}
964
+ */
965
+
966
+ Embeds.prototype.togglePlaceholder = function (e) {
967
+ var $place = $(e.target),
968
+ selection = window.getSelection(),
969
+ range, $current, text;
970
+
971
+ if (!selection || selection.rangeCount === 0) {
972
+ return;
973
+ }
974
+
975
+ range = selection.getRangeAt(0);
976
+ $current = $(range.commonAncestorContainer);
977
+
978
+ if ($current.hasClass('medium-insert-embeds-active')) {
979
+ $place = $current;
980
+ } else if ($current.closest('.medium-insert-embeds-active').length) {
981
+ $place = $current.closest('.medium-insert-embeds-active');
982
+ }
983
+
984
+ if ($place.hasClass('medium-insert-embeds-active')) {
985
+
986
+ text = $place.text().trim();
987
+
988
+ if (text === '' && $place.hasClass('medium-insert-embeds-placeholder') === false) {
989
+ $place
990
+ .addClass('medium-insert-embeds-placeholder')
991
+ .attr('data-placeholder', this.options.placeholder);
992
+ } else if (text !== '' && $place.hasClass('medium-insert-embeds-placeholder')) {
993
+ $place
994
+ .removeClass('medium-insert-embeds-placeholder')
995
+ .removeAttr('data-placeholder');
996
+ }
997
+
998
+ } else {
999
+ this.$el.find('.medium-insert-embeds-active').remove();
1000
+ }
1001
+ };
1002
+
1003
+ /**
1004
+ * Right click on placeholder in Chrome selects whole line. Fix this by placing caret at the end of line
1005
+ *
1006
+ * @param {Event} e
1007
+ * @return {void}
1008
+ */
1009
+
1010
+ Embeds.prototype.fixRightClickOnPlaceholder = function (e) {
1011
+ this.core.moveCaret($(e.target));
1012
+ };
1013
+
1014
+ /**
1015
+ * Process link
1016
+ *
1017
+ * @param {Event} e
1018
+ * @return {void}
1019
+ */
1020
+
1021
+ Embeds.prototype.processLink = function (e) {
1022
+ var $place = this.$el.find('.medium-insert-embeds-active'),
1023
+ url;
1024
+
1025
+ if (!$place.length) {
1026
+ return;
1027
+ }
1028
+
1029
+ url = $place.text().trim();
1030
+
1031
+ // Return empty placeholder on backspace, delete or enter
1032
+ if (url === '' && [8, 46, 13].indexOf(e.which) !== -1) {
1033
+ $place.remove();
1034
+ return;
1035
+ }
1036
+
1037
+ if (e.which === 13) {
1038
+ e.preventDefault();
1039
+ e.stopPropagation();
1040
+
1041
+ if (this.options.oembedProxy) {
1042
+ this.oembed(url);
1043
+ } else {
1044
+ this.parseUrl(url);
1045
+ }
1046
+ }
1047
+ };
1048
+
1049
+ /**
1050
+ * Get HTML via oEmbed proxy
1051
+ *
1052
+ * @param {string} url
1053
+ * @return {void}
1054
+ */
1055
+
1056
+ Embeds.prototype.oembed = function (url) {
1057
+ var that = this;
1058
+
1059
+ $.support.cors = true;
1060
+
1061
+ $.ajax({
1062
+ crossDomain: true,
1063
+ cache: false,
1064
+ url: this.options.oembedProxy,
1065
+ dataType: 'json',
1066
+ data: {
1067
+ url: url
1068
+ },
1069
+ success: function(data) {
1070
+ var html = data && data.html;
1071
+
1072
+ if (data && !html && data.type === 'photo' && data.url) {
1073
+ html = '<img src="' + data.url + '" alt="">';
1074
+ }
1075
+
1076
+ if (!html) {
1077
+ // Prevent render empty embed.
1078
+ $.proxy(that, 'convertBadEmbed', url)();
1079
+ return;
1080
+ }
1081
+
1082
+ if (html && html.indexOf('</script>') > -1) {
1083
+ // Store embed code with <script> tag inside wrapper attribute value.
1084
+ // Make nice attribute value escaping using jQuery.
1085
+ var $div = $('<div>')
1086
+ .attr('data-embed-code', html)
1087
+ .html(html);
1088
+ html = $('<div>').append($div).html();
1089
+ }
1090
+
1091
+ $.proxy(that, 'embed', html)();
1092
+ },
1093
+ error: function(jqXHR, textStatus, errorThrown) {
1094
+ var responseJSON = (function() {
1095
+ try {
1096
+ return JSON.parse(jqXHR.responseText);
1097
+ } catch(e) {}
1098
+ })();
1099
+
1100
+ if (typeof window.console !== 'undefined') {
1101
+ window.console.log((responseJSON && responseJSON.error) || jqXHR.status || errorThrown.message);
1102
+ } else {
1103
+ window.alert('Error requesting media from ' + that.options.oembedProxy + ' to insert: ' + errorThrown + ' (response status: ' + jqXHR.status + ')');
1104
+ }
1105
+
1106
+ $.proxy(that, 'convertBadEmbed', url)();
1107
+ }
1108
+ });
1109
+ };
1110
+
1111
+ /**
1112
+ * Get HTML using regexp
1113
+ *
1114
+ * @param {string} url
1115
+ * @return {void}
1116
+ */
1117
+
1118
+ Embeds.prototype.parseUrl = function (url) {
1119
+ var html;
1120
+
1121
+ if (!(new RegExp(['youtube', 'youtu.be', 'vimeo', 'instagram'].join('|')).test(url))) {
1122
+ $.proxy(this, 'convertBadEmbed', url)();
1123
+ return false;
1124
+ }
1125
+
1126
+ html = url.replace(/\n?/g, '')
1127
+ .replace(/^((http(s)?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/(watch\?v=|v\/)?)([a-zA-Z0-9\-_]+)(.*)?$/, '<div class="video video-youtube"><iframe width="420" height="315" src="//www.youtube.com/embed/$7" frameborder="0" allowfullscreen></iframe></div>')
1128
+ .replace(/^https?:\/\/vimeo\.com(\/.+)?\/([0-9]+)$/, '<div class="video video-vimeo"><iframe src="//player.vimeo.com/video/$2" width="500" height="281" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>')
1129
+ //.replace(/^https:\/\/twitter\.com\/(\w+)\/status\/(\d+)\/?$/, '<blockquote class="twitter-tweet" align="center" lang="en"><a href="https://twitter.com/$1/statuses/$2"></a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>')
1130
+ //.replace(/^https:\/\/www\.facebook\.com\/(video.php|photo.php)\?v=(\d+).+$/, '<div class="fb-post" data-href="https://www.facebook.com/photo.php?v=$2"><div class="fb-xfbml-parse-ignore"><a href="https://www.facebook.com/photo.php?v=$2">Post</a></div></div>')
1131
+ .replace(/^https?:\/\/instagram\.com\/p\/(.+)\/?$/, '<span class="instagram"><iframe src="//instagram.com/p/$1/embed/" width="612" height="710" frameborder="0" scrolling="no" allowtransparency="true"></iframe></span>');
1132
+
1133
+
1134
+ this.embed((/<("[^"]*"|'[^']*'|[^'">])*>/).test(html) ? html : false);
1135
+ };
1136
+
1137
+ /**
1138
+ * Add html to page
1139
+ *
1140
+ * @param {string} html
1141
+ * @return {void}
1142
+ */
1143
+
1144
+ Embeds.prototype.embed = function (html) {
1145
+ var $place = this.$el.find('.medium-insert-embeds-active');
1146
+
1147
+ if (!html) {
1148
+ alert('Incorrect URL format specified');
1149
+ return false;
1150
+ } else {
1151
+ $place.after(this.templates['src/js/templates/embeds-wrapper.hbs']({
1152
+ html: html
1153
+ }));
1154
+ $place.remove();
1155
+
1156
+ this.core.triggerInput();
1157
+
1158
+ if (html.indexOf('facebook') !== -1) {
1159
+ if (typeof(FB) !== 'undefined') {
1160
+ setTimeout(function () {
1161
+ FB.XFBML.parse();
1162
+ }, 2000);
1163
+ }
1164
+ }
1165
+ }
1166
+ };
1167
+
1168
+ /**
1169
+ * Convert bad oEmbed content to an actual line.
1170
+ * Instead of displaying the error message we convert the bad embed
1171
+ *
1172
+ * @param {string} content Bad content
1173
+ *
1174
+ * @return {void}
1175
+ */
1176
+ Embeds.prototype.convertBadEmbed = function (content) {
1177
+ var $place, $empty, $content,
1178
+ emptyTemplate = this.templates['src/js/templates/core-empty-line.hbs']().trim();
1179
+
1180
+ $place = this.$el.find('.medium-insert-embeds-active');
1181
+
1182
+ // convert embed node to an empty node and insert the bad embed inside
1183
+ $content = $(emptyTemplate);
1184
+ $place.before($content);
1185
+ $place.remove();
1186
+ $content.html(content);
1187
+
1188
+ // add an new empty node right after to simulate Enter press
1189
+ $empty = $(emptyTemplate);
1190
+ $content.after($empty);
1191
+
1192
+ this.core.triggerInput();
1193
+
1194
+ this.core.moveCaret($place);
1195
+ };
1196
+
1197
+ /**
1198
+ * Select clicked embed
1199
+ *
1200
+ * @param {Event} e
1201
+ * @returns {void}
1202
+ */
1203
+
1204
+ Embeds.prototype.selectEmbed = function (e) {
1205
+ if(this.core.options.enabled) {
1206
+ var $embed = $(e.target).hasClass('medium-insert-embeds') ? $(e.target) : $(e.target).closest('.medium-insert-embeds'),
1207
+ that = this;
1208
+
1209
+ $embed.addClass('medium-insert-embeds-selected');
1210
+
1211
+ setTimeout(function () {
1212
+ that.addToolbar();
1213
+
1214
+ if (that.options.captions) {
1215
+ that.core.addCaption($embed.find('figure'), that.options.captionPlaceholder);
1216
+ }
1217
+ }, 50);
1218
+ }
1219
+ };
1220
+
1221
+ /**
1222
+ * Unselect selected embed
1223
+ *
1224
+ * @param {Event} e
1225
+ * @returns {void}
1226
+ */
1227
+
1228
+ Embeds.prototype.unselectEmbed = function (e) {
1229
+ var $el = $(e.target).hasClass('medium-insert-embeds') ? $(e.target) : $(e.target).closest('.medium-insert-embeds'),
1230
+ $embed = this.$el.find('.medium-insert-embeds-selected');
1231
+
1232
+ if ($el.hasClass('medium-insert-embeds-selected')) {
1233
+ $embed.not($el).removeClass('medium-insert-embeds-selected');
1234
+ $('.medium-insert-embeds-toolbar, .medium-insert-embeds-toolbar2').remove();
1235
+ this.core.removeCaptions($el.find('figcaption'));
1236
+
1237
+ if ($(e.target).is('.medium-insert-caption-placeholder') || $(e.target).is('figcaption')) {
1238
+ $el.removeClass('medium-insert-embeds-selected');
1239
+ this.core.removeCaptionPlaceholder($el.find('figure'));
1240
+ }
1241
+ return;
1242
+ }
1243
+
1244
+ $embed.removeClass('medium-insert-embeds-selected');
1245
+ $('.medium-insert-embeds-toolbar, .medium-insert-embeds-toolbar2').remove();
1246
+
1247
+ if ($(e.target).is('.medium-insert-caption-placeholder')) {
1248
+ this.core.removeCaptionPlaceholder($el.find('figure'));
1249
+ } else if ($(e.target).is('figcaption') === false) {
1250
+ this.core.removeCaptions();
1251
+ }
1252
+ };
1253
+
1254
+ /**
1255
+ * Remove embed
1256
+ *
1257
+ * @param {Event} e
1258
+ * @returns {void}
1259
+ */
1260
+
1261
+ Embeds.prototype.removeEmbed = function (e) {
1262
+ var $embed, $empty;
1263
+
1264
+ if (e.which === 8 || e.which === 46) {
1265
+ $embed = this.$el.find('.medium-insert-embeds-selected');
1266
+
1267
+ if ($embed.length) {
1268
+ e.preventDefault();
1269
+
1270
+ $('.medium-insert-embeds-toolbar, .medium-insert-embeds-toolbar2').remove();
1271
+
1272
+ $empty = $(this.templates['src/js/templates/core-empty-line.hbs']().trim());
1273
+ $embed.before($empty);
1274
+ $embed.remove();
1275
+
1276
+ // Hide addons
1277
+ this.core.hideAddons();
1278
+
1279
+ this.core.moveCaret($empty);
1280
+ this.core.triggerInput();
1281
+ }
1282
+ }
1283
+ };
1284
+
1285
+ /**
1286
+ * Adds embed toolbar to editor
1287
+ *
1288
+ * @returns {void}
1289
+ */
1290
+
1291
+ Embeds.prototype.addToolbar = function () {
1292
+ var $embed = this.$el.find('.medium-insert-embeds-selected'),
1293
+ active = false,
1294
+ $toolbar, $toolbar2, top;
1295
+
1296
+ if ($embed.length === 0) {
1297
+ return;
1298
+ }
1299
+
1300
+ var mediumEditor = this.core.getEditor();
1301
+ var toolbarContainer = mediumEditor.options.elementsContainer || 'body';
1302
+
1303
+ $(toolbarContainer).append(this.templates['src/js/templates/embeds-toolbar.hbs']({
1304
+ styles: this.options.styles,
1305
+ actions: this.options.actions
1306
+ }).trim());
1307
+
1308
+ $toolbar = $('.medium-insert-embeds-toolbar');
1309
+ $toolbar2 = $('.medium-insert-embeds-toolbar2');
1310
+
1311
+ top = $embed.offset().top - $toolbar.height() - 8 - 2 - 5; // 8px - hight of an arrow under toolbar, 2px - height of an embed outset, 5px - distance from an embed
1312
+ if (top < 0) {
1313
+ top = 0;
1314
+ }
1315
+
1316
+ $toolbar
1317
+ .css({
1318
+ top: top,
1319
+ left: $embed.offset().left + $embed.width() / 2 - $toolbar.width() / 2
1320
+ })
1321
+ .show();
1322
+
1323
+ $toolbar2
1324
+ .css({
1325
+ top: $embed.offset().top + 2, // 2px - distance from a border
1326
+ left: $embed.offset().left + $embed.width() - $toolbar2.width() - 4 // 4px - distance from a border
1327
+ })
1328
+ .show();
1329
+
1330
+ $toolbar.find('button').each(function () {
1331
+ if ($embed.hasClass('medium-insert-embeds-'+ $(this).data('action'))) {
1332
+ $(this).addClass('medium-editor-button-active');
1333
+ active = true;
1334
+ }
1335
+ });
1336
+
1337
+ if (active === false) {
1338
+ $toolbar.find('button').first().addClass('medium-editor-button-active');
1339
+ }
1340
+ };
1341
+
1342
+ /**
1343
+ * Fires toolbar action
1344
+ *
1345
+ * @param {Event} e
1346
+ * @returns {void}
1347
+ */
1348
+
1349
+ Embeds.prototype.toolbarAction = function (e) {
1350
+ var $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button'),
1351
+ $li = $button.closest('li'),
1352
+ $ul = $li.closest('ul'),
1353
+ $lis = $ul.find('li'),
1354
+ $embed = this.$el.find('.medium-insert-embeds-selected'),
1355
+ that = this;
1356
+
1357
+ $button.addClass('medium-editor-button-active');
1358
+ $li.siblings().find('.medium-editor-button-active').removeClass('medium-editor-button-active');
1359
+
1360
+ $lis.find('button').each(function () {
1361
+ var className = 'medium-insert-embeds-'+ $(this).data('action');
1362
+
1363
+ if ($(this).hasClass('medium-editor-button-active')) {
1364
+ $embed.addClass(className);
1365
+
1366
+ if (that.options.styles[$(this).data('action')].added) {
1367
+ that.options.styles[$(this).data('action')].added($embed);
1368
+ }
1369
+ } else {
1370
+ $embed.removeClass(className);
1371
+
1372
+ if (that.options.styles[$(this).data('action')].removed) {
1373
+ that.options.styles[$(this).data('action')].removed($embed);
1374
+ }
1375
+ }
1376
+ });
1377
+
1378
+ this.core.triggerInput();
1379
+ };
1380
+
1381
+ /**
1382
+ * Fires toolbar2 action
1383
+ *
1384
+ * @param {Event} e
1385
+ * @returns {void}
1386
+ */
1387
+
1388
+ Embeds.prototype.toolbar2Action = function (e) {
1389
+ var $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button'),
1390
+ callback = this.options.actions[$button.data('action')].clicked;
1391
+
1392
+ if (callback) {
1393
+ callback(this.$el.find('.medium-insert-embeds-selected'));
1394
+ }
1395
+
1396
+ this.core.triggerInput();
1397
+ };
1398
+
1399
+ /** Plugin initialization */
1400
+
1401
+ $.fn[pluginName + addonName] = function (options) {
1402
+ return this.each(function () {
1403
+ if (!$.data(this, 'plugin_' + pluginName + addonName)) {
1404
+ $.data(this, 'plugin_' + pluginName + addonName, new Embeds(this, options));
1405
+ }
1406
+ });
1407
+ };
1408
+
1409
+ })(jQuery, window, document);
1410
+
1411
+ /*global MediumEditor*/
1412
+
1413
+ ;(function ($, window, document, Util, undefined) {
1414
+
1415
+ 'use strict';
1416
+
1417
+ /** Default values */
1418
+ var pluginName = 'mediumInsert',
1419
+ addonName = 'Images', // first char is uppercase
1420
+ defaults = {
1421
+ label: '<span class="fa fa-camera"></span>',
1422
+ deleteMethod: 'POST',
1423
+ deleteScript: 'delete.php',
1424
+ preview: true,
1425
+ captions: true,
1426
+ captionPlaceholder: 'Type caption for image (optional)',
1427
+ autoGrid: 3,
1428
+ fileUploadOptions: { // See https://github.com/blueimp/jQuery-File-Upload/wiki/Options
1429
+ url: 'upload.php',
1430
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i
1431
+ },
1432
+ fileDeleteOptions: {},
1433
+ styles: {
1434
+ wide: {
1435
+ label: '<span class="fa fa-align-justify"></span>',
1436
+ // added: function ($el) {},
1437
+ // removed: function ($el) {}
1438
+ },
1439
+ left: {
1440
+ label: '<span class="fa fa-align-left"></span>',
1441
+ // added: function ($el) {},
1442
+ // removed: function ($el) {}
1443
+ },
1444
+ right: {
1445
+ label: '<span class="fa fa-align-right"></span>',
1446
+ // added: function ($el) {},
1447
+ // removed: function ($el) {}
1448
+ },
1449
+ grid: {
1450
+ label: '<span class="fa fa-th"></span>',
1451
+ // added: function ($el) {},
1452
+ // removed: function ($el) {}
1453
+ }
1454
+ },
1455
+ actions: {
1456
+ remove: {
1457
+ label: '<span class="fa fa-times"></span>',
1458
+ clicked: function () {
1459
+ var $event = $.Event('keydown');
1460
+
1461
+ $event.which = 8;
1462
+ $(document).trigger($event);
1463
+ }
1464
+ }
1465
+ },
1466
+ sorting: function () {
1467
+ var that = this;
1468
+
1469
+ $('.medium-insert-images').sortable({
1470
+ group: 'medium-insert-images',
1471
+ containerSelector: '.medium-insert-images',
1472
+ itemSelector: 'figure',
1473
+ placeholder: '<figure class="placeholder">',
1474
+ handle: 'img',
1475
+ nested: false,
1476
+ vertical: false,
1477
+ afterMove: function () {
1478
+ that.core.triggerInput();
1479
+ }
1480
+ });
1481
+ },
1482
+ messages: {
1483
+ acceptFileTypesError: 'This file is not in a supported format: ',
1484
+ maxFileSizeError: 'This file is too big: '
1485
+ }
1486
+ // uploadCompleted: function ($el, data) {}
1487
+ };
1488
+
1489
+ /**
1490
+ * Images object
1491
+ *
1492
+ * Sets options, variables and calls init() function
1493
+ *
1494
+ * @constructor
1495
+ * @param {DOM} el - DOM element to init the plugin on
1496
+ * @param {object} options - Options to override defaults
1497
+ * @return {void}
1498
+ */
1499
+
1500
+ function Images (el, options) {
1501
+ this.el = el;
1502
+ this.$el = $(el);
1503
+ this.$currentImage = null;
1504
+ this.templates = window.MediumInsert.Templates;
1505
+ this.core = this.$el.data('plugin_'+ pluginName);
1506
+
1507
+ this.options = $.extend(true, {}, defaults, options);
1508
+
1509
+ this._defaults = defaults;
1510
+ this._name = pluginName;
1511
+
1512
+ // Allow image preview only in browsers, that support's that
1513
+ if (this.options.preview && !window.FileReader) {
1514
+ this.options.preview = false;
1515
+ }
1516
+
1517
+ // Extend editor's functions
1518
+ if (this.core.getEditor()) {
1519
+ this.core.getEditor()._serializePreImages = this.core.getEditor().serialize;
1520
+ this.core.getEditor().serialize = this.editorSerialize;
1521
+ }
1522
+
1523
+ this.init();
1524
+ }
1525
+
1526
+ /**
1527
+ * Initialization
1528
+ *
1529
+ * @return {void}
1530
+ */
1531
+
1532
+ Images.prototype.init = function () {
1533
+ var $images = this.$el.find('.medium-insert-images');
1534
+
1535
+ $images.find('figcaption').attr('contenteditable', true);
1536
+ $images.find('figure').attr('contenteditable', false);
1537
+
1538
+ this.events();
1539
+ this.backwardsCompatibility();
1540
+ this.sorting();
1541
+ };
1542
+
1543
+ /**
1544
+ * Event listeners
1545
+ *
1546
+ * @return {void}
1547
+ */
1548
+
1549
+ Images.prototype.events = function () {
1550
+ $(document)
1551
+ .on('click', $.proxy(this, 'unselectImage'))
1552
+ .on('keydown', $.proxy(this, 'removeImage'))
1553
+ .on('click', '.medium-insert-images-toolbar .medium-editor-action', $.proxy(this, 'toolbarAction'))
1554
+ .on('click', '.medium-insert-images-toolbar2 .medium-editor-action', $.proxy(this, 'toolbar2Action'));
1555
+
1556
+ this.$el
1557
+ .on('click', '.medium-insert-images img', $.proxy(this, 'selectImage'));
1558
+ };
1559
+
1560
+ /**
1561
+ * Replace v0.* class names with new ones
1562
+ *
1563
+ * @return {void}
1564
+ */
1565
+
1566
+ Images.prototype.backwardsCompatibility = function () {
1567
+ this.$el.find('.mediumInsert')
1568
+ .removeClass('mediumInsert')
1569
+ .addClass('medium-insert-images');
1570
+
1571
+ this.$el.find('.medium-insert-images.small')
1572
+ .removeClass('small')
1573
+ .addClass('medium-insert-images-left');
1574
+ };
1575
+
1576
+ /**
1577
+ * Extend editor's serialize function
1578
+ *
1579
+ * @return {object} Serialized data
1580
+ */
1581
+
1582
+ Images.prototype.editorSerialize = function () {
1583
+ var data = this._serializePreImages();
1584
+
1585
+ $.each(data, function (key) {
1586
+ var $data = $('<div />').html(data[key].value);
1587
+
1588
+ $data.find('.medium-insert-images').find('figcaption, figure').removeAttr('contenteditable');
1589
+
1590
+ data[key].value = $data.html();
1591
+ });
1592
+
1593
+ return data;
1594
+ };
1595
+
1596
+ /**
1597
+ * Add image
1598
+ *
1599
+ * @return {void}
1600
+ */
1601
+
1602
+ Images.prototype.add = function () {
1603
+ var that = this,
1604
+ $file = $(this.templates['src/js/templates/images-fileupload.hbs']()),
1605
+ fileUploadOptions = {
1606
+ dataType: 'json',
1607
+ add: function (e, data) {
1608
+ $.proxy(that, 'uploadAdd', e, data)();
1609
+ },
1610
+ done: function (e, data) {
1611
+ $.proxy(that, 'uploadDone', e, data)();
1612
+ }
1613
+ };
1614
+
1615
+ // Only add progress callbacks for browsers that support XHR2,
1616
+ // and test for XHR2 per:
1617
+ // http://stackoverflow.com/questions/6767887/
1618
+ // what-is-the-best-way-to-check-for-xhr2-file-upload-support
1619
+ if (new XMLHttpRequest().upload) {
1620
+ fileUploadOptions.progress = function (e, data) {
1621
+ $.proxy(that, 'uploadProgress', e, data)();
1622
+ };
1623
+
1624
+ fileUploadOptions.progressall = function (e, data) {
1625
+ $.proxy(that, 'uploadProgressall', e, data)();
1626
+ };
1627
+ }
1628
+
1629
+ $file.fileupload($.extend(true, {}, this.options.fileUploadOptions, fileUploadOptions));
1630
+
1631
+ $file.click();
1632
+ };
1633
+
1634
+ /**
1635
+ * Callback invoked as soon as files are added to the fileupload widget - via file input selection, drag & drop or add API call.
1636
+ * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#add
1637
+ *
1638
+ * @param {Event} e
1639
+ * @param {object} data
1640
+ * @return {void}
1641
+ */
1642
+
1643
+ Images.prototype.uploadAdd = function (e, data) {
1644
+ var $place = this.$el.find('.medium-insert-active'),
1645
+ that = this,
1646
+ uploadErrors = [],
1647
+ file = data.files[0],
1648
+ acceptFileTypes = this.options.fileUploadOptions.acceptFileTypes,
1649
+ maxFileSize = this.options.fileUploadOptions.maxFileSize,
1650
+ reader;
1651
+
1652
+ if (acceptFileTypes && !acceptFileTypes.test(file['type'])) {
1653
+ uploadErrors.push(this.options.messages.acceptFileTypesError + file['name']);
1654
+ } else if (maxFileSize && file['size'] > maxFileSize) {
1655
+ uploadErrors.push(this.options.messages.maxFileSizeError + file['name']);
1656
+ }
1657
+ if (uploadErrors.length > 0) {
1658
+ alert(uploadErrors.join("\n"));
1659
+ return;
1660
+ }
1661
+
1662
+ this.core.hideButtons();
1663
+
1664
+ // Replace paragraph with div, because figure elements can't be inside paragraph
1665
+ if ($place.is('p')) {
1666
+ $place.replaceWith('<div class="medium-insert-active">'+ $place.html() +'</div>');
1667
+ $place = this.$el.find('.medium-insert-active');
1668
+ this.core.moveCaret($place);
1669
+ }
1670
+
1671
+ $place.addClass('medium-insert-images');
1672
+
1673
+ if (this.options.preview === false && $place.find('progress').length === 0 && (new XMLHttpRequest().upload)) {
1674
+ $place.append(this.templates['src/js/templates/images-progressbar.hbs']());
1675
+ }
1676
+
1677
+ if (data.autoUpload || (data.autoUpload !== false && $(e.target).fileupload('option', 'autoUpload'))) {
1678
+ data.process().done(function () {
1679
+ // If preview is set to true, let the showImage handle the upload start
1680
+ if (that.options.preview) {
1681
+ reader = new FileReader();
1682
+
1683
+ reader.onload = function (e) {
1684
+ $.proxy(that, 'showImage', e.target.result, data)();
1685
+ };
1686
+
1687
+ reader.readAsDataURL(data.files[0]);
1688
+ } else {
1689
+ data.submit();
1690
+ }
1691
+ });
1692
+ }
1693
+ };
1694
+
1695
+ /**
1696
+ * Callback for global upload progress events
1697
+ * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#progressall
1698
+ *
1699
+ * @param {Event} e
1700
+ * @param {object} data
1701
+ * @return {void}
1702
+ */
1703
+
1704
+ Images.prototype.uploadProgressall = function (e, data) {
1705
+ var progress, $progressbar;
1706
+
1707
+ if (this.options.preview === false) {
1708
+ progress = parseInt(data.loaded / data.total * 100, 10);
1709
+ $progressbar = this.$el.find('.medium-insert-active').find('progress');
1710
+
1711
+ $progressbar
1712
+ .attr('value', progress)
1713
+ .text(progress);
1714
+
1715
+ if (progress === 100) {
1716
+ $progressbar.remove();
1717
+ }
1718
+ }
1719
+ };
1720
+
1721
+ /**
1722
+ * Callback for upload progress events.
1723
+ * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#progress
1724
+ *
1725
+ * @param {Event} e
1726
+ * @param {object} data
1727
+ * @return {void}
1728
+ */
1729
+
1730
+ Images.prototype.uploadProgress = function (e, data) {
1731
+ var progress, $progressbar;
1732
+
1733
+ if (this.options.preview) {
1734
+ progress = 100 - parseInt(data.loaded / data.total * 100, 10);
1735
+ $progressbar = data.context.find('.medium-insert-images-progress');
1736
+
1737
+ $progressbar.css('width', progress +'%');
1738
+
1739
+ if (progress === 0) {
1740
+ $progressbar.remove();
1741
+ }
1742
+ }
1743
+ };
1744
+
1745
+ /**
1746
+ * Callback for successful upload requests.
1747
+ * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#done
1748
+ *
1749
+ * @param {Event} e
1750
+ * @param {object} data
1751
+ * @return {void}
1752
+ */
1753
+
1754
+ Images.prototype.uploadDone = function (e, data) {
1755
+ $.proxy(this, 'showImage', data.result.files[0].url, data)();
1756
+
1757
+ this.core.clean();
1758
+ this.sorting();
1759
+ };
1760
+
1761
+ /**
1762
+ * Add uploaded / preview image to DOM
1763
+ *
1764
+ * @param {string} img
1765
+ * @returns {void}
1766
+ */
1767
+
1768
+ Images.prototype.showImage = function (img, data) {
1769
+ var $place = this.$el.find('.medium-insert-active'),
1770
+ domImage,
1771
+ that;
1772
+
1773
+ // Hide editor's placeholder
1774
+ $place.click();
1775
+
1776
+ // If preview is allowed and preview image already exists,
1777
+ // replace it with uploaded image
1778
+ that = this;
1779
+ if (this.options.preview && data.context) {
1780
+ domImage = this.getDOMImage();
1781
+ domImage.onload = function () {
1782
+ data.context.find('img').attr('src', domImage.src);
1783
+
1784
+ if (this.options.uploadCompleted) {
1785
+ this.options.uploadCompleted(data.context, data);
1786
+ }
1787
+
1788
+ that.core.triggerInput();
1789
+ }.bind(this);
1790
+ domImage.src = img;
1791
+ } else {
1792
+ data.context = $(this.templates['src/js/templates/images-image.hbs']({
1793
+ img: img,
1794
+ progress: this.options.preview
1795
+ })).appendTo($place);
1796
+
1797
+ $place.find('br').remove();
1798
+
1799
+ if (this.options.autoGrid && $place.find('figure').length >= this.options.autoGrid) {
1800
+ $.each(this.options.styles, function (style, options) {
1801
+ var className = 'medium-insert-images-'+ style;
1802
+
1803
+ $place.removeClass(className);
1804
+
1805
+ if (options.removed) {
1806
+ options.removed($place);
1807
+ }
1808
+ });
1809
+
1810
+ $place.addClass('medium-insert-images-grid');
1811
+
1812
+ if (this.options.styles.grid.added) {
1813
+ this.options.styles.grid.added($place);
1814
+ }
1815
+ }
1816
+
1817
+ if (this.options.preview) {
1818
+ data.submit();
1819
+ } else if (this.options.uploadCompleted) {
1820
+ this.options.uploadCompleted(data.context, data);
1821
+ }
1822
+ }
1823
+
1824
+ this.core.triggerInput();
1825
+
1826
+ return data.context;
1827
+ };
1828
+
1829
+ Images.prototype.getDOMImage = function () {
1830
+ return new window.Image();
1831
+ };
1832
+
1833
+ /**
1834
+ * Select clicked image
1835
+ *
1836
+ * @param {Event} e
1837
+ * @returns {void}
1838
+ */
1839
+
1840
+ Images.prototype.selectImage = function (e) {
1841
+ if(this.core.options.enabled) {
1842
+ var $image = $(e.target),
1843
+ that = this;
1844
+
1845
+ this.$currentImage = $image;
1846
+
1847
+ // Hide keyboard on mobile devices
1848
+ this.$el.blur();
1849
+
1850
+ $image.addClass('medium-insert-image-active');
1851
+ $image.closest('.medium-insert-images').addClass('medium-insert-active');
1852
+
1853
+ setTimeout(function () {
1854
+ that.addToolbar();
1855
+
1856
+ if (that.options.captions) {
1857
+ that.core.addCaption($image.closest('figure'), that.options.captionPlaceholder);
1858
+ }
1859
+ }, 50);
1860
+ }
1861
+ };
1862
+
1863
+ /**
1864
+ * Unselect selected image
1865
+ *
1866
+ * @param {Event} e
1867
+ * @returns {void}
1868
+ */
1869
+
1870
+ Images.prototype.unselectImage = function (e) {
1871
+ var $el = $(e.target),
1872
+ $image = this.$el.find('.medium-insert-image-active');
1873
+
1874
+ if ($el.is('img') && $el.hasClass('medium-insert-image-active')) {
1875
+ $image.not($el).removeClass('medium-insert-image-active');
1876
+ $('.medium-insert-images-toolbar, .medium-insert-images-toolbar2').remove();
1877
+ this.core.removeCaptions($el);
1878
+ return;
1879
+ }
1880
+
1881
+ $image.removeClass('medium-insert-image-active');
1882
+ $('.medium-insert-images-toolbar, .medium-insert-images-toolbar2').remove();
1883
+
1884
+ if ($el.is('.medium-insert-caption-placeholder')) {
1885
+ this.core.removeCaptionPlaceholder($image.closest('figure'));
1886
+ } else if ($el.is('figcaption') === false) {
1887
+ this.core.removeCaptions();
1888
+ }
1889
+ this.$currentImage = null;
1890
+ };
1891
+
1892
+ /**
1893
+ * Remove image
1894
+ *
1895
+ * @param {Event} e
1896
+ * @returns {void}
1897
+ */
1898
+
1899
+ Images.prototype.removeImage = function (e) {
1900
+ var $image, $parent, $empty;
1901
+
1902
+ if (e.which === 8 || e.which === 46) {
1903
+ $image = this.$el.find('.medium-insert-image-active');
1904
+
1905
+ if ($image.length) {
1906
+ e.preventDefault();
1907
+
1908
+ this.deleteFile($image.attr('src'));
1909
+
1910
+ $parent = $image.closest('.medium-insert-images');
1911
+ $image.closest('figure').remove();
1912
+
1913
+ $('.medium-insert-images-toolbar, .medium-insert-images-toolbar2').remove();
1914
+
1915
+ if ($parent.find('figure').length === 0) {
1916
+ $empty = $parent.next();
1917
+ if ($empty.is('p') === false || $empty.text() !== '') {
1918
+ $empty = $(this.templates['src/js/templates/core-empty-line.hbs']().trim());
1919
+ $parent.before($empty);
1920
+ }
1921
+ $parent.remove();
1922
+
1923
+ // Hide addons
1924
+ this.core.hideAddons();
1925
+
1926
+ this.core.moveCaret($empty);
1927
+ }
1928
+
1929
+ this.core.triggerInput();
1930
+ }
1931
+ }
1932
+ };
1933
+
1934
+ /**
1935
+ * Makes ajax call to deleteScript
1936
+ *
1937
+ * @param {String} file File name
1938
+ * @returns {void}
1939
+ */
1940
+
1941
+ Images.prototype.deleteFile = function (file) {
1942
+ if (this.options.deleteScript) {
1943
+ // If deleteMethod is somehow undefined, defaults to POST
1944
+ var method = this.options.deleteMethod || 'POST';
1945
+
1946
+ $.ajax($.extend(true, {}, {
1947
+ url: this.options.deleteScript,
1948
+ type: method,
1949
+ data: { file: file }
1950
+ }, this.options.fileDeleteOptions));
1951
+ }
1952
+ };
1953
+
1954
+ /**
1955
+ * Adds image toolbar to editor
1956
+ *
1957
+ * @returns {void}
1958
+ */
1959
+
1960
+ Images.prototype.addToolbar = function () {
1961
+ var $image = this.$el.find('.medium-insert-image-active'),
1962
+ $p = $image.closest('.medium-insert-images'),
1963
+ active = false,
1964
+ $toolbar, $toolbar2, top;
1965
+
1966
+ var mediumEditor = this.core.getEditor();
1967
+ var toolbarContainer = mediumEditor.options.elementsContainer || 'body';
1968
+
1969
+ $(toolbarContainer).append(this.templates['src/js/templates/images-toolbar.hbs']({
1970
+ styles: this.options.styles,
1971
+ actions: this.options.actions
1972
+ }).trim());
1973
+
1974
+ $toolbar = $('.medium-insert-images-toolbar');
1975
+ $toolbar2 = $('.medium-insert-images-toolbar2');
1976
+
1977
+ top = $image.offset().top - $toolbar.height() - 8 - 2 - 5; // 8px - hight of an arrow under toolbar, 2px - height of an image outset, 5px - distance from an image
1978
+ if (top < 0) {
1979
+ top = 0;
1980
+ }
1981
+
1982
+ $toolbar
1983
+ .css({
1984
+ top: top,
1985
+ left: $image.offset().left + $image.width() / 2 - $toolbar.width() / 2
1986
+ })
1987
+ .show();
1988
+
1989
+ $toolbar2
1990
+ .css({
1991
+ top: $image.offset().top + 2, // 2px - distance from a border
1992
+ left: $image.offset().left + $image.width() - $toolbar2.width() - 4 // 4px - distance from a border
1993
+ })
1994
+ .show();
1995
+
1996
+ $toolbar.find('button').each(function () {
1997
+ if ($p.hasClass('medium-insert-images-'+ $(this).data('action'))) {
1998
+ $(this).addClass('medium-editor-button-active');
1999
+ active = true;
2000
+ }
2001
+ });
2002
+
2003
+ if (active === false) {
2004
+ $toolbar.find('button').first().addClass('medium-editor-button-active');
2005
+ }
2006
+ };
2007
+
2008
+ /**
2009
+ * Fires toolbar action
2010
+ *
2011
+ * @param {Event} e
2012
+ * @returns {void}
2013
+ */
2014
+
2015
+ Images.prototype.toolbarAction = function (e) {
2016
+ if (this.$currentImage === null) return;
2017
+ var $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button'),
2018
+ $li = $button.closest('li'),
2019
+ $ul = $li.closest('ul'),
2020
+ $lis = $ul.find('li'),
2021
+ $p = this.$el.find('.medium-insert-active'),
2022
+ that = this;
2023
+
2024
+ $button.addClass('medium-editor-button-active');
2025
+ $li.siblings().find('.medium-editor-button-active').removeClass('medium-editor-button-active');
2026
+
2027
+ $lis.find('button').each(function () {
2028
+ var className = 'medium-insert-images-'+ $(this).data('action');
2029
+
2030
+ if ($(this).hasClass('medium-editor-button-active')) {
2031
+ $p.addClass(className);
2032
+
2033
+ if (that.options.styles[$(this).data('action')].added) {
2034
+ that.options.styles[$(this).data('action')].added($p);
2035
+ }
2036
+ } else {
2037
+ $p.removeClass(className);
2038
+
2039
+ if (that.options.styles[$(this).data('action')].removed) {
2040
+ that.options.styles[$(this).data('action')].removed($p);
2041
+ }
2042
+ }
2043
+ });
2044
+
2045
+ this.core.hideButtons();
2046
+
2047
+ this.core.triggerInput();
2048
+ };
2049
+
2050
+ /**
2051
+ * Fires toolbar2 action
2052
+ *
2053
+ * @param {Event} e
2054
+ * @returns {void}
2055
+ */
2056
+
2057
+ Images.prototype.toolbar2Action = function (e) {
2058
+ if (this.$currentImage === null) return;
2059
+ var $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button'),
2060
+ callback = this.options.actions[$button.data('action')].clicked;
2061
+
2062
+ if (callback) {
2063
+ callback(this.$el.find('.medium-insert-image-active'));
2064
+ }
2065
+
2066
+ this.core.hideButtons();
2067
+
2068
+ this.core.triggerInput();
2069
+ };
2070
+
2071
+ /**
2072
+ * Initialize sorting
2073
+ *
2074
+ * @returns {void}
2075
+ */
2076
+
2077
+ Images.prototype.sorting = function () {
2078
+ $.proxy(this.options.sorting, this)();
2079
+ };
2080
+
2081
+ /** Plugin initialization */
2082
+
2083
+ $.fn[pluginName + addonName] = function (options) {
2084
+ return this.each(function () {
2085
+ if (!$.data(this, 'plugin_' + pluginName + addonName)) {
2086
+ $.data(this, 'plugin_' + pluginName + addonName, new Images(this, options));
2087
+ }
2088
+ });
2089
+ };
2090
+
2091
+ })(jQuery, window, document, MediumEditor.util);