129 lines
4.0 KiB
Vue
129 lines
4.0 KiB
Vue
<template>
|
|
<div v-if="showCompose" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
|
<div class="w-full max-w-md mx-4 rounded-2xl bg-[#0a0a0a] border border-white/10 p-5 space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-bold text-white/90">Share to Nostr</h3>
|
|
<button
|
|
class="p-2 min-w-[44px] min-h-[44px] flex items-center justify-center rounded-lg text-white/40 hover:text-white/70 hover:bg-white/5 transition-colors"
|
|
@click="close"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<textarea
|
|
v-model="noteContent"
|
|
class="w-full h-32 px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors resize-none"
|
|
placeholder="Add a note about this content..."
|
|
/>
|
|
|
|
<div class="rounded-lg bg-white/[0.03] border border-white/5 p-3">
|
|
<p class="text-xs text-white/25 mb-1">Preview</p>
|
|
<p class="text-xs text-white/60 whitespace-pre-wrap">{{ previewText }}</p>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<button
|
|
class="flex-1 min-h-[44px] rounded-lg text-sm font-medium text-white/40 hover:text-white/70 hover:bg-white/5 transition-colors"
|
|
@click="close"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
class="flex-1 min-h-[44px] rounded-lg text-sm font-medium bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
|
|
:disabled="isPublishing || !isLoggedIn"
|
|
@click="publish"
|
|
>
|
|
{{ isPublishing ? 'Publishing...' : 'Publish' }}
|
|
</button>
|
|
</div>
|
|
|
|
<p v-if="!isLoggedIn" class="text-xs text-yellow-400/60 text-center">
|
|
Sign in with Nostr to share
|
|
</p>
|
|
|
|
<p v-if="publishResult" class="text-xs text-center" :class="publishOk ? 'text-emerald-400/60' : 'text-red-400/60'">
|
|
{{ publishResult }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import { useNostrIdentity } from '@/composables/useNostrIdentity'
|
|
import { useNostr } from '@/composables/useNostr'
|
|
|
|
const props = defineProps<{
|
|
title: string
|
|
type: string
|
|
description?: string
|
|
url?: string
|
|
}>()
|
|
|
|
const emit = defineEmits<{ close: [] }>()
|
|
|
|
const { isLoggedIn, signEvent } = useNostrIdentity()
|
|
const { publishEvent } = useNostr()
|
|
|
|
const showCompose = ref(true)
|
|
const noteContent = ref('')
|
|
const isPublishing = ref(false)
|
|
const publishResult = ref('')
|
|
const publishOk = ref(false)
|
|
|
|
const previewText = computed(() => {
|
|
const parts: string[] = []
|
|
if (noteContent.value.trim()) {
|
|
parts.push(noteContent.value.trim())
|
|
parts.push('')
|
|
}
|
|
parts.push(`${props.type.charAt(0).toUpperCase() + props.type.slice(1)}: ${props.title}`)
|
|
if (props.description) parts.push(props.description)
|
|
if (props.url) parts.push(props.url)
|
|
return parts.join('\n')
|
|
})
|
|
|
|
function close() {
|
|
showCompose.value = false
|
|
emit('close')
|
|
}
|
|
|
|
async function publish() {
|
|
isPublishing.value = true
|
|
publishResult.value = ''
|
|
|
|
const content = previewText.value
|
|
|
|
const unsigned = {
|
|
kind: 1,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags: [] as string[][],
|
|
content,
|
|
}
|
|
|
|
const signed = await signEvent(unsigned)
|
|
if (!signed) {
|
|
isPublishing.value = false
|
|
publishResult.value = 'Signing failed'
|
|
publishOk.value = false
|
|
return
|
|
}
|
|
|
|
const results = await publishEvent(signed)
|
|
const successes = results.filter(r => r.success).length
|
|
|
|
isPublishing.value = false
|
|
if (successes > 0) {
|
|
publishResult.value = `Published to ${successes}/${results.length} relays`
|
|
publishOk.value = true
|
|
setTimeout(close, 1500)
|
|
} else {
|
|
publishResult.value = 'Failed to publish'
|
|
publishOk.value = false
|
|
}
|
|
}
|
|
</script>
|