working note type and note paths widgets

This commit is contained in:
zadam 2020-01-18 19:46:30 +01:00
parent b00a9f4415
commit 493730dff6
9 changed files with 177 additions and 157 deletions

View file

@ -15,12 +15,16 @@ export default class Component {
const fun = this[name + 'Listener'];
let propagateToChildren = true;
if (typeof fun === 'function') {
await fun.call(this, data);
propagateToChildren = await fun.call(this, data) !== false;
}
for (const child of this.children) {
child.eventReceived(name, data);
if (propagateToChildren) {
for (const child of this.children) {
child.eventReceived(name, data);
}
}
}

View file

@ -130,7 +130,7 @@ class NoteDetailText extends TabAwareWidget {
}
}
async noteSwitched() {
async refresh() {
// lazy loading above can take time and tab might have been already switched to another note
if (this.tabContext.note && this.tabContext.note.type === 'text') {
this.textEditor.isReadOnly = await this.isReadOnly();

View file

@ -101,9 +101,7 @@ export default class NoteDetailWidget extends TabAwareWidget {
this.components[this.type].renderTo(this.$widget);
this.components[this.type].setTabContext(this.tabContext);
this.components[this.type].eventReceived('tabNoteSwitched', {tabId: this.tabContext.tabId});
this.components[this.type].eventReceived('setTabContext', {tabContext: this.tabContext});
}
getComponentType(disableAutoBook) {

View file

@ -0,0 +1,90 @@
import TabAwareWidget from "./tab_aware_widget.js";
import treeService from "../services/tree.js";
import treeUtils from "../services/tree_utils.js";
import linkService from "../services/link.js";
const TPL = `
<div class="dropdown hide-in-zen-mode">
<style>
.note-path-list a.current {
font-weight: bold;
}
</style>
<button class="btn btn-sm dropdown-toggle note-path-list-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="note-path-count">1 path</span>
<span class="caret"></span>
</button>
<ul class="note-path-list dropdown-menu" aria-labelledby="note-path-list-button">
</ul>
</div>`;
export default class NotePathsWidget extends TabAwareWidget {
doRender() {
this.$widget = $(TPL);
this.$notePathList = this.$widget.find(".note-path-list");
this.$notePathCount = this.$widget.find(".note-path-count");
return this.$widget;
}
async refresh() {
const {note, notePath} = this.tabContext;
if (note.noteId === 'root') {
// root doesn't have any parent, but it's still technically 1 path
this.$notePathCount.html("1 path");
this.$notePathList.empty();
await this.addPath('root', true);
}
else {
const parents = await note.getParentNotes();
this.$notePathCount.html(parents.length + " path" + (parents.length > 1 ? "s" : ""));
this.$notePathList.empty();
const pathSegments = notePath.split("/");
const activeNoteParentNoteId = pathSegments[pathSegments.length - 2]; // we know this is not root so there must be a parent
for (const parentNote of parents) {
const parentNotePath = await treeService.getSomeNotePath(parentNote);
// this is to avoid having root notes leading '/'
const notePath = parentNotePath ? (parentNotePath + '/' + note.noteId) : note.noteId;
const isCurrent = activeNoteParentNoteId === parentNote.noteId;
await this.addPath(notePath, isCurrent);
}
const cloneLink = $("<div>")
.addClass("dropdown-item")
.append(
$('<button class="btn btn-sm">')
.text('Clone note to new location...')
.on('click', () => import("../dialogs/clone_to.js").then(d => d.showDialog([note.noteId])))
);
this.$notePathList.append(cloneLink);
}
}
async addPath(notePath, isCurrent) {
const title = await treeUtils.getNotePathTitle(notePath);
const noteLink = await linkService.createNoteLink(notePath, {title});
noteLink
.addClass("no-tooltip-preview")
.addClass("dropdown-item");
if (isCurrent) {
noteLink.addClass("current");
}
this.$notePathList.append(noteLink);
}
}

View file

@ -6,6 +6,7 @@ import treeUtils from "../services/tree_utils.js";
import linkService from "../services/link.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import NoteTypeWidget from "./note_type.js";
import NotePathsWidget from "./note_paths.js";
const TPL = `
<div class="note-title-row">
@ -14,6 +15,8 @@ const TPL = `
flex-grow: 0;
flex-shrink: 0;
padding-top: 2px;
display: flex;
align-items: center;
}
.note-title {
@ -26,65 +29,51 @@ const TPL = `
}
</style>
<div style="display: flex; align-items: center;">
<div class="dropdown hide-in-zen-mode">
<button class="btn btn-sm dropdown-toggle note-path-list-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="note-path-count">1 path</span>
<input autocomplete="off" value="" class="note-title" tabindex="1">
<span class="caret"></span>
<span class="saved-indicator hide-in-zen-mode bx bx-check" title="All changes have been saved"></span>
<div class="hide-in-zen-mode" style="display: flex; align-items: center;">
<button class="btn btn-sm icon-button bx bx-play-circle render-button"
style="display: none; margin-right: 10px;"
title="Render"></button>
<button class="btn btn-sm icon-button bx bx-play-circle execute-script-button"
style="display: none; margin-right: 10px;"
title="Execute (Ctrl+Enter)"></button>
<div class="btn-group btn-group-xs">
<button type="button"
class="btn btn-sm icon-button bx bx-check-shield protect-button"
title="Protected note can be viewed and edited only after entering password">
</button>
<button type="button"
class="btn btn-sm icon-button bx bx-shield unprotect-button"
title="Not protected note can be viewed without entering password">
</button>
<ul class="note-path-list dropdown-menu" aria-labelledby="note-path-list-button">
</ul>
</div>
<input autocomplete="off" value="" class="note-title" tabindex="1">
&nbsp; &nbsp;
<span class="saved-indicator hide-in-zen-mode bx bx-check" title="All changes have been saved"></span>
<div class="hide-in-zen-mode" style="display: flex; align-items: center;">
<button class="btn btn-sm icon-button bx bx-play-circle render-button"
style="display: none; margin-right: 10px;"
title="Render"></button>
<button class="btn btn-sm icon-button bx bx-play-circle execute-script-button"
style="display: none; margin-right: 10px;"
title="Execute (Ctrl+Enter)"></button>
<div class="btn-group btn-group-xs">
<button type="button"
class="btn btn-sm icon-button bx bx-check-shield protect-button"
title="Protected note can be viewed and edited only after entering password">
<div class="note-type-actions" style="display: flex;">
<div class="dropdown note-actions">
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle">
Note actions
<span class="caret"></span>
</button>
<button type="button"
class="btn btn-sm icon-button bx bx-shield unprotect-button"
title="Not protected note can be viewed without entering password">
</button>
</div>
&nbsp; &nbsp;
<div style="display: flex;">
<!-- note type here -->
<div class="dropdown note-actions">
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle">
Note actions
<span class="caret"></span>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item show-note-revisions-button" data-bind="css: { disabled: type() == 'file' || type() == 'image' }">Revisions</a>
<a class="dropdown-item show-attributes-button"><kbd data-kb-action="ShowAttributes"></kbd> Attributes</a>
<a class="dropdown-item show-link-map-button"><kbd data-kb-action="ShowLinkMap"></kbd> Link map</a>
<a class="dropdown-item show-source-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' && type() != 'relation-map' && type() != 'search' }">
<kbd data-kb-action="ShowNoteSource"></kbd>
Note source
</a>
<a class="dropdown-item import-files-button">Import files</a>
<a class="dropdown-item export-note-button" data-bind="css: { disabled: type() != 'text' }">Export note</a>
<a class="dropdown-item print-note-button"><kbd data-kb-action="PrintActiveNote"></kbd> Print note</a>
<a class="dropdown-item show-note-info-button"><kbd data-kb-action="ShowNoteInfo"></kbd> Note info</a>
</div>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item show-note-revisions-button" data-bind="css: { disabled: type() == 'file' || type() == 'image' }">Revisions</a>
<a class="dropdown-item show-attributes-button"><kbd data-kb-action="ShowAttributes"></kbd> Attributes</a>
<a class="dropdown-item show-link-map-button"><kbd data-kb-action="ShowLinkMap"></kbd> Link map</a>
<a class="dropdown-item show-source-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' && type() != 'relation-map' && type() != 'search' }">
<kbd data-kb-action="ShowNoteSource"></kbd>
Note source
</a>
<a class="dropdown-item import-files-button">Import files</a>
<a class="dropdown-item export-note-button" data-bind="css: { disabled: type() != 'text' }">Export note</a>
<a class="dropdown-item print-note-button"><kbd data-kb-action="PrintActiveNote"></kbd> Print note</a>
<a class="dropdown-item show-note-info-button"><kbd data-kb-action="ShowNoteInfo"></kbd> Note info</a>
</div>
</div>
</div>
@ -92,19 +81,10 @@ const TPL = `
</div>`;
export default class NoteTitleWidget extends TabAwareWidget {
constructor(appContext) {
super(appContext);
this.tree = null;
}
doRender() {
this.$widget = $(TPL);
this.$noteTitle = this.$widget.find(".note-title");
this.$noteTitleRow = this.$widget.find(".note-title-row");
this.$notePathList = this.$widget.find(".note-path-list");
this.$notePathCount = this.$widget.find(".note-path-count");
this.$protectButton = this.$widget.find(".protect-button");
this.$protectButton.on('click', protectedSessionService.protectNoteAndSendToServer);
@ -114,7 +94,13 @@ export default class NoteTitleWidget extends TabAwareWidget {
this.$savedIndicator = this.$widget.find(".saved-indicator");
this.noteType = new NoteTypeWidget(this);
this.noteType = new NoteTypeWidget(this.appContext);
this.$widget.find('.note-type-actions').prepend(this.noteType.render());
this.notePaths = new NotePathsWidget(this.appContext);
this.$widget.prepend(this.notePaths.render());
this.children.push(this.noteType, this.notePaths);
this.$noteTitle.on('input', () => {
if (!this.note) {
@ -144,7 +130,7 @@ export default class NoteTitleWidget extends TabAwareWidget {
return this.$widget;
}
async activeTabChanged() {
async refresh() {
const note = this.tabContext.note;
this.$noteTitle.val(note.title);
@ -153,66 +139,6 @@ export default class NoteTitleWidget extends TabAwareWidget {
this.$protectButton.prop("disabled", note.isProtected);
this.$unprotectButton.toggleClass("active", !note.isProtected);
this.$unprotectButton.prop("disabled", !note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable());
await this.showPaths();
}
async showPaths() {
const {note, notePath} = this.tabContext;
if (note.noteId === 'root') {
// root doesn't have any parent, but it's still technically 1 path
this.$notePathCount.html("1 path");
this.$notePathList.empty();
await this.addPath('root', true);
}
else {
const parents = await note.getParentNotes();
this.$notePathCount.html(parents.length + " path" + (parents.length > 1 ? "s" : ""));
this.$notePathList.empty();
const pathSegments = notePath.split("/");
const activeNoteParentNoteId = pathSegments[pathSegments.length - 2]; // we know this is not root so there must be a parent
for (const parentNote of parents) {
const parentNotePath = await treeService.getSomeNotePath(parentNote);
// this is to avoid having root notes leading '/'
const notePath = parentNotePath ? (parentNotePath + '/' + note.noteId) : note.noteId;
const isCurrent = activeNoteParentNoteId === parentNote.noteId;
await this.addPath(notePath, isCurrent);
}
const cloneLink = $("<div>")
.addClass("dropdown-item")
.append(
$('<button class="btn btn-sm">')
.text('Clone note to new location...')
.on('click', () => import("../dialogs/clone_to.js").then(d => d.showDialog([note.noteId])))
);
this.$notePathList.append(cloneLink);
}
}
async addPath(notePath, isCurrent) {
const title = await treeUtils.getNotePathTitle(notePath);
const noteLink = await linkService.createNoteLink(notePath, {title});
noteLink
.addClass("no-tooltip-preview")
.addClass("dropdown-item");
if (isCurrent) {
noteLink.addClass("current");
}
this.$notePathList.append(noteLink);
}
noteSavedListener() {

View file

@ -38,7 +38,7 @@ export default class NoteTypeWidget extends TabAwareWidget {
doRender() {
const $widget = $(TPL);
$widget.find('.note-type').on('show.bs.dropdown', () => this.renderDropdown());
$widget.on('show.bs.dropdown', () => this.renderDropdown());
this.$noteTypeDropdown = $widget.find(".note-type-dropdown");
this.$noteTypeButton = $widget.find(".note-type-button");
@ -49,22 +49,18 @@ export default class NoteTypeWidget extends TabAwareWidget {
return $widget;
}
async update() {
async refresh() {
this.$noteTypeButton.prop("disabled",
() => ["file", "image", "search"].includes(this.ctx.note.type));
() => ["file", "image", "search"].includes(this.tabContext.note.type));
this.$noteTypeDesc.text(await this.findTypeTitle(this.ctx.note.type, this.ctx.note.mime));
this.$noteTypeDesc.text(await this.findTypeTitle(this.tabContext.note.type, this.tabContext.note.mime));
this.$executeScriptButton.toggle(this.ctx.note.mime.startsWith('application/javascript'));
this.$renderButton.toggle(this.ctx.note.type === 'render');
}
async activeTabChanged() {
this.update();
this.$executeScriptButton.toggle(this.tabContext.note.mime.startsWith('application/javascript'));
this.$renderButton.toggle(this.tabContext.note.type === 'render');
}
/** actual body is rendered lazily on note-type button click */
async renderDropdown() {
async renderDropdown() {console.log("AAAAAAAAAAAAAAAAAAA");
this.$noteTypeDropdown.empty();
for (const noteType of NOTE_TYPES.filter(nt => nt.selectable)) {
@ -79,7 +75,7 @@ export default class NoteTypeWidget extends TabAwareWidget {
this.save(noteType.type, noteType.mime);
});
if (this.ctx.note.type === noteType.type) {
if (this.tabContext.note.type === noteType.type) {
$typeLink.addClass("selected");
}
@ -105,7 +101,7 @@ export default class NoteTypeWidget extends TabAwareWidget {
this.save('code', $link.attr('data-mime-type'))
});
if (this.ctx.note.type === 'code' && this.ctx.note.mime === mimeType.mime) {
if (this.tabContext.note.type === 'code' && this.tabContext.note.mime === mimeType.mime) {
$mimeLink.addClass("selected");
this.$noteTypeDesc.text(mimeType.title);
@ -130,11 +126,11 @@ export default class NoteTypeWidget extends TabAwareWidget {
}
async save(type, mime) {
if (type !== this.ctx.note.type && !await this.confirmChangeIfContent()) {
if (type !== this.tabContext.note.type && !await this.confirmChangeIfContent()) {
return;
}
await server.put('notes/' + this.ctx.note.noteId
await server.put('notes/' + this.tabContext.note.noteId
+ '/type/' + encodeURIComponent(type)
+ '/mime/' + encodeURIComponent(mime));
@ -147,7 +143,7 @@ export default class NoteTypeWidget extends TabAwareWidget {
}
async confirmChangeIfContent() {
if (!this.ctx.getComponent().getContent()) {
if (!this.tabContext.getComponent().getContent()) {
return true;
}

View file

@ -1,11 +1,11 @@
import BasicWidget from "./basic_widget.js";
export default class TabAwareWidget extends BasicWidget {
setTabContext(tabContext) {
setTabContextListener({tabContext}) {
/** @var {TabContext} */
this.tabContext = tabContext;
this.eventReceived('tabNoteSwitched', {tabId: this.tabContext.tabId});
this.noteSwitched();
}
tabNoteSwitchedListener({tabId}) {
@ -14,10 +14,16 @@ export default class TabAwareWidget extends BasicWidget {
}
}
noteSwitched() {}
noteSwitched() {
this.refresh();
}
// to override
activeTabChanged() {}
activeTabChanged() {
this.refresh();
}
refresh() {}
activeTabChangedListener() {
this.tabContext = this.appContext.getActiveTabContext();

View file

@ -14,7 +14,9 @@ export default class TabCachingWidget extends TabAwareWidget {
this.$parent = $parent;
}
activeTabChanged() {
activeTabChangedListener() {
super.activeTabChangedListener();
for (const widget of Object.values(this.widgets)) {
widget.toggle(false);
}
@ -26,9 +28,11 @@ export default class TabCachingWidget extends TabAwareWidget {
this.children.push(widget);
widget.renderTo(this.$parent);
widget.setTabContext(this.tabContext);
widget.eventReceived('setTabContext', {tabContext: this.tabContext});
}
widget.toggle(true);
return false; // stop propagation to children
}
}

View file

@ -404,10 +404,6 @@ div.ui-tooltip {
opacity: 0.4;
}
.note-path-list a.current {
font-weight: bold;
}
button.icon-button {
font-size: 1.5em;
padding: 2px;