mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-10 19:38:39 +00:00
Compare commits
71 Commits
v0.8.0-can
...
v0.8.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36bd0b02d0 | ||
|
|
3a92c4f798 | ||
|
|
97de0ef5b4 | ||
|
|
bbf5f0efe0 | ||
|
|
ea76936508 | ||
|
|
f076cb0ead | ||
|
|
0f230253a8 | ||
|
|
13d2dde501 | ||
|
|
65fc0ed59c | ||
|
|
7ec4b8fb8c | ||
|
|
6415f0093b | ||
|
|
5795020403 | ||
|
|
f8e49ee3be | ||
|
|
1012807c65 | ||
|
|
1c7c27712e | ||
|
|
7f28c78d8c | ||
|
|
4bb874756d | ||
|
|
0882d47dc9 | ||
|
|
0c16eb1189 | ||
|
|
f2ac4c7eda | ||
|
|
0d531782ca | ||
|
|
47ff376195 | ||
|
|
33cc9d25a1 | ||
|
|
58ceeb9c5f | ||
|
|
3c00b69805 | ||
|
|
2678ca9330 | ||
|
|
6f488d963b | ||
|
|
8face25bdf | ||
|
|
0e32803247 | ||
|
|
3282344d4a | ||
|
|
78d23d86f5 | ||
|
|
3a0797955c | ||
|
|
9449e66396 | ||
|
|
b6200ab56d | ||
|
|
ff23561e21 | ||
|
|
e718428d50 | ||
|
|
ea34d66e14 | ||
|
|
d3c719d89a | ||
|
|
dcd070b3e7 | ||
|
|
32c08e49c5 | ||
|
|
28a496bc67 | ||
|
|
4386894e8a | ||
|
|
33613c7041 | ||
|
|
db1b4d48b8 | ||
|
|
f007e2cecb | ||
|
|
7e4df4c3d1 | ||
|
|
03b98b433b | ||
|
|
1b17743ed3 | ||
|
|
03f12f6aa4 | ||
|
|
0176d66a94 | ||
|
|
f078154b9b | ||
|
|
35a4c63c27 | ||
|
|
70f3508005 | ||
|
|
16e22e614b | ||
|
|
c8b2728e27 | ||
|
|
452c780d40 | ||
|
|
9567471e7f | ||
|
|
4d4923cd37 | ||
|
|
e85404a9c5 | ||
|
|
1d43e46f99 | ||
|
|
5a8c1dcb57 | ||
|
|
9ffc523443 | ||
|
|
39693a19bd | ||
|
|
18fcaff5ee | ||
|
|
568d5e4cdf | ||
|
|
99c24b5cd8 | ||
|
|
a3087d14d8 | ||
|
|
cc7de52caf | ||
|
|
05865d51c6 | ||
|
|
45089e176f | ||
|
|
00a41b95b9 |
@@ -24,7 +24,8 @@
|
||||
"debug",
|
||||
"storage",
|
||||
"infra",
|
||||
"plugin-infra"
|
||||
"plugin-cli",
|
||||
"sdk"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,3 +9,6 @@ lib
|
||||
.eslintrc.js
|
||||
packages/i18n/src/i18n-generated.ts
|
||||
e2e-dist-*
|
||||
static
|
||||
web-static
|
||||
public
|
||||
|
||||
@@ -43,7 +43,8 @@ const allPackages = [
|
||||
'packages/i18n',
|
||||
'packages/jotai',
|
||||
'packages/native',
|
||||
'packages/plugin-infra',
|
||||
'packages/infra',
|
||||
'packages/sdk',
|
||||
'packages/templates',
|
||||
'packages/theme',
|
||||
'packages/workspace',
|
||||
@@ -213,6 +214,7 @@ const config = {
|
||||
ignoreIIFE: false,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-misused-promises': ['error'],
|
||||
},
|
||||
})),
|
||||
{
|
||||
@@ -238,6 +240,7 @@ const config = {
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-floating-promises': 0,
|
||||
'@typescript-eslint/no-misused-promises': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
16
.github/actions/setup-maker/action.yml
vendored
Normal file
16
.github/actions/setup-maker/action.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Setup maker
|
||||
description: 'Setup maker dmg for electron'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Install @electron-forge/maker-dmg'
|
||||
if: runner.os == 'macos'
|
||||
shell: bash
|
||||
working-directory: ./apps/electron
|
||||
run: yarn add @electron-forge/maker-dmg --dev
|
||||
env:
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
8
.github/actions/setup-node/action.yml
vendored
8
.github/actions/setup-node/action.yml
vendored
@@ -17,10 +17,6 @@ inputs:
|
||||
description: 'Download the Electron binary'
|
||||
required: false
|
||||
default: 'true'
|
||||
npm-token:
|
||||
description: 'The NPM token to use for private packages.'
|
||||
required: false
|
||||
default: ''
|
||||
hard-link-nm:
|
||||
description: 'set nmMode to hardlinks-local in .yarnrc.yml'
|
||||
required: false
|
||||
@@ -48,20 +44,20 @@ runs:
|
||||
shell: bash
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
|
||||
- name: yarn install (try again)
|
||||
if: ${{ steps.install.outcome == 'failure' }}
|
||||
shell: bash
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
|
||||
- name: Get installed Playwright version
|
||||
id: playwright-version
|
||||
|
||||
13
.github/actions/setup-sentry/action.yml
vendored
Normal file
13
.github/actions/setup-sentry/action.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Setup @sentry/cli
|
||||
description: 'Setup @sentry/cli'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Install @sentry/cli from brew'
|
||||
if: runner.os == 'macos'
|
||||
shell: bash
|
||||
run: brew install getsentry/tools/sentry-cli
|
||||
- name: 'Install @sentry/cli from npm'
|
||||
if: runner.os != 'macos'
|
||||
shell: bash
|
||||
run: sudo npm install -g @sentry/cli --unsafe-perm
|
||||
10
.github/labeler.yml
vendored
10
.github/labeler.yml
vendored
@@ -22,8 +22,14 @@ plugin:bookmark-block:
|
||||
plugin:copilot:
|
||||
- 'plugins/copilot/**/*'
|
||||
|
||||
mod:plugin-infra:
|
||||
- 'packages/plugin-infra/**/*'
|
||||
mod:infra:
|
||||
- 'packages/infra/**/*'
|
||||
|
||||
mod:sdk:
|
||||
- 'packages/sdk/**/*'
|
||||
|
||||
mod:plugin-cli:
|
||||
- 'packages/plugin-cli/**/*'
|
||||
|
||||
mod:workspace: 'packages/workspace/**/*'
|
||||
|
||||
|
||||
92
.github/workflows/build.yml
vendored
92
.github/workflows/build.yml
vendored
@@ -47,8 +47,6 @@ jobs:
|
||||
electron-install: false
|
||||
- name: Run i18n codegen
|
||||
run: yarn i18n-codegen gen
|
||||
- name: Run Type Check
|
||||
run: yarn typecheck
|
||||
- name: Run ESLint
|
||||
run: yarn lint:eslint --max-warnings=0
|
||||
- name: Run Prettier
|
||||
@@ -58,6 +56,21 @@ jobs:
|
||||
yarn lint:prettier
|
||||
- name: Run circular
|
||||
run: yarn circular
|
||||
- name: Run Type Check
|
||||
run: yarn typecheck
|
||||
|
||||
build-server:
|
||||
name: Build Server
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Server
|
||||
run: yarn nx build @affine/server
|
||||
- name: Upload server dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -121,10 +134,33 @@ jobs:
|
||||
path: ./apps/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-storage:
|
||||
name: Build Storage
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
- name: Build Storage
|
||||
run: yarn build:storage
|
||||
- name: Upload storage.node
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
if-no-files-found: error
|
||||
|
||||
server-test:
|
||||
name: Server Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-storage
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -160,24 +196,17 @@ jobs:
|
||||
working-directory: apps/server
|
||||
env:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
- name: Download storage.node
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
- name: Build Storage
|
||||
run: yarn build:storage
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
- name: Run server tests
|
||||
run: yarn test:coverage
|
||||
working-directory: apps/server
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
- name: Upload storage.node
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
if-no-files-found: error
|
||||
- name: Upload server test coverage results
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
@@ -305,7 +334,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
spec:
|
||||
- { package: 0.7.0-canary.18 }
|
||||
- { package: 0.8.0-canary.7 }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
@@ -322,22 +355,18 @@ jobs:
|
||||
|
||||
- name: Unzip
|
||||
run: yarn unzip
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
|
||||
|
||||
- name: Run legacy playwright tests
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
|
||||
- name: Run vitest
|
||||
run: yarn test
|
||||
working-directory: ./tests/affine-legacy/0.7.0-canary.18
|
||||
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-migration
|
||||
path: ./tests/affine-legacy/0.7.0-canary.18/test-results
|
||||
name: test-results-e2e-migration-${{ matrix.spec.package }}
|
||||
path: ./tests/affine-legacy/${{ matrix.spec.package }}/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
desktop-test:
|
||||
@@ -409,6 +438,12 @@ jobs:
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: Upload desktop dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: dist-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: ./apps/electron/dist
|
||||
|
||||
- name: Run desktop tests
|
||||
if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
|
||||
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine/electron test
|
||||
@@ -423,12 +458,13 @@ jobs:
|
||||
|
||||
- name: Make bundle
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
env:
|
||||
SKIP_BUNDLE: true
|
||||
run: yarn workspace @affine/electron make --platform=darwin --arch=arm64
|
||||
|
||||
- name: Bundle output check
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
run: |
|
||||
./scripts/unzip-macos-arm64.sh
|
||||
yarn ts-node-esm ./scripts/macos-arm64-output-check.mts
|
||||
working-directory: apps/electron
|
||||
|
||||
@@ -480,11 +516,11 @@ jobs:
|
||||
build-docker:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
name: Build Docker
|
||||
needs:
|
||||
- lint
|
||||
- desktop-test
|
||||
- server-test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-server
|
||||
- build-core
|
||||
- build-storage
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download core artifact
|
||||
|
||||
2
.github/workflows/cancel.yml
vendored
2
.github/workflows/cancel.yml
vendored
@@ -14,5 +14,5 @@ jobs:
|
||||
- uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
|
||||
workflow_id: 44038251, 61883931
|
||||
workflow_id: 44038251, 61883931, 65188160
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
9
.github/workflows/dependabot.yml
vendored
Normal file
9
.github/workflows/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
versioning-strategy: increase
|
||||
commit-message:
|
||||
prefix: 'chore'
|
||||
6
.github/workflows/nightly-build.yml
vendored
6
.github/workflows/nightly-build.yml
vendored
@@ -50,6 +50,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
uses: ./.github/actions/setup-sentry
|
||||
- name: Replace Version
|
||||
run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }}
|
||||
- name: generate-assets
|
||||
@@ -110,7 +112,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
|
||||
6
.github/workflows/release-desktop-app.yml
vendored
6
.github/workflows/release-desktop-app.yml
vendored
@@ -47,6 +47,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
uses: ./.github/actions/setup-sentry
|
||||
- name: Get canary version
|
||||
id: get-canary-version
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
@@ -112,7 +114,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
|
||||
22
README.md
22
README.md
@@ -14,15 +14,6 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<!--
|
||||
Make New Badge Pattern badges inline
|
||||
See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||
-->
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
|
||||
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-66-orange.svg?style=flat-square
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
[?style=flat-square&logoColor=white&logo=affine>)](https://app.affine.pro)
|
||||
[](https://affine.pro/download)
|
||||
@@ -39,6 +30,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||
[![React-version-icon]](https://reactjs.org/)
|
||||
[![blocksuite-icon]](https://github.com/toeverything/blocksuite)
|
||||
[![Rust-version-icon]](https://www.rust-lang.org/)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_shield)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -126,10 +118,11 @@ If you have questions, you are welcome to contact us. One of the best places to
|
||||
>
|
||||
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
|
||||
|
||||
| Name | |
|
||||
| ------------------------------------ | ----------------------------------------- |
|
||||
| [@affine/bookmark](plugins/bookmark) | A block for bookmarking a website |
|
||||
| [@affine/copilot](plugins/copilot) | AI Copilot that help you document writing |
|
||||
| Official Plugin | Description |
|
||||
| ----------------------------------------------------- | ----------------------------------------- |
|
||||
| [@affine/bookmark-plugin](plugins/bookmark) | A block for bookmarking a website |
|
||||
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
|
||||
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
|
||||
|
||||
## Thanks
|
||||
|
||||
@@ -190,6 +183,7 @@ See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details
|
||||
|
||||
See [LICENSE] for details.
|
||||
|
||||
[all-contributors-badge]: https://img.shields.io/github/all-contributors/toeverything/AFFiNE/master?color=orange
|
||||
[license]: ./LICENSE
|
||||
[building.md]: ./docs/BUILDING.md
|
||||
[these people]: https://twitter.com/AffineOfficial/followers
|
||||
@@ -204,3 +198,5 @@ See [LICENSE] for details.
|
||||
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
|
||||
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fcore%2Fpackage.json&label=blocksuite
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
|
||||
|
||||
@@ -8,9 +8,9 @@ export const productionCacheGroups = {
|
||||
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';
|
||||
const name = module.context.match(
|
||||
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
|
||||
)?.[1];
|
||||
return `npm-async-${name}`;
|
||||
},
|
||||
priority: Number.MAX_SAFE_INTEGER,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
@@ -32,7 +31,7 @@ const OptimizeOptionOptions: (
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
exclude: [/\.min\.js$/, /plugins\/.+\/.+\.mjs$/],
|
||||
exclude: [/plugins\/.+\/.+\.js$/, /plugins\/.+\/.+\.mjs$/],
|
||||
parallel: true,
|
||||
extractComments: true,
|
||||
terserOptions: {
|
||||
@@ -80,6 +79,10 @@ export const createConfiguration: (
|
||||
name: 'affine',
|
||||
// to set a correct base path for the source map
|
||||
context: projectRoot,
|
||||
experiments: {
|
||||
topLevelAwait: true,
|
||||
outputModule: false,
|
||||
},
|
||||
output: {
|
||||
environment: {
|
||||
module: true,
|
||||
@@ -131,6 +134,10 @@ export const createConfiguration: (
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
// Do not mock Node.js globals
|
||||
node: false,
|
||||
requireJs: false,
|
||||
import: true,
|
||||
// Treat as missing export as error
|
||||
strictExportPresence: true,
|
||||
},
|
||||
@@ -138,6 +145,20 @@ export const createConfiguration: (
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js?$/,
|
||||
enforce: 'pre',
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('source-map-loader'),
|
||||
options: {
|
||||
filterSourceMappingUrl: (
|
||||
_url: string,
|
||||
resourcePath: string
|
||||
) => {
|
||||
return resourcePath.includes('@blocksuite');
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
@@ -253,14 +274,6 @@ export const createConfiguration: (
|
||||
ignoreOrder: true,
|
||||
}),
|
||||
]),
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, '.webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'defer',
|
||||
minify: false,
|
||||
chunks: ['index', 'plugin'],
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new VanillaExtractPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify({}),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createConfiguration, rootPath } from './config.js';
|
||||
import { merge } from 'webpack-merge';
|
||||
import { resolve } from 'node:path';
|
||||
import { join, resolve } from 'node:path';
|
||||
import type { BuildFlags } from '@affine/cli/config';
|
||||
import { getRuntimeConfig } from './runtime-config.js';
|
||||
import HTMLPlugin from 'html-webpack-plugin';
|
||||
|
||||
export default async function (cli_env: any, _: any) {
|
||||
const flags: BuildFlags = JSON.parse(
|
||||
@@ -14,15 +15,41 @@ export default async function (cli_env: any, _: any) {
|
||||
const config = createConfiguration(flags, runtimeConfig);
|
||||
return merge(config, {
|
||||
entry: {
|
||||
index: {
|
||||
asyncChunks: false,
|
||||
import: resolve(rootPath, 'src/index.tsx'),
|
||||
'polyfill/ses': {
|
||||
import: resolve(rootPath, 'src/polyfill/ses.ts'),
|
||||
},
|
||||
plugin: {
|
||||
dependOn: ['index'],
|
||||
asyncChunks: true,
|
||||
dependOn: ['polyfill/ses'],
|
||||
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
|
||||
},
|
||||
app: {
|
||||
chunkLoading: 'import',
|
||||
dependOn: ['polyfill/ses', 'plugin'],
|
||||
import: resolve(rootPath, 'src/index.tsx'),
|
||||
},
|
||||
'_plugin/index.test': {
|
||||
chunkLoading: 'import',
|
||||
dependOn: ['polyfill/ses', 'plugin'],
|
||||
import: resolve(rootPath, 'src/_plugin/index.test.tsx'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, '.webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'module',
|
||||
minify: false,
|
||||
chunks: ['app', 'plugin', 'polyfill/ses'],
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, '.webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'module',
|
||||
minify: false,
|
||||
chunks: ['_plugin/index.test', 'plugin', 'polyfill/ses'],
|
||||
filename: '_plugin/index.html',
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.2",
|
||||
"version": "0.8.0-canary.11",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
@@ -18,13 +18,13 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/icons": "^2.1.29",
|
||||
"@blocksuite/lit": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
@@ -33,6 +33,7 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"@toeverything/components": "^0.0.6",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
@@ -69,6 +70,7 @@
|
||||
"express": "^4.18.2",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
"source-map-loader": "^4.0.1",
|
||||
"style-loader": "^3.3.3",
|
||||
"swc-loader": "^0.2.3",
|
||||
"swc-plugin-coverage-instrument": "^0.0.19",
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
{
|
||||
"name": "@affine/core",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"root": "apps/core",
|
||||
"sourceRoot": "apps/core/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-script",
|
||||
"dependsOn": ["^build"],
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": ["tag:plugin"],
|
||||
"target": "build",
|
||||
"params": "ignore"
|
||||
},
|
||||
"^build"
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/.webpack/**/*",
|
||||
"{projectRoot}/**/*",
|
||||
"{projectRoot}/public/**/*",
|
||||
"{workspaceRoot}/packages/component/src/**/*",
|
||||
"{workspaceRoot}/packages/debug/src/**/*",
|
||||
"{workspaceRoot}/packages/graphql/src/**/*",
|
||||
@@ -45,7 +51,7 @@
|
||||
"options": {
|
||||
"script": "build"
|
||||
},
|
||||
"outputs": ["{projectRoot}/dist", "{projectRoot}/public/plugins"]
|
||||
"outputs": ["{projectRoot}/dist"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ const PORT = process.env.PORT || 8080;
|
||||
app.use('/', express.static('dist'));
|
||||
|
||||
app.get('/*', (req, res) => {
|
||||
if (req.url.startsWith('/plugins')) {
|
||||
res.sendFile(req.url, { root: 'dist' });
|
||||
}
|
||||
res.sendFile('index.html', { root: 'dist' });
|
||||
});
|
||||
|
||||
|
||||
47
apps/core/src/_plugin/index.test.tsx
Normal file
47
apps/core/src/_plugin/index.test.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { registeredPluginAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import { use } from 'foxact/use';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Provider } from 'jotai/react';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { _pluginNestedImportsMap } from '../bootstrap/plugins/setup';
|
||||
import { pluginRegisterPromise } from '../bootstrap/register-plugins';
|
||||
|
||||
async function main() {
|
||||
const { setup } = await import('../bootstrap/setup');
|
||||
await setup();
|
||||
const root = document.getElementById('app');
|
||||
assertExists(root);
|
||||
|
||||
const App = () => {
|
||||
use(pluginRegisterPromise);
|
||||
const plugins = useAtomValue(registeredPluginAtom);
|
||||
_pluginNestedImportsMap.forEach(value => {
|
||||
const exports = value.get('index.js');
|
||||
assertExists(exports);
|
||||
assertExists(exports?.get('entry'));
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<div data-plugins-load-status="success">
|
||||
Successfully loaded plugins:
|
||||
</div>
|
||||
{plugins.map(plugin => {
|
||||
return <div key={plugin}>{plugin}</div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
createRoot(root).render(
|
||||
<StrictMode>
|
||||
<Provider store={rootStore}>
|
||||
<App />
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
await main();
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
|
||||
import {
|
||||
BlockSuitePageList,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import '@affine/component/theme/global.css';
|
||||
import '@affine/component/theme/theme.css';
|
||||
import '@toeverything/components/style.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 { use } from 'foxact/use';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, memo, Suspense, useEffect } from 'react';
|
||||
import { lazy, memo, Suspense } from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import { router } from './router';
|
||||
import createEmotionCache from './utils/create-emotion-cache';
|
||||
|
||||
const i18n = createI18n();
|
||||
const cache = createEmotionCache();
|
||||
|
||||
const DevTools = lazy(() =>
|
||||
@@ -32,14 +32,19 @@ const future = {
|
||||
v7_startTransition: true,
|
||||
} as const;
|
||||
|
||||
export const App = memo(function App() {
|
||||
useEffect(() => {
|
||||
async function loadLanguage() {
|
||||
if (environment.isBrowser) {
|
||||
const { createI18n, setUpLanguage } = await import('@affine/i18n');
|
||||
const i18n = createI18n();
|
||||
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);
|
||||
});
|
||||
}, []);
|
||||
await setUpLanguage(i18n);
|
||||
}
|
||||
}
|
||||
|
||||
const languageLoadingPromise = loadLanguage().catch(console.error);
|
||||
|
||||
export const App = memo(function App() {
|
||||
use(languageLoadingPromise);
|
||||
return (
|
||||
<CacheProvider value={cache}>
|
||||
<AffineContext>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { PrimitiveAtom } from 'jotai';
|
||||
import { atom } from 'jotai';
|
||||
import { atomFamily, atomWithStorage } from 'jotai/utils';
|
||||
import type { AtomFamily } from 'jotai/vanilla/utils/atomFamily';
|
||||
|
||||
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
|
||||
import type { SettingProps } from '../components/affine/setting-modal';
|
||||
@@ -59,19 +61,16 @@ const defaultPageSetting = {
|
||||
mode: 'page',
|
||||
} satisfies PageLocalSetting;
|
||||
|
||||
export const pageSettingFamily = atomFamily((pageId: string) =>
|
||||
export const pageSettingFamily: AtomFamily<
|
||||
string,
|
||||
PrimitiveAtom<PageLocalSetting>
|
||||
> = atomFamily((pageId: string) =>
|
||||
atom(
|
||||
get =>
|
||||
get(pageSettingsBaseAtom)[pageId] ?? {
|
||||
...defaultPageSetting,
|
||||
},
|
||||
(
|
||||
get,
|
||||
set,
|
||||
patch:
|
||||
| Partial<PageLocalSetting>
|
||||
| ((prevSetting: PageLocalSetting | undefined) => void)
|
||||
) => {
|
||||
(get, set, patch) => {
|
||||
set(recentPageSettingsBaseAtom, ids => {
|
||||
// pick 3 recent page ids
|
||||
return [...new Set([pageId, ...ids]).values()].slice(0, 3);
|
||||
@@ -93,7 +92,7 @@ export const pageSettingFamily = atomFamily((pageId: string) =>
|
||||
|
||||
export const setPageModeAtom = atom(
|
||||
void 0,
|
||||
(get, set, pageId: string, mode: PageMode) => {
|
||||
(_, set, pageId: string, mode: PageMode) => {
|
||||
set(pageSettingFamily(pageId), { mode });
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { atom } from 'jotai/vanilla';
|
||||
|
||||
import { pageSettingFamily } from './index';
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import type {
|
||||
LocalIndexedDBDownloadProvider,
|
||||
WorkspaceAdapter,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
type RootWorkspaceMetadataV2,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import {
|
||||
migrateLocalBlobStorage,
|
||||
upgradeV1ToV2,
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { rootStore } from '@toeverything/plugin-infra/atom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
console.log('setup global');
|
||||
setupGlobal();
|
||||
|
||||
rootStore.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
WorkspaceAdapter<WorkspaceFlavour>
|
||||
>
|
||||
);
|
||||
|
||||
const value = localStorage.getItem('jotai-workspaces');
|
||||
if (value) {
|
||||
try {
|
||||
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
||||
const promises: Promise<void>[] = [];
|
||||
const newMetadata = [...metadata];
|
||||
metadata.forEach(oldMeta => {
|
||||
if (!('version' in oldMeta)) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
const upgrade = async () => {
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (!workspace) {
|
||||
console.warn('cannot find workspace', oldMeta.id);
|
||||
return;
|
||||
}
|
||||
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const doc = workspace.blockSuiteWorkspace.doc;
|
||||
const provider = createIndexedDBDownloadProvider(workspace.id, doc, {
|
||||
awareness: workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
if (doc === newDoc) {
|
||||
console.log('doc not changed');
|
||||
return;
|
||||
}
|
||||
const newWorkspace = upgradeV1ToV2(workspace);
|
||||
|
||||
const newId = await adapter.CRUD.create(
|
||||
newWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
await adapter.CRUD.delete(workspace as any);
|
||||
console.log('migrated', oldMeta.id, newId);
|
||||
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
};
|
||||
await migrateLocalBlobStorage(workspace.id, newId);
|
||||
};
|
||||
|
||||
// create a new workspace and push it to metadata
|
||||
promises.push(upgrade());
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises)
|
||||
.then(() => {
|
||||
console.log('migration done');
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('migration failed');
|
||||
})
|
||||
.finally(() => {
|
||||
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
||||
window.dispatchEvent(new CustomEvent('migration-done'));
|
||||
window.$migrationDone = true;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('error when migrating data', e);
|
||||
}
|
||||
}
|
||||
|
||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
|
||||
return Plugins.flatMap(Plugin => {
|
||||
return Plugin.Events['app:init']?.().map(
|
||||
id =>
|
||||
({
|
||||
id,
|
||||
flavour: Plugin.flavour,
|
||||
// new workspace should all support sub-doc feature
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
}) satisfies RootWorkspaceMetadataV2
|
||||
);
|
||||
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
||||
};
|
||||
|
||||
await rootStore
|
||||
.get(rootWorkspacesMetadataAtom)
|
||||
.then(meta => {
|
||||
if (meta.length === 0 && localStorage.getItem('is-first-open') === null) {
|
||||
const result = createFirst();
|
||||
console.info('create first workspace', result);
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
rootStore.set(rootWorkspacesMetadataAtom, result).catch(console.error);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
@@ -1,12 +1,27 @@
|
||||
import * as AFFiNEComponent from '@affine/component';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { CallbackMap, PluginContext } from '@affine/sdk/entry';
|
||||
import { FormatQuickBar } from '@blocksuite/blocks';
|
||||
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
|
||||
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import * as Icons from '@blocksuite/icons';
|
||||
import * as Atom from '@toeverything/plugin-infra/atom';
|
||||
import {
|
||||
contentLayoutAtom,
|
||||
currentPageAtom,
|
||||
currentWorkspaceAtom,
|
||||
editorItemsAtom,
|
||||
headerItemsAtom,
|
||||
rootStore,
|
||||
settingItemsAtom,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
import * as Jotai from 'jotai/index';
|
||||
import { Provider } from 'jotai/react';
|
||||
import * as JotaiUtils from 'jotai/utils';
|
||||
import * as React from 'react';
|
||||
import { createElement, type PropsWithChildren } from 'react';
|
||||
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import * as ReactDomClient from 'react-dom/client';
|
||||
@@ -15,35 +30,104 @@ import * as SWR from 'swr';
|
||||
import { createFetch } from './endowments/fercher';
|
||||
import { createTimers } from './endowments/timer';
|
||||
|
||||
const logger = new DebugLogger('plugins:permission');
|
||||
const dynamicImportKey = '$h_import';
|
||||
|
||||
const setupImportsMap = () => {
|
||||
importsMap.set('react', new Map(Object.entries(React)));
|
||||
importsMap.set('react/jsx-runtime', new Map(Object.entries(ReactJSXRuntime)));
|
||||
importsMap.set('react-dom', new Map(Object.entries(ReactDom)));
|
||||
importsMap.set('react-dom/client', new Map(Object.entries(ReactDomClient)));
|
||||
importsMap.set('@blocksuite/icons', new Map(Object.entries(Icons)));
|
||||
importsMap.set('@affine/component', new Map(Object.entries(AFFiNEComponent)));
|
||||
importsMap.set(
|
||||
const permissionLogger = new DebugLogger('plugins:permission');
|
||||
const importLogger = new DebugLogger('plugins:import');
|
||||
|
||||
const setupRootImportsMap = () => {
|
||||
_rootImportsMap.set('react', new Map(Object.entries(React)));
|
||||
_rootImportsMap.set(
|
||||
'react/jsx-runtime',
|
||||
new Map(Object.entries(ReactJSXRuntime))
|
||||
);
|
||||
_rootImportsMap.set('react-dom', new Map(Object.entries(ReactDom)));
|
||||
_rootImportsMap.set(
|
||||
'react-dom/client',
|
||||
new Map(Object.entries(ReactDomClient))
|
||||
);
|
||||
_rootImportsMap.set('@blocksuite/icons', new Map(Object.entries(Icons)));
|
||||
_rootImportsMap.set(
|
||||
'@affine/component',
|
||||
new Map(Object.entries(AFFiNEComponent))
|
||||
);
|
||||
_rootImportsMap.set(
|
||||
'@blocksuite/blocks/std',
|
||||
new Map(Object.entries(BlockSuiteBlocksStd))
|
||||
);
|
||||
importsMap.set(
|
||||
_rootImportsMap.set(
|
||||
'@blocksuite/global/utils',
|
||||
new Map(Object.entries(BlockSuiteGlobalUtils))
|
||||
);
|
||||
importsMap.set('jotai', new Map(Object.entries(Jotai)));
|
||||
importsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils)));
|
||||
importsMap.set(
|
||||
'@toeverything/plugin-infra/atom',
|
||||
new Map(Object.entries(Atom))
|
||||
_rootImportsMap.set('jotai', new Map(Object.entries(Jotai)));
|
||||
_rootImportsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils)));
|
||||
_rootImportsMap.set(
|
||||
'@affine/sdk/entry',
|
||||
new Map(
|
||||
Object.entries({
|
||||
rootStore: rootStore,
|
||||
currentWorkspaceAtom: currentWorkspaceAtom,
|
||||
currentPageAtom: currentPageAtom,
|
||||
contentLayoutAtom: contentLayoutAtom,
|
||||
})
|
||||
)
|
||||
);
|
||||
importsMap.set('swr', new Map(Object.entries(SWR)));
|
||||
_rootImportsMap.set('swr', new Map(Object.entries(SWR)));
|
||||
};
|
||||
|
||||
const importsMap = new Map<string, Map<string, any>>();
|
||||
setupImportsMap();
|
||||
export { importsMap };
|
||||
// module -> importName -> updater[]
|
||||
export const _rootImportsMap = new Map<string, Map<string, any>>();
|
||||
setupRootImportsMap();
|
||||
// pluginName -> module -> importName -> updater[]
|
||||
export const _pluginNestedImportsMap = new Map<
|
||||
string,
|
||||
Map<string, Map<string, any>>
|
||||
>();
|
||||
|
||||
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
|
||||
export const createImports = (pluginName: string) => {
|
||||
if (pluginImportsFunctionMap.has(pluginName)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return pluginImportsFunctionMap.get(pluginName)!;
|
||||
}
|
||||
const imports = (
|
||||
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
importLogger.debug('currentImportMap', pluginName, currentImportMap);
|
||||
|
||||
for (const [module, moduleUpdaters] of newUpdaters) {
|
||||
importLogger.debug('imports module', module, moduleUpdaters);
|
||||
let moduleImports = _rootImportsMap.get(module);
|
||||
if (!moduleImports) {
|
||||
moduleImports = currentImportMap.get(module);
|
||||
}
|
||||
if (moduleImports) {
|
||||
for (const [importName, importUpdaters] of moduleUpdaters) {
|
||||
const updateImport = (value: any) => {
|
||||
for (const importUpdater of importUpdaters) {
|
||||
importUpdater(value);
|
||||
}
|
||||
};
|
||||
if (moduleImports.has(importName)) {
|
||||
const val = moduleImports.get(importName);
|
||||
updateImport(val);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
'cannot find module in plugin import map',
|
||||
module,
|
||||
currentImportMap,
|
||||
_pluginNestedImportsMap
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
pluginImportsFunctionMap.set(pluginName, imports);
|
||||
return imports;
|
||||
};
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
@@ -54,88 +138,312 @@ const sharedGlobalThis = Object.assign(Object.create(null), timer, {
|
||||
fetch: pluginFetch,
|
||||
});
|
||||
|
||||
export const createGlobalThis = (name: string) => {
|
||||
return Object.assign(Object.create(null), sharedGlobalThis, {
|
||||
process: Object.freeze({
|
||||
env: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
},
|
||||
}),
|
||||
// UNSAFE: React will read `window` and `document`
|
||||
window: new Proxy(
|
||||
const dynamicImportMap = new Map<
|
||||
string,
|
||||
(moduleName: string) => Promise<any>
|
||||
>();
|
||||
|
||||
export const createOrGetDynamicImport = (
|
||||
baseUrl: string,
|
||||
pluginName: string
|
||||
) => {
|
||||
if (dynamicImportMap.has(pluginName)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return dynamicImportMap.get(pluginName)!;
|
||||
}
|
||||
const dynamicImport = async (moduleName: string): Promise<any> => {
|
||||
const codeUrl = `${baseUrl}/${moduleName}`;
|
||||
const analysisUrl = `${baseUrl}/${moduleName}.json`;
|
||||
const response = await fetch(codeUrl);
|
||||
const analysisResponse = await fetch(analysisUrl);
|
||||
const analysis = await analysisResponse.json();
|
||||
const exports = analysis.exports as string[];
|
||||
const code = await response.text();
|
||||
const moduleCompartment = new Compartment(
|
||||
createOrGetGlobalThis(
|
||||
pluginName,
|
||||
// use singleton here to avoid infinite loop
|
||||
createOrGetDynamicImport(pluginName, baseUrl)
|
||||
)
|
||||
);
|
||||
const entryPoint = moduleCompartment.evaluate(code, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
const moduleExports = {} as Record<string, any>;
|
||||
const setVarProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
logger.debug(`${name} is accessing window`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(window, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
logger.debug(`${name} is calling window`, key, args);
|
||||
return result.apply(window, args);
|
||||
};
|
||||
}
|
||||
logger.debug('window', key, result);
|
||||
return result;
|
||||
get(_, p: string): any {
|
||||
return (newValue: any) => {
|
||||
moduleExports[p] = newValue;
|
||||
};
|
||||
},
|
||||
}
|
||||
),
|
||||
document: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
logger.debug(`${name} is accessing document`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(document, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
logger.debug(`${name} is calling window`, key, args);
|
||||
return result.apply(document, args);
|
||||
};
|
||||
}
|
||||
logger.debug('document', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
navigator: {
|
||||
userAgent: navigator.userAgent,
|
||||
},
|
||||
|
||||
// safe to use for all plugins
|
||||
Error: globalThis.Error,
|
||||
TypeError: globalThis.TypeError,
|
||||
RangeError: globalThis.RangeError,
|
||||
console: globalThis.console,
|
||||
crypto: globalThis.crypto,
|
||||
|
||||
// copilot uses these
|
||||
CustomEvent: globalThis.CustomEvent,
|
||||
Date: globalThis.Date,
|
||||
Math: globalThis.Math,
|
||||
URL: globalThis.URL,
|
||||
URLSearchParams: globalThis.URLSearchParams,
|
||||
Headers: globalThis.Headers,
|
||||
TextEncoder: globalThis.TextEncoder,
|
||||
TextDecoder: globalThis.TextDecoder,
|
||||
Request: globalThis.Request,
|
||||
|
||||
// image-preview uses these
|
||||
Blob: globalThis.Blob,
|
||||
ClipboardItem: globalThis.ClipboardItem,
|
||||
|
||||
// fixme: use our own db api
|
||||
indexedDB: globalThis.indexedDB,
|
||||
IDBRequest: globalThis.IDBRequest,
|
||||
IDBDatabase: globalThis.IDBDatabase,
|
||||
IDBCursorWithValue: globalThis.IDBCursorWithValue,
|
||||
IDBFactory: globalThis.IDBFactory,
|
||||
IDBKeyRange: globalThis.IDBKeyRange,
|
||||
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
|
||||
IDBTransaction: globalThis.IDBTransaction,
|
||||
IDBObjectStore: globalThis.IDBObjectStore,
|
||||
IDBIndex: globalThis.IDBIndex,
|
||||
IDBCursor: globalThis.IDBCursor,
|
||||
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
|
||||
});
|
||||
);
|
||||
entryPoint({
|
||||
imports: createImports(pluginName),
|
||||
liveVar: setVarProxy,
|
||||
onceVar: setVarProxy,
|
||||
});
|
||||
importLogger.debug('import', moduleName, exports, moduleExports);
|
||||
return moduleExports;
|
||||
};
|
||||
dynamicImportMap.set(pluginName, dynamicImport);
|
||||
return dynamicImport;
|
||||
};
|
||||
|
||||
const globalThisMap = new Map<string, any>();
|
||||
|
||||
export const createOrGetGlobalThis = (
|
||||
pluginName: string,
|
||||
dynamicImport: (moduleName: string) => Promise<any>
|
||||
) => {
|
||||
if (globalThisMap.has(pluginName)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return globalThisMap.get(pluginName)!;
|
||||
}
|
||||
const pluginGlobalThis = Object.assign(
|
||||
Object.create(null),
|
||||
sharedGlobalThis,
|
||||
{
|
||||
process: Object.freeze({
|
||||
env: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
},
|
||||
}),
|
||||
// dynamic import function
|
||||
[dynamicImportKey]: dynamicImport,
|
||||
// UNSAFE: React will read `window` and `document`
|
||||
window: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
permissionLogger.debug(`${pluginName} is accessing window`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(window, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
permissionLogger.debug(
|
||||
`${pluginName} is calling window`,
|
||||
key,
|
||||
args
|
||||
);
|
||||
return result.apply(window, args);
|
||||
};
|
||||
}
|
||||
permissionLogger.debug('window', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
document: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
permissionLogger.debug(`${pluginName} is accessing document`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(document, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
permissionLogger.debug(
|
||||
`${pluginName} is calling window`,
|
||||
key,
|
||||
args
|
||||
);
|
||||
return result.apply(document, args);
|
||||
};
|
||||
}
|
||||
permissionLogger.debug('document', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
navigator: {
|
||||
userAgent: navigator.userAgent,
|
||||
},
|
||||
|
||||
// safe to use for all plugins
|
||||
Error: globalThis.Error,
|
||||
TypeError: globalThis.TypeError,
|
||||
RangeError: globalThis.RangeError,
|
||||
console: globalThis.console,
|
||||
crypto: globalThis.crypto,
|
||||
|
||||
// copilot uses these
|
||||
CustomEvent: globalThis.CustomEvent,
|
||||
Date: globalThis.Date,
|
||||
Math: globalThis.Math,
|
||||
URL: globalThis.URL,
|
||||
URLSearchParams: globalThis.URLSearchParams,
|
||||
Headers: globalThis.Headers,
|
||||
TextEncoder: globalThis.TextEncoder,
|
||||
TextDecoder: globalThis.TextDecoder,
|
||||
Request: globalThis.Request,
|
||||
|
||||
// image-preview uses these
|
||||
Blob: globalThis.Blob,
|
||||
ClipboardItem: globalThis.ClipboardItem,
|
||||
|
||||
// fixme: use our own db api
|
||||
indexedDB: globalThis.indexedDB,
|
||||
IDBRequest: globalThis.IDBRequest,
|
||||
IDBDatabase: globalThis.IDBDatabase,
|
||||
IDBCursorWithValue: globalThis.IDBCursorWithValue,
|
||||
IDBFactory: globalThis.IDBFactory,
|
||||
IDBKeyRange: globalThis.IDBKeyRange,
|
||||
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
|
||||
IDBTransaction: globalThis.IDBTransaction,
|
||||
IDBObjectStore: globalThis.IDBObjectStore,
|
||||
IDBIndex: globalThis.IDBIndex,
|
||||
IDBCursor: globalThis.IDBCursor,
|
||||
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
|
||||
}
|
||||
);
|
||||
globalThisMap.set(pluginName, pluginGlobalThis);
|
||||
return pluginGlobalThis;
|
||||
};
|
||||
|
||||
export const setupPluginCode = async (
|
||||
baseUrl: string,
|
||||
pluginName: string,
|
||||
filename: string
|
||||
) => {
|
||||
if (!_pluginNestedImportsMap.has(pluginName)) {
|
||||
_pluginNestedImportsMap.set(pluginName, new Map());
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
const isMissingPackage = (name: string) =>
|
||||
_rootImportsMap.has(name) && !currentImportMap.has(name);
|
||||
|
||||
const bundleAnalysis = await fetch(`${baseUrl}/${filename}.json`).then(res =>
|
||||
res.json()
|
||||
);
|
||||
const moduleExports = bundleAnalysis.exports as Record<string, [string]>;
|
||||
const moduleImports = bundleAnalysis.imports as string[];
|
||||
const moduleReexports = bundleAnalysis.reexports as Record<
|
||||
string,
|
||||
[localName: string, exportedName: string][]
|
||||
>;
|
||||
await Promise.all(
|
||||
moduleImports.map(name => {
|
||||
if (isMissingPackage(name)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
importLogger.debug('missing package', name);
|
||||
return setupPluginCode(baseUrl, pluginName, name);
|
||||
}
|
||||
})
|
||||
);
|
||||
const code = await fetch(`${baseUrl}/${filename.replace(/^\.\//, '')}`).then(
|
||||
res => res.text()
|
||||
);
|
||||
importLogger.debug('evaluating', filename);
|
||||
const moduleCompartment = new Compartment(
|
||||
createOrGetGlobalThis(
|
||||
pluginName,
|
||||
// use singleton here to avoid infinite loop
|
||||
createOrGetDynamicImport(baseUrl, pluginName)
|
||||
)
|
||||
);
|
||||
const entryPoint = moduleCompartment.evaluate(code, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
const moduleExportsMap = new Map<string, any>();
|
||||
const setVarProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, p: string): any {
|
||||
return (newValue: any) => {
|
||||
moduleExportsMap.set(p, newValue);
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
currentImportMap.set(filename, moduleExportsMap);
|
||||
entryPoint({
|
||||
imports: createImports(pluginName),
|
||||
liveVar: setVarProxy,
|
||||
onceVar: setVarProxy,
|
||||
});
|
||||
|
||||
for (const [newExport, [originalExport]] of Object.entries(moduleExports)) {
|
||||
if (newExport === originalExport) continue;
|
||||
const value = moduleExportsMap.get(originalExport);
|
||||
moduleExportsMap.set(newExport, value);
|
||||
moduleExportsMap.delete(originalExport);
|
||||
}
|
||||
|
||||
for (const [name, reexports] of Object.entries(moduleReexports)) {
|
||||
const targetExports = currentImportMap.get(filename);
|
||||
const moduleExports = currentImportMap.get(name);
|
||||
assertExists(targetExports);
|
||||
assertExists(moduleExports);
|
||||
for (const [exportedName, localName] of reexports) {
|
||||
const exportedValue: any = moduleExports.get(exportedName);
|
||||
assertExists(exportedValue);
|
||||
targetExports.set(localName, exportedValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
createElement(
|
||||
Provider,
|
||||
{
|
||||
store: rootStore,
|
||||
},
|
||||
children
|
||||
);
|
||||
|
||||
const group = new DisposableGroup();
|
||||
const entryLogger = new DebugLogger('plugin:entry');
|
||||
|
||||
export const evaluatePluginEntry = (pluginName: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
const pluginExports = currentImportMap.get('index.js');
|
||||
assertExists(pluginExports);
|
||||
const entryFunction = pluginExports.get('entry');
|
||||
const cleanup = entryFunction(<PluginContext>{
|
||||
register: (part, callback) => {
|
||||
entryLogger.info(`Registering ${pluginName} to ${part}`);
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(headerItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(editorItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
} else if (part === 'window') {
|
||||
rootStore.set(windowItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['window'],
|
||||
}));
|
||||
} else if (part === 'setting') {
|
||||
rootStore.set(settingItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['setting'],
|
||||
}));
|
||||
} else if (part === 'formatBar') {
|
||||
FormatQuickBar.customElements.push((page, getBlockRange) => {
|
||||
const div = document.createElement('div');
|
||||
(callback as CallbackMap['formatBar'])(div, page, getBlockRange);
|
||||
return div;
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
});
|
||||
if (typeof cleanup !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
group.add(cleanup);
|
||||
};
|
||||
|
||||
@@ -1,60 +1,7 @@
|
||||
/// <reference types="@types/webpack-env" />
|
||||
import 'ses';
|
||||
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { FormatQuickBar } from '@blocksuite/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import {
|
||||
editorItemsAtom,
|
||||
headerItemsAtom,
|
||||
registeredPluginAtom,
|
||||
rootStore,
|
||||
settingItemsAtom,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import type {
|
||||
CallbackMap,
|
||||
PluginContext,
|
||||
} from '@toeverything/plugin-infra/entry';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { createElement } from 'react';
|
||||
import { registeredPluginAtom, rootStore } from '@toeverything/infra/atom';
|
||||
|
||||
import { createGlobalThis, importsMap } from './plugins/setup';
|
||||
|
||||
if (!process.env.COVERAGE) {
|
||||
lockdown({
|
||||
evalTaming: 'unsafeEval',
|
||||
overrideTaming: 'severe',
|
||||
consoleTaming: 'unsafe',
|
||||
errorTaming: 'unsafe',
|
||||
errorTrapping: 'platform',
|
||||
unhandledRejectionTrapping: 'report',
|
||||
});
|
||||
}
|
||||
|
||||
const imports = (
|
||||
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
|
||||
) => {
|
||||
for (const [module, moduleUpdaters] of newUpdaters) {
|
||||
const moduleImports = importsMap.get(module);
|
||||
if (moduleImports) {
|
||||
for (const [importName, importUpdaters] of moduleUpdaters) {
|
||||
const updateImport = (value: any) => {
|
||||
for (const importUpdater of importUpdaters) {
|
||||
importUpdater(value);
|
||||
}
|
||||
};
|
||||
if (moduleImports.has(importName)) {
|
||||
const val = moduleImports.get(importName);
|
||||
updateImport(val);
|
||||
} else {
|
||||
console.log('import not found', importName, module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
import { evaluatePluginEntry, setupPluginCode } from './plugins/setup';
|
||||
|
||||
const builtinPluginUrl = new Set([
|
||||
'/plugins/bookmark',
|
||||
@@ -65,17 +12,6 @@ const builtinPluginUrl = new Set([
|
||||
|
||||
const logger = new DebugLogger('register-plugins');
|
||||
|
||||
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
createElement(
|
||||
Provider,
|
||||
{
|
||||
store: rootStore,
|
||||
},
|
||||
children
|
||||
);
|
||||
|
||||
const group = new DisposableGroup();
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __pluginPackageJson__: unknown[];
|
||||
@@ -83,7 +19,7 @@ declare global {
|
||||
|
||||
globalThis.__pluginPackageJson__ = [];
|
||||
|
||||
await Promise.all(
|
||||
export const pluginRegisterPromise = Promise.all(
|
||||
[...builtinPluginUrl].map(url => {
|
||||
return fetch(`${url}/package.json`)
|
||||
.then(async res => {
|
||||
@@ -99,14 +35,15 @@ await Promise.all(
|
||||
globalThis.__pluginPackageJson__.push(packageJson);
|
||||
logger.debug(`registering plugin ${pluginName}`);
|
||||
logger.debug(`package.json: ${packageJson}`);
|
||||
if (!release && process.env.NODE_ENV === 'production') {
|
||||
if (!release && !runtimeConfig.enablePlugin) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const pluginCompartment = new Compartment(createGlobalThis(pluginName));
|
||||
const baseURL = url;
|
||||
const entryURL = `${baseURL}/${core}`;
|
||||
rootStore.set(registeredPluginAtom, prev => [...prev, pluginName]);
|
||||
await fetch(entryURL).then(async res => {
|
||||
await setupPluginCode(baseURL, pluginName, core);
|
||||
console.log(`prepareImports for ${pluginName} done`);
|
||||
await fetch(entryURL).then(async () => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async (asset: string) => {
|
||||
@@ -128,70 +65,7 @@ await Promise.all(
|
||||
})
|
||||
);
|
||||
}
|
||||
const codeText = await res.text();
|
||||
try {
|
||||
const entryPoint = pluginCompartment.evaluate(codeText, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
entryPoint({
|
||||
imports,
|
||||
onceVar: {
|
||||
entry: (
|
||||
entryFunction: (context: PluginContext) => () => void
|
||||
) => {
|
||||
const cleanup = entryFunction({
|
||||
register: (part, callback) => {
|
||||
logger.info(`Registering ${pluginName} to ${part}`);
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(headerItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(editorItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
} else if (part === 'window') {
|
||||
rootStore.set(windowItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['window'],
|
||||
}));
|
||||
} else if (part === 'setting') {
|
||||
rootStore.set(settingItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['setting'],
|
||||
}));
|
||||
} else if (part === 'formatBar') {
|
||||
FormatQuickBar.customElements.push(
|
||||
(page, getBlockRange) => {
|
||||
const div = document.createElement('div');
|
||||
(callback as CallbackMap['formatBar'])(
|
||||
div,
|
||||
page,
|
||||
getBlockRange
|
||||
);
|
||||
return div;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
});
|
||||
if (typeof cleanup !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
group.add(cleanup);
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(pluginName, e);
|
||||
}
|
||||
evaluatePluginEntry(pluginName);
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
|
||||
183
apps/core/src/bootstrap/setup.ts
Normal file
183
apps/core/src/bootstrap/setup.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import {
|
||||
migrateDatabaseBlockTo3,
|
||||
migrateToSubdoc,
|
||||
} from '@affine/env/blocksuite';
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import type {
|
||||
LocalIndexedDBDownloadProvider,
|
||||
WorkspaceAdapter,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
type RootWorkspaceMetadataV2,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import {
|
||||
migrateLocalBlobStorage,
|
||||
upgradeV1ToV2,
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
async function tryMigration() {
|
||||
const value = localStorage.getItem('jotai-workspaces');
|
||||
if (value) {
|
||||
try {
|
||||
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
||||
const promises: Promise<void>[] = [];
|
||||
const newMetadata = [...metadata];
|
||||
metadata.forEach(oldMeta => {
|
||||
if (!('version' in oldMeta)) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
const upgrade = async () => {
|
||||
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (!workspace) {
|
||||
console.warn('cannot find workspace', oldMeta.id);
|
||||
return;
|
||||
}
|
||||
const doc = workspace.blockSuiteWorkspace.doc;
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
workspace.id,
|
||||
doc,
|
||||
{
|
||||
awareness:
|
||||
workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}
|
||||
) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
if (doc === newDoc) {
|
||||
console.log('doc not changed');
|
||||
return;
|
||||
}
|
||||
const newWorkspace = upgradeV1ToV2(workspace);
|
||||
await migrateDatabaseBlockTo3(newWorkspace.blockSuiteWorkspace.doc);
|
||||
|
||||
const newId = await adapter.CRUD.create(
|
||||
newWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
await adapter.CRUD.delete(workspace as any);
|
||||
console.log('migrated', oldMeta.id, newId);
|
||||
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
};
|
||||
await migrateLocalBlobStorage(workspace.id, newId);
|
||||
console.log('migrate to v2');
|
||||
};
|
||||
|
||||
// create a new workspace and push it to metadata
|
||||
promises.push(upgrade());
|
||||
} else if (oldMeta.version < WorkspaceVersion.DatabaseV3) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
promises.push(
|
||||
(async () => {
|
||||
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (workspace) {
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
workspace.id,
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
{
|
||||
awareness:
|
||||
workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}
|
||||
) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
await migrateDatabaseBlockTo3(
|
||||
workspace.blockSuiteWorkspace.doc
|
||||
);
|
||||
}
|
||||
const index = newMetadata.findIndex(
|
||||
meta => meta.id === oldMeta.id
|
||||
);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
};
|
||||
console.log('migrate to v3');
|
||||
})()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises)
|
||||
.then(() => {
|
||||
console.log('migration done');
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('migration failed', e);
|
||||
})
|
||||
.finally(() => {
|
||||
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
||||
window.dispatchEvent(new CustomEvent('migration-done'));
|
||||
window.$migrationDone = true;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('error when migrating data', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createFirstAppData() {
|
||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
|
||||
return Plugins.flatMap(Plugin => {
|
||||
return Plugin.Events['app:init']?.().map(
|
||||
id =>
|
||||
<RootWorkspaceMetadataV2>{
|
||||
id,
|
||||
flavour: Plugin.flavour,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
}
|
||||
);
|
||||
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
||||
};
|
||||
if (localStorage.getItem('is-first-open') !== null) {
|
||||
return;
|
||||
}
|
||||
const result = createFirst();
|
||||
console.info('create first workspace', result);
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
rootStore.set(rootWorkspacesMetadataAtom, result);
|
||||
}
|
||||
|
||||
export async function setup() {
|
||||
rootStore.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
WorkspaceAdapter<WorkspaceFlavour>
|
||||
>
|
||||
);
|
||||
|
||||
console.log('setup global');
|
||||
setupGlobal();
|
||||
|
||||
createFirstAppData();
|
||||
await tryMigration();
|
||||
await rootStore.get(rootWorkspacesMetadataAtom);
|
||||
console.log('setup done');
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
|
||||
|
||||
@@ -139,7 +139,6 @@ const SetDBLocationContent = ({
|
||||
if (result?.filePath) {
|
||||
onConfirmLocation(result.filePath);
|
||||
} else if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic so the type is unknown
|
||||
toast(t[result.error]());
|
||||
}
|
||||
})().catch(err => {
|
||||
@@ -273,7 +272,6 @@ export const CreateWorkspaceModal = ({
|
||||
setStep('set-syncing-mode');
|
||||
} else if (result.error || result.canceled) {
|
||||
if (result.error) {
|
||||
// @ts-expect-error: result.error is dynamic so the type is unknown
|
||||
toast(t[result.error]());
|
||||
}
|
||||
onClose();
|
||||
|
||||
@@ -36,7 +36,6 @@ export const ExportPanel: FC<{
|
||||
await syncBlobsToSqliteDb(workspace);
|
||||
const result = await window.apis?.dialog.saveDBFileAs(workspaceId);
|
||||
if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
} else if (!result?.canceled) {
|
||||
toast(t['Export success']());
|
||||
|
||||
@@ -55,7 +55,6 @@ export const StoragePanel: FC<{
|
||||
if (!result?.error && !result?.canceled) {
|
||||
toast(t['Move folder success']());
|
||||
} else if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,11 +3,13 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
registeredPluginAtom,
|
||||
settingItemsAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { pluginItem } from './style.css';
|
||||
|
||||
const PluginSettingWrapper: FC<{
|
||||
id: string;
|
||||
title?: ReactNode;
|
||||
@@ -45,7 +47,9 @@ export const Plugins = () => {
|
||||
data-testid="plugins-title"
|
||||
/>
|
||||
{allowedPlugins.map(plugin => (
|
||||
<PluginSettingWrapper key={plugin} id={plugin} title={plugin} />
|
||||
<div className={pluginItem} key={plugin}>
|
||||
<PluginSettingWrapper key={plugin} id={plugin} title={plugin} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -7,3 +7,10 @@ export const settingWrapper = style({
|
||||
minWidth: '150px',
|
||||
maxWidth: '250px',
|
||||
});
|
||||
|
||||
export const pluginItem = style({
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
transition: '0.3s',
|
||||
padding: '24px 8px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tooltip } from '@affine/component';
|
||||
import { ScrollableContainer, Tooltip } from '@affine/component';
|
||||
import {
|
||||
WorkspaceListItemSkeleton,
|
||||
WorkspaceListSkeleton,
|
||||
@@ -8,7 +8,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { FC } from 'react';
|
||||
@@ -76,10 +76,12 @@ export const SettingSidebar: FC<{
|
||||
</div>
|
||||
<div className={clsx(sidebarItemsWrapper, 'scroll')}>
|
||||
<Suspense fallback={<WorkspaceListSkeleton />}>
|
||||
<WorkspaceList
|
||||
onWorkspaceSettingClick={onWorkspaceSettingClick}
|
||||
selectedWorkspaceId={selectedWorkspaceId}
|
||||
/>
|
||||
<ScrollableContainer>
|
||||
<WorkspaceList
|
||||
onWorkspaceSettingClick={onWorkspaceSettingClick}
|
||||
selectedWorkspaceId={selectedWorkspaceId}
|
||||
/>
|
||||
</ScrollableContainer>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ export const settingSlideBar = style({
|
||||
width: '25%',
|
||||
maxWidth: '242px',
|
||||
background: 'var(--affine-background-secondary-color)',
|
||||
padding: '20px 16px',
|
||||
padding: '20px 0px',
|
||||
height: '100%',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
@@ -15,14 +15,14 @@ export const sidebarTitle = style({
|
||||
fontSize: 'var(--affine-font-h-6)',
|
||||
fontWeight: '600',
|
||||
lineHeight: 'var(--affine-line-height)',
|
||||
paddingLeft: '8px',
|
||||
padding: '0px 16px 0px 24px',
|
||||
});
|
||||
|
||||
export const sidebarSubtitle = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
lineHeight: 'var(--affine-line-height)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
paddingLeft: '8px',
|
||||
padding: '0px 16px 0px 24px',
|
||||
marginTop: '20px',
|
||||
marginBottom: '4px',
|
||||
display: 'flex',
|
||||
@@ -34,7 +34,7 @@ export const sidebarItemsWrapper = style({
|
||||
selectors: {
|
||||
'&.scroll': {
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
overflowY: 'hidden',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -42,9 +42,9 @@ export const sidebarItemsWrapper = style({
|
||||
export const sidebarSelectItem = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 8px',
|
||||
margin: '0px 16px 4px 16px',
|
||||
padding: '0px 8px',
|
||||
height: '30px',
|
||||
marginBottom: '4px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { Suspense, useCallback } from 'react';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-pa
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type React from 'react';
|
||||
import { Suspense, useMemo } from 'react';
|
||||
import { Suspense, useCallback, useMemo } from 'react';
|
||||
|
||||
import { allPageModeSelectAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
@@ -70,16 +70,20 @@ const PagePreview = ({
|
||||
};
|
||||
|
||||
const PageListEmpty = (props: {
|
||||
createPage?: () => void;
|
||||
createPage?: ReturnType<typeof usePageHelper>['createPage'];
|
||||
listType: BlockSuitePageListProps['listType'];
|
||||
}) => {
|
||||
const { listType, createPage } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const onCreatePage = useCallback(() => {
|
||||
createPage?.();
|
||||
}, [createPage]);
|
||||
|
||||
const getEmptyDescription = () => {
|
||||
if (listType === 'all') {
|
||||
const CreateNewPageButton = () => (
|
||||
<button className={emptyDescButton} onClick={createPage}>
|
||||
const createNewPageButton = (
|
||||
<button className={emptyDescButton} onClick={onCreatePage}>
|
||||
New Page
|
||||
</button>
|
||||
);
|
||||
@@ -87,7 +91,7 @@ const PageListEmpty = (props: {
|
||||
const shortcut = environment.isMacOs ? '⌘ + N' : 'Ctrl + N';
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPagesClient">
|
||||
Click on the <CreateNewPageButton /> button Or press
|
||||
Click on the {createNewPageButton} button Or press
|
||||
<kbd className={emptyDescKbd}>{{ shortcut } as any}</kbd> to create
|
||||
your first page.
|
||||
</Trans>
|
||||
@@ -96,7 +100,7 @@ const PageListEmpty = (props: {
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPages">
|
||||
Click on the
|
||||
<CreateNewPageButton />
|
||||
{createNewPageButton}
|
||||
button to create your first page.
|
||||
</Trans>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,14 +5,15 @@ export const StyledEditorModeSwitch = styled('div')<{
|
||||
showAlone?: boolean;
|
||||
}>(({ switchLeft, showAlone }) => {
|
||||
return {
|
||||
width: showAlone ? '40px' : '78px',
|
||||
maxWidth: showAlone ? '40px' : '70px',
|
||||
gap: '8px',
|
||||
height: '32px',
|
||||
background: showAlone
|
||||
? 'transparent'
|
||||
: 'var(--affine-background-secondary-color)',
|
||||
borderRadius: '12px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
padding: '0 8px',
|
||||
padding: '4px 4px',
|
||||
position: 'relative',
|
||||
|
||||
'::after': {
|
||||
@@ -25,7 +26,7 @@ export const StyledEditorModeSwitch = styled('div')<{
|
||||
borderRadius: '8px',
|
||||
zIndex: 1,
|
||||
position: 'absolute',
|
||||
transform: `translateX(${switchLeft ? '0' : '38px'})`,
|
||||
transform: `translateX(${switchLeft ? '0' : '32px'})`,
|
||||
transition: 'all .15s',
|
||||
},
|
||||
};
|
||||
@@ -54,7 +55,7 @@ export const StyledSwitchItem = styled('button')<{
|
||||
zIndex: 2,
|
||||
fontSize: '20px',
|
||||
path: {
|
||||
fill: 'currentColor',
|
||||
stroke: 'currentColor',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ const HoverAnimateController = ({
|
||||
>
|
||||
{cloneElement(children, {
|
||||
isStopped: !startAnimate,
|
||||
speed: 5,
|
||||
speed: 1,
|
||||
width: 20,
|
||||
height: 20,
|
||||
})}
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { pageSettingFamily } from '../../../../atoms';
|
||||
@@ -24,7 +24,6 @@ import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suit
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { toast } from '../../../../utils';
|
||||
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
|
||||
import * as styles from '../styles.css';
|
||||
import { LanguageMenu } from './language-menu';
|
||||
const CommonMenu = () => {
|
||||
const content = (
|
||||
@@ -71,61 +70,56 @@ const PageMenu = () => {
|
||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const [openConfirm, setOpenConfirm] = useState(false);
|
||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const handleFavorite = useCallback(() => {
|
||||
setPageMeta(pageId, { favorite: !favorite });
|
||||
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
|
||||
}, [favorite, pageId, setPageMeta, t]);
|
||||
const handleSwitchMode = useCallback(() => {
|
||||
setSetting(setting => ({
|
||||
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
|
||||
}));
|
||||
toast(
|
||||
mode === 'page'
|
||||
? t['com.affine.edgelessMode']()
|
||||
: t['com.affine.pageMode']()
|
||||
);
|
||||
}, [mode, setSetting, t]);
|
||||
const handleOnConfirm = useCallback(() => {
|
||||
removeToTrash(pageMeta.id);
|
||||
toast(t['Moved to Trash']());
|
||||
setOpenConfirm(false);
|
||||
}, [pageMeta.id, removeToTrash, t]);
|
||||
|
||||
const EditMenu = (
|
||||
<>
|
||||
<>
|
||||
<MenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onClick={() => {
|
||||
setPageMeta(pageId, { favorite: !favorite });
|
||||
toast(
|
||||
favorite
|
||||
? t['Removed from Favorites']()
|
||||
: t['Added to Favorites']()
|
||||
);
|
||||
}}
|
||||
icon={
|
||||
favorite ? (
|
||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={() => {
|
||||
setSetting(setting => ({
|
||||
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
<MoveToTrash
|
||||
data-testid="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
}}
|
||||
/>
|
||||
<div className={styles.horizontalDividerContainer}>
|
||||
<div className={styles.horizontalDivider} />
|
||||
</div>
|
||||
</>
|
||||
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
<MenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onClick={handleFavorite}
|
||||
icon={
|
||||
favorite ? (
|
||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuThemeModeSwitch />
|
||||
<LanguageMenu />
|
||||
</div>
|
||||
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={handleSwitchMode}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
<MoveToTrash
|
||||
data-testid="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -145,11 +139,7 @@ const PageMenu = () => {
|
||||
<MoveToTrash.ConfirmModal
|
||||
open={openConfirm}
|
||||
title={pageMeta.title}
|
||||
onConfirm={() => {
|
||||
removeToTrash(pageMeta.id);
|
||||
toast(t['Moved to Trash']());
|
||||
setOpenConfirm(false);
|
||||
}}
|
||||
onConfirm={handleOnConfirm}
|
||||
onCancel={() => {
|
||||
setOpenConfirm(false);
|
||||
}}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ import { SidebarSwitch } from '@affine/component/app-sidebar/sidebar-header';
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { headerItemsAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { headerItemsAtom } from '@toeverything/infra/atom';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||
import { currentModeAtom } from '../../../atoms/mode';
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import DownloadClientTip from './download-tips';
|
||||
@@ -37,8 +39,6 @@ export type BaseHeaderProps<
|
||||
|
||||
export enum HeaderRightItemName {
|
||||
EditorOptionMenu = 'editorOptionMenu',
|
||||
// some windows only items
|
||||
WindowsAppControls = 'windowsAppControls',
|
||||
}
|
||||
|
||||
type HeaderItem = {
|
||||
@@ -57,62 +57,59 @@ const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
||||
[HeaderRightItemName.EditorOptionMenu]: {
|
||||
Component: EditorOptionMenu,
|
||||
availableWhen: (_, currentPage, { isPublic }) => {
|
||||
return !isPublic && currentPage?.meta.trash !== true;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.WindowsAppControls]: {
|
||||
Component: () => {
|
||||
const handleMinimizeApp = useCallback(() => {
|
||||
window.apis?.ui.handleMinimizeApp().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
const handleMaximizeApp = useCallback(() => {
|
||||
window.apis?.ui.handleMaximizeApp().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
const handleCloseApp = useCallback(() => {
|
||||
window.apis?.ui.handleCloseApp().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
data-platform-target="win32"
|
||||
className={styles.windowAppControlsWrapper}
|
||||
>
|
||||
<button
|
||||
data-type="minimize"
|
||||
className={styles.windowAppControl}
|
||||
onClick={handleMinimizeApp}
|
||||
>
|
||||
<MinusIcon />
|
||||
</button>
|
||||
<button
|
||||
data-type="maximize"
|
||||
className={styles.windowAppControl}
|
||||
onClick={handleMaximizeApp}
|
||||
>
|
||||
<RoundedRectangleIcon />
|
||||
</button>
|
||||
<button
|
||||
data-type="close"
|
||||
className={styles.windowAppControl}
|
||||
onClick={handleCloseApp}
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
!isPublic && currentPage?.meta.trash !== true && currentPage !== null
|
||||
);
|
||||
},
|
||||
availableWhen: () => {
|
||||
return isDesktop && globalThis.platform === 'win32';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export type HeaderProps = BaseHeaderProps;
|
||||
const WindowsAppControls = () => {
|
||||
const handleMinimizeApp = useCallback(() => {
|
||||
window.apis?.ui.handleMinimizeApp().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
const handleMaximizeApp = useCallback(() => {
|
||||
window.apis?.ui.handleMaximizeApp().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
const handleCloseApp = useCallback(() => {
|
||||
window.apis?.ui.handleCloseApp().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
data-platform-target="win32"
|
||||
className={styles.windowAppControlsWrapper}
|
||||
>
|
||||
<button
|
||||
data-type="minimize"
|
||||
className={styles.windowAppControl}
|
||||
onClick={handleMinimizeApp}
|
||||
>
|
||||
<MinusIcon />
|
||||
</button>
|
||||
<button
|
||||
data-type="maximize"
|
||||
className={styles.windowAppControl}
|
||||
onClick={handleMaximizeApp}
|
||||
>
|
||||
<RoundedRectangleIcon />
|
||||
</button>
|
||||
<button
|
||||
data-type="close"
|
||||
className={styles.windowAppControl}
|
||||
onClick={handleCloseApp}
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PluginHeader = () => {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
@@ -152,10 +149,9 @@ export const Header = forwardRef<
|
||||
PropsWithChildren<HeaderProps> & HTMLAttributes<HTMLDivElement>
|
||||
>((props, ref) => {
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
const [showDownloadTip, setShowDownloadTip] = useState(true);
|
||||
// const [shouldShowGuideDownloadClientTip] = useAtom(
|
||||
// guideDownloadClientTipAtom
|
||||
// );
|
||||
const [showDownloadTip, setShowDownloadTip] = useAtom(
|
||||
guideDownloadClientTipAtom
|
||||
);
|
||||
useEffect(() => {
|
||||
setShowWarning(shouldShowWarning());
|
||||
}, []);
|
||||
@@ -163,7 +159,7 @@ export const Header = forwardRef<
|
||||
const appSidebarFloating = useAtomValue(appSidebarFloatingAtom);
|
||||
|
||||
const mode = useAtomValue(currentModeAtom);
|
||||
|
||||
const isWindowsDesktop = globalThis.platform === 'win32' && isDesktop;
|
||||
return (
|
||||
<div
|
||||
className={styles.headerContainer}
|
||||
@@ -175,7 +171,10 @@ export const Header = forwardRef<
|
||||
{showDownloadTip ? (
|
||||
<DownloadClientTip
|
||||
show={showDownloadTip}
|
||||
onClose={() => setShowDownloadTip(false)}
|
||||
onClose={() => {
|
||||
setShowDownloadTip(false);
|
||||
localStorage.setItem('affine-is-dt-hide', '1');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<BrowserWarning
|
||||
@@ -191,14 +190,25 @@ export const Header = forwardRef<
|
||||
data-has-warning={showWarning}
|
||||
data-testid="editor-header-items"
|
||||
data-is-edgeless={mode === 'edgeless'}
|
||||
data-is-page-list={props.currentPage === null}
|
||||
>
|
||||
<div className={styles.headerLeftSide}>
|
||||
{!open && <SidebarSwitch />}
|
||||
{props.leftSlot}
|
||||
<div>{!open && <SidebarSwitch />}</div>
|
||||
<div
|
||||
className={clsx(styles.headerLeftSideItem, {
|
||||
[styles.headerLeftSideOpen]: open,
|
||||
})}
|
||||
>
|
||||
{props.leftSlot}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{props.children}
|
||||
<div className={styles.headerRightSide}>
|
||||
<div
|
||||
className={clsx(styles.headerRightSide, {
|
||||
[styles.headerRightSideWindow]: isWindowsDesktop,
|
||||
})}
|
||||
>
|
||||
<PluginHeader />
|
||||
{useMemo(() => {
|
||||
return Object.entries(HeaderRightItems).map(
|
||||
@@ -222,6 +232,7 @@ export const Header = forwardRef<
|
||||
);
|
||||
}, [props])}
|
||||
</div>
|
||||
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -65,7 +65,7 @@ export const BlockSuiteEditorHeader: FC<
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.pageTitle}>
|
||||
{isEditable ? (
|
||||
<div>
|
||||
<input
|
||||
@@ -88,11 +88,7 @@ export const BlockSuiteEditorHeader: FC<
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span
|
||||
data-testid="title-edit-button"
|
||||
onClick={handleClick}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<span data-testid="title-edit-button" onClick={handleClick}>
|
||||
{title || 'Untitled'}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ComplexStyleRule } from '@vanilla-extract/css';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
import { createContainer, style } from '@vanilla-extract/css';
|
||||
|
||||
export const headerVanillaContainer = createContainer();
|
||||
|
||||
export const headerContainer = style({
|
||||
height: 'auto',
|
||||
@@ -27,35 +29,45 @@ export const headerContainer = style({
|
||||
} as ComplexStyleRule);
|
||||
|
||||
export const header = style({
|
||||
containerName: headerVanillaContainer,
|
||||
containerType: 'inline-size',
|
||||
flexShrink: 0,
|
||||
height: '52px',
|
||||
minHeight: '52px',
|
||||
width: '100%',
|
||||
padding: '0 20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: '8px 20px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto 1fr',
|
||||
alignItems: 'center',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
zIndex: 99,
|
||||
position: 'relative',
|
||||
selectors: {
|
||||
'&[data-is-edgeless="true"]': {
|
||||
'&[data-is-page-list="true"], &[data-is-edgeless="true"]': {
|
||||
borderBottom: `1px solid var(--affine-border-color)`,
|
||||
},
|
||||
},
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
alignItems: 'start',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const titleContainer = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 'auto',
|
||||
position: 'absolute',
|
||||
inset: 'auto auto auto 50%',
|
||||
transform: 'translate(-50%, 0px)',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
alignContent: 'unset',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
['WebkitAppRegion' as string]: 'no-drag',
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
alignItems: 'start',
|
||||
paddingTop: '2px',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const title = style({
|
||||
@@ -75,9 +87,26 @@ export const title = style({
|
||||
},
|
||||
},
|
||||
} as ComplexStyleRule);
|
||||
|
||||
export const pageTitle = style({
|
||||
maxWidth: '600px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
transition: 'width .15s',
|
||||
cursor: 'pointer',
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 1920px)`]: {
|
||||
maxWidth: '800px',
|
||||
},
|
||||
[`${headerVanillaContainer} (max-width: 1300px)`]: {
|
||||
maxWidth: '400px',
|
||||
},
|
||||
[`${headerVanillaContainer} (max-width: 768px)`]: {
|
||||
maxWidth: '220px',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const titleWrapper = style({
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -86,10 +115,28 @@ export const titleWrapper = style({
|
||||
export const headerLeftSide = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '150px',
|
||||
'@media': {
|
||||
'(max-width: 900px)': {
|
||||
width: 'auto',
|
||||
transition: 'all .15s',
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
height: '68px',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const headerLeftSideItem = style({
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
bottom: '8px',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const headerLeftSideOpen = style({
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
marginLeft: '20px',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -99,7 +146,21 @@ export const headerRightSide = style({
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
zIndex: 1,
|
||||
marginLeft: '20px',
|
||||
justifyContent: 'flex-end',
|
||||
transition: 'all .15s',
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
position: 'absolute',
|
||||
height: 'auto',
|
||||
right: '0',
|
||||
bottom: '8px',
|
||||
marginRight: '18px',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const headerRightSideWindow = style({
|
||||
marginRight: '140px',
|
||||
});
|
||||
|
||||
export const browserWarning = style({
|
||||
@@ -131,22 +192,12 @@ export const closeButton = style({
|
||||
});
|
||||
|
||||
export const switchWrapper = style({
|
||||
position: 'absolute',
|
||||
right: '100%',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const searchArrowWrapper = style({
|
||||
position: 'absolute',
|
||||
left: 'calc(100% + 4px)',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
@@ -164,16 +215,13 @@ export const allPageListTitleWrapper = style({
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'::after': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
background: 'var(--affine-border-color)',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
margin: '0 1px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
alignItems: 'flex-start',
|
||||
marginTop: '8px',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const pageListTitleIcon = style({
|
||||
@@ -220,30 +268,36 @@ export const windowAppControlsWrapper = style({
|
||||
gap: '2px',
|
||||
transform: 'translateX(8px)',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
right: '14px',
|
||||
});
|
||||
|
||||
export const windowAppControl = style({
|
||||
WebkitAppRegion: 'no-drag',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
width: '42px',
|
||||
height: 'calc(100% - 10px)',
|
||||
paddingTop: '10px',
|
||||
width: '51px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '0',
|
||||
selectors: {
|
||||
'&[data-type="close"]': {
|
||||
width: '56px',
|
||||
paddingRight: '14px',
|
||||
marginRight: '-14px',
|
||||
paddingRight: '5px',
|
||||
marginRight: '-12px',
|
||||
},
|
||||
'&[data-type="close"]:hover': {
|
||||
background: 'var(--affine-error-color)',
|
||||
color: '#FFFFFF',
|
||||
background: 'var(--affine-windows-close-button)',
|
||||
color: 'var(--affine-pure-white)',
|
||||
},
|
||||
'&:hover': {
|
||||
background: 'var(--affine-background-tertiary-color)',
|
||||
background: 'var(--affine-hover-color)',
|
||||
},
|
||||
},
|
||||
'@container': {
|
||||
[`${headerVanillaContainer} (max-width: 900px)`]: {
|
||||
height: '50px',
|
||||
paddingTop: '0',
|
||||
},
|
||||
},
|
||||
} as ComplexStyleRule);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import './page-detail-editor.css';
|
||||
|
||||
import { PageNotFoundError } from '@affine/env/constant';
|
||||
import type { CallbackMap, LayoutNode } from '@affine/sdk//entry';
|
||||
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
@@ -12,9 +13,7 @@ import {
|
||||
editorItemsAtom,
|
||||
rootStore,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import type { CallbackMap } from '@toeverything/plugin-infra/entry';
|
||||
import type { LayoutNode } from '@toeverything/plugin-infra/type';
|
||||
} from '@toeverything/infra/atom';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { CSSProperties, FC, ReactElement } from 'react';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { WorkspaceRegistry } from '@affine/env/workspace';
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
|
||||
@@ -25,12 +25,12 @@ export function useTransformWorkspace() {
|
||||
workspace.blockSuiteWorkspace
|
||||
);
|
||||
await WorkspaceAdapters[from].CRUD.delete(workspace as any);
|
||||
await set(workspaces => {
|
||||
set(workspaces => {
|
||||
const idx = workspaces.findIndex(ws => ws.id === workspace.id);
|
||||
workspaces.splice(idx, 1, {
|
||||
id: newId,
|
||||
flavour: to,
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
});
|
||||
return [...workspaces];
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import type { Atom } from 'jotai';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { assertEquals } from '@blocksuite/global/utils';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { getWorkspace } from '@toeverything/plugin-infra/__internal__/workspace';
|
||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -27,12 +27,12 @@ export function useAppHelper() {
|
||||
async (workspaceId: string): Promise<string> => {
|
||||
getOrCreateWorkspace(workspaceId, WorkspaceFlavour.LOCAL);
|
||||
saveWorkspaceToLocalStorage(workspaceId);
|
||||
await set(workspaces => [
|
||||
set(workspaces => [
|
||||
...workspaces,
|
||||
{
|
||||
id: workspaceId,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
},
|
||||
]);
|
||||
logger.debug('imported local workspace', workspaceId);
|
||||
@@ -72,12 +72,12 @@ export function useAppHelper() {
|
||||
jumpOnce: true,
|
||||
});
|
||||
}
|
||||
await set(workspaces => [
|
||||
set(workspaces => [
|
||||
...workspaces,
|
||||
{
|
||||
id,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
},
|
||||
]);
|
||||
logger.debug('created local workspace', id);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { StrictMode } from 'react';
|
||||
import { StrictMode, Suspense } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
async function main() {
|
||||
await import('./bootstrap/before-app');
|
||||
const { setup } = await import('./bootstrap/setup');
|
||||
await setup();
|
||||
const { App } = await import('./app');
|
||||
const root = document.getElementById('app');
|
||||
assertExists(root);
|
||||
|
||||
createRoot(root).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
<Suspense fallback={<WorkspaceFallback key="AppLoading" />}>
|
||||
<App />
|
||||
</Suspense>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import { currentWorkspaceIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
||||
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { FC, PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, Suspense, useCallback, useMemo } from 'react';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button, displayFlex, styled } from '@affine/component';
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { getWorkspace } from '@toeverything/plugin-infra/__internal__/workspace';
|
||||
import { rootStore } from '@toeverything/plugin-infra/atom';
|
||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
import { lazy } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useCollectionManager } from '@affine/component/page-list';
|
||||
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/plugin-infra/__internal__/workspace';
|
||||
import { currentPageIdAtom, rootStore } from '@toeverything/plugin-infra/atom';
|
||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||
import { currentPageIdAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import { useAtom } from 'jotai/react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { currentPageIdAtom, rootStore } from '@toeverything/plugin-infra/atom';
|
||||
import { currentPageIdAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useAtom } from 'jotai/react';
|
||||
import { type ReactElement, useCallback, useEffect } from 'react';
|
||||
@@ -27,7 +27,7 @@ const DetailPageImpl = (): ReactElement => {
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const collectionManager = useCollectionManager(currentWorkspace.id);
|
||||
const onLoad = useCallback(
|
||||
(page: Page, editor: EditorContainer) => {
|
||||
(_: Page, editor: EditorContainer) => {
|
||||
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
||||
return openPage(blockSuiteWorkspace.id, pageId);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import { currentWorkspaceIdAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import type { ReactElement } from 'react';
|
||||
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
|
||||
|
||||
|
||||
19
apps/core/src/polyfill/ses.ts
Normal file
19
apps/core/src/polyfill/ses.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'ses';
|
||||
|
||||
if (!process.env.COVERAGE) {
|
||||
lockdown({
|
||||
evalTaming: 'unsafeEval',
|
||||
overrideTaming: 'severe',
|
||||
consoleTaming: 'unsafe',
|
||||
errorTaming: 'unsafe',
|
||||
errorTrapping: 'platform',
|
||||
unhandledRejectionTrapping: 'report',
|
||||
});
|
||||
|
||||
console.log('SES lockdown complete');
|
||||
} else {
|
||||
Object.defineProperty(globalThis, 'harden', {
|
||||
value: (x: any) => Object.freeze(x),
|
||||
writable: false,
|
||||
});
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { arrayMove } from '@dnd-kit/sortable';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import { lazy, Suspense, useCallback, useTransition } from 'react';
|
||||
@@ -124,7 +124,7 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
currentWorkspaceIdAtom
|
||||
);
|
||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||
const [transitioning, transition] = useTransition();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
||||
|
||||
const handleOpenSettingModal = useCallback(
|
||||
@@ -143,7 +143,7 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
<>
|
||||
<Suspense>
|
||||
<WorkspaceListModal
|
||||
disabled={transitioning}
|
||||
disabled={isPending}
|
||||
workspaces={workspaces}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
open={
|
||||
@@ -157,10 +157,10 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
(activeId, overId) => {
|
||||
const oldIndex = workspaces.findIndex(w => w.id === activeId);
|
||||
const newIndex = workspaces.findIndex(w => w.id === overId);
|
||||
transition(() => {
|
||||
startTransition(() => {
|
||||
setWorkspaces(workspaces =>
|
||||
arrayMove(workspaces, oldIndex, newIndex)
|
||||
).catch(console.error);
|
||||
);
|
||||
});
|
||||
},
|
||||
[setWorkspaces, workspaces]
|
||||
@@ -195,11 +195,13 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
setOpenCreateWorkspaceModal(false);
|
||||
}, [setOpenCreateWorkspaceModal])}
|
||||
onCreate={useCallback(
|
||||
async id => {
|
||||
setOpenCreateWorkspaceModal(false);
|
||||
setOpenWorkspacesModal(false);
|
||||
setCurrentWorkspaceId(id);
|
||||
return jumpToSubPath(id, WorkspaceSubPath.ALL);
|
||||
id => {
|
||||
startTransition(() => {
|
||||
setOpenCreateWorkspaceModal(false);
|
||||
setOpenWorkspacesModal(false);
|
||||
setCurrentWorkspaceId(id);
|
||||
jumpToSubPath(id, WorkspaceSubPath.ALL);
|
||||
});
|
||||
},
|
||||
[
|
||||
jumpToSubPath,
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "@emotion/react",
|
||||
"incremental": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": ["webpack-env"]
|
||||
"outDir": "lib",
|
||||
"typeRoots": ["../../node_modules", "../../node_modules/@types"],
|
||||
"types": ["webpack-env", "ses", "affine__env"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/docs",
|
||||
"version": "0.8.0-canary.2",
|
||||
"version": "0.8.0-canary.11",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,18 +10,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"express": "^4.18.2",
|
||||
"jotai": "^2.2.2",
|
||||
"react": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"react-dom": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"react-server-dom-webpack": "18.3.0-canary-1fdacbefd-20230630",
|
||||
"waku": "0.12.1"
|
||||
"waku": "0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.17",
|
||||
|
||||
@@ -4,7 +4,7 @@ export default defineRouter(
|
||||
async id => {
|
||||
switch (id) {
|
||||
case 'index': {
|
||||
const { default: AppCreator } = await import('./src/app.js');
|
||||
const { default: AppCreator } = await import('./app.js');
|
||||
return AppCreator(id);
|
||||
}
|
||||
default:
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<!--/placeholder1-->
|
||||
<script src="./src/index.tsx" defer type="module"></script>
|
||||
<script src="./index.tsx" defer type="module"></script>
|
||||
<!--placeholder2-->
|
||||
<!--/placeholder2-->
|
||||
</body>
|
||||
Binary file not shown.
@@ -10,7 +10,8 @@
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react"
|
||||
},
|
||||
"include": ["src", "entries.ts"],
|
||||
"references": [
|
||||
|
||||
@@ -27,8 +27,8 @@ if (platform() === 'darwin') {
|
||||
delay: 100,
|
||||
});
|
||||
await page.waitForSelector('v-line');
|
||||
await page.focus('.affine-default-page-block-title');
|
||||
await page.type('.affine-default-page-block-title', 'test1', {
|
||||
await page.focus('.affine-doc-page-block-title');
|
||||
await page.type('.affine-doc-page-block-title', 'test1', {
|
||||
delay: 100,
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
@@ -36,8 +36,8 @@ if (platform() === 'darwin') {
|
||||
delay: 100,
|
||||
});
|
||||
await page.waitForSelector('v-line');
|
||||
await page.focus('.affine-default-page-block-title');
|
||||
await page.type('.affine-default-page-block-title', 'test2', {
|
||||
await page.focus('.affine-doc-page-block-title');
|
||||
await page.type('.affine-doc-page-block-title', 'test2', {
|
||||
delay: 100,
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
@@ -45,14 +45,14 @@ if (platform() === 'darwin') {
|
||||
delay: 100,
|
||||
});
|
||||
await page.waitForSelector('v-line');
|
||||
await page.focus('.affine-default-page-block-title');
|
||||
await page.type('.affine-default-page-block-title', 'test3', {
|
||||
await page.focus('.affine-doc-page-block-title');
|
||||
await page.type('.affine-doc-page-block-title', 'test3', {
|
||||
delay: 100,
|
||||
});
|
||||
}
|
||||
{
|
||||
const title = (await page
|
||||
.locator('.affine-default-page-block-title')
|
||||
.locator('.affine-doc-page-block-title')
|
||||
.textContent()) as string;
|
||||
expect(title.trim()).toBe('test3');
|
||||
}
|
||||
@@ -63,7 +63,7 @@ if (platform() === 'darwin') {
|
||||
await page.waitForTimeout(1000);
|
||||
{
|
||||
const title = (await page
|
||||
.locator('.affine-default-page-block-title')
|
||||
.locator('.affine-doc-page-block-title')
|
||||
.textContent()) as string;
|
||||
expect(title.trim()).toBe('test1');
|
||||
}
|
||||
@@ -73,7 +73,7 @@ if (platform() === 'darwin') {
|
||||
await page.waitForTimeout(1000);
|
||||
{
|
||||
const title = (await page
|
||||
.locator('.affine-default-page-block-title')
|
||||
.locator('.affine-doc-page-block-title')
|
||||
.textContent()) as string;
|
||||
expect(title.trim()).toBe('test3');
|
||||
}
|
||||
@@ -96,9 +96,10 @@ test('app theme', async ({ page, electronApp }) => {
|
||||
}
|
||||
|
||||
{
|
||||
await page.getByTestId('editor-option-menu').click();
|
||||
await page.getByTestId('change-theme-dark').click();
|
||||
await page.getByTestId('settings-modal-trigger').click();
|
||||
await page.getByTestId('appearance-panel-trigger').click();
|
||||
await page.waitForTimeout(50);
|
||||
await page.getByTestId('dark-theme-trigger').click();
|
||||
const themeMode = await root.evaluate(element =>
|
||||
element.getAttribute('data-theme')
|
||||
);
|
||||
|
||||
@@ -29,7 +29,6 @@ test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
|
||||
|
||||
// move db file to tmp folder
|
||||
await page.evaluate(tmpPath => {
|
||||
// @ts-expect-error
|
||||
window.apis?.dialog.setFakeDialogResult({
|
||||
filePath: tmpPath,
|
||||
});
|
||||
@@ -47,8 +46,8 @@ test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
|
||||
test('export then add', async ({ page, appInfo, workspace }) => {
|
||||
const w = await workspace.current();
|
||||
|
||||
await page.focus('.affine-default-page-block-title');
|
||||
await page.fill('.affine-default-page-block-title', 'test1');
|
||||
await page.focus('.affine-doc-page-block-title');
|
||||
await page.fill('.affine-doc-page-block-title', 'test1');
|
||||
|
||||
await page.getByTestId('slider-bar-workspace-setting-button').click();
|
||||
await expect(page.getByTestId('setting-modal')).toBeVisible();
|
||||
@@ -60,19 +59,18 @@ test('export then add', async ({ page, appInfo, workspace }) => {
|
||||
// goto workspace setting
|
||||
await page.getByTestId('workspace-list-item').click();
|
||||
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// change workspace name
|
||||
await page.getByTestId('workspace-name-input').fill(newWorkspaceName);
|
||||
await page.getByTestId('save-workspace-name').click();
|
||||
await page.waitForSelector('text="Update workspace name success"');
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const tmpPath = path.join(appInfo.sessionData, w.id + '-tmp.db');
|
||||
|
||||
// export db file to tmp folder
|
||||
await page.evaluate(tmpPath => {
|
||||
// @ts-expect-error
|
||||
window.apis?.dialog.setFakeDialogResult({
|
||||
filePath: tmpPath,
|
||||
});
|
||||
@@ -92,7 +90,6 @@ test('export then add', async ({ page, appInfo, workspace }) => {
|
||||
await page.getByTestId('add-or-new-workspace').click();
|
||||
|
||||
await page.evaluate(tmpPath => {
|
||||
// @ts-expect-error
|
||||
window.apis?.dialog.setFakeDialogResult({
|
||||
filePath: tmpPath,
|
||||
});
|
||||
@@ -106,7 +103,7 @@ test('export then add', async ({ page, appInfo, workspace }) => {
|
||||
await page.getByTestId('create-workspace-continue-button').click();
|
||||
|
||||
// sleep for a while to wait for the workspace to be added :D
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(2000);
|
||||
const newWorkspace = await workspace.current();
|
||||
expect(newWorkspace.id).not.toBe(originalId);
|
||||
// check its name is correct
|
||||
|
||||
@@ -26,8 +26,82 @@ const arch =
|
||||
? process.argv[process.argv.indexOf('--arch') + 1]
|
||||
: process.arch;
|
||||
|
||||
const platform =
|
||||
process.argv.indexOf('--platform') > 0
|
||||
? process.argv[process.argv.indexOf('--platform') + 1]
|
||||
: process.platform;
|
||||
|
||||
const windowsIconUrl = `https://cdn.affine.pro/app-icons/icon_${buildType}.ico`;
|
||||
|
||||
const makers = [
|
||||
!process.env.SKIP_BUNDLE &&
|
||||
platform === 'darwin' && {
|
||||
name: '@electron-forge/maker-dmg',
|
||||
config: {
|
||||
format: 'ULFO',
|
||||
icon: icnsPath,
|
||||
name: 'AFFiNE',
|
||||
'icon-size': 128,
|
||||
background: path.resolve(
|
||||
__dirname,
|
||||
'./resources/icons/dmg-background.png'
|
||||
),
|
||||
contents: [
|
||||
{
|
||||
x: 176,
|
||||
y: 192,
|
||||
type: 'file',
|
||||
path: path.resolve(
|
||||
__dirname,
|
||||
'out',
|
||||
buildType,
|
||||
`${productName}-darwin-${arch}`,
|
||||
`${productName}.app`
|
||||
),
|
||||
},
|
||||
{ x: 432, y: 192, type: 'link', path: '/Applications' },
|
||||
],
|
||||
file: path.resolve(
|
||||
__dirname,
|
||||
'out',
|
||||
buildType,
|
||||
`${productName}-darwin-${arch}`,
|
||||
`${productName}.app`
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
config: {
|
||||
name: 'affine',
|
||||
iconUrl: icoPath,
|
||||
setupIcon: icoPath,
|
||||
platforms: ['darwin', 'linux', 'win32'],
|
||||
},
|
||||
},
|
||||
!process.env.SKIP_BUNDLE && {
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
config: {
|
||||
name: 'AFFiNE',
|
||||
setupIcon: icoPath,
|
||||
iconUrl: windowsIconUrl,
|
||||
loadingGif: './resources/icons/affine_installing.gif',
|
||||
},
|
||||
},
|
||||
!process.env.SKIP_BUNDLE && {
|
||||
name: '@reforged/maker-appimage',
|
||||
config: {
|
||||
name: 'AFFiNE',
|
||||
iconUrl: icoPath,
|
||||
setupIcon: icoPath,
|
||||
platforms: ['linux'],
|
||||
options: {
|
||||
bin: productName,
|
||||
},
|
||||
},
|
||||
},
|
||||
].filter(Boolean);
|
||||
|
||||
/**
|
||||
* @type {import('@electron-forge/shared-types').ForgeConfig}
|
||||
*/
|
||||
@@ -57,63 +131,7 @@ module.exports = {
|
||||
// We need the following line for updater
|
||||
extraResource: ['./resources/app-update.yml'],
|
||||
},
|
||||
makers: [
|
||||
{
|
||||
name: '@electron-forge/maker-dmg',
|
||||
config: {
|
||||
format: 'ULFO',
|
||||
icon: icnsPath,
|
||||
name: 'AFFiNE',
|
||||
'icon-size': 128,
|
||||
background: './resources/icons/dmg-background.png',
|
||||
contents: [
|
||||
{
|
||||
x: 176,
|
||||
y: 192,
|
||||
type: 'file',
|
||||
path: path.resolve(
|
||||
__dirname,
|
||||
'out',
|
||||
buildType,
|
||||
`${productName}-darwin-${arch}`,
|
||||
`${productName}.app`
|
||||
),
|
||||
},
|
||||
{ x: 432, y: 192, type: 'link', path: '/Applications' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
config: {
|
||||
name: 'affine',
|
||||
iconUrl: icoPath,
|
||||
setupIcon: icoPath,
|
||||
platforms: ['darwin', 'linux', 'win32'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
config: {
|
||||
name: 'AFFiNE',
|
||||
setupIcon: icoPath,
|
||||
iconUrl: windowsIconUrl,
|
||||
loadingGif: './resources/icons/affine_installing.gif',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '@reforged/maker-appimage',
|
||||
config: {
|
||||
name: 'AFFiNE',
|
||||
iconUrl: icoPath,
|
||||
setupIcon: icoPath,
|
||||
platforms: ['linux'],
|
||||
options: {
|
||||
bin: productName,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
makers,
|
||||
hooks: {
|
||||
readPackageJson: async (_, packageJson) => {
|
||||
// we want different package name for canary build
|
||||
@@ -124,17 +142,28 @@ module.exports = {
|
||||
const { rm, cp } = require('node:fs/promises');
|
||||
const { resolve } = require('node:path');
|
||||
|
||||
await rm(
|
||||
resolve(__dirname, './node_modules/@toeverything/plugin-infra'),
|
||||
await rm(resolve(__dirname, './node_modules/@toeverything/infra'), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
await cp(
|
||||
resolve(__dirname, '../../packages/infra'),
|
||||
resolve(__dirname, './node_modules/@toeverything/infra'),
|
||||
{
|
||||
recursive: true,
|
||||
force: true,
|
||||
}
|
||||
);
|
||||
|
||||
await rm(resolve(__dirname, './node_modules/@affine/sdk'), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
await cp(
|
||||
resolve(__dirname, '../../packages/plugin-infra'),
|
||||
resolve(__dirname, './node_modules/@toeverything/plugin-infra'),
|
||||
resolve(__dirname, '../../packages/sdk'),
|
||||
resolve(__dirname, './node_modules/@affine/sdk'),
|
||||
{
|
||||
recursive: true,
|
||||
force: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.2",
|
||||
"version": "0.8.0-canary.11",
|
||||
"author": "affine",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -10,6 +10,7 @@
|
||||
"description": "AFFiNE App",
|
||||
"homepage": "https://github.com/toeverything/AFFiNE",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dev": "yarn cross-env DEV_SERVER_URL=http://localhost:8080 node scripts/dev.mjs",
|
||||
"dev:prod": "yarn node scripts/dev.mjs",
|
||||
"build": "NODE_ENV=production zx scripts/build-layers.mjs",
|
||||
@@ -26,21 +27,20 @@
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@electron-forge/cli": "^6.2.1",
|
||||
"@electron-forge/core": "^6.2.1",
|
||||
"@electron-forge/core-utils": "^6.2.1",
|
||||
"@electron-forge/maker-deb": "^6.2.1",
|
||||
"@electron-forge/maker-dmg": "^6.2.1",
|
||||
"@electron-forge/maker-squirrel": "^6.2.1",
|
||||
"@electron-forge/maker-zip": "^6.2.1",
|
||||
"@electron-forge/shared-types": "^6.2.1",
|
||||
"@electron/remote": "2.0.10",
|
||||
"@reforged/maker-appimage": "^3.3.1",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"cross-env": "7.0.3",
|
||||
@@ -59,7 +59,7 @@
|
||||
"zx": "^7.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@toeverything/plugin-infra": "workspace:*",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"electron-updater": "^6.0.0",
|
||||
"link-preview-js": "^3.0.4",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"executor": "nx:run-script",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": ["@affine/bookmark-block"],
|
||||
"projects": ["tag:plugin"],
|
||||
"target": "build",
|
||||
"params": "ignore"
|
||||
},
|
||||
|
||||
@@ -3,10 +3,11 @@ import { readdir } from 'node:fs/promises';
|
||||
|
||||
const outputRoot = fileURLToPath(
|
||||
new URL(
|
||||
'../zip-out/AFFiNE-canary.app/Contents/Resources/app',
|
||||
'../out/canary/AFFiNE-canary-darwin-arm64/AFFiNE-canary.app/Contents/Resources/app',
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
const outputList = [
|
||||
[
|
||||
'dist',
|
||||
@@ -19,7 +20,7 @@ const outputList = [
|
||||
],
|
||||
],
|
||||
['dist/plugins', ['bookmark']],
|
||||
['dist/plugins/bookmark', ['index.js']],
|
||||
['dist/plugins/bookmark', ['index.cjs']],
|
||||
] as [entry: string, expected: string[]][];
|
||||
|
||||
await Promise.all(
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set the directory
|
||||
dir="./out/canary/make/zip/darwin/arm64"
|
||||
|
||||
# Get the first file
|
||||
file=$(ls -1 $dir | head -n 1)
|
||||
|
||||
# Check if file exists and is a zip file
|
||||
if [ -f "$dir/$file" ] && [ ${file: -4} == ".zip" ]
|
||||
then
|
||||
# Unzip the file
|
||||
unzip "$dir/$file" -d "zip-out"
|
||||
else
|
||||
echo "No zip file found"
|
||||
fi
|
||||
@@ -115,7 +115,11 @@ function startPollingSecondaryDB(db: WorkspaceSQLiteDB) {
|
||||
const secondaryDB = new SecondaryWorkspaceSQLiteDB(path, db);
|
||||
return new Observable<SecondaryWorkspaceSQLiteDB>(subscriber => {
|
||||
subscriber.next(secondaryDB);
|
||||
return () => secondaryDB.destroy();
|
||||
return () => {
|
||||
secondaryDB.destroy().catch(err => {
|
||||
subscriber.error(err);
|
||||
});
|
||||
};
|
||||
});
|
||||
}),
|
||||
switchMap(secondaryDB => {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import { ValidationResult } from '@affine/native';
|
||||
import type {
|
||||
FakeDialogResult,
|
||||
LoadDBFileResult,
|
||||
MoveDBFileResult,
|
||||
SaveDBFileResult,
|
||||
SelectDBFileLocationResult,
|
||||
} from '@toeverything/infra/type';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
@@ -28,13 +35,6 @@ export async function revealDBFile(workspaceId: string) {
|
||||
await mainRPC.showItemInFolder(meta.secondaryDBPath ?? meta.mainDBPath);
|
||||
}
|
||||
|
||||
// provide a backdoor to set dialog path for testing in playwright
|
||||
export interface FakeDialogResult {
|
||||
canceled?: boolean;
|
||||
filePath?: string;
|
||||
filePaths?: string[];
|
||||
}
|
||||
|
||||
// result will be used in the next call to showOpenDialog
|
||||
// if it is being read once, it will be reset to undefined
|
||||
let fakeDialogResult: FakeDialogResult | undefined = undefined;
|
||||
@@ -53,23 +53,6 @@ export function setFakeDialogResult(result: FakeDialogResult | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
const ErrorMessages = [
|
||||
'DB_FILE_ALREADY_LOADED',
|
||||
'DB_FILE_PATH_INVALID',
|
||||
'DB_FILE_INVALID',
|
||||
'DB_FILE_MIGRATION_FAILED',
|
||||
'FILE_ALREADY_EXISTS',
|
||||
'UNKNOWN_ERROR',
|
||||
] as const;
|
||||
|
||||
type ErrorMessage = (typeof ErrorMessages)[number];
|
||||
|
||||
export interface SaveDBFileResult {
|
||||
filePath?: string;
|
||||
canceled?: boolean;
|
||||
error?: ErrorMessage;
|
||||
}
|
||||
|
||||
const extension = 'affine';
|
||||
|
||||
function getDefaultDBFileName(name: string, id: string) {
|
||||
@@ -125,12 +108,6 @@ export async function saveDBFileAs(
|
||||
}
|
||||
}
|
||||
|
||||
export interface SelectDBFileLocationResult {
|
||||
filePath?: string;
|
||||
error?: ErrorMessage;
|
||||
canceled?: boolean;
|
||||
}
|
||||
|
||||
export async function selectDBFileLocation(): Promise<SelectDBFileLocationResult> {
|
||||
try {
|
||||
const ret =
|
||||
@@ -157,12 +134,6 @@ export async function selectDBFileLocation(): Promise<SelectDBFileLocationResult
|
||||
}
|
||||
}
|
||||
|
||||
export interface LoadDBFileResult {
|
||||
workspaceId?: string;
|
||||
error?: ErrorMessage;
|
||||
canceled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user clicks the "Load" button in the "Load Workspace" dialog.
|
||||
*
|
||||
@@ -255,12 +226,6 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MoveDBFileResult {
|
||||
filePath?: string;
|
||||
error?: ErrorMessage;
|
||||
canceled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user clicks the "Move" button in the "Move Workspace Storage" setting.
|
||||
*
|
||||
|
||||
@@ -38,7 +38,7 @@ app.on('second-instance', () => {
|
||||
);
|
||||
});
|
||||
|
||||
app.on('open-url', async (_, _url) => {
|
||||
app.on('open-url', (_, _url) => {
|
||||
// todo: handle `affine://...` urls
|
||||
});
|
||||
|
||||
@@ -54,7 +54,11 @@ app.on('window-all-closed', () => {
|
||||
/**
|
||||
* @see https://www.electronjs.org/docs/v14-x-y/api/app#event-activate-macos Event: 'activate'
|
||||
*/
|
||||
app.on('activate', restoreOrCreateWindow);
|
||||
app.on('activate', () => {
|
||||
restoreOrCreateWindow().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Create app window when background process will be ready
|
||||
|
||||
@@ -34,7 +34,7 @@ export function getGoogleOauthCode() {
|
||||
logger.error('Failed to open external url', e);
|
||||
reject(e);
|
||||
});
|
||||
const handleOpenUrl = async (_: any, url: string) => {
|
||||
const handleOpenUrl = (_: any, url: string) => {
|
||||
const mainWindow = BrowserWindow.getAllWindows().find(
|
||||
w => !w.isDestroyed()
|
||||
);
|
||||
|
||||
@@ -99,7 +99,9 @@ export const registerUpdater = async () => {
|
||||
});
|
||||
autoUpdater.forceDevUpdateConfig = isDev;
|
||||
|
||||
app.on('activate', async () => {
|
||||
await checkForUpdates(false);
|
||||
app.on('activate', () => {
|
||||
checkForUpdates(false).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { parentPort } from 'node:worker_threads';
|
||||
|
||||
import type { ServerContext } from '@toeverything/plugin-infra/server';
|
||||
import type { ServerContext } from '@affine/sdk/server';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
|
||||
import { MessageEventChannel } from '../shared/utils';
|
||||
@@ -19,16 +19,21 @@ const mainThread = AsyncCall<{
|
||||
channel: new MessageEventChannel(parentPort),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
globalThis.console.log = mainThread.log;
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
globalThis.console.error = mainThread.log;
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
globalThis.console.info = mainThread.log;
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
globalThis.console.debug = mainThread.log;
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
globalThis.console.warn = mainThread.log;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const bookmarkPluginModule = require(join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, './plugins'),
|
||||
'./bookmark/index.js'
|
||||
'./bookmark/index.cjs'
|
||||
));
|
||||
|
||||
const serverContext: ServerContext = {
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
"exclude": ["node_modules", "out", "dist", "**/__tests__/**/*"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/plugin-infra"
|
||||
"path": "../../packages/infra"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/native"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/infra"
|
||||
"path": "../../packages/sdk"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/env"
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.2",
|
||||
"version": "0.8.0-canary.11",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"run-test": "./scripts/run-test.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "nodemon ./src/index.ts",
|
||||
"test": "yarn exec ts-node-esm ./scripts/run-test.ts all",
|
||||
"test:watch": "yarn exec ts-node-esm ./scripts/run-test.ts all --watch",
|
||||
|
||||
@@ -77,7 +77,7 @@ export class WorkspaceResolver {
|
||||
return workspace.permission;
|
||||
}
|
||||
|
||||
const permission = this.permissionProvider.get(workspace.id, user.id);
|
||||
const permission = await this.permissionProvider.get(workspace.id, user.id);
|
||||
|
||||
if (!permission) {
|
||||
throw new ForbiddenException();
|
||||
|
||||
@@ -9,8 +9,10 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
process.on('beforeExit', async () => {
|
||||
await app.close();
|
||||
process.on('beforeExit', () => {
|
||||
app.close().catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default {
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
},
|
||||
async viteFinal(config, { configType }) {
|
||||
async viteFinal(config, _) {
|
||||
return mergeConfig(config, {
|
||||
assetsInclude: ['**/*.md'],
|
||||
plugins: [
|
||||
@@ -50,13 +50,6 @@ export default {
|
||||
coverage: false,
|
||||
}),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'dotenv/config': fileURLToPath(
|
||||
new URL('../../../scripts/vitest/dotenv-config.ts', import.meta.url)
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
} as StorybookConfig;
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
"wait-on": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/block-std": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/icons": "^2.1.29",
|
||||
"@blocksuite/lit": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
@@ -48,5 +48,5 @@
|
||||
"@blocksuite/lit": "*",
|
||||
"@blocksuite/store": "*"
|
||||
},
|
||||
"version": "0.8.0-canary.2"
|
||||
"version": "0.8.0-canary.11"
|
||||
}
|
||||
|
||||
@@ -6,14 +6,19 @@ import { rootBlockHubAtom } from '@affine/workspace/atom';
|
||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { createMemoryStorage, Workspace } from '@blocksuite/store';
|
||||
import { createMemoryStorage, Schema, Workspace } from '@blocksuite/store';
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { use } from 'foxact/use';
|
||||
|
||||
const schema = new Schema();
|
||||
|
||||
schema.register(AffineSchemas).register(__unstableSchemas);
|
||||
|
||||
const blockSuiteWorkspace = new Workspace({
|
||||
id: 'test',
|
||||
blobStorages: [createMemoryStorage],
|
||||
schema,
|
||||
});
|
||||
|
||||
async function initPage(page: Page) {
|
||||
@@ -34,7 +39,6 @@ async function initPage(page: Page) {
|
||||
page.resetHistory();
|
||||
}
|
||||
|
||||
blockSuiteWorkspace.register(AffineSchemas).register(__unstableSchemas);
|
||||
const page = blockSuiteWorkspace.createPage('page0');
|
||||
|
||||
type BlockSuiteMeta = Meta<typeof BlockSuiteEditor>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { WorkspaceAvatarProps } from '@affine/component/workspace-avatar';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import { Workspace } from '@blocksuite/store';
|
||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import { Schema, Workspace } from '@blocksuite/store';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
|
||||
export default {
|
||||
@@ -17,8 +18,13 @@ export default {
|
||||
},
|
||||
} satisfies Meta<WorkspaceAvatarProps>;
|
||||
|
||||
const schema = new Schema();
|
||||
|
||||
schema.register(AffineSchemas).register(__unstableSchemas);
|
||||
|
||||
const basicBlockSuiteWorkspace = new Workspace({
|
||||
id: 'blocksuite-local',
|
||||
schema,
|
||||
});
|
||||
|
||||
basicBlockSuiteWorkspace.meta.setName('Hello World');
|
||||
@@ -33,6 +39,7 @@ Basic.args = {
|
||||
|
||||
const avatarBlockSuiteWorkspace = new Workspace({
|
||||
id: 'blocksuite-local',
|
||||
schema,
|
||||
});
|
||||
|
||||
avatarBlockSuiteWorkspace.meta.setName('Hello World');
|
||||
|
||||
@@ -56,13 +56,19 @@ yarn install
|
||||
### Build Native Dependencies
|
||||
|
||||
Run the following script. It will build the native module at [`/packages/native`](/packages/native) and build Node.js binding using [NAPI.rs](https://napi.rs/).
|
||||
This could take a while if you build it for the first time.
|
||||
This could take a while if you build it for the first time.
|
||||
Note: use `strip` from system instead of `binutils` if you are runinng MacOS. [see problem here](https://github.com/toeverything/AFFiNE/discussions/2840)
|
||||
|
||||
```
|
||||
yarn workspace @affine/native build
|
||||
```
|
||||
|
||||
### Build Plugins
|
||||
|
||||
```
|
||||
yarn run build:plugins
|
||||
```
|
||||
|
||||
## Debugging the Electron App
|
||||
|
||||
You need to run two scripts to run the app in development mode
|
||||
|
||||
2
nx.json
2
nx.json
@@ -36,6 +36,8 @@
|
||||
"{workspaceRoot}/packages/i18n/src/i18n-generated.ts"
|
||||
],
|
||||
"inputs": [
|
||||
"{workspaceRoot}/infra/**/*",
|
||||
"{workspaceRoot}/sdk/**/*",
|
||||
{
|
||||
"runtime": "node -v"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/monorepo",
|
||||
"version": "0.8.0-canary.2",
|
||||
"version": "0.8.0-canary.11",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
@@ -9,6 +9,7 @@
|
||||
"apps/*",
|
||||
"plugins/*",
|
||||
"packages/*",
|
||||
"packages/@types/*",
|
||||
"tests/fixtures",
|
||||
"tests/kit",
|
||||
"tests/affine-legacy/*",
|
||||
@@ -24,8 +25,8 @@
|
||||
"build": "yarn nx build @affine/core",
|
||||
"build:electron": "yarn nx build @affine/electron",
|
||||
"build:storage": "yarn nx run-many -t build -p @affine/storage",
|
||||
"build:infra": "yarn nx run-many -t build -p plugin-infra infra",
|
||||
"build:plugins": "node ./scripts/build-plugins.mjs",
|
||||
"build:infra": "yarn nx run-many -t build -p infra sdk",
|
||||
"build:plugins": "yarn nx run-many -t build --projects=tag:plugin",
|
||||
"build:storybook": "yarn nx build @affine/storybook",
|
||||
"start:web-static": "yarn workspace @affine/core static-server",
|
||||
"start:storybook": "yarn exec serve apps/storybook/storybook-static -l 6006",
|
||||
@@ -58,6 +59,7 @@
|
||||
"devDependencies": {
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@affine/cli": "workspace:*",
|
||||
"@affine/plugin-cli": "workspace:*",
|
||||
"@commitlint/cli": "^17.6.7",
|
||||
"@commitlint/config-conventional": "^17.6.7",
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -69,7 +71,6 @@
|
||||
"@taplo/cli": "^0.5.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@toeverything/plugin-infra": "workspace:*",
|
||||
"@types/eslint": "^8.44.1",
|
||||
"@types/node": "^18.17.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||
|
||||
54
packages/@types/env/__all.d.ts
vendored
Normal file
54
packages/@types/env/__all.d.ts
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { Environment, Platform, RuntimeConfig } from '@affine/env/global';
|
||||
import type {
|
||||
DBHandlerManager,
|
||||
DebugHandlerManager,
|
||||
DialogHandlerManager,
|
||||
ExportHandlerManager,
|
||||
UIHandlerManager,
|
||||
UnwrapManagerHandlerToClientSide,
|
||||
UpdaterHandlerManager,
|
||||
WorkspaceHandlerManager,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
appInfo: {
|
||||
electron: boolean;
|
||||
};
|
||||
apis: {
|
||||
db: UnwrapManagerHandlerToClientSide<DBHandlerManager>;
|
||||
debug: UnwrapManagerHandlerToClientSide<DebugHandlerManager>;
|
||||
dialog: UnwrapManagerHandlerToClientSide<DialogHandlerManager>;
|
||||
export: UnwrapManagerHandlerToClientSide<ExportHandlerManager>;
|
||||
ui: UnwrapManagerHandlerToClientSide<UIHandlerManager>;
|
||||
updater: UnwrapManagerHandlerToClientSide<UpdaterHandlerManager>;
|
||||
workspace: UnwrapManagerHandlerToClientSide<WorkspaceHandlerManager>;
|
||||
};
|
||||
events: any;
|
||||
}
|
||||
|
||||
interface WindowEventMap {
|
||||
'migration-done': CustomEvent;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var process: {
|
||||
env: Record<string, string>;
|
||||
};
|
||||
// eslint-disable-next-line no-var
|
||||
var $migrationDone: boolean;
|
||||
// eslint-disable-next-line no-var
|
||||
var platform: Platform | undefined;
|
||||
// eslint-disable-next-line no-var
|
||||
var environment: Environment;
|
||||
// eslint-disable-next-line no-var
|
||||
var runtimeConfig: RuntimeConfig;
|
||||
// eslint-disable-next-line no-var
|
||||
var $AFFINE_SETUP: boolean | undefined;
|
||||
// eslint-disable-next-line no-var
|
||||
var editorVersion: string | undefined;
|
||||
// eslint-disable-next-line no-var
|
||||
var prefixUrl: string;
|
||||
// eslint-disable-next-line no-var
|
||||
var websocketPrefixUrl: string;
|
||||
}
|
||||
11
packages/@types/env/package.json
vendored
Normal file
11
packages/@types/env/package.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@types/affine__env",
|
||||
"private": true,
|
||||
"types": "./__all.d.ts",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@affine/env": "workspace:*",
|
||||
"@toeverything/infra": "workspace:*"
|
||||
},
|
||||
"version": "0.8.0-canary.11"
|
||||
}
|
||||
@@ -4,8 +4,7 @@
|
||||
"private": true,
|
||||
"bin": {
|
||||
"build-core": "./src/bin/build-core.mjs",
|
||||
"dev-core": "./src/bin/dev-core.mjs",
|
||||
"dev-plugin": "./src/bin/dev-plugin.mjs"
|
||||
"dev-core": "./src/bin/dev-core.mjs"
|
||||
},
|
||||
"exports": {
|
||||
"./config": "./src/config/index.ts"
|
||||
@@ -13,7 +12,6 @@
|
||||
"devDependencies": {
|
||||
"@clack/core": "^0.3.2",
|
||||
"@clack/prompts": "^0.6.3",
|
||||
"@endo/static-module-record": "^0.7.20",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -22,5 +20,5 @@
|
||||
"peerDependencies": {
|
||||
"ts-node": "*"
|
||||
},
|
||||
"version": "0.8.0-canary.2"
|
||||
"version": "0.8.0-canary.11"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../plugin-infra"
|
||||
"path": "../infra"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
"@radix-ui/react-scroll-area": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"@toeverything/hooks": "workspace:*",
|
||||
"@toeverything/plugin-infra": "workspace:*",
|
||||
"@toeverything/theme": "^0.7.9",
|
||||
"@vanilla-extract/dynamic": "^2.0.3",
|
||||
"clsx": "^2.0.0",
|
||||
@@ -52,12 +51,12 @@
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/icons": "^2.1.29",
|
||||
"@blocksuite/lit": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230804190636-37f66904-nightly",
|
||||
"@types/react": "^18.2.17",
|
||||
"@types/react-datepicker": "^4.15.0",
|
||||
"@types/react-dnd": "^3.0.2",
|
||||
@@ -67,5 +66,5 @@
|
||||
"vite": "^4.4.7",
|
||||
"yjs": "^13.6.7"
|
||||
},
|
||||
"version": "0.8.0-canary.2"
|
||||
"version": "0.8.0-canary.11"
|
||||
}
|
||||
|
||||
@@ -17,23 +17,18 @@ export const navWrapperStyle = style({
|
||||
paddingBottom: '8px',
|
||||
backgroundColor: 'transparent',
|
||||
'@media': {
|
||||
[`(max-width: ${floatingMaxWidth}px)`]: {
|
||||
position: 'absolute',
|
||||
width: `calc(${navWidthVar})`,
|
||||
zIndex: 4,
|
||||
backgroundColor: 'var(--affine-background-primary-color)',
|
||||
selectors: {
|
||||
'&[data-open="false"]': {
|
||||
marginLeft: `calc((10vw + ${navWidthVar}) * -1)`,
|
||||
},
|
||||
},
|
||||
},
|
||||
print: {
|
||||
display: 'none',
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
'&[data-is-floating="true"]': {
|
||||
position: 'absolute',
|
||||
width: `calc(${navWidthVar})`,
|
||||
zIndex: 4,
|
||||
backgroundColor: 'var(--affine-background-primary-color)',
|
||||
},
|
||||
'&[data-open="false"]': {
|
||||
marginLeft: `calc(${navWidthVar} * -1)`,
|
||||
},
|
||||
@@ -93,17 +88,15 @@ export const sidebarFloatMaskStyle = style({
|
||||
right: '100%',
|
||||
bottom: 0,
|
||||
background: 'var(--affine-background-modal-color)',
|
||||
'@media': {
|
||||
[`(max-width: ${floatingMaxWidth}px)`]: {
|
||||
selectors: {
|
||||
'&[data-open="true"]': {
|
||||
opacity: 1,
|
||||
pointerEvents: 'auto',
|
||||
right: '0',
|
||||
zIndex: 3,
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
'&[data-open="true"][data-is-floating="true"]': {
|
||||
opacity: 1,
|
||||
pointerEvents: 'auto',
|
||||
right: '0',
|
||||
zIndex: 3,
|
||||
},
|
||||
},
|
||||
'@media': {
|
||||
print: {
|
||||
display: 'none',
|
||||
},
|
||||
|
||||
@@ -59,18 +59,22 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
function onResize() {
|
||||
const { matches } = window.matchMedia(
|
||||
const isFloatingMaxWidth = window.matchMedia(
|
||||
`(max-width: ${floatingMaxWidth}px)`
|
||||
);
|
||||
).matches;
|
||||
const isOverflowWidth = window.matchMedia(
|
||||
`(max-width: ${appSidebarWidth / 0.4}px)`
|
||||
).matches;
|
||||
const isFloating = isFloatingMaxWidth || isOverflowWidth;
|
||||
if (
|
||||
open === undefined &&
|
||||
localStorage.getItem(APP_SIDEBAR_OPEN) === null
|
||||
) {
|
||||
// give the initial value,
|
||||
// so that the sidebar can be closed on mobile by default
|
||||
setOpen(!matches);
|
||||
setOpen(!isFloating);
|
||||
}
|
||||
setAppSidebarFloating(matches && !!open);
|
||||
setAppSidebarFloating(isFloating && !!open);
|
||||
}
|
||||
|
||||
onResize();
|
||||
@@ -78,7 +82,7 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
}, [open, setAppSidebarFloating, setOpen]);
|
||||
}, [appSidebarWidth, open, setAppSidebarFloating, setOpen]);
|
||||
|
||||
// disable animation to avoid UI flash
|
||||
const enableAnimation = useEnableAnimation();
|
||||
@@ -99,6 +103,7 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
|
||||
})}
|
||||
data-open={open}
|
||||
data-is-macos-electron={isMacosDesktop}
|
||||
data-is-floating={appSidebarFloating}
|
||||
data-enable-animation={enableAnimation && !isResizing}
|
||||
>
|
||||
<nav className={navStyle} ref={navRef} data-testid="app-sidebar">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user