From a7b103e07af16142ec22ab92bce4341872ccb980 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 24 Jan 2023 16:24:51 +0100 Subject: [PATCH] add a button to temporarily hide TOC, closes #3555 --- src/public/app/components/note_context.js | 27 +++-- .../app/components/root_command_executor.js | 2 +- src/public/app/widgets/buttons/edit_button.js | 4 +- .../containers/right_pane_container.js | 6 +- src/public/app/widgets/toc.js | 102 ++++++++++++------ 5 files changed, 98 insertions(+), 43 deletions(-) diff --git a/src/public/app/components/note_context.js b/src/public/app/components/note_context.js index 2b9c5b11d..3de2565aa 100644 --- a/src/public/app/components/note_context.js +++ b/src/public/app/components/note_context.js @@ -15,6 +15,8 @@ class NoteContext extends Component { this.ntxId = ntxId || utils.randomString(4); this.hoistedNoteId = hoistedNoteId; this.mainNtxId = mainNtxId; + + this.resetViewScope(); } setEmpty() { @@ -27,6 +29,8 @@ class NoteContext extends Component { noteContext: this, notePath: this.notePath }); + + this.resetViewScope(); } isEmpty() { @@ -47,7 +51,7 @@ class NoteContext extends Component { this.notePath = resolvedNotePath; ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath)); - this.readOnlyTemporarilyDisabled = false; + this.resetViewScope(); this.saveToRecentNotes(resolvedNotePath); @@ -60,6 +64,14 @@ class NoteContext extends Component { }); } + await this.setHoistedNoteIfNeeded(); + + if (utils.isMobile()) { + this.triggerCommand('setActiveScreen', {screen: 'detail'}); + } + } + + async setHoistedNoteIfNeeded() { if (this.hoistedNoteId === 'root' && this.notePath.startsWith("root/_hidden") && !this.note.hasLabel("keepCurrentHoisting") @@ -76,10 +88,6 @@ class NoteContext extends Component { await this.setHoistedNoteId(hoistedNoteId); } - - if (utils.isMobile()) { - this.triggerCommand('setActiveScreen', {screen: 'detail'}); - } } getSubContexts() { @@ -201,7 +209,7 @@ class NoteContext extends Component { } async isReadOnly() { - if (this.readOnlyTemporarilyDisabled) { + if (this.viewScope.readOnlyTemporarilyDisabled) { return false; } @@ -277,6 +285,13 @@ class NoteContext extends Component { ntxId: this.ntxId })); } + + resetViewScope() { + // view scope contains data specific to one note context and one "view". + // it is used to e.g. make read-only note temporarily editable or to hide TOC + // this is reset after navigating to a different note + this.viewScope = {}; + } } export default NoteContext; diff --git a/src/public/app/components/root_command_executor.js b/src/public/app/components/root_command_executor.js index 63b810d54..4a3383da3 100644 --- a/src/public/app/components/root_command_executor.js +++ b/src/public/app/components/root_command_executor.js @@ -10,7 +10,7 @@ import froca from "../services/froca.js"; export default class RootCommandExecutor extends Component { editReadOnlyNoteCommand() { const noteContext = appContext.tabManager.getActiveContext(); - noteContext.readOnlyTemporarilyDisabled = true; + noteContext.viewScope.readOnlyTemporarilyDisabled = true; appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext }); } diff --git a/src/public/app/widgets/buttons/edit_button.js b/src/public/app/widgets/buttons/edit_button.js index dd9a7fc3b..112b3264c 100644 --- a/src/public/app/widgets/buttons/edit_button.js +++ b/src/public/app/widgets/buttons/edit_button.js @@ -15,7 +15,7 @@ export default class EditButton extends OnClickButtonWidget { .title("Edit this note") .titlePlacement("bottom") .onClick(widget => { - this.noteContext.readOnlyTemporarilyDisabled = true; + this.noteContext.viewScope.readOnlyTemporarilyDisabled = true; appContext.triggerEvent('readOnlyTemporarilyDisabled', {noteContext: this.noteContext}); @@ -56,7 +56,7 @@ export default class EditButton extends OnClickButtonWidget { && attr.name.toLowerCase().includes("readonly") && attributeService.isAffecting(attr, this.note) )) { - this.noteContext.readOnlyTemporarilyDisabled = false; + this.noteContext.viewScope.readOnlyTemporarilyDisabled = false; this.refresh(); } diff --git a/src/public/app/widgets/containers/right_pane_container.js b/src/public/app/widgets/containers/right_pane_container.js index e991a7cff..55cd457a3 100644 --- a/src/public/app/widgets/containers/right_pane_container.js +++ b/src/public/app/widgets/containers/right_pane_container.js @@ -24,17 +24,17 @@ export default class RightPaneContainer extends FlexContainer { // we'll reevaluate the visibility based on events which are probable to cause visibility change // but these events needs to be finished and only then we check if (promise) { - promise.then(() => this.reevaluateIsEnabledCommand()); + promise.then(() => this.reEvaluateRightPaneVisibilityCommand()); } else { - this.reevaluateIsEnabledCommand(); + this.reEvaluateRightPaneVisibilityCommand(); } } return promise; } - reevaluateIsEnabledCommand() { + reEvaluateRightPaneVisibilityCommand() { const oldToggle = !this.isHiddenInt(); const newToggle = this.isEnabled(); diff --git a/src/public/app/widgets/toc.js b/src/public/app/widgets/toc.js index 4f9e97429..5e826cf1f 100644 --- a/src/public/app/widgets/toc.js +++ b/src/public/app/widgets/toc.js @@ -17,6 +17,7 @@ import attributeService from "../services/attributes.js"; import RightPanelWidget from "./right_panel_widget.js"; import options from "../services/options.js"; +import OnClickButtonWidget from "./buttons/onclick_button.js"; const TPL = `
`; -/** - * Find a heading node in the parent's children given its index. - * - * @param {Element} parent Parent node to find a headingIndex'th in. - * @param {uint} headingIndex Index for the heading - * @returns {Element|null} Heading node with the given index, null couldn't be - * found (ie malformed like nested headings, etc.) - */ -function findHeadingNodeByIndex(parent, headingIndex) { - let headingNode = null; - for (let i = 0; i < parent.childCount; ++i) { - let child = parent.getChild(i); +export default class TocWidget extends RightPanelWidget { + constructor() { + super(); - // Headings appear as flattened top level children in the CKEditor - // document named as "heading" plus the level, eg "heading2", - // "heading3", "heading2", etc. and not nested wrt the heading level. If - // a heading node is found, decrement the headingIndex until zero is - // reached - if (child.name.startsWith("heading")) { - if (headingIndex === 0) { - headingNode = child; - break; - } - headingIndex--; - } + this.closeTocButton = new CloseTocButton(); + this.child(this.closeTocButton); } - return headingNode; -} - -export default class TocWidget extends RightPanelWidget { get widgetTitle() { return "Table of Contents"; } isEnabled() { - return super.isEnabled() && this.note.type === 'text'; + return super.isEnabled() + && this.note.type === 'text' + && !this.noteContext.viewScope.tocTemporarilyHidden; } async doRenderBody() { this.$body.empty().append($(TPL)); this.$toc = this.$body.find('.toc'); + this.$body.find('.toc-widget').append(this.closeTocButton.render()); } async refreshWithNote(note) { @@ -95,7 +83,7 @@ export default class TocWidget extends RightPanelWidget { if (tocLabel?.value === 'hide') { this.toggleInt(false); - this.triggerCommand("reevaluateIsEnabled"); + this.triggerCommand("reEvaluateRightPaneVisibility"); return; } @@ -112,7 +100,7 @@ export default class TocWidget extends RightPanelWidget { || headingCount >= options.getInt('minTocHeadings') ); - this.triggerCommand("reevaluateIsEnabled"); + this.triggerCommand("reEvaluateRightPaneVisibility"); } /** @@ -252,6 +240,12 @@ export default class TocWidget extends RightPanelWidget { } } + async closeTocCommand() { + this.noteContext.viewScope.tocTemporarilyHidden = true; + await this.refresh(); + this.triggerCommand('reEvaluateRightPaneVisibility'); + } + async entitiesReloadedEvent({loadResults}) { if (loadResults.isNoteContentReloaded(this.noteId)) { await this.refresh(); @@ -263,3 +257,49 @@ export default class TocWidget extends RightPanelWidget { } } } + +/** + * Find a heading node in the parent's children given its index. + * + * @param {Element} parent Parent node to find a headingIndex'th in. + * @param {uint} headingIndex Index for the heading + * @returns {Element|null} Heading node with the given index, null couldn't be + * found (ie malformed like nested headings, etc.) + */ +function findHeadingNodeByIndex(parent, headingIndex) { + let headingNode = null; + for (let i = 0; i < parent.childCount; ++i) { + let child = parent.getChild(i); + + // Headings appear as flattened top level children in the CKEditor + // document named as "heading" plus the level, eg "heading2", + // "heading3", "heading2", etc. and not nested wrt the heading level. If + // a heading node is found, decrement the headingIndex until zero is + // reached + if (child.name.startsWith("heading")) { + if (headingIndex === 0) { + headingNode = child; + break; + } + headingIndex--; + } + } + + return headingNode; +} + +class CloseTocButton extends OnClickButtonWidget { + constructor() { + super(); + + this.icon("bx-x") + .title("Close TOC") + .titlePlacement("bottom") + .onClick((widget, e) => { + e.stopPropagation(); + + widget.triggerCommand("closeToc"); + }) + .class("icon-action close-toc"); + } +} \ No newline at end of file