Skip to content

feat(queries): add multi-line queries support #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/plugin/src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const NOTICE_TIMEOUT = 5000;
export const DEFAULT_CANVAS_FILE_NAME = 'Canvas.md';
export const MARKDOWN_FILE_EXTENSION = 'md';

export const QUERY_FLAG_OPEN = `<!-- QueryToSerialize: `;
export const QUERY_FLAG_CLOSE = ` -->`;
export const QUERY_FLAG_OPEN = `<!-- QueryToSerialize:`;
export const QUERY_FLAG_CLOSE = `-->`;

// Query and serialized query structure: <!-- SerializedQuery: QUERY -->\n<markdown>\n<!-- SerializedQuery END -->
export const SERIALIZED_QUERY_START = `<!-- SerializedQuery: `;
Expand Down
17 changes: 5 additions & 12 deletions apps/plugin/src/app/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { DataviewApi } from 'obsidian-dataview/lib/api/plugin-api';
import { add, isBefore } from 'date-fns';
import { serializeQuery } from './utils/serialize-query.fn';
import { findQueries } from './utils/find-queries.fn';
import { escapeRegExp } from './utils/escape-reg-exp.fn';
import { isTableQuery } from './utils/is-table-query.fn';

export class DataviewSerializerPlugin extends Plugin {
Expand Down Expand Up @@ -227,7 +226,7 @@ export class DataviewSerializerPlugin extends Plugin {
//log(`Processing query: [${foundQuery}] in file [${file.path}]`, 'debug');
// Reference: https://github.com/IdreesInc/Waypoint/blob/master/main.ts
const serializedQuery = await serializeQuery({
query: foundQuery,
query: foundQuery.trim(),
originFile: file.path,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dataviewApi: this.dataviewApi!,
Expand All @@ -236,24 +235,18 @@ export class DataviewSerializerPlugin extends Plugin {
//log('Serialized query: ', 'debug', serializedQuery);

if ('' !== serializedQuery) {
const escapedQuery = escapeRegExp(foundQuery);

const queryToSerializeRegex = new RegExp(
`${QUERY_FLAG_OPEN}${escapedQuery}.*${QUERY_FLAG_CLOSE}\\n`,
'gm'
);

let queryAndSerializedQuery = '';
if (isTableQuery(foundQuery)) {
queryAndSerializedQuery = `${QUERY_FLAG_OPEN}${foundQuery}${QUERY_FLAG_CLOSE}\n${SERIALIZED_QUERY_START}${foundQuery}${QUERY_FLAG_CLOSE}\n\n${serializedQuery}${SERIALIZED_QUERY_END}\n`;
queryAndSerializedQuery = `${QUERY_FLAG_OPEN}${foundQuery}${QUERY_FLAG_CLOSE}\n${SERIALIZED_QUERY_START}${QUERY_FLAG_CLOSE}\n\n${serializedQuery}${SERIALIZED_QUERY_END}`;
} else {
queryAndSerializedQuery = `${QUERY_FLAG_OPEN}${foundQuery}${QUERY_FLAG_CLOSE}\n${SERIALIZED_QUERY_START}${foundQuery}${QUERY_FLAG_CLOSE}\n${serializedQuery}${SERIALIZED_QUERY_END}\n`;
queryAndSerializedQuery = `${QUERY_FLAG_OPEN}${foundQuery}${QUERY_FLAG_CLOSE}\n${SERIALIZED_QUERY_START}${QUERY_FLAG_CLOSE}\n${serializedQuery}${SERIALIZED_QUERY_END}`;
}
//log('Query to serialize regex: ', 'debug', queryToSerializeRegex);

//log('Updated text before: ', 'debug', updatedText);
const searchedString = `${QUERY_FLAG_OPEN}${foundQuery}${QUERY_FLAG_CLOSE}`;
updatedText = updatedText.replace(
queryToSerializeRegex,
searchedString,
queryAndSerializedQuery
);
//log('Updated text after: ', 'debug', updatedText);
Expand Down
8 changes: 0 additions & 8 deletions apps/plugin/src/app/utils/escape-reg-exp.fn.ts

This file was deleted.

57 changes: 44 additions & 13 deletions apps/plugin/src/app/utils/find-queries.fn.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
import { QUERY_FLAG_CLOSE, QUERY_FLAG_OPEN } from '../constants';
import { isSupportedQueryType } from './is-supported-query-type.fn';
import { createHash } from 'crypto';

function hashContent(content: string): string {
return createHash('sha256').update(content).digest('hex');
}

/**
* Detect the queries in the given string. Ignores duplicates and ignores unsupported query types
* @param text
*/
export const findQueries = (text: string): string[] => {
const retVal: string[] = [];
const seenHashes = new Set<string>();

let isCapturing = false;
let foundQuery = '';

const lines: string[] = text.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
if (
trimmedLine.startsWith(QUERY_FLAG_OPEN) &&
trimmedLine.endsWith(QUERY_FLAG_CLOSE)
) {
let foundQuery = trimmedLine.replace(QUERY_FLAG_OPEN, '');
foundQuery = foundQuery.replace(QUERY_FLAG_CLOSE, '');
foundQuery = foundQuery.trim();
if (isCapturing) {
if (line.includes(QUERY_FLAG_CLOSE)) {
const endIndex = line.indexOf(QUERY_FLAG_CLOSE);
foundQuery += `${line.substring(0, endIndex)}`;

// Ignore duplicates
// Make sure it is a supported query
if (!retVal.includes(foundQuery) && isSupportedQueryType(foundQuery)) {
retVal.push(foundQuery);
const commentHash = hashContent(foundQuery.trim());

if (isSupportedQueryType(foundQuery) && !seenHashes.has(commentHash)) {
retVal.push(foundQuery);
seenHashes.add(commentHash);
isCapturing = false;
foundQuery = '';
}
} else {
// Accumulate the current line if capturing multi-line query
foundQuery += `${line}\n`;
}
}
}
// Detect QUERY FLAG OPEN and single line comments
if (!isCapturing && line.includes(QUERY_FLAG_OPEN)) {
isCapturing = true;
const startIndex = line.indexOf(QUERY_FLAG_OPEN) + QUERY_FLAG_OPEN.length;
foundQuery = line.substring(startIndex) + '\n';
if (line.includes(QUERY_FLAG_CLOSE)) {
const endIndex = line.indexOf(QUERY_FLAG_CLOSE);
foundQuery = line.substring(startIndex, endIndex);

const commentHash = hashContent(foundQuery.trim());
// Ignore duplicates
// Make sure it is a supported query
if (isSupportedQueryType(foundQuery) && !seenHashes.has(commentHash)) {
retVal.push(foundQuery);
seenHashes.add(commentHash);
isCapturing = false;
foundQuery = '';
}
}
}
}
return retVal;
};
2 changes: 1 addition & 1 deletion apps/plugin/src/app/utils/is-supported-query-type.fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SUPPORTED_QUERY_TYPES } from '../constants';
export const isSupportedQueryType = (query: string): boolean => {
let retVal = false;

const queryLower = query.toLowerCase();
const queryLower = query.trim().toLowerCase();

for (const queryType of SUPPORTED_QUERY_TYPES) {
if (queryLower.startsWith(queryType)) {
Expand Down
2 changes: 1 addition & 1 deletion apps/plugin/src/app/utils/is-table-query.fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { QUERY_TYPE_TABLE } from '../constants';
export const isTableQuery = (query: string): boolean => {
let retVal = false;

const queryLower = query.toLowerCase();
const queryLower = query.trim().toLowerCase();

if (queryLower.startsWith(QUERY_TYPE_TABLE)) {
retVal = true;
Expand Down
18 changes: 15 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,31 @@ If the above line is present in one of your notes, this plugin will detect it, a

```
<!-- QueryToSerialize: LIST FROM #quotes WHERE public_note = true SORT file.name ASC -->
<!-- SerializedQuery: LIST FROM #quotes WHERE public_note = true SORT file.name ASC -->
<!-- SerializedQuery: -->
- [[20 years from now, the only people who will remember that you worked late are your kids]]
- [[A beautiful book is a victory won in all the battlefields of human thought.md|A beautiful book is a victory won in all the battlefields of human thought]]
- [[A busy mind accelerates the perceived passage of time. Buy more time by cultivating peace of mind]]
...
<!-- SerializedQuery: END -->
```

As you can see above, the result of the query gets added as Markdown below the query. Notice that the serialized version is surrounded by `<!-- SerializedQuery: <query> -->` and `<!-- SerializedQuery END -->`. Those allow the plugin to know what to replace. They should not be removed.
As you can see above, the result of the query gets added as Markdown below the query. Notice that the serialized version is surrounded by `<!-- SerializedQuery: -->` and `<!-- SerializedQuery END -->` to delimit the serialized query.

Whenever you update that note, the query will be executed and serialized, replacing the previous serialized version.

WARNING: For now, the queries can only be put on a single line. Take a look at [this issue](https://github.com/dsebastien/obsidian-dataview-serializer/issues/12) for details/updates.
Multiple line queries are now supported, here is an example:
```
<!-- QueryToSerialize:
TABLE
dateformat(release-date, "yyyy-MM-dd") AS "Release Date",
dateformat(started-date, "yyyy-MM-dd") AS "Started Date",
dateformat(finished-date, "yyyy-MM-dd") AS "Finished Date",
rating-out-of-ten + choice(recommended, " ❤️", "") AS "Note"
FROM "Y. Content/Games" OR "03 - Resources/Games"
WHERE file.name != "🏠 Games"
SORT finished-date DESC
-->
```

Note that a single note can include multiple queries. As soon as a file is modified, this plugin reads it and tries to locate queries to serialize. It starts by removing all the serialized queries, recognized by the `<!--SerializedQuery: END -->`line. Then, it serializes all the found queries to Markdown and saves the file again.

Expand Down