refactor: rename CSS classes for clarity, enhance layout responsiveness, and remove unused styles in index.scss; update Result.vue and index.vue for improved content rendering and structure

This commit is contained in:
PandaDEV 2025-03-15 00:21:17 +01:00
parent 71dc5b273d
commit 96e5505b4d
No known key found for this signature in database
GPG key ID: 13EFF9BAF70EE75C
3 changed files with 173 additions and 494 deletions

View file

@ -25,7 +25,7 @@ main {
justify-content: space-between; justify-content: space-between;
} }
.content { .container {
height: 376px; height: 376px;
width: 100%; width: 100%;
display: flex; display: flex;
@ -36,7 +36,7 @@ main {
flex-direction: column; flex-direction: column;
padding: 14px 8px; padding: 14px 8px;
gap: 8px; gap: 8px;
width: 286px; min-width: 286px;
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
.time-separator { .time-separator {
@ -69,349 +69,99 @@ main {
} }
} }
// .bg { .right {
// width: 100%; display: flex;
// height: 100%; flex-direction: column;
// background-color: $primary; width: 100%;
// border: 1px solid $divider; }
// border-radius: 12px;
// z-index: -1;
// position: fixed;
// outline: none;
// display: flex;
// flex-direction: column;
// }
// .search { .content {
// width: 100%; height: 100%;
// height: $search-height; font-family: CommitMono !important;
// background-color: transparent; font-size: 12px;
// outline: none; letter-spacing: 1;
// border: none; border-radius: 10px;
// font-size: 18px; width: 100%;
// color: $text; white-space: pre-wrap;
// padding-inline: 16px; word-wrap: break-word;
// border-bottom: 1px solid $divider; display: flex;
// font-family: SFRoundedMedium; flex-direction: column;
// } align-items: center;
overflow: hidden;
z-index: 2;
color: $text;
// .main-container { &:not(:has(.image)) {
// display: flex; padding: 8px;
// flex: 1; }
// }
// .results { span {
// width: $sidebar-width; font-family: CommitMono !important;
// height: calc(100vh - $search-height - $bottom-bar-height); }
// border-right: 1px solid $divider;
// display: flex;
// flex-direction: column;
// padding-inline: 8px;
// padding-bottom: 8px;
// overflow-y: auto;
// overflow-x: hidden;
// z-index: 3;
// .result { .image {
// height: 40px; width: 100%;
// font-size: 14px; height: 100%;
// display: flex; object-fit: contain;
// align-items: center; object-position: center;
// padding: 10px; }
// letter-spacing: 0.5px; }
// gap: 10px;
// overflow: hidden;
// text-overflow: clip;
// white-space: nowrap;
// color: $text;
// cursor: pointer;
// &.selected { .information {
// background-color: $divider; min-height: 160px;
// } width: 100%;
// } border-top: 1px solid $divider;
padding: 14px;
z-index: 1;
display: flex;
flex-direction: column;
gap: 14px;
// .time-separator { .title {
// font-size: 12px; font-family: SFRoundedSemiBold;
// color: $text2; font-size: 12px;
// font-family: SFRoundedSemiBold; letter-spacing: 0.6px;
// padding-left: 8px; color: $text;
// padding-bottom: 8px; }
// padding-top: 14px;
// }
// .favicon, .info-content {
// .image, display: flex;
// .icon { gap: 0;
// width: 18px; flex-direction: column;
// height: 18px;
// }
// }
// .right-panel { .info-row {
// display: flex; display: flex;
// flex-direction: column; width: 100%;
// flex: 1; font-size: 12px;
// } justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid $divider;
line-height: 1;
// .content { &:last-child {
// height: $content-view-height; border-bottom: none;
// font-family: CommitMono !important; padding-bottom: 0;
// font-size: 12px; }
// letter-spacing: 1;
// border-radius: 10px;
// width: calc(100% - $sidebar-width);
// white-space: pre-wrap;
// word-wrap: break-word;
// display: flex;
// flex-direction: column;
// align-items: center;
// overflow: hidden;
// z-index: 2;
// color: $text;
// &:not(:has(.image)) { &:first-child {
// padding: 8px; padding-top: 22px;
// } }
// span { p {
// font-family: CommitMono !important; font-family: SFRoundedMedium;
// } color: $text2;
font-weight: 500;
flex-shrink: 0;
}
// .image { span {
// width: 100%; font-family: CommitMono;
// height: 100%; color: $text;
// object-fit: contain; text-overflow: ellipsis;
// object-position: center; overflow: hidden;
// } white-space: nowrap;
// } margin-left: 32px;
}
// .bottom-bar { }
// height: $bottom-bar-height; }
// width: 100%; }
// backdrop-filter: blur(18px);
// background-color: hsla(40, 3%, 16%, 0.8);
// z-index: 100;
// border-radius: 0 0 12px 12px;
// display: flex;
// flex-direction: row;
// justify-content: space-between;
// padding-inline: 12px;
// padding-right: 6px;
// padding-top: 1px;
// align-items: center;
// font-size: 14px;
// border-top: 1px solid $divider;
// p {
// color: $text2;
// }
// .left {
// display: flex;
// align-items: center;
// gap: 8px;
// .logo {
// width: 18px;
// height: 18px;
// }
// }
// .right {
// display: flex;
// align-items: center;
// .paste p {
// color: $text;
// }
// .actions div {
// display: flex;
// align-items: center;
// gap: 2px;
// }
// .divider {
// width: 2px;
// height: 12px;
// background-color: $divider;
// margin-left: 8px;
// margin-right: 4px;
// transition: all 0.2s;
// }
// .paste,
// .actions {
// padding: 4px;
// padding-left: 8px;
// display: flex;
// align-items: center;
// gap: 8px;
// border-radius: 7px;
// background-color: transparent;
// transition: all 0.2s;
// cursor: pointer;
// }
// .paste:hover,
// .actions:hover {
// background-color: $divider;
// }
// &:hover .paste:hover ~ .divider,
// &:hover .actions:hover ~ .divider {
// opacity: 0;
// }
// }
// }
// .information {
// height: $info-panel-height;
// width: calc(100% - $sidebar-width);
// border-top: 1px solid $divider;
// background-color: $primary;
// padding: 14px;
// z-index: 1;
// display: flex;
// flex-direction: column;
// gap: 14px;
// .title {
// font-family: SFRoundedSemiBold;
// font-size: 12px;
// letter-spacing: 0.6px;
// color: $text;
// }
// .info-content {
// display: flex;
// gap: 0;
// flex-direction: column;
// .info-row {
// display: flex;
// width: 100%;
// font-size: 12px;
// justify-content: space-between;
// padding: 8px 0;
// border-bottom: 1px solid $divider;
// line-height: 1;
// &:last-child {
// border-bottom: none;
// padding-bottom: 0;
// }
// &:first-child {
// padding-top: 22px;
// }
// p {
// font-family: SFRoundedMedium;
// color: $text2;
// font-weight: 500;
// flex-shrink: 0;
// }
// span {
// font-family: CommitMono;
// color: $text;
// text-overflow: ellipsis;
// overflow: hidden;
// white-space: nowrap;
// margin-left: 32px;
// }
// }
// }
// }
// .clothoid-corner {
// clip-path: polygon(
// 13.890123px 0px,
// calc(100% - 13.890123px) 0px,
// calc(100% - 12.723414px) 0.004211px,
// calc(100% - 11.556933px) 0.025635px,
// calc(100% - 10.391895px) 0.085062px,
// calc(100% - 9.231074px) 0.199291px,
// calc(100% - 8.079275px) 0.382298px,
// calc(100% - 6.947448px) 0.662609px,
// calc(100% - 5.844179px) 1.039291px,
// calc(100% - 4.793324px) 1.542842px,
// calc(100% - 3.811369px) 2.169728px,
// calc(100% - 2.926417px) 2.926417px,
// calc(100% - 2.169728px) 3.811369px,
// calc(100% - 1.542842px) 4.793324px,
// calc(100% - 1.039291px) 5.844179px,
// calc(100% - 0.662609px) 6.947448px,
// calc(100% - 0.382298px) 8.079275px,
// calc(100% - 0.199291px) 9.231074px,
// calc(100% - 0.085062px) 10.391895px,
// calc(100% - 0.025635px) 11.556933px,
// calc(100% - 0.004211px) 12.723414px,
// 100% 13.890123px,
// 100% calc(100% - 13.890123px),
// calc(100% - 0.004211px) calc(100% - 12.723414px),
// calc(100% - 0.025635px) calc(100% - 11.556933px),
// calc(100% - 0.085062px) calc(100% - 10.391895px),
// calc(100% - 0.199291px) calc(100% - 9.231074px),
// calc(100% - 0.382298px) calc(100% - 8.079275px),
// calc(100% - 0.662609px) calc(100% - 6.947448px),
// calc(100% - 1.039291px) calc(100% - 5.844179px),
// calc(100% - 1.542842px) calc(100% - 4.793324px),
// calc(100% - 2.169728px) calc(100% - 3.811369px),
// calc(100% - 2.926417px) calc(100% - 2.926417px),
// calc(100% - 3.811369px) calc(100% - 2.169728px),
// calc(100% - 4.793324px) calc(100% - 1.542842px),
// calc(100% - 5.844179px) calc(100% - 1.039291px),
// calc(100% - 6.947448px) calc(100% - 0.662609px),
// calc(100% - 8.079275px) calc(100% - 0.382298px),
// calc(100% - 9.231074px) calc(100% - 0.199291px),
// calc(100% - 10.391895px) calc(100% - 0.085062px),
// calc(100% - 11.556933px) calc(100% - 0.025635px),
// calc(100% - 12.723414px) calc(100% - 0.004211px),
// calc(100% - 13.890123px) 100%,
// 13.890123px 100%,
// 12.723414px calc(100% - 0.004211px),
// 11.556933px calc(100% - 0.025635px),
// 10.391895px calc(100% - 0.085062px),
// 9.231074px calc(100% - 0.199291px),
// 8.079275px calc(100% - 0.382298px),
// 6.947448px calc(100% - 0.662609px),
// 5.844179px calc(100% - 1.039291px),
// 4.793324px calc(100% - 1.542842px),
// 3.811369px calc(100% - 2.169728px),
// 2.926417px calc(100% - 2.926417px),
// 2.169728px calc(100% - 3.811369px),
// 1.542842px calc(100% - 4.793324px),
// 1.039291px calc(100% - 5.844179px),
// 0.662609px calc(100% - 6.947448px),
// 0.382298px calc(100% - 8.079275px),
// 0.199291px calc(100% - 9.231074px),
// 0.085062px calc(100% - 10.391895px),
// 0.025635px calc(100% - 11.556933px),
// 0.004211px calc(100% - 12.723414px),
// 0px calc(100% - 13.890123px),
// 0px 13.890123px,
// 0.004211px 12.723414px,
// 0.025635px 11.556933px,
// 0.085062px 10.391895px,
// 0.199291px 9.231074px,
// 0.382298px 8.079275px,
// 0.662609px 6.947448px,
// 1.039291px 5.844179px,
// 1.542842px 4.793324px,
// 2.169728px 3.811369px,
// 2.926417px 2.926417px,
// 3.811369px 2.169728px,
// 4.793324px 1.542842px,
// 5.844179px 1.039291px,
// 6.947448px 0.662609px,
// 8.079275px 0.382298px,
// 9.231074px 0.199291px,
// 10.391895px 0.085062px,
// 11.556933px 0.025635px,
// 12.723414px 0.004211px,
// 13.890123px 0px
// );
// }

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
:class="['result clothoid-corner', { selected }]" :class="['result', { selected }]"
@click="$emit('select')" @click="$emit('select')"
:ref="el => { if (selected && el) $emit('setRef', el as HTMLElement) }"> :ref="el => { if (selected && el) $emit('setRef', el as HTMLElement) }">
<template v-if="item.content_type === 'image'"> <template v-if="item.content_type === 'image'">

View file

@ -1,9 +1,7 @@
<template> <template>
<main> <main>
<TopBar <TopBar ref="topBar" @search="searchHistory" />
ref="topBar" <div class="container">
@search="searchHistory" />
<div class="content">
<OverlayScrollbarsComponent <OverlayScrollbarsComponent
class="results" class="results"
ref="resultsContainer" ref="resultsContainer"
@ -23,113 +21,55 @@
:dimensions="imageDimensions[item.id]" :dimensions="imageDimensions[item.id]"
@select="selectItem(groupIndex, index)" @select="selectItem(groupIndex, index)"
@image-error="onImageError" @image-error="onImageError"
@setRef="el => selectedElement = el" /> @setRef="(el) => (selectedElement = el)" />
</div> </div>
</div> </div>
</OverlayScrollbarsComponent> </OverlayScrollbarsComponent>
<div class="right"> <div class="right">
<div class="content"></div> <div
<div class="information"></div> class="content"
v-if="selectedItem?.content_type === ContentType.Image">
<img :src="imageUrls[selectedItem.id]" alt="Image" class="image" />
</div>
<div
v-else-if="selectedItem && isYoutubeWatchUrl(selectedItem.content)"
class="content">
<img
class="image"
:src="getYoutubeThumbnail(selectedItem.content)"
alt="YouTube Thumbnail" />
</div>
<div
class="content"
v-else-if="
selectedItem?.content_type === ContentType.Link && pageOgImage
">
<img :src="pageOgImage" alt="Image" class="image" />
</div>
<OverlayScrollbarsComponent v-else class="content">
<span>{{ selectedItem?.content || "" }}</span>
</OverlayScrollbarsComponent>
<OverlayScrollbarsComponent
class="information"
:options="{ scrollbars: { autoHide: 'scroll' } }">
<div class="title">Information</div>
<div class="info-content" v-if="selectedItem && getInfo">
<div class="info-row" v-for="(row, index) in infoRows" :key="index">
<p class="label">{{ row.label }}</p>
<span
:class="{ 'url-truncate': row.isUrl }"
:data-text="row.value">
{{ row.value }}
</span>
</div>
</div>
</OverlayScrollbarsComponent>
</div> </div>
</div> </div>
<BottomBar /> <BottomBar />
</main> </main>
<!-- <div class="bg" tabindex="0"> <!-- <div class="right-panel">
<input
ref="searchInput"
v-model="searchQuery"
@input="searchHistory"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
class="search"
type="text"
placeholder="Type to filter entries..." />
<div class="main-container">
<OverlayScrollbarsComponent
class="results"
ref="resultsContainer"
:options="{ scrollbars: { autoHide: 'scroll' } }">
<template v-for="(group, groupIndex) in groupedHistory" :key="groupIndex">
<div class="time-separator">{{ group.label }}</div>
<div
v-for="(item, index) in group.items"
:key="item.id"
:class="[
'result clothoid-corner',
{ selected: isSelected(groupIndex, index) },
]"
@click="selectItem(groupIndex, index)"
:ref="
(el: any) => {
if (isSelected(groupIndex, index))
selectedElement = el as HTMLElement;
}
">
<template v-if="item.content_type === 'image'">
<img
v-if="imageUrls[item.id]"
:src="imageUrls[item.id]"
alt="Image"
class="image"
@error="onImageError" />
<img v-else src="../public/icons/Image.svg" class="icon" />
</template>
<template v-else-if="hasFavicon(item.favicon ?? '')">
<img
:src="
item.favicon
? getFaviconFromDb(item.favicon)
: '../public/icons/Link.svg'
"
alt="Favicon"
class="favicon"
@error="
($event.target as HTMLImageElement).src =
'../public/icons/Link.svg'
" />
</template>
<img
src="../public/icons/File.svg"
class="icon"
v-else-if="item.content_type === ContentType.File" />
<img
src="../public/icons/Text.svg"
class="icon"
v-else-if="item.content_type === ContentType.Text" />
<div v-else-if="item.content_type === ContentType.Color">
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<g>
<rect width="18" height="18" />
<path
d="M9 18C12.2154 18 15.1865 16.2846 16.7942 13.5C18.4019 10.7154 18.4019 7.28461 16.7942 4.5C15.1865 1.71539 12.2154 -1.22615e-06 9 0C5.78461 0 2.81347 1.71539 1.20577 4.5C-0.401925 7.28461 -0.401923 10.7154 1.20577 13.5C2.81347 16.2846 5.78461 18 9 18Z"
fill="#E5DFD5" />
<path
d="M9 16C7.14348 16 5.36301 15.2625 4.05025 13.9497C2.7375 12.637 2 10.8565 2 9C2 7.14348 2.7375 5.36301 4.05025 4.05025C5.36301 2.7375 7.14348 2 9 2C10.8565 2 12.637 2.7375 13.9497 4.05025C15.2625 5.36301 16 7.14348 16 9C16 10.8565 15.2625 12.637 13.9497 13.9497C12.637 15.2625 10.8565 16 9 16Z"
:fill="item.content" />
</g>
</svg>
</div>
<img
src="../public/icons/Code.svg"
class="icon"
v-else-if="item.content_type === ContentType.Code" />
<span v-if="item.content_type === ContentType.Image">
Image ({{ imageDimensions[item.id] || "Loading..." }})
</span>
<span v-else>{{ truncateContent(item.content) }}</span>
</div>
</template>
</OverlayScrollbarsComponent>
<div class="right-panel">
<div <div
class="content" class="content"
v-if="selectedItem?.content_type === ContentType.Image"> v-if="selectedItem?.content_type === ContentType.Image">
@ -168,33 +108,6 @@
</div> </div>
</OverlayScrollbarsComponent> </OverlayScrollbarsComponent>
</div> </div>
</div>
<div class="bottom-bar">
<div class="left">
<img class="logo" width="18px" src="../public/logo.png" alt="" />
<p>Qopy</p>
</div>
<div class="right">
<div class="paste" @click="pasteSelectedItem">
<p>Paste</p>
<img src="../public/enter.svg" alt="" />
</div>
<div class="divider"></div>
<div class="actions">
<p>Actions</p>
<div>
<img
v-if="os === 'windows' || os === 'linux'"
src="../public/ctrl.svg"
alt="" />
<img v-if="os === 'macos'" src="../public/cmd.svg" alt="" />
<img src="../public/k.svg" alt="" />
</div>
</div>
</div>
</div>
<Noise />
</div> --> </div> -->
</template> </template>
@ -217,7 +130,12 @@ import type {
InfoCode, InfoCode,
} from "~/types/types"; } from "~/types/types";
import { Key, keyboard } from "wrdu-keyboard"; import { Key, keyboard } from "wrdu-keyboard";
import { selectedGroupIndex, selectedItemIndex, selectedElement, useSelectedResult } from '~/lib/selectedResult' import {
selectedGroupIndex,
selectedItemIndex,
selectedElement,
useSelectedResult,
} from "~/lib/selectedResult";
interface GroupedHistory { interface GroupedHistory {
label: string; label: string;
@ -250,7 +168,7 @@ const imageLoading = ref<boolean>(false);
const pageTitle = ref<string>(""); const pageTitle = ref<string>("");
const pageOgImage = ref<string>(""); const pageOgImage = ref<string>("");
const topBar = ref<{ searchInput: HTMLInputElement | null } | null>(null) const topBar = ref<{ searchInput: HTMLInputElement | null } | null>(null);
const isSameDay = (date1: Date, date2: Date): boolean => { const isSameDay = (date1: Date, date2: Date): boolean => {
return ( return (
@ -313,7 +231,8 @@ const groupedHistory = computed<GroupedHistory[]>(() => {
.map(([label, items]) => ({ label, items })); .map(([label, items]) => ({ label, items }));
}); });
const { selectedItem, isSelected, selectNext, selectPrevious, selectItem } = useSelectedResult(groupedHistory) const { selectedItem, isSelected, selectNext, selectPrevious, selectItem } =
useSelectedResult(groupedHistory);
const loadHistoryChunk = async (): Promise<void> => { const loadHistoryChunk = async (): Promise<void> => {
if (isLoading) return; if (isLoading) return;
@ -411,13 +330,19 @@ const scrollToSelectedItem = (): void => {
if (isAbove) { if (isAbove) {
viewport.scrollTo({ viewport.scrollTo({
top: viewport.scrollTop + (elementRect.top - viewportRect.top) - (isFirstItemInGroup ? TOP_SCROLL_PADDING : SCROLL_PADDING), top:
behavior: "smooth" viewport.scrollTop +
(elementRect.top - viewportRect.top) -
(isFirstItemInGroup ? TOP_SCROLL_PADDING : SCROLL_PADDING),
behavior: "smooth",
}); });
} else if (isBelow) { } else if (isBelow) {
viewport.scrollTo({ viewport.scrollTo({
top: viewport.scrollTop + (elementRect.bottom - viewportRect.bottom) + SCROLL_PADDING, top:
behavior: "smooth" viewport.scrollTop +
(elementRect.bottom - viewportRect.bottom) +
SCROLL_PADDING,
behavior: "smooth",
}); });
} }
}, 10); }, 10);
@ -425,15 +350,15 @@ const scrollToSelectedItem = (): void => {
}; };
const searchHistory = async (query: string): Promise<void> => { const searchHistory = async (query: string): Promise<void> => {
searchQuery.value = query searchQuery.value = query;
if (!query.trim()) { if (!query.trim()) {
history.value = [] history.value = [];
offset = 0 offset = 0;
await loadHistoryChunk() await loadHistoryChunk();
return return;
} }
const results = await $history.searchHistory(query) const results = await $history.searchHistory(query);
history.value = results.map((item) => history.value = results.map((item) =>
Object.assign( Object.assign(
new HistoryItem( new HistoryItem(
@ -446,12 +371,12 @@ const searchHistory = async (query: string): Promise<void> => {
), ),
{ id: item.id, timestamp: new Date(item.timestamp) } { id: item.id, timestamp: new Date(item.timestamp) }
) )
) );
if (groupedHistory.value.length > 0) { if (groupedHistory.value.length > 0) {
handleSelection(0, 0, false) handleSelection(0, 0, false);
} }
} };
const pasteSelectedItem = async (): Promise<void> => { const pasteSelectedItem = async (): Promise<void> => {
if (!selectedItem.value) return; if (!selectedItem.value) return;
@ -579,9 +504,9 @@ const handleSelection = (
itemIndex: number, itemIndex: number,
shouldScroll: boolean = true shouldScroll: boolean = true
): void => { ): void => {
selectItem(groupIndex, itemIndex) selectItem(groupIndex, itemIndex);
if (shouldScroll) scrollToSelectedItem() if (shouldScroll) scrollToSelectedItem();
} };
const setupEventListeners = async (): Promise<void> => { const setupEventListeners = async (): Promise<void> => {
await listen("clipboard-content-updated", async () => { await listen("clipboard-content-updated", async () => {
@ -689,18 +614,22 @@ const hideApp = async (): Promise<void> => {
const focusSearchInput = (): void => { const focusSearchInput = (): void => {
nextTick(() => { nextTick(() => {
topBar.value?.searchInput?.focus() topBar.value?.searchInput?.focus();
}) });
} };
const onImageError = (): void => { const onImageError = (): void => {
imageLoadError.value = true; imageLoadError.value = true;
imageLoading.value = false; imageLoading.value = false;
}; };
watch([selectedGroupIndex, selectedItemIndex], () => { watch(
[selectedGroupIndex, selectedItemIndex],
() => {
scrollToSelectedItem(); scrollToSelectedItem();
}, { flush: 'post' }); },
{ flush: "post" }
);
watch(searchQuery, () => { watch(searchQuery, () => {
searchHistory(searchQuery.value); searchHistory(searchQuery.value);