diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx
index fd2d6a5c4..8c2df7dda 100644
--- a/apps/client/src/layouts/desktop_layout.tsx
+++ b/apps/client/src/layouts/desktop_layout.tsx
@@ -13,7 +13,6 @@ import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status
import SpacerWidget from "../widgets/spacer.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
-import LeftPaneToggleWidget from "../widgets/buttons/left_pane_toggle.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
@@ -24,7 +23,6 @@ import TocWidget from "../widgets/toc.js";
import HighlightsListWidget from "../widgets/highlights_list.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
-import ApiLogWidget from "../widgets/api_log.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import ScrollPadding from "../widgets/scroll_padding.js";
@@ -43,6 +41,7 @@ import SqlResults from "../widgets/sql_result.js";
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
+import ApiLog from "../widgets/api_log.jsx";
export default class DesktopLayout {
@@ -144,7 +143,7 @@ export default class DesktopLayout {
.child()
.child()
)
- .child(new ApiLogWidget())
+ .child()
.child(new FindWidget())
.child(
...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC
diff --git a/apps/client/src/widgets/api_log.css b/apps/client/src/widgets/api_log.css
new file mode 100644
index 000000000..26ab835bd
--- /dev/null
+++ b/apps/client/src/widgets/api_log.css
@@ -0,0 +1,26 @@
+.api-log-widget {
+ padding: 15px;
+ flex-grow: 1;
+ max-height: 40%;
+ position: relative;
+}
+
+.api-log-container {
+ overflow: auto;
+ height: 100%;
+ font-family: var(--monospace-font-family);
+ font-size: 0.8em;
+ white-space: pre;
+}
+
+.close-api-log-button {
+ padding: 5px;
+ border: 1px solid var(--button-border-color);
+ background-color: var(--button-background-color);
+ border-radius: var(--button-border-radius);
+ color: var(--button-text-color);
+ position: absolute;
+ top: 10px;
+ right: 40px;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/apps/client/src/widgets/api_log.ts b/apps/client/src/widgets/api_log.ts
deleted file mode 100644
index 02f7563e6..000000000
--- a/apps/client/src/widgets/api_log.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import type { EventData } from "../components/app_context.js";
-import type FNote from "../entities/fnote.js";
-import { t } from "../services/i18n.js";
-import NoteContextAwareWidget from "./note_context_aware_widget.js";
-
-const TPL = /*html*/`
-
`;
-
-export default class ApiLogWidget extends NoteContextAwareWidget {
-
- private $logContainer!: JQuery;
- private $closeButton!: JQuery;
-
- isEnabled() {
- return !!this.note && this.note.mime.startsWith("application/javascript;env=") && super.isEnabled();
- }
-
- doRender() {
- this.$widget = $(TPL);
- this.toggle(false);
-
- this.$logContainer = this.$widget.find(".api-log-container");
- this.$closeButton = this.$widget.find(".close-api-log-button");
- this.$closeButton.on("click", () => this.toggle(false));
- }
-
- async refreshWithNote(note: FNote) {
- this.$logContainer.empty();
- }
-
- apiLogMessagesEvent({ messages, noteId }: EventData<"apiLogMessages">) {
- if (!this.isNote(noteId)) {
- return;
- }
-
- this.toggle(true);
-
- for (const message of messages) {
- this.$logContainer.append(message).append($("
"));
- }
- }
-
- toggle(show: boolean) {
- this.$widget.toggleClass("hidden-api-log", !show);
- }
-}
diff --git a/apps/client/src/widgets/api_log.tsx b/apps/client/src/widgets/api_log.tsx
new file mode 100644
index 000000000..ba0f8e095
--- /dev/null
+++ b/apps/client/src/widgets/api_log.tsx
@@ -0,0 +1,41 @@
+import { useEffect, useState } from "preact/hooks";
+import "./api_log.css";
+import { useNoteContext, useTriliumEvent } from "./react/hooks";
+import ActionButton from "./react/ActionButton";
+import { t } from "../services/i18n";
+
+/**
+ * Displays the messages that are logged by the current note via `api.log`, for frontend and backend scripts.
+ */
+export default function ApiLog() {
+ const { note, noteId } = useNoteContext();
+ const [ messages, setMessages ] = useState();
+
+ useTriliumEvent("apiLogMessages", ({ messages, noteId: eventNoteId }) => {
+ if (eventNoteId !== noteId) return;
+ setMessages(messages);
+ });
+
+ // Clear when navigating away.
+ useEffect(() => setMessages(undefined), [ note ]);
+
+ const isEnabled = note?.mime.startsWith("application/javascript;env=") && messages?.length;
+ return (
+
+ {isEnabled && (
+ <>
+
setMessages(undefined)}
+ />
+
+
+ {messages.join("\n")}
+
+ >
+ )}
+
+ )
+}