mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
316 lines
9.6 KiB
TypeScript
316 lines
9.6 KiB
TypeScript
import { AffineContext } from '@affine/core/components/context';
|
|
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
|
|
import { configureMobileModules } from '@affine/core/mobile/modules';
|
|
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
|
|
import { router } from '@affine/core/mobile/router';
|
|
import { configureCommonModules } from '@affine/core/modules';
|
|
import { AIButtonProvider } from '@affine/core/modules/ai-button';
|
|
import {
|
|
AuthService,
|
|
DefaultServerService,
|
|
ServersService,
|
|
} from '@affine/core/modules/cloud';
|
|
import { DocsService } from '@affine/core/modules/doc';
|
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
|
import { I18nProvider } from '@affine/core/modules/i18n';
|
|
import { LifecycleService } from '@affine/core/modules/lifecycle';
|
|
import {
|
|
configureLocalStorageStateStorageImpls,
|
|
NbstoreProvider,
|
|
} from '@affine/core/modules/storage';
|
|
import { PopupWindowProvider } from '@affine/core/modules/url';
|
|
import { ClientSchemeProvider } from '@affine/core/modules/url/providers/client-schema';
|
|
import { configureBrowserWorkbenchModule } from '@affine/core/modules/workbench';
|
|
import { WorkspacesService } from '@affine/core/modules/workspace';
|
|
import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspace-engine';
|
|
import { I18n } from '@affine/i18n';
|
|
import { StoreManagerClient } from '@affine/nbstore/worker/client';
|
|
import { defaultBlockMarkdownAdapterMatchers } from '@blocksuite/affine/adapters';
|
|
import { Container } from '@blocksuite/affine/global/di';
|
|
import {
|
|
InlineDeltaToMarkdownAdapterExtensions,
|
|
MarkdownInlineToDeltaAdapterExtensions,
|
|
} from '@blocksuite/affine/inlines/preset';
|
|
import {
|
|
docLinkBaseURLMiddleware,
|
|
MarkdownAdapter,
|
|
titleMiddleware,
|
|
} from '@blocksuite/affine/shared/adapters';
|
|
import { App as CapacitorApp } from '@capacitor/app';
|
|
import { Keyboard } from '@capacitor/keyboard';
|
|
import { StatusBar, Style } from '@capacitor/status-bar';
|
|
import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support';
|
|
import { InAppBrowser } from '@capgo/inappbrowser';
|
|
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
|
import { OpClient } from '@toeverything/infra/op';
|
|
import { useTheme } from 'next-themes';
|
|
import { Suspense, useEffect } from 'react';
|
|
import { RouterProvider } from 'react-router-dom';
|
|
|
|
import { AffineTheme } from './plugins/affine-theme';
|
|
import { AIButton } from './plugins/ai-button';
|
|
|
|
const storeManagerClient = new StoreManagerClient(
|
|
new OpClient(
|
|
new Worker(
|
|
new URL(/* webpackChunkName: "nbstore" */ './nbstore.ts', import.meta.url)
|
|
)
|
|
)
|
|
);
|
|
window.addEventListener('beforeunload', () => {
|
|
storeManagerClient.dispose();
|
|
});
|
|
|
|
const future = {
|
|
v7_startTransition: true,
|
|
} as const;
|
|
|
|
const framework = new Framework();
|
|
configureCommonModules(framework);
|
|
configureBrowserWorkbenchModule(framework);
|
|
configureLocalStorageStateStorageImpls(framework);
|
|
configureBrowserWorkspaceFlavours(framework);
|
|
configureMobileModules(framework);
|
|
framework.impl(NbstoreProvider, {
|
|
openStore(key, options) {
|
|
const { store, dispose } = storeManagerClient.open(key, options);
|
|
return {
|
|
store,
|
|
dispose: () => {
|
|
dispose();
|
|
},
|
|
};
|
|
},
|
|
});
|
|
const frameworkProvider = framework.provider();
|
|
|
|
framework.impl(PopupWindowProvider, {
|
|
open: (url: string) => {
|
|
InAppBrowser.open({
|
|
url: url,
|
|
}).catch(console.error);
|
|
},
|
|
});
|
|
|
|
framework.impl(ClientSchemeProvider, {
|
|
getClientScheme() {
|
|
return 'affine';
|
|
},
|
|
});
|
|
|
|
framework.impl(VirtualKeyboardProvider, {
|
|
show: () => {
|
|
Keyboard.show().catch(console.error);
|
|
},
|
|
hide: () => {
|
|
// In some cases, the keyboard will show again. for example, it will show again
|
|
// when this function is called in click event of button. It may be a bug of
|
|
// android webview or capacitor.
|
|
setTimeout(() => {
|
|
Keyboard.hide().catch(console.error);
|
|
});
|
|
},
|
|
onChange: callback => {
|
|
let disposeRef = {
|
|
dispose: () => {},
|
|
};
|
|
|
|
Promise.all([
|
|
Keyboard.addListener('keyboardWillShow', info => {
|
|
callback({
|
|
visible: true,
|
|
height: info.keyboardHeight,
|
|
});
|
|
}),
|
|
Keyboard.addListener('keyboardWillHide', () => {
|
|
callback({
|
|
visible: false,
|
|
height: 0,
|
|
});
|
|
}),
|
|
])
|
|
.then(handlers => {
|
|
disposeRef.dispose = () => {
|
|
Promise.all(handlers.map(handler => handler.remove())).catch(
|
|
console.error
|
|
);
|
|
};
|
|
})
|
|
.catch(console.error);
|
|
|
|
return () => {
|
|
disposeRef.dispose();
|
|
};
|
|
},
|
|
});
|
|
|
|
framework.impl(AIButtonProvider, {
|
|
presentAIButton: () => {
|
|
return AIButton.present();
|
|
},
|
|
dismissAIButton: () => {
|
|
return AIButton.dismiss();
|
|
},
|
|
});
|
|
|
|
// ------ some apis for native ------
|
|
(window as any).getCurrentServerBaseUrl = () => {
|
|
const globalContextService = frameworkProvider.get(GlobalContextService);
|
|
const currentServerId = globalContextService.globalContext.serverId.get();
|
|
const serversService = frameworkProvider.get(ServersService);
|
|
const defaultServerService = frameworkProvider.get(DefaultServerService);
|
|
const currentServer =
|
|
(currentServerId ? serversService.server$(currentServerId).value : null) ??
|
|
defaultServerService.server;
|
|
return currentServer.baseUrl;
|
|
};
|
|
(window as any).getCurrentI18nLocale = () => {
|
|
return I18n.language;
|
|
};
|
|
(window as any).getCurrentWorkspaceId = () => {
|
|
const globalContextService = frameworkProvider.get(GlobalContextService);
|
|
return globalContextService.globalContext.workspaceId.get();
|
|
};
|
|
(window as any).getCurrentDocId = () => {
|
|
const globalContextService = frameworkProvider.get(GlobalContextService);
|
|
return globalContextService.globalContext.docId.get();
|
|
};
|
|
(window as any).getCurrentDocContentInMarkdown = async () => {
|
|
const globalContextService = frameworkProvider.get(GlobalContextService);
|
|
const currentWorkspaceId =
|
|
globalContextService.globalContext.workspaceId.get();
|
|
const currentDocId = globalContextService.globalContext.docId.get();
|
|
const workspacesService = frameworkProvider.get(WorkspacesService);
|
|
const workspaceRef = currentWorkspaceId
|
|
? workspacesService.openByWorkspaceId(currentWorkspaceId)
|
|
: null;
|
|
if (!workspaceRef) {
|
|
return;
|
|
}
|
|
const { workspace, dispose: disposeWorkspace } = workspaceRef;
|
|
|
|
const docsService = workspace.scope.get(DocsService);
|
|
const docRef = currentDocId ? docsService.open(currentDocId) : null;
|
|
if (!docRef) {
|
|
return;
|
|
}
|
|
const { doc, release: disposeDoc } = docRef;
|
|
|
|
try {
|
|
const blockSuiteDoc = doc.blockSuiteDoc;
|
|
|
|
const transformer = blockSuiteDoc.getTransformer([
|
|
docLinkBaseURLMiddleware(blockSuiteDoc.workspace.id),
|
|
titleMiddleware(blockSuiteDoc.workspace.meta.docMetas),
|
|
]);
|
|
const snapshot = transformer.docToSnapshot(blockSuiteDoc);
|
|
|
|
const container = new Container();
|
|
[
|
|
...MarkdownInlineToDeltaAdapterExtensions,
|
|
...defaultBlockMarkdownAdapterMatchers,
|
|
...InlineDeltaToMarkdownAdapterExtensions,
|
|
].forEach(ext => {
|
|
ext.setup(container);
|
|
});
|
|
const provider = container.provider();
|
|
|
|
const adapter = new MarkdownAdapter(transformer, provider);
|
|
if (!snapshot) {
|
|
return;
|
|
}
|
|
|
|
const markdownResult = await adapter.fromDocSnapshot({
|
|
snapshot,
|
|
assets: transformer.assetsManager,
|
|
});
|
|
return markdownResult.file;
|
|
} finally {
|
|
disposeDoc();
|
|
disposeWorkspace();
|
|
}
|
|
};
|
|
|
|
// setup application lifecycle events, and emit application start event
|
|
window.addEventListener('focus', () => {
|
|
frameworkProvider.get(LifecycleService).applicationFocus();
|
|
});
|
|
frameworkProvider.get(LifecycleService).applicationStart();
|
|
|
|
CapacitorApp.addListener('appUrlOpen', ({ url }) => {
|
|
// try to close browser if it's open
|
|
InAppBrowser.close().catch(e => console.error('Failed to close browser', e));
|
|
|
|
const urlObj = new URL(url);
|
|
|
|
if (urlObj.hostname === 'authentication') {
|
|
const method = urlObj.searchParams.get('method');
|
|
const payload = JSON.parse(urlObj.searchParams.get('payload') ?? 'false');
|
|
|
|
if (
|
|
!method ||
|
|
(method !== 'magic-link' && method !== 'oauth') ||
|
|
!payload
|
|
) {
|
|
console.error('Invalid authentication url', url);
|
|
return;
|
|
}
|
|
|
|
const authService = frameworkProvider
|
|
.get(DefaultServerService)
|
|
.server.scope.get(AuthService);
|
|
if (method === 'oauth') {
|
|
authService
|
|
.signInOauth(payload.code, payload.state, payload.provider)
|
|
.catch(console.error);
|
|
} else if (method === 'magic-link') {
|
|
authService
|
|
.signInMagicLink(payload.email, payload.token)
|
|
.catch(console.error);
|
|
}
|
|
}
|
|
}).catch(e => {
|
|
console.error(e);
|
|
});
|
|
|
|
const ThemeProvider = () => {
|
|
const { resolvedTheme } = useTheme();
|
|
|
|
useEffect(() => {
|
|
StatusBar.setStyle({
|
|
style:
|
|
resolvedTheme === 'dark'
|
|
? Style.Dark
|
|
: resolvedTheme === 'light'
|
|
? Style.Light
|
|
: Style.Default,
|
|
}).catch(console.error);
|
|
EdgeToEdge.setBackgroundColor({
|
|
color: resolvedTheme === 'dark' ? '#000000' : '#F5F5F5',
|
|
}).catch(console.error);
|
|
AffineTheme.onThemeChanged({
|
|
darkMode: resolvedTheme === 'dark',
|
|
}).catch(console.error);
|
|
}, [resolvedTheme]);
|
|
return null;
|
|
};
|
|
|
|
export function App() {
|
|
return (
|
|
<Suspense>
|
|
<FrameworkRoot framework={frameworkProvider}>
|
|
<I18nProvider>
|
|
<AffineContext store={getCurrentStore()}>
|
|
<ThemeProvider />
|
|
<RouterProvider
|
|
fallbackElement={<AppFallback />}
|
|
router={router}
|
|
future={future}
|
|
/>
|
|
</AffineContext>
|
|
</I18nProvider>
|
|
</FrameworkRoot>
|
|
</Suspense>
|
|
);
|
|
}
|