fix(react/settings): etapi list not always reacting to changes

This commit is contained in:
Elian Doran 2025-08-15 12:11:40 +03:00
parent c9dcbef014
commit 1f8aa90482
No known key found for this signature in database
3 changed files with 40 additions and 36 deletions

View File

@ -35,8 +35,10 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
loadResults.addOption(attributeEntity.name); loadResults.addOption(attributeEntity.name);
} else if (ec.entityName === "attachments") { } else if (ec.entityName === "attachments") {
processAttachment(loadResults, ec); processAttachment(loadResults, ec);
} else if (ec.entityName === "blobs" || ec.entityName === "etapi_tokens") { } else if (ec.entityName === "blobs") {
// NOOP - these entities are handled at the backend level and don't require frontend processing // NOOP - these entities are handled at the backend level and don't require frontend processing
} else if (ec.entityName === "etapi_tokens") {
loadResults.hasEtapiTokenChanges = true;
} else { } else {
throw new Error(`Unknown entityName '${ec.entityName}'`); throw new Error(`Unknown entityName '${ec.entityName}'`);
} }
@ -77,9 +79,7 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
noteAttributeCache.invalidate(); noteAttributeCache.invalidate();
} }
// TODO: Remove after porting the file const appContext = (await import("../components/app_context.js")).default;
// @ts-ignore
const appContext = (await import("../components/app_context.js")).default as any;
await appContext.triggerEvent("entitiesReloaded", { loadResults }); await appContext.triggerEvent("entitiesReloaded", { loadResults });
} }
} }

View File

@ -1,4 +1,4 @@
import type { AttachmentRow } from "@triliumnext/commons"; import type { AttachmentRow, EtapiTokenRow } from "@triliumnext/commons";
import type { AttributeType } from "../entities/fattribute.js"; import type { AttributeType } from "../entities/fattribute.js";
import type { EntityChange } from "../server_types.js"; import type { EntityChange } from "../server_types.js";
@ -53,6 +53,7 @@ type EntityRowMappings = {
options: OptionRow; options: OptionRow;
revisions: RevisionRow; revisions: RevisionRow;
note_reordering: NoteReorderingRow; note_reordering: NoteReorderingRow;
etapi_tokens: EtapiTokenRow;
}; };
export type EntityRowNames = keyof EntityRowMappings; export type EntityRowNames = keyof EntityRowMappings;
@ -68,6 +69,7 @@ export default class LoadResults {
private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[]; private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[];
private optionNames: string[]; private optionNames: string[];
private attachmentRows: AttachmentRow[]; private attachmentRows: AttachmentRow[];
public hasEtapiTokenChanges: boolean = false;
constructor(entityChanges: EntityChange[]) { constructor(entityChanges: EntityChange[]) {
const entities: Record<string, Record<string, any>> = {}; const entities: Record<string, Record<string, any>> = {};
@ -215,7 +217,8 @@ export default class LoadResults {
this.revisionRows.length === 0 && this.revisionRows.length === 0 &&
this.contentNoteIdToComponentId.length === 0 && this.contentNoteIdToComponentId.length === 0 &&
this.optionNames.length === 0 && this.optionNames.length === 0 &&
this.attachmentRows.length === 0 this.attachmentRows.length === 0 &&
!this.hasEtapiTokenChanges
); );
} }

View File

@ -10,6 +10,7 @@ import toast from "../../../services/toast";
import dialog from "../../../services/dialog"; import dialog from "../../../services/dialog";
import { formatDateTime } from "../../../utils/formatters"; import { formatDateTime } from "../../../utils/formatters";
import ActionButton from "../../react/ActionButton"; import ActionButton from "../../react/ActionButton";
import useTriliumEvent from "../../react/hooks";
type RenameTokenCallback = (tokenId: string, oldName: string) => Promise<void>; type RenameTokenCallback = (tokenId: string, oldName: string) => Promise<void>;
type DeleteTokenCallback = (tokenId: string, name: string ) => Promise<void>; type DeleteTokenCallback = (tokenId: string, name: string ) => Promise<void>;
@ -22,6 +23,11 @@ export default function EtapiSettings() {
} }
useEffect(refreshTokens, []); useEffect(refreshTokens, []);
useTriliumEvent("entitiesReloaded", ({loadResults}) => {
if (loadResults.hasEtapiTokenChanges) {
refreshTokens();
}
});
const createTokenCallback = useCallback(async () => { const createTokenCallback = useCallback(async () => {
const tokenName = await dialog.prompt({ const tokenName = await dialog.prompt({
@ -42,33 +48,6 @@ export default function EtapiSettings() {
message: t("etapi.token_created_message"), message: t("etapi.token_created_message"),
defaultValue: authToken defaultValue: authToken
}); });
refreshTokens();
}, []);
const renameTokenCallback = useCallback<RenameTokenCallback>(async (tokenId: string, oldName: string) => {
const tokenName = await dialog.prompt({
title: t("etapi.rename_token_title"),
message: t("etapi.rename_token_message"),
defaultValue: oldName
});
if (!tokenName?.trim()) {
return;
}
await server.patch(`etapi-tokens/${tokenId}`, { name: tokenName });
refreshTokens();
}, []);
const deleteTokenCallback = useCallback<DeleteTokenCallback>(async (tokenId: string, name: string) => {
if (!(await dialog.confirm(t("etapi.delete_token_confirmation", { name })))) {
return;
}
await server.remove(`etapi-tokens/${tokenId}`);
refreshTokens();
}, []); }, []);
return ( return (
@ -92,16 +71,38 @@ export default function EtapiSettings() {
<hr /> <hr />
<h5>{t("etapi.existing_tokens")}</h5> <h5>{t("etapi.existing_tokens")}</h5>
<TokenList tokens={tokens} renameCallback={renameTokenCallback} deleteCallback={deleteTokenCallback} /> <TokenList tokens={tokens} />
</OptionsSection> </OptionsSection>
) )
} }
function TokenList({ tokens, renameCallback, deleteCallback }: { tokens: EtapiToken[], renameCallback: RenameTokenCallback, deleteCallback: DeleteTokenCallback }) { function TokenList({ tokens }: { tokens: EtapiToken[] }) {
if (!tokens.length) { if (!tokens.length) {
return <div>{t("etapi.no_tokens_yet")}</div>; return <div>{t("etapi.no_tokens_yet")}</div>;
} }
const renameCallback = useCallback<RenameTokenCallback>(async (tokenId: string, oldName: string) => {
const tokenName = await dialog.prompt({
title: t("etapi.rename_token_title"),
message: t("etapi.rename_token_message"),
defaultValue: oldName
});
if (!tokenName?.trim()) {
return;
}
await server.patch(`etapi-tokens/${tokenId}`, { name: tokenName });
}, []);
const deleteCallback = useCallback<DeleteTokenCallback>(async (tokenId: string, name: string) => {
if (!(await dialog.confirm(t("etapi.delete_token_confirmation", { name })))) {
return;
}
await server.remove(`etapi-tokens/${tokenId}`);
}, []);
return ( return (
<div style={{ overflow: "auto", height: "500px"}}> <div style={{ overflow: "auto", height: "500px"}}>
<table className="table table-stripped"> <table className="table table-stripped">