You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
8.1 KiB

  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. try {
  31. if (document.queryCommandState(command)) {
  32. $(this).addClass(options.activeToolbarClass);
  33. } else {
  34. $(this).removeClass(options.activeToolbarClass);
  35. }
  36. }
  37. catch (ex) { };
  38. });
  39. }
  40. },
  41. execCommand = function (commandWithArgs, valueArg) {
  42. var commandArr = commandWithArgs.split(' '),
  43. command = commandArr.shift(),
  44. args = commandArr.join(' ') + (valueArg || '');
  45. if (commandWithArgs === "createLink")
  46. args = prompt("Enter the URL for this link:", "http://");
  47. document.execCommand(command, 0, args);
  48. updateToolbar();
  49. },
  50. bindHotkeys = function (hotKeys) {
  51. $.each(hotKeys, function (hotkey, command) {
  52. editor.keydown(hotkey, function (e) {
  53. if (editor.attr('contenteditable') && editor.is(':visible')) {
  54. e.preventDefault();
  55. e.stopPropagation();
  56. execCommand(command);
  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(options.readFileIntoUrl(fileInfo)).done(function (dataUrl) {
  92. execCommand('insertimage', dataUrl);
  93. }).fail(function (e) {
  94. options.fileUploadError("file-reader", e);
  95. });
  96. } else {
  97. $.when(options.readFileIntoUrl(fileInfo)).done(function (dataUrl) {
  98. //execCommand('inserthtml', '<a href="' + dataUrl + '">Download</a>');
  99. var frag = document.createDocumentFragment();
  100. var node = document.createElement("a");
  101. node.innerText = fileInfo.name;
  102. node.href = dataUrl;
  103. frag.appendChild(node);
  104. window.getSelection().getRangeAt(0).insertNode(node);
  105. }).fail(function (e) {
  106. options.fileUploadError("file-reader", e);
  107. });
  108. //options.fileUploadError("unsupported-file-type", fileInfo.type);
  109. }
  110. });
  111. },
  112. markSelection = function (input, color) {
  113. restoreSelection();
  114. if (document.queryCommandSupported('hiliteColor')) {
  115. document.execCommand('hiliteColor', 0, color || 'transparent');
  116. }
  117. saveSelection();
  118. input.data(options.selectionMarker, color);
  119. },
  120. bindToolbar = function (toolbar, options) {
  121. toolbar.find(toolbarBtnSelector).click(function () {
  122. restoreSelection();
  123. editor.focus();
  124. execCommand($(this).data(options.commandRole));
  125. saveSelection();
  126. });
  127. toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
  128. toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
  129. var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
  130. this.value = '';
  131. restoreSelection();
  132. if (newValue) {
  133. editor.focus();
  134. execCommand($(this).data(options.commandRole), newValue);
  135. }
  136. saveSelection();
  137. }).on('focus', function () {
  138. var input = $(this);
  139. if (!input.data(options.selectionMarker)) {
  140. markSelection(input, options.selectionColor);
  141. input.focus();
  142. }
  143. }).on('blur', function () {
  144. var input = $(this);
  145. if (input.data(options.selectionMarker)) {
  146. markSelection(input, false);
  147. }
  148. });
  149. toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
  150. restoreSelection();
  151. if (this.type === 'file' && this.files && this.files.length > 0) {
  152. insertFiles(this.files);
  153. }
  154. saveSelection();
  155. this.value = '';
  156. });
  157. },
  158. initFileDrops = function () {
  159. editor.on('dragenter dragover', false)
  160. .on('drop', function (e) {
  161. var dataTransfer = e.originalEvent.dataTransfer;
  162. e.stopPropagation();
  163. e.preventDefault();
  164. if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
  165. insertFiles(dataTransfer.files);
  166. }
  167. });
  168. };
  169. options = $.extend({}, $.fn.wysiwyg.defaults, userOptions);
  170. toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
  171. bindHotkeys(options.hotKeys);
  172. if (options.dragAndDropImages) {
  173. initFileDrops();
  174. }
  175. bindToolbar($(options.toolbarSelector), options);
  176. editor.attr('contenteditable', true)
  177. .on('mouseup keyup mouseout', function () {
  178. saveSelection();
  179. updateToolbar();
  180. });
  181. $(window).bind('touchend', function (e) {
  182. var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
  183. currentRange = getCurrentRange(),
  184. clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
  185. if (!clear || isInside) {
  186. saveSelection();
  187. updateToolbar();
  188. }
  189. });
  190. return this;
  191. };
  192. $.fn.wysiwyg.defaults = {
  193. hotKeys: {
  194. 'ctrl+b meta+b': 'bold',
  195. 'ctrl+i meta+i': 'italic',
  196. 'ctrl+u meta+u': 'underline',
  197. 'ctrl+z meta+z': 'undo',
  198. 'ctrl+y meta+y meta+shift+z': 'redo',
  199. 'ctrl+l meta+l': 'justifyleft',
  200. 'ctrl+r meta+r': 'justifyright',
  201. 'ctrl+e meta+e': 'justifycenter',
  202. 'ctrl+j meta+j': 'justifyfull',
  203. 'shift+tab': 'outdent',
  204. 'tab': 'indent'
  205. },
  206. toolbarSelector: '[data-role=editor-toolbar]',
  207. commandRole: 'edit',
  208. activeToolbarClass: 'btn-info',
  209. selectionMarker: 'edit-focus-marker',
  210. selectionColor: 'darkgrey',
  211. dragAndDropImages: true,
  212. fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); },
  213. readFileIntoUrl: readFileIntoDataUrl
  214. };
  215. }(window.jQuery));