1 | /* http://github.com/mindmup/bootstrap-wysiwyg */ |
---|
2 | /*global jQuery, $, FileReader*/ |
---|
3 | /*jslint browser:true*/ |
---|
4 | (function ($) { |
---|
5 | 'use strict'; |
---|
6 | var readFileIntoDataUrl = function (fileInfo) { |
---|
7 | var loader = $.Deferred(), |
---|
8 | fReader = new FileReader(); |
---|
9 | fReader.onload = function (e) { |
---|
10 | loader.resolve(e.target.result); |
---|
11 | }; |
---|
12 | fReader.onerror = loader.reject; |
---|
13 | fReader.onprogress = loader.notify; |
---|
14 | fReader.readAsDataURL(fileInfo); |
---|
15 | return loader.promise(); |
---|
16 | }; |
---|
17 | $.fn.cleanHtml = function () { |
---|
18 | var html = $(this).html(); |
---|
19 | return html && html.replace(/(<br>|\s|<div><br><\/div>| )*$/, ''); |
---|
20 | }; |
---|
21 | $.fn.wysiwyg = function (userOptions) { |
---|
22 | var editor = this, |
---|
23 | selectedRange, |
---|
24 | options, |
---|
25 | toolbarBtnSelector, |
---|
26 | updateToolbar = function () { |
---|
27 | if (options.activeToolbarClass) { |
---|
28 | $(options.toolbarSelector).find(toolbarBtnSelector).each(function () { |
---|
29 | try { |
---|
30 | var command = $(this).data(options.commandRole); |
---|
31 | if (document.queryCommandState(command)) { |
---|
32 | $(this).addClass(options.activeToolbarClass); |
---|
33 | } else { |
---|
34 | $(this).removeClass(options.activeToolbarClass); |
---|
35 | } |
---|
36 | } catch(e){} |
---|
37 | }); |
---|
38 | } |
---|
39 | }, |
---|
40 | execCommand = function (commandWithArgs, valueArg) { |
---|
41 | var commandArr = commandWithArgs.split(' '), |
---|
42 | command = commandArr.shift(), |
---|
43 | args = commandArr.join(' ') + (valueArg || ''); |
---|
44 | document.execCommand(command, 0, args); |
---|
45 | updateToolbar(); |
---|
46 | }, |
---|
47 | bindHotkeys = function (hotKeys) { |
---|
48 | $.each(hotKeys, function (hotkey, command) { |
---|
49 | editor.keydown(hotkey, function (e) { |
---|
50 | if (editor.attr('contenteditable') && editor.is(':visible')) { |
---|
51 | e.preventDefault(); |
---|
52 | e.stopPropagation(); |
---|
53 | execCommand(command); |
---|
54 | } |
---|
55 | }).keyup(hotkey, function (e) { |
---|
56 | if (editor.attr('contenteditable') && editor.is(':visible')) { |
---|
57 | e.preventDefault(); |
---|
58 | e.stopPropagation(); |
---|
59 | } |
---|
60 | }); |
---|
61 | }); |
---|
62 | }, |
---|
63 | getCurrentRange = function () { |
---|
64 | try { |
---|
65 | var sel = window.getSelection(); |
---|
66 | if (sel.getRangeAt && sel.rangeCount) { |
---|
67 | return sel.getRangeAt(0); |
---|
68 | } |
---|
69 | } catch(e){} |
---|
70 | }, |
---|
71 | saveSelection = function () { |
---|
72 | selectedRange = getCurrentRange(); |
---|
73 | }, |
---|
74 | restoreSelection = function () { |
---|
75 | try { |
---|
76 | var selection = window.getSelection(); |
---|
77 | if (selectedRange) { |
---|
78 | try { |
---|
79 | selection.removeAllRanges(); |
---|
80 | } catch (ex) { |
---|
81 | document.body.createTextRange().select(); |
---|
82 | document.selection.empty(); |
---|
83 | } |
---|
84 | |
---|
85 | selection.addRange(selectedRange); |
---|
86 | } |
---|
87 | } catch(e){} |
---|
88 | }, |
---|
89 | insertFiles = function (files) { |
---|
90 | editor.focus(); |
---|
91 | $.each(files, function (idx, fileInfo) { |
---|
92 | if (/^image\//.test(fileInfo.type)) { |
---|
93 | $.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) { |
---|
94 | execCommand('insertimage', dataUrl); |
---|
95 | }).fail(function (e) { |
---|
96 | options.fileUploadError("file-reader", e); |
---|
97 | }); |
---|
98 | } else { |
---|
99 | options.fileUploadError("unsupported-file-type", fileInfo.type); |
---|
100 | } |
---|
101 | }); |
---|
102 | }, |
---|
103 | markSelection = function (input, color) { |
---|
104 | restoreSelection(); |
---|
105 | if (document.queryCommandSupported('hiliteColor')) { |
---|
106 | document.execCommand('hiliteColor', 0, color || 'transparent'); |
---|
107 | } |
---|
108 | saveSelection(); |
---|
109 | input.data(options.selectionMarker, color); |
---|
110 | }, |
---|
111 | bindToolbar = function (toolbar, options) { |
---|
112 | toolbar.find(toolbarBtnSelector).click(function () { |
---|
113 | restoreSelection(); |
---|
114 | editor.focus(); |
---|
115 | execCommand($(this).data(options.commandRole)); |
---|
116 | saveSelection(); |
---|
117 | }); |
---|
118 | toolbar.find('[data-toggle=dropdown]').click(restoreSelection); |
---|
119 | |
---|
120 | toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () { |
---|
121 | var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */ |
---|
122 | this.value = ''; |
---|
123 | restoreSelection(); |
---|
124 | if (newValue) { |
---|
125 | editor.focus(); |
---|
126 | execCommand($(this).data(options.commandRole), newValue); |
---|
127 | } |
---|
128 | saveSelection(); |
---|
129 | }).on('focus', function () { |
---|
130 | var input = $(this); |
---|
131 | if (!input.data(options.selectionMarker)) { |
---|
132 | markSelection(input, options.selectionColor); |
---|
133 | input.focus(); |
---|
134 | } |
---|
135 | }).on('blur', function () { |
---|
136 | var input = $(this); |
---|
137 | if (input.data(options.selectionMarker)) { |
---|
138 | markSelection(input, false); |
---|
139 | } |
---|
140 | }); |
---|
141 | toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () { |
---|
142 | restoreSelection(); |
---|
143 | if (this.type === 'file' && this.files && this.files.length > 0) { |
---|
144 | insertFiles(this.files); |
---|
145 | } |
---|
146 | saveSelection(); |
---|
147 | this.value = ''; |
---|
148 | }); |
---|
149 | }, |
---|
150 | initFileDrops = function () { |
---|
151 | editor.on('dragenter dragover', false) |
---|
152 | .on('drop', function (e) { |
---|
153 | var dataTransfer = e.originalEvent.dataTransfer; |
---|
154 | e.stopPropagation(); |
---|
155 | e.preventDefault(); |
---|
156 | if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) { |
---|
157 | insertFiles(dataTransfer.files); |
---|
158 | } |
---|
159 | }); |
---|
160 | }; |
---|
161 | options = $.extend({}, $.fn.wysiwyg.defaults, userOptions); |
---|
162 | toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']'; |
---|
163 | bindHotkeys(options.hotKeys); |
---|
164 | if (options.dragAndDropImages) { |
---|
165 | initFileDrops(); |
---|
166 | } |
---|
167 | bindToolbar($(options.toolbarSelector), options); |
---|
168 | editor.attr('contenteditable', true) |
---|
169 | .on('mouseup keyup mouseout', function () { |
---|
170 | saveSelection(); |
---|
171 | updateToolbar(); |
---|
172 | }); |
---|
173 | $(window).bind('touchend', function (e) { |
---|
174 | var isInside = (editor.is(e.target) || editor.has(e.target).length > 0), |
---|
175 | currentRange = getCurrentRange(), |
---|
176 | clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset); |
---|
177 | if (!clear || isInside) { |
---|
178 | saveSelection(); |
---|
179 | updateToolbar(); |
---|
180 | } |
---|
181 | }); |
---|
182 | return this; |
---|
183 | }; |
---|
184 | $.fn.wysiwyg.defaults = { |
---|
185 | hotKeys: { |
---|
186 | 'ctrl+b meta+b': 'bold', |
---|
187 | 'ctrl+i meta+i': 'italic', |
---|
188 | 'ctrl+u meta+u': 'underline', |
---|
189 | 'ctrl+z meta+z': 'undo', |
---|
190 | 'ctrl+y meta+y meta+shift+z': 'redo', |
---|
191 | 'ctrl+l meta+l': 'justifyleft', |
---|
192 | 'ctrl+r meta+r': 'justifyright', |
---|
193 | 'ctrl+e meta+e': 'justifycenter', |
---|
194 | 'ctrl+j meta+j': 'justifyfull', |
---|
195 | 'shift+tab': 'outdent', |
---|
196 | 'tab': 'indent' |
---|
197 | }, |
---|
198 | toolbarSelector: '[data-role=editor-toolbar]', |
---|
199 | commandRole: 'edit', |
---|
200 | activeToolbarClass: 'btn-info', |
---|
201 | selectionMarker: 'edit-focus-marker', |
---|
202 | selectionColor: 'darkgrey', |
---|
203 | dragAndDropImages: true, |
---|
204 | fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); } |
---|
205 | }; |
---|
206 | }(window.jQuery)); |
---|