mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +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:
		| @@ -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"> | ||||
|             <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> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user