CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 29s
Desktop (Tauri) / Tauri desktop (Linux) (push) Canceled after 2m36s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Canceled after 2m36s
Desktop (Tauri) / Update manifest (push) Canceled after 0s
A finger cannot hover, and on a note card the hover toolbar is the only way to pin, colour or archive — so on a phone those notes could not be acted on at all. Same for deleting a checklist item, a saved view, an attachment or a preview. Marked rather than rewritten inline: one `.hover-reveal` class on the five elements and a single rule that says what it is for. The `group-hover:` reveal stays in the markup because the trigger differs per component (named groups); only the fallback is shared. `@media (hover: none)` asks the device directly, which is more honest than inferring from viewport width — a narrow window on a laptop still hovers, and a large tablet still doesn't. It sits after the Tailwind directives so it beats the opacity-0/pointer-events-none utilities on source order without !important. Tap targets follow the same shape: p-1.5 around an 18px icon lands near 30px, which is fine for a cursor and too small for a thumb. Bumped to 44px on coarse pointers only, so desktop chrome doesn't inflate. The drag grip is deliberately NOT revealed yet. Reordering still uses HTML5 drag-and-drop, which never fires from touch, so showing the handle would only promise something that does nothing. It comes with the pointer-events rewrite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
47 lines
1.5 KiB
Vue
47 lines
1.5 KiB
Vue
<script setup lang="ts">
|
||
import type { LinkPreview } from "../stores/notes";
|
||
|
||
defineProps<{ preview: LinkPreview; removable?: boolean }>();
|
||
defineEmits<{ (e: "remove"): void }>();
|
||
</script>
|
||
|
||
<template>
|
||
<div class="group/lp relative overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700">
|
||
<a
|
||
:href="preview.url"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="flex items-stretch hover:bg-neutral-50 dark:hover:bg-neutral-800/60"
|
||
>
|
||
<img
|
||
v-if="preview.image_url"
|
||
:src="preview.image_url"
|
||
alt=""
|
||
loading="lazy"
|
||
decoding="async"
|
||
class="h-auto w-24 shrink-0 self-stretch object-cover"
|
||
/>
|
||
<div class="min-w-0 flex-1 px-3 py-2">
|
||
<p v-if="preview.site_name" class="truncate text-[11px] uppercase tracking-wide text-neutral-400">
|
||
{{ preview.site_name }}
|
||
</p>
|
||
<p class="truncate text-sm font-medium text-neutral-800 dark:text-neutral-100">
|
||
{{ preview.title || preview.url }}
|
||
</p>
|
||
<p v-if="preview.description" class="mt-0.5 line-clamp-2 text-xs text-neutral-500 dark:text-neutral-400">
|
||
{{ preview.description }}
|
||
</p>
|
||
</div>
|
||
</a>
|
||
<button
|
||
v-if="removable"
|
||
type="button"
|
||
class="hover-reveal absolute right-1 top-1 rounded-full bg-black/50 px-1.5 text-white opacity-0 transition group-hover/lp:opacity-100"
|
||
aria-label="Remove preview"
|
||
@click="$emit('remove')"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
</template>
|