BxBaseEditorQuill.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaBaseView UNA Base Representation Classes
  7. * @{
  8. */
  9. /**
  10. * Quill editor representation.
  11. * @see BxDolEditor
  12. */
  13. class BxBaseEditorQuill extends BxDolEditor
  14. {
  15. /**
  16. * Common initialization params
  17. */
  18. protected static $CONF_COMMON = <<<EOS
  19. hljs.configure({ // optionally configure hljs
  20. languages: ['javascript', 'php', 'html', 'css']
  21. });
  22. $( "{bx_var_selector}" ).after( "<div id='{bx_var_editor_name}' class='bx-def-font-inputs bx-form-input-textarea bx-form-input-html bx-form-input-html-quill {bx_var_css_additional_class}'>" + $( "{bx_var_selector}" ).val() + "</div>" );
  23. //$( "{bx_var_selector}" ).hide();
  24. if (typeof bQuillRegistred === 'undefined' && {toolbar}) {
  25. Quill.register("modules/imageUploader", ImageUploader);
  26. bQuillRegistred = true;
  27. }
  28. var Embed = Quill.import('blots/embed');
  29. class ProcLink extends Embed {
  30. static create(value) {
  31. let node = super.create(value);
  32. // give it some margin
  33. node.setAttribute('href', value.id);
  34. node.innerHTML = '@' + value.value;
  35. return node;
  36. }
  37. }
  38. ProcLink.blotName = 'proc-link';
  39. ProcLink.className = 'proc-link';
  40. ProcLink.tagName = 'a';
  41. Quill.register({
  42. 'formats/proc-link': ProcLink
  43. });
  44. var oConfig = {
  45. theme: '{bx_var_skin}',
  46. modules: {
  47. syntax: true,
  48. imageResize: {},
  49. toolbar: {toolbar},
  50. mention: {
  51. allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
  52. mentionDenotationChars: ["@"],
  53. showDenotationChar: false,
  54. blotName: 'proc-link',
  55. source: function (searchTerm, renderList, mentionChar) {
  56. $.getJSON('{bx_url_root}searchExtended.php?action=get_authors&', { term: searchTerm}, function(data){
  57. renderList(data, searchTerm);
  58. });
  59. },
  60. renderItem: function(item, searchTerm){
  61. item.id = item.url;
  62. item.value = item.label;
  63. return '@' + item.value;
  64. },
  65. onSelect: function(item, insertItem){
  66. insertItem(item, false)
  67. }
  68. },
  69. }
  70. };
  71. if ({toolbar}){
  72. oConfig.modules.imageUploader = {
  73. upload: file => {
  74. return new Promise((resolve, reject) => {
  75. const formData = new FormData();
  76. formData.append("file", file);
  77. fetch("{bx_url_root}storage.php?o=sys_images_editor&t=sys_images_editor&a=upload", {
  78. method: "POST",
  79. body: formData
  80. }
  81. )
  82. .then(response => response.json())
  83. .then(result => {
  84. console.log(result);
  85. resolve(result.link);
  86. })
  87. .catch(error => {
  88. reject("Upload failed");
  89. console.error("Error:", error);
  90. });
  91. });
  92. }
  93. }
  94. }
  95. {bx_var_editor_name} = new Quill('#{bx_var_editor_name}', oConfig);
  96. {bx_var_editor_name}.keyboard.addBinding({
  97. key: ' ',
  98. handler: function(range, context) {
  99. bx_editor_on_space_enter ({bx_var_editor_name}, '{bx_var_selector}');
  100. return true;
  101. }
  102. });
  103. {bx_var_editor_name}.keyboard.bindings[13].unshift({
  104. key: 13,
  105. handler: (range, context) => {
  106. bx_editor_on_space_enter ({bx_var_editor_name}, '{bx_var_selector}')
  107. return true;
  108. }
  109. });
  110. {bx_var_editor_name}.on('text-change', function(delta, oldDelta, source) {
  111. $('{bx_var_selector}').val({bx_var_editor_name}.container.firstChild.innerHTML);
  112. });
  113. EOS;
  114. /**
  115. * Standard view initialization params
  116. */
  117. protected static $CONF_STANDARD = "";
  118. /**
  119. * Minimal view initialization params
  120. */
  121. protected static $CONF_MINI = "";
  122. /**
  123. * Full view initialization params
  124. */
  125. protected static $CONF_FULL = "";
  126. protected $_oTemplate;
  127. protected $_bJsCssAdded = false;
  128. public function __construct ($aObject, $oTemplate)
  129. {
  130. parent::__construct ($aObject);
  131. if ($oTemplate)
  132. $this->_oTemplate = $oTemplate;
  133. else
  134. $this->_oTemplate = BxDolTemplate::getInstance();
  135. }
  136. /**
  137. * Attach editor to HTML element, in most cases - textarea.
  138. * @param $sSelector - jQuery selector to attach editor to.
  139. * @param $iViewMode - editor view mode: BX_EDITOR_STANDARD, BX_EDITOR_MINI, BX_EDITOR_FULL
  140. * @param $bDynamicMode - is AJAX mode or not, the HTML with editor area is loaded dynamically.
  141. */
  142. public function attachEditor ($sSelector, $iViewMode = BX_EDITOR_STANDARD, $bDynamicMode = false, $aAttrs = [])
  143. {
  144. // set visual mode
  145. switch ($iViewMode) {
  146. case BX_EDITOR_MINI:
  147. $sToolbarItems = getParam('sys_quill_toolbar_mini');
  148. $sCustomInit = self::$CONF_MINI;
  149. break;
  150. case BX_EDITOR_FULL:
  151. $sToolbarItems = getParam('sys_quill_toolbar_full');
  152. $sCustomInit = self::$CONF_FULL;
  153. break;
  154. case BX_EDITOR_STANDARD:
  155. default:
  156. $sToolbarItems = getParam('sys_quill_toolbar_standard');
  157. $sCustomInit = self::$CONF_STANDARD;
  158. }
  159. if ($this->_sButtonsCustom !== false) {
  160. $sToolbarItems = $this->_sButtonsCustom;
  161. }
  162. $sCss = 'editor.less';
  163. $aCss = BxDolTemplate::getInstance()->_lessCss(array(
  164. 'path' => $this->_oTemplate->getCssPath($sCss),
  165. 'url' => $this->_oTemplate->getCssUrl($sCss)
  166. ));
  167. $sEditorName = 'quill_' . str_replace(['-', ' '], '_', $aAttrs['form_id'] . '_' . $aAttrs['element_name']);
  168. // initialize editor
  169. $sInitEditor = $this->_replaceMarkers(self::$CONF_COMMON, array(
  170. 'bx_var_custom_init' => $sCustomInit,
  171. 'bx_var_selector' => bx_js_string($sSelector, BX_ESCAPE_STR_APOS),
  172. 'bx_var_form_id' => $aAttrs['form_id'],
  173. 'toolbar' => $sToolbarItems ? '[' . $sToolbarItems . ']' : 'false',
  174. 'bx_var_css_additional_class' => $sToolbarItems ? '' : 'bx-form-input-html-quill-empty',
  175. 'bx_var_element_name' => str_replace(['-', ' '], '_', $aAttrs['element_name']),
  176. 'bx_var_editor_name' => $sEditorName,
  177. 'bx_var_skin' => bx_js_string($this->_aObject['skin'], BX_ESCAPE_STR_APOS),
  178. 'bx_url_root' => bx_js_string(BX_DOL_URL_ROOT, BX_ESCAPE_STR_APOS),
  179. 'bx_var_css_path' => bx_js_string($aCss['url'], BX_ESCAPE_STR_APOS),
  180. ));
  181. if ($bDynamicMode) {
  182. list($aJs, $aCss) = $this->_getJsCss(true);
  183. $sCss = $this->_oTemplate->addCss($aCss, true);
  184. $sCss = $this->_oTemplate->addCss([
  185. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/|quill.' . $this->_aObject['skin'] . '.css',
  186. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/|quill.custom.css',
  187. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/|quill.mention.css',
  188. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'highlight/|default.min.css',
  189. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/quill.imageUploader.min.css'
  190. ], true);
  191. $sScript = $sCss . "<script>
  192. if (typeof bQuillEditorInited === 'undefined') {
  193. bx_get_scripts(" . json_encode($aJs) . ", function () {
  194. bQuillEditorInited = true;
  195. $sInitEditor
  196. });
  197. } else {
  198. setTimeout(function () {
  199. $sInitEditor
  200. }, 10); // wait while html is rendered in case of dynamic adding html with tinymce
  201. }
  202. if (typeof bQuillEditorInited === 'undefined')
  203. bQuillEditorInited = true;
  204. </script>";
  205. } else {
  206. $sScript = "
  207. <script>
  208. var " . $sEditorName . ";
  209. $(document).ready(function () {
  210. $sInitEditor
  211. });
  212. </script>";
  213. }
  214. return $this->_addJsCss($bDynamicMode) . $sScript;
  215. }
  216. /**
  217. * Add css/js files which are needed for editor display and functionality.
  218. */
  219. protected function _addJsCss($bDynamicMode = false, $sInitEditor = '')
  220. {
  221. if ($bDynamicMode)
  222. return '';
  223. if ($this->_bJsCssAdded)
  224. return '';
  225. list($aJs, $aCss) = $this->_getJsCss();
  226. $this->_oTemplate->addJs($aJs);
  227. $this->_oTemplate->addCss($aCss);
  228. $this->_bJsCssAdded = true;
  229. return '';
  230. }
  231. protected function _getJsCss($bUseUrlsForJs = false)
  232. {
  233. $sJsPrefix = $bUseUrlsForJs ? BX_DOL_URL_PLUGINS_PUBLIC : BX_DIRECTORY_PATH_PLUGINS_PUBLIC;
  234. $sJsSuffix = $bUseUrlsForJs ? '' : '|';
  235. $aJs = array(
  236. $sJsPrefix . 'highlight/' . $sJsSuffix . 'highlight.min.js',
  237. $sJsPrefix . 'quill/' . $sJsSuffix . 'quill.min.js',
  238. $sJsPrefix . 'quill/' . $sJsSuffix . 'quill.mention.js',
  239. $sJsPrefix . 'quill/' . $sJsSuffix . 'quill.imageUploader.min.js',
  240. $sJsPrefix . 'quill/' . $sJsSuffix . 'quill.custom.js',
  241. $sJsPrefix . 'quill/' . $sJsSuffix . 'image-resize.min.js',
  242. );
  243. $aCss = array(
  244. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/|quill.' . $this->_aObject['skin'] . '.css',
  245. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/|quill.custom.css',
  246. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/|quill.mention.css',
  247. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'highlight/|default.min.css',
  248. BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'quill/quill.imageUploader.min.css'
  249. );
  250. return array($aJs, $aCss);
  251. }
  252. }
  253. /** @} */