bootstrap-wysiwyg.js 6.5 KB

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