feat: ott support
This commit is contained in:
1 parent
a535738301
commit
1abdb428ba
5 files changed
+35
-7
No files matched your search
@@ -75,6 +75,7 @@
|
||||
**File & export**
|
||||
- ODT export via odf-kit with extensive content.xml / styles.xml post-processing (custom node types, tables with borders, images, fonts and colors); filename derived from the first heading (fallback `document.odt`)
|
||||
- ODT import / open existing file: parses content.xml / styles.xml directly, adopts the file's margins and orientation, and reports graceful-degradation warnings
|
||||
- Opens `.ott` templates (OpenDocument Text Template): read like an `.odt`, but as a new untitled document — the first Save writes a fresh `.odt` and never overwrites the template (Word/LibreOffice behavior)
|
||||
- Word (.docx) export and import — round-trips the editor's formatting (text, fonts, lists, tables, images, headers/footers, page geometry) and opens real Word documents
|
||||
- PDF export — Raster (pixel-exact copy of the editor with a selectable text layer) and Vector (crisp, fonts embedded, via the browser print dialog)
|
||||
- Print (printer button / Ctrl+P) — opens the browser print dialog with a pixel-exact raster of the document (tables, headers/footers and page breaks intact)
|
||||
|
||||
+5
-2
@@ -335,6 +335,9 @@
|
||||
if (!editor) return;
|
||||
try {
|
||||
const isDocx = sourceName?.toLowerCase().endsWith('.docx');
|
||||
// An .ott is a template: load its content but don't bind the handle, so the
|
||||
// first Save prompts for a new .odt instead of overwriting the template.
|
||||
const isTemplate = sourceName?.toLowerCase().endsWith('.ott');
|
||||
// Pre-decode any images in a format the browser can't render (TIFF, …) to PNG.
|
||||
// Lazy: the decoder loads only when such an image is present, else this is a no-op.
|
||||
const converted = await convertUnsupportedImages(bytes);
|
||||
@@ -364,7 +367,7 @@
|
||||
header: result.headerDistanceCm ?? DEFAULT_HF_DISTANCES.header,
|
||||
footer: result.footerDistanceCm ?? DEFAULT_HF_DISTANCES.footer,
|
||||
};
|
||||
fileHandle = handle;
|
||||
fileHandle = isTemplate ? null : handle;
|
||||
|
||||
if (result.warnings.length) {
|
||||
console.warn('[import] Unsupported content in opened file:', result.warnings);
|
||||
@@ -779,7 +782,7 @@
|
||||
<input
|
||||
bind:this={fileInput}
|
||||
type="file"
|
||||
accept=".odt,.docx,application/vnd.oasis.opendocument.text,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
accept=".odt,.ott,.docx,application/vnd.oasis.opendocument.text,application/vnd.oasis.opendocument.text-template,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
class="file-input"
|
||||
onchange={handleImportFile}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// plain browser download / no-op where the API is unavailable (Firefox/Safari).
|
||||
|
||||
const ODT_MIME = 'application/vnd.oasis.opendocument.text';
|
||||
const OTT_MIME = 'application/vnd.oasis.opendocument.text-template';
|
||||
const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
||||
|
||||
const PICKER_TYPES = [
|
||||
@@ -12,6 +13,12 @@ const DOCX_PICKER_TYPES = [
|
||||
{ description: 'Word Document', accept: { [DOCX_MIME]: ['.docx'] } },
|
||||
];
|
||||
|
||||
// The open picker also accepts .ott templates (read-only; saving stays .odt-only).
|
||||
const OPEN_PICKER_TYPES = [
|
||||
{ description: 'OpenDocument Text', accept: { [ODT_MIME]: ['.odt'], [OTT_MIME]: ['.ott'] } },
|
||||
...DOCX_PICKER_TYPES,
|
||||
];
|
||||
|
||||
// showSaveFilePicker/showOpenFilePicker are not in lib.dom yet; reach them via casts.
|
||||
type WinFs = Window & {
|
||||
showSaveFilePicker?: (opts: unknown) => Promise<FileSystemFileHandle>;
|
||||
@@ -81,10 +88,10 @@ export async function saveAsDocx(bytes: Uint8Array, suggestedName: string): Prom
|
||||
await writeHandle(handle, bytes);
|
||||
}
|
||||
|
||||
// Prompt for an .odt/.docx to open, capturing its handle so a later save can overwrite
|
||||
// the same file. Returns null if cancelled. Only call when supportsFsAccess().
|
||||
// Prompt for an .odt/.ott/.docx to open, capturing its handle so a later save can
|
||||
// overwrite the same file. Returns null if cancelled. Only call when supportsFsAccess().
|
||||
export async function openOdt(): Promise<{ bytes: Uint8Array; handle: FileSystemFileHandle; name: string } | null> {
|
||||
const [handle] = await (window as WinFs).showOpenFilePicker!({ types: [...PICKER_TYPES, ...DOCX_PICKER_TYPES], multiple: false });
|
||||
const [handle] = await (window as WinFs).showOpenFilePicker!({ types: OPEN_PICKER_TYPES, multiple: false });
|
||||
if (!handle) return null;
|
||||
const file = await handle.getFile();
|
||||
return { bytes: new Uint8Array(await file.arrayBuffer()), handle, name: file.name };
|
||||
|
||||
@@ -11,9 +11,9 @@ export function saveDocName(name: string): void {
|
||||
localStorage.setItem(KEY, name);
|
||||
}
|
||||
|
||||
// Drop a trailing .odt (case-insensitive) so the field shows just the name.
|
||||
// Drop a trailing .odt or .ott (case-insensitive) so the field shows just the name.
|
||||
export function stripOdtExtension(name: string): string {
|
||||
return name.replace(/\.odt$/i, '');
|
||||
return name.replace(/\.o[dt]t$/i, '');
|
||||
}
|
||||
|
||||
// Strip filesystem-illegal characters; keep spaces so user-typed titles read
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { stripOdtExtension } from '../../src/lib/storage/documentName';
|
||||
|
||||
describe('stripOdtExtension', () => {
|
||||
it('drops a trailing .odt or .ott (case-insensitive)', () => {
|
||||
expect(stripOdtExtension('Foo.odt')).toBe('Foo');
|
||||
expect(stripOdtExtension('Foo.ott')).toBe('Foo');
|
||||
expect(stripOdtExtension('Report.ODT')).toBe('Report');
|
||||
expect(stripOdtExtension('Report.OTT')).toBe('Report');
|
||||
});
|
||||
|
||||
it('leaves other names and extensions untouched', () => {
|
||||
expect(stripOdtExtension('Foo.docx')).toBe('Foo.docx');
|
||||
expect(stripOdtExtension('my.odt.notes')).toBe('my.odt.notes');
|
||||
expect(stripOdtExtension('plain')).toBe('plain');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user