diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 0e375a209..64302d2fb 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -79,6 +79,7 @@ import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js"; import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js"; import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js"; import EditableCodeButtonsWidget from "../widgets/type_widgets/editable_code_buttons.js"; +import ApiLogWidget from "../widgets/api_log.js"; export default class DesktopLayout { constructor(customWidgets) { @@ -197,6 +198,7 @@ export default class DesktopLayout { .child(new SqlResultWidget()) ) .child(new EditableCodeButtonsWidget()) + .child(new ApiLogWidget()) .child(new FindWidget()) .child( ...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index 5ae0eaa05..6036c9c5b 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -13,6 +13,7 @@ import appContext from "./app_context.js"; import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js"; import NoteContextCachingWidget from "../widgets/note_context_caching_widget.js"; import BasicWidget from "../widgets/basic_widget.js"; +import SpacedUpdate from "./spaced_update.js"; /** * This is the main frontend API interface for scripts. It's published in the local "api" object. @@ -594,6 +595,33 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain * @returns {string} random string */ this.randomString = utils.randomString; + + this.logMessages = {}; + this.logSpacedUpdates = {}; + + /** + * Log given message to the log pane in UI + * + * @param message + */ + this.log = message => { + const {noteId} = this.startNote; + + message = utils.now() + ": " + message; + + console.log(`Script ${noteId}: ${message}`); + + this.logMessages[noteId] = this.logMessages[noteId] || []; + this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => { + const messages = this.logMessages[noteId]; + this.logMessages[noteId] = []; + + appContext.triggerEvent("apiLogMessages", {noteId, messages}); + }, 100); + + this.logMessages[noteId].push(message); + this.logSpacedUpdates[noteId].scheduleUpdate(); + }; } export default FrontendScriptApi; diff --git a/src/public/app/services/ws.js b/src/public/app/services/ws.js index 113d1f0ad..6dd34c1c6 100644 --- a/src/public/app/services/ws.js +++ b/src/public/app/services/ws.js @@ -3,6 +3,7 @@ import toastService from "./toast.js"; import server from "./server.js"; import options from "./options.js"; import frocaUpdater from "./froca_updater.js"; +import appContext from "./app_context.js"; const messageHandlers = []; @@ -118,6 +119,9 @@ async function handleMessage(event) { else if (message.type === 'consistency-checks-failed') { toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000); } + else if (message.type === 'api-log-messages') { + appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages}); + } } let entityChangeIdReachedListeners = []; diff --git a/src/public/app/widgets/api_log.js b/src/public/app/widgets/api_log.js new file mode 100644 index 000000000..72d12691a --- /dev/null +++ b/src/public/app/widgets/api_log.js @@ -0,0 +1,54 @@ +import NoteContextAwareWidget from "./note_context_aware_widget.js"; + +const TPL = ` +
`; + +export default class ApiLogWidget extends NoteContextAwareWidget { + isEnabled() { + return this.note + && this.note.mime.startsWith('application/javascript;env=') + && super.isEnabled(); + } + + doRender() { + this.$widget = $(TPL); + this.$widget.addClass("hidden-api-log"); + + this.$logContainer = this.$widget.find('.api-log-container'); + } + + async refreshWithNote(note) { + this.$logContainer.empty(); + } + + apiLogMessagesEvent({messages, noteId}) { + if (!this.isNote(noteId)) { + return; + } + + this.$widget.removeClass("hidden-api-log"); + + for (const message of messages) { + this.$logContainer.append(message).append($("