mirror of
https://github.com/zadam/trilium.git
synced 2024-11-11 01:23:57 +08:00
working note type and note paths widgets
This commit is contained in:
parent
b00a9f4415
commit
493730dff6
9 changed files with 177 additions and 157 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
90
src/public/javascripts/widgets/note_paths.js
Normal file
90
src/public/javascripts/widgets/note_paths.js
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
<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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue