Autoformat all tables on a page

This commit is contained in:
Magnus Åhall 2026-06-01 19:39:56 +02:00
parent b59c6c8b58
commit 8680cc5a62
4 changed files with 84 additions and 28 deletions

View file

@ -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;

View file

@ -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)