milestone: publish alpha version (#637)

- document folder
- full-text search
- blob storage
- basic edgeless support

Co-authored-by: tzhangchi <terry.zhangchi@outlook.com>
Co-authored-by: QiShaoXuan <qishaoxuan777@gmail.com>
Co-authored-by: DiamondThree <diamond.shx@gmail.com>
Co-authored-by: MingLiang Wang <mingliangwang0o0@gmail.com>
Co-authored-by: JimmFly <yangjinfei001@gmail.com>
Co-authored-by: Yifeng Wang <doodlewind@toeverything.info>
Co-authored-by: Himself65 <himself65@outlook.com>
Co-authored-by: lawvs <18554747+lawvs@users.noreply.github.com>
Co-authored-by: Qi <474021214@qq.com>
This commit is contained in:
authored and GitHub committed 2022-12-30 21:40:15 +08:00
1 parent cc790dcbc2
commit 6c2c7dcd48
296 files changed
+16136 -2069

No files matched your search

+441
View File
@@ -0,0 +1,441 @@
{
"projectName": "AFFiNE",
"projectOwner": "toeverything",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 50,
"commit": false,
"commitConvention": "angular",
"contributorsPerLine": 7,
"badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
"contributors": [
{
"login": "doodlewind",
"name": "Yifeng Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/7312949?v=4",
"profile": "https://github.com/doodlewind",
"contributions": [
"code",
"doc"
]
},
{
"login": "darkskygit",
"name": "DarkSky",
"avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4",
"profile": "https://darksky.eu.org/",
"contributions": [
"code",
"doc"
]
},
{
"login": "tzhangchi",
"name": "Chi Zhang",
"avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
"profile": "http://zhangchi.page/",
"contributions": [
"code",
"doc"
]
},
{
"login": "alt1o",
"name": "wang xinglong",
"avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
"profile": "https://github.com/alt1o",
"contributions": [
"code",
"doc"
]
},
{
"login": "DiamondThree",
"name": "DiamondThree",
"avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
"profile": "https://github.com/DiamondThree",
"contributions": [
"code",
"doc"
]
},
{
"login": "lawvs",
"name": "Whitewater",
"avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4",
"profile": "https://lawvs.github.io/profile/",
"contributions": [
"code",
"doc"
]
},
{
"login": "zuoxiaodong0815",
"name": "xiaodong zuo",
"avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
"profile": "https://github.com/zuoxiaodong0815",
"contributions": [
"code",
"doc"
]
},
{
"login": "Himself65",
"name": "Himself65",
"avatar_url": "https://avatars.githubusercontent.com/u/14026360?v=4",
"profile": "https://github.com/Himself65",
"contributions": [
"code",
"doc"
]
},
{
"login": "SaikaSakura",
"name": "MingLIang Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
"profile": "https://github.com/SaikaSakura",
"contributions": [
"code",
"doc"
]
},
{
"login": "QiShaoXuan",
"name": "Qi",
"avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4",
"profile": "https://github.com/QiShaoXuan",
"contributions": [
"code",
"doc"
]
},
{
"login": "linonetwo",
"name": "lin onetwo",
"avatar_url": "https://avatars.githubusercontent.com/u/3746270?v=4",
"profile": "https://onetwo.ren/wiki",
"contributions": [
"code",
"doc"
]
},
{
"login": "colelawrence",
"name": "Cole Lawrence",
"avatar_url": "https://avatars.githubusercontent.com/u/2925395?v=4",
"profile": "https://colelawrence.com/",
"contributions": [
"code",
"doc"
]
},
{
"login": "thorseraq",
"name": "x1a0t",
"avatar_url": "https://avatars.githubusercontent.com/u/20554850?v=4",
"profile": "https://github.com/thorseraq",
"contributions": [
"code",
"doc"
]
},
{
"login": "HeJiachen-PM",
"name": "HeJiachen-PM",
"avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
"profile": "https://github.com/HeJiachen-PM",
"contributions": [
"research",
"doc"
]
},
{
"login": "joebeijing",
"name": "houjoe",
"avatar_url": "https://avatars.githubusercontent.com/u/22443345?v=4",
"profile": "https://www.notion.so/houjoe/Joe-2a85f5be01004cd2b6a5ad26fbb948b1",
"contributions": [
"research",
"doc"
]
},
{
"login": "Yipei-Operation",
"name": "Yipei Wei",
"avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4",
"profile": "https://github.com/Yipei-Operation",
"contributions": [
"doc"
]
},
{
"login": "VelikaHF",
"name": "Velika",
"avatar_url": "https://avatars.githubusercontent.com/u/121547898?v=4",
"profile": "https://github.com/VelikaHF",
"contributions": [
"design"
]
},
{
"login": "Svaney-ssman",
"name": "Svaney",
"avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4",
"profile": "https://github.com/Svaney-ssman",
"contributions": [
"design"
]
},
{
"login": "fanjing22",
"name": "fanjing22",
"avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4",
"profile": "https://github.com/fanjing22",
"contributions": [
"design"
]
},
{
"login": "xell",
"name": "Guozhu Liu",
"avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4",
"profile": "http://xell.me/",
"contributions": [
"design"
]
},
{
"login": "fyZheng07",
"name": "fyZheng07",
"avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4",
"profile": "https://github.com/fyZheng07",
"contributions": [
"eventOrganizing",
"userTesting"
]
},
{
"login": "CJSS",
"name": "CJSS",
"avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4",
"profile": "https://github.com/CJSS",
"contributions": [
"doc"
]
},
{
"login": "JimmFly",
"name": "JimmFly",
"avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4",
"profile": "https://github.com/JimmFly",
"contributions": [
"code"
]
},
{
"login": "mitsuhatu",
"name": "mitsuhatu",
"avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4",
"profile": "https://github.com/mitsuhatu",
"contributions": [
"code",
"doc"
]
},
{
"login": "Austaras",
"name": "Austaras",
"avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4",
"profile": "https://shockwave.me/",
"contributions": [
"code",
"doc"
]
},
{
"login": "uptonking",
"name": "Jin Yao",
"avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
"profile": "https://github.com/uptonking",
"contributions": [
"code",
"doc"
]
},
{
"login": "CarlosZoft",
"name": "Carlos Rafael ",
"avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4",
"profile": "https://github.com/CarlosZoft",
"contributions": [
"code"
]
},
{
"login": "caleboleary",
"name": "Caleb OLeary",
"avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4",
"profile": "https://github.com/caleboleary",
"contributions": [
"code"
]
},
{
"login": "westongraham",
"name": "Weston Graham",
"avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4",
"profile": "https://github.com/westongraham",
"contributions": [
"doc"
]
},
{
"login": "pointmax",
"name": "pointmax",
"avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4",
"profile": "https://github.com/pointmax",
"contributions": [
"doc"
]
},
{
"login": "liby",
"name": "Bryan Lee",
"avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4",
"profile": "https://liby.github.io/notes",
"contributions": [
"code"
]
},
{
"login": "chenmoonmo",
"name": "Simon Li",
"avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4",
"profile": "https://github.com/chenmoonmo",
"contributions": [
"code"
]
},
{
"login": "githbq",
"name": "Bob Hu",
"avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4",
"profile": "https://github.com/githbq",
"contributions": [
"code"
]
},
{
"login": "lucky-chap",
"name": "Quavo",
"avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4",
"profile": "https://quavo.vercel.app/",
"contributions": [
"doc"
]
},
{
"login": "LuciNyan",
"name": "子瞻 Luci",
"avatar_url": "https://avatars.githubusercontent.com/u/22126563?v=4",
"profile": "https://github.com/LuciNyan",
"contributions": [
"code"
]
},
{
"login": "m1911star",
"name": "Horus",
"avatar_url": "https://avatars.githubusercontent.com/u/4948120?v=4",
"profile": "http://blog.ipili.me/",
"contributions": [
"code",
"platform"
]
},
{
"login": "fanshyiis",
"name": "Super.x",
"avatar_url": "https://avatars.githubusercontent.com/u/15103283?v=4",
"profile": "https://segmentfault.com/u/qzuser_584786517d31a",
"contributions": [
"code"
]
},
{
"login": "wangyu-1999",
"name": "Wang Yu",
"avatar_url": "https://avatars.githubusercontent.com/u/80874770?v=4",
"profile": "https://wangyu-1999.github.io/",
"contributions": [
"code"
]
},
{
"login": "felixonmars",
"name": "Felix Yan",
"avatar_url": "https://avatars.githubusercontent.com/u/1006477?v=4",
"profile": "https://felixc.at/",
"contributions": [
"code"
]
},
{
"login": "lynettelopez",
"name": "Lynette Lopez",
"avatar_url": "https://avatars.githubusercontent.com/u/32908859?v=4",
"profile": "https://github.com/lynettelopez",
"contributions": [
"code"
]
},
{
"login": "Zheaoli",
"name": "Manjusaka",
"avatar_url": "https://avatars.githubusercontent.com/u/7054676?v=4",
"profile": "http://manjusaka.itscoder.com/",
"contributions": [
"code"
]
},
{
"login": "sudongyuer",
"name": "Frozen FIsh",
"avatar_url": "https://avatars.githubusercontent.com/u/76603360?v=4",
"profile": "https://juejin.cn/user/2867982785579102/posts?sort=popular",
"contributions": [
"code"
]
},
{
"login": "MuhammedFaraz",
"name": "Mohammed Faraz",
"avatar_url": "https://avatars.githubusercontent.com/u/92734739?v=4",
"profile": "https://github.com/MuhammedFaraz",
"contributions": [
"doc"
]
},
{
"login": "Pranav4399",
"name": "Pranav Sriram ",
"avatar_url": "https://avatars.githubusercontent.com/u/28348429?v=4",
"profile": "https://pranavsriram.dev/",
"contributions": [
"code"
]
},
{
"login": "Reson-a",
"name": "Reson-a",
"avatar_url": "https://avatars.githubusercontent.com/u/20806266?v=4",
"profile": "https://github.com/Reson-a",
"contributions": [
"code"
]
}
]
}
+8
View File
@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "feat/filesystem_and_search",
"updateInternalDependencies": "patch",
"ignore": []
}
+13
View File
@@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+2
View File
@@ -1,4 +1,6 @@
**/webpack.config.js **/webpack.config.js
**/jest.config.js **/jest.config.js
**/scripts/*.js
**/node_modules/** **/node_modules/**
.github/** .github/**
**/__tests__/**
-14
View File
@@ -1,14 +0,0 @@
// https://eslint.org/docs/latest/user-guide/configuring
// "off" or 0 - turn the rule off
// "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code)
// "error" or 2 - turn the rule on as an error (exit code will be 1)
/** @type { import('eslint').Linter.Config } */
module.exports = {
extends: ['plugin:prettier/recommended'],
rules: {
'prettier/prettier': 'warn',
},
reportUnusedDisableDirectives: true,
};
+20
View File
@@ -0,0 +1,20 @@
* text=auto eol=lf
# These files are binary and should be left untouched
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.ttf binary
*.otf binary
*.eot binary
*.woff binary
*.woff2 binary
*.pdf binary
*.tar.gz binary
*.zip binary
*.7z binary
+2 -1
View File
@@ -15,8 +15,9 @@
Cache-Control "public, max-age=86400, must-revalidate" Cache-Control "public, max-age=86400, must-revalidate"
} }
handle_path /api/* { handle /api/* {
reverse_proxy {$API_SERVER} { reverse_proxy {$API_SERVER} {
health_uri /api/healthz
@error status 500 502 503 503 @error status 500 502 503 503
handle_response @error { handle_response @error {
root * /dist root * /dist
-120
View File
@@ -1,120 +0,0 @@
name: Build Pathfinder Community Version
on:
push:
branches: [master]
pull_request:
branches: [master]
# Cancels all previous workflow runs for pull requests that have not completed.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
# The concurrency group contains the workflow name and the branch name for
# pull requests or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
build:
name: Lint and Build
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
with:
node-version: 18.x
cache: 'pnpm'
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Lint
run: |
pnpm lint --max-warnings=0
# - name: Test
# run: pnpm test
- name: Build
run: pnpm build
- name: Export
run: pnpm export
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
path: ./packages/app/out
push_to_registry:
# See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
name: Push Docker image to Docker Hub
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: 'toeverything/affine-pathfinder-community'
IMAGE_TAG: canary-${{ github.sha }}
IMAGE_TAG_LATEST: nightly-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: artifact
path: packages/app/out/
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
${{ env.IMAGE_TAG }}
${{ env.IMAGE_TAG_LATEST }}
- name: Build and push Docker image
uses: docker/build-push-action@v3
with:
context: .
push: true
file: ./.github/deployment/Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-122
View File
@@ -1,122 +0,0 @@
name: Build Pathfinder LiveDemo Version
on:
push:
branches: [master]
pull_request:
branches: [master]
# Cancels all previous workflow runs for pull requests that have not completed.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
# The concurrency group contains the workflow name and the branch name for
# pull requests or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
build:
name: Lint and Build
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
registry-url: https://npm.pkg.github.com
scope: '@toeverything'
cache: 'pnpm'
- run: node scripts/module-resolve/ci.js
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Lint
run: |
pnpm lint --max-warnings=0
# - name: Test
# run: pnpm test
- name: Build
run: pnpm build
- name: Export
run: pnpm export
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
path: ./packages/app/out
push_to_registry:
# See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
name: Push Docker image to Docker Hub
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: 'toeverything/affine-pathfinder'
IMAGE_TAG: canary-${{ github.sha }}
IMAGE_TAG_LATEST: nightly-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: artifact
path: packages/app/out/
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
${{ env.IMAGE_TAG }}
${{ env.IMAGE_TAG_LATEST }}
- name: Build and push Docker image
uses: docker/build-push-action@v3
with:
context: .
push: true
file: ./.github/deployment/Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+10 -1
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
tag: tag:
description: "Custom Tag. Set nightly-latest will publish to development." description: 'Custom Tag. Set nightly-latest will publish to development.'
required: true required: true
type: string type: string
@@ -20,6 +20,7 @@ jobs:
build: build:
name: Lint and Build name: Lint and Build
runs-on: self-hosted runs-on: self-hosted
environment: development
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -62,6 +63,14 @@ jobs:
- name: Build - name: Build
run: pnpm build run: pnpm build
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
- name: Export - name: Export
run: pnpm export run: pnpm export
+334
View File
@@ -0,0 +1,334 @@
name: Build Pathfinder
on:
push:
branches: [master]
pull_request:
branches: [master]
# Cancels all previous workflow runs for pull requests that have not completed.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
# The concurrency group contains the workflow name and the branch name for
# pull requests or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
build:
name: Build on Pull Request
if: github.ref != 'refs/heads/master'
runs-on: self-hosted
environment: development
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
registry-url: https://npm.pkg.github.com
scope: '@toeverything'
cache: 'pnpm'
- run: node scripts/module-resolve/ci.js
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Build
run: pnpm build
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
- name: Export
run: pnpm export
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
path: ./packages/app/.next
lint:
name: Lint and E2E Test
runs-on: ubuntu-latest
environment: development
needs: build
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
cache: 'pnpm'
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: artifact
path: packages/app/.next/
- name: Lint & E2E Test
run: |
pnpm lint --max-warnings=0
PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium
PLAYWRIGHT_BROWSERS_PATH=0 pnpm test
PLAYWRIGHT_BROWSERS_PATH=0 pnpm test:dc
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
build-community:
name: Build Community
if: github.ref == 'refs/heads/master'
runs-on: self-hosted
environment: production
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
cache: 'pnpm'
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Build
run: pnpm build
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
- name: Export
run: pnpm export
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
path: ./packages/app/out
publish-community:
name: Push Community Image
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
needs: build-community
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: 'toeverything/affine-pathfinder-community'
IMAGE_TAG: canary-${{ github.sha }}
IMAGE_TAG_LATEST: nightly-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: artifact
path: packages/app/out/
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
${{ env.IMAGE_TAG }}
${{ env.IMAGE_TAG_LATEST }}
- name: Build Docker image
uses: docker/build-push-action@v3
with:
context: .
push: true
file: ./.github/deployment/Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-livedemo:
name: Build LiveDemo
if: github.ref == 'refs/heads/master'
runs-on: self-hosted
environment: production
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
registry-url: https://npm.pkg.github.com
scope: '@toeverything'
cache: 'pnpm'
- run: node scripts/module-resolve/ci.js
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Build
run: pnpm build
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
- name: Export
run: pnpm export
publish-livedemo:
name: Push Livedemo Image
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
needs: build-livedemo
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: 'toeverything/affine-pathfinder'
IMAGE_TAG: canary-${{ github.sha }}
IMAGE_TAG_LATEST: nightly-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: artifact
path: packages/app/out/
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
${{ env.IMAGE_TAG }}
${{ env.IMAGE_TAG_LATEST }}
- name: Build Docker image
uses: docker/build-push-action@v3
with:
context: .
push: true
file: ./.github/deployment/Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+46
View File
@@ -0,0 +1,46 @@
name: Pathfinder changelog
on:
push:
branches: [feat/filesystem_and_search, master]
# Cancels all previous workflow runs for pull requests that have not completed.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
# The concurrency group contains the workflow name and the branch name for
# pull requests or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
build:
name: publish changelog
runs-on: self-hosted
environment: development
permissions:
pull-requests: write
contents: write
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
registry-url: https://npm.pkg.github.com
scope: '@toeverything'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Create changelog
id: changesets
uses: changesets/action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+139
View File
@@ -0,0 +1,139 @@
name: Pathfinder Check
on:
push:
branches: [feat/filesystem_and_search, pathfinder1.1]
pull_request:
branches: [feat/filesystem_and_search, pathfinder1.1]
# Cancels all previous workflow runs for pull requests that have not completed.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
# The concurrency group contains the workflow name and the branch name for
# pull requests or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
build:
name: Build on Pull Request
if: github.ref != 'refs/heads/master'
runs-on: self-hosted
environment: development
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
registry-url: https://npm.pkg.github.com
scope: '@toeverything'
cache: 'pnpm'
- run: node scripts/module-resolve/ci.js
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Build
run: pnpm build
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
- name: Export
run: pnpm export
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
path: ./packages/app/.next
lint:
name: Lint and E2E Test
runs-on: ubuntu-latest
environment: development
needs: build
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 'latest'
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
cache: 'pnpm'
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Install dependencies
run: pnpm install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: artifact
path: packages/app/.next/
# Temporary shutdown of E2E testing
- name: Lint & E2E Test
run: |
pnpm lint --max-warnings=0
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
# - name: Lint & E2E Test
# run: |
# pnpm lint --max-warnings=0
# PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium
# PLAYWRIGHT_BROWSERS_PATH=0 pnpm test
# PLAYWRIGHT_BROWSERS_PATH=0 pnpm test:dc
# env:
# NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
# NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
# NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
# NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
# NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
# NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
# NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
+14 -6
View File
@@ -1,4 +1,4 @@
name: E2E & Unit Tests name: Unit Tests
on: on:
push: push:
branches: [master] branches: [master]
@@ -7,7 +7,7 @@ on:
jobs: jobs:
test: test:
timeout-minutes: 60 timeout-minutes: 60
runs-on: self-hosted runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -30,11 +30,19 @@ jobs:
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
- name: Install Playwright Browsers - name: Install Playwright browsers
run: npx playwright install --with-deps run: npx playwright install chromium
- name: Run E2E tests # - name: Run E2E tests
run: pnpm run test:e2e # run: pnpm run test:e2e
# env:
# NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
# NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
# NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
# NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
# NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
# NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
# NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
- name: Run Unit tests - name: Run Unit tests
run: pnpm run test:unit run: pnpm run test:unit
+3
View File
@@ -51,3 +51,6 @@ module-resolve.js
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/playwright/.cache/ /playwright/.cache/
# Cache
.eslintcache
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm exec lint-staged
+1
View File
@@ -0,0 +1 @@
16
+1
View File
@@ -0,0 +1 @@
pnpm-lock.yaml
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["ms-playwright.playwright", "esbenp.prettier-vscode"]
}
+1 -1
View File
@@ -3,5 +3,5 @@
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.formatOnSaveMode": "file", "editor.formatOnSaveMode": "file",
"cSpell.words": ["testid"] "cSpell.words": ["blocksuite", "livedemo", "testid"]
} }
+5
View File
@@ -0,0 +1,5 @@
# Changelog
See the [AFFiNE CHANGELOG](https://affine.pro/blog?tag=Release%20Note)
---
+45
View File
@@ -0,0 +1,45 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
For answers to common questions about this code of conduct, see <https://www.contributor-covenant.org/faq>
+3
View File
@@ -0,0 +1,3 @@
# Contributing to AFFiNE
For information related to contributing to AFFiNE, please check out the [Contributing to AFFiNE](https://docs.affine.pro/affine/developer-docs/contributions) section of the documentation at the AFFiNE site.
+379 -16
View File
@@ -1,21 +1,384 @@
MIT License # Mozilla Public License Version 2.0
Copyright (c) Toeverything Technology (Hangzhou) Co., Ltd. and its affiliates. Copyright (c) Toeverything Technology (Hangzhou) Co., Ltd. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy 1. Definitions
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all ---
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1.1. "Contributor"
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, means each individual or legal entity that creates, contributes to
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE the creation of, or owns Covered Software.
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1.2. "Contributor Version"
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE means the combination of the Contributions of others (if any) used
SOFTWARE. by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
---
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
---
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
---
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
---
- *
- 6. Disclaimer of Warranty \*
- ------------------------- \*
- *
- Covered Software is provided under this License on an "as is" \*
- basis, without warranty of any kind, either expressed, implied, or \*
- statutory, including, without limitation, warranties that the \*
- Covered Software is free of defects, merchantable, fit for a \*
- particular purpose or non-infringing. The entire risk as to the \*
- quality and performance of the Covered Software is with You. \*
- Should any Covered Software prove defective in any respect, You \*
- (not any Contributor) assume the cost of any necessary servicing, \*
- repair, or correction. This disclaimer of warranty constitutes an \*
- essential part of this License. No use of any Covered Software is \*
- authorized under this License except under this disclaimer. \*
- *
---
---
- *
- 7. Limitation of Liability \*
- -------------------------- \*
- *
- Under no circumstances and under no legal theory, whether tort \*
- (including negligence), contract, or otherwise, shall any \*
- Contributor, or anyone who distributes Covered Software as \*
- permitted above, be liable to You for any direct, indirect, \*
- special, incidental, or consequential damages of any character \*
- including, without limitation, damages for lost profits, loss of \*
- goodwill, work stoppage, computer failure or malfunction, or any \*
- and all other commercial damages or losses, even if such party \*
- shall have been informed of the possibility of such damages. This \*
- limitation of liability shall not apply to liability for death or \*
- personal injury resulting from such party's negligence to the \*
- extent applicable law prohibits such limitation. Some \*
- jurisdictions do not allow the exclusion or limitation of \*
- incidental or consequential damages, so this exclusion and \*
- limitation may not apply to You. \*
- *
---
8. Litigation
---
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
---
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
## Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
## Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
+38 -26
View File
@@ -18,7 +18,7 @@ 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:START - Do not remove or modify this section -->
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-38-orange.svg?style=flat-square [all-contributors-badge]: https://img.shields.io/badge/all_contributors-45-orange.svg?style=flat-square
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
@@ -29,6 +29,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
[![React](https://img.shields.io/badge/TypeScript-4.7-3178c6)](https://www.typescriptlang.org/) [![React](https://img.shields.io/badge/TypeScript-4.7-3178c6)](https://www.typescriptlang.org/)
[![React](https://img.shields.io/badge/React-18-61dafb)](https://reactjs.org/) [![React](https://img.shields.io/badge/React-18-61dafb)](https://reactjs.org/)
[![Rust](https://img.shields.io/badge/Rust-1.62-dea584)](https://www.rust-lang.org/) [![Rust](https://img.shields.io/badge/Rust-1.62-dea584)](https://www.rust-lang.org/)
</div> </div>
<div> <div>
@@ -66,9 +67,11 @@ Before we tell you how to get started with AFFiNE, we'd like to shamelessly plug
**Seamless transitions** — However you want your data displayed, whichever viewing mode you use, AFFiNE supports easy transitions to allow you to quickly and effortlessly view your data in the way you want. **Seamless transitions** — However you want your data displayed, whichever viewing mode you use, AFFiNE supports easy transitions to allow you to quickly and effortlessly view your data in the way you want.
**Markdown support** — When you write in AFFiNE you can use Markdown syntax which helps create an easier editing experience, that can be experienced with just a keyboard. And this allows you to export your data cleanly into Markdown. **Markdown support** — When you write in AFFiNE you can use Markdown syntax which helps create an easier editing experience, that can be experienced with just a keyboard. And this allows you to export your data cleanly into Markdown.
<!-- <!--
**Colloboration** — Whether you want to colloborate with yourself across multiple devices, or work together with others, support for colloboration and multiplayer is out-of-the-box, which makes it easy for teams to get started with AFFiNE. **Colloboration** — Whether you want to colloborate with yourself across multiple devices, or work together with others, support for colloboration and multiplayer is out-of-the-box, which makes it easy for teams to get started with AFFiNE.
--> -->
**Choice of multiple languages** — Thanks to community contributions AFFiNE offers support for multiple languages. If you don't find your language or would like to suggest some changes we welcome your contributions. **Choice of multiple languages** — Thanks to community contributions AFFiNE offers support for multiple languages. If you don't find your language or would like to suggest some changes we welcome your contributions.
## Getting started ## Getting started
@@ -99,21 +102,21 @@ We have done a major refactoring recently, if you want to see our previous versi
We would also like to give thanks to open-source projects that make AFFiNE possible: We would also like to give thanks to open-source projects that make AFFiNE possible:
- [BlockSuite](https://github.com/toeverything/BlockSuite) - AFFiNE is built with and powered by BlockSuite. - [BlockSuite](https://github.com/toeverything/BlockSuite) - AFFiNE is built with and powered by BlockSuite.
- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implementation on state management and data sync. - [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implementation on state management and data sync.
- [React](https://github.com/facebook/react) -- View layer support and web GUI framework. - [React](https://github.com/facebook/react) -- View layer support and web GUI framework.
- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST. - [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST.
- [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure. - [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure.
- [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor. - [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor.
- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend. - [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend.
- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board. - [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board.
- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library. - [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library.
- Other [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies) - Other [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies)
We use the following open source projects to help us build a better development experience: We use the following open source projects to help us build a better development experience:
- [nx](https://github.com/nrwl/nx) -- Awesome monorepo manager & build system - [nx](https://github.com/nrwl/nx) -- Awesome monorepo manager & build system
- [tolgee](https://github.com/tolgee/tolgee-platform) -- Elegant i18n collaborative editing platform - [tolgee](https://github.com/tolgee/tolgee-platform) -- Elegant i18n collaborative editing platform
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone. Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
@@ -133,45 +136,54 @@ Thanks a lot to the community for providing such powerful and simple libraries,
<td align="center"><a href="https://github.com/zuoxiaodong0815"><img src="https://avatars.githubusercontent.com/u/53252747?v=4?s=50" width="50px;" alt=""/><br /><sub><b>xiaodong zuo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/zuoxiaodong0815"><img src="https://avatars.githubusercontent.com/u/53252747?v=4?s=50" width="50px;" alt=""/><br /><sub><b>xiaodong zuo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Documentation">📖</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/Himself65"><img src="https://avatars.githubusercontent.com/u/14026360?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Himself65</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Himself65" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Himself65" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/SaikaSakura"><img src="https://avatars.githubusercontent.com/u/11530942?v=4?s=50" width="50px;" alt=""/><br /><sub><b>MingLIang Wang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/SaikaSakura"><img src="https://avatars.githubusercontent.com/u/11530942?v=4?s=50" width="50px;" alt=""/><br /><sub><b>MingLIang Wang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/QiShaoXuan"><img src="https://avatars.githubusercontent.com/u/22772830?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Qi</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/QiShaoXuan"><img src="https://avatars.githubusercontent.com/u/22772830?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Qi</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Documentation">📖</a></td>
<td align="center"><a href="https://onetwo.ren/wiki"><img src="https://avatars.githubusercontent.com/u/3746270?v=4?s=50" width="50px;" alt=""/><br /><sub><b>lin onetwo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=linonetwo" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=linonetwo" title="Documentation">📖</a></td>
<td align="center"><a href="https://colelawrence.com/"><img src="https://avatars.githubusercontent.com/u/2925395?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Cole Lawrence</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=colelawrence" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=colelawrence" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/thorseraq"><img src="https://avatars.githubusercontent.com/u/20554850?v=4?s=50" width="50px;" alt=""/><br /><sub><b>x1a0t</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=thorseraq" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=thorseraq" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/HeJiachen-PM"><img src="https://avatars.githubusercontent.com/u/79301703?v=4?s=50" width="50px;" alt=""/><br /><sub><b>HeJiachen-PM</b></sub></a><br /><a href="#research-HeJiachen-PM" title="Research">🔬</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=HeJiachen-PM" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://www.notion.so/houjoe/Joe-2a85f5be01004cd2b6a5ad26fbb948b1"><img src="https://avatars.githubusercontent.com/u/22443345?v=4?s=50" width="50px;" alt=""/><br /><sub><b>houjoe</b></sub></a><br /><a href="#research-joebeijing" title="Research">🔬</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=joebeijing" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Yipei-Operation"><img src="https://avatars.githubusercontent.com/u/79373028?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Yipei Wei</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Yipei-Operation" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/VelikaHF"><img src="https://avatars.githubusercontent.com/u/121547898?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Velika</b></sub></a><br /><a href="#design-VelikaHF" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/Svaney-ssman"><img src="https://avatars.githubusercontent.com/u/110808979?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Svaney</b></sub></a><br /><a href="#design-Svaney-ssman" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/fanjing22"><img src="https://avatars.githubusercontent.com/u/109729699?v=4?s=50" width="50px;" alt=""/><br /><sub><b>fanjing22</b></sub></a><br /><a href="#design-fanjing22" title="Design">🎨</a></td>
<td align="center"><a href="http://xell.me/"><img src="https://avatars.githubusercontent.com/u/132558?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Guozhu Liu</b></sub></a><br /><a href="#design-xell" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/fyZheng07"><img src="https://avatars.githubusercontent.com/u/63830919?v=4?s=50" width="50px;" alt=""/><br /><sub><b>fyZheng07</b></sub></a><br /><a href="#eventOrganizing-fyZheng07" title="Event Organizing">📋</a> <a href="#userTesting-fyZheng07" title="User Testing">📓</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/CJSS"><img src="https://avatars.githubusercontent.com/u/4605025?v=4?s=50" width="50px;" alt=""/><br /><sub><b>CJSS</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=CJSS" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/JimmFly"><img src="https://avatars.githubusercontent.com/u/102217452?v=4?s=50" width="50px;" alt=""/><br /><sub><b>JimmFly</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=JimmFly" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mitsuhatu"><img src="https://avatars.githubusercontent.com/u/110213079?v=4?s=50" width="50px;" alt=""/><br /><sub><b>mitsuhatu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/mitsuhatu"><img src="https://avatars.githubusercontent.com/u/110213079?v=4?s=50" width="50px;" alt=""/><br /><sub><b>mitsuhatu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Documentation">📖</a></td>
<td align="center"><a href="https://shockwave.me/"><img src="https://avatars.githubusercontent.com/u/15013925?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Austaras</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Documentation">📖</a></td> <td align="center"><a href="https://shockwave.me/"><img src="https://avatars.githubusercontent.com/u/15013925?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Austaras</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/uptonking"><img src="https://avatars.githubusercontent.com/u/11391549?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Jin Yao</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/uptonking"><img src="https://avatars.githubusercontent.com/u/11391549?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Jin Yao</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/HeJiachen-PM"><img src="https://avatars.githubusercontent.com/u/79301703?v=4?s=50" width="50px;" alt=""/><br /><sub><b>HeJiachen-PM</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=HeJiachen-PM" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Yipei-Operation"><img src="https://avatars.githubusercontent.com/u/79373028?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Yipei Wei</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Yipei-Operation" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/fanjing22"><img src="https://avatars.githubusercontent.com/u/109729699?v=4?s=50" width="50px;" alt=""/><br /><sub><b>fanjing22</b></sub></a><br /><a href="#design-fanjing22" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/Svaney-ssman"><img src="https://avatars.githubusercontent.com/u/110808979?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Svaney</b></sub></a><br /><a href="#design-Svaney-ssman" title="Design">🎨</a></td>
<td align="center"><a href="http://xell.me/"><img src="https://avatars.githubusercontent.com/u/132558?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Guozhu Liu</b></sub></a><br /><a href="#design-xell" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/fyZheng07"><img src="https://avatars.githubusercontent.com/u/63830919?v=4?s=50" width="50px;" alt=""/><br /><sub><b>fyZheng07</b></sub></a><br /><a href="#eventOrganizing-fyZheng07" title="Event Organizing">📋</a> <a href="#userTesting-fyZheng07" title="User Testing">📓</a></td>
<td align="center"><a href="https://github.com/ShortCipher5"><img src="https://avatars.githubusercontent.com/u/4605025?v=4?s=50" width="50px;" alt=""/><br /><sub><b>ShortCipher5</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=ShortCipher5" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/JimmFly"><img src="https://avatars.githubusercontent.com/u/102217452?v=4?s=50" width="50px;" alt=""/><br /><sub><b>JimmFly</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=JimmFly" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/CarlosZoft"><img src="https://avatars.githubusercontent.com/u/62192072?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Carlos Rafael </b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=CarlosZoft" title="Code">💻</a></td> <td align="center"><a href="https://github.com/CarlosZoft"><img src="https://avatars.githubusercontent.com/u/62192072?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Carlos Rafael </b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=CarlosZoft" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/caleboleary"><img src="https://avatars.githubusercontent.com/u/12816579?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Caleb OLeary</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=caleboleary" title="Code">💻</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/caleboleary"><img src="https://avatars.githubusercontent.com/u/12816579?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Caleb OLeary</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=caleboleary" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/westongraham"><img src="https://avatars.githubusercontent.com/u/89493023?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Weston Graham</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=westongraham" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/westongraham"><img src="https://avatars.githubusercontent.com/u/89493023?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Weston Graham</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=westongraham" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/pointmax"><img src="https://avatars.githubusercontent.com/u/49361135?v=4?s=50" width="50px;" alt=""/><br /><sub><b>pointmax</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=pointmax" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/pointmax"><img src="https://avatars.githubusercontent.com/u/49361135?v=4?s=50" width="50px;" alt=""/><br /><sub><b>pointmax</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=pointmax" title="Documentation">📖</a></td>
<td align="center"><a href="https://liby.github.io/notes"><img src="https://avatars.githubusercontent.com/u/38807139?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Bryan Lee</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=liby" title="Code">💻</a></td> <td align="center"><a href="https://liby.github.io/notes"><img src="https://avatars.githubusercontent.com/u/38807139?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Bryan Lee</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=liby" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/chenmoonmo"><img src="https://avatars.githubusercontent.com/u/36295999?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Simon Li</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=chenmoonmo" title="Code">💻</a></td> <td align="center"><a href="https://github.com/chenmoonmo"><img src="https://avatars.githubusercontent.com/u/36295999?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Simon Li</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=chenmoonmo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/githbq"><img src="https://avatars.githubusercontent.com/u/10009709?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Bob Hu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=githbq" title="Code">💻</a></td> <td align="center"><a href="https://github.com/githbq"><img src="https://avatars.githubusercontent.com/u/10009709?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Bob Hu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=githbq" title="Code">💻</a></td>
<td align="center"><a href="https://quavo.vercel.app/"><img src="https://avatars.githubusercontent.com/u/67266933?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Quavo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lucky-chap" title="Documentation">📖</a></td> <td align="center"><a href="https://quavo.vercel.app/"><img src="https://avatars.githubusercontent.com/u/67266933?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Quavo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lucky-chap" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/LuciNyan"><img src="https://avatars.githubusercontent.com/u/22126563?v=4?s=50" width="50px;" alt=""/><br /><sub><b>子瞻 Luci</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=LuciNyan" title="Code">💻</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/LuciNyan"><img src="https://avatars.githubusercontent.com/u/22126563?v=4?s=50" width="50px;" alt=""/><br /><sub><b>子瞻 Luci</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=LuciNyan" title="Code">💻</a></td>
<td align="center"><a href="http://blog.ipili.me/"><img src="https://avatars.githubusercontent.com/u/4948120?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Horus</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=m1911star" title="Code">💻</a> <a href="#platform-m1911star" title="Packaging/porting to new platform">📦</a></td> <td align="center"><a href="http://blog.ipili.me/"><img src="https://avatars.githubusercontent.com/u/4948120?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Horus</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=m1911star" title="Code">💻</a> <a href="#platform-m1911star" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://segmentfault.com/u/qzuser_584786517d31a"><img src="https://avatars.githubusercontent.com/u/15103283?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Super.x</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=fanshyiis" title="Code">💻</a></td> <td align="center"><a href="https://segmentfault.com/u/qzuser_584786517d31a"><img src="https://avatars.githubusercontent.com/u/15103283?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Super.x</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=fanshyiis" title="Code">💻</a></td>
<td align="center"><a href="https://wangyu-1999.github.io/"><img src="https://avatars.githubusercontent.com/u/80874770?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Wang Yu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=wangyu-1999" title="Code">💻</a></td> <td align="center"><a href="https://wangyu-1999.github.io/"><img src="https://avatars.githubusercontent.com/u/80874770?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Wang Yu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=wangyu-1999" title="Code">💻</a></td>
<td align="center"><a href="https://felixc.at/"><img src="https://avatars.githubusercontent.com/u/1006477?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Felix Yan</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=felixonmars" title="Code">💻</a></td> <td align="center"><a href="https://felixc.at/"><img src="https://avatars.githubusercontent.com/u/1006477?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Felix Yan</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=felixonmars" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/lynettelopez"><img src="https://avatars.githubusercontent.com/u/32908859?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Lynette Lopez</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lynettelopez" title="Code">💻</a></td> <td align="center"><a href="https://github.com/lynettelopez"><img src="https://avatars.githubusercontent.com/u/32908859?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Lynette Lopez</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lynettelopez" title="Code">💻</a></td>
<td align="center"><a href="http://manjusaka.itscoder.com/"><img src="https://avatars.githubusercontent.com/u/7054676?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Manjusaka</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Zheaoli" title="Code">💻</a></td> <td align="center"><a href="http://manjusaka.itscoder.com/"><img src="https://avatars.githubusercontent.com/u/7054676?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Manjusaka</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Zheaoli" title="Code">💻</a></td>
<td align="center"><a href="https://juejin.cn/user/2867982785579102/posts?sort=popular"><img src="https://avatars.githubusercontent.com/u/76603360?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Frozen FIsh</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=sudongyuer" title="Code">💻</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://juejin.cn/user/2867982785579102/posts?sort=popular"><img src="https://avatars.githubusercontent.com/u/76603360?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Frozen FIsh</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=sudongyuer" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/MuhammedFaraz"><img src="https://avatars.githubusercontent.com/u/92734739?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Mohammed Faraz</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=MuhammedFaraz" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/MuhammedFaraz"><img src="https://avatars.githubusercontent.com/u/92734739?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Mohammed Faraz</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=MuhammedFaraz" title="Documentation">📖</a></td>
<td align="center"><a href="https://pranavsriram.dev/"><img src="https://avatars.githubusercontent.com/u/28348429?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Pranav Sriram </b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Pranav4399" title="Code">💻</a></td> <td align="center"><a href="https://pranavsriram.dev/"><img src="https://avatars.githubusercontent.com/u/28348429?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Pranav Sriram </b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Pranav4399" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Reson-a"><img src="https://avatars.githubusercontent.com/u/20806266?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Reson-a</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Reson-a" title="Code">💻</a></td>
</tr> </tr>
</table> </table>
+3
View File
@@ -0,0 +1,3 @@
# Benchmarks
Example sites for benchmarking `AFFiNE`.
+9
View File
@@ -0,0 +1,9 @@
# Contributor Add
- https://allcontributors.org/docs/en/cli/usage
- https://allcontributors.org/docs/en/emoji-key
```shell
all-contributors add tzhangchi code,doc
all-contributors generate
```
+44
View File
@@ -0,0 +1,44 @@
# What we do
We, `AFFiNE` believe in shaping a world semantically connected through block components in modern applications. We're open for Fullstack Engineer positions across the OctoBase sub-team. OctoBase is an offline, scalable, and self-contained collaborative database. It provides a data collaboration engine for AFFiNE and BlockSuite. It can either run on the server as a service or be embedded in our client to offer a complete offline computing capacity.
# Full-stack Engineer
## This position is for:
- Developing AFFiNE the open source way, including coding and community engagement.
- Researching and supporting the onboarding process of new use cases for AFFiNE.pro subscribers.
- Improving our data computing engine with Rust.
- Assisting our subscribers in utilizing our product in a data-based way with help from the operational - teams.
- Researching on better activation of potential subscribers.
- Engineers who are self-organized individuals and also responsible team members, no matter whether - they are on-site or working remotely.
## What we are looking for:
- Ability to use TypeScript proficiently in engineering projects and at least one server-side development language (preferably Rust).
- Strong English communication and writing skills.
- Ability to work skillfully and comfortably within diverse and cross-functional teams.
- Love open source, share our vision, and work within those values.
## It would be great if you are:
- Experience in understanding the architecture and being responsible for the development of a function or module in a real project
- Heavy user of knowledge/project management tools
- Experience in working on a real-world database, distributed server application, or serverless application projects
- Experience in using a collaborative algorithm on your own or participating in projects
- Experienced in working with a globally distributed team.
- Enthusiastic about AFFiNE products as a user or contributor.
### What we offer:
- $2800 vouchers for latest generation MacBook Pr or working equipment of your choice.
- Public holidays and paid annual leave starting at 12 days.
- Free lunch, unlimited drinks and snacks.
- Free English language lessons (including free IELTS test) open to all employees.
- Become a maintainer of great open source projects and use Copilot powered by GitHub for free if you want.
### Contact us:
Interested? Send us your CV to [contact@toeverything.info](mailto:contact@toeverything.info).
Feel free to include any extra information (GitHub link, previous projects, personal blog etc.).
+12 -8
View File
@@ -1,17 +1,21 @@
# Jobs # Jobs
- [<b>Fullstack Engineer - Global</b>](./affine.pro-remote.md) @[affine.pro](http://affine.pro/) - [<b>Fullstack Engineer - Remote</b>](./affine.pro-remote.md) @[affine.pro](http://affine.pro/)
Rust ·TS · BlockSuite · OctoBase(JWST) · Remote , Global Rust ·TS · BlockSuite · OctoBase(JWST) · Global
- [<b>Fullstack Engineer - China</b>](./affine.pro.md) @[affine.pro](http://affine.pro/) - [<b>Fullstack Engineer - China</b>](./affine.pro.md) @[affine.pro](http://affine.pro/)
Rust ·TS · BlockSuite · OctoBase(JWST) · Remote , China Rust ·TS · BlockSuite · OctoBase(JWST) · China
- [<b>Fullstack Engineer Intern</b>](./affine.pro-intern.md) @[affine.pro](http://affine.pro/) - [<b>Fullstack Engineer - Rust</b>](./affine.pro-rust.md) @[affine.pro](http://affine.pro/)
Rust ·TS · BlockSuite · OctoBase(JWST) · Hangzhou , China Rust ·TS · BlockSuite · OctoBase(JWST) · Remote
- [<b>Full Stack Platform Engineer</b>](./mysc.app.md) @[mysc.app](https://mysc.app/) - [<b>Fullstack Engineer - Intern</b>](./affine.pro-intern.md) @[affine.pro](http://affine.pro/)
Rust · JWST · Remote · Shanghai, China Rust ·TS · BlockSuite · OctoBase(JWST) · Hangzhou , China
- [<b>Full Stack Platform Engineer</b>](./mysc.app.md) @[mysc.app](https://mysc.app/)
Rust · JWST · Remote · Shanghai, China
+53 -12
View File
@@ -1,22 +1,35 @@
{ {
"name": "pathfinder", "name": "AFFiNE",
"version": "0.0.0", "version": "0.3.0",
"private": true, "private": true,
"author": "toeverything",
"license": "MPL-2.0",
"scripts": { "scripts": {
"dev": "pnpm --filter=!@pathfinder/app build && pnpm --filter @pathfinder/app dev", "dev": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
"build": "pnpm -r build", "dev:ac": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev:ac",
"export": "pnpm --filter @pathfinder/app export", "build": " pnpm --filter=!@affine/app build && pnpm --filter!=@affine/data-services -r build",
"start": "pnpm --filter @pathfinder/app start", "export": "pnpm --filter @affine/app export",
"lint": "pnpm --filter @pathfinder/app lint", "start": "pnpm --filter @affine/app start",
"test:e2e": "playwright test", "lint": "pnpm --filter @affine/app lint",
"test": "playwright test",
"test:dc": "pnpm --filter @affine/data-services test",
"test:e2e:codegen": "npx playwright codegen http://localhost:8080",
"test:unit": "jest", "test:unit": "jest",
"postinstall": "husky install",
"notify": "node --experimental-modules scripts/notify.mjs" "notify": "node --experimental-modules scripts/notify.mjs"
}, },
"lint-staged": {
"*": "prettier --write --ignore-unknown",
"*.{ts,tsx,js,jsx}": "npx eslint --cache --fix"
},
"devDependencies": { "devDependencies": {
"@jest/globals": "^29.3.1", "@jest/globals": "^29.3.1",
"@playwright/test": "^1.28.1", "@playwright/test": "^1.29.1",
"@types/node": "18.7.18", "@typescript-eslint/eslint-plugin": "^5.47.0",
"eslint": "8.22.0", "@typescript-eslint/parser": "^5.47.0",
"@types/eslint": "^8.4.10",
"@types/node": "^18.11.17",
"eslint": "^8.30.0",
"eslint-config-next": "12.3.1", "eslint-config-next": "12.3.1",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
@@ -24,6 +37,34 @@
"jest": "^29.3.1", "jest": "^29.3.1",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"typescript": "^4.9.3" "typescript": "^4.9.3",
"lint-staged": "^13.1.0",
"husky": "^8.0.2",
"@changesets/cli": "^2.26.0"
},
"eslintConfig": {
"root": true,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"./tsconfig.json"
]
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"prettier/prettier": "warn"
},
"reportUnusedDisableDirectives": true,
"ignorePatterns": [
"src/**/*.test.ts",
"package/**/dist/*"
]
} }
} }
+9
View File
@@ -0,0 +1,9 @@
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=
# absolute path to the block suite directory
LOCAL_BLOCK_SUITE=
-18
View File
@@ -1,18 +0,0 @@
// https://eslint.org/docs/latest/user-guide/configuring
// "off" or 0 - turn the rule off
// "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code)
// "error" or 2 - turn the rule on as an error (exit code will be 1)
/** @type { import('eslint').Linter.Config } */
module.exports = {
extends: [
'next/core-web-vitals',
'plugin:@next/next/recommended',
'plugin:prettier/recommended',
],
rules: {
'prettier/prettier': 'warn',
},
reportUnusedDisableDirectives: true,
};
+7
View File
@@ -0,0 +1,7 @@
# @affine/app
## 1.0.0
### Major Changes
- cc72448: add changeset config
+56 -3
View File
@@ -1,10 +1,13 @@
// @ts-check /* eslint @typescript-eslint/no-var-requires: "off" */
const { getGitVersion, getCommitHash } = require('./scripts/gitInfo'); const { getGitVersion, getCommitHash } = require('./scripts/gitInfo');
const { dependencies } = require('./package.json');
const path = require('node:path');
const printer = require('./scripts/printer').printer;
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
productionBrowserSourceMaps: true, productionBrowserSourceMaps: true,
reactStrictMode: true, reactStrictMode: false,
swcMinify: false, swcMinify: false,
publicRuntimeConfig: { publicRuntimeConfig: {
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
@@ -13,8 +16,58 @@ const nextConfig = {
CI: process.env.CI || null, CI: process.env.CI || null,
VERSION: getGitVersion(), VERSION: getGitVersion(),
COMMIT_HASH: getCommitHash(), COMMIT_HASH: getCommitHash(),
EDITOR_VERSION: dependencies['@blocksuite/editor'],
},
webpack: config => {
config.resolve.alias['yjs'] = require.resolve('yjs');
config.module.rules.push({
test: /\.md$/i,
loader: 'raw-loader',
});
return config;
},
images: {
unoptimized: true,
},
// XXX not test yet
rewrites: async () => {
if (process.env.NODE_API_SERVER === 'ac') {
let destinationAC = 'http://100.85.73.88:12001/api/:path*';
printer.info('API request proxy to [AC Server] ' + destinationAC);
return [
{
source: '/api/:path*',
destination: destinationAC,
},
];
} else {
let destinationStandard = 'http://100.77.180.48:11001/api/:path*';
printer.info(
'API request proxy to [Standard Server] ' + destinationStandard
);
return [
{
source: '/api/:path*',
destination: destinationStandard,
},
];
}
}, },
basePath: process.env.BASE_PATH, basePath: process.env.BASE_PATH,
}; };
module.exports = nextConfig; const baseDir = process.env.LOCAL_BLOCK_SUITE ?? '/';
const withDebugLocal = require('next-debug-local')(
{
'@blocksuite/editor': path.resolve(baseDir, 'packages', 'editor'),
'@blocksuite/blocks': path.resolve(baseDir, 'packages', 'blocks'),
'@blocksuite/store': path.resolve(baseDir, 'packages', 'store'),
},
{
enable: path.isAbsolute(process.env.LOCAL_BLOCK_SUITE ?? ''),
}
);
module.exports = withDebugLocal(nextConfig);
+31 -9
View File
@@ -1,17 +1,20 @@
{ {
"name": "@pathfinder/app", "name": "@affine/app",
"version": "0.1.0", "version": "0.3.1",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev -p 8080",
"dev:ac": "NODE_API_SERVER=ac next dev -p 8080",
"build": "next build", "build": "next build",
"export": "next export", "export": "next export",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@blocksuite/blocks": "0.3.0-alpha.4", "@affine/data-services": "workspace:*",
"@blocksuite/editor": "0.3.0-alpha.4", "@blocksuite/blocks": "0.3.0-20221230100352-5dfe65e",
"@blocksuite/store": "0.3.0-alpha.4", "@blocksuite/editor": "0.3.0-20221230100352-5dfe65e",
"@blocksuite/icons": "^2.0.2",
"@blocksuite/store": "0.3.0-20221230100352-5dfe65e",
"@emotion/css": "^11.10.0", "@emotion/css": "^11.10.0",
"@emotion/react": "^11.10.4", "@emotion/react": "^11.10.4",
"@emotion/server": "^11.10.0", "@emotion/server": "^11.10.0",
@@ -21,24 +24,43 @@
"@mui/base": "^5.0.0-alpha.87", "@mui/base": "^5.0.0-alpha.87",
"@mui/icons-material": "^5.10.9", "@mui/icons-material": "^5.10.9",
"@mui/material": "^5.8.6", "@mui/material": "^5.8.6",
"@toeverything/pathfinder-logger": "workspace:@pathfinder/logger@*", "@toeverything/pathfinder-logger": "workspace:@affine/logger@*",
"cmdk": "^0.1.20",
"css-spring": "^4.1.0", "css-spring": "^4.1.0",
"dayjs": "^1.11.7",
"i18next": "^21.9.1",
"lit": "^2.3.1", "lit": "^2.3.1",
"next": "13.0.1", "next": "13.1.0",
"next-debug-local": "^0.1.5",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"quill": "^1.3.7", "quill": "^1.3.7",
"quill-cursors": "^4.0.0", "quill-cursors": "^4.0.0",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0" "react-dom": "18.2.0",
"react-i18next": "^11.18.4",
"yjs": "^13.5.43"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.7.18", "@types/node": "18.7.18",
"@types/react": "18.0.20", "@types/react": "18.0.20",
"@types/react-dom": "18.0.6", "@types/react-dom": "18.0.6",
"@types/wicg-file-system-access": "^2020.9.5",
"chalk-next": "^6.1.5",
"eslint": "8.22.0", "eslint": "8.22.0",
"eslint-config-next": "12.3.1", "eslint-config-next": "12.3.1",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"raw-loader": "^4.0.2",
"typescript": "4.8.3" "typescript": "4.8.3"
},
"eslintConfig": {
"extends": [
"next/core-web-vitals",
"plugin:@next/next/recommended"
],
"rules": {
"prettier/prettier": "warn"
},
"reportUnusedDisableDirectives": true
} }
} }
+2
View File
@@ -145,7 +145,9 @@ select,
keygen, keygen,
legend { legend {
color: var(--affine-text-color); color: var(--affine-text-color);
background-color: unset;
outline: 0; outline: 0;
border: 0;
font-size: 18px; font-size: 18px;
line-height: 1.5; line-height: 1.5;
font-family: var(--affine-font-family); font-family: var(--affine-font-family);
@@ -0,0 +1,21 @@
import { describe, test, expect } from '@jest/globals';
import { printer } from './../printer';
const chalk = require('chalk');
describe('printer', () => {
test('test debug', () => {
expect(printer.debug('test debug')).toBe(
chalk.green`debug` + chalk.white(' - test debug')
);
});
test('test info', () => {
expect(printer.info('test info')).toBe(
chalk.rgb(19, 167, 205)`info` + chalk.white(' - test info')
);
});
test('test warn', () => {
expect(printer.warn('test warn')).toBe(
chalk.yellow`warn` + chalk.white(' - test warn')
);
});
});
+20
View File
@@ -0,0 +1,20 @@
const chalk = require('chalk');
const printer = {
debug: msg => {
const result = chalk.green`debug` + chalk.white(' - ' + msg);
console.log(result);
return result;
},
info: msg => {
const result = chalk.rgb(19, 167, 205)`info` + chalk.white(' - ' + msg);
console.log(result);
return result;
},
warn: msg => {
const result = chalk.yellow`warn` + chalk.white(' - ' + msg);
console.log(result);
return result;
},
};
module.exports = { printer };
+11
View File
@@ -0,0 +1,11 @@
import { NotFoundTitle, PageContainer } from './styles';
export const NotfoundPage = () => {
return (
<PageContainer>
<NotFoundTitle>404 - Page Not Found</NotFoundTitle>
</PageContainer>
);
};
export default NotfoundPage;
+21
View File
@@ -0,0 +1,21 @@
import { styled } from '@/styles';
export const PageContainer = styled('div')(({ theme }) => {
return {
width: '100%',
height: 'calc(100vh)',
backgroundColor: theme.colors.pageBackground,
};
});
export const NotFoundTitle = styled('h1')(({ theme }) => {
return {
position: 'relative',
top: 'calc(50% - 100px)',
height: '100px',
fontSize: '60px',
lineHeight: '100px',
color: theme.colors.textColor,
textAlign: 'center',
};
});
@@ -1,119 +0,0 @@
import type { DOMAttributes, CSSProperties } from 'react';
type IconProps = {
style?: CSSProperties;
} & DOMAttributes<SVGElement>;
export const RightArrow = ({ style = {}, ...props }: IconProps) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
style={style}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.667 10.0007L8.33366 6.66732L8.33366 13.334L11.667 10.0007Z"
/>
</svg>
);
};
export const Export2Markdown = ({ style = {}, ...props }: IconProps) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
style={style}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.99963 1.66707V1.66699H10.833L16.6663 7.50033V7.50037L16.6663 7.50041V15.8337C16.6663 17.2145 15.5471 18.3337 14.1663 18.3337H5.83301C4.4523 18.3337 3.33301 17.2145 3.33301 15.8337V4.16707C3.33301 2.78636 4.4523 1.66707 5.83301 1.66707H9.99963ZM9.99963 3.00041H5.83301C5.18868 3.00041 4.66634 3.52274 4.66634 4.16707V15.8337C4.66634 16.4781 5.18868 17.0004 5.83301 17.0004H14.1663C14.8107 17.0004 15.333 16.4781 15.333 15.8337V8.33366H12.4996C11.1189 8.33366 9.99963 7.21437 9.99963 5.83366V3.00041ZM11.333 4.05265L14.2806 7.00033H12.4996C11.8553 7.00033 11.333 6.47799 11.333 5.83366V4.05265Z"
/>
<path d="M11.245 14.1671L11.2683 11.2446H11.2508L10.1775 14.1671H9.4775L8.43333 11.2446H8.41583L8.43917 14.1671H7.5V10.0371H8.9175L9.85667 12.6854H9.88L10.7783 10.0371H12.2192V14.1671H11.245Z" />
</svg>
);
};
export const Export2HTML = ({ style = {}, ...props }: IconProps) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
style={style}
{...props}
>
<path d="M5.35987 18.3335V16.7055H3.84187V18.3335H2.90137V14.4395H3.84187V15.9135H5.35987V14.4395H6.30037V18.3335H5.35987Z" />
<path d="M8.76806 15.2425V18.3335H7.82756V15.2425H6.72756V14.4395H9.86806V15.2425H8.76806Z" />
<path d="M13.8284 18.3335L13.8504 15.578H13.8339L12.8219 18.3335H12.1619L11.1774 15.578H11.1609L11.1829 18.3335H10.2974V14.4395H11.6339L12.5194 16.9365H12.5414L13.3884 14.4395H14.7469V18.3335H13.8284Z" />
<path d="M15.5503 18.3335V14.4395H16.4963V17.514H18.0033V18.3335H15.5503Z" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.99963 1.66707V1.66699H10.833L11.333 2.16699L16.1663 7.00032L16.6663 7.50033V7.50037L16.6663 7.50041V13.3337H15.333V8.33366H15.333H12.4996C11.1189 8.33366 9.99963 7.21437 9.99963 5.83366V3.00041H5.83301C5.18868 3.00041 4.66634 3.52274 4.66634 4.16707V13.3337H3.33301V4.16707C3.33301 2.78636 4.4523 1.66707 5.83301 1.66707H9.99963ZM11.333 4.05265V5.83366C11.333 6.47799 11.8553 7.00032 12.4996 7.00032H14.2806L11.333 4.05265Z"
/>
</svg>
);
};
export const LogoIcon = ({ style = {}, ...props }: IconProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="currentColor"
style={style}
{...props}
>
<path
fillRule="evenodd"
d="M10.552 2 4 21h3.838l4.168-13.14L16.176 21H20L13.447 2h-2.895Z"
clipRule="evenodd"
/>
</svg>
);
};
export const MoreIcon = ({ style = {}, ...props }: IconProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="currentColor"
>
<circle cx="12" cy="5.5" r="1.5" />
<circle cx="12" cy="12" r="1.5" />
<circle cx="12" cy="18.5" r="1.5" />
</svg>
);
};
export const ExportIcon = ({ style = {}, ...props }: IconProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="currentColor"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 3.19995C12.2121 3.19995 12.4156 3.28424 12.5656 3.43427L16.5656 7.43427L15.4343 8.56564L12.8 5.93132V14H11.2V5.93132L8.56564 8.56564L7.43427 7.43427L11.4343 3.43427C11.5843 3.28424 11.7878 3.19995 12 3.19995ZM3.79995 12V16.7992C3.79995 17.3724 3.80057 17.7543 3.82454 18.0476C3.84775 18.3317 3.88879 18.4616 3.93074 18.544C4.04579 18.7698 4.22937 18.9533 4.45516 19.0684C4.5375 19.1103 4.66747 19.1514 4.9515 19.1746C5.24487 19.1985 5.6267 19.1992 6.19995 19.1992H17.8C18.3732 19.1992 18.755 19.1985 19.0484 19.1746C19.3324 19.1514 19.4624 19.1103 19.5447 19.0684C19.7705 18.9533 19.9541 18.7698 20.0692 18.544C20.1111 18.4616 20.1522 18.3317 20.1754 18.0476C20.1993 17.7543 20.2 17.3724 20.2 16.7992V12H21.8V16.8314C21.8 17.364 21.8 17.8116 21.77 18.1779C21.7388 18.5609 21.6708 18.9249 21.4948 19.2703C21.2263 19.7972 20.798 20.2255 20.2711 20.494C19.9256 20.67 19.5617 20.738 19.1787 20.7693C18.8124 20.7992 18.3648 20.7992 17.8322 20.7992H6.16775C5.63509 20.7992 5.18749 20.7992 4.82121 20.7693C4.43823 20.738 4.07426 20.67 3.72878 20.494C3.20193 20.2255 2.77358 19.7972 2.50513 19.2703C2.3291 18.9249 2.26115 18.5609 2.22986 18.1779C2.19993 17.8116 2.19994 17.364 2.19995 16.8313L2.19995 12H3.79995Z"
/>
</svg>
);
};
@@ -1,158 +0,0 @@
import React, { useEffect, useState } from 'react';
import {
LogoIcon,
MoreIcon,
ExportIcon,
Export2Markdown,
Export2HTML,
RightArrow,
} from './icons';
import {
StyledHeader,
StyledTitle,
StyledTitleWrapper,
StyledLogo,
StyledHeaderRightSide,
IconButton,
StyledHeaderContainer,
StyledBrowserWarning,
StyledCloseButton,
StyledMenuItemWrapper,
} from './styles';
import { useEditor } from '@/components/editor-provider';
import EditorModeSwitch from '@/components/editor-mode-switch';
import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/icons';
import ThemeModeSwitch from '@/components/theme-mode-switch';
import { useModal } from '@/components/global-modal-provider';
import CloseIcon from '@mui/icons-material/Close';
import { getWarningMessage, shouldShowWarning } from './utils';
import { Menu, MenuItem } from '@/ui/menu';
const PopoverContent = () => {
const { editor, mode, setMode } = useEditor();
return (
<>
<MenuItem
onClick={() => {
setMode(mode === 'page' ? 'edgeless' : 'page');
}}
>
<StyledMenuItemWrapper>
{mode === 'page' ? <EdgelessIcon /> : <PaperIcon />}
Convert to {mode === 'page' ? 'Edgeless' : 'Page'}
</StyledMenuItemWrapper>
</MenuItem>
<Menu
placement="left-start"
content={
<>
<MenuItem
onClick={() => {
editor && editor.contentParser.onExportHtml();
}}
>
<StyledMenuItemWrapper>
<Export2HTML />
Export to HTML
</StyledMenuItemWrapper>
</MenuItem>
<MenuItem
onClick={() => {
editor && editor.contentParser.onExportMarkdown();
}}
>
<StyledMenuItemWrapper>
<Export2Markdown />
Export to Markdown
</StyledMenuItemWrapper>
</MenuItem>
</>
}
>
<MenuItem>
<StyledMenuItemWrapper>
<ExportIcon />
Export
<RightArrow />
</StyledMenuItemWrapper>
</MenuItem>
</Menu>
</>
);
};
const BrowserWarning = ({ onClose }: { onClose: () => void }) => {
return (
<StyledBrowserWarning>
{getWarningMessage()}
<StyledCloseButton onClick={onClose}>
<CloseIcon />
</StyledCloseButton>
</StyledBrowserWarning>
);
};
export const Header = () => {
const [title, setTitle] = useState('');
const [isHover, setIsHover] = useState(false);
const [showWarning, setShowWarning] = useState(shouldShowWarning());
const { contactModalHandler } = useModal();
const { editor } = useEditor();
useEffect(() => {
if (editor) {
setTitle(editor.model.title || '');
editor.model.propsUpdated.on(() => {
setTitle(editor.model.title);
});
}
}, [editor]);
return (
<StyledHeaderContainer hasWarning={showWarning} data-tauri-drag-region>
<BrowserWarning
onClose={() => {
setShowWarning(false);
}}
/>
<StyledHeader hasWarning={showWarning} data-tauri-drag-region>
<StyledLogo
data-testid="left-top-corner-logo"
onClick={() => {
contactModalHandler(true);
}}
>
<LogoIcon />
</StyledLogo>
{title ? (
<StyledTitle
data-tauri-drag-region
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
<EditorModeSwitch
isHover={isHover}
style={{
marginRight: '12px',
}}
/>
<StyledTitleWrapper>{title}</StyledTitleWrapper>
</StyledTitle>
) : null}
<StyledHeaderRightSide>
<ThemeModeSwitch />
<Menu content={<PopoverContent />} placement="bottom-end">
<IconButton>
<MoreIcon />
</IconButton>
</Menu>
</StyledHeaderRightSide>
</StyledHeader>
</StyledHeaderContainer>
);
};
@@ -71,7 +71,7 @@ export const GithubIcon = () => {
</svg> </svg>
); );
}; };
export const DiscordIcon = (props: any) => { export const DiscordIcon = () => {
return ( return (
<svg <svg
width="22" width="22"
@@ -1,5 +1,4 @@
import Modal from '@/ui/modal'; import { Modal, ModalCloseButton, ModalWrapper } from '@/ui/modal';
import CloseIcon from '@mui/icons-material/Close';
import { import {
LogoIcon, LogoIcon,
DocIcon, DocIcon,
@@ -12,7 +11,6 @@ import {
} from './icons'; } from './icons';
import logo from './affine-text-logo.png'; import logo from './affine-text-logo.png';
import { import {
StyledModalWrapper,
StyledBigLink, StyledBigLink,
StyledSmallLink, StyledSmallLink,
StyledSubTitle, StyledSubTitle,
@@ -22,9 +20,9 @@ import {
StyledLogo, StyledLogo,
StyledModalHeader, StyledModalHeader,
StyledModalHeaderLeft, StyledModalHeaderLeft,
StyledCloseButton,
StyledModalFooter, StyledModalFooter,
} from './style'; } from './style';
import bg from '@/components/contact-modal/bg.png';
const linkList = [ const linkList = [
{ {
@@ -62,9 +60,9 @@ const rightLinkList = [
}, },
{ {
icon: <DocIcon />, icon: <DocIcon />,
title: 'Check Our Docs', title: 'AFFiNE Community',
subTitle: 'docs.AFFiNE.pro', subTitle: 'community.affine.pro',
link: 'https://docs.affine.pro', link: 'https://community.affine.pro',
}, },
]; ];
@@ -75,20 +73,24 @@ type TransitionsModalProps = {
export const ContactModal = ({ open, onClose }: TransitionsModalProps) => { export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
return ( return (
<Modal open={open} onClose={onClose}> <Modal open={open} onClose={onClose} data-testid="contact-us-modal-content">
<StyledModalWrapper data-testid="contact-us-modal-content"> <ModalWrapper
width={860}
height={540}
style={{ backgroundImage: `url(${bg.src})` }}
>
<StyledModalHeader> <StyledModalHeader>
<StyledModalHeaderLeft> <StyledModalHeaderLeft>
<StyledLogo src={logo.src} alt="" /> <StyledLogo src={logo.src} alt="" />
<span>Alpha</span> <span>Alpha</span>
</StyledModalHeaderLeft> </StyledModalHeaderLeft>
<StyledCloseButton <ModalCloseButton
top={6}
right={6}
onClick={() => { onClick={() => {
onClose(); onClose();
}} }}
> />
<CloseIcon width={12} height={12} />
</StyledCloseButton>
</StyledModalHeader> </StyledModalHeader>
<StyledContent> <StyledContent>
@@ -107,10 +109,7 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
})} })}
</StyledLeftContainer> </StyledLeftContainer>
<StyledRightContainer> <StyledRightContainer>
<StyledSubTitle> <StyledSubTitle>Get in touch!</StyledSubTitle>
Get in touch! <br />
Join our community.
</StyledSubTitle>
{linkList.map(({ icon, title, link }) => { {linkList.map(({ icon, title, link }) => {
return ( return (
<StyledSmallLink key={title} href={link} target="_blank"> <StyledSmallLink key={title} href={link} target="_blank">
@@ -134,7 +133,7 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
</p> </p>
<p>Copyright &copy; 2022 Toeverything</p> <p>Copyright &copy; 2022 Toeverything</p>
</StyledModalFooter> </StyledModalFooter>
</StyledModalWrapper> </ModalWrapper>
</Modal> </Modal>
); );
}; };
@@ -1,28 +1,11 @@
import { absoluteCenter, displayFlex, styled } from '@/styles'; import { absoluteCenter, displayFlex, styled } from '@/styles';
import bg from './bg.png';
export const StyledModalWrapper = styled('div')(({ theme }) => {
return {
width: '860px',
height: '540px',
backgroundColor: theme.colors.popoverBackground,
backgroundImage: `url(${bg.src})`,
borderRadius: '20px',
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
margin: 'auto',
};
});
export const StyledBigLink = styled('a')(({ theme }) => { export const StyledBigLink = styled('a')(({ theme }) => {
return { return {
width: '320px', width: '334px',
height: '100px', height: '100px',
marginBottom: '48px', marginBottom: '48px',
paddingLeft: '114px', paddingLeft: '90px',
fontSize: '24px', fontSize: '24px',
lineHeight: '36px', lineHeight: '36px',
fontWeight: '600', fontWeight: '600',
@@ -46,7 +29,7 @@ export const StyledBigLink = styled('a')(({ theme }) => {
height: '50px', height: '50px',
marginRight: '40px', marginRight: '40px',
color: theme.colors.primaryColor, color: theme.colors.primaryColor,
...absoluteCenter({ vertical: true, position: { left: '32px' } }), ...absoluteCenter({ vertical: true, position: { left: '20px' } }),
}, },
p: { p: {
width: '100%', width: '100%',
@@ -102,6 +85,7 @@ export const StyledSmallLink = styled('a')(({ theme }) => {
}); });
export const StyledSubTitle = styled('div')(({ theme }) => { export const StyledSubTitle = styled('div')(({ theme }) => {
return { return {
width: '190px',
fontSize: '18px', fontSize: '18px',
fontWeight: '600', fontWeight: '600',
color: theme.colors.textColor, color: theme.colors.textColor,
@@ -136,7 +120,7 @@ export const StyledLogo = styled('img')({
width: 'auto', width: 'auto',
}); });
export const StyledModalHeader = styled('div')(({ theme }) => { export const StyledModalHeader = styled('div')(() => {
return { return {
height: '20px', height: '20px',
marginTop: '36px', marginTop: '36px',
@@ -162,40 +146,6 @@ export const StyledModalHeaderLeft = styled('div')(({ theme }) => {
}; };
}); });
export const StyledCloseButton = styled('div')(({ theme }) => {
return {
width: '60px',
height: '60px',
color: theme.colors.iconColor,
cursor: 'pointer',
...displayFlex('center', 'center'),
position: 'absolute',
right: '0',
top: '0',
// TODO: we need to add @emotion/babel-plugin
'::after': {
content: '""',
width: '30px',
height: '30px',
borderRadius: '6px',
...absoluteCenter({ horizontal: true, vertical: true }),
},
':hover': {
color: theme.colors.primaryColor,
'::after': {
background: theme.colors.hoverBackground,
},
},
svg: {
width: '20px',
height: '20px',
position: 'relative',
zIndex: 1,
},
};
});
export const StyledModalFooter = styled('div')(({ theme }) => { export const StyledModalFooter = styled('div')(({ theme }) => {
return { return {
fontSize: '14px', fontSize: '14px',
@@ -0,0 +1,97 @@
import { styled } from '@/styles';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import Input from '@/ui/input';
import { useState } from 'react';
interface LoginModalProps {
open: boolean;
onClose: () => void;
workSpaceName: string;
}
export const DeleteModal = ({
open,
onClose,
workSpaceName,
}: LoginModalProps) => {
const [canDelete, setCanDelete] = useState<boolean>(true);
const InputChange = (value: string) => {
if (value === workSpaceName) {
setCanDelete(false);
} else {
setCanDelete(true);
}
};
return (
<div>
<Modal open={open} onClose={onClose}>
<ModalWrapper width={620} height={334}>
<Header>
<ModalCloseButton
top={6}
right={6}
onClick={() => {
onClose();
}}
/>
</Header>
<Content>
<ContentTitle>Delete Workspace</ContentTitle>
<div>
This action cannot be undone. This will permanently delete{' '}
{workSpaceName} workspace name along with all its content.
</div>
<Input
onChange={InputChange}
placeholder="Please type “delete” to confirm"
></Input>
</Content>
<Footer>
<Button
style={{ marginRight: '12px' }}
shape="circle"
onClick={() => {
onClose();
}}
>
Cancel
</Button>
<Button shape="circle" type="danger" disabled={canDelete}>
Delete
</Button>
</Footer>
</ModalWrapper>
</Modal>
</div>
);
};
const Header = styled('div')({
position: 'relative',
height: '44px',
});
const Content = styled('div')({
display: 'flex',
padding: '0 48px',
flexDirection: 'column',
alignItems: 'center',
gap: '16px',
});
const ContentTitle = styled('h1')({
fontSize: '20px',
lineHeight: '28px',
fontWeight: 600,
textAlign: 'center',
paddingBottom: '16px',
});
const Footer = styled('div')({
height: '70px',
paddingLeft: '24px',
marginTop: '32px',
textAlign: 'center',
});
@@ -16,7 +16,9 @@ import {
} from './icons'; } from './icons';
import { Tooltip } from '@/ui/tooltip'; import { Tooltip } from '@/ui/tooltip';
import Slide from '@mui/material/Slide'; import Slide from '@mui/material/Slide';
import { useEditor } from '@/components/editor-provider'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { useAppState } from '@/providers/app-state-provider';
import useHistoryUpdated from '@/hooks/use-history-update';
const toolbarList1 = [ const toolbarList1 = [
{ {
@@ -24,6 +26,15 @@ const toolbarList1 = [
icon: <SelectIcon />, icon: <SelectIcon />,
toolTip: 'Select', toolTip: 'Select',
disable: false, disable: false,
callback: () => {
window.dispatchEvent(
new CustomEvent('affine.switch-mouse-mode', {
detail: {
type: 'default',
},
})
);
},
}, },
{ {
flavor: 'text', flavor: 'text',
@@ -34,8 +45,19 @@ const toolbarList1 = [
{ {
flavor: 'shape', flavor: 'shape',
icon: <ShapeIcon />, icon: <ShapeIcon />,
toolTip: 'Shape (coming soon)', toolTip: 'Shape',
disable: true, disable: false,
callback: () => {
window.dispatchEvent(
new CustomEvent('affine.switch-mouse-mode', {
detail: {
type: 'shape',
color: 'black',
shape: 'rectangle',
},
})
);
},
}, },
{ {
flavor: 'sticky', flavor: 'sticky',
@@ -57,34 +79,19 @@ const toolbarList1 = [
disable: true, disable: true,
}, },
]; ];
const toolbarList2 = [
{
flavor: 'undo',
icon: <UndoIcon />,
toolTip: 'Undo',
disable: false,
},
{
flavor: 'redo',
icon: <RedoIcon />,
toolTip: 'Redo',
disable: false,
},
];
const UndoRedo = () => { const UndoRedo = () => {
const [canUndo, setCanUndo] = useState(false); const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false); const [canRedo, setCanRedo] = useState(false);
const { editor } = useEditor(); const { currentPage } = useAppState();
useEffect(() => { const onHistoryUpdated = useHistoryUpdated();
if (!editor) return;
const { page } = editor;
page.signals.historyUpdated.on(() => { useEffect(() => {
onHistoryUpdated(page => {
setCanUndo(page.canUndo); setCanUndo(page.canUndo);
setCanRedo(page.canRedo); setCanRedo(page.canRedo);
}); });
}, [editor]); }, [onHistoryUpdated]);
return ( return (
<StyledToolbarWrapper> <StyledToolbarWrapper>
@@ -92,7 +99,7 @@ const UndoRedo = () => {
<StyledToolbarItem <StyledToolbarItem
disable={!canUndo} disable={!canUndo}
onClick={() => { onClick={() => {
editor?.page?.undo(); currentPage?.undo();
}} }}
> >
<UndoIcon /> <UndoIcon />
@@ -102,7 +109,7 @@ const UndoRedo = () => {
<StyledToolbarItem <StyledToolbarItem
disable={!canRedo} disable={!canRedo}
onClick={() => { onClick={() => {
editor?.page?.redo(); currentPage?.redo();
}} }}
> >
<RedoIcon /> <RedoIcon />
@@ -113,7 +120,7 @@ const UndoRedo = () => {
}; };
export const EdgelessToolbar = () => { export const EdgelessToolbar = () => {
const { mode } = useEditor(); const { mode } = useCurrentPageMeta() || {};
return ( return (
<Slide <Slide
@@ -122,22 +129,25 @@ export const EdgelessToolbar = () => {
mountOnEnter mountOnEnter
unmountOnExit unmountOnExit
> >
<StyledEdgelessToolbar> <StyledEdgelessToolbar aria-label="edgeless-toolbar">
<StyledToolbarWrapper> <StyledToolbarWrapper>
{toolbarList1.map(({ icon, toolTip, flavor, disable }, index) => { {toolbarList1.map(
return ( ({ icon, toolTip, flavor, disable, callback }, index) => {
<Tooltip key={index} content={toolTip} placement="right-start"> return (
<StyledToolbarItem <Tooltip key={index} content={toolTip} placement="right-start">
disable={disable} <StyledToolbarItem
onClick={() => { disable={disable}
console.log('flavor', flavor); onClick={() => {
}} console.log('click toolbar button:', flavor);
> callback?.();
{icon} }}
</StyledToolbarItem> >
</Tooltip> {icon}
); </StyledToolbarItem>
})} </Tooltip>
);
}
)}
</StyledToolbarWrapper> </StyledToolbarWrapper>
<UndoRedo /> <UndoRedo />
</StyledEdgelessToolbar> </StyledEdgelessToolbar>
@@ -2,12 +2,12 @@ import { styled, displayFlex } from '@/styles';
export const StyledEdgelessToolbar = styled.div(({ theme }) => ({ export const StyledEdgelessToolbar = styled.div(({ theme }) => ({
height: '320px', height: '320px',
position: 'fixed', position: 'absolute',
left: '12px', left: '12px',
top: 0, top: 0,
bottom: 0, bottom: 0,
margin: 'auto', margin: 'auto',
zIndex: theme.zIndex.modal, zIndex: theme.zIndex.modal - 1,
})); }));
export const StyledToolbarWrapper = styled.div(({ theme }) => ({ export const StyledToolbarWrapper = styled.div(({ theme }) => ({
@@ -11,9 +11,10 @@ import type {
AnimateRadioProps, AnimateRadioProps,
AnimateRadioItemProps, AnimateRadioItemProps,
} from './type'; } from './type';
import { useTheme } from '@/styles'; import { useTheme } from '@/providers/themeProvider';
import { EdgelessIcon, PaperIcon } from './icons'; import { EdgelessIcon, PaperIcon } from './icons';
import { useEditor } from '@/components/editor-provider'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { usePageHelper } from '@/hooks/use-page-helper';
const PaperItem = ({ active }: { active?: boolean }) => { const PaperItem = ({ active }: { active?: boolean }) => {
const { const {
@@ -66,7 +67,9 @@ export const EditorModeSwitch = ({
style = {}, style = {},
}: AnimateRadioProps) => { }: AnimateRadioProps) => {
const { mode: themeMode } = useTheme(); const { mode: themeMode } = useTheme();
const { mode, setMode } = useEditor(); const { changePageMode } = usePageHelper();
const { trash, mode = 'page', id = '' } = useCurrentPageMeta() || {};
const modifyRadioItemStatus = (): RadioItemStatus => { const modifyRadioItemStatus = (): RadioItemStatus => {
return { return {
left: isHover left: isHover
@@ -99,6 +102,7 @@ export const EditorModeSwitch = ({
data-testid="editor-mode-switcher" data-testid="editor-mode-switcher"
shrink={!isHover} shrink={!isHover}
style={style} style={style}
disabled={!!trash}
> >
<AnimateRadioItem <AnimateRadioItem
isLeft={true} isLeft={true}
@@ -107,7 +111,7 @@ export const EditorModeSwitch = ({
active={mode === 'page'} active={mode === 'page'}
status={radioItemStatus.left} status={radioItemStatus.left}
onClick={() => { onClick={() => {
setMode('page'); changePageMode(id, 'page');
}} }}
onMouseEnter={() => { onMouseEnter={() => {
setRadioItemStatus({ setRadioItemStatus({
@@ -123,11 +127,12 @@ export const EditorModeSwitch = ({
<AnimateRadioItem <AnimateRadioItem
isLeft={false} isLeft={false}
label="Edgeless" label="Edgeless"
data-testid="switch-edgeless-item"
icon={<EdgelessItem />} icon={<EdgelessItem />}
active={mode === 'edgeless'} active={mode === 'edgeless'}
status={radioItemStatus.right} status={radioItemStatus.right}
onClick={() => { onClick={() => {
setMode('edgeless'); changePageMode(id, 'edgeless');
}} }}
onMouseEnter={() => { onMouseEnter={() => {
setRadioItemStatus({ setRadioItemStatus({
@@ -1,45 +1,47 @@
import { displayFlex, keyframes, styled } from '@/styles'; import { displayFlex, keyframes, styled } from '@/styles';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import spring, { toString } from 'css-spring'; import spring, { toString } from 'css-spring';
import type { ItemStatus } from './type'; import type { ItemStatus } from './type';
const ANIMATE_DURATION = 500; const ANIMATE_DURATION = 500;
export const StyledAnimateRadioContainer = styled('div')<{ shrink: boolean }>( export const StyledAnimateRadioContainer = styled('div')<{
({ shrink, theme }) => { shrink: boolean;
const animateScaleStretch = keyframes`${toString( disabled: boolean;
spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' }) }>(({ shrink, theme, disabled }) => {
)}`; const animateScaleStretch = keyframes`${toString(
const animateScaleShrink = keyframes( spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' })
`${toString( )}`;
spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' }) const animateScaleShrink = keyframes(
)}` `${toString(
); spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' })
const shrinkStyle = shrink )}`
? { );
animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`, const shrinkStyle = shrink
background: 'transparent', ? {
} animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`,
: { background: 'transparent',
animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`, }
}; : {
animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`,
};
return { return {
height: '36px', height: '36px',
borderRadius: '18px', borderRadius: '18px',
background: theme.colors.hoverBackground, background: disabled ? 'transparent' : theme.colors.hoverBackground,
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`, transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`,
border: '1px solid transparent', border: '1px solid transparent',
...shrinkStyle, ...(disabled ? { pointerEvents: 'none' } : shrinkStyle),
':hover': { ':hover': {
border: `1px solid ${theme.colors.primaryColor}`, border: disabled ? '' : `1px solid ${theme.colors.primaryColor}`,
}, },
}; };
} });
);
export const StyledMiddleLine = styled('div')<{ export const StyledMiddleLine = styled('div')<{
hidden: boolean; hidden: boolean;
@@ -1,42 +0,0 @@
import type { EditorContainer } from '@blocksuite/editor';
import { createContext, useContext, useEffect, useState } from 'react';
import type { PropsWithChildren } from 'react';
type EditorContextValue = {
editor: EditorContainer | null;
mode: EditorContainer['mode'];
setEditor: (editor: EditorContainer) => void;
setMode: (mode: EditorContainer['mode']) => void;
};
type EditorContextProps = PropsWithChildren<{}>;
export const EditorContext = createContext<EditorContextValue>({
editor: null,
mode: 'page',
setEditor: () => {},
setMode: () => {},
});
export const useEditor = () => useContext(EditorContext);
export const EditorProvider = ({
children,
}: PropsWithChildren<EditorContextProps>) => {
const [editor, setEditor] = useState<EditorContainer | null>(null);
const [mode, setMode] = useState<EditorContainer['mode']>('page');
useEffect(() => {
const event = new CustomEvent('affine.switch-mode', { detail: mode });
window.dispatchEvent(event);
}, [mode]);
return (
<EditorContext.Provider value={{ editor, setEditor, mode, setMode }}>
{children}
</EditorContext.Provider>
);
};
export default EditorProvider;
-79
View File
@@ -1,79 +0,0 @@
import { useEditor } from '@/components/editor-provider';
import '@blocksuite/blocks';
import '@blocksuite/blocks/style';
import type { EditorContainer } from '@blocksuite/editor';
import { createEditor, BlockSchema } from '@blocksuite/editor';
import { Workspace } from '@blocksuite/store';
import { forwardRef, Suspense, useEffect, useRef } from 'react';
import pkg from '../../package.json';
import exampleMarkdown from './example-markdown';
// eslint-disable-next-line react/display-name
const BlockSuiteEditor = forwardRef<EditorContainer>(({}, ref) => {
const containerElement = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerElement.current) {
return;
}
const workspace = new Workspace({});
const page = workspace.createPage('page0').register(BlockSchema);
const editor = createEditor(page);
containerElement.current.appendChild(editor);
if (ref) {
if ('current' in ref) {
ref.current = editor;
} else {
ref(editor);
}
}
return () => {
editor.remove();
};
}, [ref]);
return <div id="editor" style={{ height: '100%' }} ref={containerElement} />;
});
export const Editor = () => {
const editorRef = useRef<EditorContainer>(null);
const { setEditor } = useEditor();
useEffect(() => {
if (!editorRef.current) {
return;
}
setEditor(editorRef.current);
const { page } = editorRef.current as EditorContainer;
const pageId = page.addBlock({
flavour: 'affine:page',
title: 'Welcome to the AFFiNE Alpha',
});
const groupId = page.addBlock({ flavour: 'affine:group' }, pageId);
editorRef.current.clipboard.importMarkdown(exampleMarkdown, `${groupId}`);
page.resetHistory();
}, [setEditor]);
useEffect(() => {
const version = pkg.dependencies['@blocksuite/editor'].substring(1);
console.log(`BlockSuite live demo ${version}`);
}, []);
return (
<Suspense fallback={<div>Error!</div>}>
<BlockSuiteEditor ref={editorRef} />
</Suspense>
);
};
declare global {
interface HTMLElementTagNameMap {
'editor-container': EditorContainer;
}
namespace JSX {
interface IntrinsicElements {
// TODO fix types on react
'editor-container': EditorContainer;
}
}
}
export default Editor;
@@ -1,62 +0,0 @@
const exampleMarkdown = `The AFFiNE Alpha is here! You can also view our [Official Website](https://affine.pro/)!
**What's different in AFFiNE Alpha?**
1. A much ~smoother editing~ experience, with much ~greater stability~;
2. More complete ~Markdown~ support and improved ~keyboard shortcuts~;
3. New features such as ~dark mode~;
* **Switch between view styles using the** ☀ **and** 🌙.
4. ~Clean and modern UI/UX~ design.
**Looking for Markdown syntax or keyboard shortcuts?**
* Find the (?) in the bottom right, then the ️⌨️, to view a full list of \`Keyboard Shortcuts\`
**Have an enjoyable editing experience !!!** 😃
Have some feedback or just want to get in touch? Use the (?), then 🎧 to get in touch and join our communities.
**Still in development**
There are also some things you should consider about this AFFiNE Alpha including some ~limitations~:
* Single page editing - currently editing multiple docs/pages is not supported;
* Changes are not automatically stored, to save changes you should export your data. Options can be found by going to the top right and finding the \`⋮\` icon;
* Without an import/open feature you are still able to access your data by copying it back in.
**Keyboard Shortcuts:**
1. Undo: \`⌘+Z\` or \`Ctrl+Z\`
2. Redo: \`⌘+⇧+Z\` or \`Ctrl+Shift+Z\`
3. Bold: \`⌘+B\` or \`Ctrl+B\`
4. Italic: \`⌘+I\` or \`Ctrl+I\`
5. Underline: \`⌘+U\` or \`Ctrl+U\`
6. Strikethrough: \`⌘+⇧+S\` or \`Ctrl+Shift+S\`
7. Inline code: \`⌘+E\` or \`Ctrl+E\`
8. Link: \`⌘+K\` or \`Ctrl+K\`
9. Body text: \`⌘+⌥+0\` or \`Ctrl+Shift+0\`
10. Heading 1: \`⌘+⌥+1\` or \`Ctrl+Shift+1\`
11. Heading 2: \`⌘+⌥+2\` or \`Ctrl+Shift+2\`
12. Heading 3: \`⌘+⌥+3\` or \`Ctrl+Shift+3\`
13. Heading 4: \`⌘+⌥+4\` or \`Ctrl+Shift+4\`
14. Heading 5: \`⌘+⌥+5\` or \`Ctrl+Shift+5\`
15. Heading 6: \`⌘+⌥+6\` or \`Ctrl+Shift+6\`
16. Increase indent: \`Tab\`
17. Reduce indent: \`⇧+Tab\` or \`Shift+Tab\`
**Markdown Syntax:**
1. Bold: \`**text**\`
2. Italic: \`*text*\`
3. Underline: \`~text~\`
4. Strikethrough: \`~~text~~\`
5. Inline code: \`\` \`text\` \`\`
6. Heading 1: \`# text\`
7. Heading 2: \`## text\`
8. Heading 3: \`### text\`
9. Heading 4: \`#### text\`
10. Heading 5: \`##### text\`
11. Heading 6: \`###### text\`
`;
export default exampleMarkdown;
-87
View File
@@ -1,87 +0,0 @@
import { useState } from 'react';
import {
StyledFAQ,
StyledIconWrapper,
StyledFAQWrapper,
StyledTransformIcon,
} from './style';
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
import Grow from '@mui/material/Grow';
import { Tooltip } from '@/ui/tooltip';
import { useEditor } from '@/components/editor-provider';
import { useModal } from '@/components/global-modal-provider';
import { useTheme } from '@/styles';
export const FAQ = () => {
const [showContent, setShowContent] = useState(false);
const { mode } = useTheme();
const { mode: editorMode } = useEditor();
const { shortcutsModalHandler, contactModalHandler } = useModal();
const isEdgelessDark = mode === 'dark' && editorMode === 'edgeless';
return (
<>
<StyledFAQ
className=""
onMouseEnter={() => {
setShowContent(true);
}}
onMouseLeave={() => {
setShowContent(false);
}}
>
<Grow in={showContent}>
<StyledFAQWrapper>
<Tooltip content="Contact Us" placement="left-end">
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
contactModalHandler(true);
}}
>
<ContactIcon />
</StyledIconWrapper>
</Tooltip>
<Tooltip content="Keyboard Shortcuts" placement="left-end">
<StyledIconWrapper
data-testid="shortcuts-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
shortcutsModalHandler(true);
}}
>
<KeyboardIcon />
</StyledIconWrapper>
</Tooltip>
</StyledFAQWrapper>
</Grow>
<div style={{ position: 'relative' }}>
<StyledIconWrapper
data-testid="faq-icon"
isEdgelessDark={isEdgelessDark}
>
<HelpIcon />
</StyledIconWrapper>
<StyledTransformIcon in={showContent}>
<CloseIcon />
</StyledTransformIcon>
</div>
</StyledFAQ>
</>
);
};
const routesLIst: any = [
{
path: '/',
children: [
{
element: <HelpIcon />,
},
],
},
];
@@ -0,0 +1,47 @@
import { Button } from '@/ui/button';
import { FC, useRef, ChangeEvent, ReactElement } from 'react';
import { styled } from '@/styles';
interface Props {
uploadType?: string;
children?: ReactElement;
accept?: string;
fileChange: (file: File) => void;
}
export const Upload: FC<Props> = props => {
const { fileChange, accept } = props;
const input_ref = useRef<HTMLInputElement>(null);
const _chooseFile = () => {
if (input_ref.current) {
input_ref.current.click();
}
};
const _handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
if (!files) {
return;
}
const file = files[0];
fileChange(file);
if (input_ref.current) {
input_ref.current.value = '';
}
};
return (
<UploadStyle onClick={_chooseFile}>
{props.children ?? <Button>Upload</Button>}
<input
ref={input_ref}
type="file"
style={{ display: 'none' }}
onChange={_handleInputChange}
accept={accept}
/>
</UploadStyle>
);
};
const UploadStyle = styled('div')(() => {
return {
display: 'inline-block',
};
});
@@ -1,53 +0,0 @@
import { createContext, useContext, useState } from 'react';
import type { PropsWithChildren } from 'react';
import ShortcutsModal from './shortcuts-modal';
import ContactModal from '@/components/contact-modal';
type ModalContextValue = {
shortcutsModalHandler: (visible: boolean) => void;
contactModalHandler: (visible: boolean) => void;
};
type ModalContextProps = PropsWithChildren<{}>;
export const ModalContext = createContext<ModalContextValue>({
shortcutsModalHandler: () => {},
contactModalHandler: () => {},
});
export const useModal = () => useContext(ModalContext);
export const ModalProvider = ({
children,
}: PropsWithChildren<ModalContextProps>) => {
const [openContactModal, setOpenContactModal] = useState(false);
const [openShortcutsModal, setOpenShortcutsModal] = useState(false);
return (
<ModalContext.Provider
value={{
shortcutsModalHandler: visible => {
setOpenShortcutsModal(visible);
},
contactModalHandler: visible => {
setOpenContactModal(visible);
},
}}
>
<ContactModal
open={openContactModal}
onClose={() => {
setOpenContactModal(false);
}}
></ContactModal>
<ShortcutsModal
open={openShortcutsModal}
onClose={() => {
setOpenShortcutsModal(false);
}}
></ShortcutsModal>
{children}
</ModalContext.Provider>
);
};
export default ModalProvider;
@@ -0,0 +1,77 @@
import React, { useEffect, useState } from 'react';
import {
StyledSearchArrowWrapper,
StyledSwitchWrapper,
StyledTitle,
StyledTitleWrapper,
} from './styles';
import { Content } from '@/ui/layout';
import { useAppState } from '@/providers/app-state-provider/context';
import EditorModeSwitch from '@/components/editor-mode-switch';
import QuickSearchButton from './quick-search-button';
import Header from './header';
import usePropsUpdated from '@/hooks/use-props-updated';
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
export const EditorHeader = () => {
const [title, setTitle] = useState('');
const [isHover, setIsHover] = useState(false);
const { editor } = useAppState();
const { trash: isTrash = false } = useCurrentPageMeta() || {};
const onPropsUpdated = usePropsUpdated();
useEffect(() => {
onPropsUpdated(editor => {
setTitle(editor.model?.title || 'Untitled');
});
}, [onPropsUpdated]);
useEffect(() => {
setTimeout(() => {
// If first time in, need to wait for editor to be inserted into DOM
setTitle(editor?.model?.title || 'Untitled');
}, 300);
}, [editor]);
return (
<Header
rightItems={
isTrash
? ['trashButtonGroup']
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
}
>
{title && (
<StyledTitle
onMouseEnter={() => {
if (isTrash) return;
setIsHover(true);
}}
onMouseLeave={() => {
if (isTrash) return;
setIsHover(false);
}}
>
<StyledTitleWrapper>
<StyledSwitchWrapper>
<EditorModeSwitch
isHover={isHover}
style={{
marginRight: '12px',
}}
/>
</StyledSwitchWrapper>
<Content ellipsis={true}>{title}</Content>
<StyledSearchArrowWrapper>
<QuickSearchButton />
</StyledSearchArrowWrapper>
</StyledTitleWrapper>
</StyledTitle>
)}
</Header>
);
};
export default EditorHeader;
@@ -0,0 +1,111 @@
import { Menu, MenuItem } from '@/ui/menu';
import { IconButton } from '@/ui/button';
import {
EdgelessIcon,
ExportIcon,
ExportToHtmlIcon,
ExportToMarkdownIcon,
FavouritedIcon,
FavouritesIcon,
MoreVerticalIcon,
PaperIcon,
TrashIcon,
} from '@blocksuite/icons';
import { useAppState } from '@/providers/app-state-provider';
import { usePageHelper } from '@/hooks/use-page-helper';
import { useConfirm } from '@/providers/confirm-provider';
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { toast } from '@/ui/toast';
const PopoverContent = () => {
const { editor } = useAppState();
const { toggleFavoritePage, toggleDeletePage } = usePageHelper();
const { changePageMode } = usePageHelper();
const { confirm } = useConfirm();
const {
mode = 'page',
id = '',
favorite = false,
title = '',
} = useCurrentPageMeta() || {};
return (
<>
<MenuItem
data-testid="editor-option-menu-favorite"
onClick={() => {
toggleFavoritePage(id);
toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites');
}}
icon={favorite ? <FavouritedIcon /> : <FavouritesIcon />}
>
{favorite ? 'Remove' : 'Add'} to favourites
</MenuItem>
<MenuItem
icon={mode === 'page' ? <EdgelessIcon /> : <PaperIcon />}
data-testid="editor-option-menu-edgeless"
onClick={() => {
changePageMode(id, mode === 'page' ? 'edgeless' : 'page');
}}
>
Convert to {mode === 'page' ? 'Edgeless' : 'Page'}
</MenuItem>
<Menu
placement="left-start"
content={
<>
<MenuItem
onClick={() => {
editor && editor.contentParser.onExportHtml();
}}
icon={<ExportToHtmlIcon />}
>
Export to HTML
</MenuItem>
<MenuItem
onClick={() => {
editor && editor.contentParser.onExportMarkdown();
}}
icon={<ExportToMarkdownIcon />}
>
Export to Markdown
</MenuItem>
</>
}
>
<MenuItem icon={<ExportIcon />} isDir={true}>
Export
</MenuItem>
</Menu>
<MenuItem
data-testid="editor-option-menu-delete"
onClick={() => {
confirm({
title: 'Delete page?',
content: `${title || 'Untitled'} will be moved to Trash`,
confirmText: 'Delete',
confirmType: 'danger',
}).then(confirm => {
confirm && toggleDeletePage(id);
toast('Moved to Trash');
});
}}
icon={<TrashIcon />}
>
Delete
</MenuItem>
</>
);
};
export const EditorOptionMenu = () => {
return (
<Menu content={<PopoverContent />} placement="bottom-end">
<IconButton>
<MoreVerticalIcon />
</IconButton>
</Menu>
);
};
export default EditorOptionMenu;
@@ -0,0 +1,25 @@
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/global-modal-provider';
import { useAppState } from '@/providers/app-state-provider/context';
import { IconButton } from '@/ui/button';
export const SyncUser = () => {
const { triggerLoginModal } = useModal();
const appState = useAppState();
return appState.user ? (
<IconButton iconSize="middle" disabled>
<CloudInsyncIcon />
</IconButton>
) : (
<IconButton
iconSize="middle"
data-testid="cloud-unsync-icon"
onClick={triggerLoginModal}
>
<CloudUnsyncedIcon />
</IconButton>
);
};
export default SyncUser;
@@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTheme } from '@/styles'; import { useTheme } from '@/providers/themeProvider';
import { MoonIcon, SunIcon } from './icons'; import { MoonIcon, SunIcon } from './icons';
import { StyledThemeModeSwitch, StyledSwitchItem } from './style'; import { StyledThemeModeSwitch, StyledSwitchItem } from './style';
@@ -1,5 +1,6 @@
import { displayFlex, keyframes, styled } from '@/styles'; import { displayFlex, keyframes, styled } from '@/styles';
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import spring, { toString } from 'css-spring'; import spring, { toString } from 'css-spring';
@@ -0,0 +1,53 @@
import { Button } from '@/ui/button';
import { usePageHelper } from '@/hooks/use-page-helper';
import { useAppState } from '@/providers/app-state-provider';
import { useConfirm } from '@/providers/confirm-provider';
import { useRouter } from 'next/router';
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
export const TrashButtonGroup = () => {
const { permanentlyDeletePage } = usePageHelper();
const { currentWorkspaceId } = useAppState();
const { toggleDeletePage } = usePageHelper();
const { confirm } = useConfirm();
const router = useRouter();
const { id = '' } = useCurrentPageMeta() || {};
return (
<>
<Button
bold={true}
shape="round"
style={{ marginRight: '24px' }}
onClick={() => {
toggleDeletePage(id);
}}
>
Restore it
</Button>
<Button
bold={true}
shape="round"
type="danger"
onClick={() => {
confirm({
title: 'Permanently delete',
content:
"Once deleted, you can't undo this action. Do you confirm?",
confirmText: 'Delete',
confirmType: 'danger',
}).then(confirm => {
if (confirm) {
router.push(`/workspace/${currentWorkspaceId}/all`);
permanentlyDeletePage(id);
}
});
}}
>
Delete permanently
</Button>
</>
);
};
export default TrashButtonGroup;
@@ -0,0 +1,71 @@
import React, { PropsWithChildren, ReactNode, useState } from 'react';
import {
StyledHeader,
StyledHeaderRightSide,
StyledHeaderContainer,
StyledBrowserWarning,
StyledCloseButton,
} from './styles';
import CloseIcon from '@mui/icons-material/Close';
import { getWarningMessage, shouldShowWarning } from './utils';
import EditorOptionMenu from './header-right-items/editor-option-menu';
import TrashButtonGroup from './header-right-items/trash-button-group';
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
// import SyncUser from './header-right-items/sync-user';
const BrowserWarning = ({
show,
onClose,
}: {
show: boolean;
onClose: () => void;
}) => {
return (
<StyledBrowserWarning show={show}>
{getWarningMessage()}
<StyledCloseButton onClick={onClose}>
<CloseIcon />
</StyledCloseButton>
</StyledBrowserWarning>
);
};
type HeaderRightItemNames =
| 'editorOptionMenu'
| 'trashButtonGroup'
| 'themeModeSwitch'
| 'syncUser';
const HeaderRightItems: Record<HeaderRightItemNames, ReactNode> = {
editorOptionMenu: <EditorOptionMenu key="editorOptionMenu" />,
trashButtonGroup: <TrashButtonGroup key="trashButtonGroup" />,
themeModeSwitch: <ThemeModeSwitch key="themeModeSwitch" />,
syncUser: null,
};
export const Header = ({
rightItems = ['syncUser'],
children,
}: PropsWithChildren<{ rightItems?: HeaderRightItemNames[] }>) => {
const [showWarning, setShowWarning] = useState(shouldShowWarning());
return (
<StyledHeaderContainer hasWarning={showWarning}>
<BrowserWarning
show={showWarning}
onClose={() => {
setShowWarning(false);
}}
/>
<StyledHeader hasWarning={showWarning} data-testid="editor-header-items">
{children}
<StyledHeaderRightSide>
{rightItems.map(itemName => {
return HeaderRightItems[itemName];
})}
</StyledHeaderRightSide>
</StyledHeader>
</StyledHeaderContainer>
);
};
export default Header;
@@ -0,0 +1,3 @@
export * from './header';
export * from './editor-header';
export * from './page-list-header';
@@ -0,0 +1,21 @@
import { PropsWithChildren, ReactNode } from 'react';
import Header from './header';
import { StyledPageListTittleWrapper } from './styles';
import QuickSearchButton from './quick-search-button';
export type PageListHeaderProps = PropsWithChildren<{
icon?: ReactNode;
}>;
export const PageListHeader = ({ icon, children }: PageListHeaderProps) => {
return (
<Header>
<StyledPageListTittleWrapper>
{icon}
{children}
<QuickSearchButton style={{ marginLeft: '5px' }} />
</StyledPageListTittleWrapper>
</Header>
);
};
export default PageListHeader;
@@ -0,0 +1,29 @@
import React from 'react';
import { IconButton, IconButtonProps } from '@/ui/button';
import { Tooltip } from '@/ui/tooltip';
import { ArrowDownIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/global-modal-provider';
export const QuickSearchButton = ({
onClick,
...props
}: Omit<IconButtonProps, 'children'>) => {
const { triggerQuickSearchModal } = useModal();
return (
<Tooltip content="Switch to" placement="bottom">
<IconButton
data-testid="header-quickSearchButton"
{...props}
onClick={e => {
onClick?.(e);
triggerQuickSearchModal();
}}
>
<ArrowDownIcon />
</IconButton>
</Tooltip>
);
};
export default QuickSearchButton;
@@ -1,5 +1,4 @@
import { displayFlex, styled } from '@/styles'; import { displayFlex, styled } from '@/styles';
import { MenuItem } from '@/ui/menu';
export const StyledHeaderContainer = styled.div<{ hasWarning: boolean }>( export const StyledHeaderContainer = styled.div<{ hasWarning: boolean }>(
({ hasWarning }) => { ({ hasWarning }) => {
@@ -10,14 +9,14 @@ export const StyledHeaderContainer = styled.div<{ hasWarning: boolean }>(
} }
); );
export const StyledHeader = styled.div<{ hasWarning: boolean }>( export const StyledHeader = styled.div<{ hasWarning: boolean }>(
({ hasWarning, theme }) => { ({ hasWarning }) => {
return { return {
height: '60px', height: '60px',
width: '100vw', width: '100%',
...displayFlex('space-between', 'center'), ...displayFlex('flex-end', 'center'),
background: 'var(--affine-page-background)', background: 'var(--affine-page-background)',
transition: 'background-color 0.5s', transition: 'background-color 0.5s',
position: 'fixed', position: 'absolute',
left: '0', left: '0',
top: hasWarning ? '36px' : '0', top: hasWarning ? '36px' : '0',
padding: '0 22px', padding: '0 22px',
@@ -41,20 +40,10 @@ export const StyledTitle = styled('div')(({ theme }) => ({
export const StyledTitleWrapper = styled('div')({ export const StyledTitleWrapper = styled('div')({
maxWidth: '720px', maxWidth: '720px',
overflow: 'hidden', height: '100%',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
position: 'relative', position: 'relative',
});
export const StyledLogo = styled('div')(({ theme }) => ({
color: theme.colors.primaryColor,
width: '60px',
height: '60px',
cursor: 'pointer',
marginLeft: '-22px',
...displayFlex('center', 'center'), ...displayFlex('center', 'center'),
})); });
export const StyledHeaderRightSide = styled('div')({ export const StyledHeaderRightSide = styled('div')({
height: '100%', height: '100%',
@@ -62,54 +51,23 @@ export const StyledHeaderRightSide = styled('div')({
alignItems: 'center', alignItems: 'center',
}); });
export const StyledMenuItemWrapper = styled.div(({ theme }) => { export const StyledBrowserWarning = styled.div<{ show: boolean }>(
return { ({ theme, show }) => {
height: '32px', return {
position: 'relative', backgroundColor: theme.colors.warningBackground,
cursor: 'pointer', color: theme.colors.warningColor,
...displayFlex('flex-start', 'center'), height: '36px',
svg: { width: '100vw',
width: '16px', fontSize: theme.font.sm,
height: '16px', position: 'fixed',
marginRight: '14px', left: '0',
}, top: '0',
'svg:nth-child(2)': { display: show ? 'flex' : 'none',
position: 'absolute', justifyContent: 'center',
right: 0, alignItems: 'center',
top: 0, };
bottom: 0, }
margin: 'auto', );
},
};
});
export const IconButton = styled('div')(({ theme }) => {
return {
width: '32px',
height: '32px',
...displayFlex('center', 'center'),
color: theme.colors.iconColor,
borderRadius: '5px',
':hover': {
color: theme.colors.primaryColor,
background: theme.colors.hoverBackground,
},
};
});
export const StyledBrowserWarning = styled.div(({ theme }) => {
return {
backgroundColor: theme.colors.warningBackground,
color: theme.colors.warningColor,
height: '36px',
width: '100vw',
fontSize: theme.font.sm,
position: 'fixed',
left: '0',
top: '0',
...displayFlex('center', 'center'),
};
});
export const StyledCloseButton = styled.div(({ theme }) => { export const StyledCloseButton = styled.div(({ theme }) => {
return { return {
@@ -130,3 +88,36 @@ export const StyledCloseButton = styled.div(({ theme }) => {
}, },
}; };
}); });
export const StyledSwitchWrapper = styled.div(() => {
return {
position: 'absolute',
right: '100%',
top: 0,
bottom: 0,
margin: 'auto',
...displayFlex('center', 'center'),
};
});
export const StyledSearchArrowWrapper = styled.div(() => {
return {
position: 'absolute',
left: 'calc(100% + 4px)',
top: 0,
bottom: 0,
margin: 'auto',
...displayFlex('center', 'center'),
};
});
export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => {
return {
fontSize: theme.font.sm,
color: theme.colors.textColor,
'>svg': {
fontSize: '20px',
marginRight: '12px',
},
};
});
@@ -0,0 +1,86 @@
import { useState } from 'react';
import {
StyledIsland,
StyledIconWrapper,
StyledIslandWrapper,
StyledTransformIcon,
} from './style';
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
import Grow from '@mui/material/Grow';
import { Tooltip } from '@/ui/tooltip';
import { useModal } from '@/providers/global-modal-provider';
import { useTheme } from '@/providers/themeProvider';
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
export type IslandItemNames = 'contact' | 'shortcuts';
export const HelpIsland = ({
showList = ['contact', 'shortcuts'],
}: {
showList?: IslandItemNames[];
}) => {
const [showContent, setShowContent] = useState(false);
const { mode } = useTheme();
const { mode: editorMode } = useCurrentPageMeta() || {};
const { triggerShortcutsModal, triggerContactModal } = useModal();
const isEdgelessDark = mode === 'dark' && editorMode === 'edgeless';
return (
<>
<StyledIsland
className=""
onMouseEnter={() => {
setShowContent(true);
}}
onMouseLeave={() => {
setShowContent(false);
}}
>
<Grow in={showContent}>
<StyledIslandWrapper>
{showList.includes('contact') && (
<Tooltip content="Contact Us" placement="left-end">
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
triggerContactModal();
}}
>
<ContactIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('shortcuts') && (
<Tooltip content="Keyboard Shortcuts" placement="left-end">
<StyledIconWrapper
data-testid="shortcuts-icon"
isEdgelessDark={isEdgelessDark}
onClick={() => {
setShowContent(false);
triggerShortcutsModal();
}}
>
<KeyboardIcon />
</StyledIconWrapper>
</Tooltip>
)}
</StyledIslandWrapper>
</Grow>
<div style={{ position: 'relative' }}>
<StyledIconWrapper
isEdgelessDark={isEdgelessDark}
data-testid="faq-icon"
>
<HelpIcon />
</StyledIconWrapper>
<StyledTransformIcon in={showContent}>
<CloseIcon />
</StyledTransformIcon>
</div>
</StyledIsland>
</>
);
};
export default HelpIsland;
@@ -1,6 +1,6 @@
import { displayFlex, styled } from '@/styles'; import { displayFlex, styled } from '@/styles';
export const StyledFAQ = styled('div')(({ theme }) => { export const StyledIsland = styled('div')(({ theme }) => {
return { return {
width: '32px', width: '32px',
height: '32px', height: '32px',
@@ -12,24 +12,24 @@ export const StyledFAQ = styled('div')(({ theme }) => {
zIndex: theme.zIndex.popover, zIndex: theme.zIndex.popover,
}; };
}); });
export const StyledTransformIcon = styled.div<{ in: boolean }>( export const StyledTransformIcon = styled('div', {
({ in: isIn, theme }) => ({ shouldForwardProp: prop => prop !== 'in',
height: '32px', })<{ in: boolean }>(({ in: isIn, theme }) => ({
width: '32px', height: '32px',
borderRadius: '50%', width: '32px',
position: 'absolute', borderRadius: '50%',
left: '0', position: 'absolute',
right: '0', left: '0',
bottom: '0', right: '0',
top: '0', bottom: '0',
margin: 'auto', top: '0',
...displayFlex('center', 'center'), margin: 'auto',
opacity: isIn ? 1 : 0, ...displayFlex('center', 'center'),
backgroundColor: isIn opacity: isIn ? 1 : 0,
? theme.colors.hoverBackground backgroundColor: isIn
: theme.colors.pageBackground, ? theme.colors.hoverBackground
}) : theme.colors.pageBackground,
); }));
export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>( export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>(
({ theme, isEdgelessDark }) => { ({ theme, isEdgelessDark }) => {
return { return {
@@ -57,7 +57,7 @@ export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>(
} }
); );
export const StyledFAQWrapper = styled('div')(({ theme }) => { export const StyledIslandWrapper = styled('div')(({ theme }) => {
return { return {
position: 'absolute', position: 'absolute',
bottom: '100%', bottom: '100%',
@@ -0,0 +1,65 @@
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { StyledButtonWrapper, StyledTitle } from './styles';
import { Button } from '@/ui/button';
import { Wrapper, Content } from '@/ui/layout';
import Loading from '@/components/loading';
import { useEffect, useState } from 'react';
type ImportModalProps = {
open: boolean;
onClose: () => void;
};
export const ImportModal = ({ open, onClose }: ImportModalProps) => {
const [status, setStatus] = useState<'unImported' | 'importing'>('importing');
useEffect(() => {
if (status === 'importing') {
setTimeout(() => {
setStatus('unImported');
}, 1500);
}
}, [status]);
return (
<Modal open={open} onClose={onClose}>
<ModalWrapper width={460} minHeight={240}>
<ModalCloseButton onClick={onClose} />
<StyledTitle>Import</StyledTitle>
{status === 'unImported' && (
<StyledButtonWrapper>
<Button
onClick={() => {
setStatus('importing');
}}
>
Markdown
</Button>
<Button
onClick={() => {
setStatus('importing');
}}
>
HTML
</Button>
</StyledButtonWrapper>
)}
{status === 'importing' && (
<Wrapper
wrap={true}
justifyContent="center"
style={{ marginTop: 22, paddingBottom: '32px' }}
>
<Loading size={25}></Loading>
<Content align="center" weight="500">
OOOOPS! Sorry forgot to remind you that we are working on the
import function
</Content>
</Wrapper>
)}
</ModalWrapper>
</Modal>
);
};
export default ImportModal;
@@ -0,0 +1,25 @@
import { styled } from '@/styles';
export const StyledTitle = styled.div(({ theme }) => {
return {
fontSize: theme.font.h6,
fontWeight: 600,
textAlign: 'center',
marginTop: '45px',
color: theme.colors.popoverColor,
};
});
export const StyledButtonWrapper = styled.div(() => {
return {
width: '280px',
margin: '24px auto 0',
button: {
display: 'block',
width: '100%',
':not(:last-child)': {
marginBottom: '16px',
},
},
};
});
@@ -0,0 +1,251 @@
import { EmailIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import Input from '@/ui/input';
import { useState } from 'react';
import { inviteMember, getUserByEmail } from '@affine/data-services';
import { Avatar } from '@mui/material';
interface LoginModalProps {
open: boolean;
onClose: () => void;
workspaceId: string;
onInviteSuccess: () => void;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const debounce = <T extends (...args: any) => any>(
fn: T,
time?: number,
immediate?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): ((...args: any) => any) => {
let timeoutId: null | number;
let defaultImmediate = immediate || false;
const delay = time || 300;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (...args: any) => {
if (defaultImmediate) {
fn.apply(this, args);
defaultImmediate = false;
return;
}
if (timeoutId) {
clearTimeout(timeoutId);
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
timeoutId = setTimeout(() => {
fn.apply(this, args);
timeoutId = null;
}, delay);
};
};
const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/;
export const InviteMembers = ({
open,
onClose,
workspaceId,
onInviteSuccess,
}: LoginModalProps) => {
const [email, setEmail] = useState<string>('');
const [showMember, setShowMember] = useState<boolean>(false);
const [showTip, setShowTip] = useState<boolean>(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [userData, setUserData] = useState<any>({});
const inputChange = (value: string) => {
setEmail(value);
setShowMember(true);
if (gmailReg.test(value)) {
setShowTip(false);
debounce(
() => {
getUserByEmail({
email: value,
workspace_id: workspaceId,
}).then(data => {
if (data?.name) {
setUserData(data);
setShowTip(false);
}
});
},
300,
true
)();
} else {
setShowTip(true);
}
};
return (
<div>
<Modal open={open} onClose={onClose}>
<ModalWrapper width={460} height={236}>
<Header>
<ModalCloseButton
top={6}
right={6}
onClick={() => {
onClose();
}}
/>
</Header>
<Content>
<ContentTitle>Invite members</ContentTitle>
<InviteBox>
<Input
width={360}
value={email}
onChange={inputChange}
onBlur={() => {
setShowMember(false);
}}
placeholder="Search mail (Gmail support only)"
></Input>
{showMember ? (
<Members>
{showTip ? (
<NoFind>Non-Gmail is not supported</NoFind>
) : (
<Member>
{userData?.avatar_url ? (
<Avatar src={userData?.avatar_url}></Avatar>
) : (
<MemberIcon>
<EmailIcon></EmailIcon>
</MemberIcon>
)}
<Email>{email}</Email>
{/* <div>invited</div> */}
</Member>
)}
</Members>
) : (
<></>
)}
</InviteBox>
</Content>
<Footer>
<Button
shape="circle"
type="primary"
onClick={() => {
inviteMember({ id: workspaceId, email: email })
.then(() => {
onClose();
onInviteSuccess && onInviteSuccess();
})
.catch(err => {
// toast('Invite failed');
console.log(err);
});
}}
>
Invite
</Button>
</Footer>
</ModalWrapper>
</Modal>
</div>
);
};
const Header = styled('div')({
position: 'relative',
height: '44px',
});
const Content = styled('div')({
display: 'flex',
padding: '0 48px',
flexDirection: 'column',
alignItems: 'center',
gap: '16px',
});
const ContentTitle = styled('h1')({
fontSize: '20px',
lineHeight: '28px',
fontWeight: 600,
textAlign: 'center',
paddingBottom: '16px',
});
const Footer = styled('div')({
height: '70px',
paddingLeft: '24px',
marginTop: '32px',
textAlign: 'center',
});
const InviteBox = styled('div')({
position: 'relative',
});
const Members = styled('div')(({ theme }) => {
return {
position: 'absolute',
width: '100%',
background: theme.colors.pageBackground,
textAlign: 'left',
zIndex: 1,
borderRadius: '0px 10px 10px 10px',
height: '56px',
padding: '8px 12px',
input: {
'&::placeholder': {
color: theme.colors.placeHolderColor,
},
},
};
});
const NoFind = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
lineHeight: '40px',
userSelect: 'none',
width: '100%',
};
});
const Member = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
lineHeight: '40px',
userSelect: 'none',
display: 'flex',
};
});
const MemberIcon = styled('div')(({ theme }) => {
return {
width: '40px',
height: '40px',
borderRadius: '50%',
color: theme.colors.primaryColor,
background: '#F5F5F5',
marginRight: '8px',
textAlign: 'center',
lineHeight: '45px',
// icon size
fontSize: '20px',
overflow: 'hidden',
img: {
width: '100%',
height: '100%',
},
};
});
const Email = styled('div')(({ theme }) => {
return {
flex: '1',
color: theme.colors.popoverColor,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
+2 -13
View File
@@ -1,14 +1,3 @@
import { StyledLoading, StyledLoadingItem } from './styled'; import Loading from './loading';
export * from './page-loading';
export const Loading = () => {
return (
<StyledLoading>
<StyledLoadingItem />
<StyledLoadingItem />
<StyledLoadingItem />
<StyledLoadingItem />
</StyledLoading>
);
};
export default Loading; export default Loading;
@@ -0,0 +1,20 @@
import {
StyledLoadingWrapper,
StyledLoading,
StyledLoadingItem,
} from './styled';
export const Loading = ({ size = 40 }: { size?: number }) => {
return (
<StyledLoadingWrapper size={size}>
<StyledLoading>
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
</StyledLoading>
</StyledLoadingWrapper>
);
};
export default Loading;
@@ -0,0 +1,31 @@
import { styled } from '@/styles';
import Loading from './loading';
// Used for the full page loading
const StyledLoadingContainer = styled('div')(() => {
return {
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: '#6880FF',
h1: {
fontSize: '2em',
marginTop: '15px',
fontWeight: '600',
},
};
});
export const PageLoading = ({ text = 'Loading...' }: { text?: string }) => {
return (
<StyledLoadingContainer>
<div className="wrapper">
<Loading />
<h1>{text}</h1>
</div>
</StyledLoadingContainer>
);
};
export default PageLoading;
+19 -22
View File
@@ -1,14 +1,19 @@
import { styled } from '@/styles'; import { styled } from '@/styles';
// Inspired by https://codepen.io/graphilla/pen/rNvBMYY // Inspired by https://codepen.io/graphilla/pen/rNvBMYY
const loadingItemSize = '40px'; export const StyledLoadingWrapper = styled.div<{ size?: number }>(
({ size = 40 }) => {
return {
width: size * 4,
height: size * 4,
position: 'relative',
};
}
);
export const StyledLoading = styled.div` export const StyledLoading = styled.div`
position: relative; position: absolute;
left: 50%; left: 25%;
transform: rotateX(55deg) rotateZ(-45deg) top: 25%;
translate(calc(${loadingItemSize} * -2), 0);
margin-bottom: calc(3 * ${loadingItemSize});
@keyframes slide { @keyframes slide {
0% { 0% {
transform: translate(var(--sx), var(--sy)); transform: translate(var(--sx), var(--sy));
@@ -21,24 +26,14 @@ export const StyledLoading = styled.div`
transform: translate(var(--ex), var(--ey)); transform: translate(var(--ex), var(--ey));
} }
} }
@keyframes load {
20% {
content: '.';
}
40% {
content: '..';
}
80%,
100% {
content: '...';
}
}
`; `;
export const StyledLoadingItem = styled.div` export const StyledLoadingItem = styled.div<{ size: number }>(
({ size = 40 }) => {
return `
position: absolute; position: absolute;
width: ${loadingItemSize}; width: ${size}px;
height: ${loadingItemSize}; height: ${size}px;
background: #9dacf9; background: #9dacf9;
animation: slide 0.9s cubic-bezier(0.65, 0.53, 0.59, 0.93) infinite; animation: slide 0.9s cubic-bezier(0.65, 0.53, 0.59, 0.93) infinite;
@@ -92,3 +87,5 @@ export const StyledLoadingItem = styled.div`
--ey: -50%; --ey: -50%;
} }
`; `;
}
);
@@ -0,0 +1,118 @@
import { signInWithGoogle } from '@affine/data-services';
import { styled } from '@/styles';
import { Button } from '@/ui/button';
import { useModal } from '@/providers/global-modal-provider';
import { GoogleIcon, StayLogOutIcon } from './icons';
export const GoogleLoginButton = () => {
const { triggerLoginModal } = useModal();
return (
<StyledGoogleButton
onClick={() => {
signInWithGoogle()
.then(() => {
triggerLoginModal();
})
.catch(error => {
console.log('sign google error', error);
});
}}
>
<ButtonWrapper>
<IconWrapper>
<GoogleIcon />
</IconWrapper>
<TextWrapper>
<Title>Continue with Google</Title>
<Description>Set up an AFFiNE account to sync data</Description>
</TextWrapper>
</ButtonWrapper>
</StyledGoogleButton>
);
};
export const StayLogOutButton = () => {
return (
<StyledStayLogOutButton>
<ButtonWrapper>
<IconWrapper>
<StayLogOutIcon />
</IconWrapper>
<TextWrapper>
<Title>Stay logged out</Title>
<Description>All changes are saved locally</Description>
</TextWrapper>
</ButtonWrapper>
</StyledStayLogOutButton>
);
};
const StyledGoogleButton = styled(Button)(() => {
return {
width: '361px',
height: '56px',
padding: '4px',
background: '#6880FF',
color: '#fff',
'& > span': {
marginLeft: 0,
},
':hover': {
background: '#516BF4',
color: '#fff',
},
};
});
const StyledStayLogOutButton = styled(Button)(() => {
return {
width: '361px',
height: '56px',
padding: '4px',
'& > span': {
marginLeft: 0,
},
':hover': {
borderColor: '#6880FF',
},
};
});
const ButtonWrapper = styled('div')({
display: 'flex',
flexDirection: 'row',
});
const IconWrapper = styled('div')({
width: '48px',
height: '48px',
flex: '0 48px',
borderRadius: '5px',
overflow: 'hidden',
marginRight: '12px',
});
const TextWrapper = styled('div')({
flex: 1,
textAlign: 'left',
});
const Title = styled('h1')(() => {
return {
fontSize: '18px',
lineHeight: '26px',
fontWeight: 500,
};
});
const Description = styled('p')(() => {
return {
fontSize: '16px',
lineHeight: '22px',
fontWeight: 400,
};
});
@@ -0,0 +1,6 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.327 16.3285 15.1115 18 12.5 18C9.1865 18 6.5 15.3135 6.5 12C6.5 8.6865 9.1865 6 12.5 6C14.0295 6 15.421 6.577 16.4805 7.5195L19.309 4.691C17.523 3.0265 15.134 2 12.5 2C6.9775 2 2.5 6.4775 2.5 12C2.5 17.5225 6.9775 22 12.5 22C18.0225 22 22.5 17.5225 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z" fill="#FFC107"/>
<path d="M3.65234 7.3455L6.93784 9.755C7.82684 7.554 9.97984 6 12.4993 6C14.0288 6 15.4203 6.577 16.4798 7.5195L19.3083 4.691C17.5223 3.0265 15.1333 2 12.4993 2C8.65834 2 5.32734 4.1685 3.65234 7.3455Z" fill="#FF3D00"/>
<path d="M12.5002 22.0003C15.0832 22.0003 17.4302 21.0118 19.2047 19.4043L16.1097 16.7853C15.0719 17.5745 13.8039 18.0014 12.5002 18.0003C9.89916 18.0003 7.69066 16.3418 6.85866 14.0273L3.59766 16.5398C5.25266 19.7783 8.61366 22.0003 12.5002 22.0003Z" fill="#4CAF50"/>
<path d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.7571 15.1082 17.0467 16.0766 16.108 16.7855L16.1095 16.7845L19.2045 19.4035C18.9855 19.6025 22.5 17 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z" fill="#1976D2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,42 @@
import { CloudUnsyncedIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import GoogleSvg from './google.svg';
export const GoogleIcon = () => {
return (
<GoogleIconWrapper>
<picture>
<img src={GoogleSvg.src} alt="Google" />
</picture>
</GoogleIconWrapper>
);
};
const GoogleIconWrapper = styled('div')(({ theme }) => ({
width: '48px',
height: '48px',
background: theme.colors.pageBackground,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}));
export const StayLogOutIcon = () => {
return (
<StayLogOutWrapper>
<CloudUnsyncedIcon />
</StayLogOutWrapper>
);
};
const StayLogOutWrapper = styled('div')(({ theme }) => {
return {
width: '48px',
height: '48px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '24px',
background: theme.colors.hoverBackground,
};
});
@@ -0,0 +1,68 @@
import { ResetIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { TextButton } from '@/ui/button';
import { GoogleLoginButton, StayLogOutButton } from './LoginOptionButton';
interface LoginModalProps {
open: boolean;
onClose: () => void;
}
export const LoginModal = ({ open, onClose }: LoginModalProps) => {
return (
<Modal open={open} onClose={onClose} data-testid="login-modal">
<ModalWrapper width={620} height={334}>
<Header>
<ModalCloseButton
top={6}
right={6}
onClick={() => {
onClose();
}}
/>
</Header>
<Content>
<ContentTitle>Currently not logged in</ContentTitle>
<GoogleLoginButton />
<StayLogOutButton />
</Content>
<Footer>
<TextButton icon={<StyledResetIcon />}>Clear local data</TextButton>
</Footer>
</ModalWrapper>
</Modal>
);
};
const Header = styled('div')({
position: 'relative',
height: '44px',
});
const Content = styled('div')({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '16px',
});
const ContentTitle = styled('h1')({
fontSize: '20px',
lineHeight: '28px',
fontWeight: 600,
textAlign: 'center',
paddingBottom: '16px',
});
const Footer = styled('div')({
height: '70px',
paddingLeft: '24px',
marginTop: '32px',
});
const StyledResetIcon = styled(ResetIcon)({
marginRight: '12px',
width: '20px',
height: '20px',
});
@@ -1,14 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import Modal from '@/ui/modal'; import Modal, { ModalCloseButton, ModalWrapper } from '@/ui/modal';
import getIsMobile from '@/utils/get-is-mobile'; import getIsMobile from '@/utils/get-is-mobile';
import { import { StyledButton, StyledContent, StyledTitle } from './styles';
ModalWrapper, import bg from './bg.png';
StyledButton,
StyledCloseButton,
StyledContent,
StyledTitle,
} from './styles';
import CloseIcon from '@mui/icons-material/Close';
export const MobileModal = () => { export const MobileModal = () => {
const [showModal, setShowModal] = useState(getIsMobile()); const [showModal, setShowModal] = useState(getIsMobile());
return ( return (
@@ -18,14 +12,18 @@ export const MobileModal = () => {
setShowModal(false); setShowModal(false);
}} }}
> >
<ModalWrapper> <ModalWrapper
<StyledCloseButton width={348}
height={388}
style={{ backgroundImage: `url(${bg.src})` }}
>
<ModalCloseButton
size={[30, 30]}
iconSize={[20, 20]}
onClick={() => { onClick={() => {
setShowModal(false); setShowModal(false);
}} }}
> />
<CloseIcon />
</StyledCloseButton>
<StyledTitle>Ooops!</StyledTitle> <StyledTitle>Ooops!</StyledTitle>
<StyledContent> <StyledContent>
@@ -1,38 +1,6 @@
import { displayFlex, styled } from '@/styles'; import { displayFlex, styled } from '@/styles';
import bg from './bg.png';
export const ModalWrapper = styled.div(({ theme }) => { export const StyledTitle = styled.div(() => {
return {
width: '348px',
height: '388px',
background: theme.colors.popoverBackground,
borderRadius: '28px',
position: 'relative',
backgroundImage: `url(${bg.src})`,
};
});
export const StyledCloseButton = styled.div(({ theme }) => {
return {
width: '66px',
height: '66px',
color: theme.colors.iconColor,
cursor: 'pointer',
...displayFlex('center', 'center'),
position: 'absolute',
right: '0',
top: '0',
svg: {
width: '15px',
height: '15px',
position: 'relative',
zIndex: 1,
},
};
});
export const StyledTitle = styled.div(({ theme }) => {
return { return {
...displayFlex('center', 'center'), ...displayFlex('center', 'center'),
fontSize: '20px', fontSize: '20px',
@@ -42,7 +10,7 @@ export const StyledTitle = styled.div(({ theme }) => {
}; };
}); });
export const StyledContent = styled.div(({ theme }) => { export const StyledContent = styled.div(() => {
return { return {
padding: '0 40px', padding: '0 40px',
marginTop: '32px', marginTop: '32px',
@@ -0,0 +1,27 @@
import localizedFormat from 'dayjs/plugin/localizedFormat';
import dayjs from 'dayjs';
import { PageMeta } from '@/providers/app-state-provider';
import { TableCell } from '@/ui/table';
import React from 'react';
dayjs.extend(localizedFormat);
export const DateCell = ({
pageMeta,
dateKey,
backupKey = '',
}: {
pageMeta: PageMeta;
dateKey: keyof PageMeta;
backupKey?: keyof PageMeta;
}) => {
// dayjs().format('L LT');
const value = pageMeta[dateKey] ?? pageMeta[backupKey];
return (
<TableCell ellipsis={true}>
{value ? dayjs(value as string).format('YYYY-MM-DD HH:mm') : '--'}
</TableCell>
);
};
export default DateCell;
@@ -0,0 +1,17 @@
import React from 'react';
import { Empty } from '@/ui/empty';
export const PageListEmpty = () => {
return (
<div style={{ textAlign: 'center' }}>
<Empty
width={800}
height={300}
sx={{ marginTop: '100px', marginBottom: '30px' }}
/>
<p>Tips: Click Add to Favourites/Trash and the page will appear here.</p>
<p>(Designer is grappling with designing)</p>
</div>
);
};
export default PageListEmpty;
@@ -0,0 +1,145 @@
import { PageMeta } from '@/providers/app-state-provider';
import {
FavouritedIcon,
FavouritesIcon,
PaperIcon,
EdgelessIcon,
} from '@blocksuite/icons';
import {
StyledTableContainer,
StyledTableRow,
StyledTitleLink,
StyledTitleWrapper,
} from './styles';
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table';
import { OperationCell, TrashOperationCell } from './operation-cell';
import Empty from './empty';
import { Content } from '@/ui/layout';
import React from 'react';
import DateCell from '@/components/page-list/date-cell';
import { IconButton } from '@/ui/button';
import { Tooltip } from '@/ui/tooltip';
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider/context';
import { toast } from '@/ui/toast';
import { usePageHelper } from '@/hooks/use-page-helper';
import { useTheme } from '@/providers/themeProvider';
const FavoriteTag = ({
pageMeta: { favorite, id },
}: {
pageMeta: PageMeta;
}) => {
const { toggleFavoritePage } = usePageHelper();
const { theme } = useTheme();
return (
<Tooltip
content={favorite ? 'Favourited' : 'Favourite'}
placement="top-start"
>
<IconButton
darker={true}
iconSize={[20, 20]}
onClick={e => {
e.stopPropagation();
toggleFavoritePage(id);
toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites');
}}
style={{
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
}}
className="favorite-button"
>
{favorite ? (
<FavouritedIcon data-testid="favourited-icon" />
) : (
<FavouritesIcon />
)}
</IconButton>
</Tooltip>
);
};
export const PageList = ({
pageList,
showFavoriteTag = false,
isTrash = false,
}: {
pageList: PageMeta[];
showFavoriteTag?: boolean;
isTrash?: boolean;
}) => {
const router = useRouter();
const { currentWorkspaceId } = useAppState();
if (pageList.length === 0) {
return <Empty />;
}
return (
<StyledTableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell proportion={0.5}>Title</TableCell>
<TableCell proportion={0.2}>Created</TableCell>
<TableCell proportion={0.2}>
{isTrash ? 'Moved to Trash' : 'Updated'}
</TableCell>
<TableCell proportion={0.1}></TableCell>
</TableRow>
</TableHead>
<TableBody>
{pageList.map((pageMeta, index) => {
return (
<StyledTableRow
key={`${pageMeta.id}-${index}`}
onClick={() => {
router.push(
`/workspace/${currentWorkspaceId}/${pageMeta.id}`
);
}}
>
<TableCell>
<StyledTitleWrapper>
<StyledTitleLink>
{pageMeta.mode === 'edgeless' ? (
<EdgelessIcon />
) : (
<PaperIcon />
)}
<Content ellipsis={true} color="inherit">
{pageMeta.title || 'Untitled'}
</Content>
</StyledTitleLink>
{showFavoriteTag && <FavoriteTag pageMeta={pageMeta} />}
</StyledTitleWrapper>
</TableCell>
<DateCell pageMeta={pageMeta} dateKey="createDate" />
<DateCell
pageMeta={pageMeta}
dateKey={isTrash ? 'trashDate' : 'updatedDate'}
backupKey={isTrash ? 'trashDate' : 'createDate'}
/>
<TableCell
style={{ padding: 0 }}
data-testid={`more-actions-${pageMeta.id}`}
onClick={e => {
e.stopPropagation();
}}
>
{isTrash ? (
<TrashOperationCell pageMeta={pageMeta} />
) : (
<OperationCell pageMeta={pageMeta} />
)}
</TableCell>
</StyledTableRow>
);
})}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default PageList;
@@ -0,0 +1,109 @@
import { useConfirm } from '@/providers/confirm-provider';
import { PageMeta } from '@/providers/app-state-provider';
import { Menu, MenuItem } from '@/ui/menu';
import { Wrapper } from '@/ui/layout';
import { IconButton } from '@/ui/button';
import {
MoreVerticalIcon,
RestoreIcon,
DeleteIcon,
FavouritesIcon,
FavouritedIcon,
OpenInNewIcon,
TrashIcon,
} from '@blocksuite/icons';
import { toast } from '@/ui/toast';
import { usePageHelper } from '@/hooks/use-page-helper';
export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
const { id, favorite } = pageMeta;
const { openPage } = usePageHelper();
const { toggleFavoritePage, toggleDeletePage } = usePageHelper();
const { confirm } = useConfirm();
const OperationMenu = (
<>
<MenuItem
onClick={() => {
toggleFavoritePage(id);
toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites');
}}
icon={favorite ? <FavouritedIcon /> : <FavouritesIcon />}
>
{favorite ? 'Remove' : 'Add'} to favourites
</MenuItem>
<MenuItem
onClick={() => {
openPage(id, {}, true);
}}
icon={<OpenInNewIcon />}
>
Open in new tab
</MenuItem>
<MenuItem
onClick={() => {
confirm({
title: 'Delete page?',
content: `${pageMeta.title || 'Untitled'} will be moved to Trash`,
confirmText: 'Delete',
confirmType: 'danger',
}).then(confirm => {
confirm && toggleDeletePage(id);
toast('Moved to Trash');
});
}}
icon={<TrashIcon />}
>
Delete
</MenuItem>
</>
);
return (
<Wrapper alignItems="center" justifyContent="center">
<Menu content={OperationMenu} placement="bottom-end" disablePortal={true}>
<IconButton darker={true}>
<MoreVerticalIcon />
</IconButton>
</Menu>
</Wrapper>
);
};
export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
const { id } = pageMeta;
const { openPage, getPageMeta } = usePageHelper();
const { toggleDeletePage, permanentlyDeletePage } = usePageHelper();
const { confirm } = useConfirm();
return (
<Wrapper>
<IconButton
darker={true}
style={{ marginRight: '12px' }}
onClick={() => {
toggleDeletePage(id);
toast(`${getPageMeta(id)?.title || 'Untitled'} restored`);
openPage(id);
}}
>
<RestoreIcon />
</IconButton>
<IconButton
darker={true}
onClick={() => {
confirm({
title: 'Delete permanently?',
content: "Once deleted, you can't undo this action.",
confirmText: 'Delete',
confirmType: 'danger',
}).then(confirm => {
confirm && permanentlyDeletePage(id);
toast('Permanently deleted');
});
}}
>
<DeleteIcon />
</IconButton>
</Wrapper>
);
};
@@ -0,0 +1,51 @@
import { displayFlex, styled } from '@/styles';
import { TableRow } from '@/ui/table';
export const StyledTableContainer = styled.div(() => {
return {
height: 'calc(100vh - 60px)',
padding: '78px 72px',
overflowY: 'auto',
};
});
export const StyledTitleWrapper = styled.div(({ theme }) => {
return {
...displayFlex('flex-start', 'center'),
a: {
color: 'inherit',
},
'a:visited': {
color: 'unset',
},
'a:hover': {
color: theme.colors.primaryColor,
},
};
});
export const StyledTitleLink = styled.div(({ theme }) => {
return {
maxWidth: '80%',
marginRight: '18px',
...displayFlex('flex-start', 'center'),
color: theme.colors.textColor,
'>svg': {
fontSize: '24px',
marginRight: '12px',
color: theme.colors.iconColor,
},
};
});
export const StyledTableRow = styled(TableRow)(() => {
return {
cursor: 'pointer',
'.favorite-button': {
display: 'none',
},
'&:hover': {
'.favorite-button': {
display: 'flex',
},
},
};
});
@@ -0,0 +1,18 @@
import { cloneElement, FC, PropsWithChildren, ReactNode } from 'react';
export const ProviderComposer: FC<
PropsWithChildren<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
contexts: any;
}>
> = ({ contexts, children }) =>
contexts.reduceRight(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(kids: ReactNode, parent: any) =>
cloneElement(parent, {
children: kids,
}),
children
);
export default ProviderComposer;
@@ -0,0 +1,24 @@
import { AllPagesIcon, FavouritesIcon, TrashIcon } from '@blocksuite/icons';
export const config = (currentWorkspaceId: string) => {
const List = [
{
title: 'All pages',
href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
icon: AllPagesIcon,
},
{
title: 'Favourites',
href: currentWorkspaceId
? `/workspace/${currentWorkspaceId}/favorite`
: '',
icon: FavouritesIcon,
},
{
title: 'Trash',
href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '',
icon: TrashIcon,
},
];
return List;
};
@@ -0,0 +1,35 @@
import React from 'react';
import { AddIcon } from '@blocksuite/icons';
import { StyledModalFooterContent } from './style';
import { useModal } from '@/providers/global-modal-provider';
import { Command } from 'cmdk';
import { usePageHelper } from '@/hooks/use-page-helper';
export const Footer = (props: { query: string }) => {
const { triggerQuickSearchModal } = useModal();
const { openPage, createPage } = usePageHelper();
const query = props.query;
return (
<Command.Item
data-testid="quickSearch-addNewPage"
onSelect={async () => {
const pageId = await createPage({ title: query });
if (pageId) {
openPage(pageId);
}
triggerQuickSearchModal();
}}
>
<StyledModalFooterContent>
<AddIcon />
{query ? (
<span>New &quot;{query}&quot; page</span>
) : (
<span>New page</span>
)}
</StyledModalFooterContent>
</Command.Item>
);
};
@@ -0,0 +1,118 @@
import { Modal, ModalWrapper } from '@/ui/modal';
import {
StyledContent,
StyledModalHeader,
StyledModalFooter,
StyledModalDivider,
StyledShortcut,
} from './style';
import { Input } from './input';
import { Results } from './results';
import { Footer } from './footer';
import { Command } from 'cmdk';
import { useEffect, useState } from 'react';
import { useModal } from '@/providers/global-modal-provider';
import { getUaHelper } from '@/utils';
import { useAppState } from '@/providers/app-state-provider';
type TransitionsModalProps = {
open: boolean;
onClose: () => void;
};
const isMac = () => {
return getUaHelper().isMacOs;
};
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
const [query, setQuery] = useState('');
const [loading, setLoading] = useState(true);
const [showCreatePage, setShowCreatePage] = useState(true);
const { triggerQuickSearchModal } = useModal();
const { currentWorkspaceId, workspacesMeta } = useAppState();
const currentWorkspace = workspacesMeta.find(
meta => String(meta.id) === String(currentWorkspaceId)
);
const isPublic = currentWorkspace?.public;
// Add ‘⌘+K’ shortcut keys as switches
useEffect(() => {
const down = (e: KeyboardEvent) => {
if ((e.key === 'k' && e.metaKey) || (e.key === 'k' && e.ctrlKey)) {
const selection = window.getSelection();
if (selection?.toString()) {
triggerQuickSearchModal(false);
return;
}
if (selection?.isCollapsed) {
triggerQuickSearchModal(!open);
}
}
};
if (!open) {
setQuery('');
}
document.addEventListener('keydown', down, { capture: true });
return () =>
document.removeEventListener('keydown', down, { capture: true });
}, [open, triggerQuickSearchModal]);
return (
<Modal
open={open}
onClose={onClose}
wrapperPosition={['top', 'center']}
data-testid="quickSearch"
>
<ModalWrapper
width={620}
style={{
maxHeight: '80vh',
minHeight: '350px',
top: '12vh',
}}
>
<Command
shouldFilter={false}
//Handle KeyboardEvent conflicts with blocksuite
onKeyDown={(e: React.KeyboardEvent) => {
if (
e.key === 'ArrowDown' ||
e.key === 'ArrowUp' ||
e.key === 'ArrowLeft' ||
e.key === 'ArrowRight'
) {
e.stopPropagation();
}
}}
>
<StyledModalHeader>
<Input query={query} setQuery={setQuery} setLoading={setLoading} />
<StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
</StyledModalHeader>
<StyledModalDivider />
<Command.List>
<StyledContent>
<Results
query={query}
loading={loading}
setLoading={setLoading}
setShowCreatePage={setShowCreatePage}
/>
</StyledContent>
{isPublic ? (
<></>
) : showCreatePage ? (
<>
<StyledModalDivider />
<StyledModalFooter>
<Footer query={query} />
</StyledModalFooter>
</>
) : null}
</Command.List>
</Command>
</ModalWrapper>
</Modal>
);
};
export default QuickSearch;
@@ -0,0 +1,90 @@
import React, {
Dispatch,
SetStateAction,
useEffect,
useRef,
useState,
} from 'react';
import { SearchIcon } from '@blocksuite/icons';
import { StyledInputContent, StyledLabel } from './style';
import { Command } from 'cmdk';
import { useAppState } from '@/providers/app-state-provider';
export const Input = (props: {
query: string;
setQuery: Dispatch<SetStateAction<string>>;
setLoading: Dispatch<SetStateAction<boolean>>;
}) => {
const [isComposition, setIsComposition] = useState(false);
const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const { currentWorkspaceId, workspacesMeta, currentWorkspace } =
useAppState();
const isPublic = workspacesMeta.find(
meta => String(meta.id) === String(currentWorkspaceId)
)?.public;
useEffect(() => {
inputRef.current?.addEventListener(
'blur',
() => {
inputRef.current?.focus();
},
true
);
return inputRef.current?.focus();
}, [inputRef]);
useEffect(() => {
return setInputValue(props.query);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<StyledInputContent>
<StyledLabel htmlFor=":r5:">
<SearchIcon />
</StyledLabel>
<Command.Input
ref={inputRef}
value={inputValue}
onCompositionStart={() => {
setIsComposition(true);
}}
onCompositionEnd={e => {
props.setQuery(e.data);
setIsComposition(false);
if (!props.query) {
props.setLoading(true);
}
}}
onValueChange={str => {
setInputValue(str);
if (!isComposition) {
props.setQuery(str);
if (!props.query) {
props.setLoading(true);
}
}
}}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'a' && e.metaKey) {
e.stopPropagation();
inputRef.current?.select();
return;
}
if (isComposition) {
if (
e.key === 'ArrowDown' ||
e.key === 'ArrowUp' ||
e.key === 'Enter'
) {
e.stopPropagation();
}
}
}}
placeholder={
isPublic
? `Search in ${currentWorkspace?.meta.name}`
: 'Quick Search...'
}
/>
</StyledInputContent>
);
};
@@ -0,0 +1,95 @@
export const NoResultSVG = () => {
return (
<svg
width="150"
height="150"
viewBox="0 0 154 120"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<mask
id="mask0_7969_10649"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="154"
height="120"
>
<path
d="M83.4345 10.7757H81.5042L60.4847 0.480469L14.156 4.12671L0 70.617L78.7159 119.519L146.493 98.7144L154 72.9763L137.27 68.4721L130.192 66.1128L135.34 53.2437L123.114 44.4498L108.315 32.8677L97.5905 10.9902H88.3677L83.4345 10.7757Z"
fill="#D9D9D9"
/>
</mask>
<g mask="url(#mask0_7969_10649)">
<path
d="M123.233 68.7528C122.29 66.239 117.597 67.8949 113.444 64.4631C111.728 63.0303 110.013 57.3422 110.013 57.3422C110.013 57.3422 114.302 60.3965 116.782 58.1487C119.261 55.9009 118.137 49.3891 113.35 51.0707C108.271 52.8724 106.95 50.942 101.15 48.9602C95.3503 46.9783 91.8928 50.616 91.8928 50.616C82.9789 49.5607 77.5224 51.3795 73.3442 53.6874C69.6551 49.3977 62.7916 52.4691 59.377 55.6864C55.4219 59.4098 56.1854 62.447 56.6401 63.4078C51.6384 67.2857 51.4925 71.2237 51.8443 74.1492C52.1343 77.2258 53.3709 80.1368 55.3836 82.4807C57.3963 84.8254 60.0863 86.489 63.0833 87.2414C63.0833 87.2414 62.946 93.6759 64.7305 94.2593C66.515 94.8427 67.21 92.6206 70.7361 90.8276C74.5453 89.8323 78.5862 85.8858 76.4585 83.1061C81.2029 86.0488 88.3581 84.2557 88.9501 76.6201C91.2837 81.15 101.999 80.6009 107.825 78.0872C108.528 83.4836 106.812 88.5883 104.479 90.7761C101.047 93.9676 98.2244 93.7102 96.4657 95.2202C94.7069 96.7302 98.8851 101.354 108.477 95.4004C116.979 90.1155 117.597 81.0471 111.685 73.7117C117.554 75.3332 124.211 71.3524 123.233 68.7528ZM74.1164 69.7051C74.0563 69.8424 72.9496 63.4593 78.6205 60.4994C82.5242 58.4575 87.0369 59.8474 87.5431 62.9617C88.2037 67.0455 76.999 62.1724 74.1164 69.7051Z"
fill="#BFD1FF"
/>
<path
d="M60.7011 43.79C58.0071 41.954 62.5285 27.0087 67.4873 27.249C73.3213 27.5321 82.2096 40.3583 79.0953 44.9911C77.388 47.5306 65.2052 46.8529 60.7011 43.79Z"
fill="#BFD1FF"
/>
<path
d="M45.4019 33.2972C44.2093 32.4393 46.2083 25.5758 48.3961 25.653C50.9699 25.7817 54.8992 31.71 53.5437 33.8549C52.763 35.0131 47.3923 34.73 45.4019 33.2972Z"
fill="#BFD1FF"
/>
<path
d="M83.8645 29.2825C82.8778 28.5961 84.5251 23.0539 86.3268 23.1397C88.4459 23.2512 91.6717 28.0042 90.5392 29.7286C89.9215 30.6724 85.4946 30.415 83.8645 29.2825Z"
fill="#BFD1FF"
/>
<path
d="M48.2434 31.28C47.0423 31.3829 47.0594 32.9959 48.5608 32.8672C49.7962 32.7042 49.7362 31.0483 48.2434 31.28Z"
fill="#0F3471"
/>
<path
d="M69.6396 40.5871C71.0724 40.2525 71.141 39.935 71.0638 36.5547C70.9866 32.9085 70.6434 27.2634 66.8256 27.9754C62.8619 28.087 66.1993 41.4107 69.6396 40.5871Z"
fill="#0F3471"
/>
<path
d="M62.3727 78.2623C60.8541 78.4167 60.3908 78.8114 60.4766 79.5921C60.5453 80.1669 60.9828 80.6645 62.1925 80.5787C63.1791 80.5101 64.9293 81.4881 65.547 83.4185C66.1648 85.3489 68.0093 84.3451 67.7434 82.7579C67.4431 80.6302 65.2639 77.9706 62.3727 78.2623Z"
fill="#0F3471"
/>
<path
d="M70.1447 41.7917C67.1762 42.3665 67.4765 46.476 70.3163 46.2787C73.4993 46.0556 73.3534 41.1654 70.1447 41.7917Z"
fill="#0F3471"
/>
<path
d="M48.9891 29.2474C48.9754 28.5727 48.8266 27.9075 48.5516 27.2913C48.3114 26.9481 47.2475 26.9567 46.8357 27.2913C46.1236 27.8489 46.7156 30.8088 47.8138 30.8002C48.543 30.723 49.135 30.5429 48.9891 29.2474Z"
fill="#0F3471"
/>
<path
d="M188.961 66.8503C186.707 66.5844 184.438 66.4531 182.167 66.4557C188.644 58.0822 190.892 52.6343 190.892 45.8651C190.892 32.284 177.825 17.1928 167.873 10.8098C172.086 8.85366 175.432 4.41812 175.011 -0.480713C178.203 -1.97353 176.633 -7.09542 177.13 -9.97809C178.186 -11.9256 181.249 -12.2945 180.622 -16.0351C180.107 -21.6375 169.984 -18.3945 172.223 -24.5287C172.686 -26.2961 173.33 -28.7241 171.417 -30.6887C169.435 -33.2197 161.353 -31.2035 158.676 -32.5247C157.475 -41.4215 151.959 -39.7486 145.533 -35.7763C133.23 -49.0658 110.246 -44.5702 94.88 -34.4808C87.4245 -29.5391 76.2112 -17.0475 83.5981 10.8698C74.0149 10.7068 62.1496 10.4408 52.0688 10.3636C27.3859 10.132 24.7178 13.0318 30.3716 33.4508C32.0874 42.8452 34.6612 54.6676 36.1541 61.3166C37.6383 67.3222 37.6898 73.6538 42.5629 76.2104C37.827 84.4381 43.1548 86.5057 49.4264 82.9281C51.5273 85.8871 54.359 88.2516 57.6454 89.7916C57.0963 102.429 63.977 99.1517 69.6566 92.4426C71.6298 91.1128 73.8948 91.5847 76.0482 89.62C85.5456 95.9859 93.8676 91.7391 104.515 89.0452C101.752 94.0041 87.6647 94.7162 86.4979 99.1002C85.2453 103.896 88.3167 106.942 92.1517 107.191C101.066 107.765 115.582 99.3405 117.032 86.2311C123.621 84.644 139.767 81.032 143.465 79.0931C145.31 78.2352 145.327 75.7729 145.327 74.4345C145.412 71.6633 144.194 71.0027 137.151 68.4804C150.654 69.4241 178.863 68.6434 188.627 68.6863C190.034 68.6949 190.42 66.8332 188.961 66.8503ZM169.967 7.05199C165.789 10.2435 163.841 8.99951 163.755 12.7659C163.506 24.3823 145.799 29.7702 131.634 30.5338C129.292 30.6539 127.284 30.448 125.199 30.3708C118.636 30.1305 114.158 28.1058 111.756 26.0811C109.121 23.9675 106.986 21.2988 105.501 18.2652C102.978 12.4592 102.424 5.98729 103.923 -0.163277C106.582 -3.45776 106.359 -4.45297 107.483 -4.62456C108.341 -4.77899 110.606 -3.03737 112.631 -3.41487C115.702 -3.97253 118.894 -7.25843 121.433 -8.15927C124.007 -7.7303 126.143 -3.86957 129.155 -4.47871C132.235 -4.75325 133.135 -9.54055 135.366 -10.4843C136.91 -9.94378 137.837 -7.19838 140.333 -7.50723C145.653 -8.4767 143.096 -14.5509 149.153 -8.41665C152.164 -5.24227 154.524 -4.024 156.977 -4.18701C159.92 -4.37575 161.413 -7.06968 163.369 -7.01821C165.78 -6.95815 169.452 -0.472136 172.806 -0.154699C172.984 1.18883 172.817 2.55553 172.32 3.81636C171.823 5.07727 171.013 6.19079 169.967 7.05199ZM146.459 -33.6829C148.887 -35.3216 153.048 -37.1833 154.412 -36.7286C156.3 -36.0938 155.631 -32.4389 156.986 -31.0405C158.822 -28.5267 167.496 -30.8603 169.855 -29.1016C171.974 -26.9052 168.019 -22.6413 170.507 -20.1361C172.163 -17.8197 177.894 -18.2829 178.46 -15.6491C178.374 -11.9428 173.767 -13.1868 174.865 -6.78656C175.483 4.72697 167.307 -10.0382 163.172 -9.25743C160.383 -8.91425 159.5 -6.23748 156.497 -6.28038C152.207 -6.59782 145.73 -18.18 141.981 -11.1363C141.432 -10.2784 141.123 -9.81509 140.222 -9.75503C138.412 -9.87514 137.588 -13.101 135.194 -12.7235C132.123 -12.6034 131.299 -7.45576 128.829 -6.71793C126.538 -6.71793 124.239 -10.5529 121.622 -10.4242C117.967 -10.2441 113.823 -3.56071 109.611 -6.49487C109.299 -6.73183 108.941 -6.90153 108.56 -6.99307C108.179 -7.08461 107.783 -7.09611 107.397 -7.02679C104.558 -6.7694 102.087 1.09789 99.2125 -0.386339C96.939 -1.35581 100.285 -5.46534 97.3594 -8.27938C94.7856 -10.7245 91.6198 -12.2002 96.7846 -14.1219C108.341 -18.4116 88.4283 -25.2751 110.426 -24.7261C114.861 -10.5443 141.895 -11.1621 142.607 -27.2999C145.275 -28.3466 147.137 -30.8689 146.459 -33.6829ZM96.1154 -32.662C107.74 -40.4435 121.682 -43.6436 133.264 -40.6237C139.947 -38.9078 148.132 -34.0175 142.324 -29.6335C141.94 -31.0081 141.263 -32.2837 140.342 -33.3741C139.39 -34.4637 137.914 -33.2969 138.892 -32.233C141.183 -29.7622 141.028 -24.2542 138.583 -21.9378C137.977 -23.4294 137.143 -24.8185 136.112 -26.0559C135.921 -26.2834 135.647 -26.4256 135.351 -26.4515C135.055 -26.4772 134.761 -26.3843 134.534 -26.1931C134.306 -26.002 134.164 -25.7283 134.138 -25.4322C134.113 -25.1362 134.205 -24.8421 134.397 -24.6145C135.323 -23.5164 136.661 -21.4058 136.739 -20.0417C134.475 -18.3075 131.77 -17.2434 128.931 -16.9703C128.629 -18.998 127.996 -20.9626 127.061 -22.7871C126.927 -23.051 126.693 -23.2508 126.412 -23.3426C126.13 -23.4343 125.824 -23.4104 125.56 -23.2761C125.296 -23.1419 125.096 -22.9083 125.005 -22.6267C124.913 -22.3451 124.937 -22.0387 125.071 -21.7748C125.868 -20.2232 126.415 -18.5553 126.692 -16.833C119.451 -16.7644 112.013 -20.9426 111.833 -29.4104C111.824 -29.707 111.698 -29.988 111.483 -30.1921C111.267 -30.3963 110.98 -30.5069 110.683 -30.5C108.967 -30.3198 109.748 -28.1235 109.825 -27.0168C100.877 -27.5229 94.8285 -25.4725 97.9857 -19.3468C99.1439 -17.0904 97.7026 -17.0904 95.8752 -16.181C87.6647 -12.7492 91.8 -9.56629 96.3728 -5.83425C96.6216 -4.04116 95.5148 -1.54456 96.7074 0.31717C92.5206 1.92152 86.4807 2.49633 84.13 2.65934C81.642 -13.367 85.7686 -25.7384 96.1154 -32.662ZM40.2464 68.9609C37.4924 57.5503 35.3562 47.2121 32.5679 33.0561C31.71 27.9943 28.5613 18.9602 30.9378 15.7172C37.4667 1Line truncated
fill="#0F3471"
/>
<path
d="M64.971 68.7307C64.7823 66.5687 63.0835 65.2989 60.6813 65.4705C58.8367 65.5992 57.893 67.092 58.7081 67.8127C59.5231 68.5334 60.1837 67.1864 61.4449 67.7526C61.6976 67.8642 61.9163 68.04 62.0786 68.2631C62.241 68.487 62.3413 68.7496 62.3692 69.0241C62.3971 69.2986 62.3517 69.5758 62.2375 69.8271C62.1234 70.0785 61.9446 70.2956 61.7194 70.4551C59.1885 69.52 58.7338 72.2911 60.0035 74.2387C61.625 77.1728 65.7346 75.3711 63.6498 72.0166C64.5077 70.8755 65.9662 70.2321 66.7212 72.2053C67.2102 73.4922 66.4552 74.8907 67.5791 75.3111C69.8012 76.1948 70.2559 68.2674 64.971 68.7307Z"
fill="#0F3471"
/>
<path
d="M53.0804 69.9297C51.5618 69.9297 51.0557 70.2471 51.0557 71.0364C51.0557 71.6198 51.4332 72.1603 52.6857 72.2118C53.4532 72.38 54.1517 72.7763 54.6893 73.3486C55.2269 73.9217 55.5783 74.6441 55.6971 75.4205C56.0746 77.3337 58.0307 76.613 57.9535 75.0173C57.8505 72.8638 55.9888 69.964 53.0804 69.9297Z"
fill="#0F3471"
/>
<path
d="M41.0792 48.9636C39.1488 39.123 34.9192 23.9547 36.6865 20.4972C38.145 17.666 46.7759 18.7813 51.4345 18.3866C60.9662 18.3866 71.1242 18.155 84.3107 18.3094C88.8921 18.4124 92.6584 17.8461 94.0826 22.4275C94.5287 23.8517 96.1074 24.1434 95.8671 22.0071C95.1979 16.0016 89.6471 16.1817 84.345 16.0702C74.9077 15.9673 61.7555 16.0702 56.7794 16.1646C31.3159 16.1388 31.1872 13.1446 36.6694 39.1745C37.5959 41.6282 39.1488 53.4763 40.6245 59.705C41.4052 63.008 41.8942 64.1405 42.7178 63.9174C43.5415 63.6944 43.4471 62.6305 42.8894 59.7736C42.3318 56.9167 41.3194 51.3143 41.0792 48.9636Z"
fill="#0F3471"
/>
<path
d="M42.399 27.367C40.2027 31.7425 40.3142 34.2305 41.5411 35.4059C43.0854 36.8987 47.3665 35.8091 50.5323 35.6804C57.2156 35.4059 57.3186 31.8197 53.1061 26.8094C48.8936 21.799 45.6163 20.9668 42.399 27.367ZM53.3549 32.283C53.2005 33.4155 48.662 33.7072 46.5943 33.8444C43.0939 34.0847 41.9014 33.6986 44.3465 28.4652C46.4313 23.9867 48.3531 24.1755 51.3559 28.2421C52.3254 29.5719 53.4836 31.2878 53.3549 32.283Z"
fill="#0F3471"
/>
<path
d="M87.6653 28.2415C88.3001 28.2415 88.5832 27.7954 88.3773 26.8345C88.2143 25.1787 87.6309 23.8746 86.4899 24.947C85.8979 25.5047 86.61 28.3273 87.6653 28.2415Z"
fill="#0F3471"
/>
<path
d="M86.6856 21.3856C82.7476 22.1406 80.9803 30.4025 82.4903 31.8524C84.2061 33.4739 86.9687 32.6589 89.5854 32.5388C94.5958 32.2985 95.0848 29.7505 92.6997 26.0442C90.2203 22.1835 88.4444 21.051 86.6856 21.3856ZM92.2279 29.6904C91.7817 30.4969 90.7951 30.5483 89.8599 30.6256C85.004 30.9859 81.701 32.8305 84.7123 25.7267C87.6121 18.8632 92.8456 28.6008 92.2279 29.6904Z"
fill="#0F3471"
/>
<path
d="M87.9802 28.6219C86.9335 28.7248 86.9506 30.2605 88.2633 30.1147C89.3443 29.9602 89.2928 28.4074 87.9802 28.6219Z"
fill="#0F3471"
/>
</g>
</svg>
);
};
@@ -0,0 +1,102 @@
import { Command } from 'cmdk';
import { StyledListItem, StyledNotFound } from './style';
import { useModal } from '@/providers/global-modal-provider';
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import { config } from './config';
import { NoResultSVG } from './noResultSVG';
import usePageHelper from '@/hooks/use-page-helper';
import usePageMetaList from '@/hooks/use-page-meta-list';
export const Results = (props: {
query: string;
loading: boolean;
setLoading: Dispatch<SetStateAction<boolean>>;
setShowCreatePage: Dispatch<SetStateAction<boolean>>;
}) => {
const query = props.query;
const loading = props.loading;
const setLoading = props.setLoading;
const setShowCreatePage = props.setShowCreatePage;
const { triggerQuickSearchModal } = useModal();
const pageMetaList = usePageMetaList();
const { openPage } = usePageHelper();
const router = useRouter();
const { currentWorkspaceId } = useAppState();
const { search } = usePageHelper();
const List = config(currentWorkspaceId);
const [results, setResults] = useState(new Map<string, string | undefined>());
useEffect(() => {
setResults(search(query));
setLoading(false);
//Save the Map<BlockId, PageId> obtained from the search as state
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query, setResults, setLoading]);
const pageIds = [...results.values()];
const resultsPageMeta = pageMetaList.filter(
page => pageIds.indexOf(page.id) > -1 && !page.trash
);
useEffect(() => {
setShowCreatePage(resultsPageMeta.length ? false : true);
//Determine whether to display the ‘+ New page’
}, [resultsPageMeta, setShowCreatePage]);
return loading ? null : (
<>
{query ? (
resultsPageMeta.length ? (
<Command.Group heading={`Find ${resultsPageMeta.length} results`}>
{resultsPageMeta.map(result => {
return (
<Command.Item
key={result.id}
onSelect={() => {
openPage(result.id);
triggerQuickSearchModal();
}}
value={result.id}
>
<StyledListItem>
{result.mode === 'edgeless' ? (
<EdgelessIcon />
) : (
<PaperIcon />
)}
<span>{result.title}</span>
</StyledListItem>
</Command.Item>
);
})}
</Command.Group>
) : (
<StyledNotFound>
<span>Find 0 result</span>
<NoResultSVG />
</StyledNotFound>
)
) : (
<Command.Group heading="Switch to">
{List.map(link => {
return (
<Command.Item
key={link.title}
value={link.title}
onSelect={() => {
router.push(link.href);
triggerQuickSearchModal();
}}
>
<StyledListItem>
<link.icon />
<span>{link.title}</span>
</StyledListItem>
</Command.Item>
);
})}
</Command.Group>
)}
</>
);
};
@@ -0,0 +1,153 @@
import { displayFlex, styled } from '@/styles';
export const StyledContent = styled('div')(({ theme }) => {
return {
minHeight: '220px',
maxHeight: '55vh',
width: '100%',
overflow: 'auto',
marginBottom: '10px',
...displayFlex('center', 'flex-start'),
color: theme.colors.popoverColor,
letterSpacing: '0.06em',
'[cmdk-group-heading]': {
margin: '5px 16px',
fontSize: theme.font.sm,
fontWeight: '500',
},
'[aria-selected="true"]': {
transition: 'background .15s, color .15s',
borderRadius: '5px',
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
};
});
export const StyledJumpTo = styled('div')(({ theme }) => {
return {
...displayFlex('center', 'start'),
flexDirection: 'column',
padding: '10px 10px 10px 0',
fontSize: theme.font.sm,
strong: {
fontWeight: '500',
marginBottom: '10px',
},
};
});
export const StyledNotFound = styled('div')(({ theme }) => {
return {
width: '612px',
...displayFlex('center', 'center'),
flexDirection: 'column',
padding: '5px 16px',
fontSize: theme.font.sm,
span: {
width: '100%',
fontWeight: '500',
},
'>svg': {
marginTop: '10px',
fontSize: '150px',
},
};
});
export const StyledInputContent = styled('div')(({ theme }) => {
return {
margin: '13px 0',
...displayFlex('space-between', 'center'),
input: {
width: '492px',
height: '22px',
padding: '0 12px',
fontSize: theme.font.base,
...displayFlex('space-between', 'center'),
letterSpacing: '0.06em',
color: theme.colors.popoverColor,
'::placeholder': {
color: theme.colors.placeHolderColor,
},
},
};
});
export const StyledShortcut = styled('div')(({ theme }) => {
return { color: theme.colors.placeHolderColor, fontSize: theme.font.sm };
});
export const StyledLabel = styled('label')(({ theme }) => {
return {
width: '24px',
height: '24px',
color: theme.colors.iconColor,
};
});
export const StyledModalHeader = styled('div')(({ theme }) => {
return {
height: '48px',
margin: '12px 24px 0px 24px',
...displayFlex('space-between', 'center'),
color: theme.colors.popoverColor,
};
});
export const StyledModalDivider = styled('div')(({ theme }) => {
return {
width: 'auto',
height: '0',
margin: '6px 16px 6.5px 16px',
position: 'relative',
borderTop: `0.5px solid ${theme.colors.placeHolderColor}`,
};
});
export const StyledModalFooter = styled('div')(({ theme }) => {
return {
fontSize: theme.font.sm,
lineHeight: '22px',
marginBottom: '8px',
textAlign: 'center',
...displayFlex('center', 'center'),
color: theme.colors.popoverColor,
'[aria-selected="true"]': {
transition: 'background .15s, color .15s',
borderRadius: '5px',
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
};
});
export const StyledModalFooterContent = styled.button(({ theme }) => {
return {
width: '612px',
height: '32px',
fontSize: theme.font.sm,
lineHeight: '22px',
textAlign: 'center',
...displayFlex('center', 'center'),
color: 'inherit',
borderRadius: '5px',
transition: 'background .15s, color .15s',
'>svg': {
fontSize: '20px',
marginRight: '12px',
},
};
});
export const StyledListItem = styled.button(({ theme }) => {
return {
width: '612px',
height: '32px',
fontSize: theme.font.sm,
color: 'inherit',
paddingLeft: '12px',
borderRadius: '5px',
transition: 'background .15s, color .15s',
...displayFlex('flex-start', 'center'),
'>svg': {
fontSize: '20px',
marginRight: '12px',
},
};
});
@@ -6,6 +6,7 @@ export const macKeyboardShortcuts = {
Underline: '⌘+U', Underline: '⌘+U',
Strikethrough: '⌘+⇧+S', Strikethrough: '⌘+⇧+S',
'Inline code': ' ⌘+E', 'Inline code': ' ⌘+E',
'Code block': '⌘+⌥+C',
Link: '⌘+K', Link: '⌘+K',
'Body text': '⌘+⌥+0', 'Body text': '⌘+⌥+0',
'Heading 1': '⌘+⌥+1', 'Heading 1': '⌘+⌥+1',
@@ -23,7 +24,9 @@ export const macMarkdownShortcuts = {
Italic: '*Text* ', Italic: '*Text* ',
Underline: '~Text~ ', Underline: '~Text~ ',
Strikethrough: '~~Text~~ ', Strikethrough: '~~Text~~ ',
Divider: '***',
'Inline code': '`Text` ', 'Inline code': '`Text` ',
'Code block': '``` Space',
'Heading 1': '# Text', 'Heading 1': '# Text',
'Heading 2': '## Text', 'Heading 2': '## Text',
'Heading 3': '### Text', 'Heading 3': '### Text',
@@ -40,6 +43,7 @@ export const windowsKeyboardShortcuts = {
Underline: 'Ctrl+U', Underline: 'Ctrl+U',
Strikethrough: 'Ctrl+Shift+S', Strikethrough: 'Ctrl+Shift+S',
'Inline code': ' Ctrl+E', 'Inline code': ' Ctrl+E',
'Code block': 'Ctrl+Alt+C',
Link: 'Ctrl+K', Link: 'Ctrl+K',
'Body text': 'Ctrl+Shift+0', 'Body text': 'Ctrl+Shift+0',
'Heading 1': 'Ctrl+Shift+1', 'Heading 1': 'Ctrl+Shift+1',
@@ -57,6 +61,7 @@ export const winMarkdownShortcuts = {
Underline: '~Text~ ', Underline: '~Text~ ',
Strikethrough: '~~Text~~ ', Strikethrough: '~~Text~~ ',
'Inline code': '`Text` ', 'Inline code': '`Text` ',
'Code block': '``` Text',
'Heading 1': '# Text', 'Heading 1': '# Text',
'Heading 2': '## Text', 'Heading 2': '## Text',
'Heading 3': '### Text', 'Heading 3': '### Text',
Loaded 100 of 296 files, more files were not shown because too many files have changed in this diff. Show more