mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-22 05:34:04 +02:00
added favicons and youtube thumbnails
This commit is contained in:
parent
b65121148f
commit
aa283284fe
2 changed files with 65 additions and 12 deletions
56
app.vue
56
app.vue
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="bg" @keydown.down.prevent="selectNext" @keydown.up.prevent="selectPrevious"
|
<div class="bg" @keydown.down.prevent="selectNext" @keydown.up.prevent="selectPrevious"
|
||||||
@keydown.enter.prevent="pasteSelectedItem" @keydown.esc="hideApp" tabindex="0">
|
@keydown.enter.prevent="pasteSelectedItem" @keydown.esc="hideApp" tabindex="0">
|
||||||
<input v-model="searchQuery" @input="searchHistory" autocorrect="off" autocapitalize="off" spellcheck="false"
|
<input ref="searchInput" v-model="searchQuery" @input="searchHistory" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||||
class="search" type="text" placeholder="Type to filter entries...">
|
class="search" type="text" placeholder="Type to filter entries...">
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
|
@ -32,13 +32,17 @@
|
||||||
:class="['result clothoid-corner', { 'selected': isSelected(groupIndex, index) }]"
|
:class="['result clothoid-corner', { 'selected': isSelected(groupIndex, index) }]"
|
||||||
@click="selectItem(groupIndex, index)"
|
@click="selectItem(groupIndex, index)"
|
||||||
:ref="el => { if (isSelected(groupIndex, index)) selectedElement = el }">
|
:ref="el => { if (isSelected(groupIndex, index)) selectedElement = el }">
|
||||||
<FileIcon />
|
<img v-if="isUrl(item.content)" :src="getFavicon(item.content)" alt="Favicon" class="favicon">
|
||||||
{{ truncateContent(item.content) }}
|
<FileIcon v-else />
|
||||||
|
<img v-if="item.type === 'image'" :src="item.content" alt="Image" class="preview-image">
|
||||||
|
<span v-else>{{ truncateContent(item.content) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
<OverlayScrollbarsComponent class="content">
|
<OverlayScrollbarsComponent class="content">
|
||||||
{{ selectedItem?.content || '' }}
|
<img v-if="selectedItem?.type === 'image'" :src="selectedItem.content" alt="Image" class="full-image">
|
||||||
|
<img v-else-if="isYoutubeWatchUrl(selectedItem?.content)" :src="getYoutubeThumbnail(selectedItem.content)" alt="YouTube Thumbnail" class="full-image">
|
||||||
|
<span v-else>{{ selectedItem?.content || '' }}</span>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
<Noise />
|
<Noise />
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +51,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
||||||
import Database from '@tauri-apps/plugin-sql';
|
import Database from '@tauri-apps/plugin-sql';
|
||||||
import { register, unregister, isRegistered } from '@tauri-apps/plugin-global-shortcut';
|
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
@ -64,6 +67,7 @@ const selectedGroupIndex = ref(0);
|
||||||
const selectedItemIndex = ref(0);
|
const selectedItemIndex = ref(0);
|
||||||
const resultsContainer = ref(null);
|
const resultsContainer = ref(null);
|
||||||
const selectedElement = ref(null);
|
const selectedElement = ref(null);
|
||||||
|
const searchInput = ref(null);
|
||||||
const os = platform();
|
const os = platform();
|
||||||
|
|
||||||
const groupedHistory = computed(() => {
|
const groupedHistory = computed(() => {
|
||||||
|
@ -172,6 +176,35 @@ const truncateContent = (content) => {
|
||||||
return content.length > maxChars ? content.slice(0, maxChars - 3) + '...' : content;
|
return content.length > maxChars ? content.slice(0, maxChars - 3) + '...' : content;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isUrl = (str) => {
|
||||||
|
const urlPattern = /^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
|
||||||
|
return urlPattern.test(str);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isYoutubeWatchUrl = (url) => {
|
||||||
|
return /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/watch\?v=[\w-]+/.test(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYoutubeThumbnail = (url) => {
|
||||||
|
const videoId = url.match(/[?&]v=([^&]+)/)[1];
|
||||||
|
return `https://img.youtube.com/vi/${videoId}/0.jpg`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFavicon = (url) => {
|
||||||
|
const domain = url.replace(/^(https?:\/\/)?(www\.)?/, '').split('/')[0];
|
||||||
|
return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshHistory = async () => {
|
||||||
|
const rawHistory = await db.value.select('SELECT * FROM history ORDER BY timestamp DESC');
|
||||||
|
history.value = rawHistory.map(item => {
|
||||||
|
if (item.type === 'image' && !item.content.startsWith('data:image')) {
|
||||||
|
return { ...item, content: `data:image/png;base64,${item.content}` };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
db.value = await Database.load('sqlite:data.db');
|
db.value = await Database.load('sqlite:data.db');
|
||||||
await refreshHistory();
|
await refreshHistory();
|
||||||
|
@ -181,12 +214,10 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await listen('tauri://blur', hideApp);
|
await listen('tauri://blur', hideApp);
|
||||||
|
await listen('tauri://focus', focusSearchInput);
|
||||||
|
focusSearchInput();
|
||||||
});
|
});
|
||||||
|
|
||||||
const refreshHistory = async () => {
|
|
||||||
history.value = await db.value.select('SELECT * FROM history ORDER BY timestamp DESC');
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideApp = async () => {
|
const hideApp = async () => {
|
||||||
await app.hide();
|
await app.hide();
|
||||||
await window.getCurrent().hide();
|
await window.getCurrent().hide();
|
||||||
|
@ -198,6 +229,13 @@ const showApp = async () => {
|
||||||
await window.getCurrent().show();
|
await window.getCurrent().show();
|
||||||
selectedGroupIndex.value = 0;
|
selectedGroupIndex.value = 0;
|
||||||
selectedItemIndex.value = 0;
|
selectedItemIndex.value = 0;
|
||||||
|
focusSearchInput();
|
||||||
|
};
|
||||||
|
|
||||||
|
const focusSearchInput = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
searchInput.value?.focus();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToSelectedItem = () => {
|
const scrollToSelectedItem = () => {
|
||||||
|
|
|
@ -52,6 +52,10 @@ body,
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.os-scrollbar-horizontal{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.bg {
|
.bg {
|
||||||
width: 750px;
|
width: 750px;
|
||||||
height: 474px;
|
height: 474px;
|
||||||
|
@ -120,15 +124,19 @@ body,
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.favicon{
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 53px;
|
top: 53px;
|
||||||
left: 284px;
|
left: 284px;
|
||||||
padding: 10px;
|
padding: 8px;
|
||||||
height: calc(100vh - 96px);
|
height: calc(100vh - 96px);
|
||||||
padding-inline: 14px;
|
|
||||||
font-family: SFMonoRegular !important;
|
font-family: SFMonoRegular !important;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
@ -140,6 +148,13 @@ body,
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
font-family: SFMonoRegular !important;
|
font-family: SFMonoRegular !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-image{
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue