feat: add safe OneNote export serializers

This commit is contained in:
2026-07-22 19:36:20 +02:00
parent f0f6e48188
commit ece969e269
7 changed files with 2328 additions and 0 deletions

178
src/onenote/export/zip.ts Normal file
View File

@@ -0,0 +1,178 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
import { strToU8, zipSync, type Zippable } from 'fflate';
import { forceFilenameExtension, sanitizeExportFilename } from './naming.js';
import { createOneNoteExportPlan, type OneNoteExportPlan } from './plan.js';
import {
serializeHtmlWithPlan,
serializeMarkdownWithPlan,
serializePlainTextWithPlan,
serializeStructuredJsonWithPlan,
} from './serializers.js';
import type { OneNoteExportOptions, OneNoteExportSnapshot } from './types.js';
export interface OneNoteStaticNotebookManifest {
schemaVersion: number;
exportKind: 'onenote-tools-static-notebook';
app: OneNoteExportSnapshot['app'];
parser: OneNoteExportSnapshot['parser'];
supportLevel: OneNoteExportSnapshot['supportLevel'];
supportNotes: string[];
source: OneNoteExportSnapshot['source'];
notebookName: string;
files: {
viewer: 'index.html';
structuredData: 'notebook.json';
plainText: 'notebook.txt';
markdown: 'notebook.md';
warnings: 'warnings.json';
};
counts: {
sections: number;
pages: number;
blocks: number;
resources: number;
diagnostics: number;
exportWarnings: number;
unsupportedContent: number;
};
sections: Array<{
id: string;
name: string;
sourcePath?: string;
pageCount: number;
}>;
resources: Array<{
sectionId: string;
id: string;
kind: 'image' | 'attachment';
path: string;
filename: string;
mediaType: string;
size: number;
}>;
}
export interface OneNoteExportWarningsManifest {
schemaVersion: number;
app: OneNoteExportSnapshot['app'];
parser: OneNoteExportSnapshot['parser'];
supportLevel: OneNoteExportSnapshot['supportLevel'];
source: OneNoteExportSnapshot['source'];
diagnostics: OneNoteExportPlan['diagnostics'];
exportWarnings: OneNoteExportPlan['warnings'];
unsupportedContent: OneNoteExportPlan['unsupportedContent'];
}
export interface OneNoteStaticNotebookZipArtifact {
filename: string;
mediaType: 'application/zip';
bytes: Uint8Array;
manifest: OneNoteStaticNotebookManifest;
}
/**
* Create an application-owned static export. This never writes or claims to
* write a .one/.onepkg file, and it never interprets source HTML or SVG.
*/
export function createOneNoteStaticNotebookZip(
snapshot: OneNoteExportSnapshot,
options: OneNoteExportOptions = {}
): OneNoteStaticNotebookZipArtifact {
const plan = createOneNoteExportPlan(snapshot, options);
const manifest = createStaticManifest(snapshot, plan);
const warnings: OneNoteExportWarningsManifest = {
schemaVersion: snapshot.schemaVersion,
app: snapshot.app,
parser: snapshot.parser,
supportLevel: snapshot.supportLevel,
source: snapshot.source,
diagnostics: plan.diagnostics,
exportWarnings: plan.warnings,
unsupportedContent: plan.unsupportedContent,
};
const files: Zippable = Object.create(null) as Zippable;
files['index.html'] = strToU8(serializeHtmlWithPlan(snapshot, plan, true));
files['notebook.json'] = strToU8(
serializeStructuredJsonWithPlan(snapshot, plan)
);
files['notebook.txt'] = strToU8(serializePlainTextWithPlan(snapshot, plan));
files['notebook.md'] = strToU8(
serializeMarkdownWithPlan(snapshot, plan, true)
);
files['manifest.json'] = jsonBytes(manifest);
files['warnings.json'] = jsonBytes(warnings);
for (const resource of plan.resources) files[resource.path] = resource.bytes;
const bytes = zipSync(files, {
level: 6,
mtime: new Date(1980, 0, 1, 0, 0, 0),
os: 3,
attrs: 0o644 << 16,
});
const safeName = sanitizeExportFilename(
snapshot.notebookName,
'onenote-export'
);
return {
filename: forceFilenameExtension(safeName, 'zip'),
mediaType: 'application/zip',
bytes,
manifest,
};
}
function createStaticManifest(
snapshot: OneNoteExportSnapshot,
plan: OneNoteExportPlan
): OneNoteStaticNotebookManifest {
return {
schemaVersion: snapshot.schemaVersion,
exportKind: 'onenote-tools-static-notebook',
app: snapshot.app,
parser: snapshot.parser,
supportLevel: snapshot.supportLevel,
supportNotes: snapshot.supportNotes,
source: snapshot.source,
notebookName: snapshot.notebookName,
files: {
viewer: 'index.html',
structuredData: 'notebook.json',
plainText: 'notebook.txt',
markdown: 'notebook.md',
warnings: 'warnings.json',
},
counts: {
sections: snapshot.sections.length,
pages: plan.pageCount,
blocks: plan.blockCount,
resources: plan.resources.length,
diagnostics: plan.diagnostics.length,
exportWarnings: plan.warnings.length,
unsupportedContent: plan.unsupportedContent.length,
},
sections: snapshot.sections.map((section) => ({
id: section.id,
name: section.name,
...(section.sourcePath ? { sourcePath: section.sourcePath } : {}),
pageCount: section.pages.length,
})),
resources: plan.resources.map((resource) => ({
sectionId: resource.sectionId,
id: resource.id,
kind: resource.kind,
path: resource.path,
filename: resource.filename,
mediaType: resource.mediaType,
size: resource.size,
})),
};
}
function jsonBytes(value: unknown): Uint8Array {
return strToU8(`${JSON.stringify(value, null, 2)}\n`);
}