add a button to temporarily hide TOC, closes #3555

This commit is contained in:
zadam 2023-01-24 16:24:51 +01:00
parent 64e7150765
commit a7b103e07a
5 changed files with 98 additions and 43 deletions

View File

@ -15,6 +15,8 @@ class NoteContext extends Component {
this.ntxId = ntxId || utils.randomString(4); this.ntxId = ntxId || utils.randomString(4);
this.hoistedNoteId = hoistedNoteId; this.hoistedNoteId = hoistedNoteId;
this.mainNtxId = mainNtxId; this.mainNtxId = mainNtxId;
this.resetViewScope();
} }
setEmpty() { setEmpty() {
@ -27,6 +29,8 @@ class NoteContext extends Component {
noteContext: this, noteContext: this,
notePath: this.notePath notePath: this.notePath
}); });
this.resetViewScope();
} }
isEmpty() { isEmpty() {
@ -47,7 +51,7 @@ class NoteContext extends Component {
this.notePath = resolvedNotePath; this.notePath = resolvedNotePath;
({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath)); ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
this.readOnlyTemporarilyDisabled = false; this.resetViewScope();
this.saveToRecentNotes(resolvedNotePath); 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' if (this.hoistedNoteId === 'root'
&& this.notePath.startsWith("root/_hidden") && this.notePath.startsWith("root/_hidden")
&& !this.note.hasLabel("keepCurrentHoisting") && !this.note.hasLabel("keepCurrentHoisting")
@ -76,10 +88,6 @@ class NoteContext extends Component {
await this.setHoistedNoteId(hoistedNoteId); await this.setHoistedNoteId(hoistedNoteId);
} }
if (utils.isMobile()) {
this.triggerCommand('setActiveScreen', {screen: 'detail'});
}
} }
getSubContexts() { getSubContexts() {
@ -201,7 +209,7 @@ class NoteContext extends Component {
} }
async isReadOnly() { async isReadOnly() {
if (this.readOnlyTemporarilyDisabled) { if (this.viewScope.readOnlyTemporarilyDisabled) {
return false; return false;
} }
@ -277,6 +285,13 @@ class NoteContext extends Component {
ntxId: this.ntxId 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; export default NoteContext;

View File

@ -10,7 +10,7 @@ import froca from "../services/froca.js";
export default class RootCommandExecutor extends Component { export default class RootCommandExecutor extends Component {
editReadOnlyNoteCommand() { editReadOnlyNoteCommand() {
const noteContext = appContext.tabManager.getActiveContext(); const noteContext = appContext.tabManager.getActiveContext();
noteContext.readOnlyTemporarilyDisabled = true; noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext }); appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext });
} }

View File

@ -15,7 +15,7 @@ export default class EditButton extends OnClickButtonWidget {
.title("Edit this note") .title("Edit this note")
.titlePlacement("bottom") .titlePlacement("bottom")
.onClick(widget => { .onClick(widget => {
this.noteContext.readOnlyTemporarilyDisabled = true; this.noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent('readOnlyTemporarilyDisabled', {noteContext: this.noteContext}); appContext.triggerEvent('readOnlyTemporarilyDisabled', {noteContext: this.noteContext});
@ -56,7 +56,7 @@ export default class EditButton extends OnClickButtonWidget {
&& attr.name.toLowerCase().includes("readonly") && attr.name.toLowerCase().includes("readonly")
&& attributeService.isAffecting(attr, this.note) && attributeService.isAffecting(attr, this.note)
)) { )) {
this.noteContext.readOnlyTemporarilyDisabled = false; this.noteContext.viewScope.readOnlyTemporarilyDisabled = false;
this.refresh(); this.refresh();
} }

View File

@ -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 // 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 // but these events needs to be finished and only then we check
if (promise) { if (promise) {
promise.then(() => this.reevaluateIsEnabledCommand()); promise.then(() => this.reEvaluateRightPaneVisibilityCommand());
} }
else { else {
this.reevaluateIsEnabledCommand(); this.reEvaluateRightPaneVisibilityCommand();
} }
} }
return promise; return promise;
} }
reevaluateIsEnabledCommand() { reEvaluateRightPaneVisibilityCommand() {
const oldToggle = !this.isHiddenInt(); const oldToggle = !this.isHiddenInt();
const newToggle = this.isEnabled(); const newToggle = this.isEnabled();

View File

@ -17,6 +17,7 @@
import attributeService from "../services/attributes.js"; import attributeService from "../services/attributes.js";
import RightPanelWidget from "./right_panel_widget.js"; import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js"; import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js";
const TPL = `<div class="toc-widget"> const TPL = `<div class="toc-widget">
<style> <style>
@ -24,6 +25,7 @@ const TPL = `<div class="toc-widget">
padding: 10px; padding: 10px;
contain: none; contain: none;
overflow: auto; overflow: auto;
position: relative;
} }
.toc ol { .toc ol {
@ -41,53 +43,39 @@ const TPL = `<div class="toc-widget">
.toc li:hover { .toc li:hover {
font-weight: bold; font-weight: bold;
} }
.close-toc {
position: absolute;
top: 2px;
right: 2px;
}
</style> </style>
<span class="toc"></span> <span class="toc"></span>
</div>`; </div>`;
/** export default class TocWidget extends RightPanelWidget {
* Find a heading node in the parent's children given its index. constructor() {
* super();
* @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 this.closeTocButton = new CloseTocButton();
// document named as "heading" plus the level, eg "heading2", this.child(this.closeTocButton);
// "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;
}
export default class TocWidget extends RightPanelWidget {
get widgetTitle() { get widgetTitle() {
return "Table of Contents"; return "Table of Contents";
} }
isEnabled() { isEnabled() {
return super.isEnabled() && this.note.type === 'text'; return super.isEnabled()
&& this.note.type === 'text'
&& !this.noteContext.viewScope.tocTemporarilyHidden;
} }
async doRenderBody() { async doRenderBody() {
this.$body.empty().append($(TPL)); this.$body.empty().append($(TPL));
this.$toc = this.$body.find('.toc'); this.$toc = this.$body.find('.toc');
this.$body.find('.toc-widget').append(this.closeTocButton.render());
} }
async refreshWithNote(note) { async refreshWithNote(note) {
@ -95,7 +83,7 @@ export default class TocWidget extends RightPanelWidget {
if (tocLabel?.value === 'hide') { if (tocLabel?.value === 'hide') {
this.toggleInt(false); this.toggleInt(false);
this.triggerCommand("reevaluateIsEnabled"); this.triggerCommand("reEvaluateRightPaneVisibility");
return; return;
} }
@ -112,7 +100,7 @@ export default class TocWidget extends RightPanelWidget {
|| headingCount >= options.getInt('minTocHeadings') || 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}) { async entitiesReloadedEvent({loadResults}) {
if (loadResults.isNoteContentReloaded(this.noteId)) { if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh(); 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");
}
}