mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
init: the first public commit for AFFiNE
This commit is contained in:
11
apps/ligo-virgo/.babelrc
Normal file
11
apps/ligo-virgo/.babelrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
16
apps/ligo-virgo/.browserslistrc
Normal file
16
apps/ligo-virgo/.browserslistrc
Normal file
@@ -0,0 +1,16 @@
|
||||
# This file is used by:
|
||||
# 1. autoprefixer to adjust CSS to support the below specified browsers
|
||||
# 2. babel preset-env to adjust included polyfills
|
||||
#
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
#
|
||||
# If you need to support different browsers in production, you may tweak the list below.
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major version
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
18
apps/ligo-virgo/.eslintrc.json
Normal file
18
apps/ligo-virgo/.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
12
apps/ligo-virgo/jest.config.js
Normal file
12
apps/ligo-virgo/jest.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
displayName: 'ligo-virgo',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'node_modules\\/.+\\.js$': 'jest-esm-transformer',
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/apps/ligo-virgo',
|
||||
transformIgnorePatterns: [],
|
||||
};
|
||||
20
apps/ligo-virgo/package.json
Normal file
20
apps/ligo-virgo/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "ligo-virgo",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"description": "",
|
||||
"main": "jest.config.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "AFFiNE <developer@affine.pro>",
|
||||
"dependencies": {
|
||||
"@mui/icons-material": "^5.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"firebase": "^9.8.4",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"webpack": "^5.73.0"
|
||||
}
|
||||
}
|
||||
75
apps/ligo-virgo/project.json
Normal file
75
apps/ligo-virgo/project.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"sourceRoot": "apps/ligo-virgo/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/web:webpack",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"compiler": "babel",
|
||||
"outputPath": "dist/apps/ligo-virgo",
|
||||
"index": "apps/ligo-virgo/src/index.html",
|
||||
"baseHref": "/",
|
||||
"main": "apps/ligo-virgo/src/index.tsx",
|
||||
"polyfills": "apps/ligo-virgo/src/polyfills.ts",
|
||||
"tsConfig": "apps/ligo-virgo/tsconfig.app.json",
|
||||
"assets": ["apps/ligo-virgo/src/assets"],
|
||||
"styles": [],
|
||||
"scripts": [],
|
||||
"webpackConfig": "apps/ligo-virgo/webpack.config.js"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/ligo-virgo/src/environments/environment.ts",
|
||||
"with": "apps/ligo-virgo/src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": true,
|
||||
"extractLicenses": false,
|
||||
"vendorChunk": false,
|
||||
"generateIndexHtml": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nrwl/web:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "ligo-virgo:build:development",
|
||||
"hmr": true,
|
||||
"proxyConfig": "apps/ligo-virgo/proxy.conf.json",
|
||||
"open": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "ligo-virgo:build:production",
|
||||
"hmr": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/ligo-virgo/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/apps/ligo-virgo"],
|
||||
"options": {
|
||||
"jestConfig": "apps/ligo-virgo/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
},
|
||||
"check": {
|
||||
"executor": "./tools/executors/tsCheck:tsCheck"
|
||||
}
|
||||
},
|
||||
"tags": ["app:ligo-virgo"]
|
||||
}
|
||||
13
apps/ligo-virgo/proxy.conf.json
Normal file
13
apps/ligo-virgo/proxy.conf.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "https://nightly.affine.pro/",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
"/collaboration": {
|
||||
"target": "https://canary.affine.pro",
|
||||
"ws": true,
|
||||
"changeOrigin": true,
|
||||
"secure": false
|
||||
}
|
||||
}
|
||||
0
apps/ligo-virgo/src/assets/.gitkeep
Normal file
0
apps/ligo-virgo/src/assets/.gitkeep
Normal file
BIN
apps/ligo-virgo/src/assets/images/favicon.ico
Normal file
BIN
apps/ligo-virgo/src/assets/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
60
apps/ligo-virgo/src/custom-formatter.ts
Normal file
60
apps/ligo-virgo/src/custom-formatter.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { AsyncBlock } from '@toeverything/framework/virgo';
|
||||
import { isDev } from '@toeverything/utils';
|
||||
|
||||
/**
|
||||
* Ported from https://github.com/vuejs/core/blob/main/packages/runtime-core/src/customFormatter.ts
|
||||
* See [Custom Object Formatters in Chrome DevTools](https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U)
|
||||
*/
|
||||
|
||||
const isAsyncBlock = (x: unknown): x is AsyncBlock => {
|
||||
return x instanceof AsyncBlock;
|
||||
};
|
||||
|
||||
export function initCustomFormatter() {
|
||||
if (!isDev || typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const bannerStyle = {
|
||||
style: 'color: #eee; background: #3F6FDB; margin-right: 5px; padding: 2px; border-radius: 4px',
|
||||
};
|
||||
const typeStyle = {
|
||||
style: 'color: #eee; background: #DB6D56; margin-right: 5px; padding: 2px; border-radius: 4px',
|
||||
};
|
||||
|
||||
// custom formatter for Chrome
|
||||
// https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html
|
||||
const formatter = {
|
||||
header(obj: unknown, config = { expand: false }) {
|
||||
if (!isAsyncBlock(obj) || config.expand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'div',
|
||||
{},
|
||||
['span', bannerStyle, 'AsyncBlock'],
|
||||
['span', typeStyle, obj.type],
|
||||
// @ts-expect-error Debug at development environment
|
||||
`${JSON.stringify(obj.raw_data.properties)}`,
|
||||
];
|
||||
},
|
||||
hasBody(obj: unknown) {
|
||||
return true;
|
||||
},
|
||||
body(obj: unknown) {
|
||||
return ['object', { object: obj, config: { expand: true } }];
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((window as any).devtoolsFormatters) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).devtoolsFormatters.push(formatter);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).devtoolsFormatters = [formatter];
|
||||
}
|
||||
}
|
||||
|
||||
initCustomFormatter();
|
||||
3
apps/ligo-virgo/src/environments/environment.prod.ts
Normal file
3
apps/ligo-virgo/src/environments/environment.prod.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
};
|
||||
6
apps/ligo-virgo/src/environments/environment.ts
Normal file
6
apps/ligo-virgo/src/environments/environment.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// When building for production, this file is replaced with `environment.prod.ts`.
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
};
|
||||
16
apps/ligo-virgo/src/index.html
Normal file
16
apps/ligo-virgo/src/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- local dev index.html -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="https://app.affine.pro/favicon.ico" />
|
||||
<title>Affine | Local Dev Environment</title>
|
||||
<script>
|
||||
window.global = window;
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
28
apps/ligo-virgo/src/index.tsx
Normal file
28
apps/ligo-virgo/src/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable filename-rules/match */
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import { ThemeProvider } from '@toeverything/components/ui';
|
||||
import { FeatureFlagsProvider } from '@toeverything/datasource/feature-flags';
|
||||
|
||||
import './custom-formatter';
|
||||
import { LigoVirgoRoutes } from './pages';
|
||||
import './styles.css';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
if (!container) {
|
||||
throw new Error('No root container found');
|
||||
}
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<ThemeProvider>
|
||||
<FeatureFlagsProvider>
|
||||
<LigoVirgoRoutes />
|
||||
</FeatureFlagsProvider>
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
</StrictMode>
|
||||
);
|
||||
36
apps/ligo-virgo/src/pages/AppContainer.tsx
Normal file
36
apps/ligo-virgo/src/pages/AppContainer.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import { SettingsSidebar, LayoutHeader } from '@toeverything/components/layout';
|
||||
|
||||
export function LigoVirgoRootContainer() {
|
||||
return (
|
||||
<StyledRootContainer id="idAppRoot">
|
||||
<StyledContentContainer>
|
||||
<LayoutHeader />
|
||||
<StyledMainContainer>
|
||||
<Outlet />
|
||||
</StyledMainContainer>
|
||||
</StyledContentContainer>
|
||||
<SettingsSidebar />
|
||||
</StyledRootContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledMainContainer = styled('div')({
|
||||
flex: 'auto',
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
const StyledRootContainer = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: '100vh',
|
||||
});
|
||||
|
||||
const StyledContentContainer = styled('div')({
|
||||
flex: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
64
apps/ligo-virgo/src/pages/AppRoutes.tsx
Normal file
64
apps/ligo-virgo/src/pages/AppRoutes.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
|
||||
import Agenda from './agenda';
|
||||
import { WorkspaceContainer } from './workspace';
|
||||
import Recent from './recent';
|
||||
import Search from './search';
|
||||
import Settings from './settings';
|
||||
import Shared from './shared';
|
||||
import Starred from './starred';
|
||||
import { Login } from './account';
|
||||
import { PageNotFound } from './status/page-not-found';
|
||||
import { WorkspaceNotFound } from './status/workspace-not-found';
|
||||
import { RoutePrivate } from './RoutePrivate';
|
||||
import { RoutePublicAutoLogin } from './RoutePublicAutoLogin';
|
||||
import { Tools } from './tools';
|
||||
import { Templates } from './templates';
|
||||
import { LigoVirgoRootContainer } from './AppContainer';
|
||||
import { UIPage } from './ui';
|
||||
|
||||
export function LigoVirgoRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<LigoVirgoRootContainer />}>
|
||||
<Route path="/error/404" element={<PageNotFound />} />
|
||||
<Route
|
||||
path="/error/workspace"
|
||||
element={<WorkspaceNotFound />}
|
||||
/>
|
||||
|
||||
<Route path="/agenda/*" element={<Agenda />} />
|
||||
<Route path="/recent" element={<Recent />} />
|
||||
<Route path="/search" element={<Search />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/shared" element={<Shared />} />
|
||||
<Route path="/started" element={<Starred />} />
|
||||
<Route path="/templates" element={<Templates />} />
|
||||
<Route path="/ui" element={<UIPage />} />
|
||||
<Route
|
||||
path="/:workspace_id/*"
|
||||
element={
|
||||
<RoutePrivate>
|
||||
<WorkspaceContainer />
|
||||
</RoutePrivate>
|
||||
}
|
||||
/>
|
||||
<Route path="/" element={<Navigate to="/login" replace />} />
|
||||
</Route>
|
||||
|
||||
{/* put public routes here; header and sidebar are disabled here */}
|
||||
<Route>
|
||||
<Route path="/tools/*" element={<Tools />} />
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<RoutePublicAutoLogin>
|
||||
<Login />
|
||||
</RoutePublicAutoLogin>
|
||||
}
|
||||
/>
|
||||
<Route path="/" element={<Navigate to="/login" replace />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
37
apps/ligo-virgo/src/pages/RoutePrivate.tsx
Normal file
37
apps/ligo-virgo/src/pages/RoutePrivate.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Navigate, useLocation } from 'react-router-dom';
|
||||
|
||||
import { PageLoading, Error } from '@toeverything/components/account';
|
||||
import { useUserAndSpaces } from '@toeverything/datasource/state';
|
||||
|
||||
export type RoutePrivateProps = {
|
||||
children: JSX.Element;
|
||||
unauthorizedRedirectTo?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A routing component that cannot be accessed without logging in, and can only be accessed after logging in.
|
||||
*/
|
||||
export function RoutePrivate({
|
||||
children,
|
||||
unauthorizedRedirectTo = '/login',
|
||||
}: RoutePrivateProps) {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { user, loading } = useUserAndSpaces();
|
||||
|
||||
if (user == null && loading) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<Navigate
|
||||
to={unauthorizedRedirectTo}
|
||||
state={{ from: pathname }}
|
||||
replace={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
33
apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx
Normal file
33
apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Navigate, useLocation } from 'react-router-dom';
|
||||
|
||||
import { PageLoading } from '@toeverything/components/account';
|
||||
import { useUserAndSpaces } from '@toeverything/datasource/state';
|
||||
|
||||
export type RouteUnauthorizedOnlyProps = {
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Routing components that are accessible without logging in and inaccessible after logging in will automatically jump to the specified route authorizedRedirectTo
|
||||
*/
|
||||
export function RoutePublicAutoLogin({ children }: RouteUnauthorizedOnlyProps) {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { user, loading, currentSpaceId } = useUserAndSpaces();
|
||||
|
||||
if (user == null && loading) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
if (currentSpaceId) {
|
||||
return (
|
||||
<Navigate
|
||||
to={`/${currentSpaceId}`}
|
||||
state={{ from: pathname }}
|
||||
replace={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/account/index.ts
Normal file
3
apps/ligo-virgo/src/pages/account/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Login } from '@toeverything/components/account';
|
||||
|
||||
export { Login };
|
||||
3
apps/ligo-virgo/src/pages/agenda/calendar/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/agenda/calendar/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function AgendaCalendar() {
|
||||
return <span>AgendaCalendar</span>;
|
||||
}
|
||||
18
apps/ligo-virgo/src/pages/agenda/container/index.tsx
Normal file
18
apps/ligo-virgo/src/pages/agenda/container/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import style9 from 'style9';
|
||||
|
||||
import { MuiBox as Box } from '@toeverything/components/ui';
|
||||
|
||||
const styles = style9.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
},
|
||||
});
|
||||
|
||||
export default function AgendaRootContainer() {
|
||||
return (
|
||||
<Box className={styles('container')}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/agenda/home/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/agenda/home/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function AgendaHome() {
|
||||
return <span>AgendaHome</span>;
|
||||
}
|
||||
20
apps/ligo-virgo/src/pages/agenda/index.tsx
Normal file
20
apps/ligo-virgo/src/pages/agenda/index.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
|
||||
import Container from './container';
|
||||
import Calendar from './calendar';
|
||||
import Tasks from './tasks';
|
||||
import Today from './today';
|
||||
import Home from './home';
|
||||
|
||||
export default function AgendaContainer() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Container />}>
|
||||
<Route path="/calendar" element={<Calendar />} />
|
||||
<Route path="/tasks" element={<Tasks />} />
|
||||
<Route path="/today" element={<Today />} />
|
||||
<Route path="/" element={<Home />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/agenda/tasks/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/agenda/tasks/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function AgendaTasks() {
|
||||
return <span>AgendaTasks</span>;
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/agenda/today/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/agenda/today/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function AgendaToday() {
|
||||
return <span>AgendaToday</span>;
|
||||
}
|
||||
1
apps/ligo-virgo/src/pages/index.ts
Normal file
1
apps/ligo-virgo/src/pages/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { LigoVirgoRoutes } from './AppRoutes';
|
||||
3
apps/ligo-virgo/src/pages/recent/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/recent/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Recent() {
|
||||
return <span>Recent</span>;
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/search/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/search/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Search() {
|
||||
return <span>Search</span>;
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/settings/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/settings/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Settings() {
|
||||
return <span>Settings</span>;
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/shared/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/shared/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Shared() {
|
||||
return <span>Shared</span>;
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/starred/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/starred/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Starred() {
|
||||
return <span>Starred</span>;
|
||||
}
|
||||
7
apps/ligo-virgo/src/pages/status/page-not-found.tsx
Normal file
7
apps/ligo-virgo/src/pages/status/page-not-found.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Error } from '@toeverything/components/account';
|
||||
|
||||
export function PageNotFound() {
|
||||
return <Error clearOnClick={true} />;
|
||||
}
|
||||
|
||||
export default PageNotFound;
|
||||
13
apps/ligo-virgo/src/pages/status/workspace-not-found.tsx
Normal file
13
apps/ligo-virgo/src/pages/status/workspace-not-found.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Error } from '@toeverything/components/account';
|
||||
|
||||
export function WorkspaceNotFound() {
|
||||
return (
|
||||
<Error
|
||||
subTitle="No workspace is found, please contact the admin"
|
||||
action1Text="Login or Register"
|
||||
clearOnClick={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default WorkspaceNotFound;
|
||||
98
apps/ligo-virgo/src/pages/templates/index.tsx
Normal file
98
apps/ligo-virgo/src/pages/templates/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { styled, ListButton } from '@toeverything/components/ui';
|
||||
import { TemplateData } from './template-data';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AffineEditor } from '@toeverything/components/affine-editor';
|
||||
const TemplatesContainer = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#fff',
|
||||
border: '1px solid #E2E7ED',
|
||||
borderRadius: '5px',
|
||||
margin: '0 auto',
|
||||
'.sidebar': {
|
||||
width: '240px',
|
||||
display: 'flex',
|
||||
borderRight: '1px solid #E2E7ED',
|
||||
flexDirection: 'column',
|
||||
color: 'rgba(55, 53, 47, 0.65)',
|
||||
background: 'rgb(247, 246, 243)',
|
||||
padding: '12px',
|
||||
},
|
||||
'.preview-template': {
|
||||
display: 'flex',
|
||||
},
|
||||
'.sidebar-title': {
|
||||
borderBottom: '1px solid #E2E7ED',
|
||||
},
|
||||
'.sidebar-template-type': {
|
||||
height: '600px',
|
||||
overflowY: 'scroll',
|
||||
ul: {},
|
||||
'ul li': {
|
||||
paddingLeft: '10px',
|
||||
height: '32px',
|
||||
lineHeight: '32px',
|
||||
listStyle: 'none',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
background: '#eee',
|
||||
},
|
||||
},
|
||||
},
|
||||
'.btn-use-this-template': {
|
||||
background: '#eee',
|
||||
color: '#fff',
|
||||
':hover': {
|
||||
background: '#ccc',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface ITemplateProps {
|
||||
handleClickUseThisTemplate?: () => void;
|
||||
}
|
||||
function Templates(props: ITemplateProps) {
|
||||
const handle_click_use_this_template = () => {
|
||||
props.handleClickUseThisTemplate();
|
||||
};
|
||||
const { workspace_id, page_id } = useParams();
|
||||
return (
|
||||
<TemplatesContainer>
|
||||
<div className="sidebar">
|
||||
<div className="sidebar-title">
|
||||
<ListButton
|
||||
className="btn-use-this-template"
|
||||
content="Use this template"
|
||||
onClick={handle_click_use_this_template}
|
||||
/>
|
||||
</div>
|
||||
<div className="sidebar-template-type">
|
||||
{TemplateData.map((item, index) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
{item.name}
|
||||
<ul>
|
||||
{item.subList.map((item, index) => {
|
||||
return <li key={index}>{item.name}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="preview-template">
|
||||
{page_id && (
|
||||
<AffineEditor
|
||||
workspace={workspace_id}
|
||||
rootBlockId={page_id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</TemplatesContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export { Templates };
|
||||
125
apps/ligo-virgo/src/pages/templates/template-data.ts
Normal file
125
apps/ligo-virgo/src/pages/templates/template-data.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
export const TemplateData = [
|
||||
{
|
||||
name: 'Design',
|
||||
subList: [
|
||||
{ name: '🚘 Roadmap' },
|
||||
{ name: '🔬 User Research Database' },
|
||||
{ name: '🎒 Design Tasks' },
|
||||
{ name: '✏️ Meeting Notes' },
|
||||
{ name: '🖋️ Design System' },
|
||||
{ name: '🎯 Company goals' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Student',
|
||||
subList: [
|
||||
{ name: '✏️ Class Notes' },
|
||||
{ name: '🏗 Job Applications' },
|
||||
{ name: '⚖️ Grade Calculator' },
|
||||
{ name: '🏡 Club Homepage' },
|
||||
{ name: '📚 Reading List' },
|
||||
{ name: '📜 Thesis Planning' },
|
||||
{ name: '📍 Cornell Notes System' },
|
||||
{ name: '📇 Personal CRM' },
|
||||
{ name: '✌️ Roommate Space' },
|
||||
{ name: '💸 Simple Budget' },
|
||||
{ name: '📄 Syllabus' },
|
||||
{ name: '🏠 Classroom Home' },
|
||||
{ name: '📋 Lesson Plans' },
|
||||
{ name: '🗓 Course Schedule' },
|
||||
{ name: '👋 Class Directory' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Engineering',
|
||||
subList: [
|
||||
{ name: '🎒 To-Do' },
|
||||
{ name: '🚘 Roadmap' },
|
||||
{ name: '📓 Engineering Wiki' },
|
||||
{ name: '📎 Docs' },
|
||||
{ name: '✏️ Meeting Notes' },
|
||||
{ name: '🎯 Company goals' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Human resources',
|
||||
subList: [
|
||||
{ name: '💼 Job Board' },
|
||||
{ name: '✏️ Meeting Notes' },
|
||||
{ name: '🚂 New Hire Onboarding' },
|
||||
{ name: '📮 Applicant Tracker' },
|
||||
{ name: '🏠 Company Home' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Marketing',
|
||||
subList: [
|
||||
{ name: '🎨 Brand Assets' },
|
||||
{ name: '✏️ Meeting Notes' },
|
||||
{ name: '🎤 Media List' },
|
||||
{ name: '📆 Content Calendar' },
|
||||
{ name: '🎟️ Mood Board' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Personal',
|
||||
subList: [
|
||||
{ name: '📌 Quick Note' },
|
||||
{ name: '🏠 Personal Home' },
|
||||
{ name: '✔️ Task List' },
|
||||
{ name: '🖊️ Journal' },
|
||||
{ name: '📚 Reading List' },
|
||||
{ name: '🏔️ Goals' },
|
||||
{ name: '✈️ Travel Planner' },
|
||||
{ name: '✏️ Blog Post' },
|
||||
{ name: '📔 Simple Notebook' },
|
||||
{ name: '👟 Habit Tracker' },
|
||||
{ name: '🧭 Life Wiki' },
|
||||
{ name: '👔 Resume' },
|
||||
{ name: '📥 Job Applications' },
|
||||
{ name: '📕 Weekly Agenda' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
subList: [
|
||||
{ name: '️📝 Meeting Notes' },
|
||||
{ name: '📄 Docs' },
|
||||
{ name: '🏠 Team Home' },
|
||||
{ name: '☑️ Team Tasks' },
|
||||
{ name: '✔️ Task List' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Product management',
|
||||
subList: [
|
||||
{ name: '🚘 Roadmap' },
|
||||
{ name: ' User Research Database' },
|
||||
{ name: '📎 Docs' },
|
||||
{ name: '✏️ Meeting Notes' },
|
||||
{ name: '🏗 Product Wiki' },
|
||||
{ name: '🎯 Company goals' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Sales',
|
||||
subList: [
|
||||
{ name: '✏️ Meeting Notes' },
|
||||
{ name: '👟 Sales CRM' },
|
||||
{ name: '📕 Sales Wiki' },
|
||||
{ name: '🎯 Competitive Analysis' },
|
||||
{ name: '✌️ Sales Assets' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Support',
|
||||
subList: [
|
||||
{ name: '✌️ Team Directory' },
|
||||
{ name: '❓ Product FAQs' },
|
||||
{ name: '✏️ Meeting Notes' },
|
||||
{ name: '🎒 Task List' },
|
||||
{ name: '🚨 Help Center' },
|
||||
{ name: '📎 Process Docs' },
|
||||
],
|
||||
},
|
||||
];
|
||||
9
apps/ligo-virgo/src/pages/tools/container.tsx
Normal file
9
apps/ligo-virgo/src/pages/tools/container.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
export function Container() {
|
||||
return (
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
85
apps/ligo-virgo/src/pages/tools/icons/Icons.tsx
Normal file
85
apps/ligo-virgo/src/pages/tools/icons/Icons.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { type FC, useRef } from 'react';
|
||||
import * as uiIcons from '@toeverything/components/icons';
|
||||
import { message, styled } from '@toeverything/components/ui';
|
||||
import { copy } from './copy';
|
||||
|
||||
const IconBooth: FC<{ name: string; Icon: FC<any> }> = ({ name, Icon }) => {
|
||||
const on_click = () => {
|
||||
copy(`<${name} />`);
|
||||
message.success({
|
||||
content: 'Copied.',
|
||||
});
|
||||
};
|
||||
return (
|
||||
<IconContainer title={name} onClick={on_click}>
|
||||
<Icon />
|
||||
<IconName>{name}</IconName>
|
||||
</IconContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const _icons = Object.entries(uiIcons).filter(([key]) => key !== 'timestamp');
|
||||
|
||||
export const Icons: FC = () => {
|
||||
const ref = useRef<HTMLHeadingElement>(null);
|
||||
return (
|
||||
<Container>
|
||||
<h3 ref={ref}>Example:</h3>
|
||||
<div>
|
||||
<code>
|
||||
{`import { TextIcon } from '@toeverything/components/ui'`};
|
||||
</code>
|
||||
</div>
|
||||
<h3>{`Total: ${_icons.length}`}</h3>
|
||||
<blockquote>Click to copy.</blockquote>
|
||||
<p>{`Last Updated: ${new Date(
|
||||
uiIcons.timestamp
|
||||
).toLocaleString()}`}</p>
|
||||
<hr />
|
||||
<IconsContainer>
|
||||
{_icons.map(([key, icon]) => {
|
||||
return <IconBooth key={key} name={key} Icon={icon as FC} />;
|
||||
})}
|
||||
</IconsContainer>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled('div')({
|
||||
color: '#98ACBD',
|
||||
padding: '20px',
|
||||
});
|
||||
|
||||
const IconName = styled('div')({
|
||||
width: '100%',
|
||||
marginTop: '8px',
|
||||
wordBreak: 'break-all',
|
||||
});
|
||||
|
||||
const IconContainer = styled('div')(({ theme }) => ({
|
||||
width: '112px',
|
||||
borderRadius: '4px',
|
||||
padding: '4px',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'center',
|
||||
'--color-0': theme.affine.palette.hover,
|
||||
'--color-1': theme.affine.palette.icons,
|
||||
|
||||
'& svg:first-of-type': {
|
||||
boxShadow: '0 0 6px #e0e6eb',
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: '#F5F7F8',
|
||||
},
|
||||
}));
|
||||
|
||||
const IconsContainer = styled('div')({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill,112px)',
|
||||
justifyContent: 'justify',
|
||||
alignContent: 'start',
|
||||
columnGap: '16px',
|
||||
rowGap: '24px',
|
||||
marginTop: '24px',
|
||||
});
|
||||
18
apps/ligo-virgo/src/pages/tools/icons/copy.ts
Normal file
18
apps/ligo-virgo/src/pages/tools/icons/copy.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
const create_fake_element = (value: string) => {
|
||||
const fake_element = document.createElement('textarea');
|
||||
fake_element.style.position = 'fixed';
|
||||
fake_element.style.top = '0';
|
||||
fake_element.style.clipPath = "path('M0,0 L0,0')";
|
||||
fake_element.setAttribute('readonly', '');
|
||||
fake_element.value = value;
|
||||
return fake_element;
|
||||
};
|
||||
|
||||
export const copy = (value: string) => {
|
||||
const fake_element = create_fake_element(value);
|
||||
document.body.appendChild(fake_element);
|
||||
fake_element.select();
|
||||
fake_element.setSelectionRange(0, fake_element.value.length);
|
||||
document.execCommand('copy');
|
||||
fake_element.remove();
|
||||
};
|
||||
1
apps/ligo-virgo/src/pages/tools/icons/index.ts
Normal file
1
apps/ligo-virgo/src/pages/tools/icons/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Icons } from './Icons';
|
||||
14
apps/ligo-virgo/src/pages/tools/index.tsx
Normal file
14
apps/ligo-virgo/src/pages/tools/index.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
|
||||
import { Container } from './container';
|
||||
import { Icons } from './icons';
|
||||
|
||||
export function Tools() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Container />}>
|
||||
<Route path="/icons" element={<Icons />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
9
apps/ligo-virgo/src/pages/ui/index.tsx
Normal file
9
apps/ligo-virgo/src/pages/ui/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export const UIPage = () => {
|
||||
return (
|
||||
<div className="">
|
||||
This page is used to show ui components of Affine ~
|
||||
</div>
|
||||
);
|
||||
};
|
||||
19
apps/ligo-virgo/src/pages/workspace/Container.tsx
Normal file
19
apps/ligo-virgo/src/pages/workspace/Container.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import style9 from 'style9';
|
||||
|
||||
import { MuiBox as Box } from '@toeverything/components/ui';
|
||||
|
||||
const styles = style9.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
});
|
||||
|
||||
export function WorkspaceRootContainer() {
|
||||
return (
|
||||
<Box className={styles('container')}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
39
apps/ligo-virgo/src/pages/workspace/Home.tsx
Normal file
39
apps/ligo-virgo/src/pages/workspace/Home.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useUserAndSpaces } from '@toeverything/datasource/state';
|
||||
import { services, TemplateFactory } from '@toeverything/datasource/db-service';
|
||||
|
||||
export function WorkspaceHome() {
|
||||
const navigate = useNavigate();
|
||||
const { workspace_id } = useParams();
|
||||
const { user } = useUserAndSpaces();
|
||||
|
||||
useEffect(() => {
|
||||
const navigate_to_user_initial_page = async () => {
|
||||
const recent_pages = await services.api.userConfig.getRecentPages(
|
||||
workspace_id,
|
||||
user.id
|
||||
);
|
||||
|
||||
const user_initial_page_id =
|
||||
await services.api.userConfig.getUserInitialPage(
|
||||
workspace_id,
|
||||
user.id
|
||||
);
|
||||
if (recent_pages.length === 0) {
|
||||
await services.api.editorBlock.copyTemplateToPage(
|
||||
workspace_id,
|
||||
user_initial_page_id,
|
||||
TemplateFactory.generatePageTemplateByGroupKeys({
|
||||
name: null,
|
||||
groupKeys: ['todolist'],
|
||||
})
|
||||
);
|
||||
}
|
||||
navigate(`/${workspace_id}/${user_initial_page_id}`);
|
||||
};
|
||||
navigate_to_user_initial_page();
|
||||
}, [navigate, user.id, workspace_id]);
|
||||
|
||||
return null;
|
||||
}
|
||||
37
apps/ligo-virgo/src/pages/workspace/Whiteboard.tsx
Normal file
37
apps/ligo-virgo/src/pages/workspace/Whiteboard.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { memo, useEffect } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { AffineBoard } from '@toeverything/components/affine-board';
|
||||
import { useUserAndSpaces } from '@toeverything/datasource/state';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
|
||||
const MemoAffineBoard = memo(AffineBoard, (prev, next) => {
|
||||
return prev.rootBlockId === next.rootBlockId;
|
||||
});
|
||||
|
||||
type WhiteboardProps = {
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export const Whiteboard = (props: WhiteboardProps) => {
|
||||
const { page_id } = useParams();
|
||||
const { user } = useUserAndSpaces();
|
||||
|
||||
useEffect(() => {
|
||||
if (!user?.id || !props.workspace) return;
|
||||
const update_recent_pages = async () => {
|
||||
// TODO: deal with it temporarily
|
||||
await services.api.editorBlock.getWorkspaceDbBlock(
|
||||
props.workspace,
|
||||
{
|
||||
userId: user.id,
|
||||
}
|
||||
);
|
||||
};
|
||||
update_recent_pages();
|
||||
}, [user, props.workspace]);
|
||||
|
||||
return (
|
||||
<MemoAffineBoard workspace={props.workspace} rootBlockId={page_id} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import clsx from 'clsx';
|
||||
import style9 from 'style9';
|
||||
import {
|
||||
MuiBox as Box,
|
||||
MuiButton as Button,
|
||||
MuiCollapse as Collapse,
|
||||
MuiIconButton as IconButton,
|
||||
} from '@toeverything/components/ui';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
|
||||
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
import { NewpageIcon } from '@toeverything/components/common';
|
||||
import {
|
||||
usePageTree,
|
||||
useCalendarHeatmap,
|
||||
} from '@toeverything/components/layout';
|
||||
|
||||
const styles = style9.create({
|
||||
ligoButton: {
|
||||
textTransform: 'none',
|
||||
},
|
||||
newPage: {
|
||||
color: '#B6C7D3',
|
||||
width: '26px',
|
||||
fontSize: '18px',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
});
|
||||
|
||||
export type CollapsiblePageTreeProps = {
|
||||
title?: string;
|
||||
initialOpen?: boolean;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
|
||||
const { className, style, children, title, initialOpen = true } = props;
|
||||
const navigate = useNavigate();
|
||||
const { workspace_id, page_id } = useParams();
|
||||
|
||||
const { handleAddPage } = usePageTree();
|
||||
const { addPageToday } = useCalendarHeatmap();
|
||||
|
||||
const [open, setOpen] = useState(initialOpen);
|
||||
|
||||
const create_page = useCallback(async () => {
|
||||
if (page_id) {
|
||||
const newPage = await services.api.editorBlock.create({
|
||||
workspace: workspace_id,
|
||||
type: 'page' as const,
|
||||
});
|
||||
|
||||
await handleAddPage(newPage.id);
|
||||
addPageToday();
|
||||
|
||||
navigate(`/${workspace_id}/${newPage.id}`);
|
||||
}
|
||||
}, [addPageToday, handleAddPage, navigate, page_id, workspace_id]);
|
||||
|
||||
const [newPageBtnVisible, setNewPageBtnVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingRight: 1,
|
||||
}}
|
||||
onMouseEnter={() => setNewPageBtnVisible(true)}
|
||||
onMouseLeave={() => setNewPageBtnVisible(false)}
|
||||
>
|
||||
<Button
|
||||
startIcon={
|
||||
open ? <ArrowDropDownIcon /> : <ArrowRightIcon />
|
||||
}
|
||||
onClick={() => setOpen(prev => !prev)}
|
||||
sx={{ color: '#566B7D', textTransform: 'none' }}
|
||||
className={clsx(styles('ligoButton'), className)}
|
||||
style={style}
|
||||
disableElevation
|
||||
disableRipple
|
||||
>
|
||||
{title}
|
||||
</Button>
|
||||
|
||||
{newPageBtnVisible && (
|
||||
<div
|
||||
onClick={create_page}
|
||||
className={clsx(styles('newPage'), className)}
|
||||
>
|
||||
+
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
{children ? (
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
{children}
|
||||
</Collapse>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsiblePageTree;
|
||||
18
apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx
Normal file
18
apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
/* eslint-disable filename-rules/match */
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { Page } from './index';
|
||||
|
||||
describe('App', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Page workspace="default" />);
|
||||
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have a greeting as the title', () => {
|
||||
const { getByText } = render(<Page workspace="default" />);
|
||||
|
||||
expect(getByText(/Welcome ligo-virgo/gi)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
176
apps/ligo-virgo/src/pages/workspace/docs/index.tsx
Normal file
176
apps/ligo-virgo/src/pages/workspace/docs/index.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
/* eslint-disable filename-rules/match */
|
||||
import { useEffect } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import {
|
||||
MuiBox as Box,
|
||||
MuiCircularProgress as CircularProgress,
|
||||
MuiDivider as Divider,
|
||||
styled,
|
||||
} from '@toeverything/components/ui';
|
||||
import { AffineEditor } from '@toeverything/components/affine-editor';
|
||||
import {
|
||||
CalendarHeatmap,
|
||||
PageTree,
|
||||
Activities,
|
||||
} from '@toeverything/components/layout';
|
||||
import { CollapsibleTitle } from '@toeverything/components/common';
|
||||
import {
|
||||
useShowSpaceSidebar,
|
||||
useUserAndSpaces,
|
||||
} from '@toeverything/datasource/state';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
|
||||
import { WorkspaceName } from './workspace-name';
|
||||
import { CollapsiblePageTree } from './collapsible-page-tree';
|
||||
import TemplatesPortal from './templates-portal';
|
||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||
|
||||
type PageProps = {
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export function Page(props: PageProps) {
|
||||
const { page_id } = useParams();
|
||||
const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
|
||||
useShowSpaceSidebar();
|
||||
const { user } = useUserAndSpaces();
|
||||
const templatesPortalFlag = useFlag('BooleanTemplatesPortal', false);
|
||||
const dailyNotesFlag = useFlag('BooleanDailyNotes', false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user?.id || !page_id) return;
|
||||
const updateRecentPages = async () => {
|
||||
// TODO: deal with it temporarily
|
||||
await services.api.editorBlock.getWorkspaceDbBlock(
|
||||
props.workspace,
|
||||
{
|
||||
userId: user.id,
|
||||
}
|
||||
);
|
||||
|
||||
await services.api.userConfig.addRecentPage(
|
||||
props.workspace,
|
||||
user.id,
|
||||
page_id
|
||||
);
|
||||
await services.api.editorBlock.clearUndoRedo(props.workspace);
|
||||
};
|
||||
update_recent_pages();
|
||||
}, [user, props.workspace, page_id]);
|
||||
|
||||
return (
|
||||
<LigoApp>
|
||||
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>
|
||||
<WorkspaceSidebar
|
||||
style={{
|
||||
opacity: !showSpaceSidebar && !fixedDisplay ? 0 : 1,
|
||||
transform:
|
||||
!showSpaceSidebar && !fixedDisplay
|
||||
? 'translateX(-270px)'
|
||||
: 'translateX(0px)',
|
||||
}}
|
||||
onMouseEnter={() => setSpaceSidebarVisible(true)}
|
||||
onMouseLeave={() => setSpaceSidebarVisible(false)}
|
||||
>
|
||||
<WorkspaceName />
|
||||
<Divider light={true} sx={{ my: 1, margin: '6px 0px' }} />
|
||||
<WorkspaceSidebarContent>
|
||||
<div>
|
||||
{templatesPortalFlag && <TemplatesPortal />}
|
||||
{dailyNotesFlag && (
|
||||
<div>
|
||||
<CollapsibleTitle title="Daily Notes">
|
||||
<CalendarHeatmap />
|
||||
</CollapsibleTitle>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<CollapsibleTitle
|
||||
title="Activities"
|
||||
initialOpen={false}
|
||||
>
|
||||
<Activities></Activities>
|
||||
</CollapsibleTitle>
|
||||
</div>
|
||||
<div>
|
||||
<CollapsiblePageTree title="Page Tree">
|
||||
{page_id ? <PageTree /> : null}
|
||||
</CollapsiblePageTree>
|
||||
</div>
|
||||
</div>
|
||||
</WorkspaceSidebarContent>
|
||||
</WorkspaceSidebar>
|
||||
</LigoLeftContainer>
|
||||
<LigoRightContainer>
|
||||
<LigoEditorOuterContainer>
|
||||
{page_id ? (
|
||||
<AffineEditor
|
||||
workspace={props.workspace}
|
||||
rootBlockId={page_id}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)}
|
||||
</LigoEditorOuterContainer>
|
||||
</LigoRightContainer>
|
||||
</LigoApp>
|
||||
);
|
||||
}
|
||||
|
||||
const LigoApp = styled('div')({
|
||||
width: '100vw',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flex: '1 1 0%',
|
||||
backgroundColor: 'white',
|
||||
margin: '10px 0',
|
||||
});
|
||||
|
||||
const LigoRightContainer = styled('div')({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
flex: 'auto',
|
||||
});
|
||||
|
||||
const LigoEditorOuterContainer = styled('div')({
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden',
|
||||
});
|
||||
|
||||
const LigoLeftContainer = styled('div')({
|
||||
flex: '0 0 auto',
|
||||
});
|
||||
|
||||
const WorkspaceSidebar = styled('div')(({ hidden }) => ({
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: 300,
|
||||
minWidth: 300,
|
||||
height: '100%',
|
||||
borderRadius: '0px 10px 10px 0px',
|
||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
||||
backgroundColor: '#FFFFFF',
|
||||
transitionProperty: 'left',
|
||||
transitionDuration: '0.35s',
|
||||
transitionTimingFunction: 'ease',
|
||||
padding: '16px 12px',
|
||||
}));
|
||||
|
||||
const WorkspaceSidebarContent = styled('div')({
|
||||
flex: 'auto',
|
||||
overflow: 'hidden auto',
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
|
||||
const handle_search = () => {
|
||||
//@ts-ignore
|
||||
virgo.plugins.plugins['search'].renderSearch();
|
||||
};
|
||||
const QuickFindPortalContainer = styled('div')({
|
||||
position: 'relative',
|
||||
marginLeft: '10px',
|
||||
height: '22px',
|
||||
lineHeight: '22px',
|
||||
width: '220px',
|
||||
borderRadius: '8px',
|
||||
color: '#4c6275',
|
||||
fontSize: '14px',
|
||||
paddingLeft: '20px',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
backgroundColor: '#ccc',
|
||||
},
|
||||
'.shortcutIcon': {
|
||||
position: 'absolute',
|
||||
top: '3px',
|
||||
left: '0px',
|
||||
fontSize: '16px!important',
|
||||
},
|
||||
});
|
||||
|
||||
function QuickFindPortal() {
|
||||
return (
|
||||
<QuickFindPortalContainer onClick={handle_search}>
|
||||
<SearchIcon className="shortcutIcon" /> Quick Find
|
||||
</QuickFindPortalContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuickFindPortal;
|
||||
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
styled,
|
||||
MuiBox as Box,
|
||||
MuiModal as Modal,
|
||||
} from '@toeverything/components/ui';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Templates } from '../../templates';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { AsyncBlock } from '@toeverything/framework/virgo';
|
||||
import { createEditor } from '@toeverything/components/affine-editor';
|
||||
const TemplatePortalContainer = styled('div')({
|
||||
position: 'relative',
|
||||
marginLeft: '10px',
|
||||
height: '22px',
|
||||
lineHeight: '22px',
|
||||
width: '220px',
|
||||
borderRadius: '8px',
|
||||
color: '#4c6275',
|
||||
fontSize: '14px',
|
||||
paddingLeft: '20px',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
backgroundColor: '#ccc',
|
||||
},
|
||||
'.shortcutIcon': {
|
||||
position: 'absolute',
|
||||
top: '3px',
|
||||
left: '0px',
|
||||
fontSize: '16px!important',
|
||||
},
|
||||
});
|
||||
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
top: '40%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '80%',
|
||||
height: '70%',
|
||||
boxShadow: 0,
|
||||
p: 0,
|
||||
};
|
||||
const maskStyle = {
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
};
|
||||
function TemplatesPortal() {
|
||||
const [open, set_open] = React.useState(false);
|
||||
const handle_open = () => set_open(true);
|
||||
const handle_close = () => set_open(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const get_default_workspace_id = () => {
|
||||
return window.location.pathname.split('/')[1];
|
||||
};
|
||||
const handleClickUseThisTemplate = () => {
|
||||
const block_editor = createEditor(get_default_workspace_id());
|
||||
//@ts-ignore
|
||||
block_editor.plugins
|
||||
.getPlugin('page-toolbar')
|
||||
//@ts-ignore 泛型处理
|
||||
.addDailyNote()
|
||||
.then((new_page: AsyncBlock) => {
|
||||
handle_close();
|
||||
const new_state =
|
||||
`/${get_default_workspace_id()}/` + new_page.id;
|
||||
navigate(new_state);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TemplatePortalContainer onClick={handle_open}>
|
||||
<StarIcon className="shortcutIcon" /> Templates
|
||||
</TemplatePortalContainer>
|
||||
<Modal open={open} onClose={handle_close}>
|
||||
<Box sx={style}>
|
||||
<Templates
|
||||
handleClickUseThisTemplate={handleClickUseThisTemplate}
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TemplatesPortal;
|
||||
168
apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx
Normal file
168
apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
MuiButton as Button,
|
||||
MuiSwitch as Switch,
|
||||
styled,
|
||||
MuiOutlinedInput as OutlinedInput,
|
||||
} from '@toeverything/components/ui';
|
||||
import { LogoIcon } from '@toeverything/components/icons';
|
||||
import {
|
||||
useUserAndSpaces,
|
||||
useShowSpaceSidebar,
|
||||
} from '@toeverything/datasource/state';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
|
||||
const WorkspaceContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
minHeight: 60,
|
||||
padding: '12px 0px',
|
||||
color: '#566B7D',
|
||||
});
|
||||
const LeftContainer = styled('div')({
|
||||
flex: 'auto',
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
const LogoContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 24,
|
||||
minWidth: 24,
|
||||
});
|
||||
|
||||
const StyledLogoIcon = styled(LogoIcon)(({ theme }) => {
|
||||
return {
|
||||
color: theme.affine.palette.primary,
|
||||
width: '16px !important',
|
||||
height: '16px !important',
|
||||
};
|
||||
});
|
||||
|
||||
const WorkspaceNameContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: 'auto',
|
||||
width: '100px',
|
||||
marginRight: '10px',
|
||||
input: {
|
||||
padding: '5px 10px',
|
||||
},
|
||||
span: {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
});
|
||||
|
||||
const WorkspaceReNameContainer = styled('div')({
|
||||
marginRight: '10px',
|
||||
input: {
|
||||
padding: '5px 10px',
|
||||
},
|
||||
});
|
||||
|
||||
const ToggleDisplayContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: 12,
|
||||
color: '#3E6FDB',
|
||||
padding: 6,
|
||||
minWidth: 64,
|
||||
});
|
||||
|
||||
export const WorkspaceName = () => {
|
||||
const { currentSpaceId } = useUserAndSpaces();
|
||||
const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar();
|
||||
const [inRename, setInRename] = useState(false);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
|
||||
const fetchWorkspaceName = useCallback(async () => {
|
||||
if (!currentSpaceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = await services.api.userConfig.getWorkspaceName(
|
||||
currentSpaceId
|
||||
);
|
||||
setWorkspaceName(name);
|
||||
}, [currentSpaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchWorkspaceName();
|
||||
}, [currentSpaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
let unobserve: () => void;
|
||||
const observe = async () => {
|
||||
unobserve = await services.api.userConfig.observe(
|
||||
{ workspace: currentSpaceId },
|
||||
() => {
|
||||
fetchWorkspaceName();
|
||||
}
|
||||
);
|
||||
};
|
||||
observe();
|
||||
|
||||
return () => {
|
||||
unobserve?.();
|
||||
};
|
||||
}, [currentSpaceId, fetchWorkspaceName]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setInRename(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
const handleChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
services.api.userConfig.setWorkspaceName(
|
||||
currentSpaceId,
|
||||
e.currentTarget.value
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<WorkspaceContainer>
|
||||
<LeftContainer>
|
||||
<LogoContainer>
|
||||
<StyledLogoIcon />
|
||||
</LogoContainer>
|
||||
|
||||
{inRename ? (
|
||||
<WorkspaceReNameContainer>
|
||||
<OutlinedInput
|
||||
value={workspaceName}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onMouseLeave={() => setInRename(false)}
|
||||
/>
|
||||
</WorkspaceReNameContainer>
|
||||
) : (
|
||||
<WorkspaceNameContainer>
|
||||
<span onClick={() => setInRename(true)}>
|
||||
{workspaceName}
|
||||
</span>
|
||||
</WorkspaceNameContainer>
|
||||
)}
|
||||
</LeftContainer>
|
||||
<ToggleDisplayContainer>
|
||||
<span>{fixedDisplay ? 'ON' : 'OFF'}</span>
|
||||
<Switch
|
||||
checked={fixedDisplay}
|
||||
onChange={toggleSpaceSidebar}
|
||||
size="small"
|
||||
/>
|
||||
</ToggleDisplayContainer>
|
||||
</WorkspaceContainer>
|
||||
);
|
||||
};
|
||||
41
apps/ligo-virgo/src/pages/workspace/index.tsx
Normal file
41
apps/ligo-virgo/src/pages/workspace/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
/* eslint-disable filename-rules/match */
|
||||
import { Routes, Route, useParams, Navigate } from 'react-router';
|
||||
|
||||
import { useUserAndSpaces } from '@toeverything/datasource/state';
|
||||
|
||||
import { WorkspaceRootContainer } from './Container';
|
||||
import { Page } from './docs';
|
||||
import { WorkspaceHome } from './Home';
|
||||
import Labels from './labels';
|
||||
import Pages from './pages';
|
||||
import { Whiteboard } from './Whiteboard';
|
||||
|
||||
export function WorkspaceContainer() {
|
||||
const { workspace_id } = useParams();
|
||||
const { user, currentSpaceId } = useUserAndSpaces();
|
||||
|
||||
if (
|
||||
user &&
|
||||
![currentSpaceId, 'affine2vin277tcmafwq'].includes(workspace_id)
|
||||
) {
|
||||
// return <Navigate to={`/${currentSpaceId}`} replace={true} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<WorkspaceRootContainer />}>
|
||||
<Route path="/labels" element={<Labels />} />
|
||||
<Route path="/pages" element={<Pages />} />
|
||||
<Route
|
||||
path="/:page_id/whiteboard"
|
||||
element={<Whiteboard workspace={workspace_id} />}
|
||||
/>
|
||||
<Route
|
||||
path="/:page_id"
|
||||
element={<Page workspace={workspace_id} />}
|
||||
/>
|
||||
<Route path="/" element={<WorkspaceHome />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/workspace/labels/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/workspace/labels/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function WorkspaceLabels() {
|
||||
return <span>WorkspaceLabels</span>;
|
||||
}
|
||||
3
apps/ligo-virgo/src/pages/workspace/pages/index.tsx
Normal file
3
apps/ligo-virgo/src/pages/workspace/pages/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function WorkspacePages() {
|
||||
return <span>WorkspacePages</span>;
|
||||
}
|
||||
53
apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx
Normal file
53
apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// import { FC, useMemo, useState } from 'react';
|
||||
// import { useParams } from 'react-router-dom';
|
||||
// import { TDPage } from '@toeverything/framework/whiteboard';
|
||||
// import {
|
||||
// AffineWhiteboard,
|
||||
// WhiteboardMeta,
|
||||
// AffineEditorShape
|
||||
// } from '@toeverything/components/affine-whiteboard';
|
||||
|
||||
// interface EditorShapeProps {
|
||||
// blockIds: string | string[];
|
||||
// point: [number, number];
|
||||
// }
|
||||
|
||||
// const createEditorShape = (props: EditorShapeProps): AffineEditorShape => {
|
||||
// const block_ids = Array.isArray(props.blockIds)
|
||||
// ? props.blockIds
|
||||
// : [props.blockIds];
|
||||
// return {
|
||||
// id: block_ids.join('_'),
|
||||
// label: '',
|
||||
// childIndex: 1,
|
||||
// name: 'affine_editor',
|
||||
// parentId: 'page',
|
||||
// point: props.point,
|
||||
// rotation: 0,
|
||||
// size: [400, 200],
|
||||
// style: {
|
||||
// color: 'black',
|
||||
// size: 'small',
|
||||
// isFilled: false,
|
||||
// dash: 'draw',
|
||||
// scale: 1
|
||||
// } as any,
|
||||
// type: 'affineEditor',
|
||||
// blockIds: block_ids
|
||||
// };
|
||||
// };
|
||||
|
||||
// const Whiteboard: FC = () => {
|
||||
// const { workspace_id, page_id } = useParams();
|
||||
// const [shapes, set_shapes] = useState<TDPage['shapes']>({});
|
||||
// const meta = useMemo<WhiteboardMeta>(() => {
|
||||
// return {
|
||||
// workspace: workspace_id,
|
||||
// rootBlockId: page_id
|
||||
// };
|
||||
// }, [workspace_id, page_id]);
|
||||
|
||||
// return page_id ? <AffineWhiteboard meta={meta} shapes={shapes} /> : null;
|
||||
// };
|
||||
|
||||
// export default Whiteboard;
|
||||
7
apps/ligo-virgo/src/polyfills.ts
Normal file
7
apps/ligo-virgo/src/polyfills.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
|
||||
*
|
||||
* See: https://github.com/zloirock/core-js#babel
|
||||
*/
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
285
apps/ligo-virgo/src/styles.css
Normal file
285
apps/ligo-virgo/src/styles.css
Normal file
@@ -0,0 +1,285 @@
|
||||
/* resset.dev • v5.0.2 */
|
||||
|
||||
/* # =================================================================
|
||||
# Global selectors
|
||||
# ================================================================= */
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
-webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS */
|
||||
word-break: normal;
|
||||
-moz-tab-size: 4;
|
||||
tab-size: 4;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma,
|
||||
PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif,
|
||||
Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
}
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
background-repeat: no-repeat; /* Set `background-repeat: no-repeat` to all elements and pseudo elements */
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
::before,
|
||||
::after {
|
||||
text-decoration: inherit; /* Inherit text-decoration and vertical align to ::before and ::after pseudo elements */
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
* {
|
||||
padding: 0; /* Reset `padding` and `margin` of all elements */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* # =================================================================
|
||||
# General elements
|
||||
# ================================================================= */
|
||||
|
||||
hr {
|
||||
overflow: visible; /* Show the overflow in Edge and IE */
|
||||
height: 0; /* Add the correct box sizing in Firefox */
|
||||
color: inherit; /* Correct border color in Firefox. */
|
||||
}
|
||||
|
||||
details,
|
||||
main {
|
||||
display: block; /* Render the `main` element consistently in IE. */
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item; /* Add the correct display in all browsers */
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%; /* Set font-size to 80% in `small` elements */
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none; /* Add the correct display in IE */
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* Remove the bottom border in Chrome 57 */
|
||||
/* Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari */
|
||||
text-decoration: underline;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent; /* Remove the gray background on active links in IE 10 */
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0; /* Remove the outline when hovering in all browsers */
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* Specify the font family of code elements */
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 1em; /* Correct the odd `em` font sizing in all browsers */
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder; /* Add the correct font weight in Chrome, Edge, and Safari */
|
||||
}
|
||||
|
||||
/* https://gist.github.com/unruthless/413930 */
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
table {
|
||||
border-color: inherit; /* Correct border color in all Chrome, Edge, and Safari. */
|
||||
text-indent: 0; /* Remove text indentation in Chrome, Edge, and Safari */
|
||||
}
|
||||
|
||||
iframe {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* # =================================================================
|
||||
# Forms
|
||||
# ================================================================= */
|
||||
|
||||
input {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[type='number']::-webkit-inner-spin-button,
|
||||
[type='number']::-webkit-outer-spin-button {
|
||||
height: auto; /* Correct the cursor style of increment and decrement buttons in Chrome */
|
||||
}
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield; /* Correct the odd appearance in Chrome and Safari */
|
||||
outline-offset: -2px; /* Correct the outline style in Safari */
|
||||
}
|
||||
|
||||
[type='search']::-webkit-search-decoration {
|
||||
-webkit-appearance: none; /* Remove the inner padding in Chrome and Safari on macOS */
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* Internet Explorer 11+ */
|
||||
resize: vertical; /* Specify textarea resizability */
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit; /* Specify font inheritance of form elements */
|
||||
}
|
||||
|
||||
optgroup {
|
||||
font-weight: bold; /* Restore the font weight unset by the previous rule */
|
||||
}
|
||||
|
||||
button {
|
||||
overflow: visible; /* Address `overflow` set to `hidden` in IE 8/9/10/11 */
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none; /* Firefox 40+, Internet Explorer 11- */
|
||||
}
|
||||
|
||||
/* Apply cursor pointer to button elements */
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'],
|
||||
[role='button'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Remove inner padding and border in Firefox 4+ */
|
||||
button::-moz-focus-inner,
|
||||
[type='button']::-moz-focus-inner,
|
||||
[type='reset']::-moz-focus-inner,
|
||||
[type='submit']::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Replace focus style removed in the border reset above */
|
||||
button:-moz-focusring,
|
||||
[type='button']::-moz-focus-inner,
|
||||
[type='reset']::-moz-focus-inner,
|
||||
[type='submit']::-moz-focus-inner {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
button,
|
||||
html [type='button'], /* Prevent a WebKit bug where (2) destroys native `audio` and `video`controls in Android 4 */
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button; /* Correct the inability to style clickable types in iOS */
|
||||
}
|
||||
|
||||
/* Remove the default button styling in all browsers */
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
background-color: transparent;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
button:focus,
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
/* Style select like a standard input */
|
||||
select {
|
||||
-moz-appearance: none; /* Firefox 36+ */
|
||||
-webkit-appearance: none; /* Chrome 41+ */
|
||||
}
|
||||
|
||||
select::-ms-expand {
|
||||
display: none; /* Internet Explorer 11+ */
|
||||
}
|
||||
|
||||
select::-ms-value {
|
||||
color: currentColor; /* Internet Explorer 11+ */
|
||||
}
|
||||
|
||||
legend {
|
||||
border: 0; /* Correct `color` not being inherited in IE 8/9/10/11 */
|
||||
color: inherit; /* Correct the color inheritance from `fieldset` elements in IE */
|
||||
display: table; /* Correct the text wrapping in Edge and IE */
|
||||
max-width: 100%; /* Correct the text wrapping in Edge and IE */
|
||||
white-space: normal; /* Correct the text wrapping in Edge and IE */
|
||||
max-width: 100%; /* Correct the text wrapping in Edge 18- and IE */
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
/* Correct the inability to style clickable types in iOS and Safari */
|
||||
-webkit-appearance: button;
|
||||
color: inherit;
|
||||
font: inherit; /* Change font properties to `inherit` in Chrome and Safari */
|
||||
}
|
||||
|
||||
/* Replace pointer cursor in disabled elements */
|
||||
[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* # =================================================================
|
||||
# Specify media element style
|
||||
# ================================================================= */
|
||||
|
||||
img {
|
||||
border-style: none; /* Remove border when inside `a` element in IE 8/9/10 */
|
||||
}
|
||||
|
||||
/* Add the correct vertical alignment in Chrome, Firefox, and Opera */
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* # =================================================================
|
||||
# Accessibility
|
||||
# ================================================================= */
|
||||
|
||||
/* Specify the progress cursor of updating elements */
|
||||
[aria-busy='true'] {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
/* Specify the pointer cursor of trigger elements */
|
||||
[aria-controls] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Specify the unstyled cursor of disabled, not-editable, or otherwise inoperable elements */
|
||||
[aria-disabled='true'] {
|
||||
cursor: default;
|
||||
}
|
||||
11
apps/ligo-virgo/src/template.html
Normal file
11
apps/ligo-virgo/src/template.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
22
apps/ligo-virgo/tsconfig.app.json
Normal file
22
apps/ligo-virgo/tsconfig.app.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
25
apps/ligo-virgo/tsconfig.json
Normal file
25
apps/ligo-virgo/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
// "noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": false
|
||||
// "strictNullChecks": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
apps/ligo-virgo/tsconfig.spec.json
Normal file
23
apps/ligo-virgo/tsconfig.spec.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts"
|
||||
],
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
]
|
||||
}
|
||||
197
apps/ligo-virgo/webpack.config.js
Normal file
197
apps/ligo-virgo/webpack.config.js
Normal file
@@ -0,0 +1,197 @@
|
||||
const path = require('path');
|
||||
const zlib = require('zlib');
|
||||
const webpack = require('webpack');
|
||||
const getNxWebpackConfig = require('@nrwl/react/plugins/webpack');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const Style9Plugin = require('style9/webpack');
|
||||
|
||||
const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER;
|
||||
|
||||
module.exports = function (webpackConfig) {
|
||||
const config = getNxWebpackConfig(webpackConfig);
|
||||
|
||||
const isProd = config.mode === 'production';
|
||||
|
||||
const style9 = {
|
||||
test: /\.(tsx|ts|js|mjs|jsx)$/,
|
||||
use: [
|
||||
{
|
||||
loader: Style9Plugin.loader,
|
||||
options: {
|
||||
minifyProperties: isProd,
|
||||
incrementalClassnames: isProd,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
config.experiments.topLevelAwait = true;
|
||||
|
||||
if (isProd) {
|
||||
config.module.rules.unshift(style9);
|
||||
} else {
|
||||
config.module.rules.push(style9);
|
||||
}
|
||||
|
||||
if (isProd) {
|
||||
config.entry = {
|
||||
main: [...config.entry.main, ...config.entry.polyfills],
|
||||
};
|
||||
config.devtool = false;
|
||||
config.output = {
|
||||
...config.output,
|
||||
filename: '[name].[contenthash:8].js',
|
||||
chunkFilename: '[name].[chunkhash:8].js',
|
||||
hashFunction: undefined,
|
||||
};
|
||||
config.optimization = {
|
||||
nodeEnv: 'production',
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
ecma: 2020,
|
||||
},
|
||||
extractComments: true,
|
||||
parallel: true,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
styles: {
|
||||
name: 'styles',
|
||||
type: 'css/mini-extract',
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
},
|
||||
auth: {
|
||||
test: /[\\/]node_modules[\\/](@authing|@?firebase)/,
|
||||
name: 'auth',
|
||||
priority: -5,
|
||||
chunks: 'all',
|
||||
},
|
||||
whiteboard: {
|
||||
test: /(libs\/components\/board-|[\\/]node_modules[\\/]@tldraw)/,
|
||||
name: 'whiteboard',
|
||||
priority: -7,
|
||||
chunks: 'all',
|
||||
},
|
||||
editor: {
|
||||
test: /(libs\/framework\/(ligo|virgo|editor)|[\\/]node_modules[\\/](@codemirror|@lezer|slate))/,
|
||||
name: 'editor',
|
||||
priority: -8,
|
||||
chunks: 'all',
|
||||
},
|
||||
ui: {
|
||||
test: /[\\/]node_modules[\\/](@mui|@emotion|react|katex)/,
|
||||
name: 'ui',
|
||||
priority: -9,
|
||||
chunks: 'all',
|
||||
},
|
||||
vender: {
|
||||
test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
|
||||
name: 'vender',
|
||||
priority: -10,
|
||||
chunks: 'all',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
config.module.rules.unshift({
|
||||
test: /\.css$/i,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
config.module.rules.unshift({
|
||||
test: /\.scss$/i,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
},
|
||||
],
|
||||
});
|
||||
config.module.rules.splice(6);
|
||||
} else {
|
||||
config.output = {
|
||||
...config.output,
|
||||
publicPath: '/',
|
||||
};
|
||||
|
||||
const babelLoader = config.module.rules.find(
|
||||
rule =>
|
||||
typeof rule !== 'string' &&
|
||||
rule.loader?.toString().includes('babel-loader')
|
||||
);
|
||||
if (babelLoader && typeof babelLoader !== 'string') {
|
||||
babelLoader.options['plugins'] = [
|
||||
...(babelLoader.options['plugins'] || []),
|
||||
[require.resolve('babel-plugin-open-source')],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
config.plugins = [
|
||||
...config.plugins.filter(
|
||||
p => !(isProd && p instanceof MiniCssExtractPlugin)
|
||||
),
|
||||
new webpack.DefinePlugin({
|
||||
JWT_DEV: !isProd,
|
||||
global: {},
|
||||
}),
|
||||
isProd &&
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'Affine - All In One Workos',
|
||||
favicon: path.resolve(
|
||||
__dirname,
|
||||
'./src/assets/images/favicon.ico'
|
||||
), //favicon path
|
||||
template: path.resolve(__dirname, './src/template.html'),
|
||||
publicPath: '/',
|
||||
}),
|
||||
new Style9Plugin(),
|
||||
isProd && new MiniCssExtractPlugin(),
|
||||
isProd &&
|
||||
new CompressionPlugin({
|
||||
test: /\.(js|css|html|svg|ttf|woff)$/,
|
||||
algorithm: 'brotliCompress',
|
||||
filename: '[path][base].br',
|
||||
compressionOptions: {
|
||||
params: {
|
||||
[zlib.constants.BROTLI_PARAM_QUALITY]: 11,
|
||||
},
|
||||
},
|
||||
}),
|
||||
isProd &&
|
||||
enableBundleAnalyzer &&
|
||||
new BundleAnalyzerPlugin({ analyzerMode: 'static' }),
|
||||
].filter(Boolean);
|
||||
|
||||
// Workaround for webpack infinite recompile errors
|
||||
config.watchOptions = {
|
||||
// followSymlinks: false,
|
||||
ignored: ['**/*.css'],
|
||||
};
|
||||
|
||||
return config;
|
||||
};
|
||||
Reference in New Issue
Block a user