editor.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /**
  2. * Copyright (c) UNA, Inc - https://una.io
  3. * MIT License - https://opensource.org/licenses/MIT
  4. *
  5. * @defgroup Messenger Messenger
  6. * @ingroup UnaModules
  7. * @{
  8. */
  9. /**
  10. * Quill Editor integration
  11. */
  12. ; window.oMessengerEditor = class {
  13. constructor(oOptions) {
  14. const aEditorFunctions = ['onEnter', 'onChange', 'onESC', 'onUp', 'showToolbar', 'onInit', 'onFocus', 'onBlur'];
  15. this.oHtmlEditorObject = oOptions['selector'] ? oOptions['selector'] : '.editor';
  16. this.oHtmlSendButton = oOptions['button'] ? oOptions['button'] : '.send-button';
  17. this.oPlaceholder = oOptions['placeholder'] || null;
  18. this.bUseMantions = typeof oOptions['mentions'] === 'undefined' || oOptions['mentions'];
  19. this.oFilesUploader = typeof oOptions['files_uploader'] === 'undefined' || oOptions['files_uploader'];
  20. aEditorFunctions.map(sFunc => {
  21. this[sFunc] = typeof oOptions[sFunc] === 'function' ? oOptions[sFunc] : () => true;
  22. });
  23. let aCustomToolbar = this.parseQuillToolbar(oOptions['quill_toolbar']);
  24. aCustomToolbar = aCustomToolbar.length ? aCustomToolbar : [
  25. ['bold', 'italic', 'underline', 'strike', 'link'],
  26. ['blockquote', 'code-block'],
  27. [{ 'color': [] }, { 'background': [] }]
  28. ];
  29. this.aToolbarSettings = [
  30. [{ 'direction': this.isRtl() ? 'rtl' : 'ltr' }],
  31. ...aCustomToolbar];
  32. if ($(this.oHtmlEditorObject).length) {
  33. if (!this.oEditor) {
  34. this.initClipboard();
  35. // Mentions initialization
  36. this.oMentions = typeof quillMention !== 'undefined' && this.bUseMantions ? { mention: this.initMentions() } : {};
  37. }
  38. this.oEditor = null;
  39. this.init();
  40. }
  41. }
  42. parseQuillToolbar(toolbar) {
  43. if (Array.isArray(toolbar)) {
  44. return toolbar;
  45. }
  46. if (typeof toolbar !== 'string' || !toolbar.trim()) {
  47. return [];
  48. }
  49. let str = toolbar.trim();
  50. str = str.replace(/'/g, '"');
  51. str = str.replace(/,\s*$/g, '');
  52. try {
  53. const parsed = JSON.parse(str);
  54. return Array.isArray(parsed) ? parsed : [parsed];
  55. } catch (e) {
  56. console.error("Messenger: Invalid quill_toolbar configuration", e);
  57. console.error("Original string:", toolbar);
  58. return [];
  59. }
  60. }
  61. isRtl() {
  62. return $('body').hasClass('bx-dir-rtl');
  63. }
  64. init() {
  65. if (typeof Quill === 'undefined')
  66. bx_get_scripts(['modules/boonex/messenger/js/quill/quill.min.js'], () => {
  67. this.initEditor();
  68. });
  69. else
  70. this.initEditor();
  71. }
  72. setText(mixedValue) {
  73. if (this.oEditor)
  74. this.oEditor.setText(mixedValue);
  75. }
  76. getText() {
  77. if (this.oEditor)
  78. this.oEditor.getText();
  79. }
  80. editor() {
  81. this.init();
  82. return this.oEditor;
  83. }
  84. html() {
  85. return this.oEditor && this.oEditor.root && this.oEditor.root.innerHTML;
  86. }
  87. focus() {
  88. if (this.oEditor)
  89. this.oEditor.focus();
  90. }
  91. blur() {
  92. if (this.oEditor)
  93. this.oEditor.blur();
  94. }
  95. getContents() {
  96. return this.oEditor && this.oEditor.getContents();
  97. }
  98. get length() {
  99. return this.oEditor && this.oEditor.getLength();
  100. }
  101. setContents(aValues) {
  102. if (this.oEditor)
  103. this.oEditor.setContents(!Array.isArray(aValues) ? [] : aValues);
  104. }
  105. addToCurrentPosition(sText) {
  106. if (!this.oEditor)
  107. return false;
  108. let range = this.oEditor.getSelection(true);
  109. this.oEditor.insertText(range.index, sText, Quill.sources.USER);
  110. this.oEditor.setSelection(range.index + sText.length, 1, Quill.sources.API);
  111. }
  112. initClipboard() {
  113. const _this = this;
  114. const QuillClipboard = Quill.import('modules/clipboard');
  115. class Clipboard extends QuillClipboard {
  116. onPaste(event) {
  117. if (event.clipboardData && event.clipboardData.items) {
  118. const files = [];
  119. for (let item of event.clipboardData.items) {
  120. if (item.kind === 'file') {
  121. const file = item.getAsFile();
  122. if (file) files.push(file);
  123. }
  124. }
  125. if (_this.oFilesUploader && files.length) {
  126. event.preventDefault();
  127. files.forEach(file => {
  128. _this.oFilesUploader.addFile(file);
  129. });
  130. return;
  131. }
  132. }
  133. super.onPaste(event);
  134. }
  135. };
  136. Quill.register('modules/clipboard', Clipboard, true);
  137. }
  138. initMentions() {
  139. const Embed = Quill.import("blots/embed");
  140. class MessengerMentionBlot extends Embed {
  141. static create(data) {
  142. const { denotationChar, value, url, id } = data;
  143. const node = document.createElement('a');
  144. node.innerHTML = denotationChar + value;
  145. node.setAttribute('class', 'bx-mention');
  146. node.setAttribute('href', url);
  147. return MessengerMentionBlot.setDataValues(node, { denotationChar, value, id, url });
  148. }
  149. static setDataValues(element, data) {
  150. const domNode = element;
  151. Object.keys(data).forEach(key => {
  152. domNode.dataset[key] = data[key];
  153. });
  154. return domNode;
  155. }
  156. static value(domNode) {
  157. return domNode.dataset;
  158. }
  159. }
  160. MessengerMentionBlot.blotName = "MessengerMentionBlot";
  161. Quill.register(MessengerMentionBlot);
  162. const sTailWindClasses = $('body').hasClass('bx-artificer') ? ' rounded-md shadow ring-1 ring-gray-200 dark:ring-gray-700 bg-white dark:bg-gray-800' : '';
  163. return {
  164. allowedChars: /^[\w]*$/,
  165. mentionDenotationChars: ["@"],
  166. positioningStrategy: "fixed",
  167. dataAttributes: ['url', 'value', 'id'],
  168. blotName: 'MessengerMentionBlot',
  169. minChars: 1,
  170. listItemClass: 'ql-mention-list-item',
  171. mentionContainerClass: 'ql-mention-list-container bx-popup bx-popup-color-bg bx-popup-border' + sTailWindClasses,
  172. linkTarget: '_blank',
  173. renderItem: data => `<span class="bx-def-font-small bx-def-padding-right">${data.value}</span><img src="${data.thumb}" />`,
  174. renderLoading: () => _t('_bx_messenger_loading'),
  175. source: function (searchTerm, renderList, mentionChar) {
  176. if (searchTerm.length)
  177. $.get("searchExtended.php?action=get_authors", { term: searchTerm }, oData => {
  178. if (Array.isArray(oData) && oData.length) {
  179. renderList(oData.map((oValue => {
  180. const { value, label, url, thumb } = oValue;
  181. return { id: value, value: label, url, thumb };
  182. })));
  183. }
  184. }
  185. , 'json');
  186. }
  187. };
  188. }
  189. initEditor() {
  190. const _this = this;
  191. const Delta = Quill.import('delta');
  192. this.oEditor = new Quill(this.oHtmlEditorObject, {
  193. placeholder: this.oPlaceholder,
  194. theme: 'bubble',
  195. bounds: this.oHtmlEditorObject,
  196. debug: 'error',
  197. modules: Object.assign({
  198. toolbar: _this.showToolbar() && _this.aToolbarSettings,
  199. clipboard: {
  200. matchers: [
  201. [
  202. 'IMG', () => { return { ops: [] } }
  203. ],
  204. [Node.ELEMENT_NODE, function (node, delta) {
  205. return delta.compose(new Delta().retain(delta.length(),
  206. {
  207. color: false,
  208. background: false,
  209. }
  210. ));
  211. }
  212. ]
  213. ],
  214. matchVisual: false
  215. },
  216. keyboard: {
  217. bindings: {
  218. enter: {
  219. key: 13,
  220. shiftKey: false,
  221. handler: (range, context) => {
  222. if (!this.onEnter())
  223. return true;
  224. }
  225. },
  226. up: {
  227. key: 38,
  228. shiftKey: false,
  229. handler: () => this.onUp()
  230. },
  231. esc: {
  232. key: 27,
  233. shiftKey: false,
  234. handler: () => this.onESC()
  235. }
  236. }
  237. }
  238. }, this.oMentions)
  239. });
  240. this.oEditor.on('text-change', function (delta, oldDelta, source) {
  241. if (source === 'user')
  242. _this.onChange();
  243. }).on('selection-change', function (range, oldRange, source) {
  244. if (range && !range.length)
  245. _this.onFocus();
  246. else
  247. _this.onBlur();
  248. })
  249. this.onInit();
  250. }
  251. }