Excel Export: Builds .xlsx workbooks from supplied tables or grid panel data and writes/opens them in the plugin's exports folder.
| 1 | /** Excel sheet names: no : \ / ? * [ ], at most 31 characters, unique ignoring case. */ |
| 2 | export function sheetNames(titles: string[]): string[] { |
| 3 | const used = new Set<string>() |
| 4 | return titles.map((title, i) => { |
| 5 | let base = title |
| 6 | .replace(/[:\\/?*[\]]/g, ' ') |
| 7 | .replace(/\s+/g, ' ') |
| 8 | .trim() |
| 9 | .replace(/^'+|'+$/g, '') |
| 10 | if (!base || base.toLowerCase() === 'history') base = `Sheet${i + 1}` |
| 11 | base = base.slice(0, 31).trim() |
| 12 | let name = base |
| 13 | for (let n = 2; used.has(name.toLowerCase()); n++) { |
| 14 | const suffix = ` (${n})` |
| 15 | name = base.slice(0, 31 - suffix.length).trim() + suffix |
| 16 | } |
| 17 | used.add(name.toLowerCase()) |
| 18 | return name |
| 19 | }) |
| 20 | } |
| 21 | |
| 22 | /** Rows as arrays in column order, from arrays or objects keyed by column. */ |
| 23 | export function rowArrays(columns: string[], rows: unknown[]): unknown[][] { |
| 24 | return rows.map((row) => { |
| 25 | if (Array.isArray(row)) return row |
| 26 | if (row && typeof row === 'object') { |
| 27 | const record = row as Record<string, unknown> |
| 28 | return columns.map((c) => record[c]) |
| 29 | } |
| 30 | return [row] |
| 31 | }) |
| 32 | } |
| 33 | |
| 34 | /** A file name from a title and the time: letters, digits, dashes, ending .xlsx. */ |
| 35 | export function defaultFilename(title: string, now: Date): string { |
| 36 | const slug = |
| 37 | title |
| 38 | .replace(/[^A-Za-z0-9]+/g, '-') |
| 39 | .replace(/^-|-$/g, '') |
| 40 | .slice(0, 60) |
| 41 | .replace(/-$/, '') || 'export' |
| 42 | const p = (n: number) => String(n).padStart(2, '0') |
| 43 | const stamp = `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}-${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}` |
| 44 | return `${slug}-${stamp}.xlsx` |
| 45 | } |
| 46 | |
| 47 | /** A user-given file name made safe for Windows and macOS, ending .xlsx. */ |
| 48 | export function cleanFilename(name: string): string { |
| 49 | const base = name |
| 50 | .split(/[\\/]/) |
| 51 | .pop()! |
| 52 | .replace(/[<>:"|?*\x00-\x1f]/g, '-') |
| 53 | .replace(/\.xlsx$/i, '') |
| 54 | .trim() |
| 55 | return `${base || 'export'}.xlsx` |
| 56 | } |