From 62e64820f885955f808f20c3655ad5c919b174f0 Mon Sep 17 00:00:00 2001 From: Gnkalk Date: Wed, 12 Feb 2025 18:00:43 +0330 Subject: [PATCH] Implement image search results page with detailed view - Add new SCSS files for images and default results styling - Implement TypeScript functionality for image details interaction - Update HTML templates for image results and details view - Enhance responsive grid layout for image search results - Add image copy and download functionality - Improve screen size breakpoints and transitions --- searx/static/themes/smart/css/ltr-style.css | 351 +++++++++++++----- searx/static/themes/smart/css/rtl-style.css | 351 +++++++++++++----- searx/static/themes/smart/js/app.js | 100 +++++ .../themes/smart/src/scss/definitions.scss | 14 +- searx/static/themes/smart/src/scss/reset.scss | 2 +- .../{results.scss => results/default.scss} | 2 +- .../themes/smart/src/scss/results/images.scss | 158 ++++++++ searx/static/themes/smart/src/scss/style.scss | 25 +- searx/static/themes/smart/src/ts/app.ts | 166 +++++++++ .../smart/result_templates/default.html | 2 + .../smart/result_templates/images.html | 89 +++++ searx/templates/smart/results.html | 63 +++- 12 files changed, 1121 insertions(+), 202 deletions(-) rename searx/static/themes/smart/src/scss/{results.scss => results/default.scss} (98%) create mode 100644 searx/static/themes/smart/src/scss/results/images.scss create mode 100644 searx/templates/smart/result_templates/images.html diff --git a/searx/static/themes/smart/css/ltr-style.css b/searx/static/themes/smart/css/ltr-style.css index 7202c7a76..6906e7a78 100644 --- a/searx/static/themes/smart/css/ltr-style.css +++ b/searx/static/themes/smart/css/ltr-style.css @@ -292,7 +292,7 @@ img, video { } [hidden]:where(:not([hidden="until-found"])) { - display: none; + display: none !important; } :root { @@ -343,90 +343,6 @@ img, video { width: 100%; } -.default_group .result { - border-bottom: var(--secondary-background-color) solid 1px; - padding: 1.25rem; -} - -.default_group .result .result-header { - align-items: center; - gap: 1rem; - margin-bottom: .75rem; - display: flex; -} - -.default_group .result .result-header .favicon { - width: 1rem; - height: 1rem; -} - -.default_group .result .result-header .title { - color: var(--primary-color); - flex: 1; - font-size: 1.1rem; - line-height: 1.4; - text-decoration: none; -} - -.default_group .result .result-header .date { - color: var(--secondary-text-color); - background-color: var(--secondary-background-color); - border-radius: .5rem; - padding: .25rem .5rem; - font-size: .8rem; -} - -.default_group .result .result-body { - gap: 1rem; - margin-bottom: .75rem; - display: flex; -} - -.default_group .result .result-body .thumbnail { - object-fit: cover; - border-radius: 1rem; - width: 5rem; - height: 5rem; -} - -.default_group .result .result-body .result-content { - color: var(--text-color); - font-size: .9rem; - line-height: 1.5; -} - -.default_group .result .result-meta { - flex-wrap: wrap; - justify-content: space-between; - align-items: center; - gap: .5rem; - font-size: .8rem; - display: flex; -} - -.default_group .result .result-meta .result-url { - background-color: var(--secondary-background-color); - color: var(--secondary-text-color); - border-radius: 9999px; - padding: .25rem .5rem; - font-size: .85rem; - text-decoration: none; - display: inline-block; -} - -.default_group .result .result-meta .engines { - gap: .5rem; - display: flex; -} - -.default_group .result .result-meta .engines .engine { - border: 1px solid var(--border-color); - text-wrap: nowrap; - border-radius: 9999px; - padding: .25rem .75rem; - font-size: .7rem; -} - .info-box { background: var(--background-color); border-radius: 2rem; @@ -542,6 +458,242 @@ img, video { display: none; } +.default_group .result { + border-bottom: var(--secondary-background-color) solid 1px; + padding: 1.25rem; +} + +.default_group .result .result-header { + align-items: center; + gap: 1rem; + margin-bottom: .75rem; + display: flex; +} + +.default_group .result .result-header .favicon { + width: 1rem; + height: 1rem; +} + +.default_group .result .result-header .title { + color: var(--primary-color); + flex: 1; + font-size: 1.1rem; + line-height: 1.4; + text-decoration: none; +} + +.default_group .result .result-header .date { + color: var(--secondary-text-color); + background-color: var(--secondary-background-color); + border-radius: .5rem; + padding: .25rem .5rem; + font-size: .8rem; +} + +.default_group .result .result-body { + gap: 1rem; + margin-bottom: .75rem; + display: flex; +} + +.default_group .result .result-body .thumbnail { + object-fit: cover; + border-radius: 1rem; + width: 5rem; + height: 5rem; +} + +.default_group .result .result-body .result-content { + color: var(--text-color); + font-size: .9rem; + line-height: 1.5; +} + +.default_group .result .result-meta { + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: .5rem; + font-size: .8rem; + display: flex; +} + +.default_group .result .result-meta .result-url { + background-color: var(--secondary-background-color); + color: var(--secondary-text-color); + border-radius: 9999px; + padding: .25rem .5rem; + font-size: .85rem; + text-decoration: none; + display: inline-block; +} + +.default_group .result .result-meta .engines { + gap: .5rem; + display: flex; +} + +.default_group .result .result-meta .engines .engine { + border: 1px solid var(--border-color); + text-wrap: nowrap; + border-radius: 9999px; + padding: .25rem .75rem; + font-size: .7rem; +} + +.images_group { + columns: 15rem; + gap: 1rem; +} + +.images_group .image { + cursor: pointer; +} + +.images_group .image .image-box { + border-radius: 1rem; + position: relative; + overflow: hidden; +} + +.images_group .image .image-box img { + object-fit: cover; + width: 100%; + height: 100%; + transition: transform .3s; +} + +.images_group .image .image-box .resolution { + color: #fff; + background: #000000b3; + border-radius: .25rem; + padding: .25rem .5rem; + font-size: .8rem; + position: absolute; + bottom: .5rem; + right: .5rem; +} + +.images_group .image:is(:hover, :focus-within) .image-box img { + transform: scale(1.05); +} + +.images_group .image .image-info { + padding: .5rem; +} + +.images_group .image .image-info .title { + text-overflow: ellipsis; + white-space: nowrap; + margin-bottom: .25rem; + font-size: .9rem; + display: block; + overflow: hidden; +} + +.images_group .image .image-info .source { + gap: .5rem; + font-size: .7rem; + display: flex; +} + +.images_group .image .image-info .source .favicon { + width: .8rem; + height: .8rem; +} + +.image-details-container { + border-radius: 2rem; + grid-area: image-detail; + overflow: hidden; +} + +.image-details-container .image-details { + background: var(--background-color); + border-radius: 2rem; + padding: 1.5rem; +} + +@media (width >= 90rem) { + .image-details-container .image-details { + min-width: 25rem; + } +} + +.image-details-container .image-details img { + border-radius: .5rem; + height: 30%; + margin-bottom: 1.5rem; +} + +.image-details-container .image-details h3 { + margin-bottom: 1rem; + font-size: 1.2rem; +} + +.image-details-container .image-details .description { + color: var(--text-color); + margin-bottom: 1.5rem; + font-size: .9rem; + line-height: 1.5; +} + +.image-details-container .image-details .meta { + background-color: var(--secondary-background-color); + border-radius: 1rem; + flex-direction: column; + gap: 1rem; + margin-bottom: 1.5rem; + padding: 1rem; + display: flex; +} + +.image-details-container .image-details .meta .meta-item { + justify-content: space-between; + align-items: center; + display: flex; +} + +.image-details-container .image-details .meta .source { + background-color: var(--background-color); + border-radius: 9999px; + margin-top: .5rem; + padding: .5rem 1rem; +} + +.image-details-container .image-details .engine { + align-items: center; + gap: 1rem; + display: flex; +} + +.image-details-container .image-details .engine .engine-name { + background-color: var(--secondary-background-color); + border-radius: .5rem; + padding: .5rem; +} + +.image-details-container .image-details .actions { + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1rem; + display: flex; +} + +.image-details-container .image-details .actions .btn { + text-align: center; + flex: 1; + width: 100%; + padding: .75rem; + font-weight: 500; +} + +.image-details-container .image-details .actions .btn.copy:disabled { + opacity: .5; + cursor: not-allowed; +} + footer .links a, main:has(.not-found) .button { background-color: var(--primary-color); color: var(--filled-text-color); @@ -687,11 +839,12 @@ main:has(.not-found) .button { max-width: 1800px; margin: 0 auto; padding: 0 1rem; + transition: grid-template-columns .3s ease-in-out; display: grid; overflow: auto; } -@media (width >= 62rem) { +@media (width >= 90rem) { #results { padding: 0 4rem; } @@ -724,6 +877,8 @@ main:has(.not-found) .button { #results .results-container { grid-area: results; + transition: margin-top .1s ease-in-out; + position: relative; } #results .results-container .answer-container { @@ -780,6 +935,18 @@ main:has(.not-found) .button { margin-top: 0; } +#results:has(.image-page) { + grid-template-columns: 2fr 1fr 0fr; + grid-template-areas: "search links links" + "search amount amount" + "results results image-detail" + "footer footer footer"; +} + +#results.image-open { + grid-template-columns: 2fr 0fr 1fr; +} + body:has(.sidebar_btn[open]) { background-color: var(--background-color); gap: .5rem; @@ -789,7 +956,7 @@ body:has(.sidebar_btn[open]) { body:has(.sidebar_btn[open]) #sidebar { border-width: 5px; flex: 1; - min-width: 21rem; + min-width: 25rem; } body:has(.sidebar_btn[open]) #sidebar > .container { @@ -820,7 +987,7 @@ body:has(.sidebar_btn[open]) header .links .link.config { #sidebar > .container { width: 100%; - min-width: 19rem; + min-width: 23rem; height: 100%; margin-left: 50rem; padding: 1rem; @@ -898,7 +1065,7 @@ footer { display: flex; } -@media (width <= 48rem) { +@media (width <= 56rem) { footer { justify-content: center; } @@ -922,7 +1089,7 @@ footer .links { display: flex; } -@media (width <= 30rem) { +@media (width <= 35rem) { .search_box { border-radius: 1rem; width: 70vw; @@ -998,13 +1165,13 @@ footer .links { display: flex; } -@media (width <= 48rem) { +@media (width <= 56rem) { #search.screen #search_categories .categories { transform: scale(.8); } } -@media (width <= 30rem) { +@media (width <= 35rem) { #search.screen #search_categories .categories { display: none; } diff --git a/searx/static/themes/smart/css/rtl-style.css b/searx/static/themes/smart/css/rtl-style.css index 95ac817b3..e820217c7 100644 --- a/searx/static/themes/smart/css/rtl-style.css +++ b/searx/static/themes/smart/css/rtl-style.css @@ -238,7 +238,7 @@ img, video { } [hidden]:where(:not([hidden="until-found"])) { - display: none; + display: none !important; } :root { @@ -289,90 +289,6 @@ img, video { width: 100%; } -.default_group .result { - border-bottom: var(--secondary-background-color) solid 1px; - padding: 1.25rem; -} - -.default_group .result .result-header { - align-items: center; - gap: 1rem; - margin-bottom: .75rem; - display: flex; -} - -.default_group .result .result-header .favicon { - width: 1rem; - height: 1rem; -} - -.default_group .result .result-header .title { - color: var(--primary-color); - flex: 1; - font-size: 1.1rem; - line-height: 1.4; - text-decoration: none; -} - -.default_group .result .result-header .date { - color: var(--secondary-text-color); - background-color: var(--secondary-background-color); - border-radius: .5rem; - padding: .25rem .5rem; - font-size: .8rem; -} - -.default_group .result .result-body { - gap: 1rem; - margin-bottom: .75rem; - display: flex; -} - -.default_group .result .result-body .thumbnail { - object-fit: cover; - border-radius: 1rem; - width: 5rem; - height: 5rem; -} - -.default_group .result .result-body .result-content { - color: var(--text-color); - font-size: .9rem; - line-height: 1.5; -} - -.default_group .result .result-meta { - flex-wrap: wrap; - justify-content: space-between; - align-items: center; - gap: .5rem; - font-size: .8rem; - display: flex; -} - -.default_group .result .result-meta .result-url { - background-color: var(--secondary-background-color); - color: var(--secondary-text-color); - border-radius: 9999px; - padding: .25rem .5rem; - font-size: .85rem; - text-decoration: none; - display: inline-block; -} - -.default_group .result .result-meta .engines { - gap: .5rem; - display: flex; -} - -.default_group .result .result-meta .engines .engine { - border: 1px solid var(--border-color); - text-wrap: nowrap; - border-radius: 9999px; - padding: .25rem .75rem; - font-size: .7rem; -} - .info-box { background: var(--background-color); border-radius: 2rem; @@ -488,6 +404,242 @@ img, video { display: none; } +.default_group .result { + border-bottom: var(--secondary-background-color) solid 1px; + padding: 1.25rem; +} + +.default_group .result .result-header { + align-items: center; + gap: 1rem; + margin-bottom: .75rem; + display: flex; +} + +.default_group .result .result-header .favicon { + width: 1rem; + height: 1rem; +} + +.default_group .result .result-header .title { + color: var(--primary-color); + flex: 1; + font-size: 1.1rem; + line-height: 1.4; + text-decoration: none; +} + +.default_group .result .result-header .date { + color: var(--secondary-text-color); + background-color: var(--secondary-background-color); + border-radius: .5rem; + padding: .25rem .5rem; + font-size: .8rem; +} + +.default_group .result .result-body { + gap: 1rem; + margin-bottom: .75rem; + display: flex; +} + +.default_group .result .result-body .thumbnail { + object-fit: cover; + border-radius: 1rem; + width: 5rem; + height: 5rem; +} + +.default_group .result .result-body .result-content { + color: var(--text-color); + font-size: .9rem; + line-height: 1.5; +} + +.default_group .result .result-meta { + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: .5rem; + font-size: .8rem; + display: flex; +} + +.default_group .result .result-meta .result-url { + background-color: var(--secondary-background-color); + color: var(--secondary-text-color); + border-radius: 9999px; + padding: .25rem .5rem; + font-size: .85rem; + text-decoration: none; + display: inline-block; +} + +.default_group .result .result-meta .engines { + gap: .5rem; + display: flex; +} + +.default_group .result .result-meta .engines .engine { + border: 1px solid var(--border-color); + text-wrap: nowrap; + border-radius: 9999px; + padding: .25rem .75rem; + font-size: .7rem; +} + +.images_group { + columns: 15rem; + gap: 1rem; +} + +.images_group .image { + cursor: pointer; +} + +.images_group .image .image-box { + border-radius: 1rem; + position: relative; + overflow: hidden; +} + +.images_group .image .image-box img { + object-fit: cover; + width: 100%; + height: 100%; + transition: transform .3s; +} + +.images_group .image .image-box .resolution { + color: #fff; + background: #000000b3; + border-radius: .25rem; + padding: .25rem .5rem; + font-size: .8rem; + position: absolute; + bottom: .5rem; + right: .5rem; +} + +.images_group .image:is(:hover, :focus-within) .image-box img { + transform: scale(1.05); +} + +.images_group .image .image-info { + padding: .5rem; +} + +.images_group .image .image-info .title { + text-overflow: ellipsis; + white-space: nowrap; + margin-bottom: .25rem; + font-size: .9rem; + display: block; + overflow: hidden; +} + +.images_group .image .image-info .source { + gap: .5rem; + font-size: .7rem; + display: flex; +} + +.images_group .image .image-info .source .favicon { + width: .8rem; + height: .8rem; +} + +.image-details-container { + border-radius: 2rem; + grid-area: image-detail; + overflow: hidden; +} + +.image-details-container .image-details { + background: var(--background-color); + border-radius: 2rem; + padding: 1.5rem; +} + +@media (width >= 90rem) { + .image-details-container .image-details { + min-width: 25rem; + } +} + +.image-details-container .image-details img { + border-radius: .5rem; + height: 30%; + margin-bottom: 1.5rem; +} + +.image-details-container .image-details h3 { + margin-bottom: 1rem; + font-size: 1.2rem; +} + +.image-details-container .image-details .description { + color: var(--text-color); + margin-bottom: 1.5rem; + font-size: .9rem; + line-height: 1.5; +} + +.image-details-container .image-details .meta { + background-color: var(--secondary-background-color); + border-radius: 1rem; + flex-direction: column; + gap: 1rem; + margin-bottom: 1.5rem; + padding: 1rem; + display: flex; +} + +.image-details-container .image-details .meta .meta-item { + justify-content: space-between; + align-items: center; + display: flex; +} + +.image-details-container .image-details .meta .source { + background-color: var(--background-color); + border-radius: 9999px; + margin-top: .5rem; + padding: .5rem 1rem; +} + +.image-details-container .image-details .engine { + align-items: center; + gap: 1rem; + display: flex; +} + +.image-details-container .image-details .engine .engine-name { + background-color: var(--secondary-background-color); + border-radius: .5rem; + padding: .5rem; +} + +.image-details-container .image-details .actions { + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1rem; + display: flex; +} + +.image-details-container .image-details .actions .btn { + text-align: center; + flex: 1; + width: 100%; + padding: .75rem; + font-weight: 500; +} + +.image-details-container .image-details .actions .btn.copy:disabled { + opacity: .5; + cursor: not-allowed; +} + footer .links a, main:has(.not-found) .button { background-color: var(--primary-color); color: var(--filled-text-color); @@ -633,11 +785,12 @@ main:has(.not-found) .button { max-width: 1800px; margin: 0 auto; padding: 0 1rem; + transition: grid-template-columns .3s ease-in-out; display: grid; overflow: auto; } -@media (width >= 62rem) { +@media (width >= 90rem) { #results { padding: 0 4rem; } @@ -670,6 +823,8 @@ main:has(.not-found) .button { #results .results-container { grid-area: results; + transition: margin-top .1s ease-in-out; + position: relative; } #results .results-container .answer-container { @@ -726,6 +881,18 @@ main:has(.not-found) .button { margin-top: 0; } +#results:has(.image-page) { + grid-template-columns: 2fr 1fr 0fr; + grid-template-areas: "search links links" + "search amount amount" + "results results image-detail" + "footer footer footer"; +} + +#results.image-open { + grid-template-columns: 2fr 0fr 1fr; +} + body:has(.sidebar_btn[open]) { background-color: var(--background-color); gap: .5rem; @@ -735,7 +902,7 @@ body:has(.sidebar_btn[open]) { body:has(.sidebar_btn[open]) #sidebar { border-width: 5px; flex: 1; - min-width: 21rem; + min-width: 25rem; } body:has(.sidebar_btn[open]) #sidebar > .container { @@ -766,7 +933,7 @@ body:has(.sidebar_btn[open]) header .links .link.config { #sidebar > .container { width: 100%; - min-width: 19rem; + min-width: 23rem; height: 100%; margin-right: 50rem; padding: 1rem; @@ -844,7 +1011,7 @@ footer { display: flex; } -@media (width <= 48rem) { +@media (width <= 56rem) { footer { justify-content: center; } @@ -868,7 +1035,7 @@ footer .links { display: flex; } -@media (width <= 30rem) { +@media (width <= 35rem) { .search_box { border-radius: 1rem; width: 70vw; @@ -944,13 +1111,13 @@ footer .links { display: flex; } -@media (width <= 48rem) { +@media (width <= 56rem) { #search.screen #search_categories .categories { transform: scale(.8); } } -@media (width <= 30rem) { +@media (width <= 35rem) { #search.screen #search_categories .categories { display: none; } diff --git a/searx/static/themes/smart/js/app.js b/searx/static/themes/smart/js/app.js index 3f44baffa..7827af831 100644 --- a/searx/static/themes/smart/js/app.js +++ b/searx/static/themes/smart/js/app.js @@ -585,6 +585,106 @@ function hmrAccept(bundle /*: ParcelRequire */ , id /*: string */ ) { } },{}],"3uyIQ":[function(require,module,exports,__globalThis) { +function $(selector) { + return document.querySelector(selector); +} +function $$(selector) { + return document.querySelectorAll(selector); +} +function handleImageDetails(image, { imageSrc, imageTitle, imageContent, imageSource, imageFilesize, imageDownload, formatSpan, filesizeSpan, engineSpan }) { + const resultsContainer = $("#results"); + resultsContainer.classList.add("image-open"); + imageSrc.src = image.getAttribute("data-gitee-src"); + imageTitle.innerText = image.getAttribute("data-gitee-title"); + imageContent.innerText = image.getAttribute("data-gitee-content"); + if (image.hasAttribute("data-gitee-format")) formatSpan.innerText = image.getAttribute("data-gitee-format"); + else formatSpan.innerText = "Unknown"; + if (image.hasAttribute("data-gitee-filesize")) { + if (imageFilesize.hasAttribute("hidden")) imageFilesize.setAttribute("hidden", ""); + filesizeSpan.innerText = image.getAttribute("data-gitee-filesize"); + } else imageFilesize.setAttribute("hidden", ""); + imageSource.href = image.getAttribute("data-gitee-source"); + imageDownload.href = image.getAttribute("data-gitee-src"); + 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 formatSpan = imageFormat.getElementsByClassName("value")[0]; + const filesizeSpan = imageFilesize.getElementsByClassName("value")[0]; + const engineSpan = $("#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; + const element = node; + 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; + 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").drawImage(imageSrc, 0, 0, imageSrc.width, imageSrc.height); + canvas.toBlob((blob)=>{ + if (!blob) return; + navigator.clipboard.write([ + new ClipboardItem({ + "image/*": blob + }) + ]); + }); + canvas.remove(); + }); +} +function afterLoad() { + const resultsContainer = $(".results-container"); + if (!resultsContainer || resultsContainer.nodeType !== Node.ELEMENT_NODE) return; + if (resultsContainer.classList.contains("image-page")) setupImages(resultsContainer); +} +document.addEventListener("DOMContentLoaded", afterLoad); },{}]},["1Y09f","3uyIQ"], "3uyIQ", "parcelRequire94c2") diff --git a/searx/static/themes/smart/src/scss/definitions.scss b/searx/static/themes/smart/src/scss/definitions.scss index fbf8fc05e..c1f263650 100644 --- a/searx/static/themes/smart/src/scss/definitions.scss +++ b/searx/static/themes/smart/src/scss/definitions.scss @@ -19,16 +19,14 @@ $filled-text-color-dark: $filled-text-color; // - Screens - -$screen-xs: 21rem; -$screen-sm: 30rem; -$screen-md: 48rem; -$screen-lg: 62rem; -$screen-xl: 75rem; +$screen-xs: 25rem; +$screen-sm: 35rem; +$screen-md: 56rem; +$screen-lg: 70rem; +$screen-xl: 90rem; // - Rounded corners - $border-radius: 1rem; $border-radius-sm: .5rem; -$border-radius-lg: 2rem; - -// - Transitions - \ No newline at end of file +$border-radius-lg: 2rem; \ No newline at end of file diff --git a/searx/static/themes/smart/src/scss/reset.scss b/searx/static/themes/smart/src/scss/reset.scss index 2a7fe849a..4cbe09fb6 100644 --- a/searx/static/themes/smart/src/scss/reset.scss +++ b/searx/static/themes/smart/src/scss/reset.scss @@ -369,5 +369,5 @@ video { /* Make elements with the HTML hidden attribute stay hidden by default */ [hidden]:where(:not([hidden="until-found"])) { - display: none; + display: none !important; } \ No newline at end of file diff --git a/searx/static/themes/smart/src/scss/results.scss b/searx/static/themes/smart/src/scss/results/default.scss similarity index 98% rename from searx/static/themes/smart/src/scss/results.scss rename to searx/static/themes/smart/src/scss/results/default.scss index 1103d442d..33ec28439 100644 --- a/searx/static/themes/smart/src/scss/results.scss +++ b/searx/static/themes/smart/src/scss/results/default.scss @@ -1,4 +1,4 @@ -@use './mixins.scss' as mixin; +@use '../mixins.scss' as mixin; .default_group .result { padding: 1.25rem; diff --git a/searx/static/themes/smart/src/scss/results/images.scss b/searx/static/themes/smart/src/scss/results/images.scss new file mode 100644 index 000000000..8d0d1e049 --- /dev/null +++ b/searx/static/themes/smart/src/scss/results/images.scss @@ -0,0 +1,158 @@ +@use "../mixins.scss" as mixin; +@use '../definitions.scss' as *; + +.images_group { + columns: 15rem; + gap: 1rem; + + .image { + cursor: pointer; + + .image-box { + overflow: hidden; + @include mixin.rounded(); + position: relative; + + img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; + } + + .resolution { + position: absolute; + right: 0.5rem; + bottom: .5rem; + background: rgba(0, 0, 0, 0.7); + color: white; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.8rem; + } + } + + &:is(:hover, :focus-within) { + .image-box img { + transform: scale(1.05); + } + } + + + .image-info { + padding: 0.5rem; + + .title { + display: block; + font-size: 0.9rem; + margin-bottom: 0.25rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .source { + display: flex; + gap: .5rem; + font-size: 0.7rem; + + .favicon { + width: .8rem; + height: .8rem; + } + } + } + + } +} + +.image-details-container { + grid-area: image-detail; + @include mixin.rounded(lg); + overflow: hidden; + + .image-details { + background: var(--background-color); + @include mixin.rounded(lg); + padding: 1.5rem; + + @include mixin.screens($screen-xl) { + min-width: $screen-xs; + } + + img { + height: 30%; + border-radius: 0.5rem; + margin-bottom: 1.5rem; + } + + + h3 { + font-size: 1.2rem; + margin-bottom: 1rem; + } + + .description { + color: var(--text-color); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 1.5rem; + } + + .meta { + display: flex; + flex-direction: column; + background-color: var(--secondary-background-color); + gap: 1rem; + margin-bottom: 1.5rem; + padding: 1rem; + @include mixin.rounded(); + + .meta-item { + display: flex; + justify-content: space-between; + align-items: center; + } + + .source { + margin-top: .5rem; + background-color: var(--background-color); + padding: .5rem 1rem; + @include mixin.rounded(full); + } + + } + + .engine { + display: flex; + gap: 1rem; + align-items: center; + + .engine-name { + background-color: var(--secondary-background-color); + padding: .5rem; + @include mixin.rounded(sm); + } + } + + .actions { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1rem; + + .btn { + flex: 1; + width: 100%; + padding: 0.75rem; + text-align: center; + font-weight: 500; + + &.copy:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + } + } +} \ No newline at end of file diff --git a/searx/static/themes/smart/src/scss/style.scss b/searx/static/themes/smart/src/scss/style.scss index 4a6bb6875..7e8ec2099 100644 --- a/searx/static/themes/smart/src/scss/style.scss +++ b/searx/static/themes/smart/src/scss/style.scss @@ -15,9 +15,12 @@ $dir: ltr !default; // Button @use 'button'; -@forward './results.scss'; @forward './infobox.scss'; +// Result Styles +@forward './results/default'; +@forward './results/images'; + @function toRem($value) { @return floor($value / 16) * 1rem; } @@ -188,12 +191,12 @@ main:has(.not-found) { padding: 0 1rem; max-width: 1800px; overflow: auto; + transition: grid-template-columns 300ms ease-in-out; - @include mixin.screens($screen-lg) { + @include mixin.screens($screen-xl) { padding: 0 4rem; } - &>.links { grid-area: links; width: 100%; @@ -221,6 +224,8 @@ main:has(.not-found) { .results-container { grid-area: results; + position: relative; + transition: margin-top 100ms ease-in-out; .answer-container { background-color: var(--background-color); @@ -276,6 +281,20 @@ main:has(.not-found) { margin-top: 0; } } + + &:has(.image-page) { + grid-template-columns: 2fr 1fr 0fr; + grid-template-areas: + "search links links" + "search amount amount" + "results results image-detail" + "footer footer footer"; + + } + + &.image-open { + grid-template-columns: 2fr 0fr 1fr; + } } body:has(.sidebar_btn[open]) { diff --git a/searx/static/themes/smart/src/ts/app.ts b/searx/static/themes/smart/src/ts/app.ts index e69de29bb..40f5d8632 100644 --- a/searx/static/themes/smart/src/ts/app.ts +++ b/searx/static/themes/smart/src/ts/app.ts @@ -0,0 +1,166 @@ +function $(selector: string) { + return document.querySelector(selector); +} + +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; +} + +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(); + }); +} + +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); diff --git a/searx/templates/smart/result_templates/default.html b/searx/templates/smart/result_templates/default.html index 33bfef4f8..e7a4b464d 100644 --- a/searx/templates/smart/result_templates/default.html +++ b/searx/templates/smart/result_templates/default.html @@ -11,12 +11,14 @@ endif %} > + {% if favicon_resolver != "" %}
+ {% endif %} {{ result.title | safe }} {% if result.publishedDate %}