diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f79b85d..6a11091 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -234,30 +234,41 @@ jobs: with: fetch-depth: 0 + - name: Check if release already exists + id: check_release + run: | + VERSION="${{ needs.prepare.outputs.version }}" + RELEASE_EXISTS=$(gh release view v$VERSION --json id --jq '.id' 2>/dev/null || echo "") + if [ -n "$RELEASE_EXISTS" ]; then + echo "Release v$VERSION already exists. Skipping release creation." + echo "SKIP_RELEASE=true" >> $GITHUB_ENV + else + echo "Release v$VERSION does not exist. Proceeding with release creation." + echo "SKIP_RELEASE=false" >> $GITHUB_ENV + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Download all artifacts + if: env.SKIP_RELEASE == 'false' uses: actions/download-artifact@v4 with: path: artifacts + - name: Update CHANGELOG + if: env.SKIP_RELEASE == 'false' + id: changelog + uses: requarks/changelog-action@v1 + with: + token: ${{ github.token }} + tag: ${{ github.ref_name }} + - name: Generate Release Body + if: env.SKIP_RELEASE == 'false' id: release_body run: | VERSION="${{ needs.prepare.outputs.version }}" - - # Get the most recent release tag (v* tags only) - LAST_TAG=$(git describe --match "v*" --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1` 2>/dev/null || echo "") - - if [ -n "$LAST_TAG" ]; then - echo "Debug: Found last release tag: $LAST_TAG" - CHANGES=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s") - else - echo "Debug: No previous release tag found, using first commit" - CHANGES=$(git log --pretty=format:"- %s") - fi - - echo "Debug: Changelog content:" - echo "$CHANGES" - + # Calculate hashes with corrected paths WINDOWS_ARM_HASH=$(sha256sum "artifacts/windows-arm64-binaries/Qopy-${VERSION}_arm64.msi" | awk '{ print $1 }') WINDOWS_64_HASH=$(sha256sum "artifacts/windows-x64-binaries/Qopy-${VERSION}_x64.msi" | awk '{ print $1 }') @@ -278,10 +289,9 @@ jobs: echo "Red Hat: $REDHAT_HASH" RELEASE_BODY=$(cat <<-EOF - ## ♻️ Changelog - - $CHANGES - + + ${{ needs.create-release.outputs.changelog }} + ## ⬇️ Downloads - [Windows (x64)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_x64.msi) - ${WINDOWS_64_HASH} @@ -299,6 +309,7 @@ jobs: echo "EOF" >> $GITHUB_ENV - name: Create Release + if: env.SKIP_RELEASE == 'false' uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/GET_STARTED.md b/GET_STARTED.md index 548a5e9..a793041 100644 --- a/GET_STARTED.md +++ b/GET_STARTED.md @@ -1,6 +1,6 @@ # Get Started -The default hotkey for Qopy is Windows+V which is also the hotkey for the default clipboard manager to turn that off follow [this guide](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md#disable-windowsv-for-default-clipboard-manager). +The default hotkey for Qopy is Windows+V which is also the hotkey for the default clipboard manager to turn that off follow [this guide](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md#disable-windowsv-for-default-clipboard-manager). All the data of Qopy is stored inside of a SQLite database. @@ -12,7 +12,7 @@ All the data of Qopy is stored inside of a SQLite database. ## Disable Windows+V for default clipboard manager -https://github.com/user-attachments/assets/723f9e07-3190-46ec-9bb7-15dfc112f620 + To disable the default clipboard manager popup from windows open Command prompt and run this command diff --git a/app.vue b/app.vue index 2d69efc..a60f802 100644 --- a/app.vue +++ b/app.vue @@ -1,27 +1,36 @@ \ No newline at end of file + diff --git a/assets/css/index.scss b/assets/css/index.scss index 2fb355a..5feb44b 100644 --- a/assets/css/index.scss +++ b/assets/css/index.scss @@ -22,7 +22,7 @@ $mutedtext: #78756f; position: fixed; top: 0; left: 0; - height: 54px; + height: 56px; background-color: transparent; outline: none; border: none; @@ -35,10 +35,10 @@ $mutedtext: #78756f; .results { position: absolute; - width: 284px; - top: 53px; + width: 286px; + top: 55px; left: 0; - height: calc(100vh - 95px); + height: 417px; border-right: 1px solid $divider; display: flex; flex-direction: column; @@ -46,6 +46,7 @@ $mutedtext: #78756f; padding-bottom: 8px; overflow-y: auto; overflow-x: hidden; + z-index: 3; .result { height: 40px; @@ -59,6 +60,7 @@ $mutedtext: #78756f; overflow: hidden; text-overflow: clip; white-space: nowrap; + color: $text; } .result { @@ -96,20 +98,22 @@ $mutedtext: #78756f; .content { position: absolute; - top: 53px; - left: 284px; - height: calc(100vh - 254px); + top: 55px; + left: 285px; + height: 220px; font-family: CommitMono !important; font-size: 12px; letter-spacing: 1; border-radius: 10px; - width: calc(100vw - 286px); + width: 465px; 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)) { padding: 8px; @@ -128,7 +132,7 @@ $mutedtext: #78756f; } .bottom-bar { - height: 40px; + height: 39px; width: calc(100vw - 2px); backdrop-filter: blur(18px); background-color: hsla(40, 3%, 16%, 0.8); @@ -215,18 +219,20 @@ $mutedtext: #78756f; display: flex; flex-direction: column; gap: 14px; - bottom: 40px; - left: 284px; + bottom: 39px; + left: 285px; height: 160px; - width: calc(100vw - 286px); + width: 465px; border-top: 1px solid $divider; background-color: $primary; padding: 14px; + z-index: 1; .title { font-family: SFRoundedSemiBold; font-size: 12px; letter-spacing: 0.6px; + color: $text; } .info-content { diff --git a/assets/css/settings.scss b/assets/css/settings.scss index c593334..7f2eed0 100644 --- a/assets/css/settings.scss +++ b/assets/css/settings.scss @@ -36,42 +36,118 @@ $mutedtext: #78756f; } } -.keybind-container { +p { + font-family: SFRoundedMedium; +} + +.settings-container { + width: 100%; + margin-top: 26px; + position: relative; + font-size: 12px; + font-family: SFRoundedMedium; + + .settings { + position: absolute; + left: 50%; + transform: translateX(-50%); + margin-left: -26px; + display: flex; + gap: 24px; + + .names { + display: flex; + flex-direction: column; + gap: 16px; + + p { + font-family: SFRoundedSemiBold; + color: $text2; + display: flex; + justify-content: right; + } + } + + .actions { + display: flex; + flex-direction: column; + gap: 16px; + color: $mutedtext; + } + } +} + +.launch { display: flex; - flex-direction: column; align-items: center; - justify-content: center; - height: 100vh; gap: 6px; - .title { - font-size: 20px; - font-weight: 800; + input[type="checkbox"] { + appearance: none; + width: 14px; + height: 14px; + background-color: transparent; + border-radius: 5px; + border: 1px solid $mutedtext; + position: relative; + cursor: pointer; + transition: background-color 0.2s; + + &:checked { + ~ .checkmark { + opacity: 1; + } + } } - .keybind-input { - padding: 6px; - border: 1px solid $divider; - color: $text2; - display: flex; - border-radius: 13px; - outline: none; - gap: 6px; + .checkmark { + height: 14px; + width: 14px; + position: absolute; + opacity: 0; + transition: opacity 0.2s; + } - .key { - color: $text2; - font-family: SFRoundedMedium; - background-color: $divider; - padding: 6px 8px; - border-radius: 8px; - } + p { + color: $text2; } +} - .keybind-input:focus { - border: 1px solid rgba(255, 255, 255, 0.2); +.keybind-input { + width: min-content; + white-space: nowrap; + padding: 6px; + border: 1px solid $divider; + color: $text2; + display: flex; + border-radius: 10px; + outline: none; + gap: 4px; + + .key { + color: $text2; + font-family: SFRoundedMedium; + background-color: $divider; + padding: 2px 6px; + border-radius: 6px; + font-size: 14px; } } +.keybind-input:focus { + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.empty-keybind { + border-color: rgba(255, 82, 82, 0.298); +} + +.top-bar { + width: 100%; + height: 56px; + border-bottom: 1px solid $divider; +} + .bottom-bar { height: 40px; width: calc(100vw - 2px); @@ -136,6 +212,15 @@ $mutedtext: #78756f; background-color: transparent; transition: all 0.2s; cursor: pointer; + + p { + color: $text; + } + + &.disabled { + pointer-events: none; + opacity: 0.5; + } } .actions:hover { diff --git a/package.json b/package.json index aad552e..de5e05e 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,12 @@ "sass-embedded": "1.83.0", "uuid": "11.0.3", "vue": "3.5.13", - "wrdu-keyboard": "1.1.1" + "wrdu-keyboard": "3.0.0" }, "overrides": { "chokidar": "^3.6.0" + }, + "patchedDependencies": { + "wrdu-keyboard@3.0.0": "patches/wrdu-keyboard@3.0.0.patch" } -} \ No newline at end of file +} diff --git a/pages/index.vue b/pages/index.vue index c902e9b..8f1ad50 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -65,10 +65,17 @@ YouTube Thumbnail -
- Image +
+ Image
{{ selectedItem?.content || "" }} @@ -135,9 +146,7 @@

{{ row.label }}

- + {{ row.value }}
@@ -153,12 +162,19 @@ import { OverlayScrollbarsComponent } from "overlayscrollbars-vue"; import "overlayscrollbars/overlayscrollbars.css"; import { app, window } from "@tauri-apps/api"; import { platform } from "@tauri-apps/plugin-os"; -import { enable, isEnabled } from "@tauri-apps/plugin-autostart"; import { listen } from "@tauri-apps/api/event"; import { useNuxtApp } from "#app"; import { invoke } from "@tauri-apps/api/core"; import { HistoryItem, ContentType } from "~/types/types"; -import type { InfoText, InfoImage, InfoFile, InfoLink, InfoColor, InfoCode } from "~/types/types"; +import type { + InfoText, + InfoImage, + InfoFile, + InfoLink, + InfoColor, + InfoCode, +} from "~/types/types"; +import { Key } from "wrdu-keyboard/key"; interface GroupedHistory { label: string; @@ -188,8 +204,8 @@ const imageSizes = shallowRef>({}); const lastUpdateTime = ref(Date.now()); const imageLoadError = ref(false); const imageLoading = ref(false); -const pageTitle = ref(''); -const pageOgImage = ref(''); +const pageTitle = ref(""); +const pageOgImage = ref(""); const keyboard = useKeyboard(); @@ -583,41 +599,35 @@ const setupEventListeners = async (): Promise => { searchInput.value?.blur(); }); - keyboard.down("ArrowDown", (event) => { - event.preventDefault(); + keyboard.prevent.down([Key.DownArrow], (event) => { selectNext(); }); - keyboard.down("ArrowUp", (event) => { - event.preventDefault(); + keyboard.prevent.down([Key.UpArrow], (event) => { selectPrevious(); }); - keyboard.down("Enter", (event) => { - event.preventDefault(); + keyboard.prevent.down([Key.Enter], (event) => { pasteSelectedItem(); }); - keyboard.down("Escape", (event) => { - event.preventDefault(); + keyboard.prevent.down([Key.Escape], (event) => { hideApp(); }); - keyboard.down("all", (event) => { - const isMacActionCombo = - os.value === "macos" && - (event.code === "MetaLeft" || event.code === "MetaRight") && - event.key === "k"; + switch (os.value) { + case "macos": + keyboard.prevent.down([Key.LeftMeta, Key.K], (event) => {}); - const isOtherOsActionCombo = - os.value !== "macos" && - (event.code === "ControlLeft" || event.code === "ControlRight") && - event.key === "k"; + keyboard.prevent.down([Key.RightMeta, Key.K], (event) => {}); + break; - if (isMacActionCombo || isOtherOsActionCombo) { - event.preventDefault(); - } - }); + case "linux" || "windows": + keyboard.prevent.down([Key.LeftControl, Key.K], (event) => {}); + + keyboard.prevent.down([Key.RightControl, Key.K], (event) => {}); + break; + } }; const hideApp = async (): Promise => { @@ -646,7 +656,7 @@ watch(searchQuery, () => { onMounted(async () => { try { - os.value = await platform(); + os.value = platform(); await loadHistoryChunk(); resultsContainer.value @@ -655,10 +665,6 @@ onMounted(async () => { ?.viewport?.addEventListener("scroll", handleScroll); await setupEventListeners(); - - if (!(await isEnabled())) { - await enable(); - } } catch (error) { console.error("Error during onMounted:", error); } @@ -686,27 +692,33 @@ const formatFileSize = (bytes: number): string => { const fetchPageMeta = async (url: string) => { try { - const [title, ogImage] = await invoke('fetch_page_meta', { url }) as [string, string | null]; + const [title, ogImage] = (await invoke("fetch_page_meta", { url })) as [ + string, + string | null + ]; pageTitle.value = title; if (ogImage) { pageOgImage.value = ogImage; } } catch (error) { - console.error('Error fetching page meta:', error); - pageTitle.value = 'Error loading title'; + console.error("Error fetching page meta:", error); + pageTitle.value = "Error loading title"; } }; -watch(() => selectedItem.value, (newItem) => { - if (newItem?.content_type === ContentType.Link) { - pageTitle.value = 'Loading...'; - pageOgImage.value = ''; - fetchPageMeta(newItem.content); - } else { - pageTitle.value = ''; - pageOgImage.value = ''; +watch( + () => selectedItem.value, + (newItem) => { + if (newItem?.content_type === ContentType.Link) { + pageTitle.value = "Loading..."; + pageOgImage.value = ""; + fetchPageMeta(newItem.content); + } else { + pageTitle.value = ""; + pageOgImage.value = ""; + } } -}); +); const getInfo = computed(() => { if (!selectedItem.value) return null; @@ -716,7 +728,10 @@ const getInfo = computed(() => { copied: selectedItem.value.timestamp, }; - const infoMap: Record InfoText | InfoImage | InfoFile | InfoLink | InfoColor | InfoCode> = { + const infoMap: Record< + ContentType, + () => InfoText | InfoImage | InfoFile | InfoLink | InfoColor | InfoCode + > = { [ContentType.Text]: () => ({ ...baseInfo, content_type: ContentType.Text, @@ -747,20 +762,21 @@ const getInfo = computed(() => { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); - + const rNorm = r / 255; const gNorm = g / 255; const bNorm = b / 255; - + const max = Math.max(rNorm, gNorm, bNorm); const min = Math.min(rNorm, gNorm, bNorm); - let h = 0, s = 0; + let h = 0, + s = 0; const l = (max + min) / 2; if (max !== min) { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - + switch (max) { case rNorm: h = (gNorm - bNorm) / d + (gNorm < bNorm ? 6 : 0); @@ -780,14 +796,16 @@ const getInfo = computed(() => { content_type: ContentType.Color, hex: hex, rgb: `rgb(${r}, ${g}, ${b})`, - hsl: `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`, + hsl: `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round( + l * 100 + )}%)`, }; }, [ContentType.Code]: () => ({ ...baseInfo, content_type: ContentType.Code, language: selectedItem.value!.language ?? "Unknown", - lines: selectedItem.value!.content.split('\n').length, + lines: selectedItem.value!.content.split("\n").length, }), }; @@ -799,24 +817,37 @@ const infoRows = computed(() => { const commonRows = [ { label: "Source", value: getInfo.value.source, isUrl: false }, - { label: "Content Type", value: getInfo.value.content_type.charAt(0).toUpperCase() + getInfo.value.content_type.slice(1), isUrl: false }, + { + label: "Content Type", + value: + getInfo.value.content_type.charAt(0).toUpperCase() + + getInfo.value.content_type.slice(1), + isUrl: false, + }, ]; - const typeSpecificRows: Record> = { + const typeSpecificRows: Record< + ContentType, + Array<{ label: string; value: string | number; isUrl?: boolean }> + > = { [ContentType.Text]: [ { label: "Characters", value: (getInfo.value as InfoText).characters }, { label: "Words", value: (getInfo.value as InfoText).words }, ], [ContentType.Image]: [ { label: "Dimensions", value: (getInfo.value as InfoImage).dimensions }, - { label: "Image size", value: formatFileSize((getInfo.value as InfoImage).size) }, + { + label: "Image size", + value: formatFileSize((getInfo.value as InfoImage).size), + }, ], [ContentType.File]: [ { label: "Path", value: (getInfo.value as InfoFile).path }, ], [ContentType.Link]: [ - ...((getInfo.value as InfoLink).title && (getInfo.value as InfoLink).title !== 'Loading...' - ? [{ label: "Title", value: (getInfo.value as InfoLink).title || '' }] + ...((getInfo.value as InfoLink).title && + (getInfo.value as InfoLink).title !== "Loading..." + ? [{ label: "Title", value: (getInfo.value as InfoLink).title || "" }] : []), { label: "URL", value: (getInfo.value as InfoLink).url, isUrl: true }, { label: "Characters", value: (getInfo.value as InfoLink).characters }, @@ -832,8 +863,9 @@ const infoRows = computed(() => { ], }; - const specificRows = typeSpecificRows[getInfo.value.content_type] - .filter(row => row.value !== ""); + const specificRows = typeSpecificRows[getInfo.value.content_type].filter( + (row) => row.value !== "" + ); return [ ...commonRows, diff --git a/pages/settings.vue b/pages/settings.vue index 6fd6417..d4814e0 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -1,8 +1,10 @@