From 0447368f53f9f410eee3de4554b20ceee122e9a6 Mon Sep 17 00:00:00 2001 From: Kacper Kula Date: Wed, 2 Apr 2025 10:40:00 +0100 Subject: [PATCH 1/4] wip --- src/cellParser/CellFunction.ts | 1 + src/cellParser/ModernCellParser.ts | 42 +++++++++++- src/cellParser/parseResults.ts | 10 +++ src/cellParser/parser/checkbox.ts | 15 +++++ src/cellParser/parser/image.ts | 36 ++++++++-- src/cellParser/parser/link.ts | 102 ++++++++++++++++++++--------- src/main.ts | 2 +- src/renderer/MarkdownRenderer.ts | 11 +++- src/renderer/TemplateRenderer.ts | 4 +- 9 files changed, 181 insertions(+), 42 deletions(-) diff --git a/src/cellParser/CellFunction.ts b/src/cellParser/CellFunction.ts index 753159c..d0b5983 100644 --- a/src/cellParser/CellFunction.ts +++ b/src/cellParser/CellFunction.ts @@ -4,4 +4,5 @@ export interface CellFunction { get name(): string; get sqlFunctionArgumentsCount(): number; prepare(content: T): CellParserResult; + renderAsString(content: T): string; } \ No newline at end of file diff --git a/src/cellParser/ModernCellParser.ts b/src/cellParser/ModernCellParser.ts index 2751c89..43e45d4 100644 --- a/src/cellParser/ModernCellParser.ts +++ b/src/cellParser/ModernCellParser.ts @@ -40,6 +40,8 @@ export class ModernCellParser { } } + // If it is link string, use "a" renderer. + return content } catch (e) { console.error('Error parsing cell with data:', content, e) @@ -64,6 +66,39 @@ export class ModernCellParser { } } + renderAsString(content: string) { + try { + if (!content) { + return content + } + if (typeof content !== 'string') { + return content + } + if (content.startsWith('SQLSEALCUSTOM')) { + const parsedData = parse(content.slice('SQLSEALCUSTOM('.length, -1)) + const renderer = this.getRenderer(parsedData) + return renderer!.renderAsString(parsedData.values) + } + + // FIXME: bring this one back + // If it's an array, decode it and render each individual elements + if (isStringifiedArray(content)) { + try { + const parsed: string[] = parse(content) + return parsed.map(p => this.renderer) + return renderStringifiedArray(content) + } catch (e) { + return content + } + } + + return content + } catch (e) { + console.error('Error parsing cell with data:', content, e) + return 'Parsing Error' + } + } + register(fn: CellFunction) { this.functions.set(fn.name, fn) } @@ -77,8 +112,13 @@ export class ModernCellParser { } private renderCustomElement(el: any) { + const renderer = this.getRenderer(el) + return renderer?.prepare(el.values) + } + + private getRenderer(el: any) { if (this.functions.has(el.type)) { - return this.functions.get(el.type)!.prepare(el.values) + return this.functions.get(el.type)! } else { throw new Error(`Custom function processor ${el.type} is not registered`) } diff --git a/src/cellParser/parseResults.ts b/src/cellParser/parseResults.ts index 39fc058..d26811e 100644 --- a/src/cellParser/parseResults.ts +++ b/src/cellParser/parseResults.ts @@ -57,6 +57,16 @@ export class ParseResults { }) } + renderAsString(data: Record[], columns: string[]) { + return data.map(d => { + const res: Record = {} + for (const col of columns) { + res[col] = this.cellParser.renderAsString(d[col]) + } + return res + }) + } + initialise(parentEl: HTMLElement) { this.functions.forEach(fn => fn(parentEl)) } diff --git a/src/cellParser/parser/checkbox.ts b/src/cellParser/parser/checkbox.ts index 1fafd6c..61fcfd9 100644 --- a/src/cellParser/parser/checkbox.ts +++ b/src/cellParser/parser/checkbox.ts @@ -90,4 +90,19 @@ export class CheckboxParser implements CellFunction { } } } + + renderAsString(values: Args): string { + if (!isCheckboxProp(values)) { + if (!!values[0]) { + return '[x]' + } + return '[ ]' + } else { + if (values.checked) { + return '[x]' + } else { + return '[ ] ' + } + } + } } \ No newline at end of file diff --git a/src/cellParser/parser/image.ts b/src/cellParser/parser/image.ts index 376264e..38abd17 100644 --- a/src/cellParser/parser/image.ts +++ b/src/cellParser/parser/image.ts @@ -17,10 +17,7 @@ export class ImageParser implements CellFunction { return 2 // FIXME: should it be 1 or 2? } - prepare([href, path]: Args): CellParserResult { - if (!isLinkLocal(href)) { - return createEl('img', { attr: { src: href } }); - } + getResourcePath(href: string, path?: string) { href = (href ?? '').trim() if (href.startsWith('![[')) { href = href.slice(3, -2) @@ -35,8 +32,35 @@ export class ImageParser implements CellFunction { path = (path ? parentPath + '/' : '') + href const file = this.app.vault.getFileByPath(path); if (!file) { - return 'File does not exist' + throw new Error('File does not exist') } - return this.create('img', { attr: { src: this.app.vault.getResourcePath(file) } }); + return this.app.vault.getResourcePath(file) + } + + prepare([href, path]: Args): CellParserResult { + if (!isLinkLocal(href)) { + return createEl('img', { attr: { src: href } }); + } + try { + let resourcePath = this.getResourcePath(href, path) + return this.create('img', { attr: { src: resourcePath } }); + + } catch (e) { + return e + } + } + + renderAsString([href, path]: Args): string { + if (!isLinkLocal(href)) { + return `![](${href})` + } + + try { + const resourcePath = this.getResourcePath(href, path) + return `![[${resourcePath}]]` + } catch (e) { + return e.toString() + } + } } \ No newline at end of file diff --git a/src/cellParser/parser/link.ts b/src/cellParser/parser/link.ts index 6ae1141..541cca1 100644 --- a/src/cellParser/parser/link.ts +++ b/src/cellParser/parser/link.ts @@ -3,6 +3,7 @@ import { CellFunction } from "../CellFunction"; import { CellParserResult, Result } from "../ModernCellParser"; import { App } from "obsidian"; import { isLinkLocal, isLinktext, removeExtension } from "src/utils/ui/helperFunctions"; +import { parse } from 'json5' type Args = [string] | [string, string] @@ -18,6 +19,44 @@ export class LinkParser implements CellFunction { return 2 // FIXME: should it be 1 or 2? } + parseLink(href: string, name?: string) { + if (!name) { + name = href + } + href = href.trim() + let cls = '' + if (isLinktext(href)) { + const [path, linkName] = href.slice(2, -2).split('|') + const link = this.app.metadataCache.getFirstLinkpathDest(path, '') + + if (name.trim() === href) { + name = linkName ?? path + } + + if (!link) { + href = path + } else { + href = link.path + } + cls = 'internal-link' + + } else if (isLinkLocal(href)) { + let fileHref = href + if (fileHref.endsWith('.md')) { + fileHref = fileHref.slice(0, -3) + } + if (name === href) { + const components = href.split('/') + name = removeExtension(components[components.length - 1]) + } + cls = 'internal-link' + } + + return { + name, href, cls + } + } + prepare([href, name]: Args) { if (!href) { return '' @@ -25,44 +64,47 @@ export class LinkParser implements CellFunction { if (isStringifiedArray(href)) { try { + // FIXME: this return should work differently renderStringifiedArray(href, href => this.prepare([href])) } catch (e) { return href } } + const res = this.parseLink(href, name) + const el = this.create('a', { text: res.name, href: res.href, cls: res.cls }) + return el + } - if (!name) { - name = href + renderAsString([href, name]: Args): string { + if (!href) { + return '' } - href = href.trim() - let cls = '' - if (isLinktext(href)) { - const [path, linkName] = href.slice(2, -2).split('|') - const link = this.app.metadataCache.getFirstLinkpathDest(path, '') - if (name.trim() === href) { - name = linkName ?? path - } - - if (!link) { - href = path - } else { - href = link.path - } - cls = 'internal-link' - - } else if (isLinkLocal(href)) { - let fileHref = href - if (fileHref.endsWith('.md')) { - fileHref = fileHref.slice(0, -3) - } - if (name === href) { - const components = href.split('/') - name = removeExtension(components[components.length - 1]) - } - cls = 'internal-link' + // if (isStringifiedArray(href)) { + // try { + // // FIXME: parse and run render as strings + // const arr: string[] = parse(href) + + // return arr.map(e => this.renderAsString(e)) + + // const res = renderStringifiedArray(href, href => this.prepare([href])) + // if (res instanceof HTMLElement) { + // return res.outerHTML + // } + // return res + // } catch (e) { + // return href + // } + // } + + const res = this.parseLink(href, name) + if (res.cls == 'internal-link') { + if (!name) { + return `[[${res.href}]]` } - const el = this.create('a', { text: name, href, cls }) - return el + return `[[${res.href}|${res.name}]]` + } else { + return `[${res.name}](${res.href})` + } } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 6f1b3f0..ba31960 100644 --- a/src/main.ts +++ b/src/main.ts @@ -60,7 +60,7 @@ export default class SqlSealPlugin extends Plugin { this.rendererRegistry.register('sql-seal-internal-table', new TableRenderer(this.app, this.cellParser)) this.rendererRegistry.register('sql-seal-internal-grid', new GridRenderer(this.app, this, this.cellParser)) - this.rendererRegistry.register('sql-seal-internal-markdown', new MarkdownRenderer(this.app)) + this.rendererRegistry.register('sql-seal-internal-markdown', new MarkdownRenderer(this.app, this.cellParser)) this.rendererRegistry.register('sql-seal-internal-list', new ListRenderer(this.app, this.cellParser)) this.rendererRegistry.register('sql-seal-internal-template', new TemplateRenderer(this.app, this.cellParser)) diff --git a/src/renderer/MarkdownRenderer.ts b/src/renderer/MarkdownRenderer.ts index 99386cc..3a93ee8 100644 --- a/src/renderer/MarkdownRenderer.ts +++ b/src/renderer/MarkdownRenderer.ts @@ -4,6 +4,8 @@ import { App } from "obsidian"; import { RendererConfig } from "../renderer/rendererRegistry"; import { displayError } from "../utils/ui"; import { ViewDefinition } from "../grammar/parser"; +import { ParseResults } from "../cellParser/parseResults"; +import { ModernCellParser } from "../cellParser/ModernCellParser"; const mapDataFromHeaders = (columns: string[], data: Record[]) => { return data.map(d => columns.map(c => String(d[c]))) @@ -11,7 +13,12 @@ const mapDataFromHeaders = (columns: string[], data: Record[]) => { export class MarkdownRenderer implements RendererConfig { - constructor(private readonly app: App) { } + parseResult: ParseResults; + + constructor(private readonly app: App, private readonly cellParser: ModernCellParser) { + this.parseResult = new ParseResults(cellParser) + + } get rendererKey() { return 'markdown' @@ -34,7 +41,7 @@ export class MarkdownRenderer implements RendererConfig { const tab = getMarkdownTable({ table: { head: columns, - body: mapDataFromHeaders(columns, data) + body: mapDataFromHeaders(columns, this.parseResult.renderAsString(data, columns)) } }) diff --git a/src/renderer/TemplateRenderer.ts b/src/renderer/TemplateRenderer.ts index 2309cd3..a0e9209 100644 --- a/src/renderer/TemplateRenderer.ts +++ b/src/renderer/TemplateRenderer.ts @@ -4,8 +4,8 @@ import { RendererConfig } from "./rendererRegistry"; import { displayError } from "../utils/ui"; import { ViewDefinition } from "../grammar/parser"; import Handlebars from "handlebars"; -import { ParseResults } from "src/cellParser/parseResults"; -import { ModernCellParser } from "src/cellParser/ModernCellParser"; +import { ParseResults } from "../cellParser/parseResults"; +import { ModernCellParser } from "../cellParser/ModernCellParser"; interface TemplateRendererConfig { template: HandlebarsTemplateDelegate From 053059a661242f4d84e8a2fbcc27cf6b41ae7de1 Mon Sep 17 00:00:00 2001 From: Kacper Kula Date: Wed, 2 Apr 2025 15:14:36 +0100 Subject: [PATCH 2/4] chore: refactoring renderers and adding markdown text renderers --- src/cellParser/ModernCellParser.ts | 57 ++++++++++++++++--- src/cellParser/parser/image.ts | 2 +- src/cellParser/parser/link.ts | 22 +------ src/codeblockHandler/CodeblockProcessor.ts | 7 ++- .../syncStrategy/CsvFileSyncStrategy.ts | 2 +- src/main.ts | 10 ++-- src/pluginApi/sqlSealApi.ts | 2 +- src/renderer/GridRenderer.ts | 24 +++++--- src/renderer/ListRenderer.ts | 9 ++- src/renderer/MarkdownRenderer.ts | 14 ++--- src/renderer/TableRenderer.ts | 8 +-- src/renderer/TemplateRenderer.ts | 17 +++--- src/renderer/rendererRegistry.ts | 11 +++- src/view/CSVView.ts | 4 +- 14 files changed, 110 insertions(+), 79 deletions(-) diff --git a/src/cellParser/ModernCellParser.ts b/src/cellParser/ModernCellParser.ts index 43e45d4..22ad288 100644 --- a/src/cellParser/ModernCellParser.ts +++ b/src/cellParser/ModernCellParser.ts @@ -1,7 +1,7 @@ import { CellFunction } from "./CellFunction"; import { parse } from 'json5' import { isStringifiedArray, renderStringifiedArray } from '../utils/ui' -import { SqlSealDatabase } from "src/database/database"; +import { SqlSealDatabase } from "../database/database"; export type UnregisterCallback = () => void; @@ -34,13 +34,39 @@ export class ModernCellParser { // If it's an array, decode it and render each individual elements if (isStringifiedArray(content)) { try { - return renderStringifiedArray(content) + const parsed: string[] = parse(content) + const container = createSpan() + parsed.forEach((p, i) => { + const rendered = this.render(p) + if (rendered instanceof HTMLElement) { + container.appendChild(rendered) + if (i < parsed.length -1) { + container.appendChild(document.createTextNode(', ')) + } + } + }) + return container } catch (e) { return content } } - // If it is link string, use "a" renderer. + // If it is a wikilink or markdown link, use "a" renderer + if (content.startsWith('[[') && content.endsWith(']]')) { + const renderer = this.functions.get('a'); + if (renderer) { + const [link, name] = content.slice(2, -2).split('|'); + return renderer.prepare([link, name]); + } + } else if (content.startsWith('[') && content.includes('](') && content.endsWith(')')) { + const renderer = this.functions.get('a'); + if (renderer) { + const closingBracketIndex = content.indexOf(']('); + const name = content.slice(1, closingBracketIndex); + const href = content.slice(closingBracketIndex + 2, -1); + return renderer.prepare([href, name]); + } + } return content } catch (e) { @@ -66,7 +92,7 @@ export class ModernCellParser { } } - renderAsString(content: string) { + renderAsString(content: string): string { try { if (!content) { return content @@ -85,10 +111,27 @@ export class ModernCellParser { if (isStringifiedArray(content)) { try { const parsed: string[] = parse(content) - return parsed.map(p => this.renderer) - return renderStringifiedArray(content) + return parsed.map(p => this.renderAsString(p)).join(', ') } catch (e) { - return content + return content.toString() + } + } + + + // If it is a wikilink or markdown link, use "a" renderer + if (content.startsWith('[[') && content.endsWith(']]')) { + const renderer = this.functions.get('a'); + if (renderer) { + const [link, name] = content.slice(2, -2).split('|'); + return renderer.renderAsString([link, name]); + } + } else if (content.startsWith('[') && content.includes('](') && content.endsWith(')')) { + const renderer = this.functions.get('a'); + if (renderer) { + const closingBracketIndex = content.indexOf(']('); + const name = content.slice(1, closingBracketIndex); + const href = content.slice(closingBracketIndex + 2, -1); + return renderer.renderAsString([href, name]); } } diff --git a/src/cellParser/parser/image.ts b/src/cellParser/parser/image.ts index 38abd17..e201864 100644 --- a/src/cellParser/parser/image.ts +++ b/src/cellParser/parser/image.ts @@ -1,7 +1,7 @@ +import { isLinkLocal } from "../../utils/ui/helperFunctions"; import { CellFunction } from "../CellFunction"; import { CellParserResult } from "../ModernCellParser"; import { App } from "obsidian"; -import { isLinkLocal } from "src/utils/ui/helperFunctions"; type Args = [string] | [string, string] diff --git a/src/cellParser/parser/link.ts b/src/cellParser/parser/link.ts index 541cca1..2e5745e 100644 --- a/src/cellParser/parser/link.ts +++ b/src/cellParser/parser/link.ts @@ -1,9 +1,7 @@ import { isStringifiedArray, renderStringifiedArray } from "../../utils/ui"; +import { isLinkLocal, isLinktext, removeExtension } from "../../utils/ui/helperFunctions"; import { CellFunction } from "../CellFunction"; -import { CellParserResult, Result } from "../ModernCellParser"; import { App } from "obsidian"; -import { isLinkLocal, isLinktext, removeExtension } from "src/utils/ui/helperFunctions"; -import { parse } from 'json5' type Args = [string] | [string, string] @@ -79,24 +77,6 @@ export class LinkParser implements CellFunction { if (!href) { return '' } - - // if (isStringifiedArray(href)) { - // try { - // // FIXME: parse and run render as strings - // const arr: string[] = parse(href) - - // return arr.map(e => this.renderAsString(e)) - - // const res = renderStringifiedArray(href, href => this.prepare([href])) - // if (res instanceof HTMLElement) { - // return res.outerHTML - // } - // return res - // } catch (e) { - // return href - // } - // } - const res = this.parseLink(href, name) if (res.cls == 'internal-link') { if (!name) { diff --git a/src/codeblockHandler/CodeblockProcessor.ts b/src/codeblockHandler/CodeblockProcessor.ts index 5b76cea..8b3bc01 100644 --- a/src/codeblockHandler/CodeblockProcessor.ts +++ b/src/codeblockHandler/CodeblockProcessor.ts @@ -69,7 +69,12 @@ export class CodeblockProcessor extends MarkdownRenderChild { rendererEl = this.el.createDiv({ cls: 'sqlseal-renderer-container' }) } - this.renderer = this.rendererRegistry.prepareRender(results.renderer.type.toLowerCase(), results.renderer.options)(rendererEl) + this.renderer = this.rendererRegistry + .prepareRender( + results.renderer.type.toLowerCase(), results.renderer.options + )(rendererEl, { + cellParser: this.plugin.cellParser + }) // FIXME: probably should save the one before transform and perform transform every time we execute it. this.query = results.query diff --git a/src/datamodel/syncStrategy/CsvFileSyncStrategy.ts b/src/datamodel/syncStrategy/CsvFileSyncStrategy.ts index 7439e57..baf00a4 100644 --- a/src/datamodel/syncStrategy/CsvFileSyncStrategy.ts +++ b/src/datamodel/syncStrategy/CsvFileSyncStrategy.ts @@ -56,7 +56,7 @@ export class CsvFileSyncStrategy extends ISyncStrategy { // TODO: PROBABLY SHOULD BE EXTRACTED SOMEWHERE FROM HERE later. const parsed = parse>(data, { header: true, - dynamicTyping: true, + dynamicTyping: false, skipEmptyLines: true, transformHeader: sanitise }) diff --git a/src/main.ts b/src/main.ts index ba31960..20792a6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -58,11 +58,11 @@ export default class SqlSealPlugin extends Plugin { // REGISTERING VIEWS - this.rendererRegistry.register('sql-seal-internal-table', new TableRenderer(this.app, this.cellParser)) - this.rendererRegistry.register('sql-seal-internal-grid', new GridRenderer(this.app, this, this.cellParser)) - this.rendererRegistry.register('sql-seal-internal-markdown', new MarkdownRenderer(this.app, this.cellParser)) - this.rendererRegistry.register('sql-seal-internal-list', new ListRenderer(this.app, this.cellParser)) - this.rendererRegistry.register('sql-seal-internal-template', new TemplateRenderer(this.app, this.cellParser)) + this.rendererRegistry.register('sql-seal-internal-table', new TableRenderer(this.app)) + this.rendererRegistry.register('sql-seal-internal-grid', new GridRenderer(this.app, this)) + this.rendererRegistry.register('sql-seal-internal-markdown', new MarkdownRenderer(this.app)) + this.rendererRegistry.register('sql-seal-internal-list', new ListRenderer(this.app)) + this.rendererRegistry.register('sql-seal-internal-template', new TemplateRenderer(this.app)) // start syncing when files are loaded this.app.workspace.onLayoutReady(async () => { diff --git a/src/pluginApi/sqlSealApi.ts b/src/pluginApi/sqlSealApi.ts index f159c53..47347b0 100644 --- a/src/pluginApi/sqlSealApi.ts +++ b/src/pluginApi/sqlSealApi.ts @@ -4,7 +4,7 @@ import { version } from '../../package.json' import { RendererConfig } from "../renderer/rendererRegistry"; import { CellFunction } from "../cellParser/CellFunction"; -const API_VERSION = 3; +const API_VERSION = 4; export class SQLSealRegisterApi { registeredApis: Array = [] diff --git a/src/renderer/GridRenderer.ts b/src/renderer/GridRenderer.ts index 2983331..aa34120 100644 --- a/src/renderer/GridRenderer.ts +++ b/src/renderer/GridRenderer.ts @@ -1,11 +1,11 @@ import { createGrid, GridApi, GridOptions, themeQuartz } from "ag-grid-community"; import { merge } from "lodash"; import { App } from "obsidian"; -import { RendererConfig } from "./rendererRegistry"; +import { RendererConfig, RendererContext } from "./rendererRegistry"; import { parse } from 'json5' import { ViewDefinition } from "../grammar/parser"; import SqlSealPlugin from "../main"; -import { ModernCellParser } from "src/cellParser/ModernCellParser"; +import { ModernCellParser } from "../cellParser/ModernCellParser"; const getCurrentTheme = () => { return document.body.classList.contains('theme-dark') ? 'dark' : 'light'; @@ -36,10 +36,14 @@ class GridRendererCommunicator { this.initialize() } - private gridApi: GridApi + private _gridApi: GridApi private errorEl: HTMLElement private errorOverlay: HTMLElement + get gridApi(): GridApi { + return this._gridApi + } + private showError(message: string) { this.gridApi.setGridOption('loading', false) this.errorEl.textContent = message //.replace(`TTT${prefix}_`, ''); @@ -76,8 +80,9 @@ class GridRendererCommunicator { autoHeight: true }, autoSizeStrategy: { + // make sure to fit content type: 'fitGridWidth', - defaultMinWidth: 150, + // defaultMinWidth: 150, }, pagination: true, suppressMovableColumns: true, @@ -89,7 +94,7 @@ class GridRendererCommunicator { paginationPageSize: this.plugin? this.plugin.settings.gridItemsPerPage : undefined, // ensureDomOrder: true }, this.config) - this.gridApi = createGrid( + this._gridApi = createGrid( grid, gridOptions, ); @@ -118,7 +123,7 @@ class GridRendererCommunicator { } export class GridRenderer implements RendererConfig { - constructor(private app: App, private readonly plugin: SqlSealPlugin | null, private readonly cellParser: ModernCellParser) { } + constructor(private app: App, private readonly plugin: SqlSealPlugin | null) { } get viewDefinition(): ViewDefinition { return { name: this.rendererKey, @@ -140,12 +145,13 @@ export class GridRenderer implements RendererConfig { } - render(config: Partial, el: HTMLElement) { - const communicator = new GridRendererCommunicator(el, config, this.plugin, this.app, this.cellParser) + render(config: Partial, el: HTMLElement, { cellParser }: RendererContext) { + const communicator = new GridRendererCommunicator(el, config, this.plugin, this.app, cellParser) return { render: (data: any) => { - // FIXME: we need to update that. communicator.setData(data.columns, data.data) + communicator.gridApi.autoSizeAllColumns() + }, error: (message: string) => { communicator.showInfo('error', message) diff --git a/src/renderer/ListRenderer.ts b/src/renderer/ListRenderer.ts index 792a13c..f9433a4 100644 --- a/src/renderer/ListRenderer.ts +++ b/src/renderer/ListRenderer.ts @@ -1,9 +1,8 @@ // This is renderer for a very basic List view. import { App } from "obsidian"; -import { RendererConfig } from "../renderer/rendererRegistry"; +import { RendererConfig, RendererContext } from "../renderer/rendererRegistry"; import { displayError } from "../utils/ui"; import { ViewDefinition } from "../grammar/parser"; -import { ModernCellParser } from "src/cellParser/ModernCellParser"; interface ListRendererConfig { classNames: string[] @@ -11,7 +10,7 @@ interface ListRendererConfig { export class ListRenderer implements RendererConfig { - constructor(private readonly app: App, private readonly cellParser: ModernCellParser) { } + constructor(private readonly app: App) { } get rendererKey() { return 'list' @@ -37,7 +36,7 @@ export class ListRenderer implements RendererConfig { } } - render(config: ListRendererConfig, el: HTMLElement) { + render(config: ListRendererConfig, el: HTMLElement, { cellParser }: RendererContext) { return { render: ({ columns, data }: any) => { el.empty() @@ -58,7 +57,7 @@ export class ListRenderer implements RendererConfig { text: createEl('span', { text: c, cls: 'sqlseal-column-name' }) as any, // FIXME: this should be properly typed cls: singleCol ? ['sqlseal-list-element', 'sqlseal-list-element-single'] : ['sqlseal-list-element-single'] }) - const val = this.cellParser.render(d[c]) + const val = cellParser.render(d[c]) el.append(val) el.dataset.sqlsealColumn = c }) diff --git a/src/renderer/MarkdownRenderer.ts b/src/renderer/MarkdownRenderer.ts index 3a93ee8..a0bccb0 100644 --- a/src/renderer/MarkdownRenderer.ts +++ b/src/renderer/MarkdownRenderer.ts @@ -1,7 +1,7 @@ // This is renderer for a very basic Table view. import { getMarkdownTable } from "markdown-table-ts"; import { App } from "obsidian"; -import { RendererConfig } from "../renderer/rendererRegistry"; +import { RendererConfig, RendererContext } from "../renderer/rendererRegistry"; import { displayError } from "../utils/ui"; import { ViewDefinition } from "../grammar/parser"; import { ParseResults } from "../cellParser/parseResults"; @@ -12,12 +12,7 @@ const mapDataFromHeaders = (columns: string[], data: Record[]) => { } export class MarkdownRenderer implements RendererConfig { - - parseResult: ParseResults; - - constructor(private readonly app: App, private readonly cellParser: ModernCellParser) { - this.parseResult = new ParseResults(cellParser) - + constructor(private readonly app: App) { } get rendererKey() { @@ -35,13 +30,14 @@ export class MarkdownRenderer implements RendererConfig { } } - render(config: ReturnType, el: HTMLElement) { + render(config: ReturnType, el: HTMLElement, { cellParser } : RendererContext) { + const parseResult = new ParseResults(cellParser) return { render: ({ columns, data }: any) => { const tab = getMarkdownTable({ table: { head: columns, - body: mapDataFromHeaders(columns, this.parseResult.renderAsString(data, columns)) + body: mapDataFromHeaders(columns, parseResult.renderAsString(data, columns)) } }) diff --git a/src/renderer/TableRenderer.ts b/src/renderer/TableRenderer.ts index 722ed96..59bc492 100644 --- a/src/renderer/TableRenderer.ts +++ b/src/renderer/TableRenderer.ts @@ -1,6 +1,6 @@ // This is renderer for a very basic Table view. import { App } from "obsidian"; -import { RendererConfig } from "../renderer/rendererRegistry"; +import { RendererConfig, RendererContext } from "../renderer/rendererRegistry"; import { displayError } from "../utils/ui"; import { ViewDefinition } from "../grammar/parser"; import { ModernCellParser } from "../cellParser/ModernCellParser"; @@ -11,7 +11,7 @@ interface HTMLRendererConfig { export class TableRenderer implements RendererConfig { - constructor(private readonly app: App, private cellParser: ModernCellParser) { } + constructor(private readonly app: App) { } get rendererKey() { return 'html' @@ -36,7 +36,7 @@ export class TableRenderer implements RendererConfig { } } - render(config: HTMLRendererConfig, el: HTMLElement) { + render(config: HTMLRendererConfig, el: HTMLElement, { cellParser }: RendererContext) { return { render: ({ columns, data }: any) => { el.empty() @@ -60,7 +60,7 @@ export class TableRenderer implements RendererConfig { data.forEach((d: any) => { const row = body.createEl("tr") columns.forEach((c: any) => { - row.createEl("td", { text: this.cellParser.render(d[c]) as string }) + row.createEl("td", { text: cellParser.render(d[c]) as string }) }) }) diff --git a/src/renderer/TemplateRenderer.ts b/src/renderer/TemplateRenderer.ts index a0e9209..70286fd 100644 --- a/src/renderer/TemplateRenderer.ts +++ b/src/renderer/TemplateRenderer.ts @@ -1,22 +1,17 @@ // This is renderer for a very basic List view. import { App } from "obsidian"; -import { RendererConfig } from "./rendererRegistry"; +import { RendererConfig, RendererContext } from "./rendererRegistry"; import { displayError } from "../utils/ui"; import { ViewDefinition } from "../grammar/parser"; import Handlebars from "handlebars"; import { ParseResults } from "../cellParser/parseResults"; -import { ModernCellParser } from "../cellParser/ModernCellParser"; interface TemplateRendererConfig { template: HandlebarsTemplateDelegate } export class TemplateRenderer implements RendererConfig { - - parseResult: ParseResults; - - constructor(private readonly app: App, private readonly cellParser: ModernCellParser) { - this.parseResult = new ParseResults(cellParser, (el) => new Handlebars.SafeString(el.outerHTML)) + constructor(private readonly app: App) { } get rendererKey() { @@ -42,18 +37,20 @@ export class TemplateRenderer implements RendererConfig { } } - render(config: TemplateRendererConfig, el: HTMLElement) { + render(config: TemplateRendererConfig, el: HTMLElement, { cellParser }: RendererContext) { return { render: ({ columns, data, frontmatter }: any) => { el.empty() + const parser = new ParseResults(cellParser, (el) => new Handlebars.SafeString(el.outerHTML)) + // Seems to be the only way to render handlebars into DOM. Don't like it but what can we do. el.innerHTML = config.template({ - data: this.parseResult.parse(data, columns), + data: parser.parse(data, columns), columns, properties: frontmatter }) - this.parseResult.initialise(el) + parser.initialise(el) }, error: (error: string) => { displayError(el, error) diff --git a/src/renderer/rendererRegistry.ts b/src/renderer/rendererRegistry.ts index 2512e70..e254d68 100644 --- a/src/renderer/rendererRegistry.ts +++ b/src/renderer/rendererRegistry.ts @@ -1,3 +1,4 @@ +import { ModernCellParser } from "../cellParser/ModernCellParser"; import { ViewDefinition } from "../grammar/parser"; export interface DataFormat { @@ -5,6 +6,10 @@ export interface DataFormat { columns: string[] } +export interface RendererContext { + cellParser: ModernCellParser +} + export interface RenderReturn { render: (data: any) => void; error: (errorMessage: string) => void; @@ -12,7 +17,7 @@ export interface RenderReturn { export interface RendererConfig = Record> { rendererKey: string; validateConfig: (config: string) => T, - render: (config: T, el: HTMLElement) => RenderReturn, + render: (config: T, el: HTMLElement, context: RendererContext) => RenderReturn, viewDefinition: ViewDefinition } @@ -66,8 +71,8 @@ export class RendererRegistry { } const rendererConfig = this.renderersByKey.get(type.toLowerCase())! const elConfig = rendererConfig.validateConfig(config) - return (el: HTMLElement) => { - return rendererConfig.render(elConfig, el) + return (el: HTMLElement, context: RendererContext) => { + return rendererConfig.render(elConfig, el, context) } } diff --git a/src/view/CSVView.ts b/src/view/CSVView.ts index 19067b9..384dfb6 100644 --- a/src/view/CSVView.ts +++ b/src/view/CSVView.ts @@ -201,7 +201,7 @@ export class CSVView extends TextFileView { }) - const grid = new GridRenderer(this.app, null, this.cellParser) + const grid = new GridRenderer(this.app, null) const csvView = this; const api = grid.render({ defaultColDef: { @@ -276,7 +276,7 @@ export class CSVView extends TextFileView { }) menu.showAtMouseEvent(e.event as any) } - }, gridEl) + }, gridEl, { cellParser: this.cellParser }) this.api = api; this.loadDataIntoGrid() From ed337f2232c10f87d09d04f461dbf7e0bcf40023 Mon Sep 17 00:00:00 2001 From: Kacper Kula Date: Wed, 2 Apr 2025 15:23:40 +0100 Subject: [PATCH 3/4] feat: resizing grid on layout resizes --- src/codeblockHandler/CodeblockProcessor.ts | 3 ++ src/renderer/GridRenderer.ts | 35 +++++++++++++++++++++- src/renderer/rendererRegistry.ts | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/codeblockHandler/CodeblockProcessor.ts b/src/codeblockHandler/CodeblockProcessor.ts index 8b3bc01..6271996 100644 --- a/src/codeblockHandler/CodeblockProcessor.ts +++ b/src/codeblockHandler/CodeblockProcessor.ts @@ -86,6 +86,9 @@ export class CodeblockProcessor extends MarkdownRenderChild { onunload() { this.registrator.offAll() + if (this.renderer?.cleanup) { + this.renderer.cleanup() + } } async render() { diff --git a/src/renderer/GridRenderer.ts b/src/renderer/GridRenderer.ts index aa34120..1f120ee 100644 --- a/src/renderer/GridRenderer.ts +++ b/src/renderer/GridRenderer.ts @@ -6,6 +6,7 @@ import { parse } from 'json5' import { ViewDefinition } from "../grammar/parser"; import SqlSealPlugin from "../main"; import { ModernCellParser } from "../cellParser/ModernCellParser"; +import { EventRef } from "obsidian"; const getCurrentTheme = () => { return document.body.classList.contains('theme-dark') ? 'dark' : 'light'; @@ -34,16 +35,45 @@ class GridRendererCommunicator { private cellParser: ModernCellParser ) { this.initialize() + this.setupLayoutObservers() } private _gridApi: GridApi private errorEl: HTMLElement private errorOverlay: HTMLElement + private resizeObserver: ResizeObserver + private unregisterLeafChange: EventRef | null = null get gridApi(): GridApi { return this._gridApi } + private setupLayoutObservers() { + // Observe resize changes + this.resizeObserver = new ResizeObserver(() => { + if (this._gridApi) { + this._gridApi.autoSizeAllColumns() + } + }) + this.resizeObserver.observe(this.el) + + // Observe tab switches + this.unregisterLeafChange = this.app.workspace.on('active-leaf-change', () => { + if (this._gridApi) { + this._gridApi.autoSizeAllColumns() + } + }) + } + + cleanup() { + if (this.resizeObserver) { + this.resizeObserver.disconnect() + } + if (this.unregisterLeafChange) { + this.app.workspace.offref(this.unregisterLeafChange) + } + } + private showError(message: string) { this.gridApi.setGridOption('loading', false) this.errorEl.textContent = message //.replace(`TTT${prefix}_`, ''); @@ -151,10 +181,13 @@ export class GridRenderer implements RendererConfig { render: (data: any) => { communicator.setData(data.columns, data.data) communicator.gridApi.autoSizeAllColumns() - }, error: (message: string) => { communicator.showInfo('error', message) + }, + cleanup: () => { + communicator.cleanup() + communicator.gridApi.destroy() } } } diff --git a/src/renderer/rendererRegistry.ts b/src/renderer/rendererRegistry.ts index e254d68..34f9011 100644 --- a/src/renderer/rendererRegistry.ts +++ b/src/renderer/rendererRegistry.ts @@ -13,6 +13,7 @@ export interface RendererContext { export interface RenderReturn { render: (data: any) => void; error: (errorMessage: string) => void; + cleanup?: () => void; } export interface RendererConfig = Record> { rendererKey: string; From b2ff3d59854fae2f523d6ff9b4ef93fbedc4c22c Mon Sep 17 00:00:00 2001 From: Kacper Kula Date: Wed, 2 Apr 2025 16:46:47 +0100 Subject: [PATCH 4/4] fix: fixing issue with columns with dots not rendering properly in grid view --- CHANGELOG.md | 8 ++++++++ manifest.json | 2 +- package.json | 2 +- src/renderer/GridRenderer.ts | 5 ++++- versions.json | 3 ++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9794442..422b82f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.31.0 (2025-04-02) +- feat: added text rendering for links, images and checkboxes (for MARKDOWN renderer) +- fix: fixed issue with rendering numbers +- fix: fixed how grid renders columns - now they automatically match content and don't truncate the text +- fix: grid resizing now works better when switching tabs, resizing obsidian, etc. +- fix: columns with dots in the name render properly in grid view +- chore: now external plugins can use cellRenderer from the main plugin + # 0.31.0 (2025-03-31) - feat: TEMPLATE renderer can now use checkboxes, links and images like other views. # 0.30.1 (2025-03-27) diff --git a/manifest.json b/manifest.json index f40ef11..d19f4d7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "sqlseal", "name": "SQLSeal", - "version": "0.31.0", + "version": "0.32.0", "minAppVersion": "0.15.0", "description": "Use SQL in your notes to query your vault files and CSV content.", "author": "hypersphere", diff --git a/package.json b/package.json index 7e14d92..030dcfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sqlseal", - "version": "0.31.0", + "version": "0.32.0", "description": "A plugin for Obsidian that allows you to run SQL queries on your notes.", "main": "main.js", "scripts": { diff --git a/src/renderer/GridRenderer.ts b/src/renderer/GridRenderer.ts index 1f120ee..0da26d3 100644 --- a/src/renderer/GridRenderer.ts +++ b/src/renderer/GridRenderer.ts @@ -134,7 +134,10 @@ class GridRendererCommunicator { if (!this.gridApi) { throw new Error('Grid has not been initiated') } - this.gridApi.setGridOption('columnDefs', columns.map((c: any) => ({ field: c }))) + this.gridApi.setGridOption('columnDefs', columns.map((c: any) => ({ + headerName: c, + valueGetter: (params) => params.data[c] + }))) this.gridApi.setGridOption('rowData', data) this.gridApi.setGridOption('loading', false) } diff --git a/versions.json b/versions.json index 9257539..c2b7c22 100644 --- a/versions.json +++ b/versions.json @@ -57,5 +57,6 @@ "0.29.0": "0.15.0", "0.30.0": "0.15.0", "0.30.1": "0.15.0", - "0.31.0": "0.15.0" + "0.31.0": "0.15.0", + "0.32.0": "0.15.0" } \ No newline at end of file