mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +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 () => {
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -91,10 +91,10 @@ $("#note-menu-button").click(async e => {
 | 
			
		||||
            const parentNoteId = node.data.parentNoteId;
 | 
			
		||||
            const isProtected = treeUtils.getParentProtectedStatus(node);
 | 
			
		||||
 | 
			
		||||
            treeService.createNote(node, parentNoteId, 'after', null, isProtected);
 | 
			
		||||
            treeService.createNote(node, parentNoteId, 'after', { isProtected: isProtected });
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "insertChildNote") {
 | 
			
		||||
            treeService.createNote(node, node.data.noteId, 'into', null);
 | 
			
		||||
            treeService.createNote(node, node.data.noteId, 'into');
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "delete") {
 | 
			
		||||
            treeChangesService.deleteNodes([node]);
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import treeService from './tree.js';
 | 
			
		||||
import treeCache from "./tree_cache.js";
 | 
			
		||||
import server from './server.js';
 | 
			
		||||
import treeUtils from "./tree_utils.js";
 | 
			
		||||
import infoService from "./info.js";
 | 
			
		||||
 | 
			
		||||
const $tree = $("#tree");
 | 
			
		||||
const $searchInput = $("input[name='search-text']");
 | 
			
		||||
@ -64,16 +65,35 @@ async function doSearch(searchText) {
 | 
			
		||||
 | 
			
		||||
        $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() {
 | 
			
		||||
    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();
 | 
			
		||||
 | 
			
		||||
    await treeService.reload();
 | 
			
		||||
 | 
			
		||||
    await treeService.activateNote(noteId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function init() {
 | 
			
		||||
 | 
			
		||||
@ -560,49 +560,44 @@ async function createNewTopLevelNote() {
 | 
			
		||||
 | 
			
		||||
    const rootNode = getNodesByNoteId(hoistedNoteId)[0];
 | 
			
		||||
 | 
			
		||||
    await createNote(rootNode, hoistedNoteId, "into", null, false);
 | 
			
		||||
    await createNote(rootNode, hoistedNoteId, "into");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @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) {
 | 
			
		||||
async function createNote(node, parentNoteId, target, extraOptions) {
 | 
			
		||||
    utils.assertArguments(node, parentNoteId, target);
 | 
			
		||||
 | 
			
		||||
    // 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
 | 
			
		||||
    if (!isProtected || !protectedSessionHolder.isProtectedSessionAvailable()) {
 | 
			
		||||
        isProtected = false;
 | 
			
		||||
    if (!extraOptions.isProtected || !protectedSessionHolder.isProtectedSessionAvailable()) {
 | 
			
		||||
        extraOptions.isProtected = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (noteDetailService.getActiveNoteType() !== 'text') {
 | 
			
		||||
        saveSelection = false;
 | 
			
		||||
        extraOptions.saveSelection = false;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        // 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
 | 
			
		||||
        // the selection - see https://github.com/ckeditor/ckeditor5/issues/1384
 | 
			
		||||
        saveSelection = false;
 | 
			
		||||
        extraOptions.saveSelection = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let title, content;
 | 
			
		||||
 | 
			
		||||
    if (saveSelection) {
 | 
			
		||||
        [title, content] = parseSelectedHtml(window.cutToNote.getSelectedHtml());
 | 
			
		||||
    if (extraOptions.saveSelection) {
 | 
			
		||||
        [extraOptions.title, extraOptions.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', {
 | 
			
		||||
        title: newNoteName,
 | 
			
		||||
        content: content,
 | 
			
		||||
        content: extraOptions.content,
 | 
			
		||||
        target: target,
 | 
			
		||||
        target_branchId: node.data.branchId,
 | 
			
		||||
        isProtected: isProtected,
 | 
			
		||||
        type: type
 | 
			
		||||
        isProtected: extraOptions.isProtected,
 | 
			
		||||
        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
 | 
			
		||||
        window.cutToNote.removeSelection();
 | 
			
		||||
    }
 | 
			
		||||
@ -622,9 +617,11 @@ async function createNote(node, parentNoteId, target, type, isProtected, saveSel
 | 
			
		||||
        parentNoteId: parentNoteId,
 | 
			
		||||
        refKey: branchEntity.noteId,
 | 
			
		||||
        branchId: branchEntity.branchId,
 | 
			
		||||
        isProtected: isProtected,
 | 
			
		||||
        isProtected: extraOptions.isProtected,
 | 
			
		||||
        extraClasses: await treeBuilder.getExtraClasses(noteEntity),
 | 
			
		||||
        icon: await treeBuilder.getIcon(noteEntity)
 | 
			
		||||
        icon: await treeBuilder.getIcon(noteEntity),
 | 
			
		||||
        folder: extraOptions.type === 'search',
 | 
			
		||||
        lazy: true
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (target === 'after') {
 | 
			
		||||
@ -708,13 +705,19 @@ utils.bindShortcut('ctrl+o', async () => {
 | 
			
		||||
        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();
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
@ -768,10 +771,8 @@ $scrollToActiveNoteButton.click(scrollToActiveNote);
 | 
			
		||||
export default {
 | 
			
		||||
    reload,
 | 
			
		||||
    collapseTree,
 | 
			
		||||
    scrollToActiveNote,
 | 
			
		||||
    setBranchBackgroundBasedOnProtectedStatus,
 | 
			
		||||
    setProtected,
 | 
			
		||||
    expandToNote,
 | 
			
		||||
    activateNote,
 | 
			
		||||
    getFocusedNode,
 | 
			
		||||
    getActiveNode,
 | 
			
		||||
@ -779,7 +780,6 @@ export default {
 | 
			
		||||
    setCurrentNotePathToHash,
 | 
			
		||||
    setNoteTitle,
 | 
			
		||||
    setPrefix,
 | 
			
		||||
    createNewTopLevelNote,
 | 
			
		||||
    createNote,
 | 
			
		||||
    createNoteInto,
 | 
			
		||||
    getSelectedNodes,
 | 
			
		||||
 | 
			
		||||
@ -165,12 +165,15 @@ function selectContextMenuItem(event, cmd) {
 | 
			
		||||
        const isProtected = treeUtils.getParentProtectedStatus(node);
 | 
			
		||||
        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")) {
 | 
			
		||||
        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") {
 | 
			
		||||
        branchPrefixDialog.showDialog(node);
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,7 @@ body {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-around;
 | 
			
		||||
    padding: 10px 0 10px 0;
 | 
			
		||||
    margin: 0 20px 0 10px;
 | 
			
		||||
    margin: 0 10px 0 10px;
 | 
			
		||||
    border: 1px solid var(--main-border-color);
 | 
			
		||||
    border-radius: 7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
const noteService = require('../../services/notes');
 | 
			
		||||
const repository = require('../../services/repository');
 | 
			
		||||
const noteCacheService = require('../../services/note_cache');
 | 
			
		||||
const log = require('../../services/log');
 | 
			
		||||
@ -13,20 +12,6 @@ async function searchNotes(req) {
 | 
			
		||||
    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) {
 | 
			
		||||
    const note = await repository.getNote(req.params.noteId);
 | 
			
		||||
 | 
			
		||||
@ -52,7 +37,7 @@ async function searchFromNote(req) {
 | 
			
		||||
        noteIds = await searchFromRelation(note, relationName);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        noteIds = searchService.searchForNoteIds(json.searchString);
 | 
			
		||||
        noteIds = await searchService.searchForNoteIds(json.searchString);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // we won't return search note's own noteId
 | 
			
		||||
@ -100,6 +85,5 @@ async function searchFromRelation(note, relationName) {
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    searchNotes,
 | 
			
		||||
    saveSearchToNote,
 | 
			
		||||
    searchFromNote
 | 
			
		||||
};
 | 
			
		||||
@ -209,7 +209,6 @@ function register(app) {
 | 
			
		||||
    route(POST, '/api/sender/note', [auth.checkSenderToken], senderRoute.saveNote, apiResultHandler);
 | 
			
		||||
 | 
			
		||||
    apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes);
 | 
			
		||||
    apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote);
 | 
			
		||||
    apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote);
 | 
			
		||||
 | 
			
		||||
    route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,17 @@
 | 
			
		||||
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.*') {
 | 
			
		||||
    // alias => join
 | 
			
		||||
@ -106,7 +117,7 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
 | 
			
		||||
        else if (filter.operator === '=*' || filter.operator === '!=*') {
 | 
			
		||||
            where += `${accessor}`
 | 
			
		||||
                    + (filter.operator.includes('!') ? ' NOT' : '')
 | 
			
		||||
                    + ` LIKE '` + utils.prepareSqlForLike('', filter.value, '%');
 | 
			
		||||
                    + ` LIKE ` + utils.prepareSqlForLike('', filter.value, '%');
 | 
			
		||||
        }
 | 
			
		||||
        else if (filter.operator === '*=*' || filter.operator === '!*=*') {
 | 
			
		||||
            where += `${accessor}`
 | 
			
		||||
@ -149,8 +160,5 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
 | 
			
		||||
              GROUP BY notes.noteId
 | 
			
		||||
              ORDER BY ${orderBy.join(", ")}`;
 | 
			
		||||
 | 
			
		||||
    console.log(query);
 | 
			
		||||
    console.log(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.mime = noteData.mime || parentNote.mime;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
function calculateSmartValue(v) {
 | 
			
		||||
 | 
			
		||||
@ -105,17 +105,23 @@
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div id="search-box">
 | 
			
		||||
            <div style="display: flex; align-items: center; flex-wrap: wrap;">
 | 
			
		||||
                <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">
 | 
			
		||||
                <button id="do-search-button" class="btn btn-sm icon-button jam jam-search" title="Search (enter)"></button>
 | 
			
		||||
            <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 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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user