mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
Merge pull request #136 from h-sphere/feat/text-cell-parser
fix: plenty fixes
This commit is contained in:
commit
97ec36218f
21 changed files with 312 additions and 93 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ export interface CellFunction<T = unknown> {
|
|||
get name(): string;
|
||||
get sqlFunctionArgumentsCount(): number;
|
||||
prepare(content: T): CellParserResult;
|
||||
renderAsString(content: T): string;
|
||||
}
|
||||
|
|
@ -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,12 +34,40 @@ 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 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) {
|
||||
console.error('Error parsing cell with data:', content, e)
|
||||
|
|
@ -64,6 +92,56 @@ export class ModernCellParser {
|
|||
}
|
||||
}
|
||||
|
||||
renderAsString(content: string): 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.renderAsString(p)).join(', ')
|
||||
} catch (e) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
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 +155,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`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,16 @@ export class ParseResults {
|
|||
})
|
||||
}
|
||||
|
||||
renderAsString(data: Record<string, any>[], columns: string[]) {
|
||||
return data.map(d => {
|
||||
const res: Record<string, string> = {}
|
||||
for (const col of columns) {
|
||||
res[col] = this.cellParser.renderAsString(d[col])
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
initialise(parentEl: HTMLElement) {
|
||||
this.functions.forEach(fn => fn(parentEl))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,4 +90,19 @@ export class CheckboxParser implements CellFunction<Args> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderAsString(values: Args): string {
|
||||
if (!isCheckboxProp(values)) {
|
||||
if (!!values[0]) {
|
||||
return '[x]'
|
||||
}
|
||||
return '[ ]'
|
||||
} else {
|
||||
if (values.checked) {
|
||||
return '[x]'
|
||||
} else {
|
||||
return '[ ] '
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
||||
|
|
@ -17,10 +17,7 @@ export class ImageParser implements CellFunction<Args> {
|
|||
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<Args> {
|
|||
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 ``
|
||||
}
|
||||
|
||||
try {
|
||||
const resourcePath = this.getResourcePath(href, path)
|
||||
return `![[${resourcePath}]]`
|
||||
} catch (e) {
|
||||
return e.toString()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +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";
|
||||
|
||||
type Args = [string] | [string, string]
|
||||
|
||||
|
|
@ -18,6 +17,44 @@ export class LinkParser implements CellFunction<Args> {
|
|||
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 +62,29 @@ export class LinkParser implements CellFunction<Args> {
|
|||
|
||||
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'
|
||||
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})`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -81,6 +86,9 @@ export class CodeblockProcessor extends MarkdownRenderChild {
|
|||
|
||||
onunload() {
|
||||
this.registrator.offAll()
|
||||
if (this.renderer?.cleanup) {
|
||||
this.renderer.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
async render() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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-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.cellParser))
|
||||
this.rendererRegistry.register('sql-seal-internal-template', new TemplateRenderer(this.app, this.cellParser))
|
||||
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 () => {
|
||||
|
|
|
|||
|
|
@ -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> = []
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
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";
|
||||
import { EventRef } from "obsidian";
|
||||
|
||||
const getCurrentTheme = () => {
|
||||
return document.body.classList.contains('theme-dark') ? 'dark' : 'light';
|
||||
|
|
@ -34,11 +35,44 @@ class GridRendererCommunicator {
|
|||
private cellParser: ModernCellParser
|
||||
) {
|
||||
this.initialize()
|
||||
this.setupLayoutObservers()
|
||||
}
|
||||
|
||||
private gridApi: GridApi<any>
|
||||
private _gridApi: GridApi<any>
|
||||
private errorEl: HTMLElement
|
||||
private errorOverlay: HTMLElement
|
||||
private resizeObserver: ResizeObserver
|
||||
private unregisterLeafChange: EventRef | null = null
|
||||
|
||||
get gridApi(): GridApi<any> {
|
||||
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)
|
||||
|
|
@ -76,8 +110,9 @@ class GridRendererCommunicator {
|
|||
autoHeight: true
|
||||
},
|
||||
autoSizeStrategy: {
|
||||
// make sure to fit content
|
||||
type: 'fitGridWidth',
|
||||
defaultMinWidth: 150,
|
||||
// defaultMinWidth: 150,
|
||||
},
|
||||
pagination: true,
|
||||
suppressMovableColumns: true,
|
||||
|
|
@ -89,7 +124,7 @@ class GridRendererCommunicator {
|
|||
paginationPageSize: this.plugin? this.plugin.settings.gridItemsPerPage : undefined,
|
||||
// ensureDomOrder: true
|
||||
}, this.config)
|
||||
this.gridApi = createGrid(
|
||||
this._gridApi = createGrid(
|
||||
grid,
|
||||
gridOptions,
|
||||
);
|
||||
|
|
@ -99,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)
|
||||
}
|
||||
|
|
@ -118,7 +156,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,15 +178,19 @@ 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)
|
||||
},
|
||||
cleanup: () => {
|
||||
communicator.cleanup()
|
||||
communicator.gridApi.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
// 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";
|
||||
import { ModernCellParser } from "../cellParser/ModernCellParser";
|
||||
|
||||
const mapDataFromHeaders = (columns: string[], data: Record<string, any>[]) => {
|
||||
return data.map(d => columns.map(c => String(d[c])))
|
||||
}
|
||||
|
||||
export class MarkdownRenderer implements RendererConfig {
|
||||
|
||||
constructor(private readonly app: App) { }
|
||||
constructor(private readonly app: App) {
|
||||
}
|
||||
|
||||
get rendererKey() {
|
||||
return 'markdown'
|
||||
|
|
@ -28,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, data)
|
||||
body: mapDataFromHeaders(columns, parseResult.renderAsString(data, columns))
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 "src/cellParser/parseResults";
|
||||
import { ModernCellParser } from "src/cellParser/ModernCellParser";
|
||||
import { ParseResults } from "../cellParser/parseResults";
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ModernCellParser } from "../cellParser/ModernCellParser";
|
||||
import { ViewDefinition } from "../grammar/parser";
|
||||
|
||||
export interface DataFormat {
|
||||
|
|
@ -5,14 +6,19 @@ export interface DataFormat {
|
|||
columns: string[]
|
||||
}
|
||||
|
||||
export interface RendererContext {
|
||||
cellParser: ModernCellParser
|
||||
}
|
||||
|
||||
export interface RenderReturn {
|
||||
render: (data: any) => void;
|
||||
error: (errorMessage: string) => void;
|
||||
cleanup?: () => void;
|
||||
}
|
||||
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 +72,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
Loading…
Reference in a new issue