diff --git a/package-lock.json b/package-lock.json
index 112806b..e5bca0a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "aitherboard",
- "version": "0.3.2",
+ "version": "0.3.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aitherboard",
- "version": "0.3.2",
+ "version": "0.3.3",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.20.0",
diff --git a/package.json b/package.json
index d4b1c54..25f7525 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "aitherboard",
- "version": "0.3.2",
+ "version": "0.3.3",
"type": "module",
"author": "silberengel@gitcitadel.com",
"description": "A decentralized messageboard built on the Nostr protocol.",
diff --git a/src/lib/components/EventMenu.svelte b/src/lib/components/EventMenu.svelte
index 8e82ab0..79b0822 100644
--- a/src/lib/components/EventMenu.svelte
+++ b/src/lib/components/EventMenu.svelte
@@ -27,6 +27,7 @@
import { goto } from '$app/navigation';
import Icon from './ui/Icon.svelte';
import { getEventLink } from '../services/event-links.js';
+ import { isGraspUrl } from '../services/content/git-repo-fetcher.js';
interface Props {
event: NostrEvent;
@@ -276,13 +277,111 @@
goto(getEventLink(event));
}
- function cloneEvent() {
+ async function editGraspList() {
+ const currentPubkey = sessionManager.getCurrentPubkey();
+ if (!currentPubkey) {
+ closeMenu();
+ return;
+ }
+
+ try {
+ // Fetch user's grasp list (kind 10317)
+ const graspListEvents = await nostrClient.fetchEvents(
+ [{ kinds: [10317], authors: [currentPubkey], limit: 1 }],
+ relayManager.getProfileReadRelays(),
+ { useCache: 'cache-first', cacheResults: true }
+ );
+
+ let graspListData;
+ if (graspListEvents.length > 0) {
+ // Edit existing grasp list
+ const existingEvent = graspListEvents[0];
+ graspListData = {
+ kind: 10317,
+ content: existingEvent.content || '',
+ tags: existingEvent.tags || [],
+ isClone: false
+ };
+ } else {
+ // Create new grasp list with defaults
+ const { config } = await import('../services/nostr/config.js');
+ const graspRelaysForTags = config.graspRelays.filter(r => !r.includes('thecitadel'));
+ graspListData = {
+ kind: 10317,
+ content: '',
+ tags: graspRelaysForTags.map(server => ['g', server]),
+ isClone: false
+ };
+ }
+
+ sessionStorage.setItem('aitherboard_cloneEvent', JSON.stringify(graspListData));
+ closeMenu();
+ goto('/write');
+ } catch (error) {
+ console.error('Failed to load grasp list:', error);
+ closeMenu();
+ }
+ }
+
+ async function cloneEvent() {
+ let tags = event.tags || [];
+
+ // For repo announcements, add GRASP clone if missing
+ if (event.kind === 30617 || event.kind === KIND.REPO_ANNOUNCEMENT) {
+ const hasGraspClone = tags.some(t =>
+ t[0] === 'clone' && t[1] && isGraspUrl(t[1])
+ );
+
+ if (!hasGraspClone) {
+ // Get user's grasp server preference (kind 10317) or use default
+ const currentPubkey = sessionManager.getCurrentPubkey();
+ if (currentPubkey) {
+ const dTag = tags.find(t => t[0] === 'd')?.[1] || '';
+ if (dTag) {
+ try {
+ const npub = nip19.npubEncode(currentPubkey);
+
+ // Try to fetch user's grasp server preference (kind 10317)
+ let graspServer = '';
+ try {
+ const graspListEvents = await nostrClient.fetchEvents(
+ [{ kinds: [10317], authors: [currentPubkey], limit: 1 }],
+ relayManager.getProfileReadRelays(),
+ { useCache: 'cache-first', cacheResults: true }
+ );
+ if (graspListEvents.length > 0) {
+ const gTag = graspListEvents[0].tags.find(t => t[0] === 'g');
+ if (gTag && gTag[1]) {
+ graspServer = gTag[1]; // Use first grasp server
+ }
+ }
+ } catch (error) {
+ console.warn('Failed to fetch grasp server preference:', error);
+ }
+
+ // Fallback to default if no preference found
+ if (!graspServer) {
+ graspServer = 'wss://relay.ngit.dev'; // Default
+ }
+
+ // Convert ws:// to https:// if needed
+ const httpsGraspServer = graspServer.replace(/^wss?:\/\//, 'https://').replace(/\/$/, '');
+ const graspUrl = `${httpsGraspServer}/${npub}/${dTag}.git`;
+ tags = [...tags, ['clone', graspUrl]];
+ } catch (error) {
+ console.warn('Failed to create GRASP clone URL:', error);
+ }
+ }
+ }
+ }
+ }
+
// Store event data in sessionStorage for the write page to pick up
// Ensure content is preserved (important for kind 0 which has stringified JSON)
const cloneData = {
kind: event.kind,
content: event.content || '', // Explicitly preserve content, even if empty string
- tags: event.tags || [],
+ tags: tags,
isClone: true
};
sessionStorage.setItem('aitherboard_cloneEvent', JSON.stringify(cloneData));
@@ -300,7 +399,7 @@
let allRelays = relayManager.getAllAvailableRelays();
// Add thread publish relays (includes thread-specific relays)
- allRelays = [...allRelays, ...relayManager.getThreadPublishRelays()];
+ allRelays = [...allRelays, ...relayManager.getdocumentationPublishRelays()];
// Add file metadata publish relays (includes GIF relays)
allRelays = [...allRelays, ...relayManager.getFileMetadataPublishRelays()];
@@ -509,6 +608,12 @@
Edit/Clone this event
+ {#if (event.kind === 30617 || event.kind === KIND.REPO_ANNOUNCEMENT) && isLoggedIn}
+
+ {/if}