From aebdb0b599e503d840313cdc044333e4facb70d7 Mon Sep 17 00:00:00 2001 From: James Collins Date: Tue, 28 Mar 2023 22:40:25 +1000 Subject: [PATCH] improvements to the gallery --- resources/js/components/SMEditor.vue | 421 +++++++++++++++++++++++---- 1 file changed, 364 insertions(+), 57 deletions(-) diff --git a/resources/js/components/SMEditor.vue b/resources/js/components/SMEditor.vue index fad9d63..4a9f79c 100644 --- a/resources/js/components/SMEditor.vue +++ b/resources/js/components/SMEditor.vue @@ -74,6 +74,93 @@ const props = defineProps({ const useDarkMode = false; // window.matchMedia("(prefers-color-scheme: dark)").matches; const tinyeditor = ref(null); +tinymce.PluginManager.add("gallery", function (editor) { + // Register a command to open the dialog + editor.addCommand("image-gallery", function () { + var selected = []; + var node = editor.selection.getNode(); + + if (node) { + if (!editor.dom.hasClass(node, "tinymce-sm-gallery")) { + // Check if node is a descendant of a gallery node + var galleryNode = editor.dom.getParent( + node, + ".tinymce-sm-gallery" + ); + if (!galleryNode) { + node = null; + } else { + node = galleryNode; + } + } + } + + if (node) { + // Parse the gallery contents + const childEls = node.querySelectorAll("div"); + const urls = Array.from(childEls).map((el) => { + const matches = (el as HTMLElement) + .getAttribute("style") + .match(/url\(['"]?(.*?)['"]?\)/); + return matches ? matches[1] : null; + }); + selected = urls; + } + imageBrowser( + function (url) { + let galleryContent = ""; + if (url.length > 0) { + url.forEach((item) => { + galleryContent += `
`; + }); + + galleryContent = ``; + } + + const selection = editor.selection; + if (selection) { + selection.setContent(galleryContent); + } else { + editor.insertContent(galleryContent); + } + }, + selected, + null, + true + ); + }); + + // Register a toggle button that triggers the command and displays the icon + editor.ui.registry.addToggleButton("gallery", { + icon: "gallery", + tooltip: "Image gallery", + onAction: function () { + editor.execCommand("image-gallery"); + }, + onSetup: function (api) { + var nodeChangeHandler = function () { + var node = editor.selection.getNode(); + + api.setActive( + node && + (editor.dom.hasClass(node, "tinymce-sm-gallery") || + (node.parentNode && + editor.dom.hasClass( + node.parentNode, + "tinymce-sm-gallery" + ))) + ); + }; + + editor.on("NodeChange", nodeChangeHandler); + + return function () { + editor.off("NodeChange", nodeChangeHandler); + }; + }, + }); +}); + const init = { promotion: false, emoticons_database_url: "/tinymce/plugins/emoticons/js/emojis.min.js", @@ -110,9 +197,10 @@ const init = { "emoticons", "autosave", "searchreplace", + "gallery", ], toolbar: - "h1 h2 h3 blockquote | bold italic underline strikethrough | numlist bullist | image media link anchor codesample | alignleft aligncenter alignright alignjustify | forecolor backcolor removeformat | outdent indent | charmap emoticons | undo redo", + "gallery h1 h2 h3 blockquote | bold italic underline strikethrough | numlist bullist | image media link anchor codesample | alignleft aligncenter alignright alignjustify | forecolor backcolor removeformat | outdent indent | charmap emoticons | undo redo", branding: false, menubar: false, toolbar_mode: "sliding", @@ -245,15 +333,13 @@ const fetchLinkList = () => { return pageList; }; -const imageBrowser = (callback, value, meta) => { +const imageBrowser = (callback, value, meta, gallery = false) => { var libraryPage = 1; var libraryMax = 1; var selected = value; var title = ""; var itemsFound = 0; - console.log(selected); - // Open a dialog to select a file const input = document.createElement("input"); input.setAttribute("type", "file"); @@ -288,6 +374,23 @@ const imageBrowser = (callback, value, meta) => { } }; + const updateFooter = () => { + let selectedText = ""; + + if (gallery == true) { + selectedText = ` (${selected.length} selected)`; + } + + const itemCountElem = document.getElementById( + "image-library-item-count" + ); + if (itemCountElem) { + itemCountElem.innerHTML = `${itemsFound.toString()} image${ + itemsFound == 1 ? "" : "s" + } found${selectedText}`; + } + }; + const updateLibrary = () => { const limit = 24; @@ -368,7 +471,7 @@ const imageBrowser = (callback, value, meta) => { data.media.forEach((medium) => { const item = document.createElement("div"); item.classList.add("image-library-content-item"); - if (urlMatches(medium.url, selected)) { + if (urlMatches(medium.url, selected) !== false) { item.classList.add( "image-library-content-item-selected" ); @@ -379,16 +482,33 @@ const imageBrowser = (callback, value, meta) => { ".image-library-content-item" ); - items.forEach((item) => { - item.classList.remove( + if (gallery == false) { + items.forEach((item) => { + item.classList.remove( + "image-library-content-item-selected" + ); + }); + + item.classList.add( "image-library-content-item-selected" ); - }); + selected = medium.url; + } else { + const match = urlMatches(medium.url, selected); + if (match !== false) { + selected.splice(match, 1); + item.classList.remove( + "image-library-content-item-selected" + ); + } else { + selected.push(medium.url); + item.classList.add( + "image-library-content-item-selected" + ); + } - item.classList.add( - "image-library-content-item-selected" - ); - selected = medium.url; + updateFooter(); + } }; const image = document.createElement("div"); @@ -412,45 +532,117 @@ const imageBrowser = (callback, value, meta) => { .finally(() => { loadingElem.remove(); - document.getElementById( + const paginationMax = document.getElementById( "image-library-pagination-max" - ).innerHTML = libraryMax.toString(); - - document.getElementById( - "image-library-item-count" - ).innerHTML = `${itemsFound.toString()} image${ - itemsFound == 1 ? "" : "s" - } found`; + ); + if (paginationMax) { + paginationMax.innerHTML = libraryMax.toString(); + } + updateFooter(); }); } }; - // Add the container and file input to the dialog - const dialog = tinymce.activeEditor.windowManager.open({ - title: "Image Library", - size: "large", - body: { - type: "tabpanel", - tabs: [ + const updateGallery = () => { + const galleryContainer = document.getElementById( + "image-gallery-content" + ); + if (galleryContainer != null) { + // delete existing items + const divElements = galleryContainer.querySelectorAll("div"); + divElements.forEach((div) => { + div.remove(); + }); + + const loadingElem = document.createElement("div"); + loadingElem.classList.add("image-gallery-content-loading"); + galleryContainer.appendChild(loadingElem); + + selected.forEach((url, index) => { + const item = document.createElement("div"); + item.classList.add("image-gallery-content-item"); + + const image = document.createElement("div"); + image.classList.add("image-gallery-content-item-image"); + image.style.backgroundImage = `url('${url}?w=200')`; + + const title = document.createElement("div"); + title.classList.add("image-gallery-content-item-title"); + title.innerHTML = "??"; + + const removeBtn = document.createElement("div"); + removeBtn.classList.add("image-gallery-content-item-remove"); + removeBtn.onclick = function () { + selected.splice(index, 1); + updateGallery(); + }; + + const leftBtn = document.createElement("div"); + leftBtn.classList.add("image-gallery-content-item-left"); + leftBtn.onclick = function () { + if (index > 0) { + const temp = selected[index]; + selected[index] = selected[index - 1]; + selected[index - 1] = temp; + + updateGallery(); + } + }; + + const rightBtn = document.createElement("div"); + rightBtn.classList.add("image-gallery-content-item-right"); + rightBtn.onclick = function () { + if (index < selected.length - 1) { + const temp = selected[index]; + selected[index] = selected[index + 1]; + selected[index + 1] = temp; + + updateGallery(); + } + }; + + item.appendChild(image); + item.appendChild(title); + item.appendChild(removeBtn); + item.appendChild(leftBtn); + item.appendChild(rightBtn); + + galleryContainer.appendChild(item); + }); + + const countElem = document.getElementById( + "image-gallery-item-count" + ); + if (countElem) { + countElem.innerHTML = `${selected.length} item${ + selected.length == 1 ? "" : "s" + }`; + } + + loadingElem.remove(); + } + }; + + const tabs = [ + { + name: "upload", + title: "Upload", + items: [ { - name: "upload", - title: "Upload", - items: [ - { - type: "dropzone", - name: "dropzone", - label: "Upload File", - accept: "image/*", - }, - ], + type: "dropzone", + name: "dropzone", + label: "Upload File", + accept: "image/*", }, + ], + }, + { + name: "library", + title: "Library", + items: [ { - name: "library", - title: "Library", - items: [ - { - type: "htmlpanel", - html: `
+ type: "htmlpanel", + html: `
@@ -462,16 +654,38 @@ const imageBrowser = (callback, value, meta) => {
-
-
loading...
-
-
x
+
+
...
`, - }, - ], }, ], }, + ]; + + if (gallery == true) { + tabs.push({ + name: "gallery", + title: "Gallery", + items: [ + { + type: "htmlpanel", + html: ``, + }, + ], + }); + } + + // Add the container and file input to the dialog + const dialog = tinymce.activeEditor.windowManager.open({ + title: "Image Library", + size: "large", + body: { + type: "tabpanel", + tabs: tabs, + }, initialData: {}, buttons: [ { @@ -520,6 +734,8 @@ const imageBrowser = (callback, value, meta) => { onTabChange: function (dialogApi, details) { if (details.newTabName == "library") { updateLibrary(); + } else if (details.newTabName == "gallery") { + updateGallery(); } }, }); @@ -591,7 +807,8 @@ const imageBrowser = (callback, value, meta) => { } } -#image-library-content { +#image-library-content, +#image-gallery-content { display: flex; flex-wrap: wrap; margin-top: 12px; @@ -602,7 +819,9 @@ const imageBrowser = (callback, value, meta) => { padding: 0.5rem; height: 440px; - .image-library-content-item { + .image-library-content-item, + .image-gallery-content-item { + position: relative; width: 18vw; height: 18vh; min-width: 200px; @@ -614,7 +833,6 @@ const imageBrowser = (callback, value, meta) => { &:hover, &.image-library-content-item-selected { border: 3px solid #0060ce; - position: relative; cursor: pointer; } @@ -632,13 +850,91 @@ const imageBrowser = (callback, value, meta) => { justify-content: center; border-radius: 50%; color: #fff; - box-shadow: 0 0 2px 2px #fff; + box-shadow: 0 0 0 2px #fff; background-repeat: no-repeat; background-position: center; background-color: #0060ce; } - .image-library-content-item-image { + .image-gallery-content-item-remove { + position: absolute; + top: -10px; + right: -10px; + width: 20px; + height: 20px; + font-size: 14px; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #fff; + box-shadow: 0 0 0 2px #fff; + background-repeat: no-repeat; + background-position: center; + background-size: 50%; + background-color: #ce0000; + background-image: url('data:image/svg+xml;utf8,'); + } + + .image-gallery-content-item-left { + position: absolute; + top: -10px; + right: 40px; + width: 20px; + height: 20px; + font-size: 14px; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #fff; + box-shadow: 0 0 0 2px #fff; + background-repeat: no-repeat; + background-position: center; + background-size: 50%; + background-color: #0060ce; + background-image: url('data:image/svg+xml;utf8,'); + } + + .image-gallery-content-item-right { + position: absolute; + top: -10px; + right: 15px; + width: 20px; + height: 20px; + font-size: 14px; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #fff; + box-shadow: 0 0 0 2px #fff; + background-repeat: no-repeat; + background-position: center; + background-size: 50%; + background-color: #0060ce; + background-image: url('data:image/svg+xml;utf8,'); + } + + &:first-of-type .image-gallery-content-item-left { + display: none; + } + + &:last-of-type { + .image-gallery-content-item-left { + right: 15px; + } + + .image-gallery-content-item-right { + display: none; + } + } + + .image-library-content-item-image, + .image-gallery-content-item-image { width: 100%; height: 14vh; min-height: 113px; @@ -648,7 +944,8 @@ const imageBrowser = (callback, value, meta) => { background-clip: content-box; } - .image-library-content-item-title { + .image-library-content-item-title, + .image-gallery-content-item-title { margin-top: 8px; text-align: center; font-size: 90%; @@ -659,14 +956,16 @@ const imageBrowser = (callback, value, meta) => { } } -#image-library-item-count { +#image-library-item-count, +#image-gallery-item-count { font-size: 90%; margin-top: 8px; color: #999; text-align: right; } -.image-library-content-loading { +.image-library-content-loading, +.image-gallery-content-loading { position: relative; &::after { @@ -695,6 +994,10 @@ const imageBrowser = (callback, value, meta) => { #image-library-content { height: 408px; } + + #image-gallery-content { + height: 408px; + } } @media only screen and (max-width: 450px) { @@ -717,5 +1020,9 @@ const imageBrowser = (callback, value, meta) => { #image-library-content { height: 380px; } + + #image-gallery-content { + height: 400px; + } }