diff --git a/package-lock.json b/package-lock.json index 6424b4f78..30a0dbed2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.51.2", + "version": "0.52.0-beta", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.51.2", + "version": "0.52.0-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/public/app/widgets/basic_widget.js b/src/public/app/widgets/basic_widget.js index be1f09e93..5c8cccdeb 100644 --- a/src/public/app/widgets/basic_widget.js +++ b/src/public/app/widgets/basic_widget.js @@ -103,10 +103,22 @@ class BasicWidget extends Component { this.$widget.toggleClass('hidden-int', !show); } + isHiddenInt() { + return this.$widget.hasClass('hidden-int'); + } + toggleExt(show) { this.$widget.toggleClass('hidden-ext', !show); } + isHiddenExt() { + return this.$widget.hasClass('hidden-ext'); + } + + canBeShown() { + return !this.isHiddenInt() && !this.isHiddenExt(); + } + isVisible() { return this.$widget.is(":visible"); } diff --git a/src/public/app/widgets/collapsible_widget.js b/src/public/app/widgets/collapsible_widget.js index bbe60345b..0d108f27e 100644 --- a/src/public/app/widgets/collapsible_widget.js +++ b/src/public/app/widgets/collapsible_widget.js @@ -35,8 +35,4 @@ export default class CollapsibleWidget extends NoteContextAwareWidget { /** for overriding */ async doRenderBody() {} - - isExpanded() { - return this.$bodyWrapper.hasClass("show"); - } } diff --git a/src/public/app/widgets/containers/right_pane_container.js b/src/public/app/widgets/containers/right_pane_container.js index a927045f3..204c48cb4 100644 --- a/src/public/app/widgets/containers/right_pane_container.js +++ b/src/public/app/widgets/containers/right_pane_container.js @@ -11,7 +11,9 @@ export default class RightPaneContainer extends FlexContainer { } isEnabled() { - return super.isEnabled() && this.children.length > 0 && !!this.children.find(ch => ch.isEnabled()); + return super.isEnabled() + && this.children.length > 0 + && !!this.children.find(ch => ch.isEnabled() && ch.canBeShown()); } handleEventInChildren(name, data) { @@ -21,13 +23,20 @@ export default class RightPaneContainer extends FlexContainer { // right pane is displayed only if some child widget is active // 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 - promise.then(() => { - this.toggleInt(this.isEnabled()); - - splitService.setupRightPaneResizer(); - }); + promise.then(() => this.reevaluateIsEnabledCommand()); } return promise; } + + reevaluateIsEnabledCommand() { + const oldToggle = !this.isHiddenInt(); + const newToggle = this.isEnabled(); + + if (oldToggle !== newToggle) { + this.toggleInt(newToggle); + + splitService.setupRightPaneResizer(); + } + } } diff --git a/src/public/app/widgets/toc.js b/src/public/app/widgets/toc.js index 5a181686f..adde8805b 100644 --- a/src/public/app/widgets/toc.js +++ b/src/public/app/widgets/toc.js @@ -26,11 +26,11 @@ const TPL = `
} .toc ol { - padding-left: 20px; + padding-left: 25px; } .toc > ol { - padding-left: 0; + padding-left: 10px; } @@ -75,7 +75,10 @@ function findHeadingElementByIndex(parent, headingIndex) { // "H" plus the level, eg "H2", "H3", "H2", etc and not nested wrt the // heading level. If a heading node is found, decrement the headingIndex // until zero is reached - if (child.tagName.match(/H\d+/) !== null) { + + console.log(child.tagName, headingIndex); + + if (child.tagName.match(/H\d+/i) !== null) { if (headingIndex === 0) { headingElement = child; break; @@ -86,6 +89,8 @@ function findHeadingElementByIndex(parent, headingIndex) { return headingElement; } +const MIN_HEADING_COUNT = 3; + export default class TocWidget extends CollapsibleWidget { get widgetTitle() { return "Table of Contents"; @@ -94,7 +99,7 @@ export default class TocWidget extends CollapsibleWidget { isEnabled() { return super.isEnabled() && this.note.type === 'text' - && !this.note.hasLabel('noTocWidget'); + && !this.note.hasLabel('noToc'); } async doRenderBody() { @@ -103,21 +108,23 @@ export default class TocWidget extends CollapsibleWidget { } async refreshWithNote(note) { - let toc = ""; + let $toc = "", headingCount = 0; // Check for type text unconditionally in case alwaysShowWidget is set if (this.note.type === 'text') { const { content } = await note.getNoteComplement(); - toc = await this.getToc(content); + ({$toc, headingCount} = await this.getToc(content)); } - this.$toc.html(toc); + this.$toc.html($toc); + this.toggleInt(headingCount >= MIN_HEADING_COUNT); + this.triggerCommand("reevaluateIsEnabled"); } /** * Builds a jquery table of contents. * * @param {String} html Note's html content - * @returns {jQuery} ordered list table of headings, nested by heading level + * @returns {$toc: jQuery, headingCount: integer} ordered list table of headings, nested by heading level * with an onclick event that will cause the document to scroll to * the desired position. */ @@ -133,7 +140,8 @@ export default class TocWidget extends CollapsibleWidget { // Note heading 2 is the first level Trilium makes available to the note let curLevel = 2; const $ols = [$toc]; - for (let m = null, headingIndex = 0; ((m = headingTagsRegex.exec(html)) !== null); ++headingIndex) { + let headingCount; + for (let m = null, headingIndex = 0; ((m = headingTagsRegex.exec(html)) !== null); headingIndex++) { // // Nest/unnest whatever necessary number of ordered lists // @@ -164,93 +172,101 @@ export default class TocWidget extends CollapsibleWidget { }).mouseout(function () { $(this).css("font-weight", "normal"); }); - $li.on("click", async () => { - // A readonly note can change state to "readonly disabled - // temporarily" (ie "edit this note" button) without any - // intervening events, do the readonly calculation at navigation - // time and not at outline creation time - // See https://github.com/zadam/trilium/issues/2828 - const isReadOnly = await this.noteContext.isReadOnly(); - - if (isReadOnly) { - const readonlyTextElement = await this.noteContext.getContentElement(); - const headingElement = findHeadingElementByIndex(readonlyTextElement, headingIndex); - - if (headingElement != null) { - headingElement.scrollIntoView(); - } - } else { - const textEditor = await this.noteContext.getTextEditor(); - - const model = textEditor.model; - const doc = model.document; - const root = doc.getRoot(); - - const headingNode = findHeadingNodeByIndex(root, headingIndex); - - // headingNode could be null if the html was malformed or - // with headings inside elements, just ignore and don't - // navigate (note that the TOC rendering and other TOC - // entries' navigation could be wrong too) - if (headingNode != null) { - // Setting the selection alone doesn't scroll to the - // caret, needs to be done explicitly and outside of - // the writer change callback so the scroll is - // guaranteed to happen after the selection is - // updated. - - // In addition, scrolling to a caret later in the - // document (ie "forward scrolls"), only scrolls - // barely enough to place the caret at the bottom of - // the screen, which is a usability issue, you would - // like the caret to be placed at the top or center - // of the screen. - - // To work around that issue, first scroll to the - // end of the document, then scroll to the desired - // point. This causes all the scrolls to be - // "backward scrolls" no matter the current caret - // position, which places the caret at the top of - // the screen. - - // XXX This could be fixed in another way by using - // the underlying CKEditor5 - // scrollViewportToShowTarget, which allows to - // provide a larger "viewportOffset", but that - // has coding complications (requires calling an - // internal CKEditor utils funcion and passing - // an HTML element, not a CKEditor node, and - // CKEditor5 doesn't seem to have a - // straightforward way to convert a node to an - // HTML element? (in CKEditor4 this was done - // with $(node.$) ) - - // Scroll to the end of the note to guarantee the - // next scroll is a backwards scroll that places the - // caret at the top of the screen - model.change(writer => { - writer.setSelection(root.getChild(root.childCount - 1), 0); - }); - textEditor.editing.view.scrollToTheSelection(); - // Backwards scroll to the heading - model.change(writer => { - writer.setSelection(headingNode, 0); - }); - textEditor.editing.view.scrollToTheSelection(); - } - } - }); + $li.on("click", () => this.jumpToHeading(headingIndex)); $ols[$ols.length - 1].append($li); + headingCount = headingIndex; } - return $toc; + return { + $toc, + headingCount + }; + } + + async jumpToHeading(headingIndex) { + // A readonly note can change state to "readonly disabled + // temporarily" (ie "edit this note" button) without any + // intervening events, do the readonly calculation at navigation + // time and not at outline creation time + // See https://github.com/zadam/trilium/issues/2828 + const isReadOnly = await this.noteContext.isReadOnly(); + + if (isReadOnly) { + const $readonlyTextContent = await this.noteContext.getContentElement(); + + const headingElement = findHeadingElementByIndex($readonlyTextContent[0], headingIndex); + + if (headingElement != null) { + headingElement.scrollIntoView(); + } + } else { + const textEditor = await this.noteContext.getTextEditor(); + + const model = textEditor.model; + const doc = model.document; + const root = doc.getRoot(); + + const headingNode = findHeadingNodeByIndex(root, headingIndex); + + // headingNode could be null if the html was malformed or + // with headings inside elements, just ignore and don't + // navigate (note that the TOC rendering and other TOC + // entries' navigation could be wrong too) + if (headingNode != null) { + // Setting the selection alone doesn't scroll to the + // caret, needs to be done explicitly and outside of + // the writer change callback so the scroll is + // guaranteed to happen after the selection is + // updated. + + // In addition, scrolling to a caret later in the + // document (ie "forward scrolls"), only scrolls + // barely enough to place the caret at the bottom of + // the screen, which is a usability issue, you would + // like the caret to be placed at the top or center + // of the screen. + + // To work around that issue, first scroll to the + // end of the document, then scroll to the desired + // point. This causes all the scrolls to be + // "backward scrolls" no matter the current caret + // position, which places the caret at the top of + // the screen. + + // XXX This could be fixed in another way by using + // the underlying CKEditor5 + // scrollViewportToShowTarget, which allows to + // provide a larger "viewportOffset", but that + // has coding complications (requires calling an + // internal CKEditor utils funcion and passing + // an HTML element, not a CKEditor node, and + // CKEditor5 doesn't seem to have a + // straightforward way to convert a node to an + // HTML element? (in CKEditor4 this was done + // with $(node.$) ) + + // Scroll to the end of the note to guarantee the + // next scroll is a backwards scroll that places the + // caret at the top of the screen + model.change(writer => { + writer.setSelection(root.getChild(root.childCount - 1), 0); + }); + textEditor.editing.view.scrollToTheSelection(); + // Backwards scroll to the heading + model.change(writer => { + writer.setSelection(headingNode, 0); + }); + textEditor.editing.view.scrollToTheSelection(); + } + } } async entitiesReloadedEvent({loadResults}) { - if (loadResults.isNoteContentReloaded(this.noteId) - || loadResults.getAttributes().find(attr => attr.type === 'label' - && attr.name.toLowerCase().includes('readonly') - && attributeService.isAffecting(attr, this.note))) { + if (loadResults.isNoteContentReloaded(this.noteId)) { + await this.refresh(); + } else if (loadResults.getAttributes().find(attr => attr.type === 'label' + && (attr.name.toLowerCase().includes('readonly') || attr.name === 'noToc') + && attributeService.isAffecting(attr, this.note))) { await this.refresh(); } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 647373b6d..7797f4c3a 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -241,8 +241,8 @@ body .CodeMirror { background-color: #eeeeee } -.CodeMirror pre.CodeMirror-placeholder { - color: #999 !important; +.CodeMirror pre.CodeMirror-placeholder { + color: #999 !important; } #sql-console-query { @@ -943,7 +943,6 @@ input { border: 0; height: 100%; overflow: auto; - max-height: 300px; } #right-pane .card-body ul {