Skip to content

Commit

Permalink
Allow for filtering by frontmatter properties (#265)
Browse files Browse the repository at this point in the history
* Allow for filtering by frontmatter properties

Fixes #257.

* Reformat README.md to satisfy stylecheck

* fixup! Allow for filtering by frontmatter properties
  • Loading branch information
zakj authored Aug 16, 2024
1 parent d594e10 commit bddd148
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 5 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -615,9 +615,9 @@ And while both plugins are about maps and use Leaflet.js as their visual engine,

Many important bug fixes are waiting for me to have a little spare time, in the meantime had to settle for smaller release.

- Fixed "Paste as geolocation" issue (https://github.com/esm7/obsidian-map-view/issues/253), thanks
@frees0l0!
- Fixed "Using custom property instead default location does not work" (https://github.com/esm7/obsidian-map-view/issues/251).
- Fixed "Paste as geolocation" issue (https://github.com/esm7/obsidian-map-view/issues/253), thanks
@frees0l0!
- Fixed "Using custom property instead default location does not work" (https://github.com/esm7/obsidian-map-view/issues/251).

### 5.0.2

Expand Down
61 changes: 59 additions & 2 deletions src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ export class Query {
// 1. Replace tag:#abc by "tag:#abc" because this parser doesn't like the '#' symbol
// 2. Replace path:"abc def/ghi" by "path:abc def/dhi" because the parser doesn't like quotes as part of the words
// 3. Same goes for linkedto:"", linkedfrom:"" and name:""
// 4. Replace ["property":"value"] with single quotes to avoid parser complaints.
let newString = queryString
.replace(regex.TAG_NAME_WITH_HEADER_AND_WILDCARD, '"tag:$1"')
.replace(regex.PATH_QUERY_WITH_HEADER, '"path:$1"')
.replace(regex.LINKEDTO_QUERY_WITH_HEADER, '"linkedto:$1"')
.replace(regex.LINKEDFROM_QUERY_WITH_HEADER, '"linkedfrom:$1"')
.replace(regex.NAME_QUERY_WITH_HEADER, '"name:$1"');
.replace(regex.NAME_QUERY_WITH_HEADER, '"name:$1"')
.replace(/^\[(")(.+?)\1:/, "['$2':")
.replace(/:(")(.+)?\1\]/, ":'$2']");
return newString;
}

Expand Down Expand Up @@ -154,10 +157,59 @@ export class Query {
marker.fileLine <= toLine
);
}
} else throw new Error('Unsupported query format' + value);
} else if (value.startsWith('[')) {
const propertyQueryMatch = value.match(/\[(.+?):(.*?)\]/);
if (!propertyQueryMatch) return false;

// Quoted property name or value imply exact match; otherwise substring match.
const [, propertyNameRaw, propertyQueryRaw] = propertyQueryMatch;
const [isExactName, propertyName] = unquote(propertyNameRaw);
const [isExactQuery, propertyQuery] = unquote(propertyQueryRaw);

const fileCache = this.app.metadataCache.getFileCache(marker.file);
let propertyValues: string[] = [];
if (isExactName) {
const property = fileCache.frontmatter?.[propertyName];
if (typeof property !== 'undefined')
propertyValues = [property];
} else {
propertyValues = Object.keys(fileCache.frontmatter)
.filter((k) => k.includes(propertyName))
.map((k) => fileCache.frontmatter[k]);
}

// Allow searching for the existence of a property via a blank value.
if (propertyQuery.length === 0) return propertyValues.length > 0;

const propertyQueryLower = propertyQuery.toLowerCase();
return normalizePropertyValues(propertyValues).some((p) =>
isExactQuery
? p === propertyQueryLower
: p.includes(propertyQueryLower)
);
} else throw new Error('Unsupported query format ' + value);
}
}

function normalizePropertyValues(value: unknown): string[] {
if (Array.isArray(value)) return value.flatMap(normalizePropertyValues);
if (value === null) return ['null'];
if (typeof value === 'string') return [value.toLowerCase()];
if (['boolean', 'number'].includes(typeof value))
return [value.toString().toLowerCase()];
throw new Error('Cannot coerce property: ' + value);
}

// Return whether the given string was quoted, and the unquoted value.
function unquote(s: string): [boolean, string] {
// Match any string, but only capture the first group on balanced quotes.
const match = s.match(/^(['"])?(.*)\1$/);
if (!match) {
throw new Error('Unexpected regex failure when unquoting: ' + s);
}
return [Boolean(match[1]), match[2]];
}

type Suggestion = {
// What to show in the menu, and what to insert if textToInsert doesn't exist
text: string;
Expand Down Expand Up @@ -348,6 +400,11 @@ export class QuerySuggest extends PopoverSuggest<Suggestion> {
textToInsert: 'linkedfrom:""',
cursorOffset: -1,
},
{
text: '[property:value]',
textToInsert: '[:]',
cursorOffset: -2,
},
{ text: 'LOGICAL OPERATORS', group: true },
{ text: 'AND', append: ' ' },
{ text: 'OR', append: ' ' },
Expand Down

0 comments on commit bddd148

Please sign in to comment.