Make keep-prompt snippets clickable and allow renaming the start still.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,40 +2,47 @@
|
||||
<div ref="root" class="relative shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-7 w-7 items-center justify-center rounded-full border border-white/15 text-lg leading-none text-amber-200 hover:border-amber-300/50 hover:text-amber-100 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
class="rounded-full border px-3 py-1 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-40"
|
||||
:class="open || items.length
|
||||
? 'border-amber-300/40 text-amber-100 hover:bg-amber-400/10'
|
||||
: 'border-white/15 text-zinc-400 hover:border-amber-300/40 hover:text-amber-100'"
|
||||
:disabled="disabled"
|
||||
:title="items.length ? 'Add a saved prompt' : 'Add snippets in Settings'"
|
||||
:title="items.length ? 'Add a saved snippet to the prompt' : 'Add snippets in Settings → Keep prompts'"
|
||||
:aria-expanded="open"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Add saved prompt"
|
||||
@click.stop="open = !open"
|
||||
aria-label="Add saved prompt snippet"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
+
|
||||
Snippets
|
||||
</button>
|
||||
<div
|
||||
v-if="open"
|
||||
class="absolute top-full z-30 mt-1 w-72 max-w-[calc(100vw-2rem)] overflow-hidden rounded-2xl border border-white/10 bg-zinc-950 shadow-2xl"
|
||||
:class="align === 'left' ? 'left-0' : 'right-0'"
|
||||
role="menu"
|
||||
>
|
||||
<p class="px-3 pt-2 text-[10px] uppercase tracking-wide text-zinc-500">{{ heading }}</p>
|
||||
<p v-if="!items.length" class="px-3 py-3 text-xs text-zinc-500">
|
||||
No saved snippets yet. Add video and image lists in Settings.
|
||||
</p>
|
||||
<ul v-else class="max-h-64 overflow-y-auto p-1">
|
||||
<li v-for="item in items" :key="item.id">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full flex-col items-start rounded-xl px-3 py-2 text-left hover:bg-white/5"
|
||||
role="menuitem"
|
||||
@click="pick(item)"
|
||||
>
|
||||
<span class="text-xs font-medium text-amber-100">{{ item.label }}</span>
|
||||
<span class="mt-0.5 line-clamp-2 whitespace-pre-wrap text-[11px] text-zinc-500">{{ item.text }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="open"
|
||||
ref="menu"
|
||||
class="fixed z-[80] w-72 max-w-[calc(100vw-1rem)] overflow-hidden rounded-2xl border border-white/10 bg-zinc-950 shadow-2xl"
|
||||
:style="menuStyle"
|
||||
role="menu"
|
||||
@click.stop
|
||||
>
|
||||
<p class="px-3 pt-2 text-[10px] uppercase tracking-wide text-zinc-500">{{ heading }}</p>
|
||||
<p v-if="!items.length" class="px-3 py-3 text-xs text-zinc-500">
|
||||
No snippets in this list yet. Add them in Settings → Keep prompts.
|
||||
</p>
|
||||
<ul v-else class="max-h-64 overflow-y-auto p-1">
|
||||
<li v-for="item in items" :key="item.id">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full flex-col items-start rounded-xl px-3 py-2 text-left hover:bg-white/5"
|
||||
role="menuitem"
|
||||
@click="pick(item)"
|
||||
>
|
||||
<span class="text-xs font-medium text-amber-100">{{ item.label }}</span>
|
||||
<span class="mt-0.5 line-clamp-2 whitespace-pre-wrap text-[11px] text-zinc-500">{{ item.text }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -50,7 +57,7 @@ const props = withDefaults(defineProps<{
|
||||
}>(), {
|
||||
heading: 'Saved prompts',
|
||||
disabled: false,
|
||||
align: 'right'
|
||||
align: 'left'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -58,7 +65,27 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const root = ref<HTMLElement | null>(null)
|
||||
const menu = ref<HTMLElement | null>(null)
|
||||
const open = ref(false)
|
||||
const menuStyle = ref<Record<string, string>>({})
|
||||
|
||||
function place() {
|
||||
const el = root.value
|
||||
if (!el) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
const width = Math.min(288, window.innerWidth - 16)
|
||||
let left = props.align === 'right' ? rect.right - width : rect.left
|
||||
left = Math.min(Math.max(8, left), window.innerWidth - width - 8)
|
||||
menuStyle.value = {
|
||||
top: `${Math.round(rect.bottom + 4)}px`,
|
||||
left: `${Math.round(left)}px`,
|
||||
width: `${width}px`
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
open.value = !open.value
|
||||
}
|
||||
|
||||
function pick(item: KeepPrompt) {
|
||||
emit('insert', item)
|
||||
@@ -68,6 +95,7 @@ function pick(item: KeepPrompt) {
|
||||
function onDocClick(event: MouseEvent) {
|
||||
const target = event.target as Node | null
|
||||
if (root.value && target && root.value.contains(target)) return
|
||||
if (menu.value && target && menu.value.contains(target)) return
|
||||
open.value = false
|
||||
}
|
||||
|
||||
@@ -79,13 +107,29 @@ watch(() => props.disabled, (value) => {
|
||||
if (value) open.value = false
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', onDocClick)
|
||||
window.addEventListener('keydown', onKey)
|
||||
watch(open, (value) => {
|
||||
if (typeof window === 'undefined') return
|
||||
if (value) {
|
||||
nextTick(() => {
|
||||
place()
|
||||
window.addEventListener('click', onDocClick)
|
||||
window.addEventListener('keydown', onKey)
|
||||
window.addEventListener('resize', place)
|
||||
window.addEventListener('scroll', place, true)
|
||||
})
|
||||
return
|
||||
}
|
||||
window.removeEventListener('click', onDocClick)
|
||||
window.removeEventListener('keydown', onKey)
|
||||
window.removeEventListener('resize', place)
|
||||
window.removeEventListener('scroll', place, true)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
window.removeEventListener('click', onDocClick)
|
||||
window.removeEventListener('keydown', onKey)
|
||||
window.removeEventListener('resize', place)
|
||||
window.removeEventListener('scroll', place, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -34,6 +34,18 @@
|
||||
v-else
|
||||
class="min-h-0 flex-1 overflow-y-auto whitespace-pre-wrap rounded-2xl border border-white/10 bg-zinc-900 px-4 py-3 text-base leading-relaxed text-zinc-200 [overflow-wrap:anywhere]"
|
||||
>{{ text }}</div>
|
||||
<div v-if="editable && keepItems.length" class="mt-3 flex flex-wrap gap-1.5">
|
||||
<button
|
||||
v-for="item in keepItems"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="max-w-full truncate rounded-full border border-white/10 px-2.5 py-1 text-xs text-zinc-300 hover:border-amber-300/40 hover:text-amber-100"
|
||||
:title="item.text"
|
||||
@click="onKeepInsert(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user