feat: linking to other files from codeblock

This commit is contained in:
Kacper Kula 2025-04-30 12:38:53 +01:00
parent cd0bcfd8e2
commit 03434dca10
9 changed files with 80 additions and 9 deletions

View file

@ -1,3 +1,7 @@
# 0.34.0 (2025-04-30)
- feat: you can now navigate to linked CSV and JSON file sources
- feat: improved support for minimal theme custom classes
# 0.33.0 (2025-04-09)
- fix: better text links for images (by @satkowski)
- fix: lists are now rendered properly (by @satkowski)

View file

@ -1,7 +1,7 @@
{
"id": "sqlseal",
"name": "SQLSeal",
"version": "0.33.0",
"version": "0.34.0",
"minAppVersion": "0.15.0",
"description": "Use SQL in your notes to query your vault files and CSV content.",
"author": "hypersphere",

View file

@ -1,6 +1,6 @@
{
"name": "sqlseal",
"version": "0.33.0",
"version": "0.34.0",
"description": "A plugin for Obsidian that allows you to run SQL queries on your notes.",
"main": "main.js",
"scripts": {

View file

@ -15,6 +15,7 @@ import { SQLSealLangDefinition } from '../grammar/parser';
import { RendererRegistry } from '../renderer/rendererRegistry';
import { Range } from '@codemirror/state';
import { Decorator, highlighterOperation } from '../grammar/highlighterOperation';
import { FilePathWidget } from './widgets/FilePathWidget';
const markDecorations = {
blockFlag: Decoration.mark({ class: 'cm-sqlseal-block-flag' }),
@ -85,12 +86,32 @@ export class SQLSealViewPlugin implements PluginValue {
if (decorations) {
decorations.forEach(dec => {
const decoration = markDecorations[dec.type as keyof typeof markDecorations];
if (decoration) {
builder.push(decoration.range(
if (dec.type === 'filename') {
// Get the actual filename text from the document
const filePath = view.state.doc.sliceString(
contentStart + dec.start,
contentStart + dec.end
);
console.log('FOUND FILENAME', filePath)
// Create widget decoration for the filename
const widget = new FilePathWidget(filePath, this.app);
builder.push(Decoration.replace({
widget,
inclusive: true
}).range(
contentStart + dec.start,
contentStart + dec.end
));
} else {
const decoration = markDecorations[dec.type as keyof typeof markDecorations];
if (decoration) {
builder.push(decoration.range(
contentStart + dec.start,
contentStart + dec.end
));
}
}
});
}

View file

@ -0,0 +1,17 @@
import { WidgetType } from "@codemirror/view";
import { App, Notice } from "obsidian";
export class FilePathWidget extends WidgetType {
constructor(private filePath: string, private app: App) {
super();
}
toDOM() {
const link = document.createElement("a");
link.textContent = this.filePath;
link.href = this.filePath
link.className = "internal-link";
return link;
}
}

View file

@ -11,7 +11,7 @@ const nodes = new Map([
['tableKeyword', { terminal: true, type: 'keyword' }],
['fileOpening', { terminal: true, type: 'function' }],
['tableOpening', { terminal: true, type: 'function' }],
['filename', { terminal: true, type: 'identifier' }],
['filename', { terminal: true, type: 'filename' }],
['digit', { terminal: true, type: 'identifier' }],
['tableDefinitionClosing', { terminal: true, type: 'function' }],
['selectKeyword', { terminal: false, type: 'keyword' }],

View file

@ -37,6 +37,18 @@ describe('Ohm parser', () => {
})
})
it('should parse table expression with name quoted', () => {
expect(parse('TABLE x = file("abcdef.csv")', DEFAULT_VIEWS)).toEqual({
query: '',
tables: [{ arguments: ['abcdef.csv'], type: 'file', tableAlias: 'x' }],
flags: {},
renderer: {
name: 'GRID',
options: ''
}
})
})
it('should parse SELECT statement alone', () => {
expect(parse('SELECT * FROM files', DEFAULT_VIEWS)).toEqual({
query: 'SELECT * FROM files',

View file

@ -34,8 +34,9 @@ export const SQLSealLangDefinition = (views: ViewDefinition[], flags: readonly F
| caseInsensitive<"EXPLAIN"> -- explain
${flags.length ? '| ExtraFlags -- extraFlags' : ''}
TableExpression = tableKeyword identifier "=" TableDefinition
TableDefinition = fileOpening NonemptyListOf<listElement, ","> tableDefinitionClosing -- file
TableDefinition = fileOpening TableFileExpressionArgs tableDefinitionClosing -- file
| tableOpening NonemptyListOf<listElement, ","> tableDefinitionClosing -- mdtable
TableFileExpressionArgs = filename ("," NonemptyListOf<listElement, ",">)?
identifier = (alnum | "_")+
filename = (alnum | "." | "-" | space | "_" | "/" | "\\" | "$" | "[" | "]" | "\"")+
fileOpening = caseInsensitive<"file(">
@ -109,10 +110,18 @@ const generateSemantic = (grammar: ohm.Grammar) => {
},
TableDefinition_file: (_file, args, _close) => {
return {
arguments: args.asIteration().children.map((c: ohm.Node) => c.toObject().trim()),
arguments: args.toObject(),
type: 'file'
}
},
TableFileExpressionArgs: (filename, _sep, rest) => {
// ...rest.asIteration().children.map((c: ohm.Node) => c.toObject().trim())
let remaining = []
if (rest.children.length) {
remaining = rest.children[0].asIteration().children.map((c: ohm.Node) => c.toObject().trim())
}
return [filename.toObject(), ...remaining]
},
TableDefinition_mdtable: (_file, args, _close) => {
return {
arguments: args.asIteration().children.map((c: ohm.Node) => c.toObject().trim()),
@ -136,6 +145,13 @@ const generateSemantic = (grammar: ohm.Grammar) => {
},
listElement_quoted: (_q, value, _q2) => value.sourceString,
listElement_unquoted: (v) => v.sourceString,
filename: (v) => {
const f = v.sourceString
if (f.length && f[0] === '"' && f[f.length - 1] === '"') {
return f.substring(1, f.length - 1)
}
return v.sourceString.trim()
},
_terminal() {
return this.sourceString
}

View file

@ -59,5 +59,6 @@
"0.30.1": "0.15.0",
"0.31.0": "0.15.0",
"0.32.0": "0.15.0",
"0.33.0": "0.15.0"
"0.33.0": "0.15.0",
"0.34.0": "0.15.0"
}