trilium/src/services/search/value_extractor.js

130 lines
4.2 KiB
JavaScript

"use strict";
/**
* Search string is lower cased for case insensitive comparison. But when retrieving properties
* we need case sensitive form so we have this translation object.
*/
const PROP_MAPPING = {
"noteid": "noteId",
"title": "title",
"type": "type",
"mime": "mime",
"isprotected": "isProtected",
"isarchived": "isArchived",
"datecreated": "dateCreated",
"datemodified": "dateModified",
"utcdatecreated": "utcDateCreated",
"utcdatemodified": "utcDateModified",
"parentcount": "parentCount",
"childrencount": "childrenCount",
"attributecount": "attributeCount",
"labelcount": "labelCount",
"ownedlabelcount": "ownedLabelCount",
"relationcount": "relationCount",
"ownedrelationcount": "ownedRelationCount",
"relationcountincludinglinks": "relationCountIncludingLinks",
"ownedrelationcountincludinglinks": "ownedRelationCountIncludingLinks",
"targetrelationcount": "targetRelationCount",
"targetrelationcountincludinglinks": "targetRelationCountIncludingLinks",
"contentsize": "contentSize",
"notesize": "noteSize",
"revisioncount": "revisionCount"
};
class ValueExtractor {
constructor(searchContext, propertyPath) {
this.propertyPath = propertyPath.map(pathEl => pathEl.toLowerCase());
if (this.propertyPath[0].startsWith('#')) {
this.propertyPath = ['note', 'labels', this.propertyPath[0].substr(1), ...this.propertyPath.slice( 1, this.propertyPath.length)];
}
else if (this.propertyPath[0].startsWith('~')) {
this.propertyPath = ['note', 'relations', this.propertyPath[0].substr(1), ...this.propertyPath.slice(1, this.propertyPath.length)];
}
if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyPath[this.propertyPath.length - 1])) {
searchContext.dbLoadNeeded = true;
}
}
validate() {
if (this.propertyPath[0] !== 'note') {
return `property specifier must start with 'note', but starts with '${this.propertyPath[0]}'`;
}
for (let i = 1; i < this.propertyPath.length; i++) {
const pathEl = this.propertyPath[i];
if (pathEl === 'labels') {
if (i !== this.propertyPath.length - 2) {
return `label is a terminal property specifier and must be at the end`;
}
i++;
}
else if (pathEl === 'relations') {
if (i >= this.propertyPath.length - 2) {
return `relation name or property name is missing`;
}
i++;
}
else if (pathEl in PROP_MAPPING || pathEl === 'random') {
if (i !== this.propertyPath.length - 1) {
return `${pathEl} is a terminal property specifier and must be at the end`;
}
}
else if (!["parents", "children"].includes(pathEl)) {
return `Unrecognized property specifier ${pathEl}`;
}
}
}
extract(note) {
let cursor = note;
let i;
const cur = () => this.propertyPath[i];
for (i = 0; i < this.propertyPath.length; i++) {
if (!cursor) {
return cursor;
}
if (cur() === 'labels') {
i++;
const attr = cursor.getAttributeCaseInsensitive('label', cur());
return attr ? attr.value : null;
}
if (cur() === 'relations') {
i++;
const attr = cursor.getAttributeCaseInsensitive('relation', cur());
cursor = attr ? attr.targetNote : null;
}
else if (cur() === 'parents') {
cursor = cursor.parents[0];
}
else if (cur() === 'children') {
cursor = cursor.children[0];
}
else if (cur() === 'random') {
return Math.random().toString(); // string is expected for comparison
}
else if (cur() in PROP_MAPPING) {
return cursor[PROP_MAPPING[cur()]];
}
else {
// FIXME
}
}
}
}
module.exports = ValueExtractor;