mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-09 05:05:52 +08:00
feat: add new rule for floating promise (#2726)
Co-authored-by: Himself65 <himself65@outlook.com>
(cherry picked from commit bedf838fe5)
This commit is contained in:
@@ -119,7 +119,7 @@ export function createApplicationMenu() {
|
|||||||
{
|
{
|
||||||
label: 'Open log file',
|
label: 'Open log file',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
revealLogFile();
|
await revealLogFile();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ test('on applyUpdate (from renderer), will trigger update', async () => {
|
|||||||
db.update$.subscribe(onUpdate);
|
db.update$.subscribe(onUpdate);
|
||||||
const sub = dbSubjects.externalUpdate.subscribe(onExternalUpdate);
|
const sub = dbSubjects.externalUpdate.subscribe(onExternalUpdate);
|
||||||
db.applyUpdate(getTestUpdates(), 'renderer');
|
db.applyUpdate(getTestUpdates(), 'renderer');
|
||||||
expect(onUpdate).toHaveBeenCalled(); // not yet updated
|
expect(onUpdate).toHaveBeenCalled();
|
||||||
sub.unsubscribe();
|
sub.unsubscribe();
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
merge,
|
merge,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
|
concatMap,
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
filter,
|
filter,
|
||||||
ignoreElements,
|
ignoreElements,
|
||||||
@@ -126,10 +127,8 @@ function startPollingSecondaryDB(db: WorkspaceSQLiteDB) {
|
|||||||
switchMap(secondaryDB => {
|
switchMap(secondaryDB => {
|
||||||
return interval(300000).pipe(
|
return interval(300000).pipe(
|
||||||
startWith(0),
|
startWith(0),
|
||||||
|
concatMap(() => secondaryDB.pull()),
|
||||||
tap({
|
tap({
|
||||||
next: () => {
|
|
||||||
secondaryDB.pull();
|
|
||||||
},
|
|
||||||
error: err => {
|
error: err => {
|
||||||
logger.error(`[ensureSQLiteDB] polling secondary db error`, err);
|
logger.error(`[ensureSQLiteDB] polling secondary db error`, err);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
|||||||
return await fn();
|
return await fn();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
this.runCounter--;
|
this.runCounter--;
|
||||||
if (this.runCounter === 0) {
|
if (this.runCounter === 0) {
|
||||||
@@ -115,10 +116,10 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelfUpdate = (update: Uint8Array, origin: YOrigin) => {
|
const onSelfUpdate = async (update: Uint8Array, origin: YOrigin) => {
|
||||||
// for self update from upstream, we need to push it to external DB
|
// for self update from upstream, we need to push it to external DB
|
||||||
if (origin === 'upstream' && this.db) {
|
if (origin === 'upstream' && this.db) {
|
||||||
this.addUpdateToUpdateQueue(this.db, update);
|
await this.addUpdateToUpdateQueue(this.db, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (origin === 'self') {
|
if (origin === 'self') {
|
||||||
@@ -135,12 +136,18 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
|||||||
this.yDoc.off('update', onSelfUpdate);
|
this.yDoc.off('update', onSelfUpdate);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.run(async () => {
|
this.run(() => {
|
||||||
// apply all updates from upstream
|
// apply all updates from upstream
|
||||||
const upstreamUpdate = this.upstream.getDocAsUpdates();
|
const upstreamUpdate = this.upstream.getDocAsUpdates();
|
||||||
// to initialize the yDoc, we need to apply all updates from the db
|
// to initialize the yDoc, we need to apply all updates from the db
|
||||||
this.applyUpdate(upstreamUpdate, 'upstream');
|
this.applyUpdate(upstreamUpdate, 'upstream');
|
||||||
});
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.debug('run success');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error('run error', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
applyUpdate = (data: Uint8Array, origin: YOrigin = 'upstream') => {
|
applyUpdate = (data: Uint8Array, origin: YOrigin = 'upstream') => {
|
||||||
|
|||||||
@@ -79,19 +79,19 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
override async addBlob(key: string, value: Uint8Array) {
|
override async addBlob(key: string, value: Uint8Array) {
|
||||||
const res = await super.addBlob(key, value);
|
|
||||||
this.update$.next();
|
this.update$.next();
|
||||||
|
const res = await super.addBlob(key, value);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async deleteBlob(key: string) {
|
override async deleteBlob(key: string) {
|
||||||
super.deleteBlob(key);
|
|
||||||
this.update$.next();
|
this.update$.next();
|
||||||
|
await super.deleteBlob(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async addUpdateToSQLite(db: SqliteConnection, data: Uint8Array[]) {
|
override async addUpdateToSQLite(db: SqliteConnection, data: Uint8Array[]) {
|
||||||
super.addUpdateToSQLite(db, data);
|
|
||||||
this.update$.next();
|
this.update$.next();
|
||||||
|
await super.addUpdateToSQLite(db, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ export async function moveDBFile(
|
|||||||
filePath: newFilePath,
|
filePath: newFilePath,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
db?.destroy();
|
await db?.destroy();
|
||||||
logger.error('[moveDBFile]', err);
|
logger.error('[moveDBFile]', err);
|
||||||
return {
|
return {
|
||||||
error: 'UNKNOWN_ERROR',
|
error: 'UNKNOWN_ERROR',
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function savePDFFileAs(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
shell.openPath(filePath);
|
await shell.openPath(filePath);
|
||||||
return { filePath };
|
return { filePath };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('savePDFFileAs', err);
|
logger.error('savePDFFileAs', err);
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ if (!isSingleInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.on('second-instance', () => {
|
app.on('second-instance', () => {
|
||||||
restoreOrCreateWindow();
|
restoreOrCreateWindow().catch(e =>
|
||||||
|
console.error('Failed to restore or create window:', e)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('open-url', async (_, _url) => {
|
app.on('open-url', async (_, _url) => {
|
||||||
|
|||||||
@@ -28,10 +28,12 @@ export const getExchangeTokenParams = (code: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function getGoogleOauthCode() {
|
export function getGoogleOauthCode() {
|
||||||
shell.openExternal(oauthEndpoint);
|
|
||||||
|
|
||||||
return new Promise<ReturnType<typeof getExchangeTokenParams>>(
|
return new Promise<ReturnType<typeof getExchangeTokenParams>>(
|
||||||
(resolve, reject) => {
|
(resolve, reject) => {
|
||||||
|
shell.openExternal(oauthEndpoint).catch(e => {
|
||||||
|
logger.error('Failed to open external url', e);
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
const handleOpenUrl = async (_: any, url: string) => {
|
const handleOpenUrl = async (_: any, url: string) => {
|
||||||
const mainWindow = BrowserWindow.getAllWindows().find(
|
const mainWindow = BrowserWindow.getAllWindows().find(
|
||||||
w => !w.isDestroyed()
|
w => !w.isDestroyed()
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ export const registerUpdater = async () => {
|
|||||||
// register events for checkForUpdatesAndNotify
|
// register events for checkForUpdatesAndNotify
|
||||||
_autoUpdater.on('update-available', info => {
|
_autoUpdater.on('update-available', info => {
|
||||||
if (allowAutoUpdate) {
|
if (allowAutoUpdate) {
|
||||||
_autoUpdater?.downloadUpdate();
|
_autoUpdater?.downloadUpdate().catch(e => {
|
||||||
|
logger.error('Failed to download update', e);
|
||||||
|
});
|
||||||
logger.info('Update available, downloading...', info);
|
logger.info('Update available, downloading...', info);
|
||||||
}
|
}
|
||||||
updaterSubjects.updateAvailable.next({
|
updaterSubjects.updateAvailable.next({
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
|||||||
});
|
});
|
||||||
setEditorFlags(blockSuiteWorkspace);
|
setEditorFlags(blockSuiteWorkspace);
|
||||||
if (config.enablePreloading) {
|
if (config.enablePreloading) {
|
||||||
initPageWithPreloading(page);
|
initPageWithPreloading(page).catch(err => {
|
||||||
|
logger.error('init page with preloading failed', err);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
initEmptyPage(page);
|
initEmptyPage(page);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export class AffineErrorBoundary extends Component<
|
|||||||
pageId: error.workspace.meta.pageMetas[0].id,
|
pageId: error.workspace.meta.pageMetas[0].id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.finally(() => {
|
||||||
this.setState({ error: null });
|
this.setState({ error: null });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const WorkspaceSettingDetail: React.FC<
|
|||||||
const workspaceId = workspace.id;
|
const workspaceId = workspace.id;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAffine && isOwner) {
|
if (isAffine && isOwner) {
|
||||||
preload([QueryKey.getMembers, workspaceId], fetcher);
|
preload([QueryKey.getMembers, workspaceId], fetcher).catch(console.error);
|
||||||
}
|
}
|
||||||
}, [isAffine, isOwner, workspaceId]);
|
}, [isAffine, isOwner, workspaceId]);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ const PublishPanelAffine: React.FC<PublishPanelAffineProps> = ({
|
|||||||
const shareUrl = origin + '/public-workspace/' + workspace.id;
|
const shareUrl = origin + '/public-workspace/' + workspace.id;
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const publishWorkspace = useToggleWorkspacePublish(workspace);
|
const publishWorkspace = useToggleWorkspacePublish(workspace);
|
||||||
const copyUrl = useCallback(() => {
|
const copyUrl = useCallback(async () => {
|
||||||
navigator.clipboard.writeText(shareUrl);
|
await navigator.clipboard.writeText(shareUrl);
|
||||||
toast(t['Copied link to clipboard']());
|
toast(t['Copied link to clipboard']());
|
||||||
}, [shareUrl, t]);
|
}, [shareUrl, t]);
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
|
|||||||
|
|
||||||
const createPageAndOpen = () => {
|
const createPageAndOpen = () => {
|
||||||
const page = createPage();
|
const page = createPage();
|
||||||
openPage(blockSuiteWorkspace.id, page.id);
|
return openPage(blockSuiteWorkspace.id, page.id);
|
||||||
};
|
};
|
||||||
const createEdgelessAndOpen = () => {
|
const createEdgelessAndOpen = () => {
|
||||||
const page = createPage();
|
const page = createPage();
|
||||||
setPreferredMode(page.id, 'edgeless');
|
setPreferredMode(page.id, 'edgeless');
|
||||||
openPage(blockSuiteWorkspace.id, page.id);
|
return openPage(blockSuiteWorkspace.id, page.id);
|
||||||
};
|
};
|
||||||
const importFileAndOpen = async () => {
|
const importFileAndOpen = async () => {
|
||||||
const { showImportModal } = await import('@blocksuite/blocks');
|
const { showImportModal } = await import('@blocksuite/blocks');
|
||||||
|
|||||||
+3
-1
@@ -10,7 +10,9 @@ export const EditPage = () => {
|
|||||||
const { jumpToPage } = useRouterHelper(router);
|
const { jumpToPage } = useRouterHelper(router);
|
||||||
const onClickPage = useCallback(() => {
|
const onClickPage = useCallback(() => {
|
||||||
if (workspaceId && pageId) {
|
if (workspaceId && pageId) {
|
||||||
jumpToPage(workspaceId, pageId);
|
jumpToPage(workspaceId, pageId).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [jumpToPage, pageId, workspaceId]);
|
}, [jumpToPage, pageId, workspaceId]);
|
||||||
return (
|
return (
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ const LanguageMenuContent: FC = () => {
|
|||||||
const i18n = useI18N();
|
const i18n = useI18N();
|
||||||
const changeLanguage = useCallback(
|
const changeLanguage = useCallback(
|
||||||
(event: string) => {
|
(event: string) => {
|
||||||
i18n.changeLanguage(event);
|
void i18n.changeLanguage(event);
|
||||||
},
|
},
|
||||||
[i18n]
|
[i18n]
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-2
@@ -96,8 +96,8 @@ const LocalHeaderShareMenu: React.FC<BaseHeaderProps> = props => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
onConform={() => {
|
onConform={async () => {
|
||||||
onTransformWorkspace(
|
await onTransformWorkspace(
|
||||||
WorkspaceFlavour.LOCAL,
|
WorkspaceFlavour.LOCAL,
|
||||||
WorkspaceFlavour.AFFINE,
|
WorkspaceFlavour.AFFINE,
|
||||||
props.workspace as LocalWorkspace
|
props.workspace as LocalWorkspace
|
||||||
|
|||||||
+7
-10
@@ -138,17 +138,14 @@ export const SyncUser = () => {
|
|||||||
workspace as LocalWorkspace
|
workspace as LocalWorkspace
|
||||||
);
|
);
|
||||||
// fixme(himself65): refactor this
|
// fixme(himself65): refactor this
|
||||||
router
|
await router.replace({
|
||||||
.replace({
|
pathname: `/workspace/[workspaceId]/all`,
|
||||||
pathname: `/workspace/[workspaceId]/all`,
|
query: {
|
||||||
query: {
|
workspaceId: id,
|
||||||
workspaceId: id,
|
},
|
||||||
},
|
});
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
router.reload();
|
|
||||||
});
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
router.reload();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+3
-2
@@ -63,9 +63,10 @@ export const TrashButtonGroup = () => {
|
|||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.catch(error => {
|
||||||
blockSuiteWorkspace.removePage(pageId);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
blockSuiteWorkspace.removePage(pageId);
|
||||||
}}
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const MessageCenter: FC = memo(function MessageCenter() {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setPopup(false);
|
setPopup(false);
|
||||||
onLogout();
|
return onLogout();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast(Messages[event.detail.code].message);
|
toast(Messages[event.detail.code].message);
|
||||||
|
|||||||
@@ -64,9 +64,11 @@ export const PublishedResults: FC<PublishedResultsProps> = ({
|
|||||||
<Command.Item
|
<Command.Item
|
||||||
key={result.id}
|
key={result.id}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push(
|
router
|
||||||
`/public-workspace/${router.query.workspaceId}/${result.id}`
|
.push(
|
||||||
);
|
`/public-workspace/${router.query.workspaceId}/${result.id}`
|
||||||
|
)
|
||||||
|
.catch(err => console.error(err));
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
value={result.id}
|
value={result.id}
|
||||||
|
|||||||
@@ -71,7 +71,9 @@ export const Results: FC<ResultsProps> = ({
|
|||||||
value={page.id}
|
value={page.id}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
onClose();
|
onClose();
|
||||||
jumpToPage(blockSuiteWorkspace.id, page.id);
|
jumpToPage(blockSuiteWorkspace.id, page.id).catch(
|
||||||
|
console.error
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledListItem>
|
<StyledListItem>
|
||||||
@@ -95,7 +97,7 @@ export const Results: FC<ResultsProps> = ({
|
|||||||
value={link.title}
|
value={link.title}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
onClose();
|
onClose();
|
||||||
router.push(link.href);
|
router.push(link.href).catch(console.error);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledListItem>
|
<StyledListItem>
|
||||||
@@ -133,7 +135,9 @@ export const Results: FC<ResultsProps> = ({
|
|||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
onClose();
|
onClose();
|
||||||
assertExists(blockSuiteWorkspace.id);
|
assertExists(blockSuiteWorkspace.id);
|
||||||
jumpToPage(blockSuiteWorkspace.id, result.id);
|
jumpToPage(blockSuiteWorkspace.id, result.id).catch(error =>
|
||||||
|
console.error(error)
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
value={result.id}
|
value={result.id}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { DebugLogger } from '@affine/debug';
|
||||||
import type { BlobManager } from '@blocksuite/store';
|
import type { BlobManager } from '@blocksuite/store';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import type { BlockSuiteWorkspace } from '../shared';
|
import type { BlockSuiteWorkspace } from '../shared';
|
||||||
|
|
||||||
|
const logger = new DebugLogger('useWorkspaceBlob');
|
||||||
|
|
||||||
export function useWorkspaceBlob(
|
export function useWorkspaceBlob(
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||||
): BlobManager {
|
): BlobManager {
|
||||||
@@ -21,14 +24,19 @@ export function useWorkspaceBlobImage(
|
|||||||
setBlob(null);
|
setBlob(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
blobManager?.get(key).then(blob => {
|
blobManager
|
||||||
if (controller.signal.aborted) {
|
?.get(key)
|
||||||
return;
|
.then(blob => {
|
||||||
}
|
if (controller.signal.aborted) {
|
||||||
if (blob) {
|
return;
|
||||||
setBlob(blob);
|
}
|
||||||
}
|
if (blob) {
|
||||||
});
|
setBlob(blob);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error('Failed to get blob', err);
|
||||||
|
});
|
||||||
return () => {
|
return () => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -204,7 +204,9 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.documentElement.lang = i18n.language;
|
document.documentElement.lang = i18n.language;
|
||||||
// todo(himself65): this is a hack, we should use a better way to set the language
|
// todo(himself65): this is a hack, we should use a better way to set the language
|
||||||
setUpLanguage(i18n);
|
setUpLanguage(i18n)?.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
}, [i18n]);
|
}, [i18n]);
|
||||||
useTrackRouterHistoryEffect();
|
useTrackRouterHistoryEffect();
|
||||||
const currentWorkspaceId = useAtomValue(rootCurrentWorkspaceIdAtom);
|
const currentWorkspaceId = useAtomValue(rootCurrentWorkspaceIdAtom);
|
||||||
@@ -247,7 +249,9 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
|
|||||||
logger.info('mount first data:', items);
|
logger.info('mount first data:', items);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch();
|
fetch().catch(e => {
|
||||||
|
logger.error('fetch error:', e);
|
||||||
|
});
|
||||||
return () => {
|
return () => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
logger.info('unmount');
|
logger.info('unmount');
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const NotfoundPage = () => {
|
|||||||
<Button
|
<Button
|
||||||
shape="round"
|
shape="round"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push('/');
|
router.push('/').catch(err => console.error(err));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t['Back Home']()}
|
{t['Back Home']()}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const InvitePage: NextPageWithLayout = () => {
|
|||||||
inviteData.workspace_id,
|
inviteData.workspace_id,
|
||||||
WorkspaceSubPath.ALL,
|
WorkspaceSubPath.ALL,
|
||||||
RouteLogic.REPLACE
|
RouteLogic.REPLACE
|
||||||
);
|
).catch(err => console.error(err));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Go to Workspace
|
Go to Workspace
|
||||||
@@ -64,7 +64,7 @@ const InvitePage: NextPageWithLayout = () => {
|
|||||||
<Button
|
<Button
|
||||||
shape="round"
|
shape="round"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.replace(`/`);
|
router.replace(`/`).catch(err => console.error(err));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Back to Home
|
Back to Home
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const AllPage: NextPageWithLayout = () => {
|
|||||||
if (newTab) {
|
if (newTab) {
|
||||||
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
||||||
} else {
|
} else {
|
||||||
jumpToPage(currentWorkspace.id, pageId);
|
jumpToPage(currentWorkspace.id, pageId).catch(console.error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentWorkspace, jumpToPage]
|
[currentWorkspace, jumpToPage]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const SharedPages: NextPageWithLayout = () => {
|
|||||||
if (newTab) {
|
if (newTab) {
|
||||||
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
||||||
} else {
|
} else {
|
||||||
jumpToPage(currentWorkspace.id, pageId);
|
jumpToPage(currentWorkspace.id, pageId).catch(console.error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentWorkspace, jumpToPage]
|
[currentWorkspace, jumpToPage]
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ const TrashPage: NextPageWithLayout = () => {
|
|||||||
if (newTab) {
|
if (newTab) {
|
||||||
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
||||||
} else {
|
} else {
|
||||||
jumpToPage(currentWorkspace.id, pageId);
|
jumpToPage(currentWorkspace.id, pageId).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentWorkspace, jumpToPage]
|
[currentWorkspace, jumpToPage]
|
||||||
|
|||||||
@@ -129,7 +129,9 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
workspace => {
|
workspace => {
|
||||||
setOpenWorkspacesModal(false);
|
setOpenWorkspacesModal(false);
|
||||||
setCurrentWorkspaceId(workspace.id);
|
setCurrentWorkspaceId(workspace.id);
|
||||||
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[jumpToSubPath, setCurrentWorkspaceId, setOpenWorkspacesModal]
|
[jumpToSubPath, setCurrentWorkspaceId, setOpenWorkspacesModal]
|
||||||
)}
|
)}
|
||||||
@@ -137,7 +139,11 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
workspace => {
|
workspace => {
|
||||||
setOpenWorkspacesModal(false);
|
setOpenWorkspacesModal(false);
|
||||||
setCurrentWorkspaceId(workspace.id);
|
setCurrentWorkspaceId(workspace.id);
|
||||||
jumpToSubPath(workspace.id, WorkspaceSubPath.SETTING);
|
jumpToSubPath(workspace.id, WorkspaceSubPath.SETTING).catch(
|
||||||
|
error => {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[jumpToSubPath, setCurrentWorkspaceId, setOpenWorkspacesModal]
|
[jumpToSubPath, setCurrentWorkspaceId, setOpenWorkspacesModal]
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,9 +26,13 @@ function rpcToObservable<
|
|||||||
subscriber.complete();
|
subscriber.complete();
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
handler?.().then(t => {
|
handler?.()
|
||||||
subscriber.next(t);
|
.then(t => {
|
||||||
});
|
subscriber.next(t);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
subscriber.error(err);
|
||||||
|
});
|
||||||
return event(t => {
|
return event(t => {
|
||||||
subscriber.next(t);
|
subscriber.next(t);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,20 +92,25 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (page.awarenessStore.getFlag('enable_block_hub')) {
|
if (page.awarenessStore.getFlag('enable_block_hub')) {
|
||||||
editor.createBlockHub().then(blockHub => {
|
editor
|
||||||
if (blockHubRef.current) {
|
.createBlockHub()
|
||||||
blockHubRef.current.remove();
|
.then(blockHub => {
|
||||||
}
|
if (blockHubRef.current) {
|
||||||
blockHubRef.current = blockHub;
|
blockHubRef.current.remove();
|
||||||
const toolWrapper = document.querySelector('#toolWrapper');
|
}
|
||||||
if (!toolWrapper) {
|
blockHubRef.current = blockHub;
|
||||||
console.warn(
|
const toolWrapper = document.querySelector('#toolWrapper');
|
||||||
'toolWrapper not found, block hub feature will not be available.'
|
if (!toolWrapper) {
|
||||||
);
|
console.warn(
|
||||||
} else {
|
'toolWrapper not found, block hub feature will not be available.'
|
||||||
toolWrapper.appendChild(blockHub);
|
);
|
||||||
}
|
} else {
|
||||||
});
|
toolWrapper.appendChild(blockHub);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(editor);
|
container.appendChild(editor);
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
|
|||||||
const onClickCreateLink = useCallback(() => {
|
const onClickCreateLink = useCallback(() => {
|
||||||
setIsPublic(true);
|
setIsPublic(true);
|
||||||
}, [setIsPublic]);
|
}, [setIsPublic]);
|
||||||
const onClickCopyLink = useCallback(() => {
|
const onClickCopyLink = useCallback(async () => {
|
||||||
navigator.clipboard.writeText(sharingUrl);
|
await navigator.clipboard.writeText(sharingUrl);
|
||||||
toast(t['Copied link to clipboard']());
|
toast(t['Copied link to clipboard']());
|
||||||
}, [sharingUrl, t]);
|
}, [sharingUrl, t]);
|
||||||
const onDisablePublic = useCallback(() => {
|
const onDisablePublic = useCallback(() => {
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ describe('GraphQL fetcher', () => {
|
|||||||
|
|
||||||
const gql = gqlFetcherFactory('https://example.com/graphql');
|
const gql = gqlFetcherFactory('https://example.com/graphql');
|
||||||
|
|
||||||
it('should send POST request to given endpoint', () => {
|
it('should send POST request to given endpoint', async () => {
|
||||||
gql(
|
await gql(
|
||||||
// @ts-expect-error variables is actually optional
|
// @ts-expect-error variables is actually optional
|
||||||
{ query }
|
{ query }
|
||||||
);
|
);
|
||||||
@@ -44,8 +44,8 @@ describe('GraphQL fetcher', () => {
|
|||||||
expect(ctx.method).toBe('POST');
|
expect(ctx.method).toBe('POST');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send with correct graphql JSON body', () => {
|
it('should send with correct graphql JSON body', async () => {
|
||||||
gql({
|
await gql({
|
||||||
query,
|
query,
|
||||||
// @ts-expect-error forgive the fake variables
|
// @ts-expect-error forgive the fake variables
|
||||||
variables: { a: 1, b: '2', c: { d: false } },
|
variables: { a: 1, b: '2', c: { d: false } },
|
||||||
@@ -63,8 +63,8 @@ describe('GraphQL fetcher', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly ignore nil variables', () => {
|
it('should correctly ignore nil variables', async () => {
|
||||||
gql({
|
await gql({
|
||||||
query,
|
query,
|
||||||
// @ts-expect-error forgive the fake variables
|
// @ts-expect-error forgive the fake variables
|
||||||
variables: { a: false, b: null, c: undefined },
|
variables: { a: false, b: null, c: undefined },
|
||||||
@@ -74,7 +74,7 @@ describe('GraphQL fetcher', () => {
|
|||||||
'"{\\"query\\":\\"query { field }\\",\\"variables\\":{\\"a\\":false,\\"b\\":null},\\"operationName\\":\\"query\\"}"'
|
'"{\\"query\\":\\"query { field }\\",\\"variables\\":{\\"a\\":false,\\"b\\":null},\\"operationName\\":\\"query\\"}"'
|
||||||
);
|
);
|
||||||
|
|
||||||
gql({
|
await gql({
|
||||||
query,
|
query,
|
||||||
// @ts-expect-error forgive the fake variables
|
// @ts-expect-error forgive the fake variables
|
||||||
variables: { a: false, b: null, c: undefined },
|
variables: { a: false, b: null, c: undefined },
|
||||||
|
|||||||
@@ -68,15 +68,23 @@ const standardizeLocale = (language: string) => {
|
|||||||
|
|
||||||
export const createI18n = () => {
|
export const createI18n = () => {
|
||||||
const i18n = i18next.createInstance();
|
const i18n = i18next.createInstance();
|
||||||
i18n.use(initReactI18next).init({
|
i18n
|
||||||
lng: 'en',
|
.use(initReactI18next)
|
||||||
fallbackLng,
|
.init({
|
||||||
debug: false,
|
lng: 'en',
|
||||||
resources,
|
fallbackLng,
|
||||||
interpolation: {
|
debug: false,
|
||||||
escapeValue: false, // not needed for react as it escapes by default
|
resources,
|
||||||
},
|
interpolation: {
|
||||||
});
|
escapeValue: false, // not needed for react as it escapes by default
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.info('i18n init success');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.error('i18n init failed');
|
||||||
|
});
|
||||||
|
|
||||||
i18n.on('languageChanged', lng => {
|
i18n.on('languageChanged', lng => {
|
||||||
localStorage.setItem(STORAGE_KEY, lng);
|
localStorage.setItem(STORAGE_KEY, lng);
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ const getBaseTranslations = async (baseLanguage: { tag: string }) => {
|
|||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
try {
|
try {
|
||||||
fs.access(RES_DIR);
|
await fs.access(RES_DIR);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fs.mkdir(RES_DIR);
|
fs.mkdir(RES_DIR).catch(console.error);
|
||||||
console.log('Create directory', RES_DIR);
|
console.log('Create directory', RES_DIR);
|
||||||
}
|
}
|
||||||
console.log('Loading project languages...');
|
console.log('Loading project languages...');
|
||||||
@@ -149,4 +149,7 @@ const main = async () => {
|
|||||||
console.log('Done');
|
console.log('Done');
|
||||||
};
|
};
|
||||||
|
|
||||||
main();
|
main().catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -152,4 +152,7 @@ const main = async () => {
|
|||||||
// TODO send notification
|
// TODO send notification
|
||||||
};
|
};
|
||||||
|
|
||||||
main();
|
main().catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ export function definePlugin<ID extends string>(
|
|||||||
|
|
||||||
blockSuiteAdapter
|
blockSuiteAdapter
|
||||||
.load()
|
.load()
|
||||||
.then(({ default: adapter }) => updateAdapter(adapter));
|
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||||
|
.catch(err => {
|
||||||
|
pluginLogger.error('[definePlugin] blockSuiteAdapter error', err);
|
||||||
|
});
|
||||||
|
|
||||||
if (import.meta.webpackHot) {
|
if (import.meta.webpackHot) {
|
||||||
blockSuiteAdapter.hotModuleReload(async _ => {
|
blockSuiteAdapter.hotModuleReload(async _ => {
|
||||||
@@ -64,7 +67,10 @@ export function definePlugin<ID extends string>(
|
|||||||
|
|
||||||
uiAdapterLoader
|
uiAdapterLoader
|
||||||
.load()
|
.load()
|
||||||
.then(({ default: adapter }) => updateAdapter(adapter));
|
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||||
|
.catch(err => {
|
||||||
|
pluginLogger.error('[definePlugin] blockSuiteAdapter error', err);
|
||||||
|
});
|
||||||
|
|
||||||
if (import.meta.webpackHot) {
|
if (import.meta.webpackHot) {
|
||||||
uiAdapterLoader.hotModuleReload(async _ => {
|
uiAdapterLoader.hotModuleReload(async _ => {
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ fetch(new URL('@affine-test/fixtures/large-image.png', import.meta.url))
|
|||||||
},
|
},
|
||||||
frameId
|
frameId
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Failed to load large-image.png', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Default = () => {
|
export const Default = () => {
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ fetch(new URL('@affine-test/fixtures/smile.png', import.meta.url))
|
|||||||
new Blob([buffer], { type: 'image/png' })
|
new Blob([buffer], { type: 'image/png' })
|
||||||
);
|
);
|
||||||
avatarBlockSuiteWorkspace.meta.setAvatar(id);
|
avatarBlockSuiteWorkspace.meta.setAvatar(id);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// just ignore
|
||||||
|
console.error('Failed to load smile.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BlobExample: StoryFn<WorkspaceAvatarProps> = props => {
|
export const BlobExample: StoryFn<WorkspaceAvatarProps> = props => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { DebugLogger } from '@affine/debug';
|
||||||
import type { BlobStorage } from '@blocksuite/store';
|
import type { BlobStorage } from '@blocksuite/store';
|
||||||
import { createIndexeddbStorage } from '@blocksuite/store';
|
import { createIndexeddbStorage } from '@blocksuite/store';
|
||||||
import { openDB } from 'idb';
|
import { openDB } from 'idb';
|
||||||
@@ -19,6 +20,8 @@ interface AffineBlob extends DBSchema {
|
|||||||
// todo: migrate blob storage from `createIndexeddbStorage`
|
// todo: migrate blob storage from `createIndexeddbStorage`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logger = new DebugLogger('affine:blob');
|
||||||
|
|
||||||
export const createAffineBlobStorage = (
|
export const createAffineBlobStorage = (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
workspaceApis: ReturnType<typeof createWorkspaceApis>
|
workspaceApis: ReturnType<typeof createWorkspaceApis>
|
||||||
@@ -29,19 +32,25 @@ export const createAffineBlobStorage = (
|
|||||||
db.createObjectStore('uploading', { keyPath: 'key' });
|
db.createObjectStore('uploading', { keyPath: 'key' });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
dbPromise.then(async db => {
|
dbPromise
|
||||||
const t = db.transaction('uploading', 'readwrite').objectStore('uploading');
|
.then(async db => {
|
||||||
await t.getAll().then(blobs =>
|
const t = db
|
||||||
blobs.map(({ arrayBuffer, type }) =>
|
.transaction('uploading', 'readwrite')
|
||||||
workspaceApis.uploadBlob(workspaceId, arrayBuffer, type).then(key => {
|
.objectStore('uploading');
|
||||||
const t = db
|
await t.getAll().then(blobs =>
|
||||||
.transaction('uploading', 'readwrite')
|
blobs.map(({ arrayBuffer, type }) =>
|
||||||
.objectStore('uploading');
|
workspaceApis.uploadBlob(workspaceId, arrayBuffer, type).then(key => {
|
||||||
return t.delete(key);
|
const t = db
|
||||||
})
|
.transaction('uploading', 'readwrite')
|
||||||
)
|
.objectStore('uploading');
|
||||||
);
|
return t.delete(key);
|
||||||
});
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error('[createAffineBlobStorage] dbPromise error', err);
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
crud: {
|
crud: {
|
||||||
get: async key => {
|
get: async key => {
|
||||||
@@ -60,19 +69,21 @@ export const createAffineBlobStorage = (
|
|||||||
.transaction('uploading', 'readwrite')
|
.transaction('uploading', 'readwrite')
|
||||||
.objectStore('uploading');
|
.objectStore('uploading');
|
||||||
let uploaded = false;
|
let uploaded = false;
|
||||||
t.put({
|
await t.put({
|
||||||
key,
|
key,
|
||||||
arrayBuffer,
|
arrayBuffer,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
}).then(() => {
|
|
||||||
// delete the uploading blob after uploaded
|
|
||||||
if (uploaded) {
|
|
||||||
const t = db
|
|
||||||
.transaction('uploading', 'readwrite')
|
|
||||||
.objectStore('uploading');
|
|
||||||
t.delete(key);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
// delete the uploading blob after uploaded
|
||||||
|
if (uploaded) {
|
||||||
|
const t = db
|
||||||
|
.transaction('uploading', 'readwrite')
|
||||||
|
.objectStore('uploading');
|
||||||
|
// don't await here, we don't care if it's deleted
|
||||||
|
t.delete(key).catch(err => {
|
||||||
|
logger.error('[createAffineBlobStorage] delete error', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
storage.crud.set(key, value),
|
storage.crud.set(key, value),
|
||||||
workspaceApis
|
workspaceApis
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('crud', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('delete not exist', async () => {
|
test('delete not exist', async () => {
|
||||||
expect(async () =>
|
await expect(async () =>
|
||||||
CRUD.delete({
|
CRUD.delete({
|
||||||
id: 'not_exist',
|
id: 'not_exist',
|
||||||
flavour: WorkspaceFlavour.LOCAL,
|
flavour: WorkspaceFlavour.LOCAL,
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ describe('indexeddb provider', () => {
|
|||||||
test('cleanup when connecting', async () => {
|
test('cleanup when connecting', async () => {
|
||||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
||||||
provider.connect();
|
provider.connect();
|
||||||
expect(() => provider.cleanup()).rejects.toThrowError(
|
await expect(() => provider.cleanup()).rejects.toThrowError(
|
||||||
CleanupWhenConnectingError
|
CleanupWhenConnectingError
|
||||||
);
|
);
|
||||||
await provider.whenSynced;
|
await provider.whenSynced;
|
||||||
@@ -259,7 +259,7 @@ describe('indexeddb provider', () => {
|
|||||||
yDoc.getMap().set('foo', 'bar');
|
yDoc.getMap().set('foo', 'bar');
|
||||||
const persistence = new IndexeddbPersistence('test', yDoc);
|
const persistence = new IndexeddbPersistence('test', yDoc);
|
||||||
await persistence.whenSynced;
|
await persistence.whenSynced;
|
||||||
persistence.destroy();
|
await persistence.destroy();
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const yDoc = new Doc();
|
const yDoc = new Doc();
|
||||||
@@ -274,7 +274,7 @@ describe('indexeddb provider', () => {
|
|||||||
indexedDB.databases = vi.fn(async () => {
|
indexedDB.databases = vi.fn(async () => {
|
||||||
throw new Error('not supported');
|
throw new Error('not supported');
|
||||||
});
|
});
|
||||||
expect(indexedDB.databases).rejects.toThrow('not supported');
|
await expect(indexedDB.databases).rejects.toThrow('not supported');
|
||||||
const yDoc = new Doc();
|
const yDoc = new Doc();
|
||||||
expect(indexedDB.databases).toBeCalledTimes(1);
|
expect(indexedDB.databases).toBeCalledTimes(1);
|
||||||
const provider = createIndexedDBProvider('test', yDoc);
|
const provider = createIndexedDBProvider('test', yDoc);
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ export const createIndexedDBProvider = (
|
|||||||
if (connected) {
|
if (connected) {
|
||||||
throw new CleanupWhenConnectingError();
|
throw new CleanupWhenConnectingError();
|
||||||
}
|
}
|
||||||
(await dbPromise).delete('workspace', id);
|
await (await dbPromise).delete('workspace', id);
|
||||||
},
|
},
|
||||||
whenSynced: Promise.resolve(),
|
whenSynced: Promise.resolve(),
|
||||||
get connected() {
|
get connected() {
|
||||||
|
|||||||
Reference in New Issue
Block a user