mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 23:47:12 +02:00
fix: slash autocomplete only at input start + add bookmark commands
- Fix slash command autocomplete to only trigger when / is at the beginning of input text (position 1 in TipTap), not in the middle of messages - Add /bookmark command to add NIP-29 group to user's kind 10009 list - Add /unbookmark command to remove group from user's kind 10009 list
This commit is contained in:
@@ -345,6 +345,7 @@ export const MentionEditor = forwardRef<
|
||||
);
|
||||
|
||||
// Create slash command suggestion configuration for / commands
|
||||
// Only triggers when / is at the very beginning of the input
|
||||
const slashCommandSuggestion: Omit<SuggestionOptions, "editor"> | null =
|
||||
useMemo(
|
||||
() =>
|
||||
@@ -352,6 +353,8 @@ export const MentionEditor = forwardRef<
|
||||
? {
|
||||
char: "/",
|
||||
allowSpaces: false,
|
||||
// Only allow slash commands at the start of input (position 1 in TipTap = first char)
|
||||
allow: ({ range }) => range.from === 1,
|
||||
items: async ({ query }) => {
|
||||
return await searchCommands(query);
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { NostrEvent } from "@/types/nostr";
|
||||
import type { ChatAction, GetActionsOptions } from "@/types/chat-actions";
|
||||
import eventStore from "@/services/event-store";
|
||||
import pool from "@/services/relay-pool";
|
||||
import { publishEventToRelays } from "@/services/hub";
|
||||
import { publishEventToRelays, publishEvent } from "@/services/hub";
|
||||
import accountManager from "@/services/accounts";
|
||||
import { getTagValues } from "@/lib/nostr-utils";
|
||||
import { EventFactory } from "applesauce-core/event-factory";
|
||||
@@ -496,6 +496,8 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
* Filters actions based on user's membership status:
|
||||
* - /join: only shown when user is NOT a member/admin
|
||||
* - /leave: only shown when user IS a member
|
||||
* - /bookmark: only shown when group is NOT in user's kind 10009 list
|
||||
* - /unbookmark: only shown when group IS in user's kind 10009 list
|
||||
*/
|
||||
getActions(options?: GetActionsOptions): ChatAction[] {
|
||||
const actions: ChatAction[] = [];
|
||||
@@ -563,6 +565,55 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
// Add bookmark/unbookmark actions
|
||||
// These are always available - the handler checks current state
|
||||
actions.push({
|
||||
name: "bookmark",
|
||||
description: "Add group to your group list",
|
||||
handler: async (context) => {
|
||||
try {
|
||||
await this.bookmarkGroup(context.conversation, context.activePubkey);
|
||||
return {
|
||||
success: true,
|
||||
message: "Group added to your list",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to bookmark group",
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
actions.push({
|
||||
name: "unbookmark",
|
||||
description: "Remove group from your group list",
|
||||
handler: async (context) => {
|
||||
try {
|
||||
await this.unbookmarkGroup(
|
||||
context.conversation,
|
||||
context.activePubkey,
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
message: "Group removed from your list",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to unbookmark group",
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
@@ -612,6 +663,54 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bookmark",
|
||||
description: "Add group to your group list",
|
||||
handler: async (context) => {
|
||||
try {
|
||||
await this.bookmarkGroup(
|
||||
context.conversation,
|
||||
context.activePubkey,
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
message: "Group added to your list",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to bookmark group",
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unbookmark",
|
||||
description: "Remove group from your group list",
|
||||
handler: async (context) => {
|
||||
try {
|
||||
await this.unbookmarkGroup(
|
||||
context.conversation,
|
||||
context.activePubkey,
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
message: "Group removed from your list",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to unbookmark group",
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -755,6 +854,118 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
await publishEventToRelays(event, [relayUrl]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a group to the user's group list (kind 10009)
|
||||
*/
|
||||
async bookmarkGroup(
|
||||
conversation: Conversation,
|
||||
activePubkey: string,
|
||||
): Promise<void> {
|
||||
const activeSigner = accountManager.active$.value?.signer;
|
||||
|
||||
if (!activeSigner) {
|
||||
throw new Error("No active signer");
|
||||
}
|
||||
|
||||
const groupId = conversation.metadata?.groupId;
|
||||
const relayUrl = conversation.metadata?.relayUrl;
|
||||
|
||||
if (!groupId || !relayUrl) {
|
||||
throw new Error("Group ID and relay URL required");
|
||||
}
|
||||
|
||||
// Fetch current kind 10009 event (group list)
|
||||
const currentEvent = await firstValueFrom(
|
||||
eventStore.replaceable(10009, activePubkey, ""),
|
||||
{ defaultValue: undefined },
|
||||
);
|
||||
|
||||
// Build new tags array
|
||||
let tags: string[][] = [];
|
||||
|
||||
if (currentEvent) {
|
||||
// Copy existing tags
|
||||
tags = [...currentEvent.tags];
|
||||
|
||||
// Check if group is already in the list
|
||||
const existingGroup = tags.find(
|
||||
(t) => t[0] === "group" && t[1] === groupId && t[2] === relayUrl,
|
||||
);
|
||||
|
||||
if (existingGroup) {
|
||||
throw new Error("Group is already in your list");
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new group tag
|
||||
tags.push(["group", groupId, relayUrl]);
|
||||
|
||||
// Create and publish the updated event
|
||||
const factory = new EventFactory();
|
||||
factory.setSigner(activeSigner);
|
||||
|
||||
const draft = await factory.build({
|
||||
kind: 10009,
|
||||
content: "",
|
||||
tags,
|
||||
});
|
||||
const event = await factory.sign(draft);
|
||||
await publishEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a group from the user's group list (kind 10009)
|
||||
*/
|
||||
async unbookmarkGroup(
|
||||
conversation: Conversation,
|
||||
activePubkey: string,
|
||||
): Promise<void> {
|
||||
const activeSigner = accountManager.active$.value?.signer;
|
||||
|
||||
if (!activeSigner) {
|
||||
throw new Error("No active signer");
|
||||
}
|
||||
|
||||
const groupId = conversation.metadata?.groupId;
|
||||
const relayUrl = conversation.metadata?.relayUrl;
|
||||
|
||||
if (!groupId || !relayUrl) {
|
||||
throw new Error("Group ID and relay URL required");
|
||||
}
|
||||
|
||||
// Fetch current kind 10009 event (group list)
|
||||
const currentEvent = await firstValueFrom(
|
||||
eventStore.replaceable(10009, activePubkey, ""),
|
||||
{ defaultValue: undefined },
|
||||
);
|
||||
|
||||
if (!currentEvent) {
|
||||
throw new Error("No group list found");
|
||||
}
|
||||
|
||||
// Find and remove the group tag
|
||||
const originalLength = currentEvent.tags.length;
|
||||
const tags = currentEvent.tags.filter(
|
||||
(t) => !(t[0] === "group" && t[1] === groupId && t[2] === relayUrl),
|
||||
);
|
||||
|
||||
if (tags.length === originalLength) {
|
||||
throw new Error("Group is not in your list");
|
||||
}
|
||||
|
||||
// Create and publish the updated event
|
||||
const factory = new EventFactory();
|
||||
factory.setSigner(activeSigner);
|
||||
|
||||
const draft = await factory.build({
|
||||
kind: 10009,
|
||||
content: "",
|
||||
tags,
|
||||
});
|
||||
const event = await factory.sign(draft);
|
||||
await publishEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Convert Nostr event to Message
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user