feat: render rich OneNote pages and resources
This commit is contained in:
65
src/App.tsx
65
src/App.tsx
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user