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
+}