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