[290] | 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)); |
---|