ui_bibz 2.0.0.alpha12 → 2.0.0.alpha13
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/CONTRIBUTORS.md +2 -1
- data/Gemfile.lock +62 -56
- data/app/assets/javascripts/ui_bibz.coffee +4 -0
- data/app/assets/stylesheets/ui_bibz.sass +2 -0
- data/app/inputs/custom_inputs/markdown_editor_input.rb +11 -0
- data/lib/ui_bibz.rb +1 -0
- data/lib/ui_bibz/helpers/ui_core_helper.rb +4 -0
- data/lib/ui_bibz/ui/core/input/autocomplete_field.rb +1 -1
- data/lib/ui_bibz/ui/core/input/date_picker_field.rb +1 -1
- data/lib/ui_bibz/ui/core/input/dropdown_select_field.rb +1 -1
- data/lib/ui_bibz/ui/core/input/formula_field.rb +1 -1
- data/lib/ui_bibz/ui/core/input/markdown_editor_field.rb +112 -0
- data/lib/ui_bibz/ui/core/input/multi_column_field.rb +1 -1
- data/lib/ui_bibz/ui/core/input/multi_select_field.rb +1 -1
- data/lib/ui_bibz/ui/core/input/surround_field.rb +1 -1
- data/lib/ui_bibz/ui/core/list/components/list.rb +1 -1
- data/lib/ui_bibz/version.rb +1 -1
- data/test/dummy/config/environments/test.rb +2 -2
- data/test/ui/inputs_test.rb +8 -0
- data/vendor/assets/javascripts/bootstrap-markdown.js +1470 -0
- data/vendor/assets/javascripts/marked.js +1272 -0
- data/vendor/assets/javascripts/to-markdown.js +785 -0
- data/vendor/assets/stylesheets/bootstrap-markdown.min.css +1 -0
- metadata +8 -2
data/lib/ui_bibz/version.rb
CHANGED
@@ -13,8 +13,8 @@ Rails.application.configure do
|
|
13
13
|
config.eager_load = false
|
14
14
|
|
15
15
|
# Configure static file server for tests with Cache-Control for performance.
|
16
|
-
config.
|
17
|
-
config.
|
16
|
+
config.public_file_server.enabled = true
|
17
|
+
config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
|
18
18
|
|
19
19
|
# Show full error reports and disable caching.
|
20
20
|
config.consider_all_requests_local = true
|
data/test/ui/inputs_test.rb
CHANGED
@@ -93,4 +93,12 @@ class InputsTest < ActionView::TestCase
|
|
93
93
|
|
94
94
|
assert_equal expected, actual
|
95
95
|
end
|
96
|
+
|
97
|
+
test 'markdown_editor' do
|
98
|
+
actual = UiBibz::Ui::Core::MarkdownEditorField.new('comments').render
|
99
|
+
expected = "<textarea name=\"comments\" id=\"comments\" data-provide=\"markdown\" data-iconlibrary=\"fa\">
|
100
|
+
</textarea>"
|
101
|
+
|
102
|
+
assert_equal expected, actual
|
103
|
+
end
|
96
104
|
end
|
@@ -0,0 +1,1470 @@
|
|
1
|
+
/* ===================================================
|
2
|
+
* bootstrap-markdown.js v2.10.0
|
3
|
+
* http://github.com/toopay/bootstrap-markdown
|
4
|
+
* ===================================================
|
5
|
+
* Copyright 2013-2016 Taufan Aditya
|
6
|
+
*
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
* you may not use this file except in compliance with the License.
|
9
|
+
* You may obtain a copy of the License at
|
10
|
+
*
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
*
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
* See the License for the specific language governing permissions and
|
17
|
+
* limitations under the License.
|
18
|
+
* ========================================================== */
|
19
|
+
(function(factory) {
|
20
|
+
if (typeof define === "function" && define.amd) {
|
21
|
+
//RequireJS
|
22
|
+
define(["jquery"], factory);
|
23
|
+
} else if (typeof exports === 'object') {
|
24
|
+
//Backbone.js
|
25
|
+
factory(require('jquery'));
|
26
|
+
} else {
|
27
|
+
//Jquery plugin
|
28
|
+
factory(jQuery);
|
29
|
+
}
|
30
|
+
}(function($) {
|
31
|
+
"use strict";
|
32
|
+
|
33
|
+
/* MARKDOWN CLASS DEFINITION
|
34
|
+
* ========================== */
|
35
|
+
|
36
|
+
var Markdown = function(element, options) {
|
37
|
+
// @TODO : remove this BC on next major release
|
38
|
+
// @see : https://github.com/toopay/bootstrap-markdown/issues/109
|
39
|
+
var opts = ['autofocus', 'savable', 'hideable', 'width',
|
40
|
+
'height', 'resize', 'iconlibrary', 'language',
|
41
|
+
'footer', 'fullscreen', 'hiddenButtons', 'disabledButtons'
|
42
|
+
];
|
43
|
+
$.each(opts, function(_, opt) {
|
44
|
+
if (typeof $(element).data(opt) !== 'undefined') {
|
45
|
+
options = typeof options == 'object' ? options : {};
|
46
|
+
options[opt] = $(element).data(opt);
|
47
|
+
}
|
48
|
+
});
|
49
|
+
// End BC
|
50
|
+
|
51
|
+
// Class Properties
|
52
|
+
this.$ns = 'bootstrap-markdown';
|
53
|
+
this.$element = $(element);
|
54
|
+
this.$editable = {
|
55
|
+
el: null,
|
56
|
+
type: null,
|
57
|
+
attrKeys: [],
|
58
|
+
attrValues: [],
|
59
|
+
content: null
|
60
|
+
};
|
61
|
+
this.$options = $.extend(true, {}, $.fn.markdown.defaults, options, this.$element.data('options'));
|
62
|
+
this.$oldContent = null;
|
63
|
+
this.$isPreview = false;
|
64
|
+
this.$isFullscreen = false;
|
65
|
+
this.$editor = null;
|
66
|
+
this.$textarea = null;
|
67
|
+
this.$handler = [];
|
68
|
+
this.$callback = [];
|
69
|
+
this.$nextTab = [];
|
70
|
+
|
71
|
+
this.showEditor();
|
72
|
+
};
|
73
|
+
|
74
|
+
Markdown.prototype = {
|
75
|
+
|
76
|
+
constructor: Markdown,
|
77
|
+
__alterButtons: function(name, alter) {
|
78
|
+
var handler = this.$handler,
|
79
|
+
isAll = (name == 'all'),
|
80
|
+
that = this;
|
81
|
+
|
82
|
+
$.each(handler, function(k, v) {
|
83
|
+
var halt = true;
|
84
|
+
if (isAll) {
|
85
|
+
halt = false;
|
86
|
+
} else {
|
87
|
+
halt = v.indexOf(name) < 0;
|
88
|
+
}
|
89
|
+
|
90
|
+
if (halt === false) {
|
91
|
+
alter(that.$editor.find('button[data-handler="' + v + '"]'));
|
92
|
+
}
|
93
|
+
});
|
94
|
+
},
|
95
|
+
__buildButtons: function(buttonsArray, container) {
|
96
|
+
var i,
|
97
|
+
ns = this.$ns,
|
98
|
+
handler = this.$handler,
|
99
|
+
callback = this.$callback;
|
100
|
+
|
101
|
+
for (i = 0; i < buttonsArray.length; i++) {
|
102
|
+
// Build each group container
|
103
|
+
var y, btnGroups = buttonsArray[i];
|
104
|
+
for (y = 0; y < btnGroups.length; y++) {
|
105
|
+
// Build each button group
|
106
|
+
var z,
|
107
|
+
buttons = btnGroups[y].data,
|
108
|
+
btnGroupContainer = $('<div/>', {
|
109
|
+
'class': 'btn-group'
|
110
|
+
});
|
111
|
+
|
112
|
+
for (z = 0; z < buttons.length; z++) {
|
113
|
+
var button = buttons[z],
|
114
|
+
buttonContainer, buttonIconContainer,
|
115
|
+
buttonHandler = ns + '-' + button.name,
|
116
|
+
buttonIcon = this.__getIcon(button.icon),
|
117
|
+
btnText = button.btnText ? button.btnText : '',
|
118
|
+
btnClass = button.btnClass ? button.btnClass : 'btn',
|
119
|
+
tabIndex = button.tabIndex ? button.tabIndex : '-1',
|
120
|
+
hotkey = typeof button.hotkey !== 'undefined' ? button.hotkey : '',
|
121
|
+
hotkeyCaption = typeof jQuery.hotkeys !== 'undefined' && hotkey !== '' ? ' (' + hotkey + ')' : '';
|
122
|
+
|
123
|
+
// Construct the button object
|
124
|
+
buttonContainer = $('<button></button>');
|
125
|
+
buttonContainer.text(' ' + this.__localize(btnText)).addClass('btn-default btn-sm').addClass(btnClass);
|
126
|
+
if (btnClass.match(/btn\-(primary|success|info|warning|danger|link)/)) {
|
127
|
+
buttonContainer.removeClass('btn-default');
|
128
|
+
}
|
129
|
+
buttonContainer.attr({
|
130
|
+
'type': 'button',
|
131
|
+
'title': this.__localize(button.title) + hotkeyCaption,
|
132
|
+
'tabindex': tabIndex,
|
133
|
+
'data-provider': ns,
|
134
|
+
'data-handler': buttonHandler,
|
135
|
+
'data-hotkey': hotkey
|
136
|
+
});
|
137
|
+
if (button.toggle === true) {
|
138
|
+
buttonContainer.attr('data-toggle', 'button');
|
139
|
+
}
|
140
|
+
buttonIconContainer = $('<span/>');
|
141
|
+
buttonIconContainer.addClass(buttonIcon);
|
142
|
+
buttonIconContainer.prependTo(buttonContainer);
|
143
|
+
|
144
|
+
// Attach the button object
|
145
|
+
btnGroupContainer.append(buttonContainer);
|
146
|
+
|
147
|
+
// Register handler and callback
|
148
|
+
handler.push(buttonHandler);
|
149
|
+
callback.push(button.callback);
|
150
|
+
}
|
151
|
+
|
152
|
+
// Attach the button group into container dom
|
153
|
+
container.append(btnGroupContainer);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
return container;
|
158
|
+
},
|
159
|
+
__setListener: function() {
|
160
|
+
// Set size and resizable Properties
|
161
|
+
var hasRows = typeof this.$textarea.attr('rows') !== 'undefined',
|
162
|
+
maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5',
|
163
|
+
rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows;
|
164
|
+
|
165
|
+
this.$textarea.attr('rows', rowsVal);
|
166
|
+
if (this.$options.resize) {
|
167
|
+
this.$textarea.css('resize', this.$options.resize);
|
168
|
+
}
|
169
|
+
|
170
|
+
this.$textarea.on({
|
171
|
+
'focus': $.proxy(this.focus, this),
|
172
|
+
'keyup': $.proxy(this.keyup, this),
|
173
|
+
'change': $.proxy(this.change, this),
|
174
|
+
'select': $.proxy(this.select, this)
|
175
|
+
});
|
176
|
+
|
177
|
+
if (this.eventSupported('keydown')) {
|
178
|
+
this.$textarea.on('keydown', $.proxy(this.keydown, this));
|
179
|
+
}
|
180
|
+
|
181
|
+
if (this.eventSupported('keypress')) {
|
182
|
+
this.$textarea.on('keypress', $.proxy(this.keypress, this));
|
183
|
+
}
|
184
|
+
|
185
|
+
// Re-attach markdown data
|
186
|
+
this.$textarea.data('markdown', this);
|
187
|
+
},
|
188
|
+
__handle: function(e) {
|
189
|
+
var target = $(e.currentTarget),
|
190
|
+
handler = this.$handler,
|
191
|
+
callback = this.$callback,
|
192
|
+
handlerName = target.attr('data-handler'),
|
193
|
+
callbackIndex = handler.indexOf(handlerName),
|
194
|
+
callbackHandler = callback[callbackIndex];
|
195
|
+
|
196
|
+
// Trigger the focusin
|
197
|
+
$(e.currentTarget).focus();
|
198
|
+
|
199
|
+
callbackHandler(this);
|
200
|
+
|
201
|
+
// Trigger onChange for each button handle
|
202
|
+
this.change(this);
|
203
|
+
|
204
|
+
// Unless it was the save handler,
|
205
|
+
// focusin the textarea
|
206
|
+
if (handlerName.indexOf('cmdSave') < 0) {
|
207
|
+
this.$textarea.focus();
|
208
|
+
}
|
209
|
+
|
210
|
+
e.preventDefault();
|
211
|
+
},
|
212
|
+
__localize: function(string) {
|
213
|
+
var messages = $.fn.markdown.messages,
|
214
|
+
language = this.$options.language;
|
215
|
+
if (
|
216
|
+
typeof messages !== 'undefined' &&
|
217
|
+
typeof messages[language] !== 'undefined' &&
|
218
|
+
typeof messages[language][string] !== 'undefined'
|
219
|
+
) {
|
220
|
+
return messages[language][string];
|
221
|
+
}
|
222
|
+
return string;
|
223
|
+
},
|
224
|
+
__getIcon: function(src) {
|
225
|
+
return typeof src == 'object' ? src[this.$options.iconlibrary] : src;
|
226
|
+
},
|
227
|
+
setFullscreen: function(mode) {
|
228
|
+
var $editor = this.$editor,
|
229
|
+
$textarea = this.$textarea;
|
230
|
+
|
231
|
+
if (mode === true) {
|
232
|
+
$editor.addClass('md-fullscreen-mode');
|
233
|
+
$('body').addClass('md-nooverflow');
|
234
|
+
this.$options.onFullscreen(this);
|
235
|
+
} else {
|
236
|
+
$editor.removeClass('md-fullscreen-mode');
|
237
|
+
$('body').removeClass('md-nooverflow');
|
238
|
+
this.$options.onFullscreenExit(this);
|
239
|
+
|
240
|
+
if (this.$isPreview === true)
|
241
|
+
this.hidePreview().showPreview();
|
242
|
+
}
|
243
|
+
|
244
|
+
this.$isFullscreen = mode;
|
245
|
+
$textarea.focus();
|
246
|
+
},
|
247
|
+
showEditor: function() {
|
248
|
+
var instance = this,
|
249
|
+
textarea,
|
250
|
+
ns = this.$ns,
|
251
|
+
container = this.$element,
|
252
|
+
originalHeigth = container.css('height'),
|
253
|
+
originalWidth = container.css('width'),
|
254
|
+
editable = this.$editable,
|
255
|
+
handler = this.$handler,
|
256
|
+
callback = this.$callback,
|
257
|
+
options = this.$options,
|
258
|
+
editor = $('<div/>', {
|
259
|
+
'class': 'md-editor',
|
260
|
+
click: function() {
|
261
|
+
instance.focus();
|
262
|
+
}
|
263
|
+
});
|
264
|
+
|
265
|
+
// Prepare the editor
|
266
|
+
if (this.$editor === null) {
|
267
|
+
// Create the panel
|
268
|
+
var editorHeader = $('<div/>', {
|
269
|
+
'class': 'md-header btn-toolbar'
|
270
|
+
});
|
271
|
+
|
272
|
+
// Merge the main & additional button groups together
|
273
|
+
var allBtnGroups = [];
|
274
|
+
if (options.buttons.length > 0) allBtnGroups = allBtnGroups.concat(options.buttons[0]);
|
275
|
+
if (options.additionalButtons.length > 0) {
|
276
|
+
// iterate the additional button groups
|
277
|
+
$.each(options.additionalButtons[0], function(idx, buttonGroup) {
|
278
|
+
|
279
|
+
// see if the group name of the addional group matches an existing group
|
280
|
+
var matchingGroups = $.grep(allBtnGroups, function(allButtonGroup, allIdx) {
|
281
|
+
return allButtonGroup.name === buttonGroup.name;
|
282
|
+
});
|
283
|
+
|
284
|
+
// if it matches add the addional buttons to that group, if not just add it to the all buttons group
|
285
|
+
if (matchingGroups.length > 0) {
|
286
|
+
matchingGroups[0].data = matchingGroups[0].data.concat(buttonGroup.data);
|
287
|
+
} else {
|
288
|
+
allBtnGroups.push(options.additionalButtons[0][idx]);
|
289
|
+
}
|
290
|
+
|
291
|
+
});
|
292
|
+
}
|
293
|
+
|
294
|
+
// Reduce and/or reorder the button groups
|
295
|
+
if (options.reorderButtonGroups.length > 0) {
|
296
|
+
allBtnGroups = allBtnGroups
|
297
|
+
.filter(function(btnGroup) {
|
298
|
+
return options.reorderButtonGroups.indexOf(btnGroup.name) > -1;
|
299
|
+
})
|
300
|
+
.sort(function(a, b) {
|
301
|
+
if (options.reorderButtonGroups.indexOf(a.name) < options.reorderButtonGroups.indexOf(b.name)) return -1;
|
302
|
+
if (options.reorderButtonGroups.indexOf(a.name) > options.reorderButtonGroups.indexOf(b.name)) return 1;
|
303
|
+
return 0;
|
304
|
+
});
|
305
|
+
}
|
306
|
+
|
307
|
+
// Build the buttons
|
308
|
+
if (allBtnGroups.length > 0) {
|
309
|
+
editorHeader = this.__buildButtons([allBtnGroups], editorHeader);
|
310
|
+
}
|
311
|
+
|
312
|
+
if (options.fullscreen.enable) {
|
313
|
+
editorHeader.append('<div class="md-controls"><a class="md-control md-control-fullscreen" href="#"><span class="' + this.__getIcon(options.fullscreen.icons.fullscreenOn) + '"></span></a></div>').on('click', '.md-control-fullscreen', function(e) {
|
314
|
+
e.preventDefault();
|
315
|
+
instance.setFullscreen(true);
|
316
|
+
});
|
317
|
+
}
|
318
|
+
|
319
|
+
editor.append(editorHeader);
|
320
|
+
|
321
|
+
// Wrap the textarea
|
322
|
+
if (container.is('textarea')) {
|
323
|
+
container.before(editor);
|
324
|
+
textarea = container;
|
325
|
+
textarea.addClass('md-input');
|
326
|
+
editor.append(textarea);
|
327
|
+
} else {
|
328
|
+
var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(),
|
329
|
+
currentContent = $.trim(rawContent);
|
330
|
+
|
331
|
+
// This is some arbitrary content that could be edited
|
332
|
+
textarea = $('<textarea/>', {
|
333
|
+
'class': 'md-input',
|
334
|
+
'val': currentContent
|
335
|
+
});
|
336
|
+
|
337
|
+
editor.append(textarea);
|
338
|
+
|
339
|
+
// Save the editable
|
340
|
+
editable.el = container;
|
341
|
+
editable.type = container.prop('tagName').toLowerCase();
|
342
|
+
editable.content = container.html();
|
343
|
+
|
344
|
+
$(container[0].attributes).each(function() {
|
345
|
+
editable.attrKeys.push(this.nodeName);
|
346
|
+
editable.attrValues.push(this.nodeValue);
|
347
|
+
});
|
348
|
+
|
349
|
+
// Set editor to blocked the original container
|
350
|
+
container.replaceWith(editor);
|
351
|
+
}
|
352
|
+
|
353
|
+
var editorFooter = $('<div/>', {
|
354
|
+
'class': 'md-footer'
|
355
|
+
}),
|
356
|
+
createFooter = false,
|
357
|
+
footer = '';
|
358
|
+
// Create the footer if savable
|
359
|
+
if (options.savable) {
|
360
|
+
createFooter = true;
|
361
|
+
var saveHandler = 'cmdSave';
|
362
|
+
|
363
|
+
// Register handler and callback
|
364
|
+
handler.push(saveHandler);
|
365
|
+
callback.push(options.onSave);
|
366
|
+
|
367
|
+
editorFooter.append('<button class="btn btn-success" data-provider="' +
|
368
|
+
ns +
|
369
|
+
'" data-handler="' +
|
370
|
+
saveHandler +
|
371
|
+
'"><i class="icon icon-white icon-ok"></i> ' +
|
372
|
+
this.__localize('Save') +
|
373
|
+
'</button>');
|
374
|
+
|
375
|
+
|
376
|
+
}
|
377
|
+
|
378
|
+
footer = typeof options.footer === 'function' ? options.footer(this) : options.footer;
|
379
|
+
|
380
|
+
if ($.trim(footer) !== '') {
|
381
|
+
createFooter = true;
|
382
|
+
editorFooter.append(footer);
|
383
|
+
}
|
384
|
+
|
385
|
+
if (createFooter) editor.append(editorFooter);
|
386
|
+
|
387
|
+
// Set width
|
388
|
+
if (options.width && options.width !== 'inherit') {
|
389
|
+
if (jQuery.isNumeric(options.width)) {
|
390
|
+
editor.css('display', 'table');
|
391
|
+
textarea.css('width', options.width + 'px');
|
392
|
+
} else {
|
393
|
+
editor.addClass(options.width);
|
394
|
+
}
|
395
|
+
}
|
396
|
+
|
397
|
+
// Set height
|
398
|
+
if (options.height && options.height !== 'inherit') {
|
399
|
+
if (jQuery.isNumeric(options.height)) {
|
400
|
+
var height = options.height;
|
401
|
+
if (editorHeader) height = Math.max(0, height - editorHeader.outerHeight());
|
402
|
+
if (editorFooter) height = Math.max(0, height - editorFooter.outerHeight());
|
403
|
+
textarea.css('height', height + 'px');
|
404
|
+
} else {
|
405
|
+
editor.addClass(options.height);
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
409
|
+
// Reference
|
410
|
+
this.$editor = editor;
|
411
|
+
this.$textarea = textarea;
|
412
|
+
this.$editable = editable;
|
413
|
+
this.$oldContent = this.getContent();
|
414
|
+
|
415
|
+
this.__setListener();
|
416
|
+
|
417
|
+
// Set editor attributes, data short-hand API and listener
|
418
|
+
this.$editor.attr('id', (new Date()).getTime());
|
419
|
+
this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this));
|
420
|
+
|
421
|
+
if (this.$element.is(':disabled') || this.$element.is('[readonly]')) {
|
422
|
+
this.$editor.addClass('md-editor-disabled');
|
423
|
+
this.disableButtons('all');
|
424
|
+
}
|
425
|
+
|
426
|
+
if (this.eventSupported('keydown') && typeof jQuery.hotkeys === 'object') {
|
427
|
+
editorHeader.find('[data-provider="bootstrap-markdown"]').each(function() {
|
428
|
+
var $button = $(this),
|
429
|
+
hotkey = $button.attr('data-hotkey');
|
430
|
+
if (hotkey.toLowerCase() !== '') {
|
431
|
+
textarea.bind('keydown', hotkey, function() {
|
432
|
+
$button.trigger('click');
|
433
|
+
return false;
|
434
|
+
});
|
435
|
+
}
|
436
|
+
});
|
437
|
+
}
|
438
|
+
|
439
|
+
if (options.initialstate === 'preview') {
|
440
|
+
this.showPreview();
|
441
|
+
} else if (options.initialstate === 'fullscreen' && options.fullscreen.enable) {
|
442
|
+
this.setFullscreen(true);
|
443
|
+
}
|
444
|
+
|
445
|
+
} else {
|
446
|
+
this.$editor.show();
|
447
|
+
}
|
448
|
+
|
449
|
+
if (options.autofocus) {
|
450
|
+
this.$textarea.focus();
|
451
|
+
this.$editor.addClass('active');
|
452
|
+
}
|
453
|
+
|
454
|
+
if (options.fullscreen.enable && options.fullscreen !== false) {
|
455
|
+
this.$editor.append('<div class="md-fullscreen-controls">' +
|
456
|
+
'<a href="#" class="exit-fullscreen" title="Exit fullscreen"><span class="' + this.__getIcon(options.fullscreen.icons.fullscreenOff) + '">' +
|
457
|
+
'</span></a>' +
|
458
|
+
'</div>');
|
459
|
+
this.$editor.on('click', '.exit-fullscreen', function(e) {
|
460
|
+
e.preventDefault();
|
461
|
+
instance.setFullscreen(false);
|
462
|
+
});
|
463
|
+
}
|
464
|
+
|
465
|
+
// hide hidden buttons from options
|
466
|
+
this.hideButtons(options.hiddenButtons);
|
467
|
+
|
468
|
+
// disable disabled buttons from options
|
469
|
+
this.disableButtons(options.disabledButtons);
|
470
|
+
|
471
|
+
// enable dropZone if available and configured
|
472
|
+
if (options.dropZoneOptions) {
|
473
|
+
if (this.$editor.dropzone) {
|
474
|
+
options.dropZoneOptions.init = function() {
|
475
|
+
var caretPos = 0;
|
476
|
+
this.on('drop', function(e) {
|
477
|
+
caretPos = textarea.prop('selectionStart');
|
478
|
+
});
|
479
|
+
this.on('success', function(file, path) {
|
480
|
+
var text = textarea.val();
|
481
|
+
textarea.val(text.substring(0, caretPos) + '\n\n' + text.substring(caretPos));
|
482
|
+
});
|
483
|
+
this.on('error', function(file, error, xhr) {
|
484
|
+
console.log('Error:', error);
|
485
|
+
});
|
486
|
+
};
|
487
|
+
this.$textarea.addClass('dropzone');
|
488
|
+
this.$editor.dropzone(options.dropZoneOptions);
|
489
|
+
} else {
|
490
|
+
console.log('dropZoneOptions was configured, but DropZone was not detected.');
|
491
|
+
}
|
492
|
+
}
|
493
|
+
|
494
|
+
// Trigger the onShow hook
|
495
|
+
options.onShow(this);
|
496
|
+
|
497
|
+
return this;
|
498
|
+
},
|
499
|
+
parseContent: function(val) {
|
500
|
+
var content;
|
501
|
+
|
502
|
+
// parse with supported markdown parser
|
503
|
+
val = val || this.$textarea.val();
|
504
|
+
|
505
|
+
if (this.$options.parser) {
|
506
|
+
content = this.$options.parser(val);
|
507
|
+
} else if (typeof markdown == 'object') {
|
508
|
+
content = markdown.toHTML(val);
|
509
|
+
} else if (typeof marked == 'function') {
|
510
|
+
content = marked(val);
|
511
|
+
} else {
|
512
|
+
content = val;
|
513
|
+
}
|
514
|
+
|
515
|
+
return content;
|
516
|
+
},
|
517
|
+
showPreview: function() {
|
518
|
+
var options = this.$options,
|
519
|
+
container = this.$textarea,
|
520
|
+
afterContainer = container.next(),
|
521
|
+
replacementContainer = $('<div/>', {
|
522
|
+
'class': 'md-preview',
|
523
|
+
'data-provider': 'markdown-preview'
|
524
|
+
}),
|
525
|
+
content,
|
526
|
+
callbackContent;
|
527
|
+
|
528
|
+
if (this.$isPreview === true) {
|
529
|
+
// Avoid sequenced element creation on missused scenario
|
530
|
+
// @see https://github.com/toopay/bootstrap-markdown/issues/170
|
531
|
+
return this;
|
532
|
+
}
|
533
|
+
|
534
|
+
// Give flag that tell the editor enter preview mode
|
535
|
+
this.$isPreview = true;
|
536
|
+
// Disable all buttons
|
537
|
+
this.disableButtons('all').enableButtons('cmdPreview');
|
538
|
+
|
539
|
+
// Try to get the content from callback
|
540
|
+
callbackContent = options.onPreview(this);
|
541
|
+
// Set the content based from the callback content if string otherwise parse value from textarea
|
542
|
+
content = typeof callbackContent == 'string' ? callbackContent : this.parseContent();
|
543
|
+
|
544
|
+
// Build preview element
|
545
|
+
replacementContainer.html(content);
|
546
|
+
|
547
|
+
if (afterContainer && afterContainer.attr('class') == 'md-footer') {
|
548
|
+
// If there is footer element, insert the preview container before it
|
549
|
+
replacementContainer.insertBefore(afterContainer);
|
550
|
+
} else {
|
551
|
+
// Otherwise, just append it after textarea
|
552
|
+
container.parent().append(replacementContainer);
|
553
|
+
}
|
554
|
+
|
555
|
+
// Set the preview element dimensions
|
556
|
+
replacementContainer.css({
|
557
|
+
width: container.outerWidth() + 'px',
|
558
|
+
height: container.outerHeight() + 'px'
|
559
|
+
});
|
560
|
+
|
561
|
+
if (this.$options.resize) {
|
562
|
+
replacementContainer.css('resize', this.$options.resize);
|
563
|
+
}
|
564
|
+
|
565
|
+
// Hide the last-active textarea
|
566
|
+
container.hide();
|
567
|
+
|
568
|
+
// Attach the editor instances
|
569
|
+
replacementContainer.data('markdown', this);
|
570
|
+
|
571
|
+
if (this.$element.is(':disabled') || this.$element.is('[readonly]')) {
|
572
|
+
this.$editor.addClass('md-editor-disabled');
|
573
|
+
this.disableButtons('all');
|
574
|
+
}
|
575
|
+
|
576
|
+
return this;
|
577
|
+
},
|
578
|
+
hidePreview: function() {
|
579
|
+
// Give flag that tell the editor quit preview mode
|
580
|
+
this.$isPreview = false;
|
581
|
+
|
582
|
+
// Obtain the preview container
|
583
|
+
var container = this.$editor.find('div[data-provider="markdown-preview"]');
|
584
|
+
|
585
|
+
// Remove the preview container
|
586
|
+
container.remove();
|
587
|
+
|
588
|
+
// Enable all buttons
|
589
|
+
this.enableButtons('all');
|
590
|
+
// Disable configured disabled buttons
|
591
|
+
this.disableButtons(this.$options.disabledButtons);
|
592
|
+
|
593
|
+
// Back to the editor
|
594
|
+
this.$textarea.show();
|
595
|
+
this.__setListener();
|
596
|
+
|
597
|
+
return this;
|
598
|
+
},
|
599
|
+
isDirty: function() {
|
600
|
+
return this.$oldContent != this.getContent();
|
601
|
+
},
|
602
|
+
getContent: function() {
|
603
|
+
return this.$textarea.val();
|
604
|
+
},
|
605
|
+
setContent: function(content) {
|
606
|
+
this.$textarea.val(content);
|
607
|
+
|
608
|
+
return this;
|
609
|
+
},
|
610
|
+
findSelection: function(chunk) {
|
611
|
+
var content = this.getContent(),
|
612
|
+
startChunkPosition;
|
613
|
+
|
614
|
+
if (startChunkPosition = content.indexOf(chunk), startChunkPosition >= 0 && chunk.length > 0) {
|
615
|
+
var oldSelection = this.getSelection(),
|
616
|
+
selection;
|
617
|
+
|
618
|
+
this.setSelection(startChunkPosition, startChunkPosition + chunk.length);
|
619
|
+
selection = this.getSelection();
|
620
|
+
|
621
|
+
this.setSelection(oldSelection.start, oldSelection.end);
|
622
|
+
|
623
|
+
return selection;
|
624
|
+
} else {
|
625
|
+
return null;
|
626
|
+
}
|
627
|
+
},
|
628
|
+
getSelection: function() {
|
629
|
+
|
630
|
+
var e = this.$textarea[0];
|
631
|
+
|
632
|
+
return (
|
633
|
+
|
634
|
+
('selectionStart' in e && function() {
|
635
|
+
var l = e.selectionEnd - e.selectionStart;
|
636
|
+
return {
|
637
|
+
start: e.selectionStart,
|
638
|
+
end: e.selectionEnd,
|
639
|
+
length: l,
|
640
|
+
text: e.value.substr(e.selectionStart, l)
|
641
|
+
};
|
642
|
+
}) ||
|
643
|
+
|
644
|
+
/* browser not supported */
|
645
|
+
function() {
|
646
|
+
return null;
|
647
|
+
}
|
648
|
+
|
649
|
+
)();
|
650
|
+
|
651
|
+
},
|
652
|
+
setSelection: function(start, end) {
|
653
|
+
|
654
|
+
var e = this.$textarea[0];
|
655
|
+
|
656
|
+
return (
|
657
|
+
|
658
|
+
('selectionStart' in e && function() {
|
659
|
+
e.selectionStart = start;
|
660
|
+
e.selectionEnd = end;
|
661
|
+
return;
|
662
|
+
}) ||
|
663
|
+
|
664
|
+
/* browser not supported */
|
665
|
+
function() {
|
666
|
+
return null;
|
667
|
+
}
|
668
|
+
|
669
|
+
)();
|
670
|
+
|
671
|
+
},
|
672
|
+
replaceSelection: function(text) {
|
673
|
+
|
674
|
+
var e = this.$textarea[0];
|
675
|
+
|
676
|
+
return (
|
677
|
+
|
678
|
+
('selectionStart' in e && function() {
|
679
|
+
e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length);
|
680
|
+
// Set cursor to the last replacement end
|
681
|
+
e.selectionStart = e.value.length;
|
682
|
+
return this;
|
683
|
+
}) ||
|
684
|
+
|
685
|
+
/* browser not supported */
|
686
|
+
function() {
|
687
|
+
e.value += text;
|
688
|
+
return jQuery(e);
|
689
|
+
}
|
690
|
+
|
691
|
+
)();
|
692
|
+
},
|
693
|
+
getNextTab: function() {
|
694
|
+
// Shift the nextTab
|
695
|
+
if (this.$nextTab.length === 0) {
|
696
|
+
return null;
|
697
|
+
} else {
|
698
|
+
var nextTab, tab = this.$nextTab.shift();
|
699
|
+
|
700
|
+
if (typeof tab == 'function') {
|
701
|
+
nextTab = tab();
|
702
|
+
} else if (typeof tab == 'object' && tab.length > 0) {
|
703
|
+
nextTab = tab;
|
704
|
+
}
|
705
|
+
|
706
|
+
return nextTab;
|
707
|
+
}
|
708
|
+
},
|
709
|
+
setNextTab: function(start, end) {
|
710
|
+
// Push new selection into nextTab collections
|
711
|
+
if (typeof start == 'string') {
|
712
|
+
var that = this;
|
713
|
+
this.$nextTab.push(function() {
|
714
|
+
return that.findSelection(start);
|
715
|
+
});
|
716
|
+
} else if (typeof start == 'number' && typeof end == 'number') {
|
717
|
+
var oldSelection = this.getSelection();
|
718
|
+
|
719
|
+
this.setSelection(start, end);
|
720
|
+
this.$nextTab.push(this.getSelection());
|
721
|
+
|
722
|
+
this.setSelection(oldSelection.start, oldSelection.end);
|
723
|
+
}
|
724
|
+
|
725
|
+
return;
|
726
|
+
},
|
727
|
+
__parseButtonNameParam: function(names) {
|
728
|
+
return typeof names == 'string' ?
|
729
|
+
names.split(' ') :
|
730
|
+
names;
|
731
|
+
|
732
|
+
},
|
733
|
+
enableButtons: function(name) {
|
734
|
+
var buttons = this.__parseButtonNameParam(name),
|
735
|
+
that = this;
|
736
|
+
|
737
|
+
$.each(buttons, function(i, v) {
|
738
|
+
that.__alterButtons(buttons[i], function(el) {
|
739
|
+
el.removeAttr('disabled');
|
740
|
+
});
|
741
|
+
});
|
742
|
+
|
743
|
+
return this;
|
744
|
+
},
|
745
|
+
disableButtons: function(name) {
|
746
|
+
var buttons = this.__parseButtonNameParam(name),
|
747
|
+
that = this;
|
748
|
+
|
749
|
+
$.each(buttons, function(i, v) {
|
750
|
+
that.__alterButtons(buttons[i], function(el) {
|
751
|
+
el.attr('disabled', 'disabled');
|
752
|
+
});
|
753
|
+
});
|
754
|
+
|
755
|
+
return this;
|
756
|
+
},
|
757
|
+
hideButtons: function(name) {
|
758
|
+
var buttons = this.__parseButtonNameParam(name),
|
759
|
+
that = this;
|
760
|
+
|
761
|
+
$.each(buttons, function(i, v) {
|
762
|
+
that.__alterButtons(buttons[i], function(el) {
|
763
|
+
el.addClass('hidden');
|
764
|
+
});
|
765
|
+
});
|
766
|
+
|
767
|
+
return this;
|
768
|
+
},
|
769
|
+
showButtons: function(name) {
|
770
|
+
var buttons = this.__parseButtonNameParam(name),
|
771
|
+
that = this;
|
772
|
+
|
773
|
+
$.each(buttons, function(i, v) {
|
774
|
+
that.__alterButtons(buttons[i], function(el) {
|
775
|
+
el.removeClass('hidden');
|
776
|
+
});
|
777
|
+
});
|
778
|
+
|
779
|
+
return this;
|
780
|
+
},
|
781
|
+
eventSupported: function(eventName) {
|
782
|
+
var isSupported = eventName in this.$element;
|
783
|
+
if (!isSupported) {
|
784
|
+
this.$element.setAttribute(eventName, 'return;');
|
785
|
+
isSupported = typeof this.$element[eventName] === 'function';
|
786
|
+
}
|
787
|
+
return isSupported;
|
788
|
+
},
|
789
|
+
keyup: function(e) {
|
790
|
+
var blocked = false;
|
791
|
+
switch (e.keyCode) {
|
792
|
+
case 40: // down arrow
|
793
|
+
case 38: // up arrow
|
794
|
+
case 16: // shift
|
795
|
+
case 17: // ctrl
|
796
|
+
case 18: // alt
|
797
|
+
break;
|
798
|
+
|
799
|
+
case 9: // tab
|
800
|
+
var nextTab;
|
801
|
+
if (nextTab = this.getNextTab(), nextTab !== null) {
|
802
|
+
// Get the nextTab if exists
|
803
|
+
var that = this;
|
804
|
+
setTimeout(function() {
|
805
|
+
that.setSelection(nextTab.start, nextTab.end);
|
806
|
+
}, 500);
|
807
|
+
|
808
|
+
blocked = true;
|
809
|
+
} else {
|
810
|
+
// The next tab memory contains nothing...
|
811
|
+
// check the cursor position to determine tab action
|
812
|
+
var cursor = this.getSelection();
|
813
|
+
|
814
|
+
if (cursor.start == cursor.end && cursor.end == this.getContent().length) {
|
815
|
+
// The cursor already reach the end of the content
|
816
|
+
blocked = false;
|
817
|
+
} else {
|
818
|
+
// Put the cursor to the end
|
819
|
+
this.setSelection(this.getContent().length, this.getContent().length);
|
820
|
+
|
821
|
+
blocked = true;
|
822
|
+
}
|
823
|
+
}
|
824
|
+
|
825
|
+
break;
|
826
|
+
|
827
|
+
case 13: // enter
|
828
|
+
blocked = false;
|
829
|
+
break;
|
830
|
+
case 27: // escape
|
831
|
+
if (this.$isFullscreen) this.setFullscreen(false);
|
832
|
+
blocked = false;
|
833
|
+
break;
|
834
|
+
|
835
|
+
default:
|
836
|
+
blocked = false;
|
837
|
+
}
|
838
|
+
|
839
|
+
if (blocked) {
|
840
|
+
e.stopPropagation();
|
841
|
+
e.preventDefault();
|
842
|
+
}
|
843
|
+
|
844
|
+
this.$options.onChange(this);
|
845
|
+
},
|
846
|
+
change: function(e) {
|
847
|
+
this.$options.onChange(this);
|
848
|
+
return this;
|
849
|
+
},
|
850
|
+
select: function(e) {
|
851
|
+
this.$options.onSelect(this);
|
852
|
+
return this;
|
853
|
+
},
|
854
|
+
focus: function(e) {
|
855
|
+
var options = this.$options,
|
856
|
+
isHideable = options.hideable,
|
857
|
+
editor = this.$editor;
|
858
|
+
|
859
|
+
editor.addClass('active');
|
860
|
+
|
861
|
+
// Blur other markdown(s)
|
862
|
+
$(document).find('.md-editor').each(function() {
|
863
|
+
if ($(this).attr('id') !== editor.attr('id')) {
|
864
|
+
var attachedMarkdown;
|
865
|
+
|
866
|
+
if (attachedMarkdown = $(this).find('textarea').data('markdown'),
|
867
|
+
attachedMarkdown === null) {
|
868
|
+
attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown');
|
869
|
+
}
|
870
|
+
|
871
|
+
if (attachedMarkdown) {
|
872
|
+
attachedMarkdown.blur();
|
873
|
+
}
|
874
|
+
}
|
875
|
+
});
|
876
|
+
|
877
|
+
// Trigger the onFocus hook
|
878
|
+
options.onFocus(this);
|
879
|
+
|
880
|
+
return this;
|
881
|
+
},
|
882
|
+
blur: function(e) {
|
883
|
+
var options = this.$options,
|
884
|
+
isHideable = options.hideable,
|
885
|
+
editor = this.$editor,
|
886
|
+
editable = this.$editable;
|
887
|
+
|
888
|
+
if (editor.hasClass('active') || this.$element.parent().length === 0) {
|
889
|
+
editor.removeClass('active');
|
890
|
+
|
891
|
+
if (isHideable) {
|
892
|
+
// Check for editable elements
|
893
|
+
if (editable.el !== null) {
|
894
|
+
// Build the original element
|
895
|
+
var oldElement = $('<' + editable.type + '/>'),
|
896
|
+
content = this.getContent(),
|
897
|
+
currentContent = this.parseContent(content);
|
898
|
+
|
899
|
+
$(editable.attrKeys).each(function(k, v) {
|
900
|
+
oldElement.attr(editable.attrKeys[k], editable.attrValues[k]);
|
901
|
+
});
|
902
|
+
|
903
|
+
// Get the editor content
|
904
|
+
oldElement.html(currentContent);
|
905
|
+
|
906
|
+
editor.replaceWith(oldElement);
|
907
|
+
} else {
|
908
|
+
editor.hide();
|
909
|
+
}
|
910
|
+
}
|
911
|
+
|
912
|
+
// Trigger the onBlur hook
|
913
|
+
options.onBlur(this);
|
914
|
+
}
|
915
|
+
|
916
|
+
return this;
|
917
|
+
}
|
918
|
+
|
919
|
+
};
|
920
|
+
|
921
|
+
/* MARKDOWN PLUGIN DEFINITION
|
922
|
+
* ========================== */
|
923
|
+
|
924
|
+
var old = $.fn.markdown;
|
925
|
+
|
926
|
+
$.fn.markdown = function(option) {
|
927
|
+
return this.each(function() {
|
928
|
+
var $this = $(this),
|
929
|
+
data = $this.data('markdown'),
|
930
|
+
options = typeof option == 'object' && option;
|
931
|
+
if (!data)
|
932
|
+
$this.data('markdown', (data = new Markdown(this, options)));
|
933
|
+
});
|
934
|
+
};
|
935
|
+
|
936
|
+
$.fn.markdown.messages = {};
|
937
|
+
|
938
|
+
$.fn.markdown.defaults = {
|
939
|
+
/* Editor Properties */
|
940
|
+
autofocus: false,
|
941
|
+
hideable: false,
|
942
|
+
savable: false,
|
943
|
+
width: 'inherit',
|
944
|
+
height: 'inherit',
|
945
|
+
resize: 'none',
|
946
|
+
iconlibrary: 'glyph',
|
947
|
+
language: 'en',
|
948
|
+
initialstate: 'editor',
|
949
|
+
parser: null,
|
950
|
+
dropZoneOptions: null,
|
951
|
+
|
952
|
+
/* Buttons Properties */
|
953
|
+
buttons: [
|
954
|
+
[{
|
955
|
+
name: 'groupFont',
|
956
|
+
data: [{
|
957
|
+
name: 'cmdBold',
|
958
|
+
hotkey: 'Ctrl+B',
|
959
|
+
title: 'Bold',
|
960
|
+
icon: {
|
961
|
+
glyph: 'glyphicon glyphicon-bold',
|
962
|
+
fa: 'fa fa-bold',
|
963
|
+
'fa-3': 'icon-bold',
|
964
|
+
octicons: 'octicon octicon-bold'
|
965
|
+
},
|
966
|
+
callback: function(e) {
|
967
|
+
// Give/remove ** surround the selection
|
968
|
+
var chunk, cursor, selected = e.getSelection(),
|
969
|
+
content = e.getContent();
|
970
|
+
|
971
|
+
if (selected.length === 0) {
|
972
|
+
// Give extra word
|
973
|
+
chunk = e.__localize('strong text');
|
974
|
+
} else {
|
975
|
+
chunk = selected.text;
|
976
|
+
}
|
977
|
+
|
978
|
+
// transform selection and set the cursor into chunked text
|
979
|
+
if (content.substr(selected.start - 2, 2) === '**' &&
|
980
|
+
content.substr(selected.end, 2) === '**') {
|
981
|
+
e.setSelection(selected.start - 2, selected.end + 2);
|
982
|
+
e.replaceSelection(chunk);
|
983
|
+
cursor = selected.start - 2;
|
984
|
+
} else {
|
985
|
+
e.replaceSelection('**' + chunk + '**');
|
986
|
+
cursor = selected.start + 2;
|
987
|
+
}
|
988
|
+
|
989
|
+
// Set the cursor
|
990
|
+
e.setSelection(cursor, cursor + chunk.length);
|
991
|
+
}
|
992
|
+
}, {
|
993
|
+
name: 'cmdItalic',
|
994
|
+
title: 'Italic',
|
995
|
+
hotkey: 'Ctrl+I',
|
996
|
+
icon: {
|
997
|
+
glyph: 'glyphicon glyphicon-italic',
|
998
|
+
fa: 'fa fa-italic',
|
999
|
+
'fa-3': 'icon-italic',
|
1000
|
+
octicons: 'octicon octicon-italic'
|
1001
|
+
},
|
1002
|
+
callback: function(e) {
|
1003
|
+
// Give/remove * surround the selection
|
1004
|
+
var chunk, cursor, selected = e.getSelection(),
|
1005
|
+
content = e.getContent();
|
1006
|
+
|
1007
|
+
if (selected.length === 0) {
|
1008
|
+
// Give extra word
|
1009
|
+
chunk = e.__localize('emphasized text');
|
1010
|
+
} else {
|
1011
|
+
chunk = selected.text;
|
1012
|
+
}
|
1013
|
+
|
1014
|
+
// transform selection and set the cursor into chunked text
|
1015
|
+
if (content.substr(selected.start - 1, 1) === '_' &&
|
1016
|
+
content.substr(selected.end, 1) === '_') {
|
1017
|
+
e.setSelection(selected.start - 1, selected.end + 1);
|
1018
|
+
e.replaceSelection(chunk);
|
1019
|
+
cursor = selected.start - 1;
|
1020
|
+
} else {
|
1021
|
+
e.replaceSelection('_' + chunk + '_');
|
1022
|
+
cursor = selected.start + 1;
|
1023
|
+
}
|
1024
|
+
|
1025
|
+
// Set the cursor
|
1026
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1027
|
+
}
|
1028
|
+
}, {
|
1029
|
+
name: 'cmdHeading',
|
1030
|
+
title: 'Heading',
|
1031
|
+
hotkey: 'Ctrl+H',
|
1032
|
+
icon: {
|
1033
|
+
glyph: 'glyphicon glyphicon-header',
|
1034
|
+
fa: 'fa fa-header',
|
1035
|
+
'fa-3': 'icon-font',
|
1036
|
+
octicons: 'octicon octicon-text-size'
|
1037
|
+
},
|
1038
|
+
callback: function(e) {
|
1039
|
+
// Append/remove ### surround the selection
|
1040
|
+
var chunk, cursor, selected = e.getSelection(),
|
1041
|
+
content = e.getContent(),
|
1042
|
+
pointer, prevChar;
|
1043
|
+
|
1044
|
+
if (selected.length === 0) {
|
1045
|
+
// Give extra word
|
1046
|
+
chunk = e.__localize('heading text');
|
1047
|
+
} else {
|
1048
|
+
chunk = selected.text + '\n';
|
1049
|
+
}
|
1050
|
+
|
1051
|
+
// transform selection and set the cursor into chunked text
|
1052
|
+
if ((pointer = 4, content.substr(selected.start - pointer, pointer) === '### ') ||
|
1053
|
+
(pointer = 3, content.substr(selected.start - pointer, pointer) === '###')) {
|
1054
|
+
e.setSelection(selected.start - pointer, selected.end);
|
1055
|
+
e.replaceSelection(chunk);
|
1056
|
+
cursor = selected.start - pointer;
|
1057
|
+
} else if (selected.start > 0 && (prevChar = content.substr(selected.start - 1, 1), !!prevChar && prevChar != '\n')) {
|
1058
|
+
e.replaceSelection('\n\n### ' + chunk);
|
1059
|
+
cursor = selected.start + 6;
|
1060
|
+
} else {
|
1061
|
+
// Empty string before element
|
1062
|
+
e.replaceSelection('### ' + chunk);
|
1063
|
+
cursor = selected.start + 4;
|
1064
|
+
}
|
1065
|
+
|
1066
|
+
// Set the cursor
|
1067
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1068
|
+
}
|
1069
|
+
}]
|
1070
|
+
}, {
|
1071
|
+
name: 'groupLink',
|
1072
|
+
data: [{
|
1073
|
+
name: 'cmdUrl',
|
1074
|
+
title: 'URL/Link',
|
1075
|
+
hotkey: 'Ctrl+L',
|
1076
|
+
icon: {
|
1077
|
+
glyph: 'glyphicon glyphicon-link',
|
1078
|
+
fa: 'fa fa-link',
|
1079
|
+
'fa-3': 'icon-link',
|
1080
|
+
octicons: 'octicon octicon-link'
|
1081
|
+
},
|
1082
|
+
callback: function(e) {
|
1083
|
+
// Give [] surround the selection and prepend the link
|
1084
|
+
var chunk, cursor, selected = e.getSelection(),
|
1085
|
+
content = e.getContent(),
|
1086
|
+
link;
|
1087
|
+
|
1088
|
+
if (selected.length === 0) {
|
1089
|
+
// Give extra word
|
1090
|
+
chunk = e.__localize('enter link description here');
|
1091
|
+
} else {
|
1092
|
+
chunk = selected.text;
|
1093
|
+
}
|
1094
|
+
|
1095
|
+
link = prompt(e.__localize('Insert Hyperlink'), 'http://');
|
1096
|
+
|
1097
|
+
var urlRegex = new RegExp('^((http|https)://|(mailto:)|(//))[a-z0-9]', 'i');
|
1098
|
+
if (link !== null && link !== '' && link !== 'http://' && urlRegex.test(link)) {
|
1099
|
+
var sanitizedLink = $('<div>' + link + '</div>').text();
|
1100
|
+
|
1101
|
+
// transform selection and set the cursor into chunked text
|
1102
|
+
e.replaceSelection('[' + chunk + '](' + sanitizedLink + ')');
|
1103
|
+
cursor = selected.start + 1;
|
1104
|
+
|
1105
|
+
// Set the cursor
|
1106
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1107
|
+
}
|
1108
|
+
}
|
1109
|
+
}, {
|
1110
|
+
name: 'cmdImage',
|
1111
|
+
title: 'Image',
|
1112
|
+
hotkey: 'Ctrl+G',
|
1113
|
+
icon: {
|
1114
|
+
glyph: 'glyphicon glyphicon-picture',
|
1115
|
+
fa: 'fa fa-picture-o',
|
1116
|
+
'fa-3': 'icon-picture',
|
1117
|
+
octicons: 'octicon octicon-file-media'
|
1118
|
+
},
|
1119
|
+
callback: function(e) {
|
1120
|
+
// Give ![] surround the selection and prepend the image link
|
1121
|
+
var chunk, cursor, selected = e.getSelection(),
|
1122
|
+
content = e.getContent(),
|
1123
|
+
link;
|
1124
|
+
|
1125
|
+
if (selected.length === 0) {
|
1126
|
+
// Give extra word
|
1127
|
+
chunk = e.__localize('enter image description here');
|
1128
|
+
} else {
|
1129
|
+
chunk = selected.text;
|
1130
|
+
}
|
1131
|
+
|
1132
|
+
link = prompt(e.__localize('Insert Image Hyperlink'), 'http://');
|
1133
|
+
|
1134
|
+
var urlRegex = new RegExp('^((http|https)://|(//))[a-z0-9]', 'i');
|
1135
|
+
if (link !== null && link !== '' && link !== 'http://' && urlRegex.test(link)) {
|
1136
|
+
var sanitizedLink = $('<div>' + link + '</div>').text();
|
1137
|
+
|
1138
|
+
// transform selection and set the cursor into chunked text
|
1139
|
+
e.replaceSelection(' + '")');
|
1140
|
+
cursor = selected.start + 2;
|
1141
|
+
|
1142
|
+
// Set the next tab
|
1143
|
+
e.setNextTab(e.__localize('enter image title here'));
|
1144
|
+
|
1145
|
+
// Set the cursor
|
1146
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1147
|
+
}
|
1148
|
+
}
|
1149
|
+
}]
|
1150
|
+
}, {
|
1151
|
+
name: 'groupMisc',
|
1152
|
+
data: [{
|
1153
|
+
name: 'cmdList',
|
1154
|
+
hotkey: 'Ctrl+U',
|
1155
|
+
title: 'Unordered List',
|
1156
|
+
icon: {
|
1157
|
+
glyph: 'glyphicon glyphicon-list',
|
1158
|
+
fa: 'fa fa-list',
|
1159
|
+
'fa-3': 'icon-list-ul',
|
1160
|
+
octicons: 'octicon octicon-list-unordered'
|
1161
|
+
},
|
1162
|
+
callback: function(e) {
|
1163
|
+
// Prepend/Give - surround the selection
|
1164
|
+
var chunk, cursor, selected = e.getSelection(),
|
1165
|
+
content = e.getContent();
|
1166
|
+
|
1167
|
+
// transform selection and set the cursor into chunked text
|
1168
|
+
if (selected.length === 0) {
|
1169
|
+
// Give extra word
|
1170
|
+
chunk = e.__localize('list text here');
|
1171
|
+
|
1172
|
+
e.replaceSelection('- ' + chunk);
|
1173
|
+
// Set the cursor
|
1174
|
+
cursor = selected.start + 2;
|
1175
|
+
} else {
|
1176
|
+
if (selected.text.indexOf('\n') < 0) {
|
1177
|
+
chunk = selected.text;
|
1178
|
+
|
1179
|
+
e.replaceSelection('- ' + chunk);
|
1180
|
+
|
1181
|
+
// Set the cursor
|
1182
|
+
cursor = selected.start + 2;
|
1183
|
+
} else {
|
1184
|
+
var list = [];
|
1185
|
+
|
1186
|
+
list = selected.text.split('\n');
|
1187
|
+
chunk = list[0];
|
1188
|
+
|
1189
|
+
$.each(list, function(k, v) {
|
1190
|
+
list[k] = '- ' + v;
|
1191
|
+
});
|
1192
|
+
|
1193
|
+
e.replaceSelection('\n\n' + list.join('\n'));
|
1194
|
+
|
1195
|
+
// Set the cursor
|
1196
|
+
cursor = selected.start + 4;
|
1197
|
+
}
|
1198
|
+
}
|
1199
|
+
|
1200
|
+
// Set the cursor
|
1201
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1202
|
+
}
|
1203
|
+
}, {
|
1204
|
+
name: 'cmdListO',
|
1205
|
+
hotkey: 'Ctrl+O',
|
1206
|
+
title: 'Ordered List',
|
1207
|
+
icon: {
|
1208
|
+
glyph: 'glyphicon glyphicon-th-list',
|
1209
|
+
fa: 'fa fa-list-ol',
|
1210
|
+
'fa-3': 'icon-list-ol',
|
1211
|
+
octicons: 'octicon octicon-list-ordered'
|
1212
|
+
},
|
1213
|
+
callback: function(e) {
|
1214
|
+
|
1215
|
+
// Prepend/Give - surround the selection
|
1216
|
+
var chunk, cursor, selected = e.getSelection(),
|
1217
|
+
content = e.getContent();
|
1218
|
+
|
1219
|
+
// transform selection and set the cursor into chunked text
|
1220
|
+
if (selected.length === 0) {
|
1221
|
+
// Give extra word
|
1222
|
+
chunk = e.__localize('list text here');
|
1223
|
+
e.replaceSelection('1. ' + chunk);
|
1224
|
+
// Set the cursor
|
1225
|
+
cursor = selected.start + 3;
|
1226
|
+
} else {
|
1227
|
+
if (selected.text.indexOf('\n') < 0) {
|
1228
|
+
chunk = selected.text;
|
1229
|
+
|
1230
|
+
e.replaceSelection('1. ' + chunk);
|
1231
|
+
|
1232
|
+
// Set the cursor
|
1233
|
+
cursor = selected.start + 3;
|
1234
|
+
} else {
|
1235
|
+
var list = [];
|
1236
|
+
|
1237
|
+
list = selected.text.split('\n');
|
1238
|
+
chunk = list[0];
|
1239
|
+
|
1240
|
+
$.each(list, function(k, v) {
|
1241
|
+
list[k] = '1. ' + v;
|
1242
|
+
});
|
1243
|
+
|
1244
|
+
e.replaceSelection('\n\n' + list.join('\n'));
|
1245
|
+
|
1246
|
+
// Set the cursor
|
1247
|
+
cursor = selected.start + 5;
|
1248
|
+
}
|
1249
|
+
}
|
1250
|
+
|
1251
|
+
// Set the cursor
|
1252
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1253
|
+
}
|
1254
|
+
}, {
|
1255
|
+
name: 'cmdCode',
|
1256
|
+
hotkey: 'Ctrl+K',
|
1257
|
+
title: 'Code',
|
1258
|
+
icon: {
|
1259
|
+
glyph: 'glyphicon glyphicon-asterisk',
|
1260
|
+
fa: 'fa fa-code',
|
1261
|
+
'fa-3': 'icon-code',
|
1262
|
+
octicons: 'octicon octicon-code'
|
1263
|
+
},
|
1264
|
+
callback: function(e) {
|
1265
|
+
// Give/remove ** surround the selection
|
1266
|
+
var chunk, cursor, selected = e.getSelection(),
|
1267
|
+
content = e.getContent();
|
1268
|
+
|
1269
|
+
if (selected.length === 0) {
|
1270
|
+
// Give extra word
|
1271
|
+
chunk = e.__localize('code text here');
|
1272
|
+
} else {
|
1273
|
+
chunk = selected.text;
|
1274
|
+
}
|
1275
|
+
|
1276
|
+
// transform selection and set the cursor into chunked text
|
1277
|
+
if (content.substr(selected.start - 4, 4) === '```\n' &&
|
1278
|
+
content.substr(selected.end, 4) === '\n```') {
|
1279
|
+
e.setSelection(selected.start - 4, selected.end + 4);
|
1280
|
+
e.replaceSelection(chunk);
|
1281
|
+
cursor = selected.start - 4;
|
1282
|
+
} else if (content.substr(selected.start - 1, 1) === '`' &&
|
1283
|
+
content.substr(selected.end, 1) === '`') {
|
1284
|
+
e.setSelection(selected.start - 1, selected.end + 1);
|
1285
|
+
e.replaceSelection(chunk);
|
1286
|
+
cursor = selected.start - 1;
|
1287
|
+
} else if (content.indexOf('\n') > -1) {
|
1288
|
+
e.replaceSelection('```\n' + chunk + '\n```');
|
1289
|
+
cursor = selected.start + 4;
|
1290
|
+
} else {
|
1291
|
+
e.replaceSelection('`' + chunk + '`');
|
1292
|
+
cursor = selected.start + 1;
|
1293
|
+
}
|
1294
|
+
|
1295
|
+
// Set the cursor
|
1296
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1297
|
+
}
|
1298
|
+
}, {
|
1299
|
+
name: 'cmdQuote',
|
1300
|
+
hotkey: 'Ctrl+Q',
|
1301
|
+
title: 'Quote',
|
1302
|
+
icon: {
|
1303
|
+
glyph: 'glyphicon glyphicon-comment',
|
1304
|
+
fa: 'fa fa-quote-left',
|
1305
|
+
'fa-3': 'icon-quote-left',
|
1306
|
+
octicons: 'octicon octicon-quote'
|
1307
|
+
},
|
1308
|
+
callback: function(e) {
|
1309
|
+
// Prepend/Give - surround the selection
|
1310
|
+
var chunk, cursor, selected = e.getSelection(),
|
1311
|
+
content = e.getContent();
|
1312
|
+
|
1313
|
+
// transform selection and set the cursor into chunked text
|
1314
|
+
if (selected.length === 0) {
|
1315
|
+
// Give extra word
|
1316
|
+
chunk = e.__localize('quote here');
|
1317
|
+
|
1318
|
+
e.replaceSelection('> ' + chunk);
|
1319
|
+
|
1320
|
+
// Set the cursor
|
1321
|
+
cursor = selected.start + 2;
|
1322
|
+
} else {
|
1323
|
+
if (selected.text.indexOf('\n') < 0) {
|
1324
|
+
chunk = selected.text;
|
1325
|
+
|
1326
|
+
e.replaceSelection('> ' + chunk);
|
1327
|
+
|
1328
|
+
// Set the cursor
|
1329
|
+
cursor = selected.start + 2;
|
1330
|
+
} else {
|
1331
|
+
var list = [];
|
1332
|
+
|
1333
|
+
list = selected.text.split('\n');
|
1334
|
+
chunk = list[0];
|
1335
|
+
|
1336
|
+
$.each(list, function(k, v) {
|
1337
|
+
list[k] = '> ' + v;
|
1338
|
+
});
|
1339
|
+
|
1340
|
+
e.replaceSelection('\n\n' + list.join('\n'));
|
1341
|
+
|
1342
|
+
// Set the cursor
|
1343
|
+
cursor = selected.start + 4;
|
1344
|
+
}
|
1345
|
+
}
|
1346
|
+
|
1347
|
+
// Set the cursor
|
1348
|
+
e.setSelection(cursor, cursor + chunk.length);
|
1349
|
+
}
|
1350
|
+
}]
|
1351
|
+
}, {
|
1352
|
+
name: 'groupUtil',
|
1353
|
+
data: [{
|
1354
|
+
name: 'cmdPreview',
|
1355
|
+
toggle: true,
|
1356
|
+
hotkey: 'Ctrl+P',
|
1357
|
+
title: 'Preview',
|
1358
|
+
btnText: 'Preview',
|
1359
|
+
btnClass: 'btn btn-primary btn-sm',
|
1360
|
+
icon: {
|
1361
|
+
glyph: 'glyphicon glyphicon-search',
|
1362
|
+
fa: 'fa fa-search',
|
1363
|
+
'fa-3': 'icon-search',
|
1364
|
+
octicons: 'octicon octicon-search'
|
1365
|
+
},
|
1366
|
+
callback: function(e) {
|
1367
|
+
// Check the preview mode and toggle based on this flag
|
1368
|
+
var isPreview = e.$isPreview,
|
1369
|
+
content;
|
1370
|
+
|
1371
|
+
if (isPreview === false) {
|
1372
|
+
// Give flag that tell the editor enter preview mode
|
1373
|
+
e.showPreview();
|
1374
|
+
} else {
|
1375
|
+
e.hidePreview();
|
1376
|
+
}
|
1377
|
+
}
|
1378
|
+
}]
|
1379
|
+
}]
|
1380
|
+
],
|
1381
|
+
additionalButtons: [], // Place to hook more buttons by code
|
1382
|
+
reorderButtonGroups: [],
|
1383
|
+
hiddenButtons: [], // Default hidden buttons
|
1384
|
+
disabledButtons: [], // Default disabled buttons
|
1385
|
+
footer: '',
|
1386
|
+
fullscreen: {
|
1387
|
+
enable: true,
|
1388
|
+
icons: {
|
1389
|
+
fullscreenOn: {
|
1390
|
+
fa: 'fa fa-expand',
|
1391
|
+
glyph: 'glyphicon glyphicon-fullscreen',
|
1392
|
+
'fa-3': 'icon-resize-full',
|
1393
|
+
octicons: 'octicon octicon-link-external'
|
1394
|
+
},
|
1395
|
+
fullscreenOff: {
|
1396
|
+
fa: 'fa fa-compress',
|
1397
|
+
glyph: 'glyphicon glyphicon-fullscreen',
|
1398
|
+
'fa-3': 'icon-resize-small',
|
1399
|
+
octicons: 'octicon octicon-browser'
|
1400
|
+
}
|
1401
|
+
}
|
1402
|
+
},
|
1403
|
+
|
1404
|
+
/* Events hook */
|
1405
|
+
onShow: function(e) {},
|
1406
|
+
onPreview: function(e) {},
|
1407
|
+
onSave: function(e) {},
|
1408
|
+
onBlur: function(e) {},
|
1409
|
+
onFocus: function(e) {},
|
1410
|
+
onChange: function(e) {},
|
1411
|
+
onFullscreen: function(e) {},
|
1412
|
+
onFullscreenExit: function(e) {},
|
1413
|
+
onSelect: function(e) {}
|
1414
|
+
};
|
1415
|
+
|
1416
|
+
$.fn.markdown.Constructor = Markdown;
|
1417
|
+
|
1418
|
+
|
1419
|
+
/* MARKDOWN NO CONFLICT
|
1420
|
+
* ==================== */
|
1421
|
+
|
1422
|
+
$.fn.markdown.noConflict = function() {
|
1423
|
+
$.fn.markdown = old;
|
1424
|
+
return this;
|
1425
|
+
};
|
1426
|
+
|
1427
|
+
/* MARKDOWN GLOBAL FUNCTION & DATA-API
|
1428
|
+
* ==================================== */
|
1429
|
+
var initMarkdown = function(el) {
|
1430
|
+
var $this = el;
|
1431
|
+
|
1432
|
+
if ($this.data('markdown')) {
|
1433
|
+
$this.data('markdown').showEditor();
|
1434
|
+
return;
|
1435
|
+
}
|
1436
|
+
|
1437
|
+
$this.markdown();
|
1438
|
+
};
|
1439
|
+
|
1440
|
+
var blurNonFocused = function(e) {
|
1441
|
+
var $activeElement = $(document.activeElement);
|
1442
|
+
|
1443
|
+
// Blur event
|
1444
|
+
$(document).find('.md-editor').each(function() {
|
1445
|
+
var $this = $(this),
|
1446
|
+
focused = $activeElement.closest('.md-editor')[0] === this,
|
1447
|
+
attachedMarkdown = $this.find('textarea').data('markdown') ||
|
1448
|
+
$this.find('div[data-provider="markdown-preview"]').data('markdown');
|
1449
|
+
|
1450
|
+
if (attachedMarkdown && !focused) {
|
1451
|
+
attachedMarkdown.blur();
|
1452
|
+
}
|
1453
|
+
});
|
1454
|
+
};
|
1455
|
+
|
1456
|
+
$(document)
|
1457
|
+
.on('click.markdown.data-api', '[data-provide="markdown-editable"]', function(e) {
|
1458
|
+
initMarkdown($(this));
|
1459
|
+
e.preventDefault();
|
1460
|
+
})
|
1461
|
+
.on('click focusin', function(e) {
|
1462
|
+
blurNonFocused(e);
|
1463
|
+
})
|
1464
|
+
.ready(function() {
|
1465
|
+
$('textarea[data-provide="markdown"]').each(function() {
|
1466
|
+
initMarkdown($(this));
|
1467
|
+
});
|
1468
|
+
});
|
1469
|
+
|
1470
|
+
}));
|