diff --git a/src/cellParser.ts b/src/cellParser.ts deleted file mode 100644 index b1790c2..0000000 --- a/src/cellParser.ts +++ /dev/null @@ -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 = 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' - }) - } - } -} diff --git a/src/cellParser/CellFunction.ts b/src/cellParser/CellFunction.ts new file mode 100644 index 0000000..753159c --- /dev/null +++ b/src/cellParser/CellFunction.ts @@ -0,0 +1,7 @@ +import { CellParserResult } from "./ModernCellParser"; + +export interface CellFunction { + get name(): string; + get sqlFunctionArgumentsCount(): number; + prepare(content: T): CellParserResult; +} \ No newline at end of file diff --git a/src/cellParser/ModernCellParser.ts b/src/cellParser/ModernCellParser.ts new file mode 100644 index 0000000..2751c89 --- /dev/null +++ b/src/cellParser/ModernCellParser.ts @@ -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 = 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) + }) + } +} \ No newline at end of file diff --git a/src/cellParser/RenderContext.ts b/src/cellParser/RenderContext.ts new file mode 100644 index 0000000..e7b7c07 --- /dev/null +++ b/src/cellParser/RenderContext.ts @@ -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)[] = [] + 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 = [] + } +} \ No newline at end of file diff --git a/src/cellParser/factory.ts b/src/cellParser/factory.ts new file mode 100644 index 0000000..7a2e869 --- /dev/null +++ b/src/cellParser/factory.ts @@ -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 +} \ No newline at end of file diff --git a/src/cellParser/parseResults.ts b/src/cellParser/parseResults.ts new file mode 100644 index 0000000..39fc058 --- /dev/null +++ b/src/cellParser/parseResults.ts @@ -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[], 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)) + } +} \ No newline at end of file diff --git a/src/cellParser/parser/checkbox.ts b/src/cellParser/parser/checkbox.ts new file mode 100644 index 0000000..1fafd6c --- /dev/null +++ b/src/cellParser/parser/checkbox.ts @@ -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 { + + 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) + } + }) + } + } + } +} \ No newline at end of file diff --git a/src/cellParser/parser/image.ts b/src/cellParser/parser/image.ts new file mode 100644 index 0000000..376264e --- /dev/null +++ b/src/cellParser/parser/image.ts @@ -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 { + + 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) } }); + } +} \ No newline at end of file diff --git a/src/cellParser/parser/link.ts b/src/cellParser/parser/link.ts new file mode 100644 index 0000000..6ae1141 --- /dev/null +++ b/src/cellParser/parser/link.ts @@ -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 { + + 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 + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index b5dc64f..6f1b3f0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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) { diff --git a/src/pluginApi/sqlSealApi.ts b/src/pluginApi/sqlSealApi.ts index 1140df0..f159c53 100644 --- a/src/pluginApi/sqlSealApi.ts +++ b/src/pluginApi/sqlSealApi.ts @@ -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 = [] @@ -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 = [] - private functions: Array = [] + private functions: Array = [] private flags: Array = [] 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(tableName: string, columns: columns) { diff --git a/src/renderer/GridRenderer.ts b/src/renderer/GridRenderer.ts index a5c4f47..2983331 100644 --- a/src/renderer/GridRenderer.ts +++ b/src/renderer/GridRenderer.ts @@ -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, 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, diff --git a/src/renderer/TemplateRenderer.ts b/src/renderer/TemplateRenderer.ts index 30f1ffd..31387db 100644 --- a/src/renderer/TemplateRenderer.ts +++ b/src/renderer/TemplateRenderer.ts @@ -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) diff --git a/src/settings/SQLSealSettingsTab.ts b/src/settings/SQLSealSettingsTab.ts index 4e6a3dd..ff52170 100644 --- a/src/settings/SQLSealSettingsTab.ts +++ b/src/settings/SQLSealSettingsTab.ts @@ -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 diff --git a/src/utils/ui.ts b/src/utils/ui.ts index e2a7cb2..7ce8602 100644 --- a/src/utils/ui.ts +++ b/src/utils/ui.ts @@ -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) -} \ No newline at end of file diff --git a/src/utils/ui/checkbox.ts b/src/utils/ui/checkbox.ts deleted file mode 100644 index b084209..0000000 --- a/src/utils/ui/checkbox.ts +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/utils/ui/image.ts b/src/utils/ui/image.ts deleted file mode 100644 index 632d068..0000000 --- a/src/utils/ui/image.ts +++ /dev/null @@ -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) } }); -} \ No newline at end of file diff --git a/src/utils/ui/link.ts b/src/utils/ui/link.ts deleted file mode 100644 index 40aa8b9..0000000 --- a/src/utils/ui/link.ts +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/view/CSVView.ts b/src/view/CSVView.ts index 5256cf3..19067b9 100644 --- a/src/view/CSVView.ts +++ b/src/view/CSVView.ts @@ -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); }