diff --git a/src/public/javascripts/desktop.js b/src/public/javascripts/desktop.js index aa70de123..02cb5fd5d 100644 --- a/src/public/javascripts/desktop.js +++ b/src/public/javascripts/desktop.js @@ -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); diff --git a/src/public/javascripts/mobile.js b/src/public/javascripts/mobile.js index 16a72172d..1c344373a 100644 --- a/src/public/javascripts/mobile.js +++ b/src/public/javascripts/mobile.js @@ -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]); diff --git a/src/public/javascripts/services/search_notes.js b/src/public/javascripts/services/search_notes.js index 7e983a268..3d2b1d7ca 100644 --- a/src/public/javascripts/services/search_notes.js +++ b/src/public/javascripts/services/search_notes.js @@ -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() { diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 92f1e7855..20d555f2c 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -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, diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index 4b4239470..d84af2179 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -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); diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index d870260cd..7664a0ed2 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -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; } diff --git a/src/routes/api/search.js b/src/routes/api/search.js index 4a5a179a4..f5267d6e6 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -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 }; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index 3ad6c5037..313993679 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -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); diff --git a/src/services/build_search_query.js b/src/services/build_search_query.js index efd678bc9..cfdab626c 100644 --- a/src/services/build_search_query.js +++ b/src/services/build_search_query.js @@ -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 }; }; diff --git a/src/services/notes.js b/src/services/notes.js index 48666ef82..d29636bd0 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -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; diff --git a/src/services/parse_filters.js b/src/services/parse_filters.js index c1d94679a..6b53d06da 100644 --- a/src/services/parse_filters.js +++ b/src/services/parse_filters.js @@ -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) { diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs index a9544f023..ffca1dc4e 100644 --- a/src/views/desktop.ejs +++ b/src/views/desktop.ejs @@ -105,17 +105,23 @@