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:
296 files changed
+16136
-2069
No files matched your search
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,4 +1,6 @@
|
||||
**/webpack.config.js
|
||||
**/jest.config.js
|
||||
**/scripts/*.js
|
||||
**/node_modules/**
|
||||
.github/**
|
||||
**/__tests__/**
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
@@ -15,8 +15,9 @@
|
||||
Cache-Control "public, max-age=86400, must-revalidate"
|
||||
}
|
||||
|
||||
handle_path /api/* {
|
||||
handle /api/* {
|
||||
reverse_proxy {$API_SERVER} {
|
||||
health_uri /api/healthz
|
||||
@error status 500 502 503 503
|
||||
handle_response @error {
|
||||
root * /dist
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Custom Tag. Set nightly-latest will publish to development."
|
||||
description: 'Custom Tag. Set nightly-latest will publish to development.'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
@@ -20,6 +20,7 @@ jobs:
|
||||
build:
|
||||
name: Lint and Build
|
||||
runs-on: self-hosted
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -62,6 +63,14 @@ jobs:
|
||||
|
||||
- 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
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: E2E & Unit Tests
|
||||
name: Unit Tests
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -30,11 +30,19 @@ jobs:
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: pnpm run test:e2e
|
||||
# - name: Run E2E tests
|
||||
# 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
|
||||
run: pnpm run test:unit
|
||||
@@ -51,3 +51,6 @@ module-resolve.js
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
||||
# Cache
|
||||
.eslintcache
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm exec lint-staged
|
||||
@@ -0,0 +1 @@
|
||||
pnpm-lock.yaml
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["ms-playwright.playwright", "esbenp.prettier-vscode"]
|
||||
}
|
||||
Vendored
+1
-1
@@ -3,5 +3,5 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file",
|
||||
"cSpell.words": ["testid"]
|
||||
"cSpell.words": ["blocksuite", "livedemo", "testid"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
See the [AFFiNE CHANGELOG](https://affine.pro/blog?tag=Release%20Note)
|
||||
|
||||
---
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
@@ -1,21 +1,384 @@
|
||||
MIT License
|
||||
# Mozilla Public License Version 2.0
|
||||
|
||||
Copyright (c) Toeverything Technology (Hangzhou) Co., Ltd. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
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:
|
||||
1. Definitions
|
||||
|
||||
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
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
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.
|
||||
@@ -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]: 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 -->
|
||||
|
||||
@@ -29,6 +29,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://www.rust-lang.org/)
|
||||
|
||||
</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.
|
||||
|
||||
**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.
|
||||
-->
|
||||
|
||||
**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
|
||||
@@ -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:
|
||||
|
||||
- [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.
|
||||
- [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.
|
||||
- [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.
|
||||
- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend.
|
||||
- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board.
|
||||
- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library.
|
||||
- Other [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies)
|
||||
- [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.
|
||||
- [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.
|
||||
- [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.
|
||||
- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend.
|
||||
- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board.
|
||||
- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library.
|
||||
- Other [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies)
|
||||
|
||||
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
|
||||
- [tolgee](https://github.com/tolgee/tolgee-platform) -- Elegant i18n collaborative editing platform
|
||||
- [nx](https://github.com/nrwl/nx) -- Awesome monorepo manager & build system
|
||||
- [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.
|
||||
|
||||
@@ -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>
|
||||
</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/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://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/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/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>
|
||||
<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/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://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://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>
|
||||
<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="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://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="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>
|
||||
<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://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>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Benchmarks
|
||||
|
||||
Example sites for benchmarking `AFFiNE`.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
@@ -1,17 +1,21 @@
|
||||
# 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
@@ -1,22 +1,35 @@
|
||||
{
|
||||
"name": "pathfinder",
|
||||
"version": "0.0.0",
|
||||
"name": "AFFiNE",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter=!@pathfinder/app build && pnpm --filter @pathfinder/app dev",
|
||||
"build": "pnpm -r build",
|
||||
"export": "pnpm --filter @pathfinder/app export",
|
||||
"start": "pnpm --filter @pathfinder/app start",
|
||||
"lint": "pnpm --filter @pathfinder/app lint",
|
||||
"test:e2e": "playwright test",
|
||||
"dev": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
||||
"dev:ac": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev:ac",
|
||||
"build": " pnpm --filter=!@affine/app build && pnpm --filter!=@affine/data-services -r build",
|
||||
"export": "pnpm --filter @affine/app export",
|
||||
"start": "pnpm --filter @affine/app start",
|
||||
"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",
|
||||
"postinstall": "husky install",
|
||||
"notify": "node --experimental-modules scripts/notify.mjs"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "prettier --write --ignore-unknown",
|
||||
"*.{ts,tsx,js,jsx}": "npx eslint --cache --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^29.3.1",
|
||||
"@playwright/test": "^1.28.1",
|
||||
"@types/node": "18.7.18",
|
||||
"eslint": "8.22.0",
|
||||
"@playwright/test": "^1.29.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.47.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-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
@@ -24,6 +37,34 @@
|
||||
"jest": "^29.3.1",
|
||||
"prettier": "^2.7.1",
|
||||
"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/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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=
|
||||
@@ -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,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
# @affine/app
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- cc72448: add changeset config
|
||||
@@ -1,10 +1,13 @@
|
||||
// @ts-check
|
||||
/* eslint @typescript-eslint/no-var-requires: "off" */
|
||||
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} */
|
||||
const nextConfig = {
|
||||
productionBrowserSourceMaps: true,
|
||||
reactStrictMode: true,
|
||||
reactStrictMode: false,
|
||||
swcMinify: false,
|
||||
publicRuntimeConfig: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
@@ -13,8 +16,58 @@ const nextConfig = {
|
||||
CI: process.env.CI || null,
|
||||
VERSION: getGitVersion(),
|
||||
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,
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -1,17 +1,20 @@
|
||||
{
|
||||
"name": "@pathfinder/app",
|
||||
"version": "0.1.0",
|
||||
"name": "@affine/app",
|
||||
"version": "0.3.1",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "next dev -p 8080",
|
||||
"dev:ac": "NODE_API_SERVER=ac next dev -p 8080",
|
||||
"build": "next build",
|
||||
"export": "next export",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blocksuite/blocks": "0.3.0-alpha.4",
|
||||
"@blocksuite/editor": "0.3.0-alpha.4",
|
||||
"@blocksuite/store": "0.3.0-alpha.4",
|
||||
"@affine/data-services": "workspace:*",
|
||||
"@blocksuite/blocks": "0.3.0-20221230100352-5dfe65e",
|
||||
"@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/react": "^11.10.4",
|
||||
"@emotion/server": "^11.10.0",
|
||||
@@ -21,24 +24,43 @@
|
||||
"@mui/base": "^5.0.0-alpha.87",
|
||||
"@mui/icons-material": "^5.10.9",
|
||||
"@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",
|
||||
"dayjs": "^1.11.7",
|
||||
"i18next": "^21.9.1",
|
||||
"lit": "^2.3.1",
|
||||
"next": "13.0.1",
|
||||
"next": "13.1.0",
|
||||
"next-debug-local": "^0.1.5",
|
||||
"prettier": "^2.7.1",
|
||||
"quill": "^1.3.7",
|
||||
"quill-cursors": "^4.0.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": {
|
||||
"@types/node": "18.7.18",
|
||||
"@types/react": "18.0.20",
|
||||
"@types/react-dom": "18.0.6",
|
||||
"@types/wicg-file-system-access": "^2020.9.5",
|
||||
"chalk-next": "^6.1.5",
|
||||
"eslint": "8.22.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"typescript": "4.8.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"plugin:@next/next/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "warn"
|
||||
},
|
||||
"reportUnusedDisableDirectives": true
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,9 @@ select,
|
||||
keygen,
|
||||
legend {
|
||||
color: var(--affine-text-color);
|
||||
background-color: unset;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.5;
|
||||
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')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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 };
|
||||
@@ -0,0 +1,11 @@
|
||||
import { NotFoundTitle, PageContainer } from './styles';
|
||||
|
||||
export const NotfoundPage = () => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<NotFoundTitle>404 - Page Not Found</NotFoundTitle>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotfoundPage;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
export const DiscordIcon = (props: any) => {
|
||||
export const DiscordIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Modal from '@/ui/modal';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { Modal, ModalCloseButton, ModalWrapper } from '@/ui/modal';
|
||||
import {
|
||||
LogoIcon,
|
||||
DocIcon,
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
} from './icons';
|
||||
import logo from './affine-text-logo.png';
|
||||
import {
|
||||
StyledModalWrapper,
|
||||
StyledBigLink,
|
||||
StyledSmallLink,
|
||||
StyledSubTitle,
|
||||
@@ -22,9 +20,9 @@ import {
|
||||
StyledLogo,
|
||||
StyledModalHeader,
|
||||
StyledModalHeaderLeft,
|
||||
StyledCloseButton,
|
||||
StyledModalFooter,
|
||||
} from './style';
|
||||
import bg from '@/components/contact-modal/bg.png';
|
||||
|
||||
const linkList = [
|
||||
{
|
||||
@@ -62,9 +60,9 @@ const rightLinkList = [
|
||||
},
|
||||
{
|
||||
icon: <DocIcon />,
|
||||
title: 'Check Our Docs',
|
||||
subTitle: 'docs.AFFiNE.pro',
|
||||
link: 'https://docs.affine.pro',
|
||||
title: 'AFFiNE Community',
|
||||
subTitle: 'community.affine.pro',
|
||||
link: 'https://community.affine.pro',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -75,20 +73,24 @@ type TransitionsModalProps = {
|
||||
|
||||
export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledModalWrapper data-testid="contact-us-modal-content">
|
||||
<Modal open={open} onClose={onClose} data-testid="contact-us-modal-content">
|
||||
<ModalWrapper
|
||||
width={860}
|
||||
height={540}
|
||||
style={{ backgroundImage: `url(${bg.src})` }}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledLogo src={logo.src} alt="" />
|
||||
<span>Alpha</span>
|
||||
</StyledModalHeaderLeft>
|
||||
<StyledCloseButton
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<CloseIcon width={12} height={12} />
|
||||
</StyledCloseButton>
|
||||
/>
|
||||
</StyledModalHeader>
|
||||
|
||||
<StyledContent>
|
||||
@@ -107,10 +109,7 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
|
||||
})}
|
||||
</StyledLeftContainer>
|
||||
<StyledRightContainer>
|
||||
<StyledSubTitle>
|
||||
Get in touch! <br />
|
||||
Join our community.
|
||||
</StyledSubTitle>
|
||||
<StyledSubTitle>Get in touch!</StyledSubTitle>
|
||||
{linkList.map(({ icon, title, link }) => {
|
||||
return (
|
||||
<StyledSmallLink key={title} href={link} target="_blank">
|
||||
@@ -134,7 +133,7 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
|
||||
</p>
|
||||
<p>Copyright © 2022 Toeverything</p>
|
||||
</StyledModalFooter>
|
||||
</StyledModalWrapper>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,28 +1,11 @@
|
||||
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 }) => {
|
||||
return {
|
||||
width: '320px',
|
||||
width: '334px',
|
||||
height: '100px',
|
||||
marginBottom: '48px',
|
||||
paddingLeft: '114px',
|
||||
paddingLeft: '90px',
|
||||
fontSize: '24px',
|
||||
lineHeight: '36px',
|
||||
fontWeight: '600',
|
||||
@@ -46,7 +29,7 @@ export const StyledBigLink = styled('a')(({ theme }) => {
|
||||
height: '50px',
|
||||
marginRight: '40px',
|
||||
color: theme.colors.primaryColor,
|
||||
...absoluteCenter({ vertical: true, position: { left: '32px' } }),
|
||||
...absoluteCenter({ vertical: true, position: { left: '20px' } }),
|
||||
},
|
||||
p: {
|
||||
width: '100%',
|
||||
@@ -102,6 +85,7 @@ export const StyledSmallLink = styled('a')(({ theme }) => {
|
||||
});
|
||||
export const StyledSubTitle = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '190px',
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
color: theme.colors.textColor,
|
||||
@@ -136,7 +120,7 @@ export const StyledLogo = styled('img')({
|
||||
width: 'auto',
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(({ theme }) => {
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
height: '20px',
|
||||
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 }) => {
|
||||
return {
|
||||
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';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
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 = [
|
||||
{
|
||||
@@ -24,6 +26,15 @@ const toolbarList1 = [
|
||||
icon: <SelectIcon />,
|
||||
toolTip: 'Select',
|
||||
disable: false,
|
||||
callback: () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('affine.switch-mouse-mode', {
|
||||
detail: {
|
||||
type: 'default',
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
flavor: 'text',
|
||||
@@ -34,8 +45,19 @@ const toolbarList1 = [
|
||||
{
|
||||
flavor: 'shape',
|
||||
icon: <ShapeIcon />,
|
||||
toolTip: 'Shape (coming soon)',
|
||||
disable: true,
|
||||
toolTip: 'Shape',
|
||||
disable: false,
|
||||
callback: () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('affine.switch-mouse-mode', {
|
||||
detail: {
|
||||
type: 'shape',
|
||||
color: 'black',
|
||||
shape: 'rectangle',
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
flavor: 'sticky',
|
||||
@@ -57,34 +79,19 @@ const toolbarList1 = [
|
||||
disable: true,
|
||||
},
|
||||
];
|
||||
const toolbarList2 = [
|
||||
{
|
||||
flavor: 'undo',
|
||||
icon: <UndoIcon />,
|
||||
toolTip: 'Undo',
|
||||
disable: false,
|
||||
},
|
||||
{
|
||||
flavor: 'redo',
|
||||
icon: <RedoIcon />,
|
||||
toolTip: 'Redo',
|
||||
disable: false,
|
||||
},
|
||||
];
|
||||
|
||||
const UndoRedo = () => {
|
||||
const [canUndo, setCanUndo] = useState(false);
|
||||
const [canRedo, setCanRedo] = useState(false);
|
||||
const { editor } = useEditor();
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
const { page } = editor;
|
||||
const { currentPage } = useAppState();
|
||||
const onHistoryUpdated = useHistoryUpdated();
|
||||
|
||||
page.signals.historyUpdated.on(() => {
|
||||
useEffect(() => {
|
||||
onHistoryUpdated(page => {
|
||||
setCanUndo(page.canUndo);
|
||||
setCanRedo(page.canRedo);
|
||||
});
|
||||
}, [editor]);
|
||||
}, [onHistoryUpdated]);
|
||||
|
||||
return (
|
||||
<StyledToolbarWrapper>
|
||||
@@ -92,7 +99,7 @@ const UndoRedo = () => {
|
||||
<StyledToolbarItem
|
||||
disable={!canUndo}
|
||||
onClick={() => {
|
||||
editor?.page?.undo();
|
||||
currentPage?.undo();
|
||||
}}
|
||||
>
|
||||
<UndoIcon />
|
||||
@@ -102,7 +109,7 @@ const UndoRedo = () => {
|
||||
<StyledToolbarItem
|
||||
disable={!canRedo}
|
||||
onClick={() => {
|
||||
editor?.page?.redo();
|
||||
currentPage?.redo();
|
||||
}}
|
||||
>
|
||||
<RedoIcon />
|
||||
@@ -113,7 +120,7 @@ const UndoRedo = () => {
|
||||
};
|
||||
|
||||
export const EdgelessToolbar = () => {
|
||||
const { mode } = useEditor();
|
||||
const { mode } = useCurrentPageMeta() || {};
|
||||
|
||||
return (
|
||||
<Slide
|
||||
@@ -122,22 +129,25 @@ export const EdgelessToolbar = () => {
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
<StyledEdgelessToolbar>
|
||||
<StyledEdgelessToolbar aria-label="edgeless-toolbar">
|
||||
<StyledToolbarWrapper>
|
||||
{toolbarList1.map(({ icon, toolTip, flavor, disable }, index) => {
|
||||
return (
|
||||
<Tooltip key={index} content={toolTip} placement="right-start">
|
||||
<StyledToolbarItem
|
||||
disable={disable}
|
||||
onClick={() => {
|
||||
console.log('flavor', flavor);
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</StyledToolbarItem>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
{toolbarList1.map(
|
||||
({ icon, toolTip, flavor, disable, callback }, index) => {
|
||||
return (
|
||||
<Tooltip key={index} content={toolTip} placement="right-start">
|
||||
<StyledToolbarItem
|
||||
disable={disable}
|
||||
onClick={() => {
|
||||
console.log('click toolbar button:', flavor);
|
||||
callback?.();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</StyledToolbarItem>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</StyledToolbarWrapper>
|
||||
<UndoRedo />
|
||||
</StyledEdgelessToolbar>
|
||||
|
||||
@@ -2,12 +2,12 @@ import { styled, displayFlex } from '@/styles';
|
||||
|
||||
export const StyledEdgelessToolbar = styled.div(({ theme }) => ({
|
||||
height: '320px',
|
||||
position: 'fixed',
|
||||
position: 'absolute',
|
||||
left: '12px',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto',
|
||||
zIndex: theme.zIndex.modal,
|
||||
zIndex: theme.zIndex.modal - 1,
|
||||
}));
|
||||
|
||||
export const StyledToolbarWrapper = styled.div(({ theme }) => ({
|
||||
|
||||
@@ -11,9 +11,10 @@ import type {
|
||||
AnimateRadioProps,
|
||||
AnimateRadioItemProps,
|
||||
} from './type';
|
||||
import { useTheme } from '@/styles';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
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 {
|
||||
@@ -66,7 +67,9 @@ export const EditorModeSwitch = ({
|
||||
style = {},
|
||||
}: AnimateRadioProps) => {
|
||||
const { mode: themeMode } = useTheme();
|
||||
const { mode, setMode } = useEditor();
|
||||
const { changePageMode } = usePageHelper();
|
||||
const { trash, mode = 'page', id = '' } = useCurrentPageMeta() || {};
|
||||
|
||||
const modifyRadioItemStatus = (): RadioItemStatus => {
|
||||
return {
|
||||
left: isHover
|
||||
@@ -99,6 +102,7 @@ export const EditorModeSwitch = ({
|
||||
data-testid="editor-mode-switcher"
|
||||
shrink={!isHover}
|
||||
style={style}
|
||||
disabled={!!trash}
|
||||
>
|
||||
<AnimateRadioItem
|
||||
isLeft={true}
|
||||
@@ -107,7 +111,7 @@ export const EditorModeSwitch = ({
|
||||
active={mode === 'page'}
|
||||
status={radioItemStatus.left}
|
||||
onClick={() => {
|
||||
setMode('page');
|
||||
changePageMode(id, 'page');
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setRadioItemStatus({
|
||||
@@ -123,11 +127,12 @@ export const EditorModeSwitch = ({
|
||||
<AnimateRadioItem
|
||||
isLeft={false}
|
||||
label="Edgeless"
|
||||
data-testid="switch-edgeless-item"
|
||||
icon={<EdgelessItem />}
|
||||
active={mode === 'edgeless'}
|
||||
status={radioItemStatus.right}
|
||||
onClick={() => {
|
||||
setMode('edgeless');
|
||||
changePageMode(id, 'edgeless');
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setRadioItemStatus({
|
||||
|
||||
@@ -1,45 +1,47 @@
|
||||
import { displayFlex, keyframes, styled } from '@/styles';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import spring, { toString } from 'css-spring';
|
||||
import type { ItemStatus } from './type';
|
||||
|
||||
const ANIMATE_DURATION = 500;
|
||||
|
||||
export const StyledAnimateRadioContainer = styled('div')<{ shrink: boolean }>(
|
||||
({ shrink, theme }) => {
|
||||
const animateScaleStretch = keyframes`${toString(
|
||||
spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' })
|
||||
)}`;
|
||||
const animateScaleShrink = keyframes(
|
||||
`${toString(
|
||||
spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' })
|
||||
)}`
|
||||
);
|
||||
const shrinkStyle = shrink
|
||||
? {
|
||||
animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`,
|
||||
background: 'transparent',
|
||||
}
|
||||
: {
|
||||
animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`,
|
||||
};
|
||||
export const StyledAnimateRadioContainer = styled('div')<{
|
||||
shrink: boolean;
|
||||
disabled: boolean;
|
||||
}>(({ shrink, theme, disabled }) => {
|
||||
const animateScaleStretch = keyframes`${toString(
|
||||
spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' })
|
||||
)}`;
|
||||
const animateScaleShrink = keyframes(
|
||||
`${toString(
|
||||
spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' })
|
||||
)}`
|
||||
);
|
||||
const shrinkStyle = shrink
|
||||
? {
|
||||
animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`,
|
||||
background: 'transparent',
|
||||
}
|
||||
: {
|
||||
animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`,
|
||||
};
|
||||
|
||||
return {
|
||||
height: '36px',
|
||||
borderRadius: '18px',
|
||||
background: theme.colors.hoverBackground,
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`,
|
||||
border: '1px solid transparent',
|
||||
return {
|
||||
height: '36px',
|
||||
borderRadius: '18px',
|
||||
background: disabled ? 'transparent' : theme.colors.hoverBackground,
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`,
|
||||
border: '1px solid transparent',
|
||||
|
||||
...shrinkStyle,
|
||||
':hover': {
|
||||
border: `1px solid ${theme.colors.primaryColor}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
...(disabled ? { pointerEvents: 'none' } : shrinkStyle),
|
||||
':hover': {
|
||||
border: disabled ? '' : `1px solid ${theme.colors.primaryColor}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledMiddleLine = styled('div')<{
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
File renamed without changes.
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from '@/styles';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { MoonIcon, SunIcon } from './icons';
|
||||
import { StyledThemeModeSwitch, StyledSwitchItem } from './style';
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
import { displayFlex, keyframes, styled } from '@/styles';
|
||||
import { CSSProperties } from 'react';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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;
|
||||
+56
-65
@@ -1,5 +1,4 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import { MenuItem } from '@/ui/menu';
|
||||
|
||||
export const StyledHeaderContainer = styled.div<{ hasWarning: boolean }>(
|
||||
({ hasWarning }) => {
|
||||
@@ -10,14 +9,14 @@ export const StyledHeaderContainer = styled.div<{ hasWarning: boolean }>(
|
||||
}
|
||||
);
|
||||
export const StyledHeader = styled.div<{ hasWarning: boolean }>(
|
||||
({ hasWarning, theme }) => {
|
||||
({ hasWarning }) => {
|
||||
return {
|
||||
height: '60px',
|
||||
width: '100vw',
|
||||
...displayFlex('space-between', 'center'),
|
||||
width: '100%',
|
||||
...displayFlex('flex-end', 'center'),
|
||||
background: 'var(--affine-page-background)',
|
||||
transition: 'background-color 0.5s',
|
||||
position: 'fixed',
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
top: hasWarning ? '36px' : '0',
|
||||
padding: '0 22px',
|
||||
@@ -41,20 +40,10 @@ export const StyledTitle = styled('div')(({ theme }) => ({
|
||||
|
||||
export const StyledTitleWrapper = styled('div')({
|
||||
maxWidth: '720px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const StyledLogo = styled('div')(({ theme }) => ({
|
||||
color: theme.colors.primaryColor,
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
cursor: 'pointer',
|
||||
marginLeft: '-22px',
|
||||
...displayFlex('center', 'center'),
|
||||
}));
|
||||
});
|
||||
|
||||
export const StyledHeaderRightSide = styled('div')({
|
||||
height: '100%',
|
||||
@@ -62,54 +51,23 @@ export const StyledHeaderRightSide = styled('div')({
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const StyledMenuItemWrapper = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: '32px',
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
svg: {
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
marginRight: '14px',
|
||||
},
|
||||
'svg:nth-child(2)': {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
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 StyledBrowserWarning = styled.div<{ show: boolean }>(
|
||||
({ theme, show }) => {
|
||||
return {
|
||||
backgroundColor: theme.colors.warningBackground,
|
||||
color: theme.colors.warningColor,
|
||||
height: '36px',
|
||||
width: '100vw',
|
||||
fontSize: theme.font.sm,
|
||||
position: 'fixed',
|
||||
left: '0',
|
||||
top: '0',
|
||||
display: show ? 'flex' : 'none',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledCloseButton = styled.div(({ theme }) => {
|
||||
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',
|
||||
},
|
||||
};
|
||||
});
|
||||
File renamed without changes.
File renamed without changes.
@@ -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;
|
||||
+20
-20
@@ -1,6 +1,6 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
|
||||
export const StyledFAQ = styled('div')(({ theme }) => {
|
||||
export const StyledIsland = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
@@ -12,24 +12,24 @@ export const StyledFAQ = styled('div')(({ theme }) => {
|
||||
zIndex: theme.zIndex.popover,
|
||||
};
|
||||
});
|
||||
export const StyledTransformIcon = styled.div<{ in: boolean }>(
|
||||
({ in: isIn, theme }) => ({
|
||||
height: '32px',
|
||||
width: '32px',
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
right: '0',
|
||||
bottom: '0',
|
||||
top: '0',
|
||||
margin: 'auto',
|
||||
...displayFlex('center', 'center'),
|
||||
opacity: isIn ? 1 : 0,
|
||||
backgroundColor: isIn
|
||||
? theme.colors.hoverBackground
|
||||
: theme.colors.pageBackground,
|
||||
})
|
||||
);
|
||||
export const StyledTransformIcon = styled('div', {
|
||||
shouldForwardProp: prop => prop !== 'in',
|
||||
})<{ in: boolean }>(({ in: isIn, theme }) => ({
|
||||
height: '32px',
|
||||
width: '32px',
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
right: '0',
|
||||
bottom: '0',
|
||||
top: '0',
|
||||
margin: 'auto',
|
||||
...displayFlex('center', 'center'),
|
||||
opacity: isIn ? 1 : 0,
|
||||
backgroundColor: isIn
|
||||
? theme.colors.hoverBackground
|
||||
: theme.colors.pageBackground,
|
||||
}));
|
||||
export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>(
|
||||
({ theme, isEdgelessDark }) => {
|
||||
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 {
|
||||
position: 'absolute',
|
||||
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',
|
||||
};
|
||||
});
|
||||
@@ -1,14 +1,3 @@
|
||||
import { StyledLoading, StyledLoadingItem } from './styled';
|
||||
|
||||
export const Loading = () => {
|
||||
return (
|
||||
<StyledLoading>
|
||||
<StyledLoadingItem />
|
||||
<StyledLoadingItem />
|
||||
<StyledLoadingItem />
|
||||
<StyledLoadingItem />
|
||||
</StyledLoading>
|
||||
);
|
||||
};
|
||||
|
||||
import Loading from './loading';
|
||||
export * from './page-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;
|
||||
@@ -1,14 +1,19 @@
|
||||
import { styled } from '@/styles';
|
||||
|
||||
// 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`
|
||||
position: relative;
|
||||
left: 50%;
|
||||
transform: rotateX(55deg) rotateZ(-45deg)
|
||||
translate(calc(${loadingItemSize} * -2), 0);
|
||||
margin-bottom: calc(3 * ${loadingItemSize});
|
||||
|
||||
position: absolute;
|
||||
left: 25%;
|
||||
top: 25%;
|
||||
@keyframes slide {
|
||||
0% {
|
||||
transform: translate(var(--sx), var(--sy));
|
||||
@@ -21,24 +26,14 @@ export const StyledLoading = styled.div`
|
||||
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;
|
||||
width: ${loadingItemSize};
|
||||
height: ${loadingItemSize};
|
||||
width: ${size}px;
|
||||
height: ${size}px;
|
||||
background: #9dacf9;
|
||||
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%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
);
|
||||
@@ -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 Modal from '@/ui/modal';
|
||||
import Modal, { ModalCloseButton, ModalWrapper } from '@/ui/modal';
|
||||
import getIsMobile from '@/utils/get-is-mobile';
|
||||
import {
|
||||
ModalWrapper,
|
||||
StyledButton,
|
||||
StyledCloseButton,
|
||||
StyledContent,
|
||||
StyledTitle,
|
||||
} from './styles';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { StyledButton, StyledContent, StyledTitle } from './styles';
|
||||
import bg from './bg.png';
|
||||
export const MobileModal = () => {
|
||||
const [showModal, setShowModal] = useState(getIsMobile());
|
||||
return (
|
||||
@@ -18,14 +12,18 @@ export const MobileModal = () => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
>
|
||||
<ModalWrapper>
|
||||
<StyledCloseButton
|
||||
<ModalWrapper
|
||||
width={348}
|
||||
height={388}
|
||||
style={{ backgroundImage: `url(${bg.src})` }}
|
||||
>
|
||||
<ModalCloseButton
|
||||
size={[30, 30]}
|
||||
iconSize={[20, 20]}
|
||||
onClick={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</StyledCloseButton>
|
||||
/>
|
||||
|
||||
<StyledTitle>Ooops!</StyledTitle>
|
||||
<StyledContent>
|
||||
|
||||
@@ -1,38 +1,6 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import bg from './bg.png';
|
||||
|
||||
export const ModalWrapper = styled.div(({ theme }) => {
|
||||
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 }) => {
|
||||
export const StyledTitle = styled.div(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
fontSize: '20px',
|
||||
@@ -42,7 +10,7 @@ export const StyledTitle = styled.div(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledContent = styled.div(({ theme }) => {
|
||||
export const StyledContent = styled.div(() => {
|
||||
return {
|
||||
padding: '0 40px',
|
||||
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 "{query}" 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',
|
||||
Strikethrough: '⌘+⇧+S',
|
||||
'Inline code': ' ⌘+E',
|
||||
'Code block': '⌘+⌥+C',
|
||||
Link: '⌘+K',
|
||||
'Body text': '⌘+⌥+0',
|
||||
'Heading 1': '⌘+⌥+1',
|
||||
@@ -23,7 +24,9 @@ export const macMarkdownShortcuts = {
|
||||
Italic: '*Text* ',
|
||||
Underline: '~Text~ ',
|
||||
Strikethrough: '~~Text~~ ',
|
||||
Divider: '***',
|
||||
'Inline code': '`Text` ',
|
||||
'Code block': '``` Space',
|
||||
'Heading 1': '# Text',
|
||||
'Heading 2': '## Text',
|
||||
'Heading 3': '### Text',
|
||||
@@ -40,6 +43,7 @@ export const windowsKeyboardShortcuts = {
|
||||
Underline: 'Ctrl+U',
|
||||
Strikethrough: 'Ctrl+Shift+S',
|
||||
'Inline code': ' Ctrl+E',
|
||||
'Code block': 'Ctrl+Alt+C',
|
||||
Link: 'Ctrl+K',
|
||||
'Body text': 'Ctrl+Shift+0',
|
||||
'Heading 1': 'Ctrl+Shift+1',
|
||||
@@ -57,6 +61,7 @@ export const winMarkdownShortcuts = {
|
||||
Underline: '~Text~ ',
|
||||
Strikethrough: '~~Text~~ ',
|
||||
'Inline code': '`Text` ',
|
||||
'Code block': '``` Text',
|
||||
'Heading 1': '# Text',
|
||||
'Heading 2': '## 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
Reference in new issue
Block a user