refactor!: remove next.js (#3267)

This commit is contained in:
Alex Yang authored and GitHub committed 2023-07-18 16:53:10 +00:00
1 parent 79227a1e7c
commit 47f12f77f2
296 files changed
+4115 -3617

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
import { hash } from './utils.js';
function testPackageName(regexp: RegExp): (module: any) => boolean {
return (module: any) =>
module.nameForCondition && regexp.test(module.nameForCondition());
}
export const productionCacheGroups = {
asyncVendor: {
test: /[\\/]node_modules[\\/]/,
name(module: any) {
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
const name =
module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)?.[1] ??
'unknown';
return `npm-async-${hash(name)}`;
},
priority: Number.MAX_SAFE_INTEGER,
chunks: 'async' as const,
},
mui: {
name: `npm-mui`,
test: testPackageName(/[\\/]node_modules[\\/](mui|@mui)[\\/]/),
priority: 200,
enforce: true,
},
blocksuite: {
name: `npm-blocksuite`,
test: testPackageName(/[\\/]node_modules[\\/](@blocksuite)[\\/]/),
priority: 200,
enforce: true,
},
react: {
name: `npm-react`,
test: testPackageName(
/[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/
),
priority: 200,
enforce: true,
},
jotai: {
name: `npm-jotai`,
test: testPackageName(/[\\/]node_modules[\\/](jotai)[\\/]/),
priority: 200,
enforce: true,
},
rxjs: {
name: `npm-rxjs`,
test: testPackageName(/[\\/]node_modules[\\/]rxjs[\\/]/),
priority: 200,
enforce: true,
},
lodash: {
name: `npm-lodash`,
test: testPackageName(/[\\/]node_modules[\\/]lodash[\\/]/),
priority: 200,
enforce: true,
},
emotion: {
name: `npm-emotion`,
test: testPackageName(/[\\/]node_modules[\\/](@emotion)[\\/]/),
priority: 200,
enforce: true,
},
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 190,
enforce: true,
},
styles: {
name: 'styles',
test: (module: any) =>
module.nameForCondition &&
/\.css$/.test(module.nameForCondition()) &&
!/^javascript/.test(module.type),
chunks: 'all' as const,
minSize: 1,
minChunks: 1,
reuseExistingChunk: true,
priority: 1000,
enforce: true,
},
};
+306
View File
@@ -0,0 +1,306 @@
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import HTMLPlugin from 'html-webpack-plugin';
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
import { PerfseePlugin } from '@perfsee/webpack';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import { productionCacheGroups } from './cache-group.js';
import type { BuildFlags } from '@affine/cli/config';
import { projectRoot } from '@affine/cli/config';
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
import { computeCacheKey } from './utils.js';
import type { RuntimeConfig } from '@affine/env/global';
const IN_CI = !!process.env.CI;
export const rootPath = fileURLToPath(new URL('..', import.meta.url));
const require = createRequire(rootPath);
const OptimizeOptionOptions: (
buildFlags: BuildFlags
) => webpack.Configuration['optimization'] = buildFlags => ({
minimize: buildFlags.mode === 'production',
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
parallel: true,
extractComments: true,
terserOptions: {
ecma: 2020,
compress: {
unused: true,
},
mangle: true,
},
}),
],
removeEmptyChunks: true,
providedExports: true,
usedExports: true,
sideEffects: true,
removeAvailableModules: true,
runtimeChunk: {
name: 'runtime',
},
splitChunks: {
chunks: 'all',
minSize: 1,
minChunks: 1,
maxInitialRequests: Number.MAX_SAFE_INTEGER,
maxAsyncRequests: Number.MAX_SAFE_INTEGER,
cacheGroups:
buildFlags.mode === 'production'
? productionCacheGroups
: {
default: false,
vendors: false,
},
},
});
export const createConfiguration: (
buildFlags: BuildFlags,
runtimeConfig: RuntimeConfig
) => webpack.Configuration = (buildFlags, runtimeConfig) => {
let publicPath = process.env.PUBLIC_PATH ?? '/';
const cacheKey = computeCacheKey(buildFlags);
const config = {
name: 'affine',
// to set a correct base path for the source map
context: projectRoot,
output: {
environment: {
module: true,
dynamicImport: true,
},
filename: 'js/[name].js',
// In some cases webpack will emit files starts with "_" which is reserved in web extension.
chunkFilename: 'js/chunk.[name].js',
assetModuleFilename: 'assets/[hash][ext][query]',
devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
hotUpdateChunkFilename: 'hot/[id].[fullhash].js',
hotUpdateMainFilename: 'hot/[runtime].[fullhash].json',
path: join(rootPath, 'dist'),
clean: buildFlags.mode === 'production',
globalObject: 'globalThis',
publicPath,
},
target: ['web', 'es2022'],
mode: buildFlags.mode,
devtool:
buildFlags.mode === 'production'
? buildFlags.distribution === 'desktop'
? 'inline-source-map'
: 'hidden-nosources-source-map'
: 'eval-cheap-module-source-map',
resolve: {
extensionAlias: {
'.js': ['.js', '.tsx', '.ts'],
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts', '.tsx'],
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [fileURLToPath(import.meta.url)],
},
version: cacheKey,
},
module: {
parser: {
javascript: {
// Treat as missing export as error
strictExportPresence: true,
},
},
rules: [
{
test: /\.m?js?$/,
resolve: {
fullySpecified: false,
},
},
{
oneOf: [
{
test: /\.tsx?$/,
// Compile all ts files in the workspace
include: resolve(rootPath, '..', '..'),
loader: require.resolve('swc-loader'),
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: true,
},
target: 'es2022',
externalHelpers: true,
transform: {
react: {
runtime: 'automatic',
refresh: buildFlags.mode === 'development' && {
refreshReg: '$RefreshReg$',
refreshSig: '$RefreshSig$',
emitFullSignatures: true,
},
},
},
experimental: {
keepImportAssertions: true,
plugins: [
buildFlags.coverage && [
'swc-plugin-coverage-instrument',
{},
],
].filter(Boolean),
},
},
},
},
{
test: /\.svg$/,
use: [
'thread-loader',
{
loader: '@svgr/webpack',
options: {
icon: true,
},
},
],
exclude: [/node_modules/],
},
{
test: /\.(png|jpg|gif|svg|webp)$/,
type: 'asset/resource',
},
{
test: /\.(ttf|eot|woff|woff2)$/i,
type: 'asset/resource',
},
{
test: /\.txt$/,
loader: 'raw-loader',
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: false,
sourceMap: false,
modules: false,
import: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: resolve(
rootPath,
'.webpack',
'postcss.config.cjs'
),
},
},
},
],
},
],
},
],
},
plugins: [
...(IN_CI ? [] : [new webpack.ProgressPlugin({ percentBy: 'entries' })]),
...(buildFlags.mode === 'development'
? [new ReactRefreshWebpackPlugin({ overlay: false, esModule: true })]
: []),
new HTMLPlugin({
template: join(rootPath, '.webpack', 'template.html'),
inject: 'body',
scriptLoading: 'defer',
minify: false,
chunks: ['index'],
filename: 'index.html',
}),
new MiniCssExtractPlugin({
filename: `[name].[chunkhash:8].css`,
ignoreOrder: true,
}),
new VanillaExtractPlugin(),
new webpack.DefinePlugin({
'process.env': JSON.stringify({}),
runtimeConfig: JSON.stringify(runtimeConfig),
}),
new CopyPlugin({
patterns: [
{ from: resolve(rootPath, 'public'), to: resolve(rootPath, 'dist') },
],
}),
],
optimization: OptimizeOptionOptions(buildFlags),
devServer: {
hot: 'only',
liveReload: false,
client: undefined,
historyApiFallback: true,
static: {
directory: resolve(rootPath, 'public'),
publicPath: '/',
},
} as DevServerConfiguration,
} satisfies webpack.Configuration;
if (buildFlags.mode === 'production' && process.env.PERFSEE_TOKEN) {
config.devtool = 'hidden-nosources-source-map';
config.plugins.push(
new PerfseePlugin({
project: 'affine-toeverything',
})
);
}
if (
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT
) {
config.plugins.push(
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
})
);
}
return config;
};
+20
View File
@@ -0,0 +1,20 @@
const cssnano = require('cssnano');
module.exports = function (context) {
const plugins = [
cssnano({
preset: [
'default',
{
convertValues: false,
},
],
}),
];
return {
from: context.from,
plugins,
to: context.to,
};
};
+115
View File
@@ -0,0 +1,115 @@
import type { BlockSuiteFeatureFlags, RuntimeConfig } from '@affine/env/global';
import type { BuildFlags } from '@affine/cli/config';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const packageJson = require('../package.json');
const editorFlags: BlockSuiteFeatureFlags = {
enable_database: true,
enable_slash_menu: true,
enable_edgeless_toolbar: true,
enable_block_hub: true,
enable_drag_handle: true,
enable_surface: true,
enable_linked_page: true,
enable_bookmark_operation: false,
};
export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
const buildPreset: Record<string, RuntimeConfig> = {
stable: {
enablePlugin: false,
enableTestProperties: false,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: false,
enableCloud: false,
serverAPI: 'https://localhost:3010',
editorFlags,
appVersion: packageJson.version,
editorVersion: packageJson.dependencies['@blocksuite/editor'],
},
// canary will be aggressive and enable all features
canary: {
enablePlugin: true,
enableTestProperties: true,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: true,
enableCloud: false,
serverAPI: 'https://localhost:3010',
editorFlags,
appVersion: packageJson.version,
editorVersion: packageJson.dependencies['@blocksuite/editor'],
},
};
// beta and internal versions are the same as stable
buildPreset.beta = buildPreset.stable;
buildPreset.internal = buildPreset.stable;
const currentBuild = buildFlags.channel;
if (!(currentBuild in buildPreset)) {
throw new Error(`BUILD_TYPE ${currentBuild} is not supported`);
}
const currentBuildPreset = buildPreset[currentBuild];
const environmentPreset = {
enablePlugin: process.env.ENABLE_PLUGIN
? process.env.ENABLE_PLUGIN === 'true'
: currentBuildPreset.enablePlugin,
enableTestProperties: process.env.ENABLE_TEST_PROPERTIES
? process.env.ENABLE_TEST_PROPERTIES === 'true'
: currentBuildPreset.enableTestProperties,
enableBroadcastChannelProvider: process.env.ENABLE_BC_PROVIDER
? process.env.ENABLE_BC_PROVIDER !== 'false'
: currentBuildPreset.enableBroadcastChannelProvider,
changelogUrl: process.env.CHANGELOG_URL ?? currentBuildPreset.changelogUrl,
enablePreloading: process.env.ENABLE_PRELOADING
? process.env.ENABLE_PRELOADING === 'true'
: currentBuildPreset.enablePreloading,
enableNewSettingModal: process.env.ENABLE_NEW_SETTING_MODAL
? process.env.ENABLE_NEW_SETTING_MODAL === 'true'
: currentBuildPreset.enableNewSettingModal,
enableSQLiteProvider: process.env.ENABLE_SQLITE_PROVIDER
? process.env.ENABLE_SQLITE_PROVIDER === 'true'
: currentBuildPreset.enableSQLiteProvider,
enableNewSettingUnstableApi: process.env.ENABLE_NEW_SETTING_UNSTABLE_API
? process.env.ENABLE_NEW_SETTING_UNSTABLE_API === 'true'
: currentBuildPreset.enableNewSettingUnstableApi,
enableNotificationCenter: process.env.ENABLE_NOTIFICATION_CENTER
? process.env.ENABLE_NOTIFICATION_CENTER === 'true'
: currentBuildPreset.enableNotificationCenter,
enableCloud: process.env.ENABLE_CLOUD
? process.env.ENABLE_CLOUD === 'true'
: currentBuildPreset.enableCloud,
enableMoveDatabase: process.env.ENABLE_MOVE_DATABASE
? process.env.ENABLE_MOVE_DATABASE === 'true'
: currentBuildPreset.enableMoveDatabase,
};
return {
...currentBuildPreset,
// environment preset will overwrite current build preset
// this environment variable is for debug proposes only
// do not put them into CI
...(process.env.CI ? {} : environmentPreset),
};
}
+45
View File
@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
<title>AFFiNE</title>
<meta name="theme-color" content="#fafafa" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" sizes="192x192" href="/chrome-192x192.png" />
<meta name="emotion-insertion-point" content="" />
<meta property="description" content="{description}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://app.affine.pro/" />
<meta
name="twitter:title"
content="AFFiNE:There can be more than Notion and Miro."
/>
<meta name="twitter:description" content="{description}" />
<meta name="twitter:site" content="@AffineOfficial" />
<meta name="twitter:image" content="https://affine.pro/og.jpeg" />
<meta
property="og:title"
content="AFFiNE:There can be more than Notion and Miro."
/>
<meta property="og:type" content="website" />
<meta
property="og:description"
content="There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together."
/>
<meta property="og:url" content="https://app.affine.pro/" />
<meta property="og:image" content="https://affine.pro/og.jpeg" />
<link
data-react-helmet="true"
rel="shortcut icon"
href="https://affine.pro/favicon.ico"
/>
</head>
<body>
<div id="app"></div>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
import { createHash } from 'node:crypto';
import type { BuildFlags } from '@affine/cli/config';
export function hash(content: string): string {
const hash = createHash('sha512');
hash.update(content);
const pkgHash = hash.digest('hex');
return pkgHash.substring(0, 8);
}
export function computeCacheKey(buildFlags: BuildFlags) {
return [
'1',
'node' + process.version,
buildFlags.mode,
buildFlags.distribution,
].join('-');
}
+23
View File
@@ -0,0 +1,23 @@
import { createConfiguration, rootPath } from './config.js';
import { merge } from 'webpack-merge';
import { resolve } from 'node:path';
import type { BuildFlags } from '@affine/cli/config';
import { getRuntimeConfig } from './runtime-config.js';
export default async function (cli_env: any, _: any) {
const flags: BuildFlags = JSON.parse(
Buffer.from(cli_env.flags, 'hex').toString('utf-8')
);
console.log('build flags', flags);
const runtimeConfig = getRuntimeConfig(flags);
console.log('runtime config', runtimeConfig);
const config = createConfiguration(flags, runtimeConfig);
return merge(config, {
entry: {
index: {
asyncChunks: true,
import: resolve(rootPath, 'src/index.tsx'),
},
},
});
}
@@ -1,17 +1,16 @@
{
"name": "@affine/web",
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.7.0-canary.47",
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "NODE_ENV=development next start",
"static-server": "ts-node-esm server.mts"
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
"static-server": "ts-node-esm ./server.mts"
},
"dependencies": {
"@affine-test/fixtures": "workspace:*",
"@affine/component": "workspace:*",
"@affine/copilot": "workspace:*",
"@affine/debug": "workspace:*",
"@affine/env": "workspace:*",
"@affine/graphql": "workspace:*",
@@ -34,23 +33,23 @@
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.13.6",
"@react-hookz/web": "^23.1.0",
"@sentry/nextjs": "^7.57.0",
"@toeverything/hooks": "workspace:*",
"@toeverything/infra": "workspace:*",
"@toeverything/plugin-infra": "workspace:*",
"async-call-rpc": "^6.3.1",
"cmdk": "^0.2.0",
"css-spring": "^4.1.0",
"cssnano": "^6.0.1",
"graphql": "^16.7.1",
"jotai": "^2.2.2",
"jotai-devtools": "^0.6.0",
"lit": "^2.7.5",
"lottie-web": "^5.12.2",
"mini-css-extract-plugin": "^2.7.6",
"next-themes": "^0.2.1",
"postcss-loader": "^7.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "^18.2.0",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.53",
"react-router-dom": "^6.14.1",
"rxjs": "^7.8.1",
"swr": "^2.1.5",
"y-protocols": "^1.0.5",
@@ -59,30 +58,23 @@
},
"devDependencies": {
"@perfsee/webpack": "^1.8.2",
"@redux-devtools/extension": "^3.2.5",
"@rich-data/viewer": "^2.15.6",
"@sentry/webpack-plugin": "^1.20.1",
"@swc-jotai/debug-label": "^0.0.10",
"@swc-jotai/react-refresh": "^0.0.8",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@sentry/webpack-plugin": "^2.4.0",
"@svgr/webpack": "^8.0.1",
"@swc/core": "^1.3.70",
"@types/webpack-env": "^1.18.1",
"@vanilla-extract/css": "^1.12.0",
"@vanilla-extract/next-plugin": "=2.1.2",
"dotenv": "^16.3.1",
"eslint": "^8.44.0",
"eslint-config-next": "^13.4.7",
"eslint-plugin-unicorn": "^47.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"express": "^4.18.2",
"next": "=13.4.2",
"next-debug-local": "^0.1.5",
"next-router-mock": "^0.9.7",
"html-webpack-plugin": "^5.5.3",
"raw-loader": "^4.0.2",
"redux": "^4.2.1",
"swc-plugin-coverage-instrument": "^0.0.18",
"swc-loader": "^0.2.3",
"swc-plugin-coverage-instrument": "^0.0.19",
"thread-loader": "^4.0.2",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"webpack": "^5.88.1"
},
"stableVersion": "0.0.0"
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webpack-merge": "^5.9.0"
}
}
@@ -1,9 +1,9 @@
{
"name": "@affine/web",
"name": "@affine/core",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/web",
"sourceRoot": "apps/web/src",
"root": "apps/core",
"sourceRoot": "apps/core/src",
"targets": {
"build": {
"executor": "nx:run-script",
@@ -12,26 +12,29 @@
"{projectRoot}/**/*",
"{workspaceRoot}/packages/component/src/**/*",
"{workspaceRoot}/packages/debug/src/**/*",
"{workspaceRoot}/packages/debug/graphql/**/*",
"{workspaceRoot}/packages/graphql/src/**/*",
"{workspaceRoot}/packages/hooks/src/**/*",
"{workspaceRoot}/packages/jotai/src/**/*",
"{workspaceRoot}/packages/templates/src/**/*",
"{workspaceRoot}/packages/workspace/src/**/*",
{
"env": "BUILD_TYPE"
},
{
"env": "PERFSEE_TOKEN"
}
],
"options": {
"script": "build"
},
"outputs": ["{projectRoot}/out"]
"outputs": ["{projectRoot}/dist"]
},
"dev": {
"executor": "nx:run-script",
"options": {
"script": "dev"
},
"outputs": ["{projectRoot}/.next"]
"outputs": ["{projectRoot}/dist"]
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+15
View File
@@ -0,0 +1,15 @@
// static server for web app
import express from 'express';
const app = express();
const PORT = process.env.PORT || 8080;
app.use('/', express.static('dist'));
app.get('/*', (req, res) => {
res.sendFile('index.html', { root: 'dist' });
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
+158
View File
@@ -0,0 +1,158 @@
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import { AffineContext } from '@affine/component/context';
import { WorkspaceFallback } from '@affine/component/workspace';
import { createI18n, setUpLanguage } from '@affine/i18n';
import { CacheProvider } from '@emotion/react';
import type { RouterState } from '@remix-run/router';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
} from '@toeverything/plugin-infra/manager';
import type { PropsWithChildren, ReactElement } from 'react';
import { lazy, memo, Suspense, useEffect } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { historyBaseAtom, MAX_HISTORY } from './atoms/history';
import createEmotionCache from './utils/create-emotion-cache';
const router = createBrowserRouter([
{
path: '/',
lazy: () => import('./pages/index'),
},
{
path: '/404',
lazy: () => import('./pages/404'),
},
{
path: '/workspace/:workspaceId/all',
lazy: () => import('./pages/workspace/all-page'),
},
{
path: '/workspace/:workspaceId/trash',
lazy: () => import('./pages/workspace/trash-page'),
},
{
path: '/workspace/:workspaceId/:pageId',
lazy: () => import('./pages/workspace/detail-page'),
},
]);
//#region atoms bootstrap
currentWorkspaceIdAtom.onMount = set => {
const callback = (state: RouterState) => {
const value = state.location.pathname.split('/')[2];
if (value) {
set(value);
localStorage.setItem('last_workspace_id', value);
} else {
set(null);
}
};
callback(router.state);
const unsubscribe = router.subscribe(callback);
return () => {
unsubscribe();
};
};
currentPageIdAtom.onMount = set => {
const callback = (state: RouterState) => {
const value = state.location.pathname.split('/')[3];
if (value) {
set(value);
} else {
set(null);
}
};
callback(router.state);
const unsubscribe = router.subscribe(callback);
return () => {
unsubscribe();
};
};
historyBaseAtom.onMount = set => {
const unsubscribe = router.subscribe(state => {
set(prev => {
const url = state.location.pathname;
console.log('push', url, prev.skip, prev.stack.length, prev.current);
if (prev.skip) {
return {
stack: [...prev.stack],
current: prev.current,
skip: false,
};
} else {
if (prev.current < prev.stack.length - 1) {
const newStack = prev.stack.slice(0, prev.current);
newStack.push(url);
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
} else {
const newStack = [...prev.stack, url];
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
}
}
});
});
return () => {
unsubscribe();
};
};
//#endregion
const i18n = createI18n();
const cache = createEmotionCache();
const DevTools = lazy(() =>
import('jotai-devtools').then(m => ({ default: m.DevTools }))
);
const DebugProvider = ({ children }: PropsWithChildren): ReactElement => {
return (
<>
<Suspense>{process.env.DEBUG_JOTAI === 'true' && <DevTools />}</Suspense>
{children}
</>
);
};
export const App = memo(function App() {
useEffect(() => {
document.documentElement.lang = i18n.language;
// todo(himself65): this is a hack, we should use a better way to set the language
setUpLanguage(i18n)?.catch(error => {
console.error(error);
});
}, []);
return (
<CacheProvider value={cache}>
<AffineContext>
<DebugProvider>
<Suspense fallback={<WorkspaceFallback key="RootPageLoading" />}>
<RouterProvider router={router} />
</Suspense>
</DebugProvider>
</AffineContext>
</CacheProvider>
);
});
File renamed without changes.
+52
View File
@@ -0,0 +1,52 @@
import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
export type History = {
stack: string[];
current: number;
skip: boolean;
};
export const MAX_HISTORY = 50;
export const historyBaseAtom = atomWithStorage<History>('router-history', {
stack: [],
current: 0,
skip: false,
});
export function useHistoryAtom() {
const navigate = useNavigate();
const [base, setBase] = useAtom(historyBaseAtom);
return [
base,
useCallback(
(forward: boolean) => {
setBase(prev => {
if (forward) {
const target = Math.min(prev.stack.length - 1, prev.current + 1);
const url = prev.stack[target];
navigate(url);
return {
...prev,
current: target,
skip: true,
};
} else {
const target = Math.max(0, prev.current - 1);
const url = prev.stack[target];
navigate(url);
return {
...prev,
current: target,
skip: true,
};
}
});
},
[setBase, navigate]
),
] as const;
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -12,14 +12,11 @@ import {
} from '@toeverything/plugin-infra/manager';
import { useAtomValue } from 'jotai/react';
import { Provider } from 'jotai/react';
import type { NextRouter } from 'next/router';
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
import type React from 'react';
import { Component } from 'react';
export type AffineErrorBoundaryProps = React.PropsWithChildren<{
router: NextRouter;
}>;
import { useLocation, useParams } from 'react-router-dom';
export type AffineErrorBoundaryProps = React.PropsWithChildren;
type AffineError =
| QueryParamError
@@ -32,13 +29,13 @@ interface AffineErrorBoundaryState {
error: AffineError | null;
}
export const DumpInfo = (props: Pick<AffineErrorBoundaryProps, 'router'>) => {
const router = props.router;
export const DumpInfo = () => {
const location = useLocation();
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
const currentPageId = useAtomValue(currentPageIdAtom);
const path = router.asPath;
const query = router.query;
const path = location.pathname;
const query = useParams();
return (
<>
<div>
@@ -91,24 +88,6 @@ export class AffineErrorBoundary extends Component<
Cannot find page {error.pageId} in workspace{' '}
{error.workspace.id}
</span>
<button
onClick={() => {
this.props.router
.replace({
pathname: '/workspace/[workspaceId]/[pageId]',
query: {
workspaceId: error.workspace.id,
pageId: error.workspace.meta.pageMetas[0].id,
},
})
.finally(() => {
this.setState({ error: null });
});
}}
>
{' '}
refresh{' '}
</button>
</>
</>
);
@@ -124,7 +103,7 @@ export class AffineErrorBoundary extends Component<
<>
{errorDetail}
<Provider key="JotaiProvider" store={rootStore}>
<DumpInfo router={this.props.router} />
<DumpInfo />
</Provider>
</>
);
@@ -73,11 +73,21 @@ export const AboutAffine = () => {
) : null}
</SettingWrapper>
<SettingWrapper title={t['Contact with us']()}>
<a className={link} href="https://affine.pro" target="_blank">
<a
className={link}
rel="noreferrer"
href="https://affine.pro"
target="_blank"
>
{t['Official Website']()}
<OpenInNewIcon className="icon" />
</a>
<a className={link} href="https://community.affine.pro" target="_blank">
<a
className={link}
rel="noreferrer"
href="https://community.affine.pro"
target="_blank"
>
{t['AFFiNE Community']()}
<OpenInNewIcon className="icon" />
</a>
@@ -101,11 +111,21 @@ export const AboutAffine = () => {
</div>
</SettingWrapper>
<SettingWrapper title={t['Info of legal']()}>
<a className={link} href="https://affine.pro/privacy" target="_blank">
<a
className={link}
rel="noreferrer"
href="https://affine.pro/privacy"
target="_blank"
>
{t['Privacy']()}
<OpenInNewIcon className="icon" />
</a>
<a className={link} href="https://affine.pro/terms" target="_blank">
<a
className={link}
rel="noreferrer"
href="https://affine.pro/terms"
target="_blank"
>
{t['Terms of Use']()}
<OpenInNewIcon className="icon" />
</a>
Loaded 100 of 296 files, more files were not shown because too many files have changed in this diff. Show more