diff --git a/src/Fields/Quill2.php b/src/Fields/Quill2.php new file mode 100644 index 0000000..163810f --- /dev/null +++ b/src/Fields/Quill2.php @@ -0,0 +1,507 @@ + 'height: 200px;', + ]; + } + + protected static function getFactory() + { + return 'text(300)'; + } + + public static function stylesheets($options) + { + return [ + '//cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.bubble.css', + '//cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css', + '//cdn.jsdelivr.net/npm/quill-mention@3.4.0/dist/quill.mention.min.css', + ]; + } + + public static function scripts($options) + { + return [ + '//cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js', + '//cdn.jsdelivr.net/npm/quilljs-markdown@latest/dist/quilljs-markdown.js', + // '//cdn.jsdelivr.net/npm/quill-drag-and-drop-module@0.3.0/quill-module.min.js', + '//cdn.jsdelivr.net/npm/quill-image-drop-and-paste@1.3.0/dist/quill-image-drop-and-paste.min.js', + '//cdn.jsdelivr.net/npm/quill-mention@3.4.0/dist/quill.mention.min.js', + '//cdn.jsdelivr.net/npm/quill-magic-url@4.2.0/dist/index.min.js', + ]; + } + + public static function styles($id, $options) + { + $darkTheme = ''; + + if (! isset($options['theme']) || (is_bool($options['theme']) && $options['theme'])) { + $darkTheme = << + +
+
+ {field} + {errors} +
+ +HTML; + } + + public static function onLoadJs($id, $options) + { + return '_formsjs_quillField'; + } + + public static function onLoadJsData($id, $options) + { + $route = null; + + if (isset($options['upload_route'])) { + $route = route($options['upload_route']); + } + + $mentionAtPath = $options['mention_at_path'] ?? '{at}'; + $mentionHashPath = $options['mention_hash_path'] ?? '{hash}'; + $mentionLinkPath = $options['mention_link_path'] ?? '{link}'; + + $mentions = $options['mention_ats'] ?? []; + $hashValues = $options['mention_hashes'] ?? []; + $links = $options['mention_links'] ?? []; + $theme = $options['quill_theme'] ?? 'snow'; + $placeholder = $options['placeholder'] ?? ''; + $toolbars = $options['toolbars'] ?? [ + 'basic', + 'extra', + 'lists', + 'super_sub', + 'indents', + 'headers', + 'colors', + 'image', + 'video', + ]; + + $toolbars = collect($toolbars); + + throw_if($toolbars->isEmpty(), new \Exception('You cannot have an empty toolbar.')); + + if (is_null($route) && $toolbars->contains('image')) { + throw new \Exception('You need to set an `upload_route` for handling image uploads to Quill.', 1); + } + + $container = [ + ($toolbars->contains('basic')) ? ['bold', 'italic', 'underline', 'strike', ['align' => []], 'link'] : [], + ($toolbars->contains('extra')) ? ['blockquote', 'code-block'] : [], + ($toolbars->contains('lists')) ? [['list' => 'ordered'], ['list' => 'bullet'], ['list' => 'check']] : [], + ($toolbars->contains('super_sub')) ? [['script' => 'sub'], ['script' => 'super']] : [], + ($toolbars->contains('indents')) ? [['indent' => '-1', 'indent' => '+1']] : [], + ($toolbars->contains('headers')) ? [['header' => [1, 2, 3, 4, 5, 6, false]]] : [], + ($toolbars->contains('colors')) ? [['color' => []], ['background' => []]] : [], + ($toolbars->contains('image')) ? ['image'] : [], + ($toolbars->contains('video')) ? ['video'] : [], + ['clean'] + ]; + + return json_encode([ + 'route' => $route, + 'theme' => $theme, + 'mention_at_path' => $mentionAtPath, + 'mention_hash_path' => $mentionHashPath, + 'mention_link_path' => $mentionLinkPath, + 'atValues' => $mentions, + 'hashValues' => $hashValues, + 'linkValues' => $links, + 'placeholder' => $placeholder, + 'container' => $container, + 'markdown' => $options['quill_markdown'] ?? false, + ]); + } + + public static function js($id, $options) + { + return << { + const files = _FileInput.files; + const range = this.quill.getSelection(true); + + if (!files || !files.length) { + console.log('No files selected'); + return; + } + + const _FileFormData = new FormData(); + _FileFormData.append('image', files[0]); + + this.quill.enable(false); + + window.axios + .post(_config.route, _FileFormData) + .then(response => { + this.quill.enable(true); + let range = this.quill.getSelection(true); + this.quill.editor.insertEmbed(range.index, 'image', response.data.file.url); + this.quill.setSelection(range.index + 1, Quill.sources.SILENT); + _FileInput.value = ''; + }) + .catch(error => { + console.log('Image upload failed'); + console.log(error); + this.quill.enable(true); + }); + }); + _container.appendChild(_FileInput); + } + _FileInput.click(); + } + + let _editor_toolbarOptions = { + container: _config.container, + handlers: { + image: window._formsjs_quill_file_upload, + } + }; + + let _route = _config.route; + + window[_instance+'_atValues'] = _config.atValues; + window[_instance+'_hashtagValues'] = _config.hashValues; + window[_instance+'_linkValues'] = _config.linkValues; + + window[_instance] = new Quill('#'+_id+'_Editor', { + theme: _config.theme, + placeholder: _config.placeholder, + modules: { + magicUrl: true, + toolbar: _editor_toolbarOptions, + imageDropAndPaste: { + handler: window._formsjs_quill_file_upload, + }, + mention: { + allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/, + mentionDenotationChars: ["@", "#", "^"], + source: function(searchTerm, renderList, mentionChar) { + let values; + + if (mentionChar === "@") { + values = window[_instance+'_atValues']; + } + + if (mentionChar === "#") { + values = window[_instance+'_hashtagValues']; + } + + if (mentionChar === "^") { + values = window[_instance+'_linkValues']; + } + + if (searchTerm.length === 0) { + renderList(values, searchTerm); + } else { + const matches = []; + for (let i = 0; i < values.length; i++) { + if (~values[i].value.toLowerCase().indexOf(searchTerm.toLowerCase())) { + matches.push(values[i]); + } + + renderList(matches, searchTerm); + } + } + } + }, + keyboard: { + bindings: { + image: { + key: 'i', + ctrlKey: true, + handler: window._formsjs_quill_file_upload, + }, + } + } + } + }); + + if (_config.markdown) { + new QuillMarkdown(window[_instance]); + } + + document.getElementById(_id+'_Editor').firstChild.innerHTML = element.value; + + window[_instance].on('editor-change', function () { + if (document.getElementById(_id).getAttribute('disabled') !== 'disabled') { + element.value = document.getElementById(_id+'_Editor').firstChild.innerHTML; + + let event = new CustomEvent('grafite-form-change', { 'bubbles': true }); + element.dispatchEvent(event); + } + }); + + if (element.disabled) { + window[_instance].enable(false) + } + + // window.addEventListener('mention-hovered', (event) => {console.log('hovered: ', event)}, false); + window.addEventListener('mention-clicked', function (event) { + if (event.value.denotationChar === '^') { + window.location = _config.mention_link_path.replace('{link}', event.value.id); + } + + if (event.value.denotationChar === '@') { + window.location = _config.mention_at_path.replace('{at}', event.value.id); + } + + if (event.value.denotationChar === '#') { + window.location = _config.mention_hash_path.replace('{hash}', event.value.id); + } + }, false); + } + }; +JS; + } +}