/* Back-to-historytory https://github.com/SiriusXT/trilium-back-to-history version: 0.5 for TriliumNext > 0.90.8 */ const supportType = ['text', 'code'] const autoJump = true; const showJumpButton = true; const omitNoteIds=['root'] // The following code does not need to be modified if (!autoJump && !showJumpButton) { console.log("BackUpHistory: do nothing"); return; } function creatHistory() { return new Promise((resolve, reject) => { api.runOnBackend((noteId) => { try { const { note, branch } = api.createNewNote({ parentNoteId: noteId, title: 'history', content: '{}', type: 'code', mime: 'application/json' }); resolve(note); } catch (error) { // reject(error); } }, [api.startNote.noteId]); }); } let historyNoteId; async function getHistory() { let history; if (historyNoteId !== undefined) { const childNote = await api.getNote(historyNoteId); // return JSON.parse((await (await api.getNote(historyNoteId)).getNoteComplement()).content); history = JSON.parse((await childNote.getNoteComplement()).content); return history; } const children = api.startNote.children; let haveHistory = false; for (let i = 0; i < children.length; i++) { const childNote = await api.getNote(children[i]); if (childNote.type == 'code' && childNote.mime == 'application/json' && childNote.title == 'history') { historyNoteId = childNote.noteId; try { history = JSON.parse((await childNote.getNoteComplement()).content); } catch (error) { history = {} } haveHistory = true; break; } } if (!haveHistory) { const note = await creatHistory(); historyNoteId = note.noteId; history = JSON.parse((await note.getNoteComplement()).content); } return history; } const jumpButton = `<div class="ribbon-tab-title jump-history"> <span class="ribbon-tab-title-icon bx bx-down-arrow-alt"></span> </div><div class="ribbon-tab-spacer jump-history"></div>` var jumpHistory = class extends api.NoteContextAwareWidget { get position() { return 50; } static get parentWidget() { return 'note-detail-pane'; } constructor() { super(); } isEnabled() { return super.isEnabled() && supportType.includes(this.note.type) && !omitNoteIds.includes(this.noteId); } doRender() { this.$widget = $(''); return this.$widget; } scrollHandler = () => { clearTimeout(this.updateTimer); this.updateTimer = setTimeout(async () => { try { const sc = this.$scrollingContainer[0]; const nl = this.$scrollingContainer.find('.note-list-widget')[0]; const radio = (sc.scrollTop / (sc.scrollHeight - nl.offsetHeight)).toFixed(5); if (!isNaN(radio)) { let history = await getHistory(); history[this.note.noteId] = radio; saveHistory(history); } } catch (error) { const $noteSplit = $(`.note-split[data-ntx-id="${this.noteContext.ntxId}"]`); this.$scrollingContainer = $noteSplit.children('.scrolling-container'); console.warn('jumpHistory: ', error); } }, 3000); } scrollTo = () => { const sc = this.$scrollingContainer[0]; const nl = sc.querySelector('.note-list-widget'); const scrollHeight = sc.scrollHeight - nl.offsetHeight; $(sc).animate({ scrollTop: this.scrollToRadio * scrollHeight, }, 300); } addJumpButton() { const $ribbonTab = $(`.note-split[data-ntx-id="${this.noteContext.ntxId}"]`).find('.ribbon-tab-container'); $ribbonTab.find('.jump-history').remove(); const $button = $(jumpButton); $ribbonTab.append($button); $button.on('click', this.scrollTo); $button.attr('title', "Scorll To " + this.scrollToRadio * 100 + "%"); } autoJumpFunc() { this.scrollTo(); clearInterval(this.jumpInterval); let timesRun = 0; let preHeight = 0; this.jumpInterval = setInterval(() => { timesRun += 1; if (timesRun == 5) { clearInterval(this.jumpInterval); } if (this.$scrollingContainer[0].scrollHeight != preHeight) { preHeight = this.$scrollingContainer[0].scrollHeight; this.scrollTo(); } }, 200); } async initEvent() { this.updateTimeout = 0; const $noteSplit = $(`.note-split[data-ntx-id="${this.noteContext.ntxId}"]`); this.$scrollingContainer = $noteSplit.children('.scrolling-container'); this.$scrollingContainer.off('scroll', this.scrollHandler); if (showJumpButton && this.scrollToRadio != undefined) { this.addJumpButton(); } setTimeout(() => { // Automatic jump without monitoring this.$scrollingContainer.off('scroll', this.scrollHandler); this.$scrollingContainer.on('scroll', this.scrollHandler); }, 1000); } async refreshWithNote() { const history = await getHistory(); this.scrollToRadio = history[this.note.noteId]; await this.initEvent(); if (this.scrollToRadio != undefined && autoJump) { this.autoJumpFunc(); } } async entitiesReloadedEvent({ loadResults }) { if (loadResults.isNoteContentReloaded(this.noteId)) { const $noteSplit = $(`.note-split[data-ntx-id="${this.noteContext.ntxId}"]`); this.$scrollingContainer = $noteSplit.children('.scrolling-container'); this.scrollHandler(); } if (loadResults.getAttributeRows().find(attr => attr.noteId === this.noteId)) { this.initEvent(); } } } module.exports = jumpHistory; function saveHistory(history) { const keys = Object.keys(history); while (keys.length > 100) { const oldestKey = keys.shift(); // Get the oldest key delete history[oldestKey]; // Delete the element corresponding to the key } api.runAsyncOnBackendWithManualTransactionHandling(async (historyNoteId, history) => { const historyNote = await api.getNote(historyNoteId); historyNote.setContent(JSON.stringify(history)); }, [historyNoteId, history]); }