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:
Peng Xiao
2023-06-08 17:54:25 +08:00
committed by Alex Yang
parent 47babe25b7
commit 5699c99bf6
45 changed files with 221 additions and 134 deletions
@@ -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',
+1 -1
View File
@@ -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);
+3 -1
View File
@@ -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({
+3 -1
View File
@@ -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');
@@ -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 (
@@ -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]
); );
@@ -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
@@ -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();
}} }}
/> />
</> </>
@@ -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}
> >
+16 -8
View File
@@ -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();
}; };
+6 -2
View File
@@ -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');
+1 -1
View File
@@ -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']()}
+2 -2
View File
@@ -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]
+8 -2
View File
@@ -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 },
+17 -9
View File
@@ -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);
+6 -3
View File
@@ -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);
});
+4 -1
View File
@@ -152,4 +152,7 @@ const main = async () => {
// TODO send notification // TODO send notification
}; };
main(); main().catch(e => {
console.error(e);
process.exit(1);
});
+8 -2
View File
@@ -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 => {
+33 -22
View File
@@ -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);
+1 -1
View File
@@ -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() {