start of the refactoring to widget system

This commit is contained in:
zadam
2020-01-11 21:19:56 +01:00
parent 51c3f98dde
commit 9e031dcd60
13 changed files with 437 additions and 276 deletions

View File

@@ -0,0 +1,41 @@
class BasicWidget {
/**
* @param {AppContext} appContext
*/
constructor(appContext) {
this.appContext = appContext;
this.widgetId = `widget-${this.constructor.name}`;
}
render() {
const $widget = $('<div>').attr('id', this.widgetId);
// actual rendering is async
this.doRender($widget);
return $widget;
}
/**
* for overriding
*
* @param {JQuery} $widget
*/
async doRender($widget) {}
eventReceived(name, data) {
const fun = this[name + 'Listener'];
if (typeof fun === 'function') {
fun.call(this, data);
}
}
trigger(name, data) {
this.appContext.trigger(name, data);
}
cleanup() {}
}
export default BasicWidget;

View File

@@ -0,0 +1,42 @@
import BasicWidget from "./basic_widget.js";
const WIDGET_TPL = `
<style>
.global-buttons {
display: flex;
justify-content: space-around;
padding: 3px 0 3px 0;
border: 1px solid var(--main-border-color);
border-radius: 7px;
margin: 3px 5px 5px 5px;
}
</style>
<div class="global-buttons">
<a title="Create new top level note" class="create-top-level-note-button icon-action bx bx-folder-plus"></a>
<a title="Collapse note tree" data-kb-action="CollapseTree" class="collapse-tree-button icon-action bx bx-layer-minus"></a>
<a title="Scroll to active note" data-kb-action="ScrollToActiveNote" class="scroll-to-active-note-button icon-action bx bx-crosshair"></a>
<a title="Search in notes" data-kb-action="SearchNotes" class="toggle-search-button icon-action bx bx-search"></a>
</div>
`;
class GlobalButtonsWidget extends BasicWidget {
async doRender($widget) {
$widget.append($(WIDGET_TPL));
const $createTopLevelNoteButton = $widget.find(".create-top-level-note-button");
const $collapseTreeButton = $widget.find(".collapse-tree-button");
const $scrollToActiveNoteButton = $widget.find(".scroll-to-active-note-button");
const $toggleSearchButton = $widget.find(".toggle-search-button");
$createTopLevelNoteButton.on('click', () => this.trigger('createTopLevelNote'));
$collapseTreeButton.on('click', () => this.trigger('collapseTree'));
$scrollToActiveNoteButton.on('click', () => this.trigger('scrollToActiveNote'));
$toggleSearchButton.on('click', () => this.trigger('toggleSearch'));
}
}
export default GlobalButtonsWidget;

View File

@@ -0,0 +1,65 @@
import BasicWidget from "./basic_widget.js";
import hoistedNoteService from "../services/hoisted_note.js";
import searchNotesService from "../services/search_notes.js";
import keyboardActionService from "../services/keyboard_actions.js";
import treeService from "../services/tree.js";
import treeUtils from "../services/tree_utils.js";
import noteDetailService from "../services/note_detail.js";
const TPL = `
<style>
#tree {
overflow: auto;
flex-grow: 1;
flex-shrink: 1;
flex-basis: 60%;
font-family: var(--tree-font-family);
font-size: var(--tree-font-size);
}
</style>
<div id="tree"></div>
`;
export default class NoteTreeWidget extends BasicWidget {
async doRender($widget) {
$widget.append($(TPL));
const $tree = $widget.find('#tree');
await treeService.showTree($tree);
$tree.on("click", ".unhoist-button", hoistedNoteService.unhoist);
$tree.on("click", ".refresh-search-button", searchNotesService.refreshSearch);
keyboardActionService.setGlobalActionHandler('CollapseTree', () => treeService.collapseTree()); // don't use shortened form since collapseTree() accepts argument
// fancytree doesn't support middle click so this is a way to support it
$widget.on('mousedown', '.fancytree-title', e => {
if (e.which === 2) {
const node = $.ui.fancytree.getNode(e);
treeUtils.getNotePath(node).then(notePath => {
if (notePath) {
noteDetailService.openInTab(notePath, false);
}
});
e.stopPropagation();
e.preventDefault();
}
});
}
createTopLevelNoteListener() {
treeService.createNewTopLevelNote();
}
collapseTreeListener() {
treeService.collapseTree();
}
scrollToActiveNoteListener() {
treeService.scrollToActiveNote();
}
}

View File

@@ -0,0 +1,174 @@
import BasicWidget from "./basic_widget.js";
import treeService from "../services/tree.js";
import treeCache from "../services/tree_cache.js";
import toastService from "../services/toast.js";
const helpText = `
<strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="Search">complete help on search</button>
<p>
<ul>
<li>Just enter any text for full text search</li>
<li><code>@abc</code> - returns notes with label abc</li>
<li><code>@year=2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li>
<li><code>@rock @pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li>
<li><code>@rock or @pop</code> - only one of the labels must be present</li>
<li><code>@year&lt;=2000</code> - numerical comparison (also &gt;, &gt;=, &lt;).</li>
<li><code>@dateCreated>=MONTH-1</code> - notes created in the last month</li>
<li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li>
</ul>
</p>`;
const TPL = `
<style>
.search-box {
display: none;
padding: 10px;
margin-top: 10px;
}
.search-text {
border: 1px solid var(--main-border-color);
}
</style>
<div class="search-box">
<div class="form-group">
<div class="input-group">
<input name="search-text" class="search-text form-control"
placeholder="Search text, labels" autocomplete="off">
<div class="input-group-append">
<button class="do-search-button btn btn-sm icon-button bx bx-search" title="Search (enter)"></button>
</div>
</div>
</div>
<div style="display: flex; align-items: center; justify-content: space-evenly; flex-wrap: wrap;">
<button class="save-search-button btn btn-sm"
title="This will create new saved search note under active note.">
<span class="bx bx-save"></span> Save search
</button>
<button class="close-search-button btn btn-sm">
<span class="bx bx-x"></span> Close search
</button>
</div>
</div>`;
export default class SearchBoxWidget extends BasicWidget {
async doRender($widget) {
$widget.append($(TPL));
this.$searchBox = $widget.find(".search-box");
this.$closeSearchButton = $widget.find(".close-search-button");
this.$searchInput = $widget.find("input[name='search-text']");
this.$resetSearchButton = $widget.find(".reset-search-button");
this.$doSearchButton = $widget.find(".do-search-button");
this.$saveSearchButton = $widget.find(".save-search-button");
this.$searchInput.on('keyup',e => {
const searchText = this.$searchInput.val();
if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(searchText) === "") {
this.$resetSearchButton.trigger('click');
return;
}
if (e && e.which === $.ui.keyCode.ENTER) {
this.doSearch();
}
});
this.$doSearchButton.on('click', () => this.doSearch()); // keep long form because of argument
this.$resetSearchButton.on('click', () => this.resetSearchListener());
this.$saveSearchButton.on('click', () => this.saveSearch());
this.$closeSearchButton.on('click', () => this.hideSearchListener());
}
doSearch(searchText) {
if (searchText) {
this.$searchInput.val(searchText);
}
else {
searchText = this.$searchInput.val();
}
if (searchText.trim().length === 0) {
toastService.showMessage("Please enter search criteria first.");
this.$searchInput.trigger('focus');
return;
}
this.trigger('searchForResults', {
searchText: this.$searchInput.val()
});
this.$searchBox.tooltip("hide");
}
async saveSearch() {
const searchString = this.$searchInput.val().trim();
if (searchString.length === 0) {
alert("Write some search criteria first so there is something to save.");
return;
}
let activeNode = treeService.getActiveNode();
const parentNote = await treeCache.getNote(activeNode.data.noteId);
if (parentNote.type === 'search') {
activeNode = activeNode.getParent();
}
await treeService.createNote(activeNode, activeNode.data.noteId, 'into', {
type: "search",
mime: "application/json",
title: searchString,
content: JSON.stringify({ searchString: searchString })
});
this.resetSearchListener();
}
showSearchListener() {
this.$searchBox.slideDown();
this.$searchBox.tooltip({
trigger: 'focus',
html: true,
title: helpText,
placement: 'right',
delay: {
show: 500, // necessary because sliding out may cause wrong position
hide: 200
}
});
this.$searchInput.trigger('focus');
}
hideSearchListener() {
this.resetSearchListener();
this.$searchBox.slideUp();
}
toggleSearchListener() {
if (this.$searchBox.is(":hidden")) {
this.showSearchListener();
}
else {
this.hideSearchListener();
this.trigger('hideSearchResults');
}
}
resetSearchListener() {
this.$searchInput.val("");
}
}

View File

@@ -0,0 +1,68 @@
import BasicWidget from "./basic_widget.js";
import toastService from "../services/toast.js";
import server from "../services/server.js";
const TPL = `
<style>
.search-results {
padding: 0 5px 5px 15px;
flex-basis: 40%;
flex-grow: 1;
flex-shrink: 1;
margin-top: 10px;
display: none;
overflow: auto;
border-bottom: 2px solid var(--main-border-color);
}
.search-results ul {
padding: 5px 5px 5px 15px;
}
</style>
<div class="search-results">
<strong>Search results:</strong>
<ul class="search-results-inner"></ul>
</div>
`;
export default class SearchResultsWidget extends BasicWidget {
async doRender($widget) {
$widget.append($(TPL));
this.$searchResults = $widget.find(".search-results");
this.$searchResultsInner = $widget.find(".search-results-inner");
}
async searchForResultsListener({searchText}) {
const response = await server.get('search/' + encodeURIComponent(searchText));
if (!response.success) {
toastService.showError("Search failed.", 3000);
return;
}
this.$searchResultsInner.empty();
this.$searchResults.show();
for (const result of response.results) {
const link = $('<a>', {
href: 'javascript:',
text: result.title
}).attr('data-action', 'note').attr('data-note-path', result.path);
const $result = $('<li>').append(link);
this.$searchResultsInner.append($result);
}
// have at least some feedback which is good especially in situations
// when the result list does not change with a query
toastService.showMessage("Search finished successfully.");
}
hideSearchResultsListener() {
this.$searchResults.hide();
}
}