mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
redesign of search input. Saved search is now saved under active note and doesn't need page reload
This commit is contained in:
parent
8fb6edad67
commit
89b8e2bb08
@ -123,7 +123,10 @@ if (utils.isElectron()) {
|
|||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const parentNode = treeService.getActiveNode();
|
const parentNode = treeService.getActiveNode();
|
||||||
|
|
||||||
const {note} = await treeService.createNote(parentNode, parentNode.data.noteId, 'into', "text", parentNode.data.isProtected);
|
const {note} = await treeService.createNote(parentNode, parentNode.data.noteId, 'into', {
|
||||||
|
type: "text",
|
||||||
|
isProtected: parentNode.data.isProtected
|
||||||
|
});
|
||||||
|
|
||||||
await treeService.activateNote(note.noteId);
|
await treeService.activateNote(note.noteId);
|
||||||
|
|
||||||
|
@ -91,10 +91,10 @@ $("#note-menu-button").click(async e => {
|
|||||||
const parentNoteId = node.data.parentNoteId;
|
const parentNoteId = node.data.parentNoteId;
|
||||||
const isProtected = treeUtils.getParentProtectedStatus(node);
|
const isProtected = treeUtils.getParentProtectedStatus(node);
|
||||||
|
|
||||||
treeService.createNote(node, parentNoteId, 'after', null, isProtected);
|
treeService.createNote(node, parentNoteId, 'after', { isProtected: isProtected });
|
||||||
}
|
}
|
||||||
else if (cmd === "insertChildNote") {
|
else if (cmd === "insertChildNote") {
|
||||||
treeService.createNote(node, node.data.noteId, 'into', null);
|
treeService.createNote(node, node.data.noteId, 'into');
|
||||||
}
|
}
|
||||||
else if (cmd === "delete") {
|
else if (cmd === "delete") {
|
||||||
treeChangesService.deleteNodes([node]);
|
treeChangesService.deleteNodes([node]);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
|
import treeCache from "./tree_cache.js";
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import treeUtils from "./tree_utils.js";
|
import infoService from "./info.js";
|
||||||
|
|
||||||
const $tree = $("#tree");
|
const $tree = $("#tree");
|
||||||
const $searchInput = $("input[name='search-text']");
|
const $searchInput = $("input[name='search-text']");
|
||||||
@ -64,16 +65,35 @@ async function doSearch(searchText) {
|
|||||||
|
|
||||||
$searchResultsInner.append($result);
|
$searchResultsInner.append($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// have at least some feedback which is good especially in situations
|
||||||
|
// when the result list does not change with a query
|
||||||
|
infoService.showMessage("Search finished successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSearch() {
|
async function saveSearch() {
|
||||||
const {noteId} = await server.post('search/' + encodeURIComponent($searchInput.val()));
|
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();
|
resetSearch();
|
||||||
|
|
||||||
await treeService.reload();
|
|
||||||
|
|
||||||
await treeService.activateNote(noteId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
@ -560,49 +560,44 @@ async function createNewTopLevelNote() {
|
|||||||
|
|
||||||
const rootNode = getNodesByNoteId(hoistedNoteId)[0];
|
const rootNode = getNodesByNoteId(hoistedNoteId)[0];
|
||||||
|
|
||||||
await createNote(rootNode, hoistedNoteId, "into", null, false);
|
await createNote(rootNode, hoistedNoteId, "into");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function createNote(node, parentNoteId, target, extraOptions) {
|
||||||
* @param type - type can be falsy - in that case it will be chosen automatically based on parent note
|
|
||||||
*/
|
|
||||||
async function createNote(node, parentNoteId, target, type, isProtected, saveSelection = false) {
|
|
||||||
utils.assertArguments(node, parentNoteId, target);
|
utils.assertArguments(node, parentNoteId, target);
|
||||||
|
|
||||||
// if isProtected isn't available (user didn't enter password yet), then note is created as unencrypted
|
// if isProtected isn't available (user didn't enter password yet), then note is created as unencrypted
|
||||||
// but this is quite weird since user doesn't see WHERE the note is being created so it shouldn't occur often
|
// but this is quite weird since user doesn't see WHERE the note is being created so it shouldn't occur often
|
||||||
if (!isProtected || !protectedSessionHolder.isProtectedSessionAvailable()) {
|
if (!extraOptions.isProtected || !protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||||
isProtected = false;
|
extraOptions.isProtected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteDetailService.getActiveNoteType() !== 'text') {
|
if (noteDetailService.getActiveNoteType() !== 'text') {
|
||||||
saveSelection = false;
|
extraOptions.saveSelection = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// just disable this feature altogether - there's a problem that note containing image or table at the beginning
|
// just disable this feature altogether - there's a problem that note containing image or table at the beginning
|
||||||
// of the content will be auto-selected by CKEditor and then CTRL-P with no user interaction will automatically save
|
// of the content will be auto-selected by CKEditor and then CTRL-P with no user interaction will automatically save
|
||||||
// the selection - see https://github.com/ckeditor/ckeditor5/issues/1384
|
// the selection - see https://github.com/ckeditor/ckeditor5/issues/1384
|
||||||
saveSelection = false;
|
extraOptions.saveSelection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let title, content;
|
if (extraOptions.saveSelection) {
|
||||||
|
[extraOptions.title, extraOptions.content] = parseSelectedHtml(window.cutToNote.getSelectedHtml());
|
||||||
if (saveSelection) {
|
|
||||||
[title, content] = parseSelectedHtml(window.cutToNote.getSelectedHtml());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNoteName = title || "new note";
|
const newNoteName = extraOptions.title || "new note";
|
||||||
|
|
||||||
const {note, branch} = await server.post('notes/' + parentNoteId + '/children', {
|
const {note, branch} = await server.post('notes/' + parentNoteId + '/children', {
|
||||||
title: newNoteName,
|
title: newNoteName,
|
||||||
content: content,
|
content: extraOptions.content,
|
||||||
target: target,
|
target: target,
|
||||||
target_branchId: node.data.branchId,
|
target_branchId: node.data.branchId,
|
||||||
isProtected: isProtected,
|
isProtected: extraOptions.isProtected,
|
||||||
type: type
|
type: extraOptions.type
|
||||||
});
|
});
|
||||||
|
|
||||||
if (saveSelection) {
|
if (extraOptions.saveSelection) {
|
||||||
// we remove the selection only after it was saved to server to make sure we don't lose anything
|
// we remove the selection only after it was saved to server to make sure we don't lose anything
|
||||||
window.cutToNote.removeSelection();
|
window.cutToNote.removeSelection();
|
||||||
}
|
}
|
||||||
@ -622,9 +617,11 @@ async function createNote(node, parentNoteId, target, type, isProtected, saveSel
|
|||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
refKey: branchEntity.noteId,
|
refKey: branchEntity.noteId,
|
||||||
branchId: branchEntity.branchId,
|
branchId: branchEntity.branchId,
|
||||||
isProtected: isProtected,
|
isProtected: extraOptions.isProtected,
|
||||||
extraClasses: await treeBuilder.getExtraClasses(noteEntity),
|
extraClasses: await treeBuilder.getExtraClasses(noteEntity),
|
||||||
icon: await treeBuilder.getIcon(noteEntity)
|
icon: await treeBuilder.getIcon(noteEntity),
|
||||||
|
folder: extraOptions.type === 'search',
|
||||||
|
lazy: true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (target === 'after') {
|
if (target === 'after') {
|
||||||
@ -708,13 +705,19 @@ utils.bindShortcut('ctrl+o', async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createNote(node, parentNoteId, 'after', null, isProtected, true);
|
await createNote(node, parentNoteId, 'after', {
|
||||||
|
isProtected: isProtected,
|
||||||
|
saveSelection: true
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createNoteInto() {
|
async function createNoteInto() {
|
||||||
const node = getActiveNode();
|
const node = getActiveNode();
|
||||||
|
|
||||||
createNote(node, node.data.noteId, 'into', null, node.data.isProtected, true);
|
await createNote(node, node.data.noteId, 'into', {
|
||||||
|
isProtected: node.data.isProtected,
|
||||||
|
saveSelection: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkFolderStatus(node) {
|
async function checkFolderStatus(node) {
|
||||||
@ -768,10 +771,8 @@ $scrollToActiveNoteButton.click(scrollToActiveNote);
|
|||||||
export default {
|
export default {
|
||||||
reload,
|
reload,
|
||||||
collapseTree,
|
collapseTree,
|
||||||
scrollToActiveNote,
|
|
||||||
setBranchBackgroundBasedOnProtectedStatus,
|
setBranchBackgroundBasedOnProtectedStatus,
|
||||||
setProtected,
|
setProtected,
|
||||||
expandToNote,
|
|
||||||
activateNote,
|
activateNote,
|
||||||
getFocusedNode,
|
getFocusedNode,
|
||||||
getActiveNode,
|
getActiveNode,
|
||||||
@ -779,7 +780,6 @@ export default {
|
|||||||
setCurrentNotePathToHash,
|
setCurrentNotePathToHash,
|
||||||
setNoteTitle,
|
setNoteTitle,
|
||||||
setPrefix,
|
setPrefix,
|
||||||
createNewTopLevelNote,
|
|
||||||
createNote,
|
createNote,
|
||||||
createNoteInto,
|
createNoteInto,
|
||||||
getSelectedNodes,
|
getSelectedNodes,
|
||||||
|
@ -165,12 +165,15 @@ function selectContextMenuItem(event, cmd) {
|
|||||||
const isProtected = treeUtils.getParentProtectedStatus(node);
|
const isProtected = treeUtils.getParentProtectedStatus(node);
|
||||||
const type = cmd.split("_")[1];
|
const type = cmd.split("_")[1];
|
||||||
|
|
||||||
treeService.createNote(node, parentNoteId, 'after', type, isProtected);
|
treeService.createNote(node, parentNoteId, 'after', {
|
||||||
|
type: type,
|
||||||
|
isProtected: isProtected
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if (cmd.startsWith("insertChildNote")) {
|
else if (cmd.startsWith("insertChildNote")) {
|
||||||
const type = cmd.split("_")[1];
|
const type = cmd.split("_")[1];
|
||||||
|
|
||||||
treeService.createNote(node, node.data.noteId, 'into', type);
|
treeService.createNote(node, node.data.noteId, 'into', { type: type });
|
||||||
}
|
}
|
||||||
else if (cmd === "editBranchPrefix") {
|
else if (cmd === "editBranchPrefix") {
|
||||||
branchPrefixDialog.showDialog(node);
|
branchPrefixDialog.showDialog(node);
|
||||||
|
@ -66,7 +66,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
padding: 10px 0 10px 0;
|
padding: 10px 0 10px 0;
|
||||||
margin: 0 20px 0 10px;
|
margin: 0 10px 0 10px;
|
||||||
border: 1px solid var(--main-border-color);
|
border: 1px solid var(--main-border-color);
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const noteService = require('../../services/notes');
|
|
||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
const noteCacheService = require('../../services/note_cache');
|
const noteCacheService = require('../../services/note_cache');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
@ -13,20 +12,6 @@ async function searchNotes(req) {
|
|||||||
return noteIds.map(noteCacheService.getNotePath).filter(res => !!res);
|
return noteIds.map(noteCacheService.getNotePath).filter(res => !!res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSearchToNote(req) {
|
|
||||||
const content = {
|
|
||||||
searchString: req.params.searchString
|
|
||||||
};
|
|
||||||
|
|
||||||
const {note} = await noteService.createNote('root', req.params.searchString, content, {
|
|
||||||
json: true,
|
|
||||||
type: 'search',
|
|
||||||
mime: "application/json"
|
|
||||||
});
|
|
||||||
|
|
||||||
return { noteId: note.noteId };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function searchFromNote(req) {
|
async function searchFromNote(req) {
|
||||||
const note = await repository.getNote(req.params.noteId);
|
const note = await repository.getNote(req.params.noteId);
|
||||||
|
|
||||||
@ -52,7 +37,7 @@ async function searchFromNote(req) {
|
|||||||
noteIds = await searchFromRelation(note, relationName);
|
noteIds = await searchFromRelation(note, relationName);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
noteIds = searchService.searchForNoteIds(json.searchString);
|
noteIds = await searchService.searchForNoteIds(json.searchString);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we won't return search note's own noteId
|
// we won't return search note's own noteId
|
||||||
@ -100,6 +85,5 @@ async function searchFromRelation(note, relationName) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
searchNotes,
|
searchNotes,
|
||||||
saveSearchToNote,
|
|
||||||
searchFromNote
|
searchFromNote
|
||||||
};
|
};
|
@ -209,7 +209,6 @@ function register(app) {
|
|||||||
route(POST, '/api/sender/note', [auth.checkSenderToken], senderRoute.saveNote, apiResultHandler);
|
route(POST, '/api/sender/note', [auth.checkSenderToken], senderRoute.saveNote, apiResultHandler);
|
||||||
|
|
||||||
apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes);
|
apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes);
|
||||||
apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote);
|
|
||||||
apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote);
|
apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote);
|
||||||
|
|
||||||
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
|
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
|
||||||
const VIRTUAL_ATTRIBUTES = ["dateCreated", "dateCreated", "dateModified", "utcDateCreated", "utcDateModified", "isProtected", "title", "content", "type", "mime", "text"];
|
const VIRTUAL_ATTRIBUTES = [
|
||||||
|
"dateCreated",
|
||||||
|
"dateModified",
|
||||||
|
"utcDateCreated",
|
||||||
|
"utcDateModified",
|
||||||
|
"isProtected",
|
||||||
|
"title",
|
||||||
|
"content",
|
||||||
|
"type",
|
||||||
|
"mime",
|
||||||
|
"text"
|
||||||
|
];
|
||||||
|
|
||||||
module.exports = function(filters, selectedColumns = 'notes.*') {
|
module.exports = function(filters, selectedColumns = 'notes.*') {
|
||||||
// alias => join
|
// alias => join
|
||||||
@ -106,7 +117,7 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
|
|||||||
else if (filter.operator === '=*' || filter.operator === '!=*') {
|
else if (filter.operator === '=*' || filter.operator === '!=*') {
|
||||||
where += `${accessor}`
|
where += `${accessor}`
|
||||||
+ (filter.operator.includes('!') ? ' NOT' : '')
|
+ (filter.operator.includes('!') ? ' NOT' : '')
|
||||||
+ ` LIKE '` + utils.prepareSqlForLike('', filter.value, '%');
|
+ ` LIKE ` + utils.prepareSqlForLike('', filter.value, '%');
|
||||||
}
|
}
|
||||||
else if (filter.operator === '*=*' || filter.operator === '!*=*') {
|
else if (filter.operator === '*=*' || filter.operator === '!*=*') {
|
||||||
where += `${accessor}`
|
where += `${accessor}`
|
||||||
@ -149,8 +160,5 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
|
|||||||
GROUP BY notes.noteId
|
GROUP BY notes.noteId
|
||||||
ORDER BY ${orderBy.join(", ")}`;
|
ORDER BY ${orderBy.join(", ")}`;
|
||||||
|
|
||||||
console.log(query);
|
|
||||||
console.log(params);
|
|
||||||
|
|
||||||
return { query, params };
|
return { query, params };
|
||||||
};
|
};
|
||||||
|
@ -77,6 +77,18 @@ async function createNewNote(parentNoteId, noteData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!noteData.mime) {
|
||||||
|
if (noteData.type === 'text') {
|
||||||
|
noteData.mime = 'text/html';
|
||||||
|
}
|
||||||
|
else if (noteData.type === 'code') {
|
||||||
|
noteData.mime = 'text/plain';
|
||||||
|
}
|
||||||
|
else if (noteData.type === 'relation-map' || noteData.type === 'search') {
|
||||||
|
noteData.mime = 'application/json';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
noteData.type = noteData.type || parentNote.type;
|
noteData.type = noteData.type || parentNote.type;
|
||||||
noteData.mime = noteData.mime || parentNote.mime;
|
noteData.mime = noteData.mime || parentNote.mime;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\w_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([\w_-]+|"[^"]+"))?/ig;
|
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\w_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([\w_/-]+|"[^"]+"))?/ig;
|
||||||
const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i;
|
const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i;
|
||||||
|
|
||||||
function calculateSmartValue(v) {
|
function calculateSmartValue(v) {
|
||||||
|
@ -105,17 +105,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="search-box">
|
<div id="search-box">
|
||||||
<div style="display: flex; align-items: center; flex-wrap: wrap;">
|
<div class="form-group">
|
||||||
<input name="search-text" id="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px; flex-basis: 5em; min-width: 0;" autocomplete="off">
|
<div class="input-group">
|
||||||
<button id="do-search-button" class="btn btn-sm icon-button jam jam-search" title="Search (enter)"></button>
|
<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 jam jam-search" title="Search (enter)"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button id="save-search-button" class="btn btn-sm icon-button jam jam-save" title="Save search"></button>
|
|
||||||
|
|
||||||
|
<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="jam jam-save"></span> Save search</button>
|
||||||
|
|
||||||
<button id="close-search-button" class="btn btn-sm icon-button jam jam-close" title="Close search"></button>
|
<button id="close-search-button" class="btn btn-sm"><span class="jam jam-close"></span> Close search</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user