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.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;

View file

@ -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 });
}

View file

@ -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();
}

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
// 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();

View file

@ -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 = `<div class="toc-widget">
<style>
@ -24,6 +25,7 @@ const TPL = `<div class="toc-widget">
padding: 10px;
contain: none;
overflow: auto;
position: relative;
}
.toc ol {
@ -41,53 +43,39 @@ const TPL = `<div class="toc-widget">
.toc li:hover {
font-weight: bold;
}
.close-toc {
position: absolute;
top: 2px;
right: 2px;
}
</style>
<span class="toc"></span>
</div>`;
/**
* 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;
}
export default class TocWidget extends RightPanelWidget {
constructor() {
super();
this.closeTocButton = new CloseTocButton();
this.child(this.closeTocButton);
}
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");
}
}