From aa690ea712fc12ca4cb779cc717fa85c575c61e6 Mon Sep 17 00:00:00 2001 From: Gnkalk Date: Tue, 18 Feb 2025 01:06:03 +0330 Subject: [PATCH] Add close button for image details view - Implement close button in image details container - Update CSS for LTR and RTL layouts to position close button - Add click event listener to close image details view - Refactor TypeScript code to separate image-related functionality - Update HTML template to include close button with icon --- searx/static/themes/smart/css/ltr-style.css | 11 ++ searx/static/themes/smart/css/rtl-style.css | 11 ++ searx/static/themes/smart/js/app.js | 77 ++++++-- .../themes/smart/src/scss/results/images.scss | 17 ++ searx/static/themes/smart/src/ts/app.ts | 177 +----------------- searx/static/themes/smart/src/ts/images.ts | 176 +++++++++++++++++ searx/templates/smart/base.html | 5 +- searx/templates/smart/results.html | 1 + 8 files changed, 287 insertions(+), 188 deletions(-) create mode 100644 searx/static/themes/smart/src/ts/images.ts diff --git a/searx/static/themes/smart/css/ltr-style.css b/searx/static/themes/smart/css/ltr-style.css index ca28849b8..14ae5a983 100644 --- a/searx/static/themes/smart/css/ltr-style.css +++ b/searx/static/themes/smart/css/ltr-style.css @@ -739,6 +739,17 @@ img, video { cursor: not-allowed; } +.image-details-container .image-details .close { + color: var(--text-color); + background-color: var(--secondary-background-color); + cursor: pointer; + border-radius: 9999px; + padding: .25rem; + position: absolute; + top: 1rem; + right: 1rem; +} + #preferences { grid-template: "header links" min-content "main main" diff --git a/searx/static/themes/smart/css/rtl-style.css b/searx/static/themes/smart/css/rtl-style.css index 23a8df6ab..dc8a149d0 100644 --- a/searx/static/themes/smart/css/rtl-style.css +++ b/searx/static/themes/smart/css/rtl-style.css @@ -685,6 +685,17 @@ img, video { cursor: not-allowed; } +.image-details-container .image-details .close { + color: var(--text-color); + background-color: var(--secondary-background-color); + cursor: pointer; + border-radius: 9999px; + padding: .25rem; + position: absolute; + top: 1rem; + left: 1rem; +} + #preferences { grid-template: "header links" min-content "main main" diff --git a/searx/static/themes/smart/js/app.js b/searx/static/themes/smart/js/app.js index 6ac7322b0..a29366274 100644 --- a/searx/static/themes/smart/js/app.js +++ b/searx/static/themes/smart/js/app.js @@ -585,14 +585,27 @@ function hmrAccept(bundle /*: ParcelRequire */ , id /*: string */ ) { } },{}],"3uyIQ":[function(require,module,exports,__globalThis) { +var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); +parcelHelpers.defineInteropFlag(exports); +parcelHelpers.export(exports, "$", ()=>$); +parcelHelpers.export(exports, "$$", ()=>$$); +var _images = require("./images"); function $(selector) { return document.querySelector(selector); } function $$(selector) { return document.querySelectorAll(selector); } +(0, _images.checkImagePage)(); +document.addEventListener("DOMContentLoaded", function() {}); + +},{"./images":"jxVSe","@parcel/transformer-js/src/esmodule-helpers.js":"j7FRh"}],"jxVSe":[function(require,module,exports,__globalThis) { +var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); +parcelHelpers.defineInteropFlag(exports); +parcelHelpers.export(exports, "checkImagePage", ()=>checkImagePage); +var _app = require("./app"); function handleImageDetails(image, { imageSrc, imageTitle, imageContent, imageSource, imageFilesize, imageDownload, formatSpan, filesizeSpan, engineSpan }) { - const resultsContainer = $("#results"); + const resultsContainer = (0, _app.$)("#results"); resultsContainer.classList.add("image-open"); imageSrc.src = image.getAttribute("data-gitee-src"); imageTitle.innerText = image.getAttribute("data-gitee-title"); @@ -608,17 +621,17 @@ function handleImageDetails(image, { imageSrc, imageTitle, imageContent, imageSo engineSpan.innerText = image.getAttribute("data-gitee-engine"); } function setupImages(resultsContainer) { - const imageSrc = $("#image-src"); - const imageTitle = $("#image-title"); - const imageContent = $("#image-content"); - const imageFormat = $("#image-format"); - const imageFilesize = $("#image-filesize"); - const imageSource = $("#image-source"); - const imageDownload = $("#image-download"); - const imageCopy = $("#image-copy"); + const imageSrc = (0, _app.$)("#image-src"); + const imageTitle = (0, _app.$)("#image-title"); + const imageContent = (0, _app.$)("#image-content"); + const imageFormat = (0, _app.$)("#image-format"); + const imageFilesize = (0, _app.$)("#image-filesize"); + const imageSource = (0, _app.$)("#image-source"); + const imageDownload = (0, _app.$)("#image-download"); + const imageCopy = (0, _app.$)("#image-copy"); const formatSpan = imageFormat.getElementsByClassName("value")[0]; const filesizeSpan = imageFilesize.getElementsByClassName("value")[0]; - const engineSpan = $("#image-engine"); + const engineSpan = (0, _app.$)("#image-engine"); const ImagesObserver = new MutationObserver((mutationsList)=>{ for (const mutation of mutationsList)if (mutation.type === "childList") mutation.addedNodes.forEach((node)=>{ if (node.nodeType === Node.ELEMENT_NODE) return; @@ -643,7 +656,7 @@ function setupImages(resultsContainer) { childList: true, subtree: true }); - $$(".image").forEach((node)=>{ + (0, _app.$$)(".image").forEach((node)=>{ if (node.nodeType !== Node.ELEMENT_NODE) return; const image = node; image.addEventListener("click", (e)=>{ @@ -678,8 +691,8 @@ function setupImages(resultsContainer) { }); canvas.remove(); }); - const bodyContainer = $("#results"); - const imageDetailsContainer = $("#image-details"); + const bodyContainer = (0, _app.$)("#results"); + const imageDetailsContainer = (0, _app.$)("#image-details"); bodyContainer.addEventListener("scroll", function() { if (bodyContainer.scrollTop < 50) { imageDetailsContainer.style.top = "0px"; @@ -689,13 +702,45 @@ function setupImages(resultsContainer) { imageDetailsContainer.style.height = "95vh"; imageDetailsContainer.style.top = `calc(${bodyContainer.scrollTop}px - 15vh)`; }); + imageDetailsContainer.getElementsByClassName("close")[0].addEventListener("click", function() { + bodyContainer.classList.remove("image-open"); + }); } -function afterLoad() { - const resultsContainer = $(".results-container"); +function checkImagePage() { + const resultsContainer = (0, _app.$)(".results-container"); if (!resultsContainer || resultsContainer.nodeType !== Node.ELEMENT_NODE) return; if (resultsContainer.classList.contains("image-page")) setupImages(resultsContainer); } -document.addEventListener("DOMContentLoaded", afterLoad); + +},{"@parcel/transformer-js/src/esmodule-helpers.js":"j7FRh","./app":"3uyIQ"}],"j7FRh":[function(require,module,exports,__globalThis) { +exports.interopDefault = function(a) { + return a && a.__esModule ? a : { + default: a + }; +}; +exports.defineInteropFlag = function(a) { + Object.defineProperty(a, '__esModule', { + value: true + }); +}; +exports.exportAll = function(source, dest) { + Object.keys(source).forEach(function(key) { + if (key === 'default' || key === '__esModule' || Object.prototype.hasOwnProperty.call(dest, key)) return; + Object.defineProperty(dest, key, { + enumerable: true, + get: function() { + return source[key]; + } + }); + }); + return dest; +}; +exports.export = function(dest, destName, get) { + Object.defineProperty(dest, destName, { + enumerable: true, + get: get + }); +}; },{}]},["1Y09f","3uyIQ"], "3uyIQ", "parcelRequire94c2") diff --git a/searx/static/themes/smart/src/scss/results/images.scss b/searx/static/themes/smart/src/scss/results/images.scss index 22d628925..09193ca8c 100644 --- a/searx/static/themes/smart/src/scss/results/images.scss +++ b/searx/static/themes/smart/src/scss/results/images.scss @@ -2,11 +2,17 @@ @use '../definitions.scss' as *; .images_group { + // this thing make some lags columns: 15rem; + + // allternative way + // display: flex; + // flex-wrap: wrap; gap: 1rem; .image { cursor: pointer; + // height: 15rem; .image-box { overflow: hidden; @@ -158,5 +164,16 @@ } } } + + .close { + position: absolute; + top: 1rem; + @include mixin.right(1rem); + color: var(--text-color); + @include mixin.rounded(full); + background-color: var(--secondary-background-color); + padding: 0.25rem; + cursor: pointer; + } } } \ No newline at end of file diff --git a/searx/static/themes/smart/src/ts/app.ts b/searx/static/themes/smart/src/ts/app.ts index 27ced4b79..8ae64f193 100644 --- a/searx/static/themes/smart/src/ts/app.ts +++ b/searx/static/themes/smart/src/ts/app.ts @@ -1,178 +1,13 @@ -function $(selector: string) { +import { checkImagePage } from "./images"; + +export function $(selector: string) { return document.querySelector(selector); } -function $$(selector: string) { +export function $$(selector: string) { return document.querySelectorAll(selector); } -interface ImageDetails { - imageSrc: HTMLImageElement; - imageTitle: HTMLSpanElement; - imageContent: HTMLSpanElement; - imageSource: HTMLLinkElement; - imageFilesize: HTMLSpanElement; - imageDownload: HTMLLinkElement; - formatSpan: HTMLSpanElement; - filesizeSpan: HTMLSpanElement; - engineSpan: HTMLSpanElement; -} +checkImagePage(); -function handleImageDetails( - image: HTMLElement, - { - imageSrc, - imageTitle, - imageContent, - imageSource, - imageFilesize, - imageDownload, - formatSpan, - filesizeSpan, - engineSpan, - }: ImageDetails -) { - const resultsContainer = $("#results") as HTMLDivElement; - resultsContainer.classList.add("image-open"); - - imageSrc.src = image.getAttribute("data-gitee-src") as string; - imageTitle.innerText = image.getAttribute("data-gitee-title") as string; - imageContent.innerText = image.getAttribute("data-gitee-content") as string; - - if (image.hasAttribute("data-gitee-format")) { - formatSpan.innerText = image.getAttribute( - "data-gitee-format" - ) as string; - } else { - formatSpan.innerText = "Unknown"; - } - - if (image.hasAttribute("data-gitee-filesize")) { - if (imageFilesize.hasAttribute("hidden")) - imageFilesize.setAttribute("hidden", ""); - - filesizeSpan.innerText = image.getAttribute( - "data-gitee-filesize" - ) as string; - } else { - imageFilesize.setAttribute("hidden", ""); - } - - imageSource.href = image.getAttribute("data-gitee-source") as string; - imageDownload.href = image.getAttribute("data-gitee-src") as string; - engineSpan.innerText = image.getAttribute("data-gitee-engine") as string; -} - -function setupImages(resultsContainer: HTMLElement) { - const imageSrc = $("#image-src") as HTMLImageElement; - const imageTitle = $("#image-title") as HTMLHeadingElement; - const imageContent = $("#image-content") as HTMLParagraphElement; - const imageFormat = $("#image-format") as HTMLDivElement; - const imageFilesize = $("#image-filesize") as HTMLDivElement; - const imageSource = $("#image-source") as HTMLLinkElement; - const imageDownload = $("#image-download") as HTMLLinkElement; - const imageCopy = $("#image-copy") as HTMLButtonElement; - - const formatSpan = imageFormat.getElementsByClassName( - "value" - )[0] as HTMLSpanElement; - const filesizeSpan = imageFilesize.getElementsByClassName( - "value" - )[0] as HTMLSpanElement; - const engineSpan = $("#image-engine") as HTMLSpanElement; - - const ImagesObserver = new MutationObserver((mutationsList) => { - for (const mutation of mutationsList) { - if (mutation.type === "childList") { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) return; - const element = node as HTMLElement; - if (element.classList.contains("image")) { - element.addEventListener("click", (e) => { - e.preventDefault(); - handleImageDetails(element, { - imageSrc, - imageTitle, - imageContent, - imageSource, - imageFilesize, - imageDownload, - filesizeSpan, - formatSpan, - engineSpan, - }); - }); - } - }); - } - } - }); - - ImagesObserver.observe(resultsContainer, { - childList: true, - subtree: true, - }); - - $$(".image").forEach((node) => { - if (node.nodeType !== Node.ELEMENT_NODE) return; - const image = node as HTMLElement; - void image.addEventListener("click", (e) => { - e.preventDefault(); - handleImageDetails(image, { - imageSrc, - imageTitle, - imageContent, - imageSource, - imageFilesize, - imageDownload, - filesizeSpan, - formatSpan, - engineSpan, - }); - }); - }); - - // Copy not working at localhost and only works in HTTPS pages - if (location.protocol === "http:") { - imageCopy.setAttribute("disabled", "true"); - } - imageCopy.addEventListener("click", () => { - const canvas = document.createElement("canvas"); - canvas.width = imageSrc.width; - canvas.height = imageSrc.height; - (canvas.getContext("2d") as CanvasRenderingContext2D).drawImage( - imageSrc, - 0, - 0, - imageSrc.width, - imageSrc.height - ); - canvas.toBlob((blob) => { - if (!blob) return; - navigator.clipboard.write([new ClipboardItem({ "image/*": blob })]); - }); - void canvas.remove(); - }); - - const bodyContainer = $("#results") as HTMLDivElement; - const imageDetailsContainer = $("#image-details") as HTMLDivElement; - bodyContainer.addEventListener("scroll", function () { - if (bodyContainer.scrollTop < 50) { - imageDetailsContainer.style.top = "0px"; - imageDetailsContainer.style.height = "80vh"; - return; - } - imageDetailsContainer.style.height = "95vh"; - imageDetailsContainer.style.top = `calc(${bodyContainer.scrollTop}px - 15vh)`; - }); -} - -function afterLoad() { - const resultsContainer = $(".results-container"); - if (!resultsContainer || resultsContainer.nodeType !== Node.ELEMENT_NODE) - return; - if (resultsContainer.classList.contains("image-page")) - setupImages(resultsContainer as HTMLDivElement); -} - -document.addEventListener("DOMContentLoaded", afterLoad); +document.addEventListener("DOMContentLoaded", function () {}); diff --git a/searx/static/themes/smart/src/ts/images.ts b/searx/static/themes/smart/src/ts/images.ts new file mode 100644 index 000000000..c7845977e --- /dev/null +++ b/searx/static/themes/smart/src/ts/images.ts @@ -0,0 +1,176 @@ +import { $, $$ } from "./app"; + +interface ImageDetails { + imageSrc: HTMLImageElement; + imageTitle: HTMLSpanElement; + imageContent: HTMLSpanElement; + imageSource: HTMLLinkElement; + imageFilesize: HTMLSpanElement; + imageDownload: HTMLLinkElement; + formatSpan: HTMLSpanElement; + filesizeSpan: HTMLSpanElement; + engineSpan: HTMLSpanElement; +} + +function handleImageDetails( + image: HTMLElement, + { + imageSrc, + imageTitle, + imageContent, + imageSource, + imageFilesize, + imageDownload, + formatSpan, + filesizeSpan, + engineSpan, + }: ImageDetails +) { + const resultsContainer = $("#results") as HTMLDivElement; + resultsContainer.classList.add("image-open"); + + imageSrc.src = image.getAttribute("data-gitee-src") as string; + imageTitle.innerText = image.getAttribute("data-gitee-title") as string; + imageContent.innerText = image.getAttribute("data-gitee-content") as string; + + if (image.hasAttribute("data-gitee-format")) { + formatSpan.innerText = image.getAttribute( + "data-gitee-format" + ) as string; + } else { + formatSpan.innerText = "Unknown"; + } + + if (image.hasAttribute("data-gitee-filesize")) { + if (imageFilesize.hasAttribute("hidden")) + imageFilesize.setAttribute("hidden", ""); + + filesizeSpan.innerText = image.getAttribute( + "data-gitee-filesize" + ) as string; + } else { + imageFilesize.setAttribute("hidden", ""); + } + + imageSource.href = image.getAttribute("data-gitee-source") as string; + imageDownload.href = image.getAttribute("data-gitee-src") as string; + engineSpan.innerText = image.getAttribute("data-gitee-engine") as string; +} + +function setupImages(resultsContainer: HTMLElement) { + const imageSrc = $("#image-src") as HTMLImageElement; + const imageTitle = $("#image-title") as HTMLHeadingElement; + const imageContent = $("#image-content") as HTMLParagraphElement; + const imageFormat = $("#image-format") as HTMLDivElement; + const imageFilesize = $("#image-filesize") as HTMLDivElement; + const imageSource = $("#image-source") as HTMLLinkElement; + const imageDownload = $("#image-download") as HTMLLinkElement; + const imageCopy = $("#image-copy") as HTMLButtonElement; + + const formatSpan = imageFormat.getElementsByClassName( + "value" + )[0] as HTMLSpanElement; + const filesizeSpan = imageFilesize.getElementsByClassName( + "value" + )[0] as HTMLSpanElement; + const engineSpan = $("#image-engine") as HTMLSpanElement; + + const ImagesObserver = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) return; + const element = node as HTMLElement; + if (element.classList.contains("image")) { + element.addEventListener("click", (e) => { + e.preventDefault(); + handleImageDetails(element, { + imageSrc, + imageTitle, + imageContent, + imageSource, + imageFilesize, + imageDownload, + filesizeSpan, + formatSpan, + engineSpan, + }); + }); + } + }); + } + } + }); + + ImagesObserver.observe(resultsContainer, { + childList: true, + subtree: true, + }); + + $$(".image").forEach((node) => { + if (node.nodeType !== Node.ELEMENT_NODE) return; + const image = node as HTMLElement; + void image.addEventListener("click", (e) => { + e.preventDefault(); + handleImageDetails(image, { + imageSrc, + imageTitle, + imageContent, + imageSource, + imageFilesize, + imageDownload, + filesizeSpan, + formatSpan, + engineSpan, + }); + }); + }); + + // Copy not working at localhost and only works in HTTPS pages + if (location.protocol === "http:") { + imageCopy.setAttribute("disabled", "true"); + } + imageCopy.addEventListener("click", () => { + const canvas = document.createElement("canvas"); + canvas.width = imageSrc.width; + canvas.height = imageSrc.height; + (canvas.getContext("2d") as CanvasRenderingContext2D).drawImage( + imageSrc, + 0, + 0, + imageSrc.width, + imageSrc.height + ); + canvas.toBlob((blob) => { + if (!blob) return; + navigator.clipboard.write([new ClipboardItem({ "image/*": blob })]); + }); + void canvas.remove(); + }); + + const bodyContainer = $("#results") as HTMLDivElement; + const imageDetailsContainer = $("#image-details") as HTMLDivElement; + bodyContainer.addEventListener("scroll", function () { + if (bodyContainer.scrollTop < 50) { + imageDetailsContainer.style.top = "0px"; + imageDetailsContainer.style.height = "80vh"; + return; + } + imageDetailsContainer.style.height = "95vh"; + imageDetailsContainer.style.top = `calc(${bodyContainer.scrollTop}px - 15vh)`; + }); + + imageDetailsContainer + .getElementsByClassName("close")[0] + .addEventListener("click", function () { + bodyContainer.classList.remove("image-open"); + }); +} + +export function checkImagePage() { + const resultsContainer = $(".results-container"); + if (!resultsContainer || resultsContainer.nodeType !== Node.ELEMENT_NODE) + return; + if (resultsContainer.classList.contains("image-page")) + setupImages(resultsContainer as HTMLDivElement); +} diff --git a/searx/templates/smart/base.html b/searx/templates/smart/base.html index a9c31db09..316d04be5 100644 --- a/searx/templates/smart/base.html +++ b/searx/templates/smart/base.html @@ -105,6 +105,9 @@ {% if endpoint == 'index' or endpoint == 'results' %} {% include 'smart/madules/sidebar.html' %} {% endif %} - + diff --git a/searx/templates/smart/results.html b/searx/templates/smart/results.html index ee6596c27..d8e03ba9d 100644 --- a/searx/templates/smart/results.html +++ b/searx/templates/smart/results.html @@ -73,6 +73,7 @@ block body %} {% include 'smart/madules/search_header.html' %}
+
{{ icon_small('close') }}

...

...