feat: render rich OneNote pages and resources

This commit is contained in:
2026-07-22 19:18:42 +02:00
parent 141be03f5e
commit 1750a6b5c3
12 changed files with 1252 additions and 64 deletions

View File

@@ -16,6 +16,7 @@ import type {
OneNotePackageDto,
OneNotePackagedSectionDto,
OneNotePageDto,
OneNoteResourcePayloadDto,
OneNoteSectionDto,
} from './onenote/model/dto.js';
import type { ParserDiagnostic } from './onenote/model/diagnostics.js';
@@ -302,6 +303,54 @@ export function OneNoteApplication({
}
};
const loadSelectedResource = useCallback(
async (resourceId: string): Promise<OneNoteResourcePayloadDto> => {
const client = activeClient.current;
if (!client || state.phase !== 'loaded') {
throw new WorkerClientError(
'session-not-available',
'The local parser session is no longer available.'
);
}
const response = await client.getResource(
state.source.sessionId,
resourceId,
state.source.kind === 'onepkg' ? selectedSectionId : undefined
);
if (response.type === 'failure') {
throw new WorkerClientError(
response.error.code,
response.error.message
);
}
return response.resource;
},
[selectedSectionId, state]
);
const downloadSelectedResource = useCallback(
async (resourceId: string, suggestedName?: string): Promise<void> => {
const resource = await loadSelectedResource(resourceId);
const filename = safeDownloadFilename(
resource.filename ??
suggestedName ??
`onenote-resource.${resource.extension ?? 'bin'}`
);
const url = URL.createObjectURL(
new Blob([resource.bytes], { type: resource.mediaType })
);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = filename;
anchor.rel = 'noopener';
document.body.append(anchor);
anchor.click();
anchor.remove();
window.setTimeout(() => URL.revokeObjectURL(url), 1_000);
},
[loadSelectedResource]
);
const visibleDiagnostics = uniqueDiagnostics(
(() => {
if (state.phase !== 'loaded') return [];
@@ -390,6 +439,8 @@ export function OneNoteApplication({
error={
pageState.phase === 'error' ? pageState.message : undefined
}
loadResource={loadSelectedResource}
downloadResource={downloadSelectedResource}
/>
</div>
@@ -420,6 +471,20 @@ export function OneNoteApplication({
);
}
function safeDownloadFilename(value: string): string {
const basename = value.split(/[\\/]/u).at(-1) ?? value;
const withoutUnsafeCharacters = [...basename.normalize('NFC')]
.map((character) => {
const code = character.codePointAt(0) ?? 0;
return code <= 0x1f || code === 0x7f || '<>:"|?*'.includes(character)
? '_'
: character;
})
.join('');
const cleaned = withoutUnsafeCharacters.replace(/[. ]+$/gu, '').trim();
return (cleaned || 'onenote-resource.bin').slice(0, 180);
}
export function App() {
const [helpOpen, setHelpOpen] = useState(false);