mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
start of the refactoring to widget system
This commit is contained in:
parent
51c3f98dde
commit
9e031dcd60
@ -34,6 +34,7 @@ import keyboardActionService from "./services/keyboard_actions.js";
|
|||||||
import splitService from "./services/split.js";
|
import splitService from "./services/split.js";
|
||||||
import optionService from "./services/options.js";
|
import optionService from "./services/options.js";
|
||||||
import noteContentRenderer from "./services/note_content_renderer.js";
|
import noteContentRenderer from "./services/note_content_renderer.js";
|
||||||
|
import AppContext from "./services/app_context.js";
|
||||||
|
|
||||||
window.glob.isDesktop = utils.isDesktop;
|
window.glob.isDesktop = utils.isDesktop;
|
||||||
window.glob.isMobile = utils.isMobile;
|
window.glob.isMobile = utils.isMobile;
|
||||||
@ -117,10 +118,6 @@ $("#logout-button").on('click', () => {
|
|||||||
$logoutForm.trigger('submit');
|
$logoutForm.trigger('submit');
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#tree").on("click", ".unhoist-button", hoistedNoteService.unhoist);
|
|
||||||
|
|
||||||
$("#tree").on("click", ".refresh-search-button", searchNotesService.refreshSearch);
|
|
||||||
|
|
||||||
$("body").on("click", "a.external", function () {
|
$("body").on("click", "a.external", function () {
|
||||||
window.open($(this).attr("href"), '_blank');
|
window.open($(this).attr("href"), '_blank');
|
||||||
});
|
});
|
||||||
@ -186,7 +183,8 @@ macInit.init();
|
|||||||
|
|
||||||
searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash
|
searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash
|
||||||
|
|
||||||
treeService.showTree();
|
const appContext = new AppContext();
|
||||||
|
appContext.showWidgets();
|
||||||
|
|
||||||
entrypoints.registerEntrypoints();
|
entrypoints.registerEntrypoints();
|
||||||
|
|
||||||
|
33
src/public/javascripts/services/app_context.js
Normal file
33
src/public/javascripts/services/app_context.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import GlobalButtonsWidget from "../widgets/global_buttons.js";
|
||||||
|
import SearchBoxWidget from "../widgets/search_box.js";
|
||||||
|
import SearchResultsWidget from "../widgets/search_results.js";
|
||||||
|
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||||
|
|
||||||
|
export default class AppContext {
|
||||||
|
constructor() {
|
||||||
|
this.widgets = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger(name, data) {
|
||||||
|
for (const widget of this.widgets) {
|
||||||
|
widget.eventReceived(name, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showWidgets() {
|
||||||
|
const $leftPane = $("#left-pane");
|
||||||
|
|
||||||
|
this.widgets = [
|
||||||
|
new GlobalButtonsWidget(this),
|
||||||
|
new SearchBoxWidget(this),
|
||||||
|
new SearchResultsWidget(this),
|
||||||
|
new NoteTreeWidget(this)
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const widget of this.widgets) {
|
||||||
|
const $widget = widget.render();
|
||||||
|
|
||||||
|
$leftPane.append($widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,6 @@ function registerEntrypoints() {
|
|||||||
$("#enter-protected-session-button").on('click', protectedSessionService.enterProtectedSession);
|
$("#enter-protected-session-button").on('click', protectedSessionService.enterProtectedSession);
|
||||||
$("#leave-protected-session-button").on('click', protectedSessionService.leaveProtectedSession);
|
$("#leave-protected-session-button").on('click', protectedSessionService.leaveProtectedSession);
|
||||||
|
|
||||||
$("#toggle-search-button").on('click', searchNotesService.toggleSearch);
|
|
||||||
keyboardActionService.setGlobalActionHandler('SearchNotes', searchNotesService.toggleSearch);
|
keyboardActionService.setGlobalActionHandler('SearchNotes', searchNotesService.toggleSearch);
|
||||||
|
|
||||||
const $noteTabContainer = $("#note-tab-container");
|
const $noteTabContainer = $("#note-tab-container");
|
||||||
|
@ -3,136 +3,6 @@ import treeCache from "./tree_cache.js";
|
|||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import toastService from "./toast.js";
|
import toastService from "./toast.js";
|
||||||
|
|
||||||
const $searchInput = $("input[name='search-text']");
|
|
||||||
const $resetSearchButton = $("#reset-search-button");
|
|
||||||
const $doSearchButton = $("#do-search-button");
|
|
||||||
const $saveSearchButton = $("#save-search-button");
|
|
||||||
const $searchBox = $("#search-box");
|
|
||||||
const $searchResults = $("#search-results");
|
|
||||||
const $searchResultsInner = $("#search-results-inner");
|
|
||||||
const $closeSearchButton = $("#close-search-button");
|
|
||||||
|
|
||||||
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<=2000</code> - numerical comparison (also >, >=, <).</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>`;
|
|
||||||
|
|
||||||
function showSearch() {
|
|
||||||
$searchBox.slideDown();
|
|
||||||
|
|
||||||
$searchBox.tooltip({
|
|
||||||
trigger: 'focus',
|
|
||||||
html: true,
|
|
||||||
title: helpText,
|
|
||||||
placement: 'right',
|
|
||||||
delay: {
|
|
||||||
show: 500, // necessary because sliding out may cause wrong position
|
|
||||||
hide: 200
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$searchInput.trigger('focus');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideSearch() {
|
|
||||||
resetSearch();
|
|
||||||
|
|
||||||
$searchResults.hide();
|
|
||||||
$searchBox.slideUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSearch() {
|
|
||||||
if ($searchBox.is(":hidden")) {
|
|
||||||
showSearch();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hideSearch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetSearch() {
|
|
||||||
$searchInput.val("");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doSearch(searchText) {
|
|
||||||
if (searchText) {
|
|
||||||
$searchInput.val(searchText);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
searchText = $searchInput.val();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchText.trim().length === 0) {
|
|
||||||
toastService.showMessage("Please enter search criteria first.");
|
|
||||||
|
|
||||||
$searchInput.trigger('focus');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$searchBox.tooltip("hide");
|
|
||||||
|
|
||||||
const response = await server.get('search/' + encodeURIComponent(searchText));
|
|
||||||
|
|
||||||
if (!response.success) {
|
|
||||||
toastService.showError("Search failed.", 3000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$searchResultsInner.empty();
|
|
||||||
$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);
|
|
||||||
|
|
||||||
$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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveSearch() {
|
|
||||||
const searchString = $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 })
|
|
||||||
});
|
|
||||||
|
|
||||||
resetSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshSearch() {
|
async function refreshSearch() {
|
||||||
const activeNode = treeService.getActiveNode();
|
const activeNode = treeService.getActiveNode();
|
||||||
|
|
||||||
@ -157,32 +27,12 @@ function init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$searchInput.on('keyup',e => {
|
|
||||||
const searchText = $searchInput.val();
|
|
||||||
|
|
||||||
if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(searchText) === "") {
|
|
||||||
$resetSearchButton.trigger('click');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e && e.which === $.ui.keyCode.ENTER) {
|
|
||||||
doSearch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$doSearchButton.on('click', () => doSearch()); // keep long form because of argument
|
|
||||||
$resetSearchButton.on('click', resetSearch);
|
|
||||||
|
|
||||||
$saveSearchButton.on('click', saveSearch);
|
|
||||||
|
|
||||||
$closeSearchButton.on('click', hideSearch);
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
toggleSearch,
|
// toggleSearch,
|
||||||
resetSearch,
|
// resetSearch,
|
||||||
showSearch,
|
// showSearch,
|
||||||
|
// doSearch,
|
||||||
refreshSearch,
|
refreshSearch,
|
||||||
doSearch,
|
|
||||||
init,
|
init,
|
||||||
searchInSubtree,
|
searchInSubtree,
|
||||||
getHelpText: () => helpText
|
getHelpText: () => helpText
|
||||||
|
@ -18,11 +18,6 @@ import keyboardActionService from "./keyboard_actions.js";
|
|||||||
|
|
||||||
let tree;
|
let tree;
|
||||||
|
|
||||||
const $tree = $("#tree");
|
|
||||||
const $createTopLevelNoteButton = $("#create-top-level-note-button");
|
|
||||||
const $collapseTreeButton = $("#collapse-tree-button");
|
|
||||||
const $scrollToActiveNoteButton = $("#scroll-to-active-note-button");
|
|
||||||
|
|
||||||
let setFrontendAsLoaded;
|
let setFrontendAsLoaded;
|
||||||
const frontendLoaded = new Promise(resolve => { setFrontendAsLoaded = resolve; });
|
const frontendLoaded = new Promise(resolve => { setFrontendAsLoaded = resolve; });
|
||||||
|
|
||||||
@ -429,7 +424,7 @@ async function treeInitialized() {
|
|||||||
setFrontendAsLoaded();
|
setFrontendAsLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initFancyTree(treeData) {
|
async function initFancyTree($tree, treeData) {
|
||||||
utils.assertArguments(treeData);
|
utils.assertArguments(treeData);
|
||||||
|
|
||||||
$tree.fancytree({
|
$tree.fancytree({
|
||||||
@ -750,10 +745,10 @@ async function sortAlphabetically(noteId) {
|
|||||||
await reload();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showTree() {
|
async function showTree($tree) {
|
||||||
const treeData = await loadTreeData();
|
const treeData = await loadTreeData();
|
||||||
|
|
||||||
await initFancyTree(treeData);
|
await initFancyTree($tree, treeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.subscribeToMessages(message => {
|
ws.subscribeToMessages(message => {
|
||||||
@ -882,22 +877,6 @@ $(window).bind('hashchange', async function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// fancytree doesn't support middle click so this is a way to support it
|
|
||||||
$tree.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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function duplicateNote(noteId, parentNoteId) {
|
async function duplicateNote(noteId, parentNoteId) {
|
||||||
const {note} = await server.post(`notes/${noteId}/duplicate/${parentNoteId}`);
|
const {note} = await server.post(`notes/${noteId}/duplicate/${parentNoteId}`);
|
||||||
|
|
||||||
@ -913,12 +892,6 @@ function getNodeByKey(key) {
|
|||||||
return tree.getNodeByKey(key);
|
return tree.getNodeByKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardActionService.setGlobalActionHandler('CollapseTree', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument
|
|
||||||
$collapseTreeButton.on('click', () => collapseTree());
|
|
||||||
|
|
||||||
$createTopLevelNoteButton.on('click', createNewTopLevelNote);
|
|
||||||
$scrollToActiveNoteButton.on('click', scrollToActiveNote);
|
|
||||||
|
|
||||||
frontendLoaded.then(bundle.executeStartupBundles);
|
frontendLoaded.then(bundle.executeStartupBundles);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -948,6 +921,7 @@ export default {
|
|||||||
getSomeNotePath,
|
getSomeNotePath,
|
||||||
focusTree,
|
focusTree,
|
||||||
scrollToActiveNote,
|
scrollToActiveNote,
|
||||||
|
createNewTopLevelNote,
|
||||||
duplicateNote,
|
duplicateNote,
|
||||||
getNodeByKey
|
getNodeByKey
|
||||||
};
|
};
|
41
src/public/javascripts/widgets/basic_widget.js
Normal file
41
src/public/javascripts/widgets/basic_widget.js
Normal 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;
|
42
src/public/javascripts/widgets/global_buttons.js
Normal file
42
src/public/javascripts/widgets/global_buttons.js
Normal 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;
|
65
src/public/javascripts/widgets/note_tree.js
Normal file
65
src/public/javascripts/widgets/note_tree.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
174
src/public/javascripts/widgets/search_box.js
Normal file
174
src/public/javascripts/widgets/search_box.js
Normal 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<=2000</code> - numerical comparison (also >, >=, <).</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("");
|
||||||
|
}
|
||||||
|
}
|
68
src/public/javascripts/widgets/search_results.js
Normal file
68
src/public/javascripts/widgets/search_results.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -45,21 +45,6 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search-box {
|
|
||||||
display: none;
|
|
||||||
padding: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tree {
|
|
||||||
overflow: auto;
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
flex-basis: 60%;
|
|
||||||
font-family: var(--tree-font-family);
|
|
||||||
font-size: var(--tree-font-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#left-pane {
|
#left-pane {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -95,15 +80,6 @@ body {
|
|||||||
margin: 0 15px 0 5px;
|
margin: 0 15px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
@ -264,25 +264,6 @@ div.ui-tooltip {
|
|||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-text {
|
|
||||||
border: 1px solid var(--main-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* .search-inactive is added to search window <webview> when the window
|
* .search-inactive is added to search window <webview> when the window
|
||||||
* is inactive.
|
* is inactive.
|
||||||
|
@ -138,55 +138,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; flex-grow: 1; flex-shrink: 1; min-height: 0;">
|
<div style="display: flex; flex-grow: 1; flex-shrink: 1; min-height: 0;">
|
||||||
<div id="left-pane" class="hide-in-zen-mode">
|
<div id="left-pane" class="hide-in-zen-mode"></div>
|
||||||
<div id="global-buttons">
|
|
||||||
<a id="create-top-level-note-button" title="Create new top level note" class="icon-action bx bx-folder-plus"></a>
|
|
||||||
|
|
||||||
<a id="collapse-tree-button" title="Collapse note tree" data-kb-action="CollapseTree" class="icon-action bx bx-layer-minus"></a>
|
|
||||||
|
|
||||||
<a id="scroll-to-active-note-button" title="Scroll to active note" data-kb-action="ScrollToActiveNote" class="icon-action bx bx-crosshair"></a>
|
|
||||||
|
|
||||||
<a id="toggle-search-button" title="Search in notes" data-kb-action="SearchNotes" class="icon-action bx bx-search"></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="search-box">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="input-group">
|
|
||||||
<input name="search-text" id="search-text" class="form-control"
|
|
||||||
placeholder="Search text, labels" autocomplete="off">
|
|
||||||
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button id="do-search-button" class="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 id="save-search-button" class="btn btn-sm"
|
|
||||||
title="This will create new saved search note under active note.">
|
|
||||||
<span class="bx bx-save"></span> Save search</button>
|
|
||||||
|
|
||||||
<button id="close-search-button" class="btn btn-sm"><span class="bx bx-x"></span> Close search</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="search-results">
|
|
||||||
<strong>Search results:</strong>
|
|
||||||
|
|
||||||
<ul id="search-results-inner"></ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="tree"></div>
|
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% include center.ejs %>
|
<% include center.ejs %>
|
||||||
|
|
||||||
<% include sidebar.ejs %>
|
<% include sidebar.ejs %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
||||||
|
|
||||||
<% include dialogs/about.ejs %>
|
<% include dialogs/about.ejs %>
|
||||||
<% include dialogs/add_link.ejs %>
|
<% include dialogs/add_link.ejs %>
|
||||||
<% include dialogs/attributes.ejs %>
|
<% include dialogs/attributes.ejs %>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user