feat: Add VitePress docs, EPUB to PDF tool, Phosphor icons, and licensing updates
- Set up VitePress documentation site (docs:dev, docs:build, docs:preview)
- Added Getting Started, Tools Reference, Contributing, and Commercial License pages
- Created self-hosting guides for Docker, Vercel, Netlify, Cloudflare, AWS, Hostinger, Nginx, Apache
- Updated README with documentation link, sponsors section, and docs contribution guide
- Added EPUB to PDF converter using LibreOffice WASM
- Migrated to Phosphor Icons for consistent iconography
- Added donation ribbon banner on landing page
- Removed 'Like My Work?' section (replaced by ribbon)
- Updated licensing.html with delivery model, AGPL notice, invoicing, and no-refund policy
- Added Commercial License documentation page
- Updated translations table (Chinese added, marked non-English as In Progress)
- Added sponsors.yml workflow for auto-generating sponsor avatars
2025-12-27 19:30:31 +05:30
|
|
|
/**
|
|
|
|
|
* LibreOffice WASM Converter Wrapper
|
|
|
|
|
*
|
|
|
|
|
* Uses @matbee/libreoffice-converter package for document conversion.
|
|
|
|
|
* Handles progress tracking and provides simpler API.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { WorkerBrowserConverter } from '@matbee/libreoffice-converter/browser';
|
2026-01-04 19:08:50 +05:30
|
|
|
|
|
|
|
|
const LIBREOFFICE_LOCAL_PATH = import.meta.env.BASE_URL + 'libreoffice-wasm/';
|
feat: Add VitePress docs, EPUB to PDF tool, Phosphor icons, and licensing updates
- Set up VitePress documentation site (docs:dev, docs:build, docs:preview)
- Added Getting Started, Tools Reference, Contributing, and Commercial License pages
- Created self-hosting guides for Docker, Vercel, Netlify, Cloudflare, AWS, Hostinger, Nginx, Apache
- Updated README with documentation link, sponsors section, and docs contribution guide
- Added EPUB to PDF converter using LibreOffice WASM
- Migrated to Phosphor Icons for consistent iconography
- Added donation ribbon banner on landing page
- Removed 'Like My Work?' section (replaced by ribbon)
- Updated licensing.html with delivery model, AGPL notice, invoicing, and no-refund policy
- Added Commercial License documentation page
- Updated translations table (Chinese added, marked non-English as In Progress)
- Added sponsors.yml workflow for auto-generating sponsor avatars
2025-12-27 19:30:31 +05:30
|
|
|
|
|
|
|
|
export interface LoadProgress {
|
|
|
|
|
phase: 'loading' | 'initializing' | 'converting' | 'complete' | 'ready';
|
|
|
|
|
percent: number;
|
|
|
|
|
message: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ProgressCallback = (progress: LoadProgress) => void;
|
|
|
|
|
|
|
|
|
|
// Singleton for converter instance
|
|
|
|
|
let converterInstance: LibreOfficeConverter | null = null;
|
|
|
|
|
|
|
|
|
|
export class LibreOfficeConverter {
|
|
|
|
|
private converter: WorkerBrowserConverter | null = null;
|
|
|
|
|
private initialized = false;
|
|
|
|
|
private initializing = false;
|
|
|
|
|
private basePath: string;
|
|
|
|
|
|
2025-12-30 22:58:25 +05:30
|
|
|
constructor(basePath?: string) {
|
2026-01-04 19:08:50 +05:30
|
|
|
this.basePath = basePath || LIBREOFFICE_LOCAL_PATH;
|
feat: Add VitePress docs, EPUB to PDF tool, Phosphor icons, and licensing updates
- Set up VitePress documentation site (docs:dev, docs:build, docs:preview)
- Added Getting Started, Tools Reference, Contributing, and Commercial License pages
- Created self-hosting guides for Docker, Vercel, Netlify, Cloudflare, AWS, Hostinger, Nginx, Apache
- Updated README with documentation link, sponsors section, and docs contribution guide
- Added EPUB to PDF converter using LibreOffice WASM
- Migrated to Phosphor Icons for consistent iconography
- Added donation ribbon banner on landing page
- Removed 'Like My Work?' section (replaced by ribbon)
- Updated licensing.html with delivery model, AGPL notice, invoicing, and no-refund policy
- Added Commercial License documentation page
- Updated translations table (Chinese added, marked non-English as In Progress)
- Added sponsors.yml workflow for auto-generating sponsor avatars
2025-12-27 19:30:31 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async initialize(onProgress?: ProgressCallback): Promise<void> {
|
|
|
|
|
if (this.initialized) return;
|
|
|
|
|
|
|
|
|
|
if (this.initializing) {
|
|
|
|
|
while (this.initializing) {
|
|
|
|
|
await new Promise(r => setTimeout(r, 100));
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.initializing = true;
|
|
|
|
|
let progressCallback = onProgress; // Store original callback
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
progressCallback?.({ phase: 'loading', percent: 0, message: 'Loading conversion engine...' });
|
|
|
|
|
|
|
|
|
|
this.converter = new WorkerBrowserConverter({
|
|
|
|
|
sofficeJs: `${this.basePath}soffice.js`,
|
|
|
|
|
sofficeWasm: `${this.basePath}soffice.wasm.gz`,
|
|
|
|
|
sofficeData: `${this.basePath}soffice.data.gz`,
|
2026-01-04 19:08:50 +05:30
|
|
|
sofficeWorkerJs: `${this.basePath}soffice.worker.js`,
|
|
|
|
|
browserWorkerJs: `${this.basePath}browser.worker.global.js`,
|
feat: Add VitePress docs, EPUB to PDF tool, Phosphor icons, and licensing updates
- Set up VitePress documentation site (docs:dev, docs:build, docs:preview)
- Added Getting Started, Tools Reference, Contributing, and Commercial License pages
- Created self-hosting guides for Docker, Vercel, Netlify, Cloudflare, AWS, Hostinger, Nginx, Apache
- Updated README with documentation link, sponsors section, and docs contribution guide
- Added EPUB to PDF converter using LibreOffice WASM
- Migrated to Phosphor Icons for consistent iconography
- Added donation ribbon banner on landing page
- Removed 'Like My Work?' section (replaced by ribbon)
- Updated licensing.html with delivery model, AGPL notice, invoicing, and no-refund policy
- Added Commercial License documentation page
- Updated translations table (Chinese added, marked non-English as In Progress)
- Added sponsors.yml workflow for auto-generating sponsor avatars
2025-12-27 19:30:31 +05:30
|
|
|
verbose: false,
|
|
|
|
|
onProgress: (info: { phase: string; percent: number; message: string }) => {
|
|
|
|
|
if (progressCallback && !this.initialized) {
|
|
|
|
|
const simplifiedMessage = `Loading conversion engine (${Math.round(info.percent)}%)...`;
|
|
|
|
|
progressCallback({
|
|
|
|
|
phase: info.phase as LoadProgress['phase'],
|
|
|
|
|
percent: info.percent,
|
|
|
|
|
message: simplifiedMessage
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onReady: () => {
|
|
|
|
|
console.log('[LibreOffice] Ready!');
|
|
|
|
|
},
|
|
|
|
|
onError: (error: Error) => {
|
|
|
|
|
console.error('[LibreOffice] Error:', error);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await this.converter.initialize();
|
|
|
|
|
this.initialized = true;
|
|
|
|
|
|
|
|
|
|
// Call completion message
|
|
|
|
|
progressCallback?.({ phase: 'ready', percent: 100, message: 'Conversion engine ready!' });
|
|
|
|
|
|
|
|
|
|
// Null out the callback to prevent any late-firing progress updates
|
|
|
|
|
progressCallback = undefined;
|
|
|
|
|
} finally {
|
|
|
|
|
this.initializing = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isReady(): boolean {
|
|
|
|
|
return this.initialized && this.converter !== null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async convertToPdf(file: File): Promise<Blob> {
|
|
|
|
|
if (!this.converter) {
|
|
|
|
|
throw new Error('Converter not initialized');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[LibreOffice] Converting ${file.name} to PDF...`);
|
|
|
|
|
console.log(`[LibreOffice] File type: ${file.type}, Size: ${file.size} bytes`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[LibreOffice] Reading file as ArrayBuffer...`);
|
|
|
|
|
const arrayBuffer = await file.arrayBuffer();
|
|
|
|
|
const uint8Array = new Uint8Array(arrayBuffer);
|
|
|
|
|
console.log(`[LibreOffice] File loaded, ${uint8Array.length} bytes`);
|
|
|
|
|
|
|
|
|
|
console.log(`[LibreOffice] Calling converter.convert() with buffer...`);
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
|
|
// Detect input format - critical for CSV to apply import filters
|
|
|
|
|
const ext = file.name.split('.').pop()?.toLowerCase() || '';
|
|
|
|
|
console.log(`[LibreOffice] Detected format from extension: ${ext}`);
|
|
|
|
|
|
|
|
|
|
const result = await this.converter.convert(uint8Array, {
|
|
|
|
|
outputFormat: 'pdf',
|
|
|
|
|
inputFormat: ext as any, // Explicitly specify format for CSV import filters
|
|
|
|
|
}, file.name);
|
|
|
|
|
|
|
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
|
console.log(`[LibreOffice] Conversion complete! Duration: ${duration}ms, Size: ${result.data.length} bytes`);
|
|
|
|
|
|
|
|
|
|
// Create a copy to avoid SharedArrayBuffer type issues
|
|
|
|
|
const data = new Uint8Array(result.data);
|
|
|
|
|
return new Blob([data], { type: result.mimeType });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[LibreOffice] Conversion FAILED for ${file.name}:`, error);
|
|
|
|
|
console.error(`[LibreOffice] Error details:`, {
|
|
|
|
|
message: error instanceof Error ? error.message : String(error),
|
|
|
|
|
stack: error instanceof Error ? error.stack : undefined
|
|
|
|
|
});
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async wordToPdf(file: File): Promise<Blob> {
|
|
|
|
|
return this.convertToPdf(file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async pptToPdf(file: File): Promise<Blob> {
|
|
|
|
|
return this.convertToPdf(file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async excelToPdf(file: File): Promise<Blob> {
|
|
|
|
|
return this.convertToPdf(file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async destroy(): Promise<void> {
|
|
|
|
|
if (this.converter) {
|
|
|
|
|
await this.converter.destroy();
|
|
|
|
|
}
|
|
|
|
|
this.converter = null;
|
|
|
|
|
this.initialized = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getLibreOfficeConverter(basePath?: string): LibreOfficeConverter {
|
|
|
|
|
if (!converterInstance) {
|
|
|
|
|
converterInstance = new LibreOfficeConverter(basePath);
|
|
|
|
|
}
|
|
|
|
|
return converterInstance;
|
|
|
|
|
}
|