diff --git a/web/src/App.svelte b/web/src/App.svelte index 62a02b8..9229ebc 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -4,6 +4,7 @@ import { parseRoute, buildHash, currentHash } from './lib/route.js' import MarkdownEditor from './lib/MarkdownEditor.svelte' import ExcelEditor from './lib/ExcelEditor.svelte' + import { isSpreadsheetName } from './lib/spreadsheet.js' let user = $state(null) let loading = $state(true) @@ -398,10 +399,6 @@ return `${(n / (1024 * 1024)).toFixed(1)} MB` } - function isExcelName(name) { - return /\.xlsx$/i.test(name || '') - } - async function onAttachFile(e) { const file = e.target?.files?.[0] e.target.value = '' @@ -963,7 +960,7 @@ {formatBytes(a.sizeBytes)}
- {#if isExcelName(a.filename)} + {#if isSpreadsheetName(a.filename)} {/if} import { api } from './api.js' + import { spreadsheetWriteOptions } from './spreadsheet.js' let { projectId, @@ -87,11 +88,9 @@ try { await ensureXlsx() workbook.Sheets[activeSheet] = gridToSheet(grid) - const out = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }) - await api.putAttachment(projectId, ticketId, attachment.id, out, { - contentType: - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - }) + const { bookType, contentType } = spreadsheetWriteOptions(attachment.filename) + const out = XLSX.write(workbook, { bookType, type: 'array' }) + await api.putAttachment(projectId, ticketId, attachment.id, out, { contentType }) onsaved() msgOk() } catch (e) { diff --git a/web/src/lib/spreadsheet.js b/web/src/lib/spreadsheet.js new file mode 100644 index 0000000..1cd15ad --- /dev/null +++ b/web/src/lib/spreadsheet.js @@ -0,0 +1,45 @@ +/** Extensions SheetJS can read/write for simple cell editing. */ +const FORMATS = { + xlsx: { + bookType: 'xlsx', + contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }, + xlsm: { + bookType: 'xlsm', + contentType: 'application/vnd.ms-excel.sheet.macroEnabled.12', + }, + xlsb: { + bookType: 'xlsb', + contentType: 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + }, + xls: { + bookType: 'xls', + contentType: 'application/vnd.ms-excel', + }, + ods: { + bookType: 'ods', + contentType: 'application/vnd.oasis.opendocument.spreadsheet', + }, + fods: { + bookType: 'fods', + contentType: 'application/vnd.oasis.opendocument.spreadsheet', + }, + csv: { + bookType: 'csv', + contentType: 'text/csv', + }, +} + +const EXT_RE = /\.([a-z0-9]+)$/i + +export function isSpreadsheetName(name) { + const m = String(name || '').match(EXT_RE) + if (!m) return false + return Object.prototype.hasOwnProperty.call(FORMATS, m[1].toLowerCase()) +} + +export function spreadsheetWriteOptions(filename) { + const m = String(filename || '').match(EXT_RE) + const ext = m ? m[1].toLowerCase() : 'xlsx' + return FORMATS[ext] || FORMATS.xlsx +}