bootstrap-wysiwyg.js 6.5 KB

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