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
This commit is contained in:
Gnkalk 2025-02-12 18:00:43 +03:30
parent 0e1fd94c5c
commit 62e64820f8
12 changed files with 1121 additions and 202 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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")

View file

@ -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 -

View file

@ -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;
}

View file

@ -1,4 +1,4 @@
@use './mixins.scss' as mixin;
@use '../mixins.scss' as mixin;
.default_group .result {
padding: 1.25rem;

View file

@ -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;
}
}
}
}
}

View file

@ -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]) {

View file

@ -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);

View file

@ -11,12 +11,14 @@
endif
%}
>
{% if favicon_resolver != "" %}
<div class="favicon">
<img
loading="lazy"
src="{{ favicon_url(result.parsed_url.netloc) }}"
/>
</div>
{% endif %}
<span class="title"> {{ result.title | safe }} </span>
{% if result.publishedDate %}
<time class="date" datetime="{{ result.publishedDate }}">

View file

@ -0,0 +1,89 @@
{% from 'smart/icons.html' import icon_small %}
<a
href="{{ result.img_src }}"
class="image"
{%
if
results_on_new_tab
%}target="_blank"
rel="noopener noreferrer"
{%
else
%}rel="noreferrer"
{%
endif
%}
data-gitee-src="{{ image_proxify(result.img_src) }}"
data-gitee-title="{{ result.title|striptags }}"
data-gitee-url="{{ result.url }}"
data-gitee-content="{{ result.content|striptags }}"
{%
if
result.img_format
%}
data-gitee-format="{{ result.img_format }}"
{%
endif
%}
{%
if
result.filesize
%}
data-gitee-filesize="{{ result.filesize }}"
{%
endif
%}
{%-
if
result.source
%}
data-gitee-source="{{ result.source }}"
{%
endif
%}
data-gitee-engine="{{ result.engine }}"
{%
if
favicon_resolver
!=""
and
result.parsed_url.netloc
%}
{% set favicon_url = favicon_url(result.parsed_url.netloc) %}
{%
if
favicon_url
%}
data-gitee-favicon="{{ favicon_url }}"
{%
endif
%}
{%
endif
%}{{
result.parsed_url.netloc
}}
data-gitee-netloc="{{ result.parsed_url.netloc }}"
>
<div class="image-box">
<img
src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}"
alt="{{ result.title|striptags }}"
loading="lazy"
/>
{% if result.resolution %}
<span class="resolution">{{ result.resolution }}</span>
{% endif %}
</div>
<div class="image-info">
<div class="source">
{% if favicon_url %}
<div class="favicon">
<img loading="lazy" src="{{ favicon_url }}" />
</div>
{% endif %}{{ result.parsed_url.netloc }}
</div>
<span class="title">{{ result.title|striptags }}</span>
</div>
</a>

View file

@ -4,10 +4,9 @@
<a href="" class="link">{{icon_small('donate')}}</a>
{% include 'smart/madules/sidebar_btn.html' %}
</div>
{% if results and results|map(attribute='template')|unique|list|count and
'images' in results|map(attribute='template')|unique|list %} {% set
is_image_page = 'only_template_images' %} {% endif %}
<div class="results-container {{ 'image-page' if is_image_page else '' }}">
<div
class="results-container {% if results and results|map(attribute='template')|unique|list|count and 'images.html' in results|map(attribute='template')|unique|list %} image-page {% endif %}"
>
{% if answers %}
<div class="answer-container">
<div class="title">
@ -57,4 +56,58 @@ is_image_page = 'only_template_images' %} {% endif %}
<div class="info-panel">
{% if infoboxes %} {% include 'smart/madules/infoboxs.html' %} {% endif %}
</div>
{% include 'smart/footer.html' %} {% endblock %}
{% if 'images.html' in results|map(attribute='template')|unique|list%}
<div class="image-details-container">
<div class="image-details">
<img src=".." alt=".." id="image-src" />
<div class="actions">
<a
href="..."
class="btn primary"
download="image.png"
id="image-download"
>
Download
</a>
<button class="btn copy" id="image-copy">Copy</button>
</div>
<div class="detail-info">
<h3 id="image-title">...</h3>
<p class="description" id="image-content">...</p>
<div class="meta">
<div class="meta-item" id="image-format">
<span class="label">{{ _('Format') }}:</span>
<span class="value">...</span>
</div>
<div class="meta-item" id="image-filesize">
<span class="label">{{ _('Filesize') }}:</span>
<span class="value">...</span>
</div>
<a
href="..."
class="source"
id="image-source"
{%
if
results_on_new_tab
%}target="_blank"
rel="noopener noreferrer"
{%
else
%}rel="noreferrer"
{%
endif
%}
>
{{ _('View source') }}
</a>
</div>
<p class="engine">
{{ _('Engine') }}
<span class="engine-name" id="image-engine"></span>
</p>
</div>
</div>
</div>
{% endif %} {% include 'smart/footer.html' %} {% endblock %}