chore: refactoring renderers and adding markdown text renderers

This commit is contained in:
Kacper Kula 2025-04-02 15:14:36 +01:00
parent 0447368f53
commit 053059a661
14 changed files with 110 additions and 79 deletions

View file

@ -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]);
}
}

View file

@ -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]

View file

@ -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<Args> {
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) {

View file

@ -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

View file

@ -56,7 +56,7 @@ export class CsvFileSyncStrategy extends ISyncStrategy {
// TODO: PROBABLY SHOULD BE EXTRACTED SOMEWHERE FROM HERE later.
const parsed = parse<Record<string, string>>(data, {
header: true,
dynamicTyping: true,
dynamicTyping: false,
skipEmptyLines: true,
transformHeader: sanitise
})

View file

@ -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 () => {

View file

@ -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<SQLSealApi> = []

View file

@ -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<any>
private _gridApi: GridApi<any>
private errorEl: HTMLElement
private errorOverlay: HTMLElement
get gridApi(): GridApi<any> {
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<GridOptions>, el: HTMLElement) {
const communicator = new GridRendererCommunicator(el, config, this.plugin, this.app, this.cellParser)
render(config: Partial<GridOptions>, 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)

View file

@ -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
})

View file

@ -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<string, any>[]) => {
}
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<typeof this.validateConfig>, el: HTMLElement) {
render(config: ReturnType<typeof this.validateConfig>, 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))
}
})

View file

@ -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 })
})
})

View file

@ -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)

View file

@ -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<T extends Record<string, any> = Record<string, any>> {
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)
}
}

View file

@ -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()