mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
sync deletion events (#455)
This commit is contained in:
parent
f9d3f88cfb
commit
049b2d56c8
@ -29,12 +29,13 @@ import { PublicMessageContainer } from "./public-message-container.tsx";
|
|||||||
import { ChatMessage } from "./message.ts";
|
import { ChatMessage } from "./message.ts";
|
||||||
import { filter, forever, map } from "./_helper.ts";
|
import { filter, forever, map } from "./_helper.ts";
|
||||||
import { RightPanel } from "./components/right-panel.tsx";
|
import { RightPanel } from "./components/right-panel.tsx";
|
||||||
import { ComponentChildren } from "https://esm.sh/preact@10.17.1";
|
|
||||||
import { SignIn } from "./sign-in.tsx";
|
import { SignIn } from "./sign-in.tsx";
|
||||||
import { getTags, Parsed_Event } from "../nostr.ts";
|
import { getTags, Parsed_Event } from "../nostr.ts";
|
||||||
import { Toast } from "./components/toast.tsx";
|
import { Toast } from "./components/toast.tsx";
|
||||||
import { ToastChannel } from "./components/toast.tsx";
|
import { ToastChannel } from "./components/toast.tsx";
|
||||||
import { RightPanelChannel } from "./components/right-panel.tsx";
|
import { RightPanelChannel } from "./components/right-panel.tsx";
|
||||||
|
import { getRelayInformation } from "./relay-detail.tsx";
|
||||||
|
import { func_IsAdmin } from "./message-list.tsx";
|
||||||
|
|
||||||
export async function Start(database: DexieDatabase) {
|
export async function Start(database: DexieDatabase) {
|
||||||
console.log("Start the application");
|
console.log("Start the application");
|
||||||
@ -56,6 +57,7 @@ export async function Start(database: DexieDatabase) {
|
|||||||
const toastInputChan: ToastChannel = new Channel();
|
const toastInputChan: ToastChannel = new Channel();
|
||||||
const dbView = await Database_View.New(database, database, database);
|
const dbView = await Database_View.New(database, database, database);
|
||||||
const newNostrEventChannel = new Channel<NostrEvent>();
|
const newNostrEventChannel = new Channel<NostrEvent>();
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
for await (const event of newNostrEventChannel) {
|
for await (const event of newNostrEventChannel) {
|
||||||
const err = await pool.sendEvent(event);
|
const err = await pool.sendEvent(event);
|
||||||
@ -246,6 +248,7 @@ export class App {
|
|||||||
forever(sync_client_specific_data(this.pool, this.ctx, this.database));
|
forever(sync_client_specific_data(this.pool, this.ctx, this.database));
|
||||||
forever(sync_profile_events(this.database, this.pool));
|
forever(sync_profile_events(this.database, this.pool));
|
||||||
forever(sync_public_notes(this.pool, this.database));
|
forever(sync_public_notes(this.pool, this.database));
|
||||||
|
forever(sync_deletion_events(this.pool, this.database));
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -315,7 +318,24 @@ type AppProps = {
|
|||||||
installPrompt: InstallPrompt;
|
installPrompt: InstallPrompt;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AppComponent extends Component<AppProps> {
|
export class AppComponent extends Component<AppProps, {
|
||||||
|
isAdmin: func_IsAdmin | undefined;
|
||||||
|
admin: string | undefined;
|
||||||
|
}> {
|
||||||
|
state = {
|
||||||
|
isAdmin: undefined,
|
||||||
|
admin: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
await this.updateAdminState();
|
||||||
|
for await (const update of this.props.eventBus.onChange()) {
|
||||||
|
if (update.type == "SelectRelay") {
|
||||||
|
this.updateAdminState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render(props: AppProps) {
|
render(props: AppProps) {
|
||||||
const t = Date.now();
|
const t = Date.now();
|
||||||
const model = props.model;
|
const model = props.model;
|
||||||
@ -350,6 +370,7 @@ export class AppComponent extends Component<AppProps> {
|
|||||||
getProfilesByText: app.database.getProfilesByText,
|
getProfilesByText: app.database.getProfilesByText,
|
||||||
isUserBlocked: app.conversationLists.isUserBlocked,
|
isUserBlocked: app.conversationLists.isUserBlocked,
|
||||||
getEventByID: app.database.getEventByID,
|
getEventByID: app.database.getEventByID,
|
||||||
|
isAdmin: this.state.isAdmin,
|
||||||
}}
|
}}
|
||||||
userBlocker={app.conversationLists}
|
userBlocker={app.conversationLists}
|
||||||
/>
|
/>
|
||||||
@ -376,6 +397,7 @@ export class AppComponent extends Component<AppProps> {
|
|||||||
getProfilesByText: app.database.getProfilesByText,
|
getProfilesByText: app.database.getProfilesByText,
|
||||||
isUserBlocked: app.conversationLists.isUserBlocked,
|
isUserBlocked: app.conversationLists.isUserBlocked,
|
||||||
getEventByID: app.database.getEventByID,
|
getEventByID: app.database.getEventByID,
|
||||||
|
isAdmin: this.state.isAdmin,
|
||||||
}}
|
}}
|
||||||
messages={Array.from(
|
messages={Array.from(
|
||||||
map(
|
map(
|
||||||
@ -386,7 +408,8 @@ export class AppComponent extends Component<AppProps> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const relays = app.database.getRelayRecord(e.id);
|
const relays = app.database.getRelayRecord(e.id);
|
||||||
return relays.has(model.currentRelay);
|
return relays.has(model.currentRelay) &&
|
||||||
|
!app.database.isDeleted(e.id, this.state.admin);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(e) => {
|
(e) => {
|
||||||
@ -464,6 +487,22 @@ export class AppComponent extends Component<AppProps> {
|
|||||||
console.debug("AppComponent:end", Date.now() - t);
|
console.debug("AppComponent:end", Date.now() - t);
|
||||||
return final;
|
return final;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateAdminState = async () => {
|
||||||
|
const currentRelayInformation = await getRelayInformation(this.props.model.currentRelay);
|
||||||
|
if (currentRelayInformation instanceof Error) {
|
||||||
|
console.error(currentRelayInformation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
admin: currentRelayInformation.pubkey,
|
||||||
|
isAdmin: this.isAdmin(currentRelayInformation.pubkey),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
isAdmin = (admin?: string) => (pubkey: string) => {
|
||||||
|
return admin === pubkey;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: move to somewhere else
|
// todo: move to somewhere else
|
||||||
@ -573,6 +612,33 @@ const sync_client_specific_data = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sync_deletion_events = async (
|
||||||
|
pool: ConnectionPool,
|
||||||
|
database: Database_View,
|
||||||
|
) => {
|
||||||
|
const stream = await pool.newSub("sync_deletion_events", {
|
||||||
|
kinds: [NostrKind.DELETE],
|
||||||
|
since: hours_ago(48),
|
||||||
|
});
|
||||||
|
if (stream instanceof Error) {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
for await (const msg of stream.chan) {
|
||||||
|
if (msg.res.type === "EOSE") {
|
||||||
|
continue;
|
||||||
|
} else if (msg.res.type === "NOTICE") {
|
||||||
|
console.log(`Notice: ${msg.res.note}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = await database.addEvent(msg.res.event, msg.url);
|
||||||
|
if (ok instanceof Error) {
|
||||||
|
console.error(msg);
|
||||||
|
console.error(ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export function hours_ago(hours: number) {
|
export function hours_ago(hours: number) {
|
||||||
return Math.floor(Date.now() / 1000) - hours * 60 * 60;
|
return Math.floor(Date.now() / 1000) - hours * 60 * 60;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import { NavigationModel } from "./nav.tsx";
|
import { NavigationModel } from "./nav.tsx";
|
||||||
import { ProfileData } from "../features/profile.ts";
|
|
||||||
|
|
||||||
import { DM_Model } from "./dm.tsx";
|
import { DM_Model } from "./dm.tsx";
|
||||||
import { Public_Model } from "./public-message-container.tsx";
|
import { Public_Model } from "./public-message-container.tsx";
|
||||||
import { App } from "./app.tsx";
|
import { App } from "./app.tsx";
|
||||||
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
|
||||||
import { default_blowater_relay } from "./relay-config.ts";
|
import { default_blowater_relay } from "./relay-config.ts";
|
||||||
|
|
||||||
export type Model = {
|
export type Model = {
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
PutChannel,
|
PutChannel,
|
||||||
sleep,
|
sleep,
|
||||||
} from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
} from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||||
import { prepareNormalNostrEvent } from "../../libs/nostr.ts/event.ts";
|
import { prepareDeletionEvent, prepareNormalNostrEvent } from "../../libs/nostr.ts/event.ts";
|
||||||
import { prepareReplyEvent } from "../nostr.ts";
|
import { prepareReplyEvent } from "../nostr.ts";
|
||||||
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
||||||
import { NoteID } from "../../libs/nostr.ts/nip19.ts";
|
import { NoteID } from "../../libs/nostr.ts/nip19.ts";
|
||||||
@ -55,7 +55,7 @@ import { SendingEventRejection, ToastChannel } from "./components/toast.tsx";
|
|||||||
import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";
|
import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";
|
||||||
import { default_blowater_relay } from "./relay-config.ts";
|
import { default_blowater_relay } from "./relay-config.ts";
|
||||||
import { forever } from "./_helper.ts";
|
import { forever } from "./_helper.ts";
|
||||||
import { func_GetEventByID } from "./message-list.tsx";
|
import { DeleteEvent, func_GetEventByID } from "./message-list.tsx";
|
||||||
import { FilterContent } from "./filter.tsx";
|
import { FilterContent } from "./filter.tsx";
|
||||||
import { CloseRightPanel } from "./components/right-panel.tsx";
|
import { CloseRightPanel } from "./components/right-panel.tsx";
|
||||||
import { RightPanelChannel } from "./components/right-panel.tsx";
|
import { RightPanelChannel } from "./components/right-panel.tsx";
|
||||||
@ -86,7 +86,8 @@ export type UI_Interaction_Event =
|
|||||||
| FilterContent
|
| FilterContent
|
||||||
| CloseRightPanel
|
| CloseRightPanel
|
||||||
| ReplyToMessage
|
| ReplyToMessage
|
||||||
| EditorSelectProfile;
|
| EditorSelectProfile
|
||||||
|
| DeleteEvent;
|
||||||
|
|
||||||
type BackToContactList = {
|
type BackToContactList = {
|
||||||
type: "BackToContactList";
|
type: "BackToContactList";
|
||||||
@ -317,6 +318,25 @@ const handle_update_event = async (chan: PutChannel<true>, args: {
|
|||||||
app.rightPanelInputChan.put(undefined);
|
app.rightPanelInputChan.put(undefined);
|
||||||
} //
|
} //
|
||||||
//
|
//
|
||||||
|
// Deletion
|
||||||
|
//
|
||||||
|
else if (event.type == "DeleteEvent") {
|
||||||
|
const deletionEvent = await prepareDeletionEvent(
|
||||||
|
app.ctx,
|
||||||
|
"Request deletion",
|
||||||
|
event.event,
|
||||||
|
);
|
||||||
|
if (deletionEvent instanceof Error) {
|
||||||
|
app.toastInputChan.put(() => deletionEvent.message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const err = await current_relay.sendEvent(deletionEvent);
|
||||||
|
if (err instanceof Error) {
|
||||||
|
app.toastInputChan.put(() => err.message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} //
|
||||||
|
//
|
||||||
// DM
|
// DM
|
||||||
//
|
//
|
||||||
else if (event.type == "ViewUserDetail") {
|
else if (event.type == "ViewUserDetail") {
|
||||||
|
@ -9,7 +9,7 @@ import { IconButtonClass } from "./components/tw.ts";
|
|||||||
|
|
||||||
import { LeftArrowIcon } from "./icons/left-arrow-icon.tsx";
|
import { LeftArrowIcon } from "./icons/left-arrow-icon.tsx";
|
||||||
import { MessagePanel_V0 } from "./message-panel.tsx";
|
import { MessagePanel_V0 } from "./message-panel.tsx";
|
||||||
import { func_GetProfileByPublicKey, func_GetProfilesByText, ProfileGetter } from "./search.tsx";
|
import { func_GetProfileByPublicKey, func_GetProfilesByText } from "./search.tsx";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConversationList,
|
ConversationList,
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
NewMessageChecker,
|
NewMessageChecker,
|
||||||
PinListGetter,
|
PinListGetter,
|
||||||
} from "./conversation-list.tsx";
|
} from "./conversation-list.tsx";
|
||||||
import { func_GetEventByID } from "./message-list.tsx";
|
import { func_GetEventByID, func_IsAdmin } from "./message-list.tsx";
|
||||||
|
|
||||||
export type DM_Model = {
|
export type DM_Model = {
|
||||||
currentConversation: PublicKey | undefined;
|
currentConversation: PublicKey | undefined;
|
||||||
@ -36,6 +36,7 @@ type DirectMessageContainerProps = {
|
|||||||
relayRecordGetter: RelayRecordGetter;
|
relayRecordGetter: RelayRecordGetter;
|
||||||
isUserBlocked: (pubkey: PublicKey) => boolean;
|
isUserBlocked: (pubkey: PublicKey) => boolean;
|
||||||
getEventByID: func_GetEventByID;
|
getEventByID: func_GetEventByID;
|
||||||
|
isAdmin: func_IsAdmin | undefined;
|
||||||
};
|
};
|
||||||
userBlocker: UserBlocker;
|
userBlocker: UserBlocker;
|
||||||
} & DM_Model;
|
} & DM_Model;
|
||||||
|
@ -13,18 +13,17 @@ export function DeleteIcon(props: {
|
|||||||
<svg
|
<svg
|
||||||
class={props.class}
|
class={props.class}
|
||||||
style={props.style}
|
style={props.style}
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<g>
|
<path
|
||||||
<path
|
d="M16 6V5.2C16 4.0799 16 3.51984 15.782 3.09202C15.5903 2.71569 15.2843 2.40973 14.908 2.21799C14.4802 2 13.9201 2 12.8 2H11.2C10.0799 2 9.51984 2 9.09202 2.21799C8.71569 2.40973 8.40973 2.71569 8.21799 3.09202C8 3.51984 8 4.0799 8 5.2V6M10 11.5V16.5M14 11.5V16.5M3 6H21M19 6V17.2C19 18.8802 19 19.7202 18.673 20.362C18.3854 20.9265 17.9265 21.3854 17.362 21.673C16.7202 22 15.8802 22 14.2 22H9.8C8.11984 22 7.27976 22 6.63803 21.673C6.07354 21.3854 5.6146 20.9265 5.32698 20.362C5 19.7202 5 18.8802 5 17.2V6"
|
||||||
d="M10.6667 4.00016V3.46683C10.6667 2.72009 10.6667 2.34672 10.5213 2.06151C10.3935 1.81063 10.1895 1.60665 9.93865 1.47882C9.65344 1.3335 9.28007 1.3335 8.53333 1.3335H7.46667C6.71993 1.3335 6.34656 1.3335 6.06135 1.47882C5.81046 1.60665 5.60649 1.81063 5.47866 2.06151C5.33333 2.34672 5.33333 2.72009 5.33333 3.46683V4.00016M6.66667 7.66683V11.0002M9.33333 7.66683V11.0002M2 4.00016H14M12.6667 4.00016V11.4668C12.6667 12.5869 12.6667 13.147 12.4487 13.5748C12.2569 13.9511 11.951 14.2571 11.5746 14.4488C11.1468 14.6668 10.5868 14.6668 9.46667 14.6668H6.53333C5.41323 14.6668 4.85318 14.6668 4.42535 14.4488C4.04903 14.2571 3.74307 13.9511 3.55132 13.5748C3.33333 13.147 3.33333 12.5869 3.33333 11.4668V4.00016"
|
stroke="currentColor"
|
||||||
stroke-width="1.33333"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,23 +29,32 @@ import { AboutIcon } from "./icons/about-icon.tsx";
|
|||||||
import { BackgroundColor_MessagePanel, PrimaryTextColor } from "./style/colors.ts";
|
import { BackgroundColor_MessagePanel, PrimaryTextColor } from "./style/colors.ts";
|
||||||
import { Parsed_Event } from "../nostr.ts";
|
import { Parsed_Event } from "../nostr.ts";
|
||||||
import { NoteID } from "../../libs/nostr.ts/nip19.ts";
|
import { NoteID } from "../../libs/nostr.ts/nip19.ts";
|
||||||
import { robohash } from "./relay-detail.tsx";
|
import { RelayInformation, robohash } from "./relay-detail.tsx";
|
||||||
import { ReplyIcon } from "./icons/reply-icon.tsx";
|
import { ReplyIcon } from "./icons/reply-icon.tsx";
|
||||||
import { ChatMessagesGetter } from "./app_update.tsx";
|
import { ChatMessagesGetter } from "./app_update.tsx";
|
||||||
import { NostrKind } from "../../libs/nostr.ts/nostr.ts";
|
import { NostrKind } from "../../libs/nostr.ts/nostr.ts";
|
||||||
import { func_GetProfileByPublicKey } from "./search.tsx";
|
import { func_GetProfileByPublicKey } from "./search.tsx";
|
||||||
|
import { DeleteIcon } from "./icons/delete-icon.tsx";
|
||||||
|
|
||||||
|
export type func_IsAdmin = (pubkey: string) => boolean;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
myPublicKey: PublicKey;
|
myPublicKey: PublicKey;
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
emit: emitFunc<
|
emit: emitFunc<
|
||||||
DirectMessagePanelUpdate | SelectConversation | SyncEvent | ViewUserDetail | ReplyToMessage
|
| DirectMessagePanelUpdate
|
||||||
|
| SelectConversation
|
||||||
|
| SyncEvent
|
||||||
|
| ViewUserDetail
|
||||||
|
| ReplyToMessage
|
||||||
|
| DeleteEvent
|
||||||
>;
|
>;
|
||||||
getters: {
|
getters: {
|
||||||
messageGetter: ChatMessagesGetter;
|
messageGetter: ChatMessagesGetter;
|
||||||
getProfileByPublicKey: func_GetProfileByPublicKey;
|
getProfileByPublicKey: func_GetProfileByPublicKey;
|
||||||
relayRecordGetter: RelayRecordGetter;
|
relayRecordGetter: RelayRecordGetter;
|
||||||
getEventByID: func_GetEventByID;
|
getEventByID: func_GetEventByID;
|
||||||
|
isAdmin: func_IsAdmin | undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,16 +287,23 @@ function MessageBoxGroup(props: {
|
|||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
myPublicKey: PublicKey;
|
myPublicKey: PublicKey;
|
||||||
emit: emitFunc<
|
emit: emitFunc<
|
||||||
DirectMessagePanelUpdate | ViewUserDetail | SelectConversation | SyncEvent | ReplyToMessage
|
| DirectMessagePanelUpdate
|
||||||
|
| ViewUserDetail
|
||||||
|
| SelectConversation
|
||||||
|
| SyncEvent
|
||||||
|
| ReplyToMessage
|
||||||
|
| DeleteEvent
|
||||||
>;
|
>;
|
||||||
getters: {
|
getters: {
|
||||||
messageGetter: ChatMessagesGetter;
|
messageGetter: ChatMessagesGetter;
|
||||||
getProfileByPublicKey: func_GetProfileByPublicKey;
|
getProfileByPublicKey: func_GetProfileByPublicKey;
|
||||||
relayRecordGetter: RelayRecordGetter;
|
relayRecordGetter: RelayRecordGetter;
|
||||||
getEventByID: func_GetEventByID;
|
getEventByID: func_GetEventByID;
|
||||||
|
isAdmin: func_IsAdmin | undefined;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
const first_message = props.messages[0];
|
const first_message = props.messages[0];
|
||||||
|
const { myPublicKey } = props;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
@ -296,7 +312,12 @@ function MessageBoxGroup(props: {
|
|||||||
isMobile() ? "select-none" : ""
|
isMobile() ? "select-none" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{MessageActions(first_message, props.emit)}
|
{MessageActions({
|
||||||
|
isAdmin: props.getters.isAdmin,
|
||||||
|
myPublicKey,
|
||||||
|
message: first_message,
|
||||||
|
emit: props.emit,
|
||||||
|
})}
|
||||||
{renderRelply(first_message.event, props.getters, props.emit)}
|
{renderRelply(first_message.event, props.getters, props.emit)}
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -345,7 +366,12 @@ function MessageBoxGroup(props: {
|
|||||||
isMobile() ? "select-none" : ""
|
isMobile() ? "select-none" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{MessageActions(msg, props.emit)}
|
{MessageActions({
|
||||||
|
isAdmin: props.getters.isAdmin,
|
||||||
|
myPublicKey,
|
||||||
|
message: msg,
|
||||||
|
emit: props.emit,
|
||||||
|
})}
|
||||||
{Time(msg.created_at)}
|
{Time(msg.created_at)}
|
||||||
<div
|
<div
|
||||||
class={`flex-1`}
|
class={`flex-1`}
|
||||||
@ -377,10 +403,13 @@ export type ReplyToMessage = {
|
|||||||
event: Parsed_Event;
|
event: Parsed_Event;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MessageActions(
|
function MessageActions(args: {
|
||||||
message: ChatMessage,
|
myPublicKey: PublicKey;
|
||||||
emit: emitFunc<DirectMessagePanelUpdate | ReplyToMessage>,
|
message: ChatMessage;
|
||||||
) {
|
emit: emitFunc<DirectMessagePanelUpdate | ReplyToMessage | DeleteEvent>;
|
||||||
|
isAdmin: func_IsAdmin | undefined;
|
||||||
|
}) {
|
||||||
|
const { myPublicKey, message, emit, isAdmin } = args;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`hidden
|
class={`hidden
|
||||||
@ -405,6 +434,24 @@ function MessageActions(
|
|||||||
<ReplyIcon class={`w-5 h-5 text-[#B6BAC0] hover:text-[#D9DBDE]`} />
|
<ReplyIcon class={`w-5 h-5 text-[#B6BAC0] hover:text-[#D9DBDE]`} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{(myPublicKey.hex === message.author.hex || (isAdmin && isAdmin(myPublicKey.hex))) &&
|
||||||
|
(message.event.kind === NostrKind.TEXT_NOTE) &&
|
||||||
|
(
|
||||||
|
<button
|
||||||
|
class={`flex items-center justify-center
|
||||||
|
p-1
|
||||||
|
bg-[#313338] hover:bg-[#3A3C41]`}
|
||||||
|
onClick={() => {
|
||||||
|
emit({
|
||||||
|
type: "DeleteEvent",
|
||||||
|
event: message.event,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon class={`w-5 h-5 text-[#B6BAC0] hover:text-[#D9DBDE]`} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class={`flex items-center justify-center
|
class={`flex items-center justify-center
|
||||||
p-1
|
p-1
|
||||||
@ -422,6 +469,11 @@ function MessageActions(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DeleteEvent = {
|
||||||
|
type: "DeleteEvent";
|
||||||
|
event: Parsed_Event;
|
||||||
|
};
|
||||||
|
|
||||||
function last<T>(array: Array<T>): T | undefined {
|
function last<T>(array: Array<T>): T | undefined {
|
||||||
if (array.length == 0) {
|
if (array.length == 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -23,7 +23,13 @@ import {
|
|||||||
LinkColor,
|
LinkColor,
|
||||||
} from "./style/colors.ts";
|
} from "./style/colors.ts";
|
||||||
import { BlockUser, UnblockUser } from "./user-detail.tsx";
|
import { BlockUser, UnblockUser } from "./user-detail.tsx";
|
||||||
import { func_GetEventByID, MessageList, ReplyToMessage } from "./message-list.tsx";
|
import {
|
||||||
|
DeleteEvent,
|
||||||
|
func_GetEventByID,
|
||||||
|
func_IsAdmin,
|
||||||
|
MessageList,
|
||||||
|
ReplyToMessage,
|
||||||
|
} from "./message-list.tsx";
|
||||||
import { MessageList_V0 } from "./message-list.tsx";
|
import { MessageList_V0 } from "./message-list.tsx";
|
||||||
import { func_GetProfileByPublicKey } from "./search.tsx";
|
import { func_GetProfileByPublicKey } from "./search.tsx";
|
||||||
|
|
||||||
@ -47,7 +53,6 @@ export type ViewUserDetail = {
|
|||||||
|
|
||||||
interface MessagePanelProps {
|
interface MessagePanelProps {
|
||||||
myPublicKey: PublicKey;
|
myPublicKey: PublicKey;
|
||||||
|
|
||||||
emit: emitFunc<
|
emit: emitFunc<
|
||||||
| EditorEvent
|
| EditorEvent
|
||||||
| DirectMessagePanelUpdate
|
| DirectMessagePanelUpdate
|
||||||
@ -58,6 +63,7 @@ interface MessagePanelProps {
|
|||||||
| UnblockUser
|
| UnblockUser
|
||||||
| SyncEvent
|
| SyncEvent
|
||||||
| ReplyToMessage
|
| ReplyToMessage
|
||||||
|
| DeleteEvent
|
||||||
>;
|
>;
|
||||||
eventSub: EventSubscriber<UI_Interaction_Event>;
|
eventSub: EventSubscriber<UI_Interaction_Event>;
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
@ -68,6 +74,7 @@ interface MessagePanelProps {
|
|||||||
relayRecordGetter: RelayRecordGetter;
|
relayRecordGetter: RelayRecordGetter;
|
||||||
isUserBlocked: (pubkey: PublicKey) => boolean;
|
isUserBlocked: (pubkey: PublicKey) => boolean;
|
||||||
getEventByID: func_GetEventByID;
|
getEventByID: func_GetEventByID;
|
||||||
|
isAdmin: func_IsAdmin | undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ import { RelaySwitchList } from "./relay-switch-list.tsx";
|
|||||||
import { SocialIcon } from "./icons/social-icon.tsx";
|
import { SocialIcon } from "./icons/social-icon.tsx";
|
||||||
import { SearchIcon } from "./icons/search-icon.tsx";
|
import { SearchIcon } from "./icons/search-icon.tsx";
|
||||||
import { StartSearch } from "./search_model.ts";
|
import { StartSearch } from "./search_model.ts";
|
||||||
import { setState } from "./_helper.ts";
|
|
||||||
|
|
||||||
export type InstallPrompt = {
|
export type InstallPrompt = {
|
||||||
event: Event | undefined;
|
event: Event | undefined;
|
||||||
@ -49,8 +48,8 @@ type Props = {
|
|||||||
emit: emitFunc<NavigationUpdate | SelectRelay | StartSearch>;
|
emit: emitFunc<NavigationUpdate | SelectRelay | StartSearch>;
|
||||||
installPrompt: InstallPrompt;
|
installPrompt: InstallPrompt;
|
||||||
pool: ConnectionPool;
|
pool: ConnectionPool;
|
||||||
currentRelay?: string;
|
|
||||||
activeNav: NavTabID;
|
activeNav: NavTabID;
|
||||||
|
currentRelay: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@ -140,8 +139,13 @@ export class NavBar extends Component<Props, State> {
|
|||||||
render(props: Props) {
|
render(props: Props) {
|
||||||
return (
|
return (
|
||||||
<div class={this.styles.container}>
|
<div class={this.styles.container}>
|
||||||
{/* <Avatar class={this.styles.avatar} picture={this.props.profile?.profile?.picture} /> */}
|
{
|
||||||
{<RelaySwitchList emit={props.emit} pool={props.pool} currentRelay={props.currentRelay} />}
|
<RelaySwitchList
|
||||||
|
emit={props.emit}
|
||||||
|
pool={props.pool}
|
||||||
|
currentRelay={props.currentRelay}
|
||||||
|
/>
|
||||||
|
}
|
||||||
{this.tabs.map(({ icon, id }) => (
|
{this.tabs.map(({ icon, id }) => (
|
||||||
<div class={this.styles.tabsContainer}>
|
<div class={this.styles.tabsContainer}>
|
||||||
{id === "Setting" && this.state.installPrompt.event
|
{id === "Setting" && this.state.installPrompt.event
|
||||||
|
@ -3,7 +3,7 @@ import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";
|
|||||||
import { emitFunc, EventBus } from "../event-bus.ts";
|
import { emitFunc, EventBus } from "../event-bus.ts";
|
||||||
import { ChatMessagesGetter, UI_Interaction_Event } from "./app_update.tsx";
|
import { ChatMessagesGetter, UI_Interaction_Event } from "./app_update.tsx";
|
||||||
import { setState } from "./_helper.ts";
|
import { setState } from "./_helper.ts";
|
||||||
import { func_GetProfileByPublicKey, func_GetProfilesByText, ProfileGetter } from "./search.tsx";
|
import { func_GetProfileByPublicKey, func_GetProfilesByText } from "./search.tsx";
|
||||||
|
|
||||||
import { RelayRecordGetter } from "../database.ts";
|
import { RelayRecordGetter } from "../database.ts";
|
||||||
import { NewMessageChecker } from "./conversation-list.tsx";
|
import { NewMessageChecker } from "./conversation-list.tsx";
|
||||||
@ -13,7 +13,7 @@ import { NostrAccountContext } from "../../libs/nostr.ts/nostr.ts";
|
|||||||
import { MessagePanel } from "./message-panel.tsx";
|
import { MessagePanel } from "./message-panel.tsx";
|
||||||
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
||||||
import { ChatMessage } from "./message.ts";
|
import { ChatMessage } from "./message.ts";
|
||||||
import { func_GetEventByID } from "./message-list.tsx";
|
import { func_GetEventByID, func_IsAdmin } from "./message-list.tsx";
|
||||||
import { Filter, FilterContent } from "./filter.tsx";
|
import { Filter, FilterContent } from "./filter.tsx";
|
||||||
import { NoteID } from "../../libs/nostr.ts/nip19.ts";
|
import { NoteID } from "../../libs/nostr.ts/nip19.ts";
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ type Props = {
|
|||||||
relayRecordGetter: RelayRecordGetter;
|
relayRecordGetter: RelayRecordGetter;
|
||||||
isUserBlocked: func_IsUserBlocked;
|
isUserBlocked: func_IsUserBlocked;
|
||||||
getEventByID: func_GetEventByID;
|
getEventByID: func_GetEventByID;
|
||||||
|
isAdmin: func_IsAdmin | undefined;
|
||||||
};
|
};
|
||||||
} & Public_Model;
|
} & Public_Model;
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ import { setState } from "./_helper.ts";
|
|||||||
import { AddIcon } from "./icons/add-icon.tsx";
|
import { AddIcon } from "./icons/add-icon.tsx";
|
||||||
|
|
||||||
type RelaySwitchListProps = {
|
type RelaySwitchListProps = {
|
||||||
currentRelay?: string;
|
|
||||||
pool: ConnectionPool;
|
pool: ConnectionPool;
|
||||||
emit: emitFunc<SelectRelay | NavigationUpdate>;
|
emit: emitFunc<SelectRelay | NavigationUpdate>;
|
||||||
|
currentRelay: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RelaySwitchListState = {
|
type RelaySwitchListState = {
|
||||||
|
@ -248,10 +248,7 @@ export class RelaySetting extends Component<RelaySettingProp, RelaySettingState>
|
|||||||
onClick={this.removeRelay(props, r.url)}
|
onClick={this.removeRelay(props, r.url)}
|
||||||
>
|
>
|
||||||
<DeleteIcon
|
<DeleteIcon
|
||||||
class={`w-[1rem] h-[1rem]`}
|
class={`w-[1rem] h-[1rem] text-[${ErrorColor}]`}
|
||||||
style={{
|
|
||||||
stroke: ErrorColor,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
@ -65,6 +65,7 @@ export class Database_View implements ProfileSetter, ProfileGetter, EventRemover
|
|||||||
private readonly sourceOfChange = csp.chan<{ event: Parsed_Event; relay?: string }>(buffer_size);
|
private readonly sourceOfChange = csp.chan<{ event: Parsed_Event; relay?: string }>(buffer_size);
|
||||||
private readonly caster = csp.multi<{ event: Parsed_Event; relay?: string }>(this.sourceOfChange);
|
private readonly caster = csp.multi<{ event: Parsed_Event; relay?: string }>(this.sourceOfChange);
|
||||||
private readonly profiles = new Map<string, Profile_Nostr_Event>();
|
private readonly profiles = new Map<string, Profile_Nostr_Event>();
|
||||||
|
private readonly deletionEvents = new Map</* event id */ string, /* deletion event */ Parsed_Event>();
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
private readonly eventsAdapter: EventsAdapter,
|
private readonly eventsAdapter: EventsAdapter,
|
||||||
@ -125,18 +126,22 @@ export class Database_View implements ProfileSetter, ProfileGetter, EventRemover
|
|||||||
new Set(all_removed_events.map((mark) => mark.event_id)),
|
new Set(all_removed_events.map((mark) => mark.event_id)),
|
||||||
);
|
);
|
||||||
console.log("Datebase_View:New time spent", Date.now() - t);
|
console.log("Datebase_View:New time spent", Date.now() - t);
|
||||||
for (const e of db.events.values()) {
|
for (const event of db.events.values()) {
|
||||||
if (e.kind == NostrKind.META_DATA) {
|
if (event.kind == NostrKind.META_DATA) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const pEvent = parseProfileEvent(e);
|
const pEvent = parseProfileEvent(event);
|
||||||
if (pEvent instanceof Error) {
|
if (pEvent instanceof Error) {
|
||||||
console.error(pEvent);
|
console.error(pEvent);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
db.setProfile(pEvent);
|
db.setProfile(pEvent);
|
||||||
|
} else if (event.kind == NostrKind.DELETE) {
|
||||||
|
event.parsedTags.e.forEach((event_id) => {
|
||||||
|
db.deletionEvents.set(event_id, event);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(`Datebase_View:Deletion events size: ${db.deletionEvents.size}`);
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +164,19 @@ export class Database_View implements ProfileSetter, ProfileGetter, EventRemover
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDeleted(id: string, admin?: string) {
|
||||||
|
const deletionEvent = this.deletionEvents.get(id);
|
||||||
|
if (deletionEvent == undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const targetEvent = this.getEventByID(id);
|
||||||
|
if (targetEvent == undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deletionEvent.pubkey == targetEvent.publicKey.hex ||
|
||||||
|
deletionEvent.pubkey == admin;
|
||||||
|
}
|
||||||
|
|
||||||
async remove(id: string): Promise<void> {
|
async remove(id: string): Promise<void> {
|
||||||
this.removedEvents.add(id);
|
this.removedEvents.add(id);
|
||||||
await this.eventMarker.markEvent(id, "removed");
|
await this.eventMarker.markEvent(id, "removed");
|
||||||
@ -274,6 +292,10 @@ export class Database_View implements ProfileSetter, ProfileGetter, EventRemover
|
|||||||
return pEvent;
|
return pEvent;
|
||||||
}
|
}
|
||||||
this.setProfile(pEvent);
|
this.setProfile(pEvent);
|
||||||
|
} else if (parsedEvent.kind == NostrKind.DELETE) {
|
||||||
|
parsedEvent.parsedTags.e.forEach((event_id) => {
|
||||||
|
this.deletionEvents.set(event_id, parsedEvent);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.eventsAdapter.put(event);
|
await this.eventsAdapter.put(event);
|
||||||
|
Loading…
Reference in New Issue
Block a user