From 680e95fd6b1e90cc0a4759a56c7bc91d6c16055c Mon Sep 17 00:00:00 2001 From: takahashim Date: Sun, 7 Apr 2024 20:08:15 +0900 Subject: [PATCH 1/8] copy app/packs/src/decidim/editor/extensions/decidim_kit --- .../editor/extensions/decidim_kit/index.js | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/packs/src/decidim/editor/extensions/decidim_kit/index.js diff --git a/app/packs/src/decidim/editor/extensions/decidim_kit/index.js b/app/packs/src/decidim/editor/extensions/decidim_kit/index.js new file mode 100644 index 000000000..33da43f28 --- /dev/null +++ b/app/packs/src/decidim/editor/extensions/decidim_kit/index.js @@ -0,0 +1,84 @@ +import { Extension } from "@tiptap/core"; + +import StarterKit from "@tiptap/starter-kit"; +import CodeBlock from "@tiptap/extension-code-block"; +import Underline from "@tiptap/extension-underline"; + +import CharacterCount from "src/decidim/editor/extensions/character_count"; +import Bold from "src/decidim/editor/extensions/bold"; +import Dialog from "src/decidim/editor/extensions/dialog"; +import Hashtag from "src/decidim/editor/extensions/hashtag"; +import Heading from "src/decidim/editor/extensions/heading"; +import OrderedList from "src/decidim/editor/extensions/ordered_list"; +import Image from "src/decidim/editor/extensions/image"; +import Indent from "src/decidim/editor/extensions/indent"; +import Link from "src/decidim/editor/extensions/link"; +import Mention from "src/decidim/editor/extensions/mention"; +import VideoEmbed from "src/decidim/editor/extensions/video_embed"; +import Emoji from "src/decidim/editor/extensions/emoji"; + +export default Extension.create({ + name: "decidimKit", + + addOptions() { + return { + characterCount: { limit: null }, + heading: { levels: [2, 3, 4, 5, 6] }, + link: { allowTargetControl: false }, + videoEmbed: false, + image: { + uploadDialogSelector: null, + uploadImagesPath: null, + contentTypes: /^image\/(jpe?g|png|svg|webp)$/i + }, + hashtag: false, + mention: false, + emoji: false + }; + }, + + addExtensions() { + const extensions = [ + StarterKit.configure({ + heading: false, + bold: false, + orderedList: false, + codeBlock: false + }), + CharacterCount.configure(this.options.characterCount), + Link.configure({ openOnClick: false, ...this.options.link }), + Bold, + Dialog, + Indent, + OrderedList, + CodeBlock, + Underline + ]; + + if (this.options.heading !== false) { + extensions.push(Heading.configure(this.options.heading)); + } + + if (this.options.videoEmbed !== false) { + extensions.push(VideoEmbed.configure(this.options.videoEmbed)); + } + + if (this.options.image !== false && this.options.image.uploadDialogSelector) { + extensions.push(Image.configure(this.options.image)); + } + + if (this.options.hashtag !== false) { + extensions.push(Hashtag.configure(this.options.hashtag)); + } + + if (this.options.mention !== false) { + extensions.push(Mention.configure(this.options.mention)); + } + + if (this.options.emoji !== false) { + extensions.push(Emoji.configure(this.options.emoji)); + } + + return extensions; + } +}); From 2ee0f291cac276ee88551bbdce0749a15eae843d Mon Sep 17 00:00:00 2001 From: takahashim Date: Sun, 7 Apr 2024 20:09:57 +0900 Subject: [PATCH 2/8] copy app/packs/src/decidim/editor/toolbar.js --- app/packs/src/decidim/editor/toolbar.js | 263 ++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 app/packs/src/decidim/editor/toolbar.js diff --git a/app/packs/src/decidim/editor/toolbar.js b/app/packs/src/decidim/editor/toolbar.js new file mode 100644 index 000000000..62836aac1 --- /dev/null +++ b/app/packs/src/decidim/editor/toolbar.js @@ -0,0 +1,263 @@ +import { getDictionary } from "src/decidim/i18n"; +import html from "src/decidim/editor/utilities/html"; + +import iconsUrl from "images/decidim/remixicon.symbol.svg"; + +const createIcon = (iconName) => { + return ``; +}; + +const createEditorToolbarGroup = () => { + return html("div").dom((el) => el.classList.add("editor-toolbar-group")); +}; + +const createEditorToolbarToggle = (editor, { type, label, icon, action, activatable = true }) => { + return html("button").dom((ctrl) => { + ctrl.classList.add("editor-toolbar-control"); + ctrl.dataset.editorType = type; + if (activatable) { + ctrl.dataset.editorSelectionType = type; + } + ctrl.type = "button"; + ctrl.ariaLabel = label; + ctrl.title = label; + ctrl.innerHTML = createIcon(icon); + ctrl.addEventListener("click", (ev) => { + ev.preventDefault(); + editor.commands.focus(); + action(); + }) + }); +}; + +const createEditorToolbarSelect = (editor, { type, label, options, action, activatable = true }) => { + return html("select").dom((ctrl) => { + ctrl.classList.add("editor-toolbar-control", "!pr-8"); + ctrl.dataset.editorType = type; + if (activatable) { + ctrl.dataset.editorSelectionType = type; + } + ctrl.ariaLabel = label; + ctrl.title = label; + options.forEach(({ label: optionLabel, value }) => { + const option = document.createElement("option"); + option.setAttribute("value", value); + option.textContent = optionLabel; + ctrl.appendChild(option); + }); + ctrl.addEventListener("change", () => { + editor.commands.focus(); + action(ctrl.value); + }); + }) +}; + +/** + * Creates the editor toolbar for the given editor instance. + * + * @param {Editor} editor An instance of the rich text editor. + * @returns {HTMLElement} The toolbar element + */ +export default function createEditorToolbar(editor) { + const i18n = getDictionary("editor.toolbar"); + + const supported = { nodes: [], marks: [], extensions: [] }; + editor.extensionManager.extensions.forEach((ext) => { + if (ext.type === "node") { + supported.nodes.push(ext.name); + } else if (ext.type === "mark") { + supported.marks.push(ext.name); + } else if (ext.type === "extension") { + supported.extensions.push(ext.name); + } + }); + + // Create the toolbar element + const toolbar = html("div"). + dom((el) => el.classList.add("editor-toolbar")). + append( + // Text style controls + createEditorToolbarGroup(editor).append( + createEditorToolbarSelect(editor, { + type: "heading", + label: i18n["control.heading"], + options: [ + { value: "normal", label: i18n["textStyle.normal"] }, + { value: 2, label: i18n["textStyle.heading"].replace("%level%", 2) }, + { value: 3, label: i18n["textStyle.heading"].replace("%level%", 3) }, + { value: 4, label: i18n["textStyle.heading"].replace("%level%", 4) }, + { value: 5, label: i18n["textStyle.heading"].replace("%level%", 5) }, + { value: 6, label: i18n["textStyle.heading"].replace("%level%", 6) } + ], + action: (value) => { + if (value === "normal") { + editor.commands.setParagraph(); + } else { + editor.commands.toggleHeading({ level: parseInt(value, 10) }); + } + } + }).render(supported.nodes.includes("heading")) + ) + ). + append( + // Basic styling controls + createEditorToolbarGroup(editor).append( + createEditorToolbarToggle(editor, { + type: "bold", + icon: "bold", + label: i18n["control.bold"], + action: () => editor.commands.toggleBold() + }).render(supported.marks.includes("bold")), + createEditorToolbarToggle(editor, { + type: "italic", + icon: "italic", + label: i18n["control.italic"], + action: () => editor.commands.toggleItalic() + }).render(supported.marks.includes("italic")), + createEditorToolbarToggle(editor, { + type: "underline", + icon: "underline", + label: i18n["control.underline"], + action: () => editor.commands.toggleUnderline() + }).render(supported.marks.includes("underline")), + createEditorToolbarToggle(editor, { + type: "hardBreak", + icon: "text-wrap", + label: i18n["control.hardBreak"], + activatable: false, + action: () => editor.commands.setHardBreak() + }).render(supported.nodes.includes("hardBreak")) + ) + ). + append( + // List controls + createEditorToolbarGroup(editor).append( + createEditorToolbarToggle(editor, { + type: "orderedList", + icon: "list-ordered", + label: i18n["control.orderedList"], + action: () => editor.commands.toggleOrderedList() + }).render(supported.nodes.includes("orderedList")), + createEditorToolbarToggle(editor, { + type: "bulletList", + icon: "list-unordered", + label: i18n["control.bulletList"], + action: () => editor.commands.toggleBulletList() + }).render(supported.nodes.includes("bulletList")) + ) + ). + append( + // Link and erase styles + createEditorToolbarGroup(editor).append( + createEditorToolbarToggle(editor, { + type: "link", + icon: "link", + label: i18n["control.link"], + action: () => editor.commands.linkDialog() + }).render(supported.marks.includes("link")), + createEditorToolbarToggle(editor, { + type: "common:eraseStyles", + icon: "eraser-line", + label: i18n["control.common.eraseStyles"], + activatable: false, + action: () => { + if (editor.isActive("link") && editor.view.state.selection.empty) { + const originalPos = editor.view.state.selection.anchor; + editor.chain().focus().extendMarkRange("link").unsetLink().setTextSelection(originalPos).run(); + } else { + editor.chain().focus().clearNodes().unsetAllMarks().run(); + } + } + }).render( + supported.nodes.includes("heading") || + supported.marks.includes("bold") || + supported.marks.includes("italic") || + supported.marks.includes("underline") || + supported.nodes.includes("hardBreak") || + supported.nodes.includes("orderedList") || + supported.nodes.includes("bulletList") || + supported.marks.includes("link") + ) + ) + ). + append( + // Block styling + createEditorToolbarGroup(editor).append( + createEditorToolbarToggle(editor, { + type: "codeBlock", + icon: "code-line", + label: i18n["control.codeBlock"], + action: () => editor.commands.toggleCodeBlock() + }).render(supported.nodes.includes("codeBlock")), + createEditorToolbarToggle(editor, { + type: "blockquote", + icon: "double-quotes-l", + label: i18n["control.blockquote"], + action: () => editor.commands.toggleBlockquote() + }).render(supported.nodes.includes("blockquote")) + ) + ). + append( + // Indent and outdent + createEditorToolbarGroup(editor).append( + createEditorToolbarToggle(editor, { + type: "indent:indent", + icon: "indent-increase", + label: i18n["control.indent.indent"], + activatable: false, + action: () => editor.commands.indent() + }).render(supported.extensions.includes("indent")), + createEditorToolbarToggle(editor, { + type: "indent:outdent", + icon: "indent-decrease", + label: i18n["control.indent.outdent"], + activatable: false, + action: () => editor.commands.outdent() + }).render(supported.extensions.includes("indent")) + ) + ). + append( + // Multimedia + createEditorToolbarGroup(editor).append( + createEditorToolbarToggle(editor, { + type: "videoEmbed", + icon: "video-line", + label: i18n["control.videoEmbed"], + action: () => editor.commands.videoEmbedDialog() + }).render(supported.nodes.includes("videoEmbed")), + createEditorToolbarToggle(editor, { + type: "image", + icon: "image-line", + label: i18n["control.image"], + action: () => editor.commands.imageDialog() + }).render(supported.nodes.includes("image")) + ) + ). + render() + ; + + const selectionControls = toolbar.querySelectorAll(".editor-toolbar-control[data-editor-selection-type]"); + const headingSelect = toolbar.querySelector(".editor-toolbar-control[data-editor-type='heading']"); + const selectionUpdated = () => { + if (editor.isActive("heading")) { + const { level } = editor.getAttributes("heading"); + headingSelect.value = `${level}`; + } else if (headingSelect) { + headingSelect.value = "normal"; + } + + selectionControls.forEach((ctrl) => { + if (editor.isActive(ctrl.dataset.editorSelectionType)) { + ctrl.classList.add("active"); + } else { + ctrl.classList.remove("active"); + } + }); + } + editor.on("update", selectionUpdated); + editor.on("selectionUpdate", selectionUpdated); + + return toolbar; +}; From ebc76a63d65c038ad5f7a103667d4b76119afa07 Mon Sep 17 00:00:00 2001 From: takahashim Date: Sun, 7 Apr 2024 23:51:33 +0900 Subject: [PATCH 3/8] Add HtmlEdit extension for Tiptap --- .../cfj/editor/extensions/html_edit/index.js | 145 ++++++++++++++++++ .../editor/extensions/decidim_kit/index.js | 2 + app/packs/src/decidim/editor/toolbar.js | 11 ++ .../decidim/cfj/tiptap_html_edit.scss | 44 ++++++ .../decidim/decidim_application.scss | 2 +- 5 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js create mode 100644 app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss diff --git a/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js b/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js new file mode 100644 index 000000000..34d970df6 --- /dev/null +++ b/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js @@ -0,0 +1,145 @@ +import { Editor, Extension } from "@tiptap/core"; +import StarterKit from "@tiptap/starter-kit"; + +/** + * HTML edit extension for the Tiptap editor. + */ +export default Extension.create({ + name: 'htmlEdit', + + onCreate() { + const htmlEditButtonOptions = { + title: "HTMLソースを編集", + okText: "OK", + cancelText: "キャンセル", + }; + + const createEditorContainer = (options) => { + const overlayContainer = document.createElement("div"); + overlayContainer.setAttribute("class", "html-edit-overlay"); + const popupContainer = document.createElement("div"); + popupContainer.setAttribute("class", "html-edit-popup-container"); + const popupTitle = document.createElement("div"); + popupTitle.setAttribute("class", "html-edit-popup-title"); + popupTitle.innerText = options.title; + const htmlEditor = document.createElement("div"); + htmlEditor.setAttribute("class", "html-edit-box"); + const htmlEditTextarea = document.createElement("textarea"); + htmlEditTextarea.setAttribute("class", "html-edit-textarea"); + const buttonGroup = document.createElement("div"); + buttonGroup.setAttribute("class", "html-edit-button-group"); + const buttonCancel = document.createElement("button"); + buttonCancel.setAttribute("class", "html-edit-button-cancel"); + buttonCancel.innerHTML = options.cancelText; + const buttonOk = document.createElement("button"); + buttonOk.setAttribute("class", "html-edit-button-save"); + buttonOk.innerHTML = options.okText; + + buttonGroup.appendChild(buttonCancel); + buttonGroup.appendChild(buttonOk); + htmlEditor.appendChild(popupTitle); + htmlEditor.appendChild(htmlEditTextarea); + htmlEditor.appendChild(buttonGroup); + overlayContainer.appendChild(htmlEditor); + document.body.appendChild(overlayContainer); + + buttonOk.addEventListener('click', () => { + const updatedHtml = htmlEditTextarea.value; + this.editor.commands.setContent(updatedHtml, { html: true }); + overlayContainer.style.display = 'none'; + }); + + buttonCancel.addEventListener('click', () => { + overlayContainer.style.display = 'none'; + }); + }; + + /* add Editor if html-editor-overlay is not found */ + if (document.querySelectorAll('.html-edit-overlay').length === 0) { + createEditorContainer(htmlEditButtonOptions); + } + }, + + addCommands() { + return { + openHtmlEditModal: () => ({ editor }) => { + + // Adapted FROM jsfiddle here: https://jsfiddle.net/buksy/rxucg1gd/ + const formatHtml = (code) => { + const whitespace = " ".repeat(2); // Default indenting 4 whitespaces + let currentIndent = 0; + const newlineChar = "\n"; + let prevChar = null; + let char = null; + let nextChar = null; + + let result = ""; + for (let pos = 0; pos <= code.length; pos++) { + prevChar = char; + char = code.substr(pos, 1); + nextChar = code.substr(pos + 1, 1); + + const isBrTag = code.substr(pos, 4) === "
"; + const isOpeningTag = char === "<" && nextChar !== "/" && !isBrTag; + const isClosingTag = char === "<" && nextChar === "/" && !isBrTag; + const isTagEnd = prevChar === ">" && char !== "<" && currentIndent > 0; + const isTagNext = !isBrTag && !isOpeningTag && !isClosingTag && isTagEnd && code.substr(pos, code.substr(pos).indexOf("<")).trim() === ""; + if (isBrTag) { + // If opening tag, add newline character and indention + result += newlineChar; + currentIndent--; + pos += 4; + } + if (isOpeningTag) { + // If opening tag, add newline character and indention + result += newlineChar + whitespace.repeat(currentIndent); + currentIndent++; + } + // if Closing tag, add newline and indention + else if (isClosingTag) { + // If there're more closing tags than opening + if (--currentIndent < 0) currentIndent = 0; + result += newlineChar + whitespace.repeat(currentIndent); + } + // remove multiple whitespaces + else if (char === " " && nextChar === " ") + char = ""; + // remove empty lines + else if (char === newlineChar) { + //debugger; + if (code.substr(pos, code.substr(pos).indexOf("<")).trim() === "") + char = ""; + } + if(isTagEnd && !isTagNext) { + result += newlineChar + whitespace.repeat(currentIndent); + } + + result += char; + } + console.dir({ + before: code, + after: result + }); + return result; + } + + const noNewlines = (src) => { + const replaced = src + .replace(/\s+/g, " ") // convert multiple spaces to a single space. This is how HTML treats them + .replace(/(<[^\/<>]+>)\s+/g, "$1") // remove spaces after the start of a new tag + .replace(/<\/(p|ol|ul)>\s/g, "") // remove spaces after the end of lists and paragraphs, they tend to break quill + .replace(/\s<(p|ol|ul)>/g, "<$1>") // remove spaces before the start of lists and paragraphs, they tend to break quill + .replace(/<\/li>\s
  • /g, "
  • ") // remove spaces between list items, they tend to break quill + .replace(/\s<\//g, "]+>)\s(<[^\/<>]+>)/g, "$1$2") // remove space between multiple starting tags + .trim(); + return replaced; + }; + + const html = editor.getHTML(); + document.querySelector('.html-edit-textarea').value = formatHtml(html); + document.querySelector('.html-edit-overlay').style.display = 'flex'; + } + }; + } +}); diff --git a/app/packs/src/decidim/editor/extensions/decidim_kit/index.js b/app/packs/src/decidim/editor/extensions/decidim_kit/index.js index 33da43f28..e7056d91b 100644 --- a/app/packs/src/decidim/editor/extensions/decidim_kit/index.js +++ b/app/packs/src/decidim/editor/extensions/decidim_kit/index.js @@ -16,6 +16,7 @@ import Link from "src/decidim/editor/extensions/link"; import Mention from "src/decidim/editor/extensions/mention"; import VideoEmbed from "src/decidim/editor/extensions/video_embed"; import Emoji from "src/decidim/editor/extensions/emoji"; +import HtmlEdit from "src/decidim/cfj/editor/extensions/html_edit"; export default Extension.create({ name: "decidimKit", @@ -52,6 +53,7 @@ export default Extension.create({ Indent, OrderedList, CodeBlock, + HtmlEdit, Underline ]; diff --git a/app/packs/src/decidim/editor/toolbar.js b/app/packs/src/decidim/editor/toolbar.js index 62836aac1..4203ac157 100644 --- a/app/packs/src/decidim/editor/toolbar.js +++ b/app/packs/src/decidim/editor/toolbar.js @@ -235,6 +235,17 @@ export default function createEditorToolbar(editor) { }).render(supported.nodes.includes("image")) ) ). + append( + // Html Tag Edit + createEditorToolbarGroup(editor).append( + createEditorToolbarToggle(editor, { + type: "htmlEdit", + icon: "file-text-line", + label: "HTML Edit", + action: () => editor.commands.openHtmlEditModal() + }).render(true) + ) + ). render() ; diff --git a/app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss b/app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss new file mode 100644 index 000000000..3baadf5ac --- /dev/null +++ b/app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss @@ -0,0 +1,44 @@ +.html-edit-overlay { + background: #0000007d; + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + justify-content: center; + align-items: center; +} +.html-edit-box { + background: #ddd; + padding: 20px; + border-radius: 10px; +} +.html-edit-textarea { + background: #fff; + left: 15px; + min-width: 600px; + min-height: 300px; +} +.html-edit-button-group { + position: absolute; + bottom: 100px; + transform: scale(1.5); + left: calc(50% - 60px); +} +.html-edit-button-cancel { + margin-right: 20px; + background: #ddd; + padding: 10px; + border-radius: 10px; +} +.html-edit-button-save { + margin-right: 20px; + background: #ddd; + padding: 10px; + border-radius: 10px; +} +.html-edit-popup-title { + margin: 0; + display: block; +} diff --git a/app/packs/stylesheets/decidim/decidim_application.scss b/app/packs/stylesheets/decidim/decidim_application.scss index fc4049938..cbf83c945 100644 --- a/app/packs/stylesheets/decidim/decidim_application.scss +++ b/app/packs/stylesheets/decidim/decidim_application.scss @@ -10,4 +10,4 @@ @import "./cfj/forms"; @import "./cfj/search"; @import "./cfj/media_print"; -@import "./cfj/ql_html_editor"; +@import "./cfj/tiptap_html_edit"; From 3dcd5df72d2f2acbcc641116955d4b2aecc9afe8 Mon Sep 17 00:00:00 2001 From: takahashim Date: Mon, 8 Apr 2024 00:43:10 +0900 Subject: [PATCH 4/8] fix to add popupContainer --- app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js b/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js index 34d970df6..deffa1798 100644 --- a/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js +++ b/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js @@ -40,7 +40,8 @@ export default Extension.create({ htmlEditor.appendChild(popupTitle); htmlEditor.appendChild(htmlEditTextarea); htmlEditor.appendChild(buttonGroup); - overlayContainer.appendChild(htmlEditor); + popupContainer.appendChild(htmlEditor); + overlayContainer.appendChild(popupContainer); document.body.appendChild(overlayContainer); buttonOk.addEventListener('click', () => { From d450c339efe25546aa1ae53efee9f214a9509bfb Mon Sep 17 00:00:00 2001 From: takahashim Date: Mon, 8 Apr 2024 00:43:21 +0900 Subject: [PATCH 5/8] fix style --- .../decidim/cfj/tiptap_html_edit.scss | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss b/app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss index 3baadf5ac..d35810aeb 100644 --- a/app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss +++ b/app/packs/stylesheets/decidim/cfj/tiptap_html_edit.scss @@ -1,3 +1,6 @@ +/* + * for TipTap HTML Edit extension + */ .html-edit-overlay { background: #0000007d; display: none; @@ -9,33 +12,43 @@ justify-content: center; align-items: center; } +.html-edit-popup-container { + position: relative; + background: #ddd; + margin-left: 120px; + width: calc(100% - 160px); + height: calc(100% - 40px); + padding: 20px; +} .html-edit-box { - background: #ddd; - padding: 20px; - border-radius: 10px; + background: #ddd; + position: absolute; + width: calc(100% - 40px); + height: calc(100% - 40px); + padding: 20px; } .html-edit-textarea { background: #fff; - left: 15px; + width: calc(100% - 20px); + height: calc(100% - 80px); min-width: 600px; min-height: 300px; } .html-edit-button-group { position: absolute; - bottom: 100px; + bottom: 20px; transform: scale(1.5); left: calc(50% - 60px); } .html-edit-button-cancel { margin-right: 20px; - background: #ddd; - padding: 10px; + background: #ccc; + padding: 5px 20px; border-radius: 10px; } .html-edit-button-save { - margin-right: 20px; - background: #ddd; - padding: 10px; + background: #ccc; + padding: 5px 20px; border-radius: 10px; } .html-edit-popup-title { From 3421daedca5617c84d9798fa3819e17cea69c903 Mon Sep 17 00:00:00 2001 From: takahashim Date: Mon, 8 Apr 2024 00:43:45 +0900 Subject: [PATCH 6/8] add missing locale --- config/locales/ja.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 7f944f1f8..04ce40568 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -53,6 +53,8 @@ ja: officializations: index: nickname: アカウントID + menu: + components: コンポーネント officializations: show_email_modal: hidden: 非表示 From 3ece869c338d827b651b8cfce587772382ff7a2a Mon Sep 17 00:00:00 2001 From: takahashim Date: Mon, 8 Apr 2024 01:15:22 +0900 Subject: [PATCH 7/8] fix tag handling from/to editor --- .../cfj/editor/extensions/html_edit/index.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js b/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js index deffa1798..fd6e1ba27 100644 --- a/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js +++ b/app/packs/src/decidim/cfj/editor/extensions/html_edit/index.js @@ -14,6 +14,19 @@ export default Extension.create({ cancelText: "キャンセル", }; + const noNewlines = (src) => { + const replaced = src + .replace(/\s+/g, " ") // convert multiple spaces to a single space. This is how HTML treats them + .replace(/(<[^\/<>]+>)\s+/g, "$1") // remove spaces after the start of a new tag + .replace(/<\/(p|ol|ul)>\s/g, "") // remove spaces after the end of lists and paragraphs, they tend to break quill + .replace(/\s<(p|ol|ul)>/g, "<$1>") // remove spaces before the start of lists and paragraphs, they tend to break quill + .replace(/<\/li>\s
  • /g, "
  • ") // remove spaces between list items, they tend to break quill + .replace(/\s<\//g, "]+>)\s(<[^\/<>]+>)/g, "$1$2") // remove space between multiple starting tags + .trim(); + return replaced; + }; + const createEditorContainer = (options) => { const overlayContainer = document.createElement("div"); overlayContainer.setAttribute("class", "html-edit-overlay"); @@ -45,7 +58,7 @@ export default Extension.create({ document.body.appendChild(overlayContainer); buttonOk.addEventListener('click', () => { - const updatedHtml = htmlEditTextarea.value; + const updatedHtml = noNewlines(htmlEditTextarea.value); this.editor.commands.setContent(updatedHtml, { html: true }); overlayContainer.style.display = 'none'; }); @@ -124,19 +137,6 @@ export default Extension.create({ return result; } - const noNewlines = (src) => { - const replaced = src - .replace(/\s+/g, " ") // convert multiple spaces to a single space. This is how HTML treats them - .replace(/(<[^\/<>]+>)\s+/g, "$1") // remove spaces after the start of a new tag - .replace(/<\/(p|ol|ul)>\s/g, "") // remove spaces after the end of lists and paragraphs, they tend to break quill - .replace(/\s<(p|ol|ul)>/g, "<$1>") // remove spaces before the start of lists and paragraphs, they tend to break quill - .replace(/<\/li>\s
  • /g, "
  • ") // remove spaces between list items, they tend to break quill - .replace(/\s<\//g, "]+>)\s(<[^\/<>]+>)/g, "$1$2") // remove space between multiple starting tags - .trim(); - return replaced; - }; - const html = editor.getHTML(); document.querySelector('.html-edit-textarea').value = formatHtml(html); document.querySelector('.html-edit-overlay').style.display = 'flex'; From 4e2c8a6a037f115524d3eb3c59a74cae0a1cfa6b Mon Sep 17 00:00:00 2001 From: takahashim Date: Mon, 8 Apr 2024 01:19:43 +0900 Subject: [PATCH 8/8] remove files for quill --- app/packs/src/decidim/cfj/html_edit_button.js | 175 ------------------ .../decidim/cfj/ql_html_editor.scss | 53 ------ 2 files changed, 228 deletions(-) delete mode 100644 app/packs/src/decidim/cfj/html_edit_button.js delete mode 100644 app/packs/stylesheets/decidim/cfj/ql_html_editor.scss diff --git a/app/packs/src/decidim/cfj/html_edit_button.js b/app/packs/src/decidim/cfj/html_edit_button.js deleted file mode 100644 index 1b8a5c532..000000000 --- a/app/packs/src/decidim/cfj/html_edit_button.js +++ /dev/null @@ -1,175 +0,0 @@ -export class HtmlEditButton { - constructor(quill, options) { - let debug = options && options.debug; - console.log("logging enabled"); - // Add button to all quill toolbar instances - const toolbarModule = quill.getModule("toolbar"); - if (!toolbarModule) { - throw new Error( - 'quill.HtmlEditButton requires the "toolbar" module to be included too' - ); - } - this.registerDivModule(); - let toolbarEl = toolbarModule.container; - const buttonContainer = document.createElement("span"); - buttonContainer.setAttribute("class", "ql-formats"); - const button = document.createElement("button"); - button.innerHTML = options.buttonHTML || "<>"; - button.title = options.buttonTitle || "Show HTML source"; - button.onclick = function(e) { - e.preventDefault(); - this.launchPopupEditor(quill, options); - }; - buttonContainer.appendChild(button); - toolbarEl.appendChild(buttonContainer); - } - - registerDivModule() { - // To allow divs to be inserted into html editor - // obtained from issue: https://github.com/quilljs/quill/issues/2040 - var Block = Quill.import("blots/block"); - class Div extends Block {} - Div.tagName = "div"; - Div.blotName = "div"; - Div.allowedChildren = Block.allowedChildren; - Div.allowedChildren.push(Block); - Quill.register(Div); - } - - launchPopupEditor(quill, options) { - const htmlFromEditor = quill.container.querySelector(".ql-editor").innerHTML; - const popupContainer = document.createElement("div"); - const overlayContainer = document.createElement("div"); - const msg = options.msg || 'Edit HTML here, when you click "OK" the quill editor\'s contents will be replaced'; - const cancelText = options.cancelText || "Cancel"; - const okText = options.okText || "Ok"; - - overlayContainer.setAttribute("class", "ql-html-overlayContainer"); - popupContainer.setAttribute("class", "ql-html-popupContainer"); - const popupTitle = document.createElement("i"); - popupTitle.setAttribute("class", "ql-html-popupTitle"); - popupTitle.innerText = msg; - const textContainer = document.createElement("div"); - textContainer.appendChild(popupTitle); - textContainer.setAttribute("class", "ql-html-textContainer"); - const codeBlock = document.createElement("pre"); - codeBlock.setAttribute("data-language", "xml"); - codeBlock.innerText = this.formatHTML(htmlFromEditor); - const htmlEditor = document.createElement("div"); - htmlEditor.setAttribute("class", "ql-html-textArea"); - const buttonCancel = document.createElement("button"); - buttonCancel.innerHTML = cancelText; - buttonCancel.setAttribute("class", "ql-html-buttonCancel"); - const buttonOk = document.createElement("button"); - buttonOk.innerHTML = okText; - const buttonGroup = document.createElement("div"); - buttonGroup.setAttribute("class", "ql-html-buttonGroup"); - - buttonGroup.appendChild(buttonCancel); - buttonGroup.appendChild(buttonOk); - htmlEditor.appendChild(codeBlock); - textContainer.appendChild(htmlEditor); - textContainer.appendChild(buttonGroup); - popupContainer.appendChild(textContainer); - overlayContainer.appendChild(popupContainer); - document.body.appendChild(overlayContainer); - var editor = new Quill(htmlEditor, { - modules: { syntax: options.syntax }, - }); - - buttonCancel.onclick = function() { - document.body.removeChild(overlayContainer); - }; - overlayContainer.onclick = buttonCancel.onclick; - popupContainer.onclick = function(e) { - e.preventDefault(); - e.stopPropagation(); - }; - buttonOk.onclick = function() { - const output = editor.container.querySelector(".ql-editor").innerText; - const noNewlines = output - .replace(/\s+/g, " ") // convert multiple spaces to a single space. This is how HTML treats them - .replace(/(<[^\/<>]+>)\s+/g, "$1") // remove spaces after the start of a new tag - .replace(/<\/(p|ol|ul)>\s/g, "") // remove spaces after the end of lists and paragraphs, they tend to break quill - .replace(/\s<(p|ol|ul)>/g, "<$1>") // remove spaces before the start of lists and paragraphs, they tend to break quill - .replace(/<\/li>\s
  • /g, "
  • ") // remove spaces between list items, they tend to break quill - .replace(/\s<\//g, "]+>)\s(<[^\/<>]+>)/g, "$1$2") // remove space between multiple starting tags - .trim(); - quill.container.querySelector(".ql-editor").innerHTML = noNewlines; - document.body.removeChild(overlayContainer); - }; - } - - // Adapted FROM jsfiddle here: https://jsfiddle.net/buksy/rxucg1gd/ - formatHTML(code) { - // "use strict"; - let stripWhiteSpaces = true; - let stripEmptyLines = true; - const whitespace = " ".repeat(2); // Default indenting 4 whitespaces - let currentIndent = 0; - const newlineChar = "\n"; - let prevChar = null; - let char = null; - let nextChar = null; - - let result = ""; - for (let pos = 0; pos <= code.length; pos++) { - prevChar = char; - char = code.substr(pos, 1); - nextChar = code.substr(pos + 1, 1); - - const isBrTag = code.substr(pos, 4) === "
    "; - const isOpeningTag = char === "<" && nextChar !== "/" && !isBrTag; - const isClosingTag = char === "<" && nextChar === "/" && !isBrTag; - const isTagEnd = prevChar === ">" && char !== "<" && currentIndent > 0; - const isTagNext = !isBrTag && !isOpeningTag && !isClosingTag && isTagEnd && code.substr(pos, code.substr(pos).indexOf("<")).trim() === ""; - if (isBrTag) { - // If opening tag, add newline character and indention - result += newlineChar; - currentIndent--; - pos += 4; - } - if (isOpeningTag) { - // If opening tag, add newline character and indention - result += newlineChar + whitespace.repeat(currentIndent); - currentIndent++; - } - // if Closing tag, add newline and indention - else if (isClosingTag) { - // If there're more closing tags than opening - if (--currentIndent < 0) currentIndent = 0; - result += newlineChar + whitespace.repeat(currentIndent); - } - // remove multiple whitespaces - else if (stripWhiteSpaces === true && char === " " && nextChar === " ") - char = ""; - // remove empty lines - else if (stripEmptyLines === true && char === newlineChar) { - //debugger; - if (code.substr(pos, code.substr(pos).indexOf("<")).trim() === "") - char = ""; - } - if(isTagEnd && !isTagNext) { - result += newlineChar + whitespace.repeat(currentIndent); - } - - result += char; - } - console.dir({ - before: code, - after: result - }); - return result; - } -} - -export const htmlEditButtonOptions = { - debug: true, // logging, default:false - msg: "HTMLソースを編集", //Custom message to display in the editor, default: Edit HTML here, when you click "OK" the quill editor's contents will be replaced - okText: "OK", // Text to display in the OK button, default: Ok, - cancelText: "キャンセル", // Text to display in the cancel button, default: Cancel - buttonHTML: "<>", // Text to display in the toolbar button, default: <> - buttonTitle: "HTMLソースを編集", // Text to display as the tooltip for the toolbar button, default: Show HTML source - syntax: false // Show the HTML with syntax highlighting. Requires highlightjs on window.hljs (similar to Quill itself), default: false -} diff --git a/app/packs/stylesheets/decidim/cfj/ql_html_editor.scss b/app/packs/stylesheets/decidim/cfj/ql_html_editor.scss deleted file mode 100644 index 84eb531f5..000000000 --- a/app/packs/stylesheets/decidim/cfj/ql_html_editor.scss +++ /dev/null @@ -1,53 +0,0 @@ -/* - * for Quill HTML Editor - */ -.ql-html-overlayContainer { - background: #0000007d; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 9999; -} - -.ql-html-popupContainer { - background: #ddd; - position: absolute; - top: 5%; - left: 5%; - right: 5%; - bottom: 5%; - border-radius: 10px; -} - -.ql-html-textContainer { - position: relative; - width: calc(100% - 40px); - height: calc(100% - 40px); - padding: 20px; -} - -.ql-html-textArea { - background: #fff; - position: absolute; - left: 15px; - width: calc(100% - 45px); - height: calc(100% - 116px); -} - -.ql-html-buttonCancel { - margin-right: 20px; -} - -.ql-html-popupTitle { - margin: 0; - display: block; -} - -.ql-html-buttonGroup { - position: absolute; - bottom: 20px; - transform: scale(1.5); - left: calc(50% - 60px); -}