mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 03:48:39 +00:00
Compare commits
36 Commits
v0.7.0-can
...
v0.7.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b1aff1db1 | ||
|
|
3a64b43032 | ||
|
|
59f53760d1 | ||
|
|
2980c1afac | ||
|
|
39054a7c3d | ||
|
|
4b7e47e265 | ||
|
|
4e7824583d | ||
|
|
ba53c74130 | ||
|
|
bc263e7afb | ||
|
|
bc27412425 | ||
|
|
fa8086d525 | ||
|
|
04534c2008 | ||
|
|
780fffb88f | ||
|
|
1e72d3c270 | ||
|
|
1e38d36161 | ||
|
|
bb9908e1fa | ||
|
|
6bafa83cef | ||
|
|
2c249781a2 | ||
|
|
8334ac031b | ||
|
|
635ca081e4 | ||
|
|
10f879f29a | ||
|
|
521e505a01 | ||
|
|
f968587f6f | ||
|
|
e70f8e74ec | ||
|
|
32fd01ed33 | ||
|
|
00718f8c9a | ||
|
|
20ee9d485d | ||
|
|
e3f66d7e22 | ||
|
|
be81e63eed | ||
|
|
2cf4e8ebce | ||
|
|
e6e98975ed | ||
|
|
ccb0df10e4 | ||
|
|
dd31d1e8c6 | ||
|
|
a494bad543 | ||
|
|
363699a175 | ||
|
|
439ef1ba90 |
4
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
4
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
@@ -21,8 +21,8 @@ body:
|
||||
label: Distribution version
|
||||
description: What version of AFFiNE are you using?
|
||||
options:
|
||||
- macOS x64
|
||||
- macOS ARM 64
|
||||
- macOS x64 (Intel)
|
||||
- macOS ARM 64 (Apple Silicon)
|
||||
- Windows x64
|
||||
- Linux
|
||||
- Web (app.affine.pro)
|
||||
|
||||
3
.github/workflows/nightly-build.yml
vendored
3
.github/workflows/nightly-build.yml
vendored
@@ -120,7 +120,7 @@ jobs:
|
||||
run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }}
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: before-make-web-static
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
@@ -159,6 +159,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv apps/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
1
.github/workflows/release-desktop-app.yml
vendored
1
.github/workflows/release-desktop-app.yml
vendored
@@ -159,6 +159,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv apps/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -106,11 +106,12 @@ export const createConfiguration: (
|
||||
devtool:
|
||||
buildFlags.mode === 'production'
|
||||
? buildFlags.distribution === 'desktop'
|
||||
? 'inline-cheap-source-map'
|
||||
? 'nosources-source-map'
|
||||
: 'source-map'
|
||||
: 'eval-cheap-module-source-map',
|
||||
|
||||
resolve: {
|
||||
symlinks: true,
|
||||
extensionAlias: {
|
||||
'.js': ['.js', '.tsx', '.ts'],
|
||||
'.mjs': ['.mjs', '.mts'],
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
rel="shortcut icon"
|
||||
href="https://affine.pro/favicon.ico"
|
||||
/>
|
||||
<link rel="stylesheet" href="/plugins/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.51",
|
||||
"version": "0.7.0-canary.55",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
@@ -19,20 +19,20 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/icons": "^2.1.26",
|
||||
"@blocksuite/lit": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.1",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
@@ -49,7 +49,7 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-is": "18.2.0",
|
||||
"react-resizable-panels": "^0.0.53",
|
||||
"react-resizable-panels": "^0.0.54",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"ses": "^0.18.5",
|
||||
@@ -61,9 +61,9 @@
|
||||
"devDependencies": {
|
||||
"@perfsee/webpack": "^1.8.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@sentry/webpack-plugin": "^2.4.0",
|
||||
"@sentry/webpack-plugin": "^2.5.0",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
"@swc/core": "^1.3.70",
|
||||
"@swc/core": "^1.3.71",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
|
||||
@@ -5,117 +5,13 @@ import { AffineContext } from '@affine/component/context';
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { createI18n, setUpLanguage } from '@affine/i18n';
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import type { RouterState } from '@remix-run/router';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, memo, Suspense, useEffect } from 'react';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import { historyBaseAtom, MAX_HISTORY } from './atoms/history';
|
||||
import { router } from './router';
|
||||
import createEmotionCache from './utils/create-emotion-cache';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
lazy: () => import('./pages/index'),
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
{
|
||||
path: '/workspace/:workspaceId/all',
|
||||
lazy: () => import('./pages/workspace/all-page'),
|
||||
},
|
||||
{
|
||||
path: '/workspace/:workspaceId/trash',
|
||||
lazy: () => import('./pages/workspace/trash-page'),
|
||||
},
|
||||
{
|
||||
path: '/workspace/:workspaceId/:pageId',
|
||||
lazy: () => import('./pages/workspace/detail-page'),
|
||||
},
|
||||
]);
|
||||
|
||||
//#region atoms bootstrap
|
||||
|
||||
currentWorkspaceIdAtom.onMount = set => {
|
||||
const callback = (state: RouterState) => {
|
||||
const value = state.location.pathname.split('/')[2];
|
||||
if (value) {
|
||||
set(value);
|
||||
localStorage.setItem('last_workspace_id', value);
|
||||
}
|
||||
};
|
||||
callback(router.state);
|
||||
|
||||
const unsubscribe = router.subscribe(callback);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
};
|
||||
|
||||
currentPageIdAtom.onMount = set => {
|
||||
const callback = (state: RouterState) => {
|
||||
const value = state.location.pathname.split('/')[3];
|
||||
if (value) {
|
||||
set(value);
|
||||
}
|
||||
};
|
||||
callback(router.state);
|
||||
|
||||
const unsubscribe = router.subscribe(callback);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
};
|
||||
|
||||
historyBaseAtom.onMount = set => {
|
||||
const unsubscribe = router.subscribe(state => {
|
||||
set(prev => {
|
||||
const url = state.location.pathname;
|
||||
console.log('push', url, prev.skip, prev.stack.length, prev.current);
|
||||
if (prev.skip) {
|
||||
return {
|
||||
stack: [...prev.stack],
|
||||
current: prev.current,
|
||||
skip: false,
|
||||
};
|
||||
} else {
|
||||
if (prev.current < prev.stack.length - 1) {
|
||||
const newStack = prev.stack.slice(0, prev.current);
|
||||
newStack.push(url);
|
||||
if (newStack.length > MAX_HISTORY) {
|
||||
newStack.shift();
|
||||
}
|
||||
return {
|
||||
stack: newStack,
|
||||
current: newStack.length - 1,
|
||||
skip: false,
|
||||
};
|
||||
} else {
|
||||
const newStack = [...prev.stack, url];
|
||||
if (newStack.length > MAX_HISTORY) {
|
||||
newStack.shift();
|
||||
}
|
||||
return {
|
||||
stack: newStack,
|
||||
current: newStack.length - 1,
|
||||
skip: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
};
|
||||
//#endregion
|
||||
|
||||
const i18n = createI18n();
|
||||
const cache = createEmotionCache();
|
||||
|
||||
@@ -132,6 +28,10 @@ const DebugProvider = ({ children }: PropsWithChildren): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
const future = {
|
||||
v7_startTransition: true,
|
||||
} as const;
|
||||
|
||||
export const App = memo(function App() {
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = i18n.language;
|
||||
@@ -144,9 +44,11 @@ export const App = memo(function App() {
|
||||
<CacheProvider value={cache}>
|
||||
<AffineContext>
|
||||
<DebugProvider>
|
||||
<Suspense fallback={<WorkspaceFallback key="RootPageLoading" />}>
|
||||
<RouterProvider router={router} />
|
||||
</Suspense>
|
||||
<RouterProvider
|
||||
fallbackElement={<WorkspaceFallback key="RouterFallback" />}
|
||||
router={router}
|
||||
future={future}
|
||||
/>
|
||||
</DebugProvider>
|
||||
</AffineContext>
|
||||
</CacheProvider>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { router } from '../router';
|
||||
|
||||
export type History = {
|
||||
stack: string[];
|
||||
current: number;
|
||||
@@ -11,11 +13,62 @@ export type History = {
|
||||
|
||||
export const MAX_HISTORY = 50;
|
||||
|
||||
export const historyBaseAtom = atomWithStorage<History>('router-history', {
|
||||
stack: [],
|
||||
current: 0,
|
||||
skip: false,
|
||||
});
|
||||
const historyBaseAtom = atomWithStorage<History>(
|
||||
'router-history',
|
||||
{
|
||||
stack: [],
|
||||
current: 0,
|
||||
skip: false,
|
||||
},
|
||||
createJSONStorage(() => sessionStorage)
|
||||
);
|
||||
|
||||
historyBaseAtom.onMount = set => {
|
||||
const unsubscribe = router.subscribe(state => {
|
||||
set(prev => {
|
||||
const url = state.location.pathname;
|
||||
|
||||
// if stack top is the same as current, skip
|
||||
if (prev.stack[prev.current] === url) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (prev.skip) {
|
||||
return {
|
||||
stack: [...prev.stack],
|
||||
current: prev.current,
|
||||
skip: false,
|
||||
};
|
||||
} else {
|
||||
if (prev.current < prev.stack.length - 1) {
|
||||
const newStack = prev.stack.slice(0, prev.current);
|
||||
newStack.push(url);
|
||||
if (newStack.length > MAX_HISTORY) {
|
||||
newStack.shift();
|
||||
}
|
||||
return {
|
||||
stack: newStack,
|
||||
current: newStack.length - 1,
|
||||
skip: false,
|
||||
};
|
||||
} else {
|
||||
const newStack = [...prev.stack, url];
|
||||
if (newStack.length > MAX_HISTORY) {
|
||||
newStack.shift();
|
||||
}
|
||||
return {
|
||||
stack: newStack,
|
||||
current: newStack.length - 1,
|
||||
skip: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
};
|
||||
|
||||
export function useHistoryAtom() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { atom } from 'jotai/vanilla';
|
||||
|
||||
import { pageSettingFamily } from './index';
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { rootStore } from '@toeverything/plugin-infra/manager';
|
||||
import { rootStore } from '@toeverything/plugin-infra/atom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
|
||||
@@ -6,18 +6,18 @@ import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
|
||||
import * as Icons from '@blocksuite/icons';
|
||||
import type {
|
||||
CallbackMap,
|
||||
PluginContext,
|
||||
} from '@toeverything/plugin-infra/entry';
|
||||
import * as Manager from '@toeverything/plugin-infra/manager';
|
||||
import * as Atom from '@toeverything/plugin-infra/atom';
|
||||
import {
|
||||
editorItemsAtom,
|
||||
headerItemsAtom,
|
||||
registeredPluginAtom,
|
||||
rootStore,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import type {
|
||||
CallbackMap,
|
||||
PluginContext,
|
||||
} from '@toeverything/plugin-infra/entry';
|
||||
import * as Jotai from 'jotai';
|
||||
import { Provider } from 'jotai/react';
|
||||
import * as JotaiUtils from 'jotai/utils';
|
||||
@@ -48,8 +48,8 @@ const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
);
|
||||
|
||||
const customRequire = (id: string) => {
|
||||
if (id === '@toeverything/plugin-infra/manager') {
|
||||
return Manager;
|
||||
if (id === '@toeverything/plugin-infra/atom') {
|
||||
return Atom;
|
||||
}
|
||||
if (id === 'react') {
|
||||
return React;
|
||||
@@ -81,9 +81,6 @@ const customRequire = (id: string) => {
|
||||
if (id === 'jotai/utils') {
|
||||
return JotaiUtils;
|
||||
}
|
||||
if (id === '../plugin.js') {
|
||||
return entryCompartment.evaluate('exports');
|
||||
}
|
||||
throw new Error(`Cannot find module '${id}'`);
|
||||
};
|
||||
|
||||
@@ -99,6 +96,8 @@ const createGlobalThis = () => {
|
||||
document,
|
||||
navigator,
|
||||
userAgent: navigator.userAgent,
|
||||
// todo: permission control
|
||||
fetch: globalThis.fetch,
|
||||
|
||||
// fixme: use our own db api
|
||||
indexedDB: globalThis.indexedDB,
|
||||
@@ -121,35 +120,46 @@ const createGlobalThis = () => {
|
||||
};
|
||||
|
||||
const group = new DisposableGroup();
|
||||
const pluginList = await (
|
||||
const pluginList = (await (
|
||||
await fetch(new URL(`./plugins/plugin-list.json`, window.location.origin))
|
||||
).json();
|
||||
const builtInPlugins: string[] = pluginList.map((plugin: any) => plugin.name);
|
||||
const pluginGlobalThis = createGlobalThis();
|
||||
const pluginEntry = await fetch('/plugins/plugin.js').then(res => res.text());
|
||||
const entryCompartment = new Compartment(pluginGlobalThis, {});
|
||||
entryCompartment.evaluate(pluginEntry, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
).json()) as { name: string; assets: string[]; release: boolean }[];
|
||||
|
||||
await Promise.all(
|
||||
builtInPlugins.map(plugin => {
|
||||
pluginList.map(({ name: plugin, release, assets }) => {
|
||||
if (!release && process.env.NODE_ENV !== 'development') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const pluginCompartment = new Compartment(createGlobalThis(), {});
|
||||
const pluginGlobalThis = pluginCompartment.globalThis;
|
||||
const baseURL = new URL(`./plugins/${plugin}/`, window.location.origin);
|
||||
const packageJsonURL = new URL('package.json', baseURL);
|
||||
return fetch(packageJsonURL).then(async res => {
|
||||
const packageJson = await res.json();
|
||||
const pluginConfig = packageJson['affinePlugin'];
|
||||
if (
|
||||
pluginConfig.release === false &&
|
||||
process.env.NODE_ENV !== 'development'
|
||||
) {
|
||||
return;
|
||||
const entryURL = new URL('index.js', baseURL);
|
||||
rootStore.set(registeredPluginAtom, prev => [...prev, plugin]);
|
||||
return fetch(entryURL).then(async res => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async asset => {
|
||||
if (asset.endsWith('.css')) {
|
||||
const res = await fetch(new URL(asset, baseURL));
|
||||
if (res.ok) {
|
||||
// todo: how to put css file into sandbox?
|
||||
return res.text().then(text => {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('plugin-id', plugin);
|
||||
style.textContent = text;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
rootStore.set(registeredPluginAtom, prev => [...prev, plugin]);
|
||||
const coreEntry = new URL(pluginConfig.entry.core, baseURL.toString());
|
||||
const codeText = await fetch(coreEntry).then(res => res.text());
|
||||
pluginCompartment.evaluate(codeText);
|
||||
const codeText = await res.text();
|
||||
pluginCompartment.evaluate(codeText, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
pluginGlobalThis.__INTERNAL__ENTRY = {
|
||||
register: (part, callback) => {
|
||||
if (part === 'headerItem') {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
|
||||
|
||||
@@ -79,7 +79,7 @@ const NameWorkspaceContent = ({
|
||||
<div className={style.buttonGroup}>
|
||||
<Button
|
||||
data-testid="create-workspace-close-button"
|
||||
type="light"
|
||||
type="primary"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t.Cancel()}
|
||||
@@ -155,7 +155,7 @@ const SetDBLocationContent = ({
|
||||
<Button
|
||||
disabled={opening}
|
||||
data-testid="create-workspace-customize-button"
|
||||
type="light"
|
||||
type="primary"
|
||||
onClick={handleSelectDBFileLocation}
|
||||
>
|
||||
{t['Customize']()}
|
||||
|
||||
@@ -47,7 +47,7 @@ const LanguageMenuContent: FC<{
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu: FC<{ triggerProps: ButtonProps }> = ({
|
||||
export const LanguageMenu: FC<{ triggerProps?: ButtonProps }> = ({
|
||||
triggerProps,
|
||||
}) => {
|
||||
const i18n = useI18N();
|
||||
|
||||
@@ -99,7 +99,7 @@ export const WorkspaceDeleteModal = ({
|
||||
data-testid="delete-workspace-confirm-button"
|
||||
disabled={!allowDelete}
|
||||
onClick={handleDelete}
|
||||
type="danger"
|
||||
type="error"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
|
||||
@@ -37,7 +37,7 @@ export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleLeave}
|
||||
type="danger"
|
||||
type="error"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
|
||||
@@ -1,31 +1,51 @@
|
||||
import { Button, toast } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { FC } from 'react';
|
||||
import { type FC, useCallback } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
|
||||
async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) {
|
||||
if (window.apis && isDesktop) {
|
||||
const bs = workspace.blockSuiteWorkspace.blobs;
|
||||
const blobsInDb = await window.apis.db.getBlobKeys(workspace.id);
|
||||
const blobsInStorage = await bs.list();
|
||||
const blobsToSync = blobsInStorage.filter(
|
||||
blob => !blobsInDb.includes(blob)
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
blobsToSync.map(async blobKey => {
|
||||
const blob = await bs.get(blobKey);
|
||||
if (blob) {
|
||||
const bin = new Uint8Array(await blob.arrayBuffer());
|
||||
await window.apis.db.addBlob(workspace.id, blobKey, bin);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ExportPanel: FC<{
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
const workspaceId = workspace.id;
|
||||
const t = useAFFiNEI18N();
|
||||
const onExport = useCallback(async () => {
|
||||
await syncBlobsToSqliteDb(workspace);
|
||||
const result = await window.apis?.dialog.saveDBFileAs(workspaceId);
|
||||
if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
} else if (!result?.canceled) {
|
||||
toast(t['Export success']());
|
||||
}
|
||||
}, [t, workspace, workspaceId]);
|
||||
return (
|
||||
<>
|
||||
<SettingRow name={t['Export']()} desc={t['Export Description']()}>
|
||||
<Button
|
||||
size="small"
|
||||
data-testid="export-affine-backup"
|
||||
onClick={async () => {
|
||||
const result = await window.apis?.dialog.saveDBFileAs(workspaceId);
|
||||
if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
} else if (!result?.canceled) {
|
||||
toast(t['Export success']());
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button data-testid="export-affine-backup" onClick={onExport}>
|
||||
{t['Export']()}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
|
||||
@@ -84,13 +84,12 @@ export const ProfilePanel: FC<{
|
||||
/>
|
||||
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
|
||||
<IconButton
|
||||
size="middle"
|
||||
data-testid="save-workspace-name"
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName(input);
|
||||
}}
|
||||
active={true}
|
||||
style={{
|
||||
color: 'var(--affine-primary-color)',
|
||||
marginLeft: '12px',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -74,7 +74,7 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
|
||||
<FlexWrapper justifyContent="space-between">
|
||||
<Button
|
||||
className={style.urlButton}
|
||||
size="middle"
|
||||
size="large"
|
||||
onClick={useCallback(() => {
|
||||
window.open(shareUrl, '_blank');
|
||||
}, [shareUrl])}
|
||||
@@ -82,7 +82,7 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
|
||||
>
|
||||
{shareUrl}
|
||||
</Button>
|
||||
<Button size="middle" onClick={copyUrl}>
|
||||
<Button size="large" onClick={copyUrl}>
|
||||
{t['Copy']()}
|
||||
</Button>
|
||||
</FlexWrapper>
|
||||
|
||||
@@ -79,14 +79,13 @@ export const StoragePanel: FC<{
|
||||
<Button
|
||||
data-testid="move-folder"
|
||||
className={style.urlButton}
|
||||
size="middle"
|
||||
size="large"
|
||||
onClick={handleMoveTo}
|
||||
>
|
||||
{secondaryPath}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
size="small"
|
||||
data-testid="reveal-folder"
|
||||
data-disabled={moveToInProgress}
|
||||
onClick={onRevealDBFile}
|
||||
@@ -96,7 +95,6 @@ export const StoragePanel: FC<{
|
||||
</FlexWrapper>
|
||||
) : (
|
||||
<Button
|
||||
size="small"
|
||||
data-testid="move-folder"
|
||||
data-disabled={moveToInProgress}
|
||||
onClick={handleMoveTo}
|
||||
|
||||
@@ -32,11 +32,7 @@ export const ThemeSettings = () => {
|
||||
[setTheme]
|
||||
)}
|
||||
>
|
||||
<RadioButton
|
||||
bold={true}
|
||||
value="system"
|
||||
data-testid="system-theme-trigger"
|
||||
>
|
||||
<RadioButton value="system" data-testid="system-theme-trigger">
|
||||
{t['system']()}
|
||||
</RadioButton>
|
||||
<RadioButton bold={true} value="light" data-testid="light-theme-trigger">
|
||||
@@ -117,7 +113,7 @@ export const AppearanceSettings = () => {
|
||||
desc={t['Select the language for the interface.']()}
|
||||
>
|
||||
<div className={settingWrapper}>
|
||||
<LanguageMenu triggerProps={{ size: 'small' }} />
|
||||
<LanguageMenu />
|
||||
</div>
|
||||
</SettingRow>
|
||||
{environment.isDesktop ? (
|
||||
|
||||
@@ -3,13 +3,12 @@ import {
|
||||
SettingWrapper,
|
||||
} from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { registeredPluginAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { registeredPluginAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
|
||||
export const Plugins = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const allowedPlugins = useAtomValue(registeredPluginAtom);
|
||||
console.log('allowedPlugins', allowedPlugins);
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Tooltip } from '@affine/component';
|
||||
import {
|
||||
WorkspaceListItemSkeleton,
|
||||
WorkspaceListSkeleton,
|
||||
@@ -19,6 +20,7 @@ import type {
|
||||
GeneralSettingList,
|
||||
} from '../general-setting';
|
||||
import {
|
||||
currentWorkspaceLabel,
|
||||
settingSlideBar,
|
||||
sidebarItemsWrapper,
|
||||
sidebarSelectItem,
|
||||
@@ -133,9 +135,18 @@ const WorkspaceListItem = ({
|
||||
<WorkspaceAvatar size={14} workspace={workspace} className="icon" />
|
||||
<span className="setting-name">{workspaceName}</span>
|
||||
{isCurrent ? (
|
||||
<div className="current-label" data-testid="current-workspace-label">
|
||||
Current
|
||||
</div>
|
||||
<Tooltip
|
||||
content="Current"
|
||||
title="Current"
|
||||
offset={[0, -5]}
|
||||
placement="top"
|
||||
disablePortal={false}
|
||||
>
|
||||
<div
|
||||
className={currentWorkspaceLabel}
|
||||
data-testid="current-workspace-label"
|
||||
></div>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -75,20 +75,21 @@ globalStyle(`${settingSlideBar} .setting-name`, {
|
||||
whiteSpace: 'nowrap',
|
||||
flexGrow: 1,
|
||||
});
|
||||
globalStyle(`${settingSlideBar} .current-label`, {
|
||||
export const currentWorkspaceLabel = style({
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '8px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '0 5px',
|
||||
// TODO: use color variable
|
||||
background: '#1E96EB',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
fontWeight: '600',
|
||||
color: 'var(--affine-white)',
|
||||
marginLeft: '10px',
|
||||
flexShrink: 0,
|
||||
selectors: {
|
||||
'&::after': {
|
||||
content: '""',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
background: 'var(--affine-blue)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const accountButton = style({
|
||||
|
||||
@@ -4,13 +4,15 @@ import { PageList, PageListTrashView } from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { type PageMeta, type Workspace } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { getPagePreviewText } from '@toeverything/hooks/use-block-suite-page-preview';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-page-preview';
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Suspense, useMemo } from 'react';
|
||||
|
||||
import { allPageModeSelectAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
@@ -39,6 +41,34 @@ const filter = {
|
||||
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
|
||||
};
|
||||
|
||||
const PagePreviewInner = ({
|
||||
workspace,
|
||||
pageId,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}) => {
|
||||
const page = useBlockSuiteWorkspacePage(workspace, pageId);
|
||||
assertExists(page);
|
||||
const previewAtom = useBlockSuitePagePreview(page);
|
||||
const preview = useAtomValue(previewAtom);
|
||||
return preview;
|
||||
};
|
||||
|
||||
const PagePreview = ({
|
||||
workspace,
|
||||
pageId,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}) => {
|
||||
return (
|
||||
<Suspense>
|
||||
<PagePreviewInner workspace={workspace} pageId={pageId} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
const PageListEmpty = (props: {
|
||||
createPage?: () => void;
|
||||
listType: BlockSuitePageListProps['listType'];
|
||||
@@ -147,8 +177,6 @@ export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
||||
|
||||
if (listType === 'trash') {
|
||||
const pageList: TrashListData[] = list.map(pageMeta => {
|
||||
const page = blockSuiteWorkspace.getPage(pageMeta.id);
|
||||
const preview = page ? getPagePreviewText(page) : undefined;
|
||||
return {
|
||||
icon: isPreferredEdgeless(pageMeta.id) ? (
|
||||
<EdgelessIcon />
|
||||
@@ -157,7 +185,9 @@ export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
||||
),
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
preview,
|
||||
preview: (
|
||||
<PagePreview workspace={blockSuiteWorkspace} pageId={pageMeta.id} />
|
||||
),
|
||||
createDate: new Date(pageMeta.createDate),
|
||||
trashDate: pageMeta.trashDate
|
||||
? new Date(pageMeta.trashDate)
|
||||
@@ -186,12 +216,13 @@ export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
||||
|
||||
const pageList: ListData[] = list.map(pageMeta => {
|
||||
const page = blockSuiteWorkspace.getPage(pageMeta.id);
|
||||
const preview = page ? getPagePreviewText(page) : undefined;
|
||||
return {
|
||||
icon: isPreferredEdgeless(pageMeta.id) ? <EdgelessIcon /> : <PageIcon />,
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
preview,
|
||||
preview: (
|
||||
<PagePreview workspace={blockSuiteWorkspace} pageId={pageMeta.id} />
|
||||
),
|
||||
tags:
|
||||
page?.meta.tags?.map(id => tagOptionMap[id]).filter(v => v != null) ??
|
||||
[],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
@@ -15,15 +16,23 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
|
||||
[pageSettings]
|
||||
);
|
||||
const setPageMode = useSetAtom(setPageModeAtom);
|
||||
const createPageAndOpen = useCallback(() => {
|
||||
const page = createPage();
|
||||
return openPage(blockSuiteWorkspace.id, page.id);
|
||||
}, [blockSuiteWorkspace.id, createPage, openPage]);
|
||||
const createEdgelessAndOpen = useCallback(() => {
|
||||
const page = createPage();
|
||||
setPageMode(page.id, 'edgeless');
|
||||
return openPage(blockSuiteWorkspace.id, page.id);
|
||||
}, [blockSuiteWorkspace.id, createPage, openPage, setPageMode]);
|
||||
const createPageAndOpen = useCallback(
|
||||
(id?: string, mode?: 'page' | 'edgeless') => {
|
||||
const page = createPage(id);
|
||||
initEmptyPage(page); // we don't need to wait it to be loaded right?
|
||||
if (mode) {
|
||||
setPageMode(page.id, mode);
|
||||
}
|
||||
openPage(blockSuiteWorkspace.id, page.id);
|
||||
},
|
||||
[blockSuiteWorkspace.id, createPage, openPage, setPageMode]
|
||||
);
|
||||
const createEdgelessAndOpen = useCallback(
|
||||
(id?: string) => {
|
||||
return createPageAndOpen(id, 'edgeless');
|
||||
},
|
||||
[createPageAndOpen]
|
||||
);
|
||||
const importFileAndOpen = useCallback(async () => {
|
||||
const { showImportModal } = await import('@blocksuite/blocks');
|
||||
showImportModal({ workspace: blockSuiteWorkspace });
|
||||
|
||||
@@ -43,6 +43,9 @@ export const EditorModeSwitch = ({
|
||||
assertExists(pageMeta);
|
||||
const { trash } = pageMeta;
|
||||
useEffect(() => {
|
||||
if (trash) {
|
||||
return;
|
||||
}
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
if (
|
||||
!environment.isServer && environment.isMacOs
|
||||
@@ -64,7 +67,7 @@ export const EditorModeSwitch = ({
|
||||
document.addEventListener('keydown', keydown, { capture: true });
|
||||
return () =>
|
||||
document.removeEventListener('keydown', keydown, { capture: true });
|
||||
}, [setSetting, t]);
|
||||
}, [setSetting, t, trash]);
|
||||
|
||||
return (
|
||||
<Tooltip content={<TooltipContent />}>
|
||||
@@ -77,6 +80,7 @@ export const EditorModeSwitch = ({
|
||||
data-testid="switch-page-mode-button"
|
||||
active={currentMode === 'page'}
|
||||
hide={trash && currentMode !== 'page'}
|
||||
trash={trash}
|
||||
onClick={() => {
|
||||
setSetting(setting => {
|
||||
if (setting?.mode !== 'page') {
|
||||
@@ -90,6 +94,7 @@ export const EditorModeSwitch = ({
|
||||
data-testid="switch-edgeless-mode-button"
|
||||
active={currentMode === 'edgeless'}
|
||||
hide={trash && currentMode !== 'edgeless'}
|
||||
trash={trash}
|
||||
onClick={() => {
|
||||
setSetting(setting => {
|
||||
if (setting?.mode !== 'edgeless') {
|
||||
|
||||
@@ -34,14 +34,19 @@ export const StyledEditorModeSwitch = styled('div')<{
|
||||
export const StyledSwitchItem = styled('button')<{
|
||||
active?: boolean;
|
||||
hide?: boolean;
|
||||
}>(({ active = false, hide = false }) => {
|
||||
trash?: boolean;
|
||||
}>(({ active = false, hide = false, trash = false }) => {
|
||||
return {
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '8px',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
boxShadow: active ? 'var(--affine-shadow-1)' : 'none',
|
||||
color: active ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)',
|
||||
color: active
|
||||
? trash
|
||||
? 'var(--affine-error-color)'
|
||||
: 'var(--affine-primary-color)'
|
||||
: 'var(--affine-icon-color)',
|
||||
display: hide ? 'none' : 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -8,12 +8,14 @@ import { StyledSwitchItem } from './style';
|
||||
type HoverAnimateControllerProps = {
|
||||
active?: boolean;
|
||||
hide?: boolean;
|
||||
trash?: boolean;
|
||||
children: React.ReactElement;
|
||||
} & HTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
const HoverAnimateController = ({
|
||||
active,
|
||||
hide,
|
||||
trash,
|
||||
children,
|
||||
...props
|
||||
}: HoverAnimateControllerProps) => {
|
||||
@@ -22,6 +24,7 @@ const HoverAnimateController = ({
|
||||
<StyledSwitchItem
|
||||
hide={hide}
|
||||
active={active}
|
||||
trash={trash}
|
||||
onMouseEnter={() => {
|
||||
setStartAnimate(true);
|
||||
}}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -45,7 +45,7 @@ const CommonMenu = () => {
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}>
|
||||
<IconButton data-testid="editor-option-menu">
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
@@ -138,7 +138,7 @@ const PageMenu = () => {
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}>
|
||||
<IconButton data-testid="editor-option-menu">
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
|
||||
@@ -51,13 +51,13 @@ export const LanguageMenu: React.FC = () => {
|
||||
disablePortal={true}
|
||||
>
|
||||
<StyledButton
|
||||
type="plain"
|
||||
icon={
|
||||
<StyledArrowDownContainer>
|
||||
<ArrowDownSmallIcon />
|
||||
</StyledArrowDownContainer>
|
||||
}
|
||||
iconPosition="end"
|
||||
noBorder={true}
|
||||
data-testid="language-menu-button"
|
||||
>
|
||||
<StyledCurrentLanguage>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const group = style({
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
bottom: '100px',
|
||||
left: '0',
|
||||
display: 'flex',
|
||||
gap: '24px',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
export const buttonContainer = style({
|
||||
boxShadow: 'var(--affine-float-button-shadow-2)',
|
||||
borderRadius: '8px',
|
||||
});
|
||||
@@ -3,14 +3,14 @@ import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../../hooks/use-navigate-helper';
|
||||
|
||||
import { buttonContainer, group } from './styles.css';
|
||||
export const TrashButtonGroup = () => {
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
@@ -29,32 +29,34 @@ export const TrashButtonGroup = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
bold={true}
|
||||
shape="round"
|
||||
style={{ marginRight: '24px' }}
|
||||
onClick={() => {
|
||||
restoreFromTrash(pageId);
|
||||
}}
|
||||
>
|
||||
{t['Restore it']()}
|
||||
</Button>
|
||||
<Button
|
||||
bold={true}
|
||||
shape="round"
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{t['Delete permanently']()}
|
||||
</Button>
|
||||
<div className={group}>
|
||||
<div className={buttonContainer}>
|
||||
<Button
|
||||
type="processing"
|
||||
onClick={() => {
|
||||
restoreFromTrash(pageId);
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
{t['Restore it']()}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={buttonContainer}>
|
||||
<Button
|
||||
type="error"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
{t['Delete permanently']()}
|
||||
</Button>
|
||||
</div>
|
||||
<Confirm
|
||||
title={t['TrashButtonGroupTitle']()}
|
||||
content={t['TrashButtonGroupDescription']()}
|
||||
confirmText={t['Delete']()}
|
||||
confirmType="danger"
|
||||
confirmType="error"
|
||||
open={open}
|
||||
onConfirm={useCallback(() => {
|
||||
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
@@ -67,7 +69,7 @@ export const TrashButtonGroup = () => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { SidebarSwitch } from '@affine/component/app-sidebar/sidebar-header';
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { headerItemsAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { headerItemsAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||
import {
|
||||
@@ -23,7 +23,6 @@ import { currentModeAtom } from '../../../atoms/mode';
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import DownloadClientTip from './download-tips';
|
||||
import { EditorOptionMenu } from './header-right-items/editor-option-menu';
|
||||
import TrashButtonGroup from './header-right-items/trash-button-group';
|
||||
import * as styles from './styles.css';
|
||||
import { OSWarningMessage, shouldShowWarning } from './utils';
|
||||
|
||||
@@ -38,7 +37,6 @@ export type BaseHeaderProps<
|
||||
|
||||
export enum HeaderRightItemName {
|
||||
EditorOptionMenu = 'editorOptionMenu',
|
||||
TrashButtonGroup = 'trashButtonGroup',
|
||||
// some windows only items
|
||||
WindowsAppControls = 'windowsAppControls',
|
||||
}
|
||||
@@ -56,16 +54,10 @@ type HeaderItem = {
|
||||
};
|
||||
|
||||
const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
||||
[HeaderRightItemName.TrashButtonGroup]: {
|
||||
Component: TrashButtonGroup,
|
||||
availableWhen: (_, currentPage) => {
|
||||
return currentPage?.meta.trash === true;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.EditorOptionMenu]: {
|
||||
Component: EditorOptionMenu,
|
||||
availableWhen: (_, currentPage, { isPublic }) => {
|
||||
return !isPublic;
|
||||
return !isPublic && currentPage?.meta.trash !== true;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.WindowsAppControls]: {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Button } from '@affine/component';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type {
|
||||
FC,
|
||||
@@ -7,7 +11,7 @@ import type {
|
||||
PropsWithChildren,
|
||||
ReactElement,
|
||||
} from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { openQuickSearchModalAtom } from '../../../atoms';
|
||||
import { QuickSearchButton } from '../../pure/quick-search-button';
|
||||
@@ -27,10 +31,25 @@ export const BlockSuiteEditorHeader: FC<
|
||||
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
|
||||
meta => meta.id === currentPage?.id
|
||||
);
|
||||
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (isEditable) {
|
||||
setIsEditable(!isEditable);
|
||||
const value = inputRef.current?.value;
|
||||
if (value !== pageMeta?.title && currentPage) {
|
||||
pageTitleMeta.setPageTitle(currentPage?.id, value || '');
|
||||
}
|
||||
} else {
|
||||
setIsEditable(!isEditable);
|
||||
}
|
||||
}, [currentPage, isEditable, pageMeta?.title, pageTitleMeta]);
|
||||
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
assertExists(pageMeta);
|
||||
const title = pageMeta.title;
|
||||
|
||||
const title = pageMeta?.title;
|
||||
return (
|
||||
<Header ref={headerRef} {...props}>
|
||||
{children}
|
||||
@@ -46,8 +65,38 @@ export const BlockSuiteEditorHeader: FC<
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.title}>{title || 'Untitled'}</div>
|
||||
|
||||
<div>
|
||||
{isEditable ? (
|
||||
<div>
|
||||
<input
|
||||
autoFocus={true}
|
||||
className={styles.title}
|
||||
type="text"
|
||||
data-testid="title-content"
|
||||
defaultValue={pageMeta?.title}
|
||||
onBlur={handleClick}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
data-testid="save-edit-button"
|
||||
style={{
|
||||
marginLeft: '12px',
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span
|
||||
data-testid="title-edit-button"
|
||||
onClick={handleClick}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{title || 'Untitled'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.searchArrowWrapper}>
|
||||
<QuickSearchButton
|
||||
onClick={() => {
|
||||
|
||||
@@ -7,15 +7,14 @@ import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Page, Workspace } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import type { CallbackMap } from '@toeverything/plugin-infra/entry';
|
||||
import {
|
||||
affinePluginsAtom,
|
||||
contentLayoutAtom,
|
||||
editorItemsAtom,
|
||||
rootStore,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
import type { AffinePlugin, LayoutNode } from '@toeverything/plugin-infra/type';
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import type { CallbackMap } from '@toeverything/plugin-infra/entry';
|
||||
import type { LayoutNode } from '@toeverything/plugin-infra/type';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { CSSProperties, FC, ReactElement } from 'react';
|
||||
@@ -25,6 +24,7 @@ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
import { pageSettingFamily } from '../atoms';
|
||||
import { fontStyleOptions, useAppSetting } from '../atoms/settings';
|
||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||
import TrashButtonGroup from './blocksuite/workspace-header/header-right-items/trash-button-group';
|
||||
import * as styles from './page-detail-editor.css';
|
||||
import { pluginContainer } from './page-detail-editor.css';
|
||||
|
||||
@@ -67,62 +67,65 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
}, [appSettings.fontStyle]);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
className={clsx(styles.editor, {
|
||||
'full-screen': appSettings.fullWidthLayout,
|
||||
})}
|
||||
style={
|
||||
{
|
||||
'--affine-font-family': value,
|
||||
} as CSSProperties
|
||||
}
|
||||
key={`${workspace.id}-${pageId}`}
|
||||
mode={isPublic ? 'page' : currentMode}
|
||||
page={page}
|
||||
onInit={useCallback(
|
||||
(page: Page, editor: Readonly<EditorContainer>) => {
|
||||
onInit(page, editor);
|
||||
},
|
||||
[onInit]
|
||||
)}
|
||||
setBlockHub={setBlockHub}
|
||||
onLoad={useCallback(
|
||||
(page: Page, editor: EditorContainer) => {
|
||||
page.workspace.setPageMeta(page.id, {
|
||||
updatedDate: Date.now(),
|
||||
});
|
||||
localStorage.setItem('last_page_id', page.id);
|
||||
let dispose = () => {};
|
||||
if (onLoad) {
|
||||
dispose = onLoad(page, editor);
|
||||
}
|
||||
const editorItems = rootStore.get(editorItemsAtom);
|
||||
let disposes: (() => void)[] = [];
|
||||
const renderTimeout = setTimeout(() => {
|
||||
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
|
||||
const div = document.createElement('div');
|
||||
div.setAttribute('plugin-id', id);
|
||||
const cleanup = editorItem(div, editor);
|
||||
assertExists(parent);
|
||||
document.body.appendChild(div);
|
||||
return () => {
|
||||
cleanup();
|
||||
document.body.removeChild(div);
|
||||
};
|
||||
<>
|
||||
<Editor
|
||||
className={clsx(styles.editor, {
|
||||
'full-screen': appSettings.fullWidthLayout,
|
||||
})}
|
||||
style={
|
||||
{
|
||||
'--affine-font-family': value,
|
||||
} as CSSProperties
|
||||
}
|
||||
key={`${workspace.id}-${pageId}`}
|
||||
mode={isPublic ? 'page' : currentMode}
|
||||
page={page}
|
||||
onInit={useCallback(
|
||||
(page: Page, editor: Readonly<EditorContainer>) => {
|
||||
onInit(page, editor);
|
||||
},
|
||||
[onInit]
|
||||
)}
|
||||
setBlockHub={setBlockHub}
|
||||
onLoad={useCallback(
|
||||
(page: Page, editor: EditorContainer) => {
|
||||
page.workspace.setPageMeta(page.id, {
|
||||
updatedDate: Date.now(),
|
||||
});
|
||||
localStorage.setItem('last_page_id', page.id);
|
||||
let dispose = () => {};
|
||||
if (onLoad) {
|
||||
dispose = onLoad(page, editor);
|
||||
}
|
||||
const editorItems = rootStore.get(editorItemsAtom);
|
||||
let disposes: (() => void)[] = [];
|
||||
const renderTimeout = setTimeout(() => {
|
||||
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
|
||||
const div = document.createElement('div');
|
||||
div.setAttribute('plugin-id', id);
|
||||
const cleanup = editorItem(div, editor);
|
||||
assertExists(parent);
|
||||
document.body.appendChild(div);
|
||||
return () => {
|
||||
cleanup();
|
||||
document.body.removeChild(div);
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
dispose();
|
||||
clearTimeout(renderTimeout);
|
||||
setTimeout(() => {
|
||||
disposes.forEach(dispose => dispose());
|
||||
});
|
||||
};
|
||||
},
|
||||
[onLoad]
|
||||
)}
|
||||
/>
|
||||
return () => {
|
||||
dispose();
|
||||
clearTimeout(renderTimeout);
|
||||
setTimeout(() => {
|
||||
disposes.forEach(dispose => dispose());
|
||||
});
|
||||
};
|
||||
},
|
||||
[onLoad]
|
||||
)}
|
||||
/>
|
||||
{meta.trash && <TrashButtonGroup />}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -160,7 +163,6 @@ const PluginContentAdapter = memo<{
|
||||
type LayoutPanelProps = {
|
||||
node: LayoutNode;
|
||||
editorProps: PageDetailEditorProps;
|
||||
plugins: AffinePlugin<string>[];
|
||||
};
|
||||
|
||||
const LayoutPanel = memo(function LayoutPanel(
|
||||
@@ -185,21 +187,13 @@ const LayoutPanel = memo(function LayoutPanel(
|
||||
>
|
||||
<Panel defaultSize={node.splitPercentage}>
|
||||
<Suspense>
|
||||
<LayoutPanel
|
||||
node={node.first}
|
||||
editorProps={props.editorProps}
|
||||
plugins={props.plugins}
|
||||
/>
|
||||
<LayoutPanel node={node.first} editorProps={props.editorProps} />
|
||||
</Suspense>
|
||||
</Panel>
|
||||
<PanelResizeHandle />
|
||||
<Panel defaultSize={100 - node.splitPercentage}>
|
||||
<Suspense>
|
||||
<LayoutPanel
|
||||
node={node.second}
|
||||
editorProps={props.editorProps}
|
||||
plugins={props.plugins}
|
||||
/>
|
||||
<LayoutPanel node={node.second} editorProps={props.editorProps} />
|
||||
</Suspense>
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
@@ -215,16 +209,11 @@ export const PageDetailEditor: FC<PageDetailEditorProps> = props => {
|
||||
}
|
||||
|
||||
const layout = useAtomValue(contentLayoutAtom);
|
||||
const affinePluginsMap = useAtomValue(affinePluginsAtom);
|
||||
const plugins = useMemo(
|
||||
() => Object.values(affinePluginsMap),
|
||||
[affinePluginsMap]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Suspense>
|
||||
<LayoutPanel node={layout} editorProps={props} plugins={plugins} />
|
||||
<LayoutPanel node={layout} editorProps={props} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -14,8 +14,7 @@ export const Footer: FC = () => {
|
||||
<StyledFooter data-testid="workspace-list-modal-footer">
|
||||
<StyledSignInButton
|
||||
data-testid="sign-in-button"
|
||||
noBorder
|
||||
bold
|
||||
type="plain"
|
||||
icon={
|
||||
<div className="circle">
|
||||
<CloudWorkspaceIcon />
|
||||
|
||||
@@ -50,8 +50,6 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
size={[24, 24]}
|
||||
iconSize={[15, 15]}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import {
|
||||
EditCollectionModel,
|
||||
useCollectionManager,
|
||||
} from '@affine/component/page-list';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useGetPageInfoById } from '../../../../hooks/use-get-page-info';
|
||||
|
||||
type AddCollectionButtonProps = {
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
export const AddCollectionButton = ({
|
||||
workspace,
|
||||
}: AddCollectionButtonProps) => {
|
||||
const getPageInfo = useGetPageInfoById(workspace);
|
||||
const setting = useCollectionManager(workspace.id);
|
||||
const t = useAFFiNEI18N();
|
||||
const [show, showUpdateCollection] = useState(false);
|
||||
const defaultCollection = {
|
||||
id: uuidv4(),
|
||||
name: '',
|
||||
pinned: true,
|
||||
filterList: [],
|
||||
workspaceId: workspace.id,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
data-testid="slider-bar-add-collection-button"
|
||||
onClick={() => showUpdateCollection(true)}
|
||||
size="small"
|
||||
>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
|
||||
<EditCollectionModel
|
||||
propertiesMeta={workspace.meta.properties}
|
||||
getPageInfo={getPageInfo}
|
||||
onConfirm={setting.saveCollection}
|
||||
open={show}
|
||||
onClose={() => showUpdateCollection(false)}
|
||||
title={t['Save As New Collection']()}
|
||||
init={defaultCollection}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
DeleteIcon,
|
||||
FilterIcon,
|
||||
InformationIcon,
|
||||
MoreHorizontalIcon,
|
||||
UnpinIcon,
|
||||
ViewLayersIcon,
|
||||
@@ -192,6 +193,7 @@ const CollectionRenderer = ({
|
||||
/>
|
||||
<MenuItem
|
||||
data-testid="collection-item"
|
||||
data-type="collection-list-item"
|
||||
ref={setNodeRef}
|
||||
onCollapsedChange={setCollapsed}
|
||||
active={isOver}
|
||||
@@ -251,21 +253,34 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
|
||||
const metas = useBlockSuitePageMeta(workspace);
|
||||
const { savedCollections } = useSavedCollections(workspace.id);
|
||||
const getPageInfo = useGetPageInfoById(workspace);
|
||||
const pinedCollections = useMemo(
|
||||
() => savedCollections.filter(v => v.pinned),
|
||||
[savedCollections]
|
||||
);
|
||||
if (pinedCollections.length === 0) {
|
||||
return (
|
||||
<MenuItem
|
||||
data-testid="slider-bar-collection-null-description"
|
||||
icon={<InformationIcon />}
|
||||
disabled
|
||||
>
|
||||
<span>Create a collection</span>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div data-testid="collections" className={styles.wrapper}>
|
||||
{savedCollections
|
||||
.filter(v => v.pinned)
|
||||
.map(view => {
|
||||
return (
|
||||
<CollectionRenderer
|
||||
getPageInfo={getPageInfo}
|
||||
key={view.id}
|
||||
collection={view}
|
||||
pages={metas}
|
||||
workspace={workspace}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{pinedCollections.map(view => {
|
||||
return (
|
||||
<CollectionRenderer
|
||||
getPageInfo={getPageInfo}
|
||||
key={view.id}
|
||||
collection={view}
|
||||
pages={metas}
|
||||
workspace={workspace}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useHistoryAtom } from '../../atoms/history';
|
||||
import { useAppSetting } from '../../atoms/settings';
|
||||
import type { AllWorkspace } from '../../shared';
|
||||
import { CollectionsList } from '../pure/workspace-slider-bar/collections';
|
||||
import { AddCollectionButton } from '../pure/workspace-slider-bar/collections/add-collection-button';
|
||||
import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list';
|
||||
import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector';
|
||||
import ImportPage from './import-page';
|
||||
@@ -190,7 +191,9 @@ export const RootAppSidebar = ({
|
||||
<SidebarScrollableContainer>
|
||||
<CategoryDivider label={t['Favorites']()} />
|
||||
<FavoriteList workspace={blockSuiteWorkspace} />
|
||||
<CategoryDivider label={t['Collections']()} />
|
||||
<CategoryDivider label={t['Collections']()}>
|
||||
<AddCollectionButton workspace={blockSuiteWorkspace} />
|
||||
</CategoryDivider>
|
||||
<CollectionsList workspace={blockSuiteWorkspace} />
|
||||
<CategoryDivider label={t['others']()} />
|
||||
<RouteMenuLinkItem
|
||||
|
||||
@@ -2,7 +2,7 @@ import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { WorkspaceRegistry } from '@affine/env/workspace';
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
ToolContainer,
|
||||
WorkspaceFallback,
|
||||
} from '@affine/component/workspace';
|
||||
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
rootBlockHubAtom,
|
||||
@@ -28,16 +27,12 @@ import {
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/plugin-infra/__internal__/react';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
import { currentWorkspaceIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { FC, PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { lazy, Suspense, useCallback, useMemo } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
import {
|
||||
@@ -47,6 +42,7 @@ import {
|
||||
} from '../atoms';
|
||||
import { useAppSetting } from '../atoms/settings';
|
||||
import { AppContainer } from '../components/affine/app-container';
|
||||
import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
|
||||
import type { IslandItemNames } from '../components/pure/help-island';
|
||||
import { HelpIsland } from '../components/pure/help-island';
|
||||
import { processCollectionsDrag } from '../components/pure/workspace-slider-bar/collections';
|
||||
@@ -111,20 +107,6 @@ export const CurrentWorkspaceContext = ({
|
||||
const workspaceId = useAtomValue(currentWorkspaceIdAtom);
|
||||
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const exist = metadata.find(m => m.id === workspaceId);
|
||||
const navigate = useNavigate();
|
||||
// fixme(himself65): this is not a good way to handle this,
|
||||
// need a better way to check whether this workspace really exist.
|
||||
useEffect(() => {
|
||||
const id = setTimeout(() => {
|
||||
if (!exist) {
|
||||
navigate('/');
|
||||
globalThis.HALTING_PROBLEM_TIMEOUT <<= 1;
|
||||
}
|
||||
}, globalThis.HALTING_PROBLEM_TIMEOUT);
|
||||
return () => {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}, [exist, metadata.length, navigate]);
|
||||
if (metadata.length === 0) {
|
||||
return <WorkspaceFallback key="no-workspace" />;
|
||||
}
|
||||
@@ -171,32 +153,21 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
|
||||
|
||||
export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const [currentPageId, setCurrentPageId] = useAtom(currentPageIdAtom);
|
||||
const { jumpToPage, openPage } = useNavigateHelper();
|
||||
const { openPage } = useNavigateHelper();
|
||||
|
||||
usePassiveWorkspaceEffect(currentWorkspace.blockSuiteWorkspace);
|
||||
|
||||
useEffect(() => {
|
||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(
|
||||
`${currentWorkspace.blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`
|
||||
);
|
||||
if (page && page.meta.jumpOnce) {
|
||||
currentWorkspace.blockSuiteWorkspace.meta.setPageMeta(page.id, {
|
||||
jumpOnce: false,
|
||||
});
|
||||
setCurrentPageId(currentPageId);
|
||||
jumpToPage(currentWorkspace.id, page.id);
|
||||
}
|
||||
}, [currentPageId, currentWorkspace, jumpToPage, setCurrentPageId]);
|
||||
|
||||
const [, setOpenWorkspacesModal] = useAtom(openWorkspacesModalAtom);
|
||||
const helper = useBlockSuiteWorkspaceHelper(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
const helper = usePageHelper(currentWorkspace.blockSuiteWorkspace);
|
||||
|
||||
const handleCreatePage = useCallback(() => {
|
||||
return helper.createPage(nanoid());
|
||||
}, [helper]);
|
||||
const id = nanoid();
|
||||
helper.createPage(id);
|
||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(id);
|
||||
assertExists(page);
|
||||
return page;
|
||||
}, [currentWorkspace.blockSuiteWorkspace, helper]);
|
||||
|
||||
const handleOpenWorkspaceListModal = useCallback(() => {
|
||||
setOpenWorkspacesModal(true);
|
||||
}, [setOpenWorkspacesModal]);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { getWorkspace } from '@toeverything/plugin-infra/__internal__/workspace';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { lazy, useEffect, useRef } from 'react';
|
||||
|
||||
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { useWorkspace } from '../hooks/use-workspace';
|
||||
import { rootStore } from '@toeverything/plugin-infra/atom';
|
||||
import { lazy } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
const AllWorkspaceModals = lazy(() =>
|
||||
import('../providers/modal-provider').then(({ AllWorkspaceModals }) => ({
|
||||
@@ -14,81 +12,35 @@ const AllWorkspaceModals = lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
type WorkspaceLoaderProps = {
|
||||
id: string;
|
||||
};
|
||||
const logger = new DebugLogger('index-page');
|
||||
|
||||
const WorkspaceLoader = (props: WorkspaceLoaderProps): null => {
|
||||
useWorkspace(props.id);
|
||||
export const loader: LoaderFunction = async () => {
|
||||
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
|
||||
const lastId = localStorage.getItem('last_workspace_id');
|
||||
const lastPageId = localStorage.getItem('last_page_id');
|
||||
const target = (lastId && meta.find(({ id }) => id === lastId)) || meta.at(0);
|
||||
if (target) {
|
||||
const targetWorkspace = getWorkspace(target.id);
|
||||
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
|
||||
({ trash }) => !trash
|
||||
);
|
||||
const pageId =
|
||||
nonTrashPages.find(({ id }) => id === lastPageId)?.id ??
|
||||
nonTrashPages.at(0)?.id;
|
||||
if (pageId) {
|
||||
logger.debug('Found target workspace. Jump to page', pageId);
|
||||
return redirect(`/workspace/${targetWorkspace.id}/${pageId}`);
|
||||
} else {
|
||||
logger.debug('Found target workspace. Jump to all page');
|
||||
return redirect(`/workspace/${targetWorkspace.id}/all`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const logger = new DebugLogger('index-page');
|
||||
|
||||
export const Component = () => {
|
||||
const meta = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const jumpOnceRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (jumpOnceRef.current) {
|
||||
return;
|
||||
}
|
||||
const lastId = localStorage.getItem('last_workspace_id');
|
||||
const lastPageId = localStorage.getItem('last_page_id');
|
||||
const target =
|
||||
(lastId && meta.find(({ id }) => id === lastId)) || meta.at(0);
|
||||
if (target) {
|
||||
const targetWorkspace = getWorkspace(target.id);
|
||||
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
|
||||
({ trash }) => !trash
|
||||
);
|
||||
const pageId =
|
||||
nonTrashPages.find(({ id }) => id === lastPageId)?.id ??
|
||||
nonTrashPages.at(0)?.id;
|
||||
if (pageId) {
|
||||
logger.debug('Found target workspace. Jump to page', pageId);
|
||||
navigateHelper.jumpToPage(
|
||||
targetWorkspace.id,
|
||||
pageId,
|
||||
RouteLogic.REPLACE
|
||||
);
|
||||
jumpOnceRef.current = true;
|
||||
} else {
|
||||
const clearId = setTimeout(() => {
|
||||
dispose.dispose();
|
||||
logger.debug('Found target workspace. Jump to all pages');
|
||||
navigateHelper.jumpToSubPath(
|
||||
targetWorkspace.id,
|
||||
WorkspaceSubPath.ALL,
|
||||
RouteLogic.REPLACE
|
||||
);
|
||||
jumpOnceRef.current = true;
|
||||
}, 1000);
|
||||
const dispose = targetWorkspace.slots.pageAdded.once(pageId => {
|
||||
clearTimeout(clearId);
|
||||
navigateHelper.jumpToPage(
|
||||
targetWorkspace.id,
|
||||
pageId,
|
||||
RouteLogic.REPLACE
|
||||
);
|
||||
jumpOnceRef.current = true;
|
||||
});
|
||||
return () => {
|
||||
clearTimeout(clearId);
|
||||
dispose.dispose();
|
||||
jumpOnceRef.current = false;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
console.warn('No workspace found');
|
||||
}
|
||||
return;
|
||||
}, [meta, navigateHelper]);
|
||||
return (
|
||||
<>
|
||||
{meta.map(({ id }) => (
|
||||
<WorkspaceLoader id={id} key={id} />
|
||||
))}
|
||||
<AllWorkspaceModals />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
import { useCollectionManager } from '@affine/component/page-list';
|
||||
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { useCallback } from 'react';
|
||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/plugin-infra/__internal__/workspace';
|
||||
import { currentPageIdAtom, rootStore } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtom } from 'jotai/react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
import { getUIAdapter } from '../../adapters/workspace';
|
||||
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
import { WorkspaceLayout } from '../../layouts/workspace-layout';
|
||||
|
||||
const AllPage = () => {
|
||||
export const loader: LoaderFunction = async args => {
|
||||
const workspaceId = args.params.workspaceId;
|
||||
assertExists(workspaceId);
|
||||
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
|
||||
const workspace = await rootStore.get(workspaceAtom);
|
||||
const page = workspace.getPage(
|
||||
`${workspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`
|
||||
);
|
||||
if (page && page.meta.jumpOnce) {
|
||||
workspace.meta.setPageMeta(page.id, {
|
||||
jumpOnce: false,
|
||||
});
|
||||
return redirect(`/workspace/${workspace.id}/${page.id}`);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const AllPage = () => {
|
||||
const { jumpToPage } = useNavigateHelper();
|
||||
const [currentPageId, setCurrentPageId] = useAtom(currentPageIdAtom);
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const setting = useCollectionManager(currentWorkspace.id);
|
||||
const onClickPage = useCallback(
|
||||
@@ -23,6 +46,18 @@ const AllPage = () => {
|
||||
},
|
||||
[currentWorkspace, jumpToPage]
|
||||
);
|
||||
useEffect(() => {
|
||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(
|
||||
`${currentWorkspace.blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`
|
||||
);
|
||||
if (page && page.meta.jumpOnce) {
|
||||
currentWorkspace.blockSuiteWorkspace.meta.setPageMeta(page.id, {
|
||||
jumpOnce: false,
|
||||
});
|
||||
setCurrentPageId(currentPageId);
|
||||
jumpToPage(currentWorkspace.id, page.id);
|
||||
}
|
||||
}, [currentPageId, currentWorkspace, jumpToPage, setCurrentPageId]);
|
||||
const { PageList, Header } = getUIAdapter(currentWorkspace.flavour);
|
||||
return (
|
||||
<>
|
||||
@@ -42,9 +77,5 @@ const AllPage = () => {
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
return (
|
||||
<WorkspaceLayout>
|
||||
<AllPage />
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
return <AllPage />;
|
||||
};
|
||||
|
||||
@@ -7,18 +7,18 @@ import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { currentPageIdAtom, rootStore } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useAtom } from 'jotai/react';
|
||||
import { type ReactElement, useCallback, useEffect } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { getUIAdapter } from '../../adapters/workspace';
|
||||
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
import { WorkspaceLayout } from '../../layouts/workspace-layout';
|
||||
|
||||
const WorkspaceDetailPageImpl = (): ReactElement => {
|
||||
const DetailPageImpl = (): ReactElement => {
|
||||
const { openPage, jumpToSubPath } = useNavigateHelper();
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
@@ -68,7 +68,7 @@ const WorkspaceDetailPageImpl = (): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
const WorkspaceDetailPage = (): ReactElement => {
|
||||
export const DetailPage = (): ReactElement => {
|
||||
const { workspaceId, pageId } = useParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
@@ -92,6 +92,13 @@ const WorkspaceDetailPage = (): ReactElement => {
|
||||
currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
||||
if (!page) {
|
||||
navigate('/404');
|
||||
} else {
|
||||
// fixme: cleanup jumpOnce in the right time
|
||||
if (page.meta.jumpOnce) {
|
||||
currentWorkspace.blockSuiteWorkspace.setPageMeta(currentPageId, {
|
||||
jumpOnce: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,13 +117,17 @@ const WorkspaceDetailPage = (): ReactElement => {
|
||||
if (!currentPageId || !page) {
|
||||
return <PageDetailSkeleton key="current-page-is-null" />;
|
||||
}
|
||||
return <WorkspaceDetailPageImpl />;
|
||||
return <DetailPageImpl />;
|
||||
};
|
||||
|
||||
export const loader: LoaderFunction = args => {
|
||||
if (args.params.pageId) {
|
||||
localStorage.setItem('last_page_id', args.params.pageId);
|
||||
rootStore.set(currentPageIdAtom, args.params.pageId);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
return (
|
||||
<WorkspaceLayout>
|
||||
<WorkspaceDetailPage />
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
return <DetailPage />;
|
||||
};
|
||||
|
||||
29
apps/core/src/pages/workspace/index.tsx
Normal file
29
apps/core/src/pages/workspace/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import type { ReactElement } from 'react';
|
||||
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
|
||||
|
||||
import { WorkspaceLayout } from '../../layouts/workspace-layout';
|
||||
|
||||
export const loader: LoaderFunction = async args => {
|
||||
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
|
||||
if (!meta.some(({ id }) => id === args.params.workspaceId)) {
|
||||
return redirect('/404');
|
||||
}
|
||||
if (args.params.workspaceId) {
|
||||
localStorage.setItem('last_workspace_id', args.params.workspaceId);
|
||||
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const Component = (): ReactElement => {
|
||||
return (
|
||||
<WorkspaceLayout>
|
||||
<Outlet />
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
};
|
||||
@@ -6,9 +6,8 @@ import { getUIAdapter } from '../../adapters/workspace';
|
||||
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
|
||||
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
import { WorkspaceLayout } from '../../layouts/workspace-layout';
|
||||
|
||||
const TrashPage = () => {
|
||||
export const TrashPage = () => {
|
||||
const { jumpToPage } = useNavigateHelper();
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const onClickPage = useCallback(
|
||||
@@ -44,9 +43,5 @@ const TrashPage = () => {
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
return (
|
||||
<WorkspaceLayout>
|
||||
<TrashPage />
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
return <TrashPage />;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { arrayMove } from '@dnd-kit/sortable';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import { lazy, Suspense, useCallback, useTransition } from 'react';
|
||||
|
||||
37
apps/core/src/router.ts
Normal file
37
apps/core/src/router.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
|
||||
export const router = createBrowserRouter(
|
||||
[
|
||||
{
|
||||
path: '/',
|
||||
lazy: () => import('./pages/index'),
|
||||
},
|
||||
{
|
||||
path: '/workspace/:workspaceId',
|
||||
lazy: () => import('./pages/workspace/index'),
|
||||
children: [
|
||||
{
|
||||
path: 'all',
|
||||
lazy: () => import('./pages/workspace/all-page'),
|
||||
},
|
||||
{
|
||||
path: 'trash',
|
||||
lazy: () => import('./pages/workspace/trash-page'),
|
||||
},
|
||||
{
|
||||
path: ':pageId',
|
||||
lazy: () => import('./pages/workspace/detail-page'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
],
|
||||
{
|
||||
future: {
|
||||
v7_normalizeFormMethod: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/docs",
|
||||
"version": "0.7.0-canary.51",
|
||||
"version": "0.7.0-canary.55",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,12 +10,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"express": "^4.18.2",
|
||||
"jotai": "^2.2.2",
|
||||
"react": "18.3.0-canary-1fdacbefd-20230630",
|
||||
@@ -24,12 +24,12 @@
|
||||
"waku": "0.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@types/react": "^18.2.17",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vanilla-extract/css": "^1.12.0",
|
||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,18 @@ module.exports = {
|
||||
loadingGif: './resources/icons/affine_installing.gif',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '@reforged/maker-appimage',
|
||||
config: {
|
||||
name: 'AFFiNE',
|
||||
iconUrl: icoPath,
|
||||
setupIcon: icoPath,
|
||||
platforms: ['linux'],
|
||||
options: {
|
||||
bin: productName,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
readPackageJson: async (_, packageJson) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.51",
|
||||
"version": "0.7.0-canary.55",
|
||||
"author": "affine",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -26,10 +26,10 @@
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@electron-forge/cli": "^6.2.1",
|
||||
"@electron-forge/core": "^6.2.1",
|
||||
"@electron-forge/core-utils": "^6.2.1",
|
||||
@@ -39,15 +39,16 @@
|
||||
"@electron-forge/maker-zip": "^6.2.1",
|
||||
"@electron-forge/shared-types": "^6.2.1",
|
||||
"@electron/remote": "2.0.10",
|
||||
"@reforged/maker-appimage": "^3.3.1",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "^25.2.0",
|
||||
"electron": "^25.3.1",
|
||||
"electron-log": "^5.0.0-beta.24",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esbuild": "^0.18.11",
|
||||
"esbuild": "^0.18.15",
|
||||
"fs-extra": "^11.1.1",
|
||||
"jotai": "^2.2.2",
|
||||
"ts-node": "^10.9.1",
|
||||
|
||||
@@ -15,7 +15,6 @@ if (process.platform === 'win32') {
|
||||
|
||||
async function buildLayers() {
|
||||
const common = config();
|
||||
await esbuild.build(common.workers);
|
||||
await esbuild.build({
|
||||
...common.layers,
|
||||
define: {
|
||||
|
||||
@@ -15,7 +15,7 @@ const DEV_SERVER_URL = process.env.DEV_SERVER_URL;
|
||||
/** @type 'production' | 'development'' */
|
||||
const mode = (process.env.NODE_ENV = process.env.NODE_ENV || 'development');
|
||||
|
||||
/** @return {{layers: import('esbuild').BuildOptions, workers: import('esbuild').BuildOptions}} */
|
||||
/** @return {{layers: import('esbuild').BuildOptions}} */
|
||||
export const config = () => {
|
||||
const define = Object.fromEntries([
|
||||
['process.env.NODE_ENV', `"${mode}"`],
|
||||
@@ -52,23 +52,5 @@ export const config = () => {
|
||||
assetNames: '[name]',
|
||||
treeShaking: true,
|
||||
},
|
||||
workers: {
|
||||
entryPoints: [
|
||||
resolve(electronDir, './src/main/workers/plugin.worker.ts'),
|
||||
],
|
||||
entryNames: '[dir]/[name]',
|
||||
outdir: resolve(electronDir, './dist/workers'),
|
||||
bundle: true,
|
||||
target: `node${NODE_MAJOR_VERSION}`,
|
||||
platform: 'node',
|
||||
external: ['@toeverything/plugin-infra', 'async-call-rpc'],
|
||||
define: define,
|
||||
format: 'cjs',
|
||||
loader: {
|
||||
'.node': 'copy',
|
||||
},
|
||||
assetNames: '[name]',
|
||||
treeShaking: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -96,37 +96,8 @@ async function watchLayers() {
|
||||
});
|
||||
}
|
||||
|
||||
async function watchWorkers() {
|
||||
return new Promise(async resolve => {
|
||||
let initialBuild = false;
|
||||
|
||||
const buildContext = await esbuild.context({
|
||||
...common.workers,
|
||||
plugins: [
|
||||
...(common.workers.plugins ?? []),
|
||||
{
|
||||
name: 'electron-dev:reload-app-on-workers-change',
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
if (initialBuild) {
|
||||
console.log(`[workers] has changed, [re]launching electron...`);
|
||||
spawnOrReloadElectron();
|
||||
} else {
|
||||
resolve();
|
||||
initialBuild = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await buildContext.watch();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await watchLayers();
|
||||
await watchWorkers();
|
||||
|
||||
if (watchMode) {
|
||||
console.log(`Watching for changes...`);
|
||||
|
||||
@@ -41,6 +41,7 @@ cd(repoRootDir);
|
||||
|
||||
// step 1: build web (nextjs) dist
|
||||
if (!process.env.SKIP_WEB_BUILD) {
|
||||
await $`yarn -T run build:plugins`;
|
||||
await $`DISTRIBUTION=desktop yarn nx build @affine/core`;
|
||||
await fs.move(affineCoreOutDir, publicAffineOutDir, { overwrite: true });
|
||||
}
|
||||
|
||||
@@ -16,18 +16,10 @@ const outputList = [
|
||||
'preload.js',
|
||||
'affine.darwin-arm64.node',
|
||||
'plugins',
|
||||
'workers',
|
||||
],
|
||||
],
|
||||
['dist/plugins', ['bookmark-block']],
|
||||
['dist/plugins/bookmark-block', ['index.mjs']],
|
||||
['dist/workers', ['plugin.worker.js']],
|
||||
[
|
||||
'node_modules/@toeverything/plugin-infra/dist',
|
||||
['manager.js', 'manager.cjs'],
|
||||
],
|
||||
['node_modules/@blocksuite/global/dist', ['utils.js']],
|
||||
['node_modules/jotai', ['vanilla.js']],
|
||||
['dist/plugins', ['bookmark']],
|
||||
['dist/plugins/bookmark', ['index.js']],
|
||||
] as [entry: string, expected: string[]][];
|
||||
|
||||
await Promise.all(
|
||||
|
||||
@@ -52,12 +52,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
};
|
||||
|
||||
setupListener(docId?: string) {
|
||||
logger.debug(
|
||||
'WorkspaceSQLiteDB:setupListener',
|
||||
this.workspaceId,
|
||||
docId,
|
||||
this.getWorkspaceName()
|
||||
);
|
||||
logger.debug('WorkspaceSQLiteDB:setupListener', this.workspaceId, docId);
|
||||
const doc = this.getDoc(docId);
|
||||
if (doc) {
|
||||
const onUpdate = async (update: Uint8Array, origin: YOrigin) => {
|
||||
|
||||
@@ -29,6 +29,7 @@ export async function savePDFFileAs(
|
||||
buttonLabel: 'Save',
|
||||
defaultPath: `${pageTitle}.pdf`,
|
||||
message: 'Save Page as a PDF file',
|
||||
filters: [{ name: 'PDF Files', extensions: ['pdf'] }],
|
||||
}));
|
||||
const filePath = ret.filePath;
|
||||
if (ret.canceled || !filePath) {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
|
||||
import { logger } from '@affine/electron/main/logger';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
import { MessageEventChannel } from './utils';
|
||||
|
||||
declare global {
|
||||
// fixme(himself65):
|
||||
// remove this when bookmark block plugin is migrated to plugin-infra
|
||||
@@ -15,56 +11,25 @@ declare global {
|
||||
}
|
||||
|
||||
export function registerPlugin() {
|
||||
const pluginWorkerPath = join(__dirname, './workers/plugin.worker.js');
|
||||
const asyncCall = AsyncCall<
|
||||
Record<string, (...args: any) => PromiseLike<any>>
|
||||
>(
|
||||
{
|
||||
log: (...args: any[]) => {
|
||||
logger.log('Plugin Worker', ...args);
|
||||
},
|
||||
},
|
||||
{
|
||||
channel: new MessageEventChannel(new Worker(pluginWorkerPath)),
|
||||
}
|
||||
);
|
||||
globalThis.asyncCall = asyncCall;
|
||||
logger.info('import plugin manager');
|
||||
import('@toeverything/plugin-infra/manager')
|
||||
.then(({ rootStore, affinePluginsAtom }) => {
|
||||
logger.info('import plugin manager');
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, './plugins'),
|
||||
'./bookmark-block/index.mjs'
|
||||
);
|
||||
logger.info('bookmark plugin path:', bookmarkPluginPath);
|
||||
import('file://' + bookmarkPluginPath);
|
||||
let dispose: () => void = () => {
|
||||
// noop
|
||||
};
|
||||
rootStore.sub(affinePluginsAtom, () => {
|
||||
dispose();
|
||||
const plugins = rootStore.get(affinePluginsAtom);
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
logger.info('register plugin', plugin.definition.id);
|
||||
plugin.definition.commands.forEach(command => {
|
||||
logger.info('register plugin command', command);
|
||||
ipcMain.handle(command, (event, ...args) =>
|
||||
asyncCall[command](...args)
|
||||
);
|
||||
});
|
||||
});
|
||||
dispose = () => {
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
plugin.definition.commands.forEach(command => {
|
||||
logger.info('unregister plugin command', command);
|
||||
ipcMain.removeHandler(command);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('import plugin manager error', error);
|
||||
});
|
||||
globalThis.asyncCall = {};
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, './plugins'),
|
||||
'./bookmark/index.js'
|
||||
);
|
||||
logger.info('bookmark plugin path:', bookmarkPluginPath);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { entry } = require(bookmarkPluginPath);
|
||||
|
||||
entry({
|
||||
registerCommand: (command: string, handler: (...args: any[]) => any) => {
|
||||
logger.info('register plugin command', command);
|
||||
ipcMain.handle(command, (event, ...args) => handler(...args));
|
||||
globalThis.asyncCall[command] = handler;
|
||||
},
|
||||
registerCommands: (command: string) => {
|
||||
ipcMain.removeHandler(command);
|
||||
delete globalThis.asyncCall[command];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { parentPort } from 'node:worker_threads';
|
||||
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
|
||||
import { MessageEventChannel } from '../utils';
|
||||
|
||||
const commandProxy: Record<string, (...args: any[]) => Promise<any>> = {};
|
||||
|
||||
if (!parentPort) {
|
||||
throw new Error('parentPort is undefined');
|
||||
}
|
||||
|
||||
const mainThread = AsyncCall<{
|
||||
log: (...args: any[]) => Promise<void>;
|
||||
}>(commandProxy, {
|
||||
channel: new MessageEventChannel(parentPort),
|
||||
});
|
||||
|
||||
globalThis.console.log = mainThread.log;
|
||||
globalThis.console.error = mainThread.log;
|
||||
globalThis.console.info = mainThread.log;
|
||||
globalThis.console.debug = mainThread.log;
|
||||
globalThis.console.warn = mainThread.log;
|
||||
|
||||
console.log('import plugin infra');
|
||||
|
||||
import('@toeverything/plugin-infra/manager')
|
||||
.then(({ rootStore, affinePluginsAtom }) => {
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, '../plugins'),
|
||||
'./bookmark-block/index.mjs'
|
||||
);
|
||||
|
||||
console.log('import bookmark plugin', bookmarkPluginPath);
|
||||
|
||||
import('file://' + bookmarkPluginPath).catch(console.log);
|
||||
rootStore.sub(affinePluginsAtom, () => {
|
||||
const plugins = rootStore.get(affinePluginsAtom);
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
console.log('handle plugin', plugin.definition.id);
|
||||
if (plugin.serverAdapter) {
|
||||
try {
|
||||
plugin.serverAdapter({
|
||||
registerCommand: (command, fn) => {
|
||||
console.log('register command', command);
|
||||
commandProxy[command] = fn;
|
||||
},
|
||||
unregisterCommand: command => {
|
||||
console.log('unregister command', command);
|
||||
delete commandProxy[command];
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'error when handle plugin',
|
||||
plugin.definition.id,
|
||||
`${e}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log('no server adapter, skipping.');
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.51",
|
||||
"version": "0.7.0-canary.55",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -15,18 +15,18 @@
|
||||
"postinstall": "prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.7.5",
|
||||
"@auth/prisma-adapter": "^1.0.0",
|
||||
"@aws-sdk/client-s3": "^3.363.0",
|
||||
"@apollo/server": "^4.8.1",
|
||||
"@auth/prisma-adapter": "^1.0.1",
|
||||
"@aws-sdk/client-s3": "^3.378.0",
|
||||
"@nestjs/apollo": "^12.0.7",
|
||||
"@nestjs/common": "^10.0.4",
|
||||
"@nestjs/core": "^10.0.4",
|
||||
"@nestjs/graphql": "^12.0.7",
|
||||
"@nestjs/platform-express": "^10.0.4",
|
||||
"@node-rs/argon2": "^1.5.0",
|
||||
"@node-rs/crc32": "^1.7.0",
|
||||
"@node-rs/jsonwebtoken": "^0.2.0",
|
||||
"@prisma/client": "^4.16.2",
|
||||
"@nestjs/common": "^10.1.2",
|
||||
"@nestjs/core": "^10.1.2",
|
||||
"@nestjs/graphql": "^12.0.8",
|
||||
"@nestjs/platform-express": "^10.1.2",
|
||||
"@node-rs/argon2": "^1.5.2",
|
||||
"@node-rs/crc32": "^1.7.2",
|
||||
"@node-rs/jsonwebtoken": "^0.2.3",
|
||||
"@prisma/client": "^5.0.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
@@ -37,7 +37,7 @@
|
||||
"next-auth": "^4.22.1",
|
||||
"nodemailer": "^6.9.3",
|
||||
"parse-duration": "^1.1.0",
|
||||
"prisma": "^4.16.2",
|
||||
"prisma": "^5.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
"semver": "^7.5.4"
|
||||
@@ -45,14 +45,14 @@
|
||||
"devDependencies": {
|
||||
"@affine/storage": "workspace:*",
|
||||
"@napi-rs/image": "^1.6.1",
|
||||
"@nestjs/testing": "^10.0.4",
|
||||
"@nestjs/testing": "^10.1.2",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^18.16.19",
|
||||
"@types/nodemailer": "^6.4.8",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^18.17.1",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"c8": "^8.0.0",
|
||||
"c8": "^8.0.1",
|
||||
"nodemon": "^2.0.22",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-node": "^10.9.1",
|
||||
|
||||
@@ -9,7 +9,7 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
this.$on('beforeExit', async () => {
|
||||
process.on('beforeExit', async () => {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,34 +9,34 @@
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@storybook/addon-actions": "^7.0.24",
|
||||
"@storybook/addon-essentials": "^7.0.24",
|
||||
"@storybook/addon-interactions": "^7.0.24",
|
||||
"@storybook/addon-links": "^7.0.24",
|
||||
"@storybook/addon-storysource": "^7.0.24",
|
||||
"@storybook/blocks": "^7.0.24",
|
||||
"@storybook/builder-vite": "^7.0.24",
|
||||
"@storybook/addon-actions": "^7.1.1",
|
||||
"@storybook/addon-essentials": "^7.1.1",
|
||||
"@storybook/addon-interactions": "^7.1.1",
|
||||
"@storybook/addon-links": "^7.1.1",
|
||||
"@storybook/addon-storysource": "^7.1.1",
|
||||
"@storybook/blocks": "^7.1.1",
|
||||
"@storybook/builder-vite": "^7.1.1",
|
||||
"@storybook/jest": "^0.1.0",
|
||||
"@storybook/react": "^7.0.24",
|
||||
"@storybook/react-vite": "^7.0.24",
|
||||
"@storybook/react": "^7.1.1",
|
||||
"@storybook/react-vite": "^7.1.1",
|
||||
"@storybook/test-runner": "^0.11.0",
|
||||
"@storybook/testing-library": "^0.2.0",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"concurrently": "^8.2.0",
|
||||
"jest-mock": "^29.5.0",
|
||||
"jest-mock": "^29.6.1",
|
||||
"serve": "^14.2.0",
|
||||
"storybook": "^7.0.24",
|
||||
"storybook": "^7.1.1",
|
||||
"storybook-dark-mode": "^3.0.0",
|
||||
"wait-on": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/block-std": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/icons": "^2.1.26",
|
||||
"@blocksuite/lit": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
@@ -48,5 +48,5 @@
|
||||
"@blocksuite/lit": "*",
|
||||
"@blocksuite/store": "*"
|
||||
},
|
||||
"version": "0.7.0-canary.51"
|
||||
"version": "0.7.0-canary.55"
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ import {
|
||||
} from '@affine/component/app-sidebar';
|
||||
import { AddPageButton } from '@affine/component/app-sidebar';
|
||||
import { CategoryDivider } from '@affine/component/app-sidebar';
|
||||
import {
|
||||
navHeaderStyle,
|
||||
sidebarButtonStyle,
|
||||
} from '@affine/component/app-sidebar';
|
||||
import { navHeaderStyle } from '@affine/component/app-sidebar';
|
||||
import { MenuLinkItem } from '@affine/component/app-sidebar';
|
||||
import { QuickSearchInput } from '@affine/component/app-sidebar';
|
||||
import {
|
||||
@@ -54,7 +51,7 @@ const Main = () => {
|
||||
<div className={navHeaderStyle}>
|
||||
{!open && (
|
||||
<IconButton
|
||||
className={sidebarButtonStyle}
|
||||
size="large"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
|
||||
@@ -5,9 +5,9 @@ import { DropdownButton } from '@affine/component';
|
||||
import { RadioButton, RadioButtonGroup } from '@affine/component';
|
||||
import { Menu } from '@affine/component';
|
||||
import { toast } from '@affine/component';
|
||||
import { InformationIcon } from '@blocksuite/icons';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default {
|
||||
title: 'AFFiNE/Button',
|
||||
component: Button,
|
||||
@@ -22,8 +22,9 @@ const Template: StoryFn<ButtonProps> = args => <Button {...args} />;
|
||||
export const Primary = Template.bind(undefined);
|
||||
Primary.args = {
|
||||
type: 'primary',
|
||||
children: 'This is a primary button',
|
||||
children: 'Content',
|
||||
onClick: () => toast('Click button'),
|
||||
icon: <InformationIcon />,
|
||||
};
|
||||
|
||||
export const Default = Template.bind(undefined);
|
||||
@@ -35,7 +36,7 @@ Default.args = {
|
||||
|
||||
export const Light = Template.bind(undefined);
|
||||
Light.args = {
|
||||
type: 'light',
|
||||
type: 'error',
|
||||
children: 'This is a light button',
|
||||
onClick: () => toast('Click button'),
|
||||
};
|
||||
@@ -49,7 +50,7 @@ Warning.args = {
|
||||
|
||||
export const Danger = Template.bind(undefined);
|
||||
Danger.args = {
|
||||
type: 'danger',
|
||||
type: 'success',
|
||||
children: 'This is a danger button',
|
||||
onClick: () => toast('Click button'),
|
||||
};
|
||||
|
||||
25
apps/storybook/src/stories/icon-button.stories.tsx
Normal file
25
apps/storybook/src/stories/icon-button.stories.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
/* deepscan-disable USELESS_ARROW_FUNC_BIND */
|
||||
import { IconButton, type IconButtonProps } from '@affine/component';
|
||||
import { toast } from '@affine/component';
|
||||
import { InformationIcon } from '@blocksuite/icons';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
export default {
|
||||
title: 'AFFiNE/IconButton',
|
||||
component: IconButton,
|
||||
} as Meta<IconButtonProps>;
|
||||
|
||||
const IconButtonTemplate: StoryFn<IconButtonProps> = args => {
|
||||
return (
|
||||
<>
|
||||
<h1>This is icon button</h1>
|
||||
<IconButton {...args} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Icon = IconButtonTemplate.bind(undefined);
|
||||
Icon.args = {
|
||||
children: <InformationIcon />,
|
||||
onClick: () => toast('Click button'),
|
||||
withoutPadding: true,
|
||||
};
|
||||
@@ -12,6 +12,12 @@ export default {
|
||||
} satisfies Meta<typeof NotificationCenter>;
|
||||
|
||||
let id = 0;
|
||||
const image = (
|
||||
<video autoPlay muted loop>
|
||||
<source src="/editingVideo.mp4" type="video/mp4" />
|
||||
<source src="/editingVideo.webm" type="video/webm" />
|
||||
</video>
|
||||
);
|
||||
export const Basic = () => {
|
||||
const push = useSetAtom(pushNotificationAtom);
|
||||
const expand = useAtomValue(expandNotificationCenterAtom);
|
||||
@@ -196,6 +202,27 @@ export const Basic = () => {
|
||||
dark error
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
const key = id++;
|
||||
push({
|
||||
key: `${key}`,
|
||||
title: `${key} title`,
|
||||
message: `gif test`,
|
||||
type: 'info',
|
||||
multimedia: image,
|
||||
timeout: 3000,
|
||||
undo: async () => {
|
||||
console.log('undo');
|
||||
},
|
||||
progressingBar: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
gif
|
||||
</button>
|
||||
</div>
|
||||
<NotificationCenter />
|
||||
</>
|
||||
);
|
||||
|
||||
37
package.json
37
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/monorepo",
|
||||
"version": "0.7.0-canary.51",
|
||||
"version": "0.7.0-canary.55",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
@@ -19,14 +19,13 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "dev-core",
|
||||
"dev:plugin": "vite build --watch",
|
||||
"dev:electron": "yarn workspace @affine/electron dev:app",
|
||||
"dev:plugins": "./apps/electron/scripts/plugins/dev-plugins.mjs",
|
||||
"build": "yarn nx build @affine/core",
|
||||
"build:electron": "yarn nx build @affine/electron",
|
||||
"build:storage": "yarn nx run-many -t build -p @affine/storage",
|
||||
"build:infra": "yarn nx run-many -t build -p plugin-infra infra",
|
||||
"build:plugins": "yarn workspace @affine/bookmark-block build && yarn vite build",
|
||||
"build:plugins": "node ./scripts/build-plugins.mjs",
|
||||
"build:storybook": "yarn nx build @affine/storybook",
|
||||
"start:web-static": "yarn workspace @affine/core static-server",
|
||||
"start:storybook": "yarn exec serve apps/storybook/storybook-static -l 6006",
|
||||
@@ -64,40 +63,40 @@
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
"@magic-works/i18n-codegen": "^0.5.0",
|
||||
"@nx/vite": "16.5.3",
|
||||
"@nx/vite": "16.5.5",
|
||||
"@perfsee/sdk": "^1.8.3",
|
||||
"@playwright/test": "^1.35.1",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@taplo/cli": "^0.5.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@toeverything/plugin-infra": "workspace:*",
|
||||
"@types/eslint": "^8.40.2",
|
||||
"@types/node": "^18.16.19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@types/eslint": "^8.44.1",
|
||||
"@types/node": "^18.17.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||
"@typescript-eslint/parser": "^6.2.0",
|
||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||
"@vanilla-extract/webpack-plugin": "^2.2.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"@vitest/coverage-istanbul": "^0.33.0",
|
||||
"@vitest/ui": "^0.33.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-i": "^2.27.5-3",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.33.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.19.0",
|
||||
"eslint-plugin-unicorn": "^47.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"fake-indexeddb": "4.0.1",
|
||||
"eslint-plugin-unicorn": "^48.0.0",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"fake-indexeddb": "4.0.2",
|
||||
"happy-dom": "^10.5.2",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.3",
|
||||
"madge": "^6.1.0",
|
||||
"msw": "^1.2.2",
|
||||
"msw": "^1.2.3",
|
||||
"nanoid": "^4.0.2",
|
||||
"nx": "16.5.3",
|
||||
"nx": "16.5.5",
|
||||
"nx-cloud": "latest",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^3.0.0",
|
||||
@@ -105,8 +104,8 @@
|
||||
"serve": "^14.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.4",
|
||||
"vite-plugin-istanbul": "^4.1.0",
|
||||
"vite": "^4.4.7",
|
||||
"vite-plugin-istanbul": "^5.0.0",
|
||||
"vite-plugin-static-copy": "^0.17.0",
|
||||
"vite-tsconfig-paths": "^4.2.0",
|
||||
"vitest": "^0.33.0",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"private": true,
|
||||
"bin": {
|
||||
"build-core": "./src/bin/build-core.mjs",
|
||||
"dev-core": "./src/bin/dev-core.mjs"
|
||||
"dev-core": "./src/bin/dev-core.mjs",
|
||||
"dev-plugin": "./src/bin/dev-plugin.mjs"
|
||||
},
|
||||
"exports": {
|
||||
"./config": "./src/config/index.ts"
|
||||
@@ -20,5 +21,5 @@
|
||||
"peerDependencies": {
|
||||
"ts-node": "*"
|
||||
},
|
||||
"version": "0.7.0-canary.51"
|
||||
"version": "0.7.0-canary.55"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from 'node:path';
|
||||
|
||||
import type { BuildFlags } from '../config/index.js';
|
||||
import { projectRoot } from '../config/index.js';
|
||||
import { buildI18N } from '../util/i18n.js';
|
||||
|
||||
const cwd = path.resolve(projectRoot, 'apps', 'core');
|
||||
|
||||
@@ -43,12 +44,7 @@ const flags = {
|
||||
coverage: process.env.COVERAGE === 'true',
|
||||
} satisfies BuildFlags;
|
||||
|
||||
spawn('vite', ['build'], {
|
||||
cwd: projectRoot,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
});
|
||||
|
||||
buildI18N();
|
||||
spawn(
|
||||
'node',
|
||||
[
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as p from '@clack/prompts';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
import { type BuildFlags, projectRoot } from '../config/index.js';
|
||||
import { watchI18N } from '../util/i18n.js';
|
||||
|
||||
const files = ['.env', '.env.local'];
|
||||
|
||||
@@ -91,12 +92,7 @@ flags.mode = buildFlags.mode as any;
|
||||
flags.channel = buildFlags.channel as any;
|
||||
flags.coverage = buildFlags.coverage;
|
||||
|
||||
spawn('vite', ['build', '--watch'], {
|
||||
cwd: projectRoot,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
});
|
||||
|
||||
watchI18N();
|
||||
spawn(
|
||||
'node',
|
||||
[
|
||||
|
||||
16
packages/cli/src/bin/dev-plugin.mjs
Executable file
16
packages/cli/src/bin/dev-plugin.mjs
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--loader',
|
||||
'ts-node/esm/transpile-only',
|
||||
fileURLToPath(new URL('./dev-plugin.ts', import.meta.url)),
|
||||
...process.argv.slice(2),
|
||||
],
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
if (child.status) process.exit(child.status);
|
||||
203
packages/cli/src/bin/dev-plugin.ts
Normal file
203
packages/cli/src/bin/dev-plugin.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { ok } from 'node:assert';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { mkdir, open, readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import { build } from 'vite';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { projectRoot } from '../config/index.js';
|
||||
|
||||
const args = process.argv.splice(2);
|
||||
|
||||
const result = parseArgs({
|
||||
args,
|
||||
options: {
|
||||
watch: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
plugin: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const plugin = result.values.plugin;
|
||||
|
||||
if (typeof plugin !== 'string') {
|
||||
throw new Error('plugin is required');
|
||||
}
|
||||
|
||||
const isWatch = result.values.watch;
|
||||
ok(typeof isWatch === 'boolean');
|
||||
|
||||
const packageJsonSchema = z.object({
|
||||
name: z.string(),
|
||||
affinePlugin: z.object({
|
||||
release: z.boolean(),
|
||||
entry: z.object({
|
||||
core: z.string(),
|
||||
server: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const external = [
|
||||
// built-in packages
|
||||
/^@affine/,
|
||||
/^@blocksuite/,
|
||||
/^@toeverything/,
|
||||
|
||||
// react
|
||||
/^react/,
|
||||
/^react-dom/,
|
||||
|
||||
// store
|
||||
/^jotai/,
|
||||
|
||||
// css
|
||||
/^@vanilla-extract/,
|
||||
|
||||
// remove this when bookmark plugin is ready
|
||||
'link-preview-js',
|
||||
];
|
||||
|
||||
const allPluginDir = path.resolve(projectRoot, 'plugins');
|
||||
|
||||
const getPluginDir = (plugin: string) => path.resolve(allPluginDir, plugin);
|
||||
const pluginDir = getPluginDir(plugin);
|
||||
const packageJsonFile = path.resolve(pluginDir, 'package.json');
|
||||
|
||||
const json: z.infer<typeof packageJsonSchema> = await readFile(
|
||||
packageJsonFile,
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
}
|
||||
)
|
||||
.then(text => JSON.parse(text))
|
||||
.then(async json => {
|
||||
const { success } = await packageJsonSchema.safeParseAsync(json);
|
||||
if (success) {
|
||||
return json;
|
||||
} else {
|
||||
throw new Error('invalid package.json');
|
||||
}
|
||||
});
|
||||
|
||||
type Metadata = {
|
||||
assets: Set<string>;
|
||||
};
|
||||
|
||||
const metadata: Metadata = {
|
||||
assets: new Set(),
|
||||
};
|
||||
|
||||
const outDir = path.resolve(projectRoot, 'apps', 'core', 'public', 'plugins');
|
||||
|
||||
const coreOutDir = path.resolve(outDir, plugin);
|
||||
|
||||
const serverOutDir = path.resolve(
|
||||
projectRoot,
|
||||
'apps',
|
||||
'electron',
|
||||
'dist',
|
||||
'plugins',
|
||||
plugin
|
||||
);
|
||||
|
||||
const pluginListJsonPath = path.resolve(outDir, 'plugin-list.json');
|
||||
|
||||
const coreEntry = path.resolve(pluginDir, json.affinePlugin.entry.core);
|
||||
if (json.affinePlugin.entry.server) {
|
||||
const serverEntry = path.resolve(pluginDir, json.affinePlugin.entry.server);
|
||||
await build({
|
||||
build: {
|
||||
watch: isWatch ? {} : undefined,
|
||||
minify: false,
|
||||
outDir: serverOutDir,
|
||||
emptyOutDir: true,
|
||||
lib: {
|
||||
entry: serverEntry,
|
||||
fileName: 'index',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await build({
|
||||
build: {
|
||||
watch: isWatch ? {} : undefined,
|
||||
minify: false,
|
||||
outDir: coreOutDir,
|
||||
emptyOutDir: true,
|
||||
lib: {
|
||||
entry: coreEntry,
|
||||
fileName: 'index',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: chunkInfo => {
|
||||
if (chunkInfo.name) {
|
||||
metadata.assets.add(chunkInfo.name);
|
||||
return chunkInfo.name;
|
||||
} else {
|
||||
throw new Error('no name');
|
||||
}
|
||||
},
|
||||
manualChunks: () => 'plugin',
|
||||
},
|
||||
external,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vanillaExtractPlugin(),
|
||||
react(),
|
||||
{
|
||||
name: 'generate-list-json',
|
||||
async generateBundle() {
|
||||
if (!existsSync(outDir)) {
|
||||
await mkdir(outDir, { recursive: true });
|
||||
}
|
||||
const file = await open(pluginListJsonPath, 'w+', 0o777);
|
||||
const txt = await file.readFile({
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
if (!txt) {
|
||||
console.log('generate plugin-list.json');
|
||||
await file.write(
|
||||
JSON.stringify([
|
||||
{
|
||||
release: json.affinePlugin.release,
|
||||
name: plugin,
|
||||
assets: [...metadata.assets],
|
||||
},
|
||||
])
|
||||
);
|
||||
} else {
|
||||
console.log('modify plugin-list.json');
|
||||
const list = JSON.parse(txt);
|
||||
const index = list.findIndex((item: any) => item.name === plugin);
|
||||
if (index === -1) {
|
||||
list.push({
|
||||
release: json.affinePlugin.release,
|
||||
name: plugin,
|
||||
assets: [...metadata.assets],
|
||||
});
|
||||
} else {
|
||||
list[index].assets = [...metadata.assets];
|
||||
}
|
||||
await file.write(JSON.stringify(list), 0);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
32
packages/cli/src/util/i18n.ts
Normal file
32
packages/cli/src/util/i18n.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { runCli } from '@magic-works/i18n-codegen';
|
||||
|
||||
import { projectRoot } from '../config/index.js';
|
||||
|
||||
const configPath = resolve(projectRoot, '.i18n-codegen.json');
|
||||
|
||||
export const watchI18N = () => {
|
||||
runCli(
|
||||
{
|
||||
config: configPath,
|
||||
watch: true,
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const buildI18N = () => {
|
||||
runCli(
|
||||
{
|
||||
config: configPath,
|
||||
watch: false,
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
};
|
||||
41
packages/cli/src/util/infra.ts
Normal file
41
packages/cli/src/util/infra.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { build } from 'vite';
|
||||
|
||||
import { projectRoot } from '../config/index.js';
|
||||
|
||||
const infraFilePath = resolve(
|
||||
projectRoot,
|
||||
'packages',
|
||||
'infra',
|
||||
'vite.config.ts'
|
||||
);
|
||||
const pluginInfraFilePath = resolve(
|
||||
projectRoot,
|
||||
'packages',
|
||||
'plugin-infra',
|
||||
'vite.config.ts'
|
||||
);
|
||||
|
||||
export const buildInfra = async () => {
|
||||
await build({
|
||||
configFile: infraFilePath,
|
||||
});
|
||||
await build({
|
||||
configFile: pluginInfraFilePath,
|
||||
});
|
||||
};
|
||||
|
||||
export const watchInfra = async () => {
|
||||
spawn('vite', ['build', '--watch'], {
|
||||
cwd: resolve(projectRoot, 'packages', 'infra'),
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
spawn('vite', ['build', '--watch'], {
|
||||
cwd: resolve(projectRoot, 'packages', 'plugin-infra'),
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
};
|
||||
@@ -28,7 +28,7 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/base": "5.0.0-beta.8",
|
||||
"@mui/icons-material": "^5.14.1",
|
||||
"@mui/material": "^5.14.1",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@radix-ui/react-avatar": "^1.0.3",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
@@ -37,35 +37,35 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"@toeverything/hooks": "workspace:*",
|
||||
"@toeverything/plugin-infra": "workspace:*",
|
||||
"@toeverything/theme": "^0.7.6",
|
||||
"@toeverything/theme": "^0.7.9",
|
||||
"@vanilla-extract/dynamic": "^2.0.3",
|
||||
"clsx": "^1.2.1",
|
||||
"clsx": "^2.0.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"jotai": "^2.2.2",
|
||||
"lit": "^2.7.6",
|
||||
"lottie-web": "^5.12.2",
|
||||
"react": "18.2.0",
|
||||
"react-datepicker": "^4.15.0",
|
||||
"react-datepicker": "^4.16.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-error-boundary": "^4.0.10",
|
||||
"react-is": "^18.2.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/icons": "^2.1.26",
|
||||
"@blocksuite/lit": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230719163314-76d863fc-nightly",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-datepicker": "^4.11.2",
|
||||
"@blocksuite/blocks": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230720073515-bea92e0f-nightly",
|
||||
"@types/react": "^18.2.17",
|
||||
"@types/react-datepicker": "^4.15.0",
|
||||
"@types/react-dnd": "^3.0.2",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vanilla-extract/css": "^1.12.0",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.4",
|
||||
"vite": "^4.4.7",
|
||||
"yjs": "^13.6.7"
|
||||
},
|
||||
"version": "0.7.0-canary.51"
|
||||
"version": "0.7.0-canary.55"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const root = style({
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
height: '16px',
|
||||
minHeight: '16px',
|
||||
userSelect: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
selectors: {
|
||||
'&:not(:first-of-type)': {
|
||||
marginTop: '10px',
|
||||
|
||||
@@ -83,12 +83,6 @@ export const navBodyStyle = style({
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const sidebarButtonStyle = style({
|
||||
height: '32px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
zIndex: 1,
|
||||
});
|
||||
|
||||
export const sidebarFloatMaskStyle = style({
|
||||
transition: 'opacity .15s',
|
||||
opacity: 0,
|
||||
|
||||
@@ -8,7 +8,7 @@ export const root = style({
|
||||
minHeight: '30px',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0 8px 0 12px',
|
||||
padding: '0 12px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
margin: '2px 0',
|
||||
selectors: {
|
||||
@@ -32,7 +32,20 @@ export const root = style({
|
||||
'&[data-collapsible="true"]': {
|
||||
width: 'calc(100% + 8px)',
|
||||
transform: 'translateX(-8px)',
|
||||
paddingLeft: '8px',
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '12px',
|
||||
},
|
||||
'&[data-type="collection-list-item"][data-collapsible="false"]:hover': {
|
||||
width: 'calc(100% + 8px)',
|
||||
transform: 'translateX(-8px)',
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '12px',
|
||||
},
|
||||
'&[data-type="favorite-list-item"][data-collapsible="false"]:hover': {
|
||||
width: 'calc(100% + 8px)',
|
||||
transform: 'translateX(-8px)',
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '12px',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -54,8 +67,8 @@ export const icon = style({
|
||||
});
|
||||
|
||||
export const collapsedIconContainer = style({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -79,7 +92,7 @@ export const iconsContainer = style({
|
||||
flexShrink: 0,
|
||||
selectors: {
|
||||
'&[data-collapsible="true"]': {
|
||||
width: '40px',
|
||||
width: '44px',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ export const SidebarHeader = (props: SidebarHeaderProps) => {
|
||||
<>
|
||||
{environment.isMacOs && <div style={{ flex: 1 }} />}
|
||||
<IconButton
|
||||
size="middle"
|
||||
data-testid="app-sidebar-arrow-button-back"
|
||||
disabled={props.router?.history.current === 0}
|
||||
onClick={() => {
|
||||
@@ -33,7 +32,6 @@ export const SidebarHeader = (props: SidebarHeaderProps) => {
|
||||
<ArrowLeftSmallIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="middle"
|
||||
data-testid="app-sidebar-arrow-button-forward"
|
||||
disabled={
|
||||
props.router
|
||||
|
||||
@@ -3,7 +3,6 @@ import { SidebarIcon } from '@blocksuite/icons';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
import { IconButton, Tooltip } from '../../../';
|
||||
import { sidebarButtonStyle } from '../index.css';
|
||||
import { appSidebarOpenAtom } from '../index.jotai';
|
||||
|
||||
export const SidebarSwitch = () => {
|
||||
@@ -20,11 +19,14 @@ export const SidebarSwitch = () => {
|
||||
zIndex={1000}
|
||||
>
|
||||
<IconButton
|
||||
size="large"
|
||||
data-testid={`app-sidebar-arrow-button-${open ? 'collapse' : 'expand'}`}
|
||||
className={sidebarButtonStyle}
|
||||
style={{
|
||||
zIndex: 1,
|
||||
}}
|
||||
onClick={() => setOpen(open => !open)}
|
||||
>
|
||||
<SidebarIcon width={24} height={24} />
|
||||
<SidebarIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { editorContainerModuleAtom } from '@affine/jotai';
|
||||
import type { BlockHub } from '@blocksuite/blocks';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { LitBlockSpec } from '@blocksuite/lit';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { Skeleton } from '@mui/material';
|
||||
import { use } from 'foxact/use';
|
||||
@@ -25,6 +26,8 @@ export type EditorProps = {
|
||||
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
pagePreset?: LitBlockSpec[];
|
||||
edgelessPreset?: LitBlockSpec[];
|
||||
};
|
||||
|
||||
export type ErrorBoundaryProps = {
|
||||
@@ -45,7 +48,7 @@ const ImagePreviewModal = lazy(() =>
|
||||
);
|
||||
|
||||
const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
||||
const { onLoad, page, mode, style, onInit } = props;
|
||||
const { onLoad, page, mode, style } = props;
|
||||
if (!page.loaded) {
|
||||
use(page.waitForLoaded());
|
||||
}
|
||||
@@ -59,6 +62,11 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
||||
editorRef.current = new JotaiEditorContainer();
|
||||
editorRef.current.autofocus = true;
|
||||
globalThis.currentEditor = editorRef.current;
|
||||
|
||||
// set page preset
|
||||
if (props.pagePreset) editorRef.current.pagePreset = props.pagePreset;
|
||||
if (props.edgelessPreset)
|
||||
editorRef.current.edgelessPreset = props.edgelessPreset;
|
||||
}
|
||||
const editor = editorRef.current;
|
||||
assertExists(editorRef, 'editorRef.current should not be null');
|
||||
@@ -66,14 +74,9 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
||||
editor.mode = mode;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (editor.page !== page) {
|
||||
editor.page = page;
|
||||
if (page.root === null) {
|
||||
onInit(page, editor);
|
||||
}
|
||||
}
|
||||
}, [editor, page, onInit]);
|
||||
if (editor.page !== page) {
|
||||
editor.page = page;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (editor.page && onLoad) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ProviderComposer } from '@affine/component/provider-composer';
|
||||
import { ThemeProvider } from '@affine/component/theme-provider';
|
||||
import { rootStore } from '@toeverything/plugin-infra/manager';
|
||||
import { rootStore } from '@toeverything/plugin-infra/atom';
|
||||
import { Provider } from 'jotai';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -133,19 +133,7 @@ export const groupStyle = style({
|
||||
});
|
||||
|
||||
export const buttonStyle = style({
|
||||
minWidth: '24px',
|
||||
height: '24px',
|
||||
margin: '10px 6px',
|
||||
padding: '0 0',
|
||||
':hover': {
|
||||
backgroundColor: 'var(--affine-background-error-color)',
|
||||
backgroundSize: '24px 24px',
|
||||
},
|
||||
});
|
||||
|
||||
export const buttonIconStyle = style({
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
});
|
||||
|
||||
export const scaleIndicatorButtonStyle = style({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import '@blocksuite/blocks';
|
||||
|
||||
import { Button, Tooltip } from '@affine/component';
|
||||
import type { ImageBlockModel } from '@blocksuite/blocks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
@@ -24,9 +23,10 @@ import type { FallbackProps } from 'react-error-boundary';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { Button, IconButton } from '../../ui/button';
|
||||
import { Tooltip } from '../../ui/tooltip';
|
||||
import { useZoomControls } from './hooks/use-zoom';
|
||||
import {
|
||||
buttonIconStyle,
|
||||
buttonStyle,
|
||||
captionStyle,
|
||||
groupStyle,
|
||||
@@ -339,12 +339,11 @@ const ImagePreviewModalImpl = (
|
||||
<div className={imagePreviewActionBarStyle}>
|
||||
<div>
|
||||
<Tooltip content={'Previous'} disablePortal={false}>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="previous-image-button"
|
||||
icon={<ArrowLeftSmallIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
icon={<ArrowLeftSmallIcon />}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
previousImageHandler(blockId);
|
||||
@@ -352,12 +351,11 @@ const ImagePreviewModalImpl = (
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content={'Next'} disablePortal={false}>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="next-image-button"
|
||||
icon={<ArrowRightSmallIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
icon={<ArrowRightSmallIcon />}
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
type="plain"
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
nextImageHandler(blockId);
|
||||
@@ -371,55 +369,50 @@ const ImagePreviewModalImpl = (
|
||||
disablePortal={true}
|
||||
showArrow={false}
|
||||
>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="fit-to-screen-button"
|
||||
icon={<ViewBarIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
hoverColor={'-moz-initial'}
|
||||
icon={<ViewBarIcon />}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
onClick={() => resetZoom()}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content={'Zoom out'} disablePortal={false}>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="zoom-out-button"
|
||||
icon={<MinusIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
icon={<MinusIcon />}
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
type="plain"
|
||||
onClick={zoomOut}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content={'Reset Scale'} disablePortal={false}>
|
||||
<Button
|
||||
data-testid="reset-scale-button"
|
||||
noBorder={true}
|
||||
size={'middle'}
|
||||
type="plain"
|
||||
size={'large'}
|
||||
className={scaleIndicatorButtonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={resetScale}
|
||||
>
|
||||
{`${(currentScale * 100).toFixed(0)}%`}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content={'Zoom in'} disablePortal={false}>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="zoom-in-button"
|
||||
icon={<PlusIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
icon={<PlusIcon />}
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
type="plain"
|
||||
onClick={() => zoomIn()}
|
||||
/>
|
||||
</Tooltip>
|
||||
<div className={groupStyle}></div>
|
||||
<Tooltip content={'Download'} disablePortal={false}>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="download-button"
|
||||
icon={<DownloadIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
icon={<DownloadIcon />}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
downloadHandler(blockId).catch(err => {
|
||||
@@ -429,12 +422,11 @@ const ImagePreviewModalImpl = (
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content={'Copy to clipboard'} disablePortal={false}>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="copy-to-clipboard-button"
|
||||
icon={<CopyIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
icon={<CopyIcon />}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={() => {
|
||||
if (!imageRef.current) {
|
||||
return;
|
||||
@@ -471,13 +463,12 @@ const ImagePreviewModalImpl = (
|
||||
</Tooltip>
|
||||
<div className={groupStyle}></div>
|
||||
<Tooltip content={'Delete'} disablePortal={false}>
|
||||
<Button
|
||||
<IconButton
|
||||
data-testid="delete-button"
|
||||
icon={<DeleteIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
icon={<DeleteIcon />}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
onClick={() => blockId && deleteHandler(blockId)}
|
||||
hoverColor="var(--affine-error-color)"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
// License on the MIT
|
||||
// https://github.com/emilkowalski/sonner/blob/5cb703edc108a23fd74979235c2f3c4005edd2a7/src/styles.css
|
||||
|
||||
import { keyframes, style, styleVariants } from '@vanilla-extract/css';
|
||||
import {
|
||||
globalStyle,
|
||||
keyframes,
|
||||
style,
|
||||
styleVariants,
|
||||
} from '@vanilla-extract/css';
|
||||
|
||||
const swipeOut = keyframes({
|
||||
'0%': {
|
||||
@@ -19,12 +24,28 @@ const swipeOut = keyframes({
|
||||
|
||||
export const notificationCenterViewportStyle = style({
|
||||
position: 'fixed',
|
||||
bottom: '200px',
|
||||
right: '60px',
|
||||
height: '500px',
|
||||
bottom: '20px',
|
||||
right: '20px',
|
||||
width: '380px',
|
||||
margin: 0,
|
||||
zIndex: 2147483647,
|
||||
outline: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
});
|
||||
export const notificationMultimediaStyle = style({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '230px',
|
||||
borderRadius: '8px 8px 0 0',
|
||||
overflow: 'hidden',
|
||||
marginBottom: '16px',
|
||||
});
|
||||
globalStyle(`${notificationMultimediaStyle} > *`, {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
cursor: 'unset',
|
||||
});
|
||||
|
||||
export const notificationStyle = style({
|
||||
@@ -128,18 +149,32 @@ export const notificationStyle = style({
|
||||
export const notificationIconStyle = style({
|
||||
fontSize: '24px',
|
||||
marginLeft: '18px',
|
||||
marginRight: '12px',
|
||||
marginRight: '8px',
|
||||
color: 'var(--affine-processing-color)',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
export const hasMediaStyle = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
paddingTop: '0',
|
||||
paddingBottom: '16px',
|
||||
width: '380px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'var(--affine-shadow-1)',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
background: 'var(--affine-white)',
|
||||
transition: 'all 0.3s',
|
||||
});
|
||||
export const notificationContentStyle = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
padding: '16px 0',
|
||||
width: '100%',
|
||||
paddingTop: '16px',
|
||||
paddingBottom: '16px',
|
||||
width: '380px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'var(--affine-shadow-1)',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
@@ -181,6 +216,26 @@ export const closeButtonStyle = style({
|
||||
export const closeButtonWithoutUndoStyle = style({
|
||||
marginLeft: '92px',
|
||||
});
|
||||
export const closeButtonWithMediaStyle = style({
|
||||
position: 'absolute',
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
fontSize: '16px',
|
||||
top: '6px',
|
||||
right: '6px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '4px',
|
||||
color: 'var(--affine-pure-black)',
|
||||
':hover': {
|
||||
background: 'var(--affine-hover-color)',
|
||||
},
|
||||
});
|
||||
export const closeButtonColorStyle = style({
|
||||
color: 'var(--affine-white)',
|
||||
});
|
||||
export const undoButtonStyle = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
background: 'var(--affine-hover-color)',
|
||||
@@ -189,6 +244,10 @@ export const undoButtonStyle = style({
|
||||
color: 'var(--affine-processing-color)',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
export const undoButtonWithMediaStyle = style({
|
||||
marginLeft: 'auto',
|
||||
marginRight: '16px',
|
||||
});
|
||||
export const messageStyle = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
width: '200px',
|
||||
|
||||
@@ -8,6 +8,7 @@ export type Notification = {
|
||||
theme?: 'light' | 'dark';
|
||||
timeout?: number;
|
||||
progressingBar?: boolean;
|
||||
multimedia?: React.ReactNode | JSX.Element | HTMLElement;
|
||||
// actions
|
||||
undo?: () => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -288,10 +288,20 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={clsx(styles.notificationContentStyle, {
|
||||
className={clsx({
|
||||
[typeStyle]: notification.theme,
|
||||
[styles.hasMediaStyle]: notification.multimedia,
|
||||
[styles.notificationContentStyle]: !notification.multimedia,
|
||||
})}
|
||||
>
|
||||
{notification.multimedia ? (
|
||||
<div className={styles.notificationMultimediaStyle}>
|
||||
<>{notification.multimedia}</>
|
||||
<IconButton className={styles.closeButtonWithMediaStyle}>
|
||||
<CloseIcon onClick={onClickRemove} />
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null}
|
||||
<Toast.Title
|
||||
className={clsx(styles.notificationTitleStyle, {
|
||||
[styles.darkColorStyle]: notification.theme === 'dark',
|
||||
@@ -312,25 +322,28 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
<div
|
||||
className={clsx(styles.undoButtonStyle, {
|
||||
[styles.darkColorStyle]: notification.theme === 'dark',
|
||||
[styles.undoButtonWithMediaStyle]: notification.multimedia,
|
||||
})}
|
||||
onClick={onClickUndo}
|
||||
>
|
||||
UNDO
|
||||
</div>
|
||||
)}
|
||||
<IconButton
|
||||
className={clsx(styles.closeButtonStyle, {
|
||||
[styles.closeButtonWithoutUndoStyle]: !notification.undo,
|
||||
})}
|
||||
style={{
|
||||
color:
|
||||
notification.theme === 'dark'
|
||||
? 'var(--affine-white)'
|
||||
: 'var(--affine-icon-color)',
|
||||
}}
|
||||
>
|
||||
<CloseIcon onClick={onClickRemove} />
|
||||
</IconButton>
|
||||
{notification.multimedia ? null : (
|
||||
<IconButton
|
||||
className={clsx(styles.closeButtonStyle, {
|
||||
[styles.closeButtonWithoutUndoStyle]: !notification.undo,
|
||||
})}
|
||||
style={{
|
||||
color:
|
||||
notification.theme === 'dark'
|
||||
? 'var(--affine-white)'
|
||||
: 'var(--affine-icon-color)',
|
||||
}}
|
||||
>
|
||||
<CloseIcon onClick={onClickRemove} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Toast.Title>
|
||||
<Toast.Description
|
||||
className={clsx(styles.messageStyle, {
|
||||
|
||||
@@ -19,12 +19,7 @@ export const FavoriteTag = forwardRef<
|
||||
>
|
||||
<IconButton
|
||||
ref={ref}
|
||||
iconSize={[20, 20]}
|
||||
style={{
|
||||
color: active
|
||||
? 'var(--affine-primary-color)'
|
||||
: 'var(--affine-icon-color)',
|
||||
}}
|
||||
active={active}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onClick?.(e);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type TitleCellProps = {
|
||||
icon: JSX.Element;
|
||||
text: string;
|
||||
desc?: string;
|
||||
desc?: React.ReactNode;
|
||||
suffix?: JSX.Element;
|
||||
/**
|
||||
* Customize the children of the cell
|
||||
|
||||
@@ -147,8 +147,6 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
hoverBackground="var(--affine-background-error-color)"
|
||||
hoverColor="var(--affine-error-color)"
|
||||
>
|
||||
<DeletePermanentlyIcon />
|
||||
</IconButton>
|
||||
@@ -157,7 +155,7 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||
title={t['Delete permanently?']()}
|
||||
content={t['TrashButtonGroupDescription']()}
|
||||
confirmText={t['Delete']()}
|
||||
confirmType="danger"
|
||||
confirmType="error"
|
||||
open={open}
|
||||
onConfirm={() => {
|
||||
onPermanentlyDeletePage();
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ExportToPdfIcon,
|
||||
ExportToPngIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -36,7 +37,7 @@ export const ExportToPdfMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'pdf' });
|
||||
setPushNotification({
|
||||
key: 'export-to-pdf',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -45,7 +46,7 @@ export const ExportToPdfMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: 'export-to-pdf',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
@@ -59,7 +60,7 @@ export const ExportToPdfMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'pdf' });
|
||||
setPushNotification({
|
||||
key: 'export-to-pdf',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -68,7 +69,7 @@ export const ExportToPdfMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: 'export-to-pdf',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
@@ -104,7 +105,7 @@ export const ExportToHtmlMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'html' });
|
||||
setPushNotification({
|
||||
key: 'export-to-html',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -112,6 +113,12 @@ export const ExportToHtmlMenuItem = ({
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
onSelect?.({ type: 'html' });
|
||||
}, [currentEditor, onSelect, setPushNotification, t]);
|
||||
@@ -146,7 +153,7 @@ export const ExportToPngMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'png' });
|
||||
setPushNotification({
|
||||
key: 'export-to-png',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -155,7 +162,7 @@ export const ExportToPngMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: 'export-to-png',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
@@ -192,7 +199,7 @@ export const ExportToMarkdownMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'markdown' });
|
||||
setPushNotification({
|
||||
key: 'export-to-markdown',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -201,7 +208,7 @@ export const ExportToMarkdownMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: 'export-to-markdown',
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
|
||||
@@ -44,7 +44,7 @@ const ConfirmModal = ({
|
||||
title: title || 'Untitled',
|
||||
})}
|
||||
confirmText={t.Delete()}
|
||||
confirmType="danger"
|
||||
confirmType="error"
|
||||
{...confirmModalProps}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ export type ListData = {
|
||||
pageId: string;
|
||||
icon: JSX.Element;
|
||||
title: string;
|
||||
preview?: string;
|
||||
preview?: React.ReactNode;
|
||||
tags: Tag[];
|
||||
favorite: boolean;
|
||||
createDate: Date;
|
||||
@@ -34,7 +34,7 @@ export type TrashListData = {
|
||||
pageId: string;
|
||||
icon: JSX.Element;
|
||||
title: string;
|
||||
preview?: string;
|
||||
preview?: React.ReactNode;
|
||||
createDate: Date;
|
||||
// TODO remove optional after assert that trashDate is always set
|
||||
trashDate?: Date;
|
||||
|
||||
@@ -20,8 +20,8 @@ export const viewButton = style({
|
||||
padding: '4px 8px',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
background: 'var(--affine-white)',
|
||||
['WebkitAppRegion' as string]: 'no-drag',
|
||||
maxWidth: '200px',
|
||||
overflow: 'hidden',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
transition: 'margin-left 0.2s ease-in-out',
|
||||
@@ -41,6 +41,8 @@ export const viewButton = style({
|
||||
},
|
||||
});
|
||||
globalStyle(`${viewButton} > span`, {
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
@@ -75,8 +77,12 @@ export const filterButton = style({
|
||||
padding: '4px 8px',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
background: 'var(--affine-white)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
['WebkitAppRegion' as string]: 'no-drag',
|
||||
transition: 'margin-left 0.2s ease-in-out',
|
||||
':hover': {
|
||||
borderColor: 'var(--affine-border-color)',
|
||||
|
||||
@@ -207,9 +207,7 @@ export const CollectionList = ({
|
||||
}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
className={clsx(styles.viewButton)}
|
||||
hoverColor="var(--affine-icon-color)"
|
||||
data-testid="collection-select"
|
||||
>
|
||||
<Tooltip
|
||||
@@ -235,8 +233,6 @@ export const CollectionList = ({
|
||||
<Button
|
||||
icon={<FilteredIcon />}
|
||||
className={clsx(styles.filterButton)}
|
||||
size="small"
|
||||
hoverColor="var(--affine-icon-color)"
|
||||
data-testid="create-first-filter"
|
||||
>
|
||||
{t['com.affine.filter']()}
|
||||
|
||||
@@ -56,12 +56,7 @@ export const EditCollectionModel = ({
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton
|
||||
top={12}
|
||||
right={12}
|
||||
onClick={onClose}
|
||||
hoverColor="var(--affine-icon-color)"
|
||||
/>
|
||||
<ModalCloseButton top={12} right={12} onClick={onClose} />
|
||||
{init ? (
|
||||
<EditCollection
|
||||
propertiesMeta={propertiesMeta}
|
||||
@@ -248,14 +243,14 @@ export const EditCollection = ({
|
||||
marginTop: 40,
|
||||
}}
|
||||
>
|
||||
<Button className={styles.cancelButton} onClick={onCancel}>
|
||||
<Button size="large" onClick={onCancel}>
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
<Button
|
||||
style={{
|
||||
marginLeft: 20,
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
size="large"
|
||||
data-testid="save-collection"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
@@ -281,17 +276,13 @@ export const SaveCollectionButton = ({
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className={styles.saveButton}
|
||||
onClick={() => changeShow(true)}
|
||||
size="middle"
|
||||
data-testid="save-as-collection"
|
||||
icon={<SaveIcon />}
|
||||
size="large"
|
||||
style={{ padding: '7px 8px' }}
|
||||
>
|
||||
<div className={styles.saveButtonContainer}>
|
||||
<div className={styles.saveIcon}>
|
||||
<SaveIcon />
|
||||
</div>
|
||||
<div className={styles.saveText}>Save As Collection</div>
|
||||
</div>
|
||||
Save As Collection
|
||||
</Button>
|
||||
<EditCollectionModel
|
||||
title={t['Save As New Collection']()}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user