From 8680cc5a62ab7ca7f426bbbc485a88735e265d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 1 Jun 2026 19:39:56 +0200 Subject: [PATCH] Autoformat all tables on a page --- static/css/markdown.css | 1 + static/css/notes2.css | 2 +- static/js/file.mjs | 2 +- static/js/page_node.mjs | 107 ++++++++++++++++++++++++++++++---------- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/static/css/markdown.css b/static/css/markdown.css index 84eb0b2..cf80c34 100644 --- a/static/css/markdown.css +++ b/static/css/markdown.css @@ -42,6 +42,7 @@ table { border: 1px solid #ccc; border-collapse: collapse; + margin-top: 14px; th { text-align: left; diff --git a/static/css/notes2.css b/static/css/notes2.css index 31e1f1f..e02b90e 100644 --- a/static/css/notes2.css +++ b/static/css/notes2.css @@ -21,7 +21,7 @@ html { "tree hum content content ding" "tree hum blank blank ding" ; - grid-template-columns: min-content minmax(16px, 1fr) minmax(min-content, 820px) 80px minmax(16px, 1fr); + grid-template-columns: min-content minmax(16px, 1fr) minmax(min-content, calc(900px - 120px)) 120px minmax(16px, 1fr); grid-template-rows: min-content min-content 48px 1fr; diff --git a/static/js/file.mjs b/static/js/file.mjs index 2674737..4322218 100644 --- a/static/js/file.mjs +++ b/static/js/file.mjs @@ -8,7 +8,7 @@ export class N2File extends CustomHTMLElement { :host { display: inline-grid; grid-template-columns: min-content min-content; - align-items: center; + align-items: end; white-space: nowrap; cursor: pointer; diff --git a/static/js/page_node.mjs b/static/js/page_node.mjs index 753aa46..6291c8f 100644 --- a/static/js/page_node.mjs +++ b/static/js/page_node.mjs @@ -75,13 +75,17 @@ export class N2PageNodeUI extends CustomHTMLElement { this.elNodeContent.addEventListener('input', event => this.contentChanged(event)) this.elNodeContent.addEventListener('paste', async (event) => this.pasteHandler(event)) this.elIconMarkdown.addEventListener('click', () => this.showMarkdown(!this.showMarkdown())) - this.elIconTableFormat.addEventListener('click', () => { - const from = this.elNodeContent.selectionStart - const to = this.elNodeContent.selectionEnd - const sel = this.elNodeContent.value.slice(from, to) - - this.formatTable(sel) + this.elIconTableFormat.addEventListener('click', event => { + if (!event.shiftKey) + this.elNodeContent.value = this.formatAllTables(this.elNodeContent.value) + else { + const from = this.elNodeContent.selectionStart + const to = this.elNodeContent.selectionEnd + const text = this.elNodeContent.value.slice(from, to) + const formatted = this.formatAllTables(text) + this.elNodeContent.setRangeText(formatted, from, to, 'select'); + } }) this.showMarkdown(true) @@ -164,37 +168,88 @@ export class N2PageNodeUI extends CustomHTMLElement { this.elNodeContent.selectionEnd = data.position.end this.elNodeContent.focus() }// }}} - formatTable(t) { - const lines = t.split(/\r?\n/) - if (lines < 1) - return - let first = -1 - let last = -1 + findTables(lines) {// {{{ + let tables = [] + let curr = { from: -1, to: -1 } + for (let i = 0; i < lines.length; i++) { + const linecols = lines[i].split('|').length - 2 // Gives empty value in front of first pipe and after last one. + + if (linecols >= 1) { + if (curr.from == -1) + curr.from = i + curr.to = i + } else if (linecols < 1 && curr.to > -1) { + tables.push(curr) + curr = { from: -1, to: -1 } + } + } + + if (curr.from > -1) + tables.push(curr) + + return tables + }// }}} + formatAllTables(text) {// {{{ + const lines = text.split(/\r?\n/) + const tables = this.findTables(lines) + for (const table of tables) { + const formattedLines = this.formatTable(lines.slice(table.from, table.to + 1)) + lines.splice(table.from, formattedLines.length, ...formattedLines) + } + + return lines.join("\n") + }// }}} + formatTable(lines) {// {{{ let numColumns = 0 let colwidth = [] for (let i = 0; i < lines.length; i++) { // -1 for split, -1 because number of columns are one less than number of pipes. - const columns = lines[i].split('|') - const linecols = columns.length - 1 - 1 + const columns = lines[i].split('|').slice(1) + const linecols = columns.length - 2 numColumns = Math.max(numColumns, linecols) - if (linecols >= 1) { - if (first == -1) - first = i - last = i - continue + // Keep count of column width. + for (let j = 0; j < columns.length - 1; j++) { + colwidth[j] = Math.max(colwidth[j] || 0, columns[j].trim().length) } - - if (linecols < 1 && last > -1) - break - - // Keep count of column width } - console.log(first, last, columns) - } + // Build up each line correct. + let extendHeader + for (let i = 0; i < lines.length; i++) { + // Build lines with columns. + const cols = lines[i].split('|').slice(1, -1) + + // Second line should be headers. + if (i === 1) { + extendHeader = true + for (let j = 0; j < colwidth.length; j++) { + extendHeader &= ((cols[j] || '').match(/^\s*[-]*\s*$/) !== null) + } + + + } + + if (i === 1 && extendHeader) { + for (let j = 0; j < colwidth.length; j++) + cols[j] = '-'.repeat(colwidth[j]) + + } else { + for (let j = 0; j < colwidth.length; j++) { + cols[j] = (cols[j] || '').trim() + const cw = colwidth[j] + const padWidth = cw - (cols[j]?.length || 0) // may be a column that doesn't exist on this line. + cols[j] = cols[j] + ' '.repeat(padWidth > 0 ? padWidth : 0) + } + } + + lines[i] = '│ ' + cols.join(' │ ') + ' │' + } + + return lines + }// }}} } customElements.define('n2-nodeui', N2PageNodeUI)