feat(core): onenote importer (#15198)

#### PR Dependency Tree


* **PR #15198** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added OneNote import support for `.one`, `.onetoc2`, and `.onepkg`,
including OneNote-to-markdown content conversion.
* OneNote now appears in the import flow with a dedicated label and
format tooltip, and file pickers recognize the new OneNote type.

* **Bug Fixes**
* Import options that are desktop-only are now disabled when not running
in the desktop app, with clear messaging.
* Improved imported asset handling by converting non-image embedded
assets into attachments for more consistent results.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-07-06 06:20:52 +08:00
committed by GitHub
parent 8d72e4dc29
commit 9581432d21
22 changed files with 1621 additions and 112 deletions
@@ -38,6 +38,7 @@ test('should clear pending mail records when user is deleted', async t => {
const userKey = `${sendMailKey}:SignIn:${user.email}`;
const userRetryKey = `${sendMailKey}:VerifyEmail:${user.email}`;
const anotherKey = `${sendMailKey}:SignIn:${another.email}`;
const senderRetryKey = `${retryMailKey}:MemberInvitation:invited@affine.pro`;
await cache.mapSet(sendMailKey, userKey, 1);
await cache.mapSet(sendMailKey, anotherKey, 1);
@@ -51,6 +52,20 @@ test('should clear pending mail records when user is deleted', async t => {
props: { url: 'https://affine.pro/verify' },
})
);
await cache.mapSet(
retryMailKey,
senderRetryKey,
JSON.stringify({
startTime: Date.now(),
name: 'MemberInvitation',
to: 'invited@affine.pro',
props: {
user: { $$userId: user.id },
workspace: { $$workspaceId: 'workspace-id' },
url: 'https://affine.pro/invite',
},
})
);
await mailJob.onUserDeleted({ ...user, ownedWorkspaces: [] });
@@ -62,6 +77,17 @@ test('should clear pending mail records when user is deleted', async t => {
to: user.email,
} as Jobs['notification.sendMail'])
);
t.true(
await shouldRemove({
name: 'MemberInvitation',
to: 'invited@affine.pro',
props: {
user: { $$userId: user.id },
workspace: { $$workspaceId: 'workspace-id' },
url: 'https://affine.pro/invite',
},
} as Jobs['notification.sendMail'])
);
t.false(
await shouldRemove({
to: another.email,
@@ -69,6 +95,7 @@ test('should clear pending mail records when user is deleted', async t => {
);
t.is(await cache.mapGet(sendMailKey, userKey), undefined);
t.is(await cache.mapGet(retryMailKey, userRetryKey), undefined);
t.is(await cache.mapGet(retryMailKey, senderRetryKey), undefined);
t.is(await cache.mapGet(sendMailKey, anotherKey), 1);
});
+35 -1
View File
@@ -126,6 +126,38 @@ export class MailJob {
);
}
private isInvitationMailSentByUser(
job: Jobs['notification.sendMail'],
userId: string
) {
return (
job.name === 'MemberInvitation' &&
'user' in job.props &&
typeof job.props.user === 'object' &&
job.props.user !== null &&
'$$userId' in job.props.user &&
job.props.user.$$userId === userId
);
}
private async deleteInvitationMailCacheBySender(userId: string) {
const keys = await this.cache.mapKeys(retryMailKey);
await Promise.all(
keys.map(async key => {
const job = await this.cache.mapGet<
Jobs['notification.sendMail'] | string
>(retryMailKey, key);
const jobData =
typeof job === 'string'
? (JSON.parse(job) as Jobs['notification.sendMail'])
: job;
if (jobData && this.isInvitationMailSentByUser(jobData, userId)) {
await this.cache.mapDelete(retryMailKey, key);
}
})
);
}
private async sendMailInternal({
startTime,
name,
@@ -294,9 +326,11 @@ export class MailJob {
async onUserDeleted(user: Events['user.deleted']) {
await Promise.all([
this.deleteRecipientMailCache(user.email),
this.deleteInvitationMailCacheBySender(user.id),
this.queue.removeWhere(
'notification.sendMail',
job => job.to === user.email
job =>
job.to === user.email || this.isInvitationMailSentByUser(job, user.id)
),
]);
}