const { Plugin } = require('obsidian'); class CsvParser { constructor(lines) { let rows = lines.split("\n").filter((l) => l.length > 0); this._cells = rows.map((l) => this._parseRow(l)); } _parseRow(line) { const cells = []; let i = 0; while (i <= line.length) { if (i === line.length) { // trailing comma produced an empty final field break; } if (line[i] === '"') { // quoted field i++; // skip opening quote let value = ''; while (i < line.length) { if (line[i] === '"') { if (i + 1 < line.length && line[i + 1] === '"') { // escaped double quote value += '"'; i += 2; } else { // closing quote i++; // skip closing quote break; } } else { value += line[i]; i++; } } cells.push(value); // skip comma after closing quote if (i < line.length && line[i] === ',') { i++; } } else { // unquoted field let end = line.indexOf(',', i); if (end === -1) { cells.push(line.substring(i)); i = line.length; } else { cells.push(line.substring(i, end)); i = end + 1; } } } return cells; } cells() { return this._cells; } } const COPY_ICON_SVG = ''; class CsvPlugin extends Plugin { async onload() { this.addStyle(); this.registerMarkdownCodeBlockProcessor('csv', (source, el, ctx) => { const wrapper = el.createEl('div'); wrapper.addClass('csv-table-wrapper'); const table = wrapper.createEl('table'); table.addClass('csv-table'); let cp = new CsvParser(source); let allCells = cp.cells(); let twrap = null; for (let rowIdx = 0; rowIdx < allCells.length; rowIdx++) { let row = allCells[rowIdx]; if (twrap == null) { twrap = table.createEl('thead'); } else if (twrap.nodeName == 'THEAD') { twrap = table.createEl('tbody'); } let rowElem = twrap.createEl('tr'); for (let colIdx = 0; colIdx < row.length; colIdx++) { let col = row[colIdx]; if (rowIdx === 0) { // Header cell with copy button let td = rowElem.createEl('td'); td.addClass('csv-table-header'); td.createEl('span', { text: col }); let btn = td.createEl('button'); btn.addClass('csv-copy-btn'); btn.setAttribute('aria-label', 'Copy column'); btn.innerHTML = COPY_ICON_SVG; const ci = colIdx; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let columnText = allCells .map((r) => (ci < r.length ? r[ci] : '')) .join('\n'); navigator.clipboard.writeText(columnText); }); } else { rowElem.createEl('td', { text: col }); } } } }); } addStyle() { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = this.app.vault.adapter.getResourcePath('styles.css'); document.head.appendChild(link); } } module.exports = CsvPlugin;