feat: new cell renderer to enable rendering elements inside templates

This commit is contained in:
Kacper Kula 2025-03-31 14:21:38 +01:00
parent 8704e46db3
commit 91f5b78f77
19 changed files with 486 additions and 265 deletions

View file

@ -1,67 +0,0 @@
import { Database } from "sql.js";
import { parse } from 'json5'
import { SqlSealDatabase } from "./database/database";
import { isStringifiedArray, renderStringifiedArray } from "./utils/ui";
export interface CellParser {
render: (content: string) => Node | string;
}
export class CellParserRegistar implements CellParser {
functions: Map<string, any> = new Map()
constructor(private db: SqlSealDatabase) {
}
register(name: string, fn: any, argsCount = 1) {
if (this.functions.has(name)) {
throw new Error('Function with that name already exists: ' + name)
}
this.functions.set(name, fn)
this.db.registerCustomFunction(name, argsCount)
}
unregister(name: string) {
this.functions.delete(name)
}
private renderCustomElement(el: any): Node | string {
if (this.functions.has(el.type)) {
return this.functions.get(el.type)!(el.values)
} else {
throw new Error(`Custom function processor ${el.type} is not registered`)
}
}
render(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))
return this.renderCustomElement(parsedData)
}
// If it's an array, decode it and render each individual elements
if (isStringifiedArray(content)) {
try {
return renderStringifiedArray(content)
} catch (e) {
return content
}
}
return content
} catch (e) {
console.error('Error parsing cell with data:', content, e)
return createDiv({
text: 'Parsing error',
cls: 'sqlseal-parse-error'
})
}
}
}

View file

@ -0,0 +1,7 @@
import { CellParserResult } from "./ModernCellParser";
export interface CellFunction<T = unknown> {
get name(): string;
get sqlFunctionArgumentsCount(): number;
prepare(content: T): CellParserResult;
}

View file

@ -0,0 +1,93 @@
import { CellFunction } from "./CellFunction";
import { parse } from 'json5'
import { isStringifiedArray, renderStringifiedArray } from '../utils/ui'
import { SqlSealDatabase } from "src/database/database";
export type UnregisterCallback = () => void;
export type OnRunCallback = (el: HTMLElement) => void | UnregisterCallback;
export interface Result {
element: HTMLElement,
onRunCallback?: OnRunCallback
}
export type CellParserResult = Result | string | HTMLElement
export class ModernCellParser {
private functions: Map<string, CellFunction> = new Map()
prepare(content: string): CellParserResult {
try {
if (!content) {
return content
}
if (typeof content !== 'string') {
return content
}
if (content.startsWith('SQLSEALCUSTOM')) {
const parsedData = parse(content.slice('SQLSEALCUSTOM('.length, -1))
// FIXME: zod or arctype here.
return this.renderCustomElement(parsedData)
}
// If it's an array, decode it and render each individual elements
if (isStringifiedArray(content)) {
try {
return renderStringifiedArray(content)
} catch (e) {
return content
}
}
return content
} catch (e) {
console.error('Error parsing cell with data:', content, e)
return createDiv({
text: 'Parsing error',
cls: 'sqlseal-parse-error'
})
}
}
render(content: string) {
const res = this.prepare(content)
if (typeof res === 'string' || !res) {
return res
} else if (res instanceof HTMLElement) {
return res
} else {
if (res.onRunCallback) {
res.onRunCallback(res.element)
}
return res.element
}
}
register(fn: CellFunction) {
this.functions.set(fn.name, fn)
}
unregister(fn: string | CellFunction) {
if (typeof fn === 'string') {
this.functions.delete(fn)
} else {
this.functions.delete(fn.name)
}
}
private renderCustomElement(el: any) {
if (this.functions.has(el.type)) {
return this.functions.get(el.type)!.prepare(el.values)
} else {
throw new Error(`Custom function processor ${el.type} is not registered`)
}
}
// FIXME: this should be extracted to separate class / function but for now it's fine.
registerDbFunctions(db: SqlSealDatabase) {
this.functions.forEach(funct => {
db.registerCustomFunction(funct.name, funct.sqlFunctionArgumentsCount)
})
}
}

View file

@ -0,0 +1,62 @@
// This is render context which collects all the parsed elements and maintains overvability of them.
import { ModernCellParser, OnRunCallback, UnregisterCallback } from "./ModernCellParser";
class RenderContext {
private isContextActive: boolean = false;
private contextActivateCalbacks: (() => ReturnType<OnRunCallback>)[] = []
private contextDeactivateCallbacks: UnregisterCallback[] = []
constructor(private readonly cellParser: ModernCellParser) {
}
render(context: string) {
const res = this.cellParser.prepare(context)
if (typeof res === 'string') {
return res
}
if (res instanceof HTMLElement) {
return res
}
if (res.onRunCallback) {
// FIXME: not sure if this is the proper element, this might get recreated inside the handlebars. To check.
this.contextActivateCalbacks.push(res.onRunCallback.apply(res.element, [res.element]))
}
return res.element
}
activate() {
if (this.isContextActive) {
throw new Error('Context already active')
}
this.isContextActive = true
for (const cb of this.contextActivateCalbacks) {
const res = cb()
if (res) {
this.contextDeactivateCallbacks.push(res)
}
}
}
deactivate() {
if (!this.isContextActive) {
throw new Error('Context not active')
}
this.isContextActive = false
for (const cb of this.contextDeactivateCallbacks) {
cb()
}
this.contextDeactivateCallbacks = []
}
clear() {
try {
this.deactivate()
} catch (e) { }
this.contextActivateCalbacks = []
}
}

13
src/cellParser/factory.ts Normal file
View file

@ -0,0 +1,13 @@
import { App } from "obsidian"
import { ModernCellParser } from "./ModernCellParser"
import { LinkParser } from "./parser/link"
import { ImageParser } from "./parser/image"
import { CheckboxParser } from "./parser/checkbox"
export const getCellParser = (app: App, create = createEl) => {
const cellParser = new ModernCellParser()
cellParser.register(new LinkParser(app, create))
cellParser.register(new ImageParser(app, create))
cellParser.register(new CheckboxParser(app, create))
return cellParser
}

View file

@ -0,0 +1,63 @@
import { identity, uniqueId } from "lodash";
import { ModernCellParser } from "./ModernCellParser";
type OnSerialisation = (el: HTMLElement) => any;
export class ParseResults {
functions: ((parentEl: HTMLElement) => void)[] = []
constructor(
private readonly cellParser: ModernCellParser,
private readonly serialise?: OnSerialisation
) { }
parse(data: Record<string, any>[], columns: string[]) {
this.functions = []
return data.map(d => {
const res: any = {}
for (const col of columns) {
const data = this.cellParser.prepare(d[col])
if (data instanceof Element) {
if (this.serialise) {
res[col] = this.serialise(data)
} else {
res[col] = data
}
} else if (typeof data === 'string') {
res[col] = data
} else if (!data) {
res[col] = ''
} else {
if (!this.serialise) {
res[col] = data.element
this.functions.push((_parentEl) => {
if (data.onRunCallback) {
data.onRunCallback(data.element)
}
})
} else {
const id = uniqueId('sqlseal__')
data.element.id = id
res[col] = this.serialise(data.element)
this.functions.push((parentEl: HTMLElement) => {
const resultingElement = parentEl.find('#' + id)
if (!resultingElement) {
return
}
if (data.onRunCallback) {
data.onRunCallback(resultingElement)
}
})
}
}
}
return res
})
}
initialise(parentEl: HTMLElement) {
this.functions.forEach(fn => fn(parentEl))
}
}

View file

@ -0,0 +1,93 @@
import { CellFunction } from "../CellFunction";
import { CellParserResult } from "../ModernCellParser";
import { App } from "obsidian";
type Args = number[] | CheckboxProps
interface CheckboxProps {
checked: boolean,
path: string,
position: {
line: number,
lineContent: string
},
task: string
}
const isCheckboxProp = (arg: any): arg is CheckboxProps => {
return arg && arg['position'] && arg['path']
}
export class CheckboxParser implements CellFunction<Args> {
constructor(private readonly app: App, private readonly create = createEl) { }
get name(): string {
return 'checkbox'
}
get sqlFunctionArgumentsCount() {
return 1
}
prepare(values: Args): CellParserResult {
if (!isCheckboxProp(values)) {
const el = createEl('input', {
type: 'checkbox',
attr: {
disabled: true,
checked: !!values[0] ? true : null
}
});
return el
}
const el = createEl('input', {
type: 'checkbox',
attr: {
checked: !!(values && values.checked) ? true : null
}
})
return {
element: el,
onRunCallback: (el: HTMLInputElement) => {
el.addEventListener('change', async () => {
try {
// Values should contain the data we need
if (!values || !values.path) {
console.error('Missing required checkbox data')
return
}
const file = this.app.vault.getFileByPath(values.path)
if (!file) {
console.error('File not found:', values.path)
return
}
// Read file content
const content = await this.app.vault.read(file)
const lines = content.split('\n')
// Get the line containing the task
const lineIndex = values.position?.line
if (lineIndex >= 0 && lineIndex < lines.length) {
// Update the task status
if (el.checked) {
lines[lineIndex] = lines[lineIndex].replace('- [ ]', '- [x]')
} else {
lines[lineIndex] = lines[lineIndex].replace('- [x]', '- [ ]')
}
// Write back to the file
await this.app.vault.modify(file, lines.join('\n'))
}
} catch (error) {
console.error('Error updating task:', error)
}
})
}
}
}
}

View file

@ -0,0 +1,42 @@
import { CellFunction } from "../CellFunction";
import { CellParserResult } from "../ModernCellParser";
import { App } from "obsidian";
import { isLinkLocal } from "src/utils/ui/helperFunctions";
type Args = [string] | [string, string]
export class ImageParser implements CellFunction<Args> {
constructor(private readonly app: App, private readonly create = createEl) { }
get name(): string {
return 'img'
}
get sqlFunctionArgumentsCount() {
return 2 // FIXME: should it be 1 or 2?
}
prepare([href, path]: Args): CellParserResult {
if (!isLinkLocal(href)) {
return createEl('img', { attr: { src: href } });
}
href = (href ?? '').trim()
if (href.startsWith('![[')) {
href = href.slice(3, -2)
}
let parentPath = ''
if (path) {
const parent = this.app.vault.getFileByPath(path)
if (parent) {
parentPath = parent.parent?.path ?? ''
}
}
path = (path ? parentPath + '/' : '') + href
const file = this.app.vault.getFileByPath(path);
if (!file) {
return 'File does not exist'
}
return this.create('img', { attr: { src: this.app.vault.getResourcePath(file) } });
}
}

View file

@ -0,0 +1,68 @@
import { isStringifiedArray, renderStringifiedArray } from "../../utils/ui";
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]
export class LinkParser implements CellFunction<Args> {
constructor(private readonly app: App, private readonly create = createEl) { }
get name(): string {
return 'a'
}
get sqlFunctionArgumentsCount() {
return 2 // FIXME: should it be 1 or 2?
}
prepare([href, name]: Args) {
if (!href) {
return ''
}
if (isStringifiedArray(href)) {
try {
renderStringifiedArray(href, href => this.prepare([href]))
} catch (e) {
return href
}
}
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'
}
const el = this.create('a', { text: name, href, cls })
return el
}
}

View file

@ -10,24 +10,30 @@ import { CSV_VIEW_EXTENSIONS, CSV_VIEW_TYPE, CSVView } from './view/CSVView';
import { createSqlSealEditorExtension } from './editorExtension/inlineCodeBlock';
import { ListRenderer } from './renderer/ListRenderer';
import { JSON_VIEW_EXTENSIONS, JSON_VIEW_TYPE, JsonView } from './view/JsonView';
import { CellParserRegistar } from './cellParser';
import { registerAPI } from '@vanakat/plugin-api';
import { SQLSealRegisterApi } from './pluginApi/sqlSealApi';
import { registerDefaultFunctions } from './utils/ui';
import { DatabaseTable } from './database/table';
import { FilepathHasher } from './utils/hasher';
import { SQLSealViewPlugin } from './editorExtension/syntaxHighlight';
import { EditorView, ViewPlugin } from '@codemirror/view';
import { TemplateRenderer } from './renderer/TemplateRenderer';
import { ModernCellParser } from './cellParser/ModernCellParser';
import { getCellParser } from './cellParser/factory';
import { CellFunction } from './cellParser/CellFunction';
export default class SqlSealPlugin extends Plugin {
settings: SQLSealSettings;
fileSync: SealFileSync;
sqlSeal: SqlSeal;
rendererRegistry: RendererRegistry = new RendererRegistry();
cellParserRegistar: CellParserRegistar;
cellParser: ModernCellParser;
async onload() {
// Preparing cell parser
this.cellParser = getCellParser(this.app, createEl)
await this.loadSettings();
const settingsTab = new SQLSealSettingsTab(this.app, this, this.settings)
this.addSettingTab(settingsTab);
@ -47,16 +53,16 @@ export default class SqlSealPlugin extends Plugin {
this.sqlSeal = sqlSeal
await this.sqlSeal.db.connect()
this.cellParserRegistar = new CellParserRegistar(this.sqlSeal.db)
registerDefaultFunctions(this.cellParserRegistar, this.app)
this.cellParser.registerDbFunctions(this.sqlSeal.db)
// REGISTERING VIEWS
this.rendererRegistry.register('sql-seal-internal-table', new TableRenderer(this.app, this.cellParserRegistar))
this.rendererRegistry.register('sql-seal-internal-grid', new GridRenderer(this.app, this, this.cellParserRegistar))
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-list', new ListRenderer(this.app, this.cellParserRegistar))
this.rendererRegistry.register('sql-seal-internal-template', new TemplateRenderer(this.app, this.cellParserRegistar))
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))
// start syncing when files are loaded
this.app.workspace.onLayoutReady(async () => {
@ -184,7 +190,7 @@ export default class SqlSealPlugin extends Plugin {
async registerViews() {
this.registerView(
CSV_VIEW_TYPE,
(leaf) => new CSVView(leaf, this.settings.enableEditing, this.cellParserRegistar)
(leaf) => new CSVView(leaf, this.settings.enableEditing, this.cellParser)
);
this.registerView(
JSON_VIEW_TYPE,
@ -200,12 +206,12 @@ export default class SqlSealPlugin extends Plugin {
this.rendererRegistry.unregister(name)
}
registerSQLSealFunction(name: string, fn: any, argumentsCount: number = 1) {
this.cellParserRegistar.register(name, fn, argumentsCount)
registerSQLSealFunction(fn: CellFunction) {
this.cellParser.register(fn)
}
unregisterSQLSealFunction(name: string) {
this.cellParserRegistar.unregister(name)
this.cellParser.unregister(name)
}
registerSQLSealFlag(flag: Flag) {

View file

@ -2,8 +2,9 @@ import { Plugin } from "obsidian"
import SqlSealPlugin from "../main"
import { version } from '../../package.json'
import { RendererConfig } from "../renderer/rendererRegistry";
import { CellFunction } from "../cellParser/CellFunction";
const API_VERSION = 2;
const API_VERSION = 3;
export class SQLSealRegisterApi {
registeredApis: Array<SQLSealApi> = []
@ -36,11 +37,6 @@ interface RegisteredView {
viewClass: any;
}
interface RegisteredFunction {
name: string,
fn: CallableFunction
}
// TODO: use the type from registrator
export interface RegisteredFlag {
name: string,
@ -49,7 +45,7 @@ export interface RegisteredFlag {
export class SQLSealApi {
private views: Array<RegisteredView> = []
private functions: Array<RegisteredFunction> = []
private functions: Array<CellFunction> = []
private flags: Array<RegisteredFlag> = []
constructor(private readonly plugin: Plugin, private sqlSealPlugin: SqlSealPlugin) {
@ -66,12 +62,9 @@ export class SQLSealApi {
this.sqlSealPlugin.registerSQLSealView(name, viewClass)
}
registerCustomFunction(name: string, fn: CallableFunction, argumentsCount: number = 1) {
this.functions.push({
name,
fn,
})
this.sqlSealPlugin.registerSQLSealFunction(name, fn, argumentsCount)
registerCustomFunction(fn: CellFunction) {
this.functions.push(fn)
this.sqlSealPlugin.registerSQLSealFunction(fn)
}
registerTable<const columns extends string[]>(tableName: string, columns: columns) {

View file

@ -3,9 +3,9 @@ import { merge } from "lodash";
import { App } from "obsidian";
import { RendererConfig } from "./rendererRegistry";
import { parse } from 'json5'
import { CellParser } from "../cellParser";
import { ViewDefinition } from "../grammar/parser";
import SqlSealPlugin from "../main";
import { ModernCellParser } from "src/cellParser/ModernCellParser";
const getCurrentTheme = () => {
return document.body.classList.contains('theme-dark') ? 'dark' : 'light';
@ -31,7 +31,7 @@ class GridRendererCommunicator {
private config: Partial<GridOptions>,
private plugin: SqlSealPlugin | null,
private app: App,
private cellParser: CellParser
private cellParser: ModernCellParser
) {
this.initialize()
}
@ -118,7 +118,7 @@ class GridRendererCommunicator {
}
export class GridRenderer implements RendererConfig {
constructor(private app: App, private readonly plugin: SqlSealPlugin | null, private readonly cellParser: CellParser) { }
constructor(private app: App, private readonly plugin: SqlSealPlugin | null, private readonly cellParser: ModernCellParser) { }
get viewDefinition(): ViewDefinition {
return {
name: this.rendererKey,

View file

@ -5,6 +5,9 @@ import { RendererConfig } from "./rendererRegistry";
import { displayError } from "../utils/ui";
import { ViewDefinition } from "../grammar/parser";
import Handlebars from "handlebars";
import { getCellParser } from "src/cellParser/factory";
import { uniqueId } from "lodash";
import { ParseResults } from "src/cellParser/parseResults";
interface TemplateRendererConfig {
template: HandlebarsTemplateDelegate
@ -12,7 +15,12 @@ interface TemplateRendererConfig {
export class TemplateRenderer implements RendererConfig {
constructor(private readonly app: App, private readonly cellParser: CellParser) { }
parseResult: ParseResults;
constructor(private readonly app: App, private readonly _cellParser: CellParser) {
const cellParser = getCellParser(this.app, createEl)
this.parseResult = new ParseResults(cellParser, (el) => new Handlebars.SafeString(el.outerHTML))
}
get rendererKey() {
return 'template'
@ -43,7 +51,12 @@ export class TemplateRenderer implements RendererConfig {
el.empty()
// 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, columns, properties: frontmatter })
el.innerHTML = config.template({
data: this.parseResult.parse(data, columns),
columns,
properties: frontmatter
})
this.parseResult.initialise(el)
},
error: (error: string) => {
displayError(el, error)

View file

@ -122,7 +122,6 @@ export class SQLSealSettingsTab extends PluginSettingTab {
.addOption('markdown', 'Markdown Table')
.setValue(this.settings.defaultView)
.onChange(async (value) => {
console.log(value)
this.settings.defaultView = value as 'grid' | 'html' | 'markdown';
if (!value) {
this.settings.defaultView = DEFAULT_SETTINGS.defaultView

View file

@ -1,9 +1,4 @@
import { identity } from "lodash"
import { App } from "obsidian"
import { CellParserRegistar } from "../cellParser"
import { renderCheckbox } from "./ui/checkbox"
import { renderLink } from "./ui/link"
import { renderImage } from "./ui/image"
export const displayNotice = (el: HTMLElement, text: string) => {
el.empty()
@ -31,9 +26,3 @@ export const renderStringifiedArray = (input: string, transformer: (f: string) =
el.append(...links)
return el
}
export const registerDefaultFunctions = (registar: CellParserRegistar, app: App) => {
registar.register('checkbox', renderCheckbox(app), 1)
registar.register('a', renderLink(app), 2)
registar.register('img', renderImage(app), 2)
}

View file

@ -1,75 +0,0 @@
import { App } from "obsidian";
interface CheckboxProps {
checked: boolean,
path: string,
position: {
line: number,
lineContent: string
},
task: string
}
const isCheckboxProp = (arg: any): arg is CheckboxProps => {
return arg && arg['position'] && arg['path']
}
export const renderCheckbox = (app: App) => (values: number[] | CheckboxProps) => {
if (!isCheckboxProp(values)) {
const el = createEl('input', {
type: 'checkbox',
attr: {
disabled: true
}
});
el.checked = !!values[0]
return el
}
const el = createEl('input', {
type: 'checkbox',
})
if (values && values.checked) {
el.checked = true
}
el.addEventListener('change', async () => {
try {
// Values should contain the data we need
if (!values || !values.path) {
console.error('Missing required checkbox data')
return
}
const file = app.vault.getFileByPath(values.path)
if (!file) {
console.error('File not found:', values.path)
return
}
// Read file content
const content = await app.vault.read(file)
const lines = content.split('\n')
// Get the line containing the task
const lineIndex = values.position?.line
if (lineIndex >= 0 && lineIndex < lines.length) {
// Update the task status
if (el.checked) {
lines[lineIndex] = lines[lineIndex].replace('- [ ]', '- [x]')
} else {
lines[lineIndex] = lines[lineIndex].replace('- [x]', '- [ ]')
}
// Write back to the file
await app.vault.modify(file, lines.join('\n'))
}
} catch (error) {
console.error('Error updating task:', error)
}
})
return el
}

View file

@ -1,25 +0,0 @@
import { App } from "obsidian";
import { isLinkLocal } from "./helperFunctions";
export const renderImage = (app: App) => ([href, path]: [string, string]) => {
if (!isLinkLocal(href)) {
return createEl('img', { attr: { src: href } });
}
href = (href ?? '').trim()
if (href.startsWith('![[')) {
href = href.slice(3, -2)
}
let parentPath = ''
if (path) {
const parent = app.vault.getFileByPath(path)
if (parent) {
parentPath = parent.parent?.path ?? ''
}
}
path = (path ? parentPath + '/' : '') + href
const file = app.vault.getFileByPath(path);
if (!file) {
return 'File does not exist'
}
return createEl('img', { attr: { src: app.vault.getResourcePath(file) } });
}

View file

@ -1,53 +0,0 @@
import { App } from "obsidian"
import { isLinkLocal, isLinktext, removeExtension } from "./helperFunctions"
import { isStringifiedArray, renderStringifiedArray } from "../ui"
type LinkType = [string] | [string, string]
export const renderLink = (app: App, create = createEl) => ([href, name]: LinkType): string | Node => {
if (!href) {
return ''
}
if (isStringifiedArray(href)) {
try {
return renderStringifiedArray(href, href => renderLink(app, create)([href]))
} catch (e) {
return href
}
}
if (!name) {
name = href
}
href = href.trim()
let cls = ''
if (isLinktext(href)) {
const [path, linkName] = href.slice(2, -2).split('|')
const link = 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 el = create('a', { text: name, href, cls })
return el
}

View file

@ -4,8 +4,8 @@ import { DeleteConfirmationModal } from '../modal/deleteConfirmationModal';
import { RenameColumnModal } from '../modal/renameColumnModal';
import { CodeSampleModal } from '../modal/showCodeSample';
import { GridRenderer } from '../renderer/GridRenderer';
import { CellParser } from '../cellParser';
import { errorNotice } from '../utils/notice';
import { ModernCellParser } from 'src/cellParser/ModernCellParser';
export const CSV_VIEW_TYPE = "csv-viewer" as const;
export const CSV_VIEW_EXTENSIONS = ['csv'];
@ -17,7 +17,7 @@ export class CSVView extends TextFileView {
constructor(
leaf: WorkspaceLeaf,
private enableEditing: boolean,
private cellParser: CellParser
private cellParser: ModernCellParser
) {
super(leaf);
}