mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	fix setting mime after import + cleanup of old search code
This commit is contained in:
		| @@ -176,7 +176,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | ||||
|      * @returns {Promise<NoteShort[]>} | ||||
|      */ | ||||
|     this.searchForNotes = async searchString => { | ||||
|         const noteIds = await this.runOnServer(async searchString => { | ||||
|         const noteIds = await this.runOnBackend(async searchString => { | ||||
|             const notes = await api.searchForNotes(searchString); | ||||
|  | ||||
|             return notes.map(note => note.noteId); | ||||
|   | ||||
| @@ -127,7 +127,7 @@ class ImageTypeWidget extends TypeWidget { | ||||
|  | ||||
|         this.$widget.show(); | ||||
|  | ||||
|         const noteComplement = await this.tabContext.getNoteComplement(); | ||||
|         const noteComplement = await this.tabContext.getNoteComplement();console.log(noteComplement, note); | ||||
|  | ||||
|         this.$fileName.text(attributeMap.originalFileName || "?"); | ||||
|         this.$fileSize.text(noteComplement.contentLength + " bytes"); | ||||
|   | ||||
| @@ -18,6 +18,7 @@ function updateFile(req) { | ||||
|     noteRevisionService.createNoteRevision(note); | ||||
|  | ||||
|     note.mime = file.mimetype.toLowerCase(); | ||||
|     note.save(); | ||||
|  | ||||
|     note.setContent(file.buffer); | ||||
|  | ||||
|   | ||||
| @@ -25,6 +25,8 @@ function getNote(req) { | ||||
|  | ||||
|     const contentMetadata = note.getContentMetadata(); | ||||
|  | ||||
|     note.contentLength = contentMetadata.contentLength; | ||||
|  | ||||
|     note.combinedUtcDateModified = note.utcDateModified > contentMetadata.utcDateModified ? note.utcDateModified : contentMetadata.utcDateModified; | ||||
|     note.combinedDateModified = note.utcDateModified > contentMetadata.utcDateModified ? note.dateModified : contentMetadata.dateModified; | ||||
|  | ||||
|   | ||||
| @@ -1,158 +0,0 @@ | ||||
| const utils = require('./utils'); | ||||
|  | ||||
| const VIRTUAL_ATTRIBUTES = [ | ||||
|     "dateCreated", | ||||
|     "dateModified", | ||||
|     "utcDateCreated", | ||||
|     "utcDateModified", | ||||
|     "noteId", | ||||
|     "isProtected", | ||||
|     "title", | ||||
|     "content", | ||||
|     "type", | ||||
|     "mime", | ||||
|     "text", | ||||
|     "parentCount" | ||||
| ]; | ||||
|  | ||||
| module.exports = function(filters, selectedColumns = 'notes.*') { | ||||
|     // alias => join | ||||
|     const joins = { | ||||
|         "notes": null | ||||
|     }; | ||||
|  | ||||
|     let attrFilterId = 1; | ||||
|  | ||||
|     function getAccessor(property) { | ||||
|         let accessor; | ||||
|  | ||||
|         if (!VIRTUAL_ATTRIBUTES.includes(property)) { | ||||
|             // not reusing existing filters to support multi-valued filters e.g. "@tag=christmas @tag=shopping" | ||||
|             // can match notes because @tag can be both "shopping" and "christmas" | ||||
|             const alias = "attr_" + property + "_" + attrFilterId++; | ||||
|  | ||||
|             // forcing to use particular index since SQLite query planner would often choose something pretty bad | ||||
|             joins[alias] = `LEFT JOIN attributes AS ${alias} INDEXED BY IDX_attributes_noteId_index ` | ||||
|                 + `ON ${alias}.noteId = notes.noteId ` | ||||
|                 + `AND ${alias}.name = '${property}' AND ${alias}.isDeleted = 0`; | ||||
|  | ||||
|             accessor = `${alias}.value`; | ||||
|         } | ||||
|         else if (property === 'content') { | ||||
|             const alias = "note_contents"; | ||||
|  | ||||
|             if (!(alias in joins)) { | ||||
|                 joins[alias] = `LEFT JOIN note_contents ON note_contents.noteId = notes.noteId`; | ||||
|             } | ||||
|  | ||||
|             accessor = `${alias}.${property}`; | ||||
|         } | ||||
|         else if (property === 'parentCount') { | ||||
|             // need to cast as string for the equality operator to work | ||||
|             // for >= etc. it is cast again to DECIMAL | ||||
|             // also cannot use COUNT() in WHERE so using subquery ... | ||||
|             accessor = `CAST((SELECT COUNT(1) FROM branches WHERE branches.noteId = notes.noteId AND isDeleted = 0) AS STRING)`; | ||||
|         } | ||||
|         else { | ||||
|             accessor = "notes." + property; | ||||
|         } | ||||
|  | ||||
|         return accessor; | ||||
|     } | ||||
|  | ||||
|     let orderBy = []; | ||||
|  | ||||
|     const orderByFilter = filters.find(filter => filter.name.toLowerCase() === 'orderby'); | ||||
|  | ||||
|     if (orderByFilter) { | ||||
|         orderBy = orderByFilter.value.split(",").map(prop => { | ||||
|             const direction = prop.includes("-") ? "DESC" : "ASC"; | ||||
|             const cleanedProp = prop.trim().replace(/-/g, ""); | ||||
|  | ||||
|             return getAccessor(cleanedProp) + " " + direction; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let where = '1'; | ||||
|     const params = []; | ||||
|  | ||||
|         for (const filter of filters) { | ||||
|             if (['isarchived', 'in', 'orderby', 'limit'].includes(filter.name.toLowerCase())) { | ||||
|                 continue; // these are not real filters | ||||
|             } | ||||
|  | ||||
|         where += " " + filter.relation + " "; | ||||
|  | ||||
|             const accessor = getAccessor(filter.name); | ||||
|  | ||||
|             if (filter.operator === 'exists') { | ||||
|             where += `${accessor} IS NOT NULL`; | ||||
|         } | ||||
|         else if (filter.operator === 'not-exists') { | ||||
|             where += `${accessor} IS NULL`; | ||||
|         } | ||||
|         else if (filter.operator === '=' || filter.operator === '!=') { | ||||
|             where += `${accessor} ${filter.operator} ?`; | ||||
|                 params.push(filter.value); | ||||
|             } else if (filter.operator === '*=' || filter.operator === '!*=') { | ||||
|             where += `${accessor}` | ||||
|                     + (filter.operator.includes('!') ? ' NOT' : '') | ||||
|                     + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, ''); | ||||
|             } else if (filter.operator === '=*' || filter.operator === '!=*') { | ||||
|             where += `${accessor}` | ||||
|                     + (filter.operator.includes('!') ? ' NOT' : '') | ||||
|                     + ` LIKE ` + utils.prepareSqlForLike('', filter.value, '%'); | ||||
|             } else if (filter.operator === '*=*' || filter.operator === '!*=*') { | ||||
|                 const columns = filter.name === 'text' ? [getAccessor("title"), getAccessor("content")] : [accessor]; | ||||
|  | ||||
|                 let condition = "(" + columns.map(column => | ||||
|                     `${column}` + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '%')) | ||||
|                     .join(" OR ") + ")"; | ||||
|  | ||||
|                 if (filter.operator.includes('!')) { | ||||
|                     condition = "NOT(" + condition + ")"; | ||||
|                 } | ||||
|  | ||||
|                 if (['text', 'title', 'content'].includes(filter.name)) { | ||||
|                     // for title/content search does not make sense to search for protected notes | ||||
|                     condition = `(${condition} AND notes.isProtected = 0)`; | ||||
|                 } | ||||
|  | ||||
|             where += condition; | ||||
|         } | ||||
|         else if ([">", ">=", "<", "<="].includes(filter.operator)) { | ||||
|                 let floatParam; | ||||
|  | ||||
|                 // from https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers | ||||
|                 if (/^[+-]?([0-9]*[.])?[0-9]+$/.test(filter.value)) { | ||||
|                     floatParam = parseFloat(filter.value); | ||||
|                 } | ||||
|  | ||||
|                 if (floatParam === undefined || isNaN(floatParam)) { | ||||
|                     // if the value can't be parsed as float then we assume that string comparison should be used instead of numeric | ||||
|                 where += `${accessor} ${filter.operator} ?`; | ||||
|                     params.push(filter.value); | ||||
|                 } else { | ||||
|                 where += `CAST(${accessor} AS DECIMAL) ${filter.operator} ?`; | ||||
|                     params.push(floatParam); | ||||
|                 } | ||||
|             } else { | ||||
|                 throw new Error("Unknown operator " + filter.operator); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     if (orderBy.length === 0) { | ||||
|         // if no ordering is given then order at least by note title | ||||
|         orderBy.push("notes.title"); | ||||
|     } | ||||
|  | ||||
|     const query = `SELECT ${selectedColumns} FROM notes | ||||
|             ${Object.values(joins).join('\r\n')} | ||||
|               WHERE | ||||
|                 notes.isDeleted = 0 | ||||
|                 AND (${where}) | ||||
|               GROUP BY notes.noteId | ||||
|               ORDER BY ${orderBy.join(", ")}`; | ||||
|  | ||||
|     return { query, params }; | ||||
| }; | ||||
| @@ -15,7 +15,7 @@ const isSvg = require('is-svg'); | ||||
| async function processImage(uploadBuffer, originalName, shrinkImageSwitch) { | ||||
|     const origImageFormat = getImageType(uploadBuffer); | ||||
|  | ||||
|     if (origImageFormat && ["webp", "svg"].includes(origImageFormat.ext)) { | ||||
|     if (origImageFormat && ["webp", "svg", "gif"].includes(origImageFormat.ext)) { | ||||
|         // JIMP does not support webp at the moment: https://github.com/oliver-moran/jimp/issues/144 | ||||
|         shrinkImageSwitch = false; | ||||
|     } | ||||
| @@ -61,6 +61,8 @@ function updateImage(noteId, uploadBuffer, originalName) { | ||||
|     processImage(uploadBuffer, originalName, true).then(({buffer, imageFormat}) => { | ||||
|         sql.transactional(() => { | ||||
|             note.mime = getImageMimeFromExtension(imageFormat.ext); | ||||
|             note.save(); | ||||
|  | ||||
|             note.setContent(buffer); | ||||
|         }) | ||||
|     }); | ||||
| @@ -88,6 +90,8 @@ function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch) | ||||
|     processImage(uploadBuffer, originalName, shrinkImageSwitch).then(({buffer, imageFormat}) => { | ||||
|         sql.transactional(() => { | ||||
|             note.mime = getImageMimeFromExtension(imageFormat.ext); | ||||
|             note.save(); | ||||
|  | ||||
|             note.setContent(buffer); | ||||
|         }) | ||||
|     }); | ||||
|   | ||||
| @@ -105,7 +105,7 @@ function createNewNote(params) { | ||||
|         throw new Error(`Note title must not be empty`); | ||||
|     } | ||||
|  | ||||
|     sql.transactional(() => { | ||||
|     return sql.transactional(() => { | ||||
|         const note = new Note({ | ||||
|             noteId: params.noteId, // optionally can force specific noteId | ||||
|             title: params.title, | ||||
| @@ -744,8 +744,8 @@ function duplicateNote(noteId, parentNoteId) { | ||||
|     const newNote = new Note(origNote); | ||||
|     newNote.noteId = undefined; // force creation of new note | ||||
|     newNote.title += " (dup)"; | ||||
|  | ||||
|     newNote.save(); | ||||
|  | ||||
|     newNote.setContent(origNote.getContent()); | ||||
|  | ||||
|     const newBranch = new Branch({ | ||||
|   | ||||
| @@ -1,89 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| /** | ||||
|  * Missing things from the OLD search: | ||||
|  * - orderBy | ||||
|  * - limit | ||||
|  * - in - replaced with note.ancestors | ||||
|  * - content in attribute search | ||||
|  * - not - pherhaps not necessary | ||||
|  * | ||||
|  * other potential additions: | ||||
|  * - targetRelations - either named or not | ||||
|  * - any relation without name | ||||
|  */ | ||||
|  | ||||
| const repository = require('./repository'); | ||||
| const sql = require('./sql'); | ||||
| const log = require('./log'); | ||||
| const parseFilters = require('./search/parse_filters.js'); | ||||
| const buildSearchQuery = require('./build_search_query'); | ||||
| const noteCacheService = require('./note_cache/note_cache_service'); | ||||
|  | ||||
| function searchForNotes(searchString) { | ||||
|     const noteIds = searchForNoteIds(searchString); | ||||
|  | ||||
|     return repository.getNotes(noteIds); | ||||
| } | ||||
|  | ||||
| function searchForNoteIds(searchString) { | ||||
|     const filters = parseFilters(searchString); | ||||
|  | ||||
|     const {query, params} = buildSearchQuery(filters, 'notes.noteId'); | ||||
|  | ||||
|     try { | ||||
|         let noteIds = sql.getColumn(query, params); | ||||
|  | ||||
|         noteIds = noteIds.filter(noteCacheService.isAvailable); | ||||
|  | ||||
|         const isArchivedFilter = filters.find(filter => filter.name.toLowerCase() === 'isarchived'); | ||||
|  | ||||
|         if (isArchivedFilter) { | ||||
|             if (isArchivedFilter.operator === 'exists') { | ||||
|                 noteIds = noteIds.filter(noteCacheService.isArchived); | ||||
|             } | ||||
|             else if (isArchivedFilter.operator === 'not-exists') { | ||||
|                 noteIds = noteIds.filter(noteId => !noteCacheService.isArchived(noteId)); | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Unrecognized isArchived operator ${isArchivedFilter.operator}`); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const isInFilters = filters.filter(filter => filter.name.toLowerCase() === 'in'); | ||||
|  | ||||
|         for (const isInFilter of isInFilters) { | ||||
|             if (isInFilter.operator === '=') { | ||||
|                 noteIds = noteIds.filter(noteId => noteCacheService.isInAncestor(noteId, isInFilter.value)); | ||||
|             } | ||||
|             else if (isInFilter.operator === '!=') { | ||||
|                 noteIds = noteIds.filter(noteId => !noteCacheService.isInAncestor(noteId, isInFilter.value)); | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Unrecognized isIn operator ${isInFilter.operator}`); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const limitFilter = filters.find(filter => filter.name.toLowerCase() === 'limit'); | ||||
|  | ||||
|         if (limitFilter) { | ||||
|             const limit = parseInt(limitFilter.value); | ||||
|  | ||||
|             return noteIds.splice(0, limit); | ||||
|         } | ||||
|         else { | ||||
|             return noteIds; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     catch (e) { | ||||
|         log.error("Search failed for " + query); | ||||
|  | ||||
|         throw e; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     searchForNotes, | ||||
|     searchForNoteIds | ||||
| }; | ||||
| @@ -1,118 +0,0 @@ | ||||
| const dayjs = require("dayjs"); | ||||
|  | ||||
| const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([^\s=*"]+|"[^"]+"))?/igu; | ||||
| const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i; | ||||
|  | ||||
| function calculateSmartValue(v) { | ||||
|     const match = smartValueRegex.exec(v); | ||||
|     if (match === null) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const keyword = match[1].toUpperCase(); | ||||
|     const num = match[2] ? parseInt(match[2].replace(/ /g, "")) : 0; // can contain spaces between sign and digits | ||||
|  | ||||
|     let format, date; | ||||
|  | ||||
|     if (keyword === 'NOW') { | ||||
|         date = dayjs().add(num, 'second'); | ||||
|         format = "YYYY-MM-DD HH:mm:ss"; | ||||
|     } | ||||
|     else if (keyword === 'TODAY') { | ||||
|         date = dayjs().add(num, 'day'); | ||||
|         format = "YYYY-MM-DD"; | ||||
|     } | ||||
|     else if (keyword === 'WEEK') { | ||||
|         // FIXME: this will always use sunday as start of the week | ||||
|         date = dayjs().startOf('week').add(7 * num, 'day'); | ||||
|         format = "YYYY-MM-DD"; | ||||
|     } | ||||
|     else if (keyword === 'MONTH') { | ||||
|         date = dayjs().add(num, 'month'); | ||||
|         format = "YYYY-MM"; | ||||
|     } | ||||
|     else if (keyword === 'YEAR') { | ||||
|         date = dayjs().add(num, 'year'); | ||||
|         format = "YYYY"; | ||||
|     } | ||||
|     else { | ||||
|         throw new Error("Unrecognized keyword: " + keyword); | ||||
|     } | ||||
|  | ||||
|     return date.format(format); | ||||
| } | ||||
|  | ||||
| module.exports = function (searchText) { | ||||
|     searchText = searchText.trim(); | ||||
|  | ||||
|     // if the string doesn't start with attribute then we consider it as just standard full text search | ||||
|     if (!searchText.startsWith("@")) { | ||||
|         // replace with space instead of empty string since these characters are probably separators | ||||
|         const filters = []; | ||||
|  | ||||
|         if (searchText.startsWith('"') && searchText.endsWith('"')) { | ||||
|             // "bla bla" will search for exact match | ||||
|             searchText = searchText.substr(1, searchText.length - 2); | ||||
|  | ||||
|             filters.push({ | ||||
|                 relation: 'and', | ||||
|                 name: 'text', | ||||
|                 operator: '*=*', | ||||
|                 value: searchText | ||||
|             }); | ||||
|         } | ||||
|         else { | ||||
|             const tokens = searchText.split(/\s+/); | ||||
|  | ||||
|             for (const token of tokens) { | ||||
|                 filters.push({ | ||||
|                     relation: 'and', | ||||
|                             name: 'text', | ||||
|                             operator: '*=*', | ||||
|                             value: token | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         filters.push({ | ||||
|             relation: 'and', | ||||
|             name: 'isArchived', | ||||
|             operator: 'not-exists' | ||||
|         }); | ||||
|  | ||||
|         filters.push({ | ||||
|             relation: 'or', | ||||
|             name: 'noteId', | ||||
|             operator: '=', | ||||
|             value: searchText | ||||
|         }); | ||||
|  | ||||
|         return filters; | ||||
|     } | ||||
|  | ||||
|     const filters = []; | ||||
|  | ||||
|     function trimQuotes(str) { return str.startsWith('"') ? str.substr(1, str.length - 2) : str; } | ||||
|  | ||||
|     let match; | ||||
|  | ||||
|     while (match = filterRegex.exec(searchText)) { | ||||
|         const relation = match[2] !== undefined ? match[2].toLowerCase() : 'and'; | ||||
|         const operator = match[3] === '!' ? 'not-exists' : 'exists'; | ||||
|  | ||||
|         const value = match[7] !== undefined ? trimQuotes(match[7]) : null; | ||||
|  | ||||
|         filters.push({ | ||||
|             relation: relation, | ||||
|             name: trimQuotes(match[4]), | ||||
|             operator: match[6] !== undefined ? match[6] : operator, | ||||
|             value: ( | ||||
|                 value && value.match(smartValueRegex) | ||||
|                     ? calculateSmartValue(value) | ||||
|                     : value | ||||
|             ) | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     return filters; | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user