mirror of
https://github.com/rooyca/query-json.git
synced 2026-07-22 10:10:29 +00:00
update(feat): query in codeblocks
- Now we are using MarkdownPostProcessor instead of workspace.on('editor-change')
- Now is posible to query elements inside an array using numbers
This commit is contained in:
parent
5d6f8ee710
commit
451a5bcd27
6 changed files with 255 additions and 46 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea
|
||||
.idea*
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
|
|
|
|||
34
README.md
34
README.md
|
|
@ -72,6 +72,18 @@ Furthermore, QJSON extends its functionality to external JSON files. For example
|
|||
@data.json>store.books.0.author;
|
||||
```
|
||||
|
||||
It's also posible to query the file directly using the `#qj-query` flag:
|
||||
|
||||
~~~markdown
|
||||
```qjson
|
||||
#qj-id: 24
|
||||
#qj-file: data.json
|
||||
#qj-show-json
|
||||
#qj-hide-id
|
||||
#qj-query: pageProps.heroData[win_rate >= 55 && role == Mage]
|
||||
```
|
||||
~~~
|
||||
|
||||
## 🏳️ Flags
|
||||
|
||||
Query JSON supports various flags to enhance customization and functionality:
|
||||
|
|
@ -96,6 +108,28 @@ This flag allows you to display the JSON within the rendered output. By default
|
|||
|
||||
If provided, this flag specifies the file path containing the JSON data. In its absence, the plugin scans for JSON data within the code block.
|
||||
|
||||
#### `#qj-query` (optional)
|
||||
|
||||
This flag allows you to query the JSON file directly from your codeblock. The query syntax must be inside brackets `[]`. The supported operators are:
|
||||
|
||||
- Logical operators: `&&`, `||`
|
||||
- Comparison operators: `==`, `!=`, `>`, `>=`, `<`, `<=`
|
||||
|
||||
**Example**:
|
||||
|
||||
~~~markdown
|
||||
```qjson
|
||||
#qj-id: 24
|
||||
#qj-file: data.json
|
||||
#qj-show-json
|
||||
#qj-hide-id
|
||||
#qj-query: pageProps.heroData[win_rate >= 55 && role == Mage]
|
||||
```
|
||||
~~~
|
||||
|
||||
> [!NOTE]
|
||||
> The `#qj-show-json` flag is mandatory when using the `#qj-query` flag.
|
||||
|
||||
## 🛠️ Contribution
|
||||
|
||||
If you encounter any issues or have suggestions for improvement, please feel free to contribute to the project. Your feedback is invaluable in enhancing the plugin's functionality and user experience.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "query-json",
|
||||
"name": "Query JSON",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Read, query and work with JSON.",
|
||||
"author": "rooyca",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "query-json",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"description": "Read, query and work with JSON.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
121
src/functions.ts
Normal file
121
src/functions.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
export function parseQuery(query) {
|
||||
const parts = query.split('.');
|
||||
return parts.map(part => {
|
||||
const match = part.match(/(\w+)\[(.*)\]/);
|
||||
if (match) {
|
||||
return {
|
||||
type: 'filter',
|
||||
field: match[1],
|
||||
condition: match[2]
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'field',
|
||||
name: part
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function parseCondition(condition) {
|
||||
const logicalOperators = ['&&', '||'];
|
||||
let conditions = [];
|
||||
let operators = [];
|
||||
|
||||
let remainingCondition = condition;
|
||||
logicalOperators.forEach(operator => {
|
||||
if (remainingCondition.includes(operator)) {
|
||||
const parts = remainingCondition.split(operator).map(s => s.trim());
|
||||
conditions.push(parseSingleCondition(parts[0]));
|
||||
operators.push(operator);
|
||||
remainingCondition = parts[1];
|
||||
}
|
||||
});
|
||||
|
||||
if (conditions.length === 0) {
|
||||
conditions.push(parseSingleCondition(remainingCondition));
|
||||
} else {
|
||||
conditions.push(parseSingleCondition(remainingCondition));
|
||||
}
|
||||
|
||||
return { conditions, operators };
|
||||
}
|
||||
|
||||
function parseSingleCondition(condition) {
|
||||
const comparisonOperators = ['>=', '<=', '==', '>', '<', '!='];
|
||||
for (let operator of comparisonOperators) {
|
||||
if (condition.includes(operator)) {
|
||||
const [key, value] = condition.split(operator).map(s => s.trim());
|
||||
return { key, operator, value };
|
||||
}
|
||||
}
|
||||
throw new Error(`Invalid condition: ${condition}`);
|
||||
}
|
||||
|
||||
export function executeQuery(json, parsedQuery) {
|
||||
let result = json;
|
||||
let finalResult;
|
||||
|
||||
parsedQuery.forEach(part => {
|
||||
if (part.type === 'field') {
|
||||
result = result[part.name];
|
||||
} else if (part.type === 'filter') {
|
||||
const { conditions, operators } = parseCondition(part.condition);
|
||||
const filteredResult = result[part.field].filter(item => {
|
||||
return evaluateConditions(item, conditions, operators);
|
||||
});
|
||||
// Store the filtered result in finalResult
|
||||
if (!finalResult) {
|
||||
finalResult = filteredResult;
|
||||
} else {
|
||||
// Merge the filtered result with finalResult
|
||||
finalResult = finalResult.concat(filteredResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If no filter conditions were applied, finalResult will be undefined, so set it to the original result
|
||||
finalResult = finalResult || result;
|
||||
|
||||
try {
|
||||
if (parsedQuery.length > 0 && parsedQuery[parsedQuery.length - 1].type === 'field') {
|
||||
// Map only the required field from finalResult if the last part of the query is a 'field' type
|
||||
finalResult = finalResult.map(item => item[parsedQuery[parsedQuery.length - 1].name]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Handle any potential errors during mapping
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
function evaluateConditions(item, conditions, operators) {
|
||||
let result = evaluateCondition(item, conditions[0]);
|
||||
|
||||
for (let i = 0; i < operators.length; i++) {
|
||||
const operator = operators[i];
|
||||
const nextConditionResult = evaluateCondition(item, conditions[i + 1]);
|
||||
|
||||
if (operator === '&&') {
|
||||
result = result && nextConditionResult;
|
||||
} else if (operator === '||') {
|
||||
result = result || nextConditionResult;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function evaluateCondition(item, condition) {
|
||||
const { key, operator, value } = condition;
|
||||
switch (operator) {
|
||||
case '>=': return item[key] >= Number(value);
|
||||
case '<=': return item[key] <= Number(value);
|
||||
case '==': return item[key] == value;
|
||||
case '>': return item[key] > Number(value);
|
||||
case '<': return item[key] < Number(value);
|
||||
case '!=': return item[key] != value;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
140
src/main.ts
140
src/main.ts
|
|
@ -1,4 +1,5 @@
|
|||
import { App, Notice, Plugin } from 'obsidian';
|
||||
import { parseQuery, executeQuery} from './functions';
|
||||
|
||||
export default class QJSON extends Plugin {
|
||||
|
||||
|
|
@ -23,6 +24,13 @@ export default class QJSON extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
let query;
|
||||
|
||||
if (source.includes('#qj-query:')) {
|
||||
query = source.match(/#qj-query: (.+)/)[1];
|
||||
query = parseQuery(query);
|
||||
}
|
||||
|
||||
let desc;
|
||||
|
||||
if (!source.includes('#qj-hide-id')) {
|
||||
|
|
@ -48,65 +56,111 @@ export default class QJSON extends Plugin {
|
|||
}
|
||||
|
||||
const json = JSON.parse(source);
|
||||
el.createEl('pre', {text: JSON.stringify(json, null, 2), cls: 'QJSON-'+id+' cdQjson '+showJson});
|
||||
|
||||
if (query) {
|
||||
const result = executeQuery(json, query);
|
||||
el.createEl('pre', {text: JSON.stringify(result, null, 2), cls: 'QJSON-'+id+' cdQjson '+showJson});
|
||||
} else {
|
||||
el.createEl('pre', {text: JSON.stringify(json, null, 2), cls: 'QJSON-'+id+' cdQjson '+showJson});
|
||||
}
|
||||
|
||||
qjCount = document.querySelectorAll('.cdQjson').length;
|
||||
statusBarItemEl.setText('QJSON: ' + qjCount);
|
||||
});
|
||||
|
||||
this.registerEvent(this.app.workspace.on('editor-change', async (editor, info) => {
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
// this.registerEvent(this.app.workspace.on('editor-change', async (editor, info) => {
|
||||
// const cursor = editor.getCursor();
|
||||
// const line = editor.getLine(cursor.line);
|
||||
|
||||
const lastChar = line[line.length - 1];
|
||||
// const lastChar = line[line.length - 1];
|
||||
|
||||
const match = line.match(/@(.+)>(.+)|@(.+)>/);
|
||||
if (!match) return;
|
||||
// const match = line.match(/@(.+)>(.+)|@(.+)>/);
|
||||
// if (!match) return;
|
||||
|
||||
if (lastChar !== ';' && lastChar !== '.' && lastChar !== '>') return;
|
||||
// if (lastChar !== ';' && lastChar !== '.' && lastChar !== '>') return;
|
||||
|
||||
const id = match[1] || match[3];
|
||||
let path = "";
|
||||
// const id = match[1] || match[3];
|
||||
// let path = "";
|
||||
|
||||
if (match[2] !== undefined) {
|
||||
path = match[2].slice(0, -1).replace(/>/, '')
|
||||
}
|
||||
// if (match[2] !== undefined) {
|
||||
// path = match[2].slice(0, -1).replace(/>/, '')
|
||||
// }
|
||||
|
||||
let json;
|
||||
// let json;
|
||||
|
||||
if (!isNaN(parseInt(id)) && !id.includes('.json')) {
|
||||
const el = document.querySelector('.QJSON-' + id);
|
||||
if (!el) return;
|
||||
json = JSON.parse(el.innerText);
|
||||
} else {
|
||||
json = JSON.parse(await this.app.vault.adapter.read(id));
|
||||
}
|
||||
// if (!isNaN(parseInt(id)) && !id.includes('.json')) {
|
||||
// const el = document.querySelector('.QJSON-' + id);
|
||||
// if (!el) return;
|
||||
// json = JSON.parse(el.innerText);
|
||||
// } else {
|
||||
// json = JSON.parse(await this.app.vault.adapter.read(id));
|
||||
// }
|
||||
|
||||
const value = getJSONPath(json, path);
|
||||
// const value = getJSONPath(json, path);
|
||||
|
||||
if (lastChar !== ';') {
|
||||
if (value !== undefined) {
|
||||
const notice = document.querySelector('.notice');
|
||||
if (notice) notice.remove();
|
||||
// if (lastChar !== ';') {
|
||||
// if (value !== undefined) {
|
||||
// const notice = document.querySelector('.notice');
|
||||
// if (notice) notice.remove();
|
||||
|
||||
const keys = Object.keys(value);
|
||||
const keysAreNumbers = keys.every(key => !isNaN(parseInt(key)));
|
||||
// const keys = Object.keys(value);
|
||||
// const keysAreNumbers = keys.every(key => !isNaN(parseInt(key)));
|
||||
|
||||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
||||
new Notice(value.toString());
|
||||
} else if (keysAreNumbers) {
|
||||
new Notice('Total Keys: ' + (keys.length- 1 ));
|
||||
} else {
|
||||
new Notice('Total Keys: ' + keys.length + '\n' + '____________' + '\n' + keys.join('\n'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const atIndex = line.indexOf('@');
|
||||
const replaceEnd = { line: cursor.line, ch: line.length }; // Replace to end of line
|
||||
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
editor.replaceRange(stringValue, { line: cursor.line, ch: atIndex }, replaceEnd);
|
||||
}
|
||||
}));
|
||||
// if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
||||
// new Notice(value.toString());
|
||||
// } else if (keysAreNumbers) {
|
||||
// new Notice('Total Keys: ' + (keys.length- 1 ));
|
||||
// } else {
|
||||
// new Notice('Total Keys: ' + keys.length + '\n' + '____________' + '\n' + keys.join('\n'));
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// const atIndex = line.indexOf('@');
|
||||
// const replaceEnd = { line: cursor.line, ch: line.length }; // Replace to end of line
|
||||
// const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
// editor.replaceRange(stringValue, { line: cursor.line, ch: atIndex }, replaceEnd);
|
||||
// }
|
||||
// }));
|
||||
|
||||
this.registerMarkdownPostProcessor( async (element, context) => {
|
||||
const codeblocks = element.findAll("code");
|
||||
|
||||
for (let codeblock of codeblocks) {
|
||||
if (codeblock.classList.length >= 1) return;
|
||||
|
||||
const text = codeblock.innerText;
|
||||
const regex = /@(.+)\.json>(.+);/;
|
||||
const match = text.match(regex);
|
||||
|
||||
if (match) {
|
||||
let result;
|
||||
|
||||
try {
|
||||
let file = await this.app.vault.adapter.read(match[1] + ".json");
|
||||
file = JSON.parse(file);
|
||||
result = getJSONPath(file, match[2]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
new Notice("Error! Something went wrong!");
|
||||
result = "Error!";
|
||||
}
|
||||
|
||||
let stringResult;
|
||||
let tagName;
|
||||
|
||||
if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
|
||||
stringResult = result;
|
||||
tagName = "span";
|
||||
} else {
|
||||
stringResult = JSON.stringify(result, null, 2);
|
||||
tagName = "pre";
|
||||
}
|
||||
|
||||
const resultEl = codeblock.createEl(tagName, {text: stringResult});
|
||||
codeblock.replaceWith(resultEl);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue