Support ODS, XLS, and other spreadsheet formats in the attachment editor.

Detect editable attachments by extension and write back in the original book type instead of always forcing xlsx.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-27 23:56:14 +01:00
co-authored by Cursor
parent 6e6b13270a
commit 26a3723a06
3 changed files with 51 additions and 10 deletions
+2 -5
View File
@@ -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 @@
<span class="muted">{formatBytes(a.sizeBytes)}</span>
</div>
<div class="row" style="width: auto; justify-content: flex-end;">
{#if isExcelName(a.filename)}
{#if isSpreadsheetName(a.filename)}
<button class="primary" onclick={() => (excelAtt = a)}>Edit</button>
{/if}
<a
+4 -5
View File
@@ -1,5 +1,6 @@
<script>
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) {
+45
View File
@@ -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
}