mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-10 11:28:45 +00:00
Compare commits
61 Commits
v0.5.4-can
...
v0.5.4-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10cd000822 | ||
|
|
496225a92e | ||
|
|
1ef408c9ad | ||
|
|
8d8119b39b | ||
|
|
80c1f9e546 | ||
|
|
dbd3249ae5 | ||
|
|
fbbcb4bad9 | ||
|
|
33069c87d0 | ||
|
|
637b8203d3 | ||
|
|
92859bf8b9 | ||
|
|
8a617f91e6 | ||
|
|
84b36c1d35 | ||
|
|
2c49c774af | ||
|
|
de0b300aca | ||
|
|
4a50fe584c | ||
|
|
f7d1d922fa | ||
|
|
1b12972afd | ||
|
|
33c48eed79 | ||
|
|
9631c99f7b | ||
|
|
097cce34b5 | ||
|
|
52b9734a7b | ||
|
|
6d7f06c1c3 | ||
|
|
238f69b4e7 | ||
|
|
3d43e61087 | ||
|
|
66c3b09c67 | ||
|
|
1e84ad1484 | ||
|
|
b3a3911cea | ||
|
|
86988bd6e8 | ||
|
|
9096ac2960 | ||
|
|
ec39c23fb7 | ||
|
|
b036fe8502 | ||
|
|
71142a3f1d | ||
|
|
aace740df5 | ||
|
|
f42d656cfa | ||
|
|
88124994e1 | ||
|
|
5a881ec223 | ||
|
|
12b61d34c3 | ||
|
|
4eff5f3c38 | ||
|
|
648fad65e0 | ||
|
|
a2844e54d2 | ||
|
|
850cfe1187 | ||
|
|
9030767d16 | ||
|
|
a4e7d0d0c3 | ||
|
|
99898b2260 | ||
|
|
1031fbc7ec | ||
|
|
31cccafb40 | ||
|
|
73a7c01580 | ||
|
|
f9b012cac9 | ||
|
|
101cd18067 | ||
|
|
2c466617de | ||
|
|
2ff5ef9d5d | ||
|
|
903b6eaf30 | ||
|
|
fd4b664e4f | ||
|
|
51a4bdc5e4 | ||
|
|
ee695bbcb9 | ||
|
|
ef0521fa2a | ||
|
|
73d5b2081a | ||
|
|
70fbbb39c1 | ||
|
|
b6ca2aa063 | ||
|
|
b3c1434055 | ||
|
|
4599a9a601 |
@@ -45,7 +45,14 @@ const config = {
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
varsIgnorePattern: '^_',
|
||||
argsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
|
||||
2
.github/CLA.md
vendored
2
.github/CLA.md
vendored
@@ -56,5 +56,5 @@ Example:
|
||||
- Skye Sun, @skyesun, 2023/04/14
|
||||
- Jordy Delgado, @Jdelgad8, 2023/04/17
|
||||
- Howard Do, @howarddo2208, 2023/04/20
|
||||
- Kevin Deng, @sxzz, 2023/04/21
|
||||
- 三咲智子 Kevin Deng, @sxzz, 2023/04/21
|
||||
- Moeyua, @moeyua, 2023/04/22
|
||||
|
||||
22
.github/workflows/release-desktop-app.yml
vendored
22
.github/workflows/release-desktop-app.yml
vendored
@@ -63,6 +63,7 @@ jobs:
|
||||
NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
API_SERVER_PROFILE: prod
|
||||
ENABLE_TEST_PROPERTIES: false
|
||||
|
||||
- name: Upload Artifact (web-static)
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -76,6 +77,12 @@ jobs:
|
||||
name: before-make-electron-dist
|
||||
path: apps/electron/dist
|
||||
|
||||
- name: Upload YML Build Script
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: release-yml-build-script
|
||||
path: apps/electron/scripts/generate-yml.js
|
||||
|
||||
make-distribution:
|
||||
environment: ${{ github.ref_name == 'master' && 'production' || 'development' }}
|
||||
strategy:
|
||||
@@ -122,7 +129,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
|
||||
mv apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
- name: Save artifacts (windows)
|
||||
if: ${{ matrix.spec.platform == 'windows' }}
|
||||
run: |
|
||||
@@ -169,7 +176,17 @@ jobs:
|
||||
with:
|
||||
name: affine-linux-x64-builds
|
||||
path: ./
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: release-yml-build-script
|
||||
path: ./
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Generate Release yml
|
||||
run: |
|
||||
RELEASE_VERSION=${{ github.event.inputs.version }} node generate-yml.js
|
||||
- name: Create Release Draft
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
@@ -188,3 +205,4 @@ jobs:
|
||||
./RELEASES
|
||||
./*.AppImage
|
||||
./*.apk
|
||||
./*.yml
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -57,6 +57,7 @@ Thumbs.db
|
||||
.next
|
||||
out/
|
||||
storybook-static
|
||||
i18n_generated.ts
|
||||
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
|
||||
21
.i18n-codegen.json
Normal file
21
.i18n-codegen.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "./node_modules/@magic-works/i18n-codegen/schema.json",
|
||||
"version": 1,
|
||||
"list": [
|
||||
{
|
||||
"input": "./packages/i18n/src/resources/en.json",
|
||||
"output": "./packages/i18n/src/i18n_generated",
|
||||
"parser": {
|
||||
"type": "i18next",
|
||||
"contextSeparator": "$",
|
||||
"pluralSeparator": "_"
|
||||
},
|
||||
"generator": {
|
||||
"type": "i18next/react-hooks",
|
||||
"hooks": "useAFFiNEI18N",
|
||||
"emitTS": true,
|
||||
"shouldUnescape": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,11 +20,11 @@ const browserWindow = {
|
||||
isDestroyed: () => {
|
||||
return false;
|
||||
},
|
||||
setWindowButtonVisibility: (v: boolean) => {
|
||||
setWindowButtonVisibility: (_v: boolean) => {
|
||||
// will be stubbed later
|
||||
},
|
||||
webContents: {
|
||||
send: (type: string, ...args: any[]) => {
|
||||
send: (_type: string, ..._args: any[]) => {
|
||||
// ...
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,7 +8,11 @@ import type { AppContext } from '../context';
|
||||
export async function listWorkspaces(context: AppContext) {
|
||||
const basePath = path.join(context.appDataPath, 'workspaces');
|
||||
try {
|
||||
return fs.readdir(basePath);
|
||||
return fs
|
||||
.readdir(basePath, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
.then(dirs => dirs.filter(dir => dir.isDirectory()).map(dir => dir.name));
|
||||
} catch (error) {
|
||||
logger.error('listWorkspaces', error);
|
||||
return [];
|
||||
|
||||
@@ -28,19 +28,14 @@ if (!isSingleInstance) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
app.on('second-instance', (event, argv) => {
|
||||
app.on('second-instance', () => {
|
||||
restoreOrCreateWindow();
|
||||
});
|
||||
|
||||
app.on('open-url', async (_, url) => {
|
||||
app.on('open-url', async (_, _url) => {
|
||||
// todo: handle `affine://...` urls
|
||||
});
|
||||
|
||||
/**
|
||||
* Disable Hardware Acceleration for more power-save
|
||||
*/
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
/**
|
||||
* Shout down background process if all windows was closed
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,8 @@ import { isMacOS } from '../../utils';
|
||||
const IS_DEV: boolean =
|
||||
process.env.NODE_ENV === 'development' && !process.env.CI;
|
||||
|
||||
const DEV_TOOL = process.env.DEV_TOOL === 'true';
|
||||
|
||||
async function createWindow() {
|
||||
logger.info('create window');
|
||||
const mainWindowState = electronWindowState({
|
||||
@@ -57,7 +59,7 @@ async function createWindow() {
|
||||
|
||||
logger.info('main window is ready to show');
|
||||
|
||||
if (IS_DEV) {
|
||||
if (DEV_TOOL) {
|
||||
browserWindow.webContents.openDevTools();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.5.4-canary.15",
|
||||
"version": "0.5.4-canary.25",
|
||||
"author": "affine",
|
||||
"description": "AFFiNE App",
|
||||
"homepage": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -38,7 +38,7 @@
|
||||
"@types/better-sqlite3": "^7.6.4",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "24.1.3",
|
||||
"electron": "24.2.0",
|
||||
"electron-log": "^5.0.0-beta.23",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
@@ -47,11 +47,11 @@
|
||||
"playwright": "^1.33.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"undici": "^5.22.0",
|
||||
"zx": "^7.2.1"
|
||||
"zx": "^7.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^8.3.0",
|
||||
"yjs": "^13.6.0"
|
||||
"yjs": "^13.6.1"
|
||||
},
|
||||
"build": {
|
||||
"protocols": [
|
||||
|
||||
63
apps/electron/scripts/generate-yml.js
Normal file
63
apps/electron/scripts/generate-yml.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// do not run in your local machine
|
||||
/* eslint-disable */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
/* eslint-enable */
|
||||
|
||||
const yml = {
|
||||
version: process.env.RELEASE_VERSION ?? '0.0.0',
|
||||
files: [],
|
||||
};
|
||||
|
||||
let fileList = [];
|
||||
// TODO: maybe add `beta` and `stable`
|
||||
const BUILD_TYPE = process.env.BUILD_TYPE || 'canary';
|
||||
|
||||
const generateYml = async () => {
|
||||
fileList = [
|
||||
`affine-${BUILD_TYPE}-macos-arm64.dmg`,
|
||||
`affine-${BUILD_TYPE}-macos-arm64.zip`,
|
||||
`affine-${BUILD_TYPE}-macos-x64.zip`,
|
||||
`affine-${BUILD_TYPE}-macos-x64.dmg`,
|
||||
];
|
||||
fileList.forEach(fileName => {
|
||||
const filePath = path.join(__dirname, './', fileName);
|
||||
try {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const hash = crypto
|
||||
.createHash('sha512')
|
||||
.update(fileData)
|
||||
.digest('base64');
|
||||
const size = fs.statSync(filePath).size;
|
||||
|
||||
yml.files.push({
|
||||
url: fileName,
|
||||
sha512: hash,
|
||||
size: size,
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
yml.path = yml.files[0].url;
|
||||
yml.sha512 = yml.files[0].sha512;
|
||||
yml.releaseDate = new Date().toISOString();
|
||||
|
||||
const ymlStr =
|
||||
`version: ${yml.version}\n` +
|
||||
`files:\n` +
|
||||
yml.files
|
||||
.map(file => {
|
||||
return (
|
||||
` - url: ${file.url}\n` +
|
||||
` sha512: ${file.sha512}\n` +
|
||||
` size: ${file.size}\n`
|
||||
);
|
||||
})
|
||||
.join('') +
|
||||
`path: ${yml.path}\n` +
|
||||
`sha512: ${yml.sha512}\n` +
|
||||
`releaseDate: ${yml.releaseDate}\n`;
|
||||
|
||||
fs.writeFileSync(`./latest-mac.yml`, ymlStr);
|
||||
};
|
||||
generateYml();
|
||||
@@ -1,15 +1,36 @@
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { test } from '@affine-test/kit/playwright';
|
||||
import { test, testResultDir } from '@affine-test/kit/playwright';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
import type { ElectronApplication } from 'playwright';
|
||||
import { _electron as electron } from 'playwright';
|
||||
|
||||
test('new page', async () => {
|
||||
const electronApp = await electron.launch({
|
||||
let electronApp: ElectronApplication;
|
||||
let page: Page;
|
||||
|
||||
test.beforeEach(async () => {
|
||||
electronApp = await electron.launch({
|
||||
args: [resolve(__dirname, '..')],
|
||||
executablePath: resolve(__dirname, '../node_modules/.bin/electron'),
|
||||
colorScheme: 'light',
|
||||
});
|
||||
const page = await electronApp.firstWindow();
|
||||
page = await electronApp.firstWindow();
|
||||
await page.getByTestId('onboarding-modal-close-button').click({
|
||||
delay: 100,
|
||||
});
|
||||
// cleanup page data
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
// cleanup page data
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
await page.close();
|
||||
await electronApp.close();
|
||||
});
|
||||
|
||||
test('new page', async () => {
|
||||
await page.getByTestId('new-page-button').click({
|
||||
delay: 100,
|
||||
});
|
||||
@@ -19,5 +40,60 @@ test('new page', async () => {
|
||||
() => globalThis.currentWorkspace.flavour
|
||||
);
|
||||
expect(flavour).toBe('local');
|
||||
await electronApp.close();
|
||||
});
|
||||
|
||||
test('app theme', async () => {
|
||||
await page.waitForSelector('v-line');
|
||||
const root = page.locator('html');
|
||||
{
|
||||
const themeMode = await root.evaluate(element =>
|
||||
element.getAttribute('data-theme')
|
||||
);
|
||||
expect(themeMode).toBe('light');
|
||||
}
|
||||
await page.screenshot({
|
||||
path: resolve(testResultDir, 'affine-light-theme-electron.png'),
|
||||
});
|
||||
await page.getByTestId('editor-option-menu').click();
|
||||
await page.getByTestId('change-theme-dark').click();
|
||||
await page.waitForTimeout(50);
|
||||
{
|
||||
const themeMode = await root.evaluate(element =>
|
||||
element.getAttribute('data-theme')
|
||||
);
|
||||
expect(themeMode).toBe('dark');
|
||||
}
|
||||
await page.screenshot({
|
||||
path: resolve(testResultDir, 'affine-dark-theme-electron.png'),
|
||||
});
|
||||
});
|
||||
|
||||
test('affine cloud disabled', async () => {
|
||||
await page.getByTestId('new-page-button').click({
|
||||
delay: 100,
|
||||
});
|
||||
await page.waitForSelector('v-line');
|
||||
await page.getByTestId('current-workspace').click();
|
||||
await page.getByTestId('sign-in-button').click();
|
||||
await page.getByTestId('disable-affine-cloud-modal').waitFor({
|
||||
state: 'visible',
|
||||
});
|
||||
});
|
||||
test('affine onboarding button', async () => {
|
||||
await page.getByTestId('help-island').click();
|
||||
await page.getByTestId('easy-guide').click();
|
||||
const onboardingModal = page.locator('[data-testid=onboarding-modal]');
|
||||
expect(await onboardingModal.isVisible()).toEqual(true);
|
||||
const switchVideo = page.locator(
|
||||
'[data-testid=onboarding-modal-switch-video]'
|
||||
);
|
||||
expect(await switchVideo.isVisible()).toEqual(true);
|
||||
await page.getByTestId('onboarding-modal-next-button').click();
|
||||
const editingVideo = page.locator(
|
||||
'[data-testid=onboarding-modal-editing-video]'
|
||||
);
|
||||
expect(await editingVideo.isVisible()).toEqual(true);
|
||||
await page.getByTestId('onboarding-modal-ok-button').click();
|
||||
|
||||
expect(await onboardingModal.isVisible()).toEqual(false);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.5.4-canary.15",
|
||||
"version": "0.5.4-canary.25",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -14,7 +14,7 @@
|
||||
"postinstall": "prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.7.0",
|
||||
"@apollo/server": "^4.7.1",
|
||||
"@nestjs/apollo": "^11.0.5",
|
||||
"@nestjs/common": "^9.4.0",
|
||||
"@nestjs/core": "^9.4.0",
|
||||
@@ -36,16 +36,16 @@
|
||||
"@nestjs/testing": "^9.4.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/jsonwebtoken": "^9.0.2",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^18.16.2",
|
||||
"@types/node": "^18.16.5",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"c8": "^7.13.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4",
|
||||
"vitest": "^0.30.1"
|
||||
"vitest": "^0.31.0"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"exec": "node",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { UnauthorizedException } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import { Config, ConfigModule } from '../config';
|
||||
import { ConfigModule } from '../config';
|
||||
import { getDefaultAFFiNEConfig } from '../config/default';
|
||||
import { GqlModule } from '../graphql.module';
|
||||
import { AuthModule } from '../modules/auth';
|
||||
@@ -15,7 +15,6 @@ import { PrismaModule } from '../prisma';
|
||||
globalThis.AFFiNE = getDefaultAFFiNEConfig();
|
||||
|
||||
let auth: AuthService;
|
||||
let config: Config;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
@@ -38,7 +37,6 @@ beforeEach(async () => {
|
||||
AuthModule,
|
||||
],
|
||||
}).compile();
|
||||
config = module.get(Config);
|
||||
auth = module.get(AuthService);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ beforeEach(async () => {
|
||||
config = module.get(Config);
|
||||
});
|
||||
|
||||
test('should be able to get config', t => {
|
||||
test('should be able to get config', () => {
|
||||
ok(typeof config.host === 'string');
|
||||
equal(config.env, 'test');
|
||||
});
|
||||
|
||||
test('should be able to override config', async t => {
|
||||
test('should be able to override config', async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
|
||||
import { runCli } from '@magic-works/i18n-codegen';
|
||||
import { PerfseePlugin } from '@perfsee/webpack';
|
||||
import { withSentryConfig } from '@sentry/nextjs';
|
||||
import SentryWebpackPlugin from '@sentry/webpack-plugin';
|
||||
import debugLocal from 'next-debug-local';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { blockSuiteFeatureFlags, buildFlags } from './preset.config.mjs';
|
||||
import { getCommitHash, getGitVersion } from './scripts/gitInfo.mjs';
|
||||
@@ -17,6 +19,21 @@ const withVanillaExtract = createVanillaExtractPlugin();
|
||||
console.info('Build Flags', buildFlags);
|
||||
console.info('Editor Flags', blockSuiteFeatureFlags);
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
await runCli(
|
||||
{
|
||||
config: fileURLToPath(
|
||||
new URL('../../.i18n-codegen.json', import.meta.url)
|
||||
),
|
||||
watch: false,
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const enableDebugLocal = path.isAbsolute(process.env.LOCAL_BLOCK_SUITE ?? '');
|
||||
|
||||
if (enableDebugLocal) {
|
||||
@@ -63,6 +80,11 @@ const nextConfig = {
|
||||
removeConsole: {
|
||||
exclude: ['error', 'log', 'warn', 'info'],
|
||||
},
|
||||
reactRemoveProperties: !buildFlags.enableTestProperties
|
||||
? {
|
||||
properties: ['^data-testid$'],
|
||||
}
|
||||
: false,
|
||||
emotion: {
|
||||
sourceMap: true,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/web",
|
||||
"private": true,
|
||||
"version": "0.5.4-canary.15",
|
||||
"version": "0.5.4-canary.25",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
@@ -19,28 +19,28 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230427041825-7fff957d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230427041825-7fff957d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230427041825-7fff957d-nightly",
|
||||
"@blocksuite/icons": "^2.1.13",
|
||||
"@blocksuite/store": "0.0.0-20230427041825-7fff957d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230505225643-03f75e5e-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230505225643-03f75e5e-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230505225643-03f75e5e-nightly",
|
||||
"@blocksuite/icons": "^2.1.15",
|
||||
"@blocksuite/store": "0.0.0-20230505225643-03f75e5e-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.10.7",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/cache": "^11.10.8",
|
||||
"@emotion/react": "^11.10.8",
|
||||
"@emotion/server": "^11.10.0",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@mui/material": "^5.12.2",
|
||||
"@emotion/styled": "^11.10.8",
|
||||
"@mui/material": "^5.12.3",
|
||||
"@react-hookz/web": "^23.0.0",
|
||||
"@sentry/nextjs": "^7.50.0",
|
||||
"@sentry/nextjs": "^7.51.0",
|
||||
"@toeverything/hooks": "workspace:*",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"graphql": "^16.6.0",
|
||||
"jotai": "^2.0.4",
|
||||
"jotai-devtools": "^0.4.0",
|
||||
"lit": "^2.7.3",
|
||||
"jotai": "^2.1.0",
|
||||
"jotai-devtools": "^0.5.2",
|
||||
"lit": "^2.7.4",
|
||||
"lottie-web": "^5.11.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18.2.0",
|
||||
@@ -48,7 +48,7 @@
|
||||
"react-is": "^18.2.0",
|
||||
"swr": "^2.1.5",
|
||||
"y-protocols": "^1.0.5",
|
||||
"yjs": "^13.6.0",
|
||||
"yjs": "^13.6.1",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -56,24 +56,24 @@
|
||||
"@redux-devtools/extension": "^3.2.5",
|
||||
"@rich-data/viewer": "^2.15.6",
|
||||
"@sentry/webpack-plugin": "^1.20.1",
|
||||
"@swc-jotai/debug-label": "^0.0.9",
|
||||
"@swc-jotai/react-refresh": "^0.0.7",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.1",
|
||||
"@swc-jotai/debug-label": "^0.0.10",
|
||||
"@swc-jotai/react-refresh": "^0.0.8",
|
||||
"@types/react": "^18.2.5",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@vanilla-extract/css": "^1.11.0",
|
||||
"@vanilla-extract/next-plugin": "^2.1.2",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-next": "^13.3.1",
|
||||
"next": "=13.2.3",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-next": "^13.4.1",
|
||||
"next": "^13.4.1",
|
||||
"next-debug-local": "^0.1.5",
|
||||
"next-router-mock": "^0.9.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
"redux": "^4.2.1",
|
||||
"swc-plugin-coverage-instrument": "=0.0.14",
|
||||
"swc-plugin-coverage-instrument": "^0.0.18",
|
||||
"typescript": "^5.0.4",
|
||||
"webpack": "^5.81.0"
|
||||
"webpack": "^5.82.0"
|
||||
},
|
||||
"stableVersion": "0.0.0"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ export const blockSuiteFeatureFlags = {
|
||||
* @type {import('@affine/env').BuildFlags}
|
||||
*/
|
||||
export const buildFlags = {
|
||||
enableTestProperties: process.env.ENABLE_TEST_PROPERTIES
|
||||
? process.env.ENABLE_TEST_PROPERTIES === 'true'
|
||||
: true,
|
||||
enableLegacyCloud: process.env.ENABLE_LEGACY_PROVIDER
|
||||
? process.env.ENABLE_LEGACY_PROVIDER === 'true'
|
||||
: true,
|
||||
|
||||
BIN
apps/web/public/editingVideo.mp4
Normal file
BIN
apps/web/public/editingVideo.mp4
Normal file
Binary file not shown.
BIN
apps/web/public/switchVideo.mp4
Normal file
BIN
apps/web/public/switchVideo.mp4
Normal file
Binary file not shown.
71
apps/web/src/atoms/__tests__/atom.spec.ts
Normal file
71
apps/web/src/atoms/__tests__/atom.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { initPage } from '@affine/env/blocksuite';
|
||||
import {
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import {
|
||||
_cleanupBlockSuiteWorkspaceCache,
|
||||
createEmptyBlockSuiteWorkspace,
|
||||
} from '@affine/workspace/utils';
|
||||
import type { ParagraphBlockModel } from '@blocksuite/blocks/models';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { createStore } from 'jotai';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { WorkspacePlugins } from '../../plugins';
|
||||
import { rootCurrentWorkspaceAtom } from '../root';
|
||||
|
||||
describe('currentWorkspace atom', () => {
|
||||
test('should be defined', async () => {
|
||||
const store = createStore();
|
||||
let id: string;
|
||||
{
|
||||
const workspace = createEmptyBlockSuiteWorkspace(
|
||||
'test',
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
initPage(page);
|
||||
const frameId = page.getBlockByFlavour('affine:frame').at(0)
|
||||
?.id as string;
|
||||
id = page.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new page.Text('test 1'),
|
||||
},
|
||||
frameId
|
||||
);
|
||||
const provider = createIndexedDBDownloadProvider(workspace);
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
const workspaceId = await WorkspacePlugins[
|
||||
WorkspaceFlavour.LOCAL
|
||||
].CRUD.create(workspace);
|
||||
store.set(rootWorkspacesMetadataAtom, [
|
||||
{
|
||||
id: workspaceId,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
},
|
||||
]);
|
||||
_cleanupBlockSuiteWorkspaceCache();
|
||||
}
|
||||
store.set(
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
store.get(rootWorkspacesMetadataAtom)[0].id
|
||||
);
|
||||
const workspace = await store.get(rootCurrentWorkspaceAtom);
|
||||
expect(workspace).toBeDefined();
|
||||
const page = workspace.blockSuiteWorkspace.getPage('page0') as Page;
|
||||
expect(page).not.toBeNull();
|
||||
const paragraphBlock = page.getBlockById(id) as ParagraphBlockModel;
|
||||
expect(paragraphBlock).not.toBeNull();
|
||||
expect(paragraphBlock.text.toString()).toBe('test 1');
|
||||
});
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
|
||||
export type Visibility = Record<string, boolean>;
|
||||
|
||||
const DEFAULT_VALUE = '0.0.0';
|
||||
|
||||
export const lastVersionAtom = atomWithStorage('lastVersion', DEFAULT_VALUE);
|
||||
|
||||
export const guideHiddenAtom = atomWithStorage<Visibility>('guideHidden', {});
|
||||
|
||||
export const guideHiddenUntilNextUpdateAtom = atomWithStorage<Visibility>(
|
||||
'guideHiddenUntilNextUpdate',
|
||||
{}
|
||||
);
|
||||
69
apps/web/src/atoms/guide.ts
Normal file
69
apps/web/src/atoms/guide.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
// these atoms cannot be moved to @affine/jotai since they use atoms from @affine/component
|
||||
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
|
||||
import { atom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
|
||||
export type Guide = {
|
||||
// should show quick search tips
|
||||
quickSearchTips: boolean;
|
||||
// should show change log
|
||||
changeLog: boolean;
|
||||
// should show recording tips
|
||||
onBoarding: boolean;
|
||||
};
|
||||
|
||||
const guidePrimitiveAtom = atomWithStorage<Guide>('helper-guide', {
|
||||
quickSearchTips: true,
|
||||
changeLog: true,
|
||||
onBoarding: true,
|
||||
});
|
||||
|
||||
export const guideQuickSearchTipsAtom = atom<
|
||||
Guide['quickSearchTips'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
const open = get(appSidebarOpenAtom);
|
||||
const guide = get(guidePrimitiveAtom);
|
||||
// only show the tips when the sidebar is closed
|
||||
return guide.quickSearchTips && open === false;
|
||||
},
|
||||
(_, set, open) => {
|
||||
set(guidePrimitiveAtom, tips => ({
|
||||
...tips,
|
||||
quickSearchTips: open,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
export const guideChangeLogAtom = atom<
|
||||
Guide['changeLog'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
return get(guidePrimitiveAtom).changeLog;
|
||||
},
|
||||
(_, set, open) => {
|
||||
set(guidePrimitiveAtom, tips => ({
|
||||
...tips,
|
||||
changeLog: open,
|
||||
}));
|
||||
}
|
||||
);
|
||||
export const guideOnboardingAtom = atom<
|
||||
Guide['onBoarding'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
return get(guidePrimitiveAtom).onBoarding;
|
||||
},
|
||||
(_, set, open) => {
|
||||
set(guidePrimitiveAtom, tips => ({
|
||||
...tips,
|
||||
onBoarding: open,
|
||||
}));
|
||||
}
|
||||
);
|
||||
@@ -77,6 +77,9 @@ export const currentEditorAtom = rootCurrentEditorAtom;
|
||||
export const openWorkspacesModalAtom = atom(false);
|
||||
export const openCreateWorkspaceModalAtom = atom(false);
|
||||
export const openQuickSearchModalAtom = atom(false);
|
||||
export const openOnboardingModalAtom = atom(false);
|
||||
|
||||
export const openDisableCloudAlertModalAtom = atom(false);
|
||||
|
||||
export { workspacesAtom } from './root';
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ import {
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import type {
|
||||
NecessaryProvider,
|
||||
WorkspaceRegistry,
|
||||
} from '@affine/workspace/type';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { atom } from 'jotai';
|
||||
@@ -37,18 +41,38 @@ export const workspacesAtom = atom<Promise<AllWorkspace[]>>(async get => {
|
||||
WorkspacePlugins[workspace.flavour as keyof typeof WorkspacePlugins];
|
||||
assertExists(plugin);
|
||||
const { CRUD } = plugin;
|
||||
return CRUD.get(workspace.id);
|
||||
return CRUD.get(workspace.id).then(workspace => {
|
||||
if (workspace === null) {
|
||||
console.warn(
|
||||
'workspace is null. this should not happen. If you see this error, please report it to the developer.'
|
||||
);
|
||||
}
|
||||
return workspace;
|
||||
});
|
||||
})
|
||||
).then(workspaces =>
|
||||
workspaces.filter(
|
||||
(workspace): workspace is WorkspaceRegistry['affine' | 'local'] =>
|
||||
workspace !== null
|
||||
)
|
||||
);
|
||||
logger.info('workspaces', workspaces);
|
||||
workspaces.forEach(workspace => {
|
||||
if (workspace === null) {
|
||||
console.warn(
|
||||
'workspace is null. this should not happen. If you see this error, please report it to the developer.'
|
||||
);
|
||||
const workspaceProviders = workspaces.map(workspace =>
|
||||
workspace.providers.filter(
|
||||
(provider): provider is NecessaryProvider =>
|
||||
'necessary' in provider && provider.necessary
|
||||
)
|
||||
);
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const providers of workspaceProviders) {
|
||||
for (const provider of providers) {
|
||||
provider.sync();
|
||||
promises.push(provider.whenReady);
|
||||
}
|
||||
});
|
||||
return workspaces.filter(workspace => workspace !== null) as AllWorkspace[];
|
||||
}
|
||||
// we will wait for all the necessary providers to be ready
|
||||
await Promise.all(promises);
|
||||
logger.info('workspaces', workspaces);
|
||||
return workspaces;
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -77,6 +101,15 @@ export const rootCurrentWorkspaceAtom = atom<Promise<AllWorkspace>>(
|
||||
`cannot find the workspace with id ${targetId} in the plugin ${targetWorkspace.flavour}.`
|
||||
);
|
||||
}
|
||||
const providers = workspace.providers.filter(
|
||||
(provider): provider is NecessaryProvider =>
|
||||
'necessary' in provider && provider.necessary === true
|
||||
);
|
||||
for (const provider of providers) {
|
||||
provider.sync();
|
||||
// we will wait for the necessary providers to be ready
|
||||
await provider.whenReady;
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { BlockSuiteWorkspace } from '../../shared';
|
||||
import { createAffineProviders, createLocalProviders } from '..';
|
||||
|
||||
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
|
||||
beforeEach(() => {
|
||||
blockSuiteWorkspace = new BlockSuiteWorkspace({ id: 'test' });
|
||||
});
|
||||
|
||||
describe('blocksuite providers', () => {
|
||||
test('should be valid provider', () => {
|
||||
[createLocalProviders, createAffineProviders].forEach(createProviders => {
|
||||
createProviders(blockSuiteWorkspace).forEach(provider => {
|
||||
expect(provider).toBeTypeOf('object');
|
||||
expect(provider).toHaveProperty('flavour');
|
||||
expect(provider).toHaveProperty('connect');
|
||||
expect(provider.connect).toBeTypeOf('function');
|
||||
expect(provider).toHaveProperty('disconnect');
|
||||
expect(provider.disconnect).toBeTypeOf('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,15 @@
|
||||
import { config } from '@affine/env';
|
||||
import {
|
||||
createIndexedDBProvider,
|
||||
createIndexedDBDownloadProvider,
|
||||
createLocalProviders,
|
||||
} from '@affine/workspace/providers';
|
||||
import { createBroadCastChannelProvider } from '@affine/workspace/providers';
|
||||
import {
|
||||
createAffineWebSocketProvider,
|
||||
createBroadCastChannelProvider,
|
||||
} from '@affine/workspace/providers';
|
||||
import type { Provider } from '@affine/workspace/type';
|
||||
|
||||
import type { BlockSuiteWorkspace } from '../shared';
|
||||
import { createAffineWebSocketProvider } from './providers';
|
||||
import { createAffineDownloadProvider } from './providers/affine';
|
||||
|
||||
export const createAffineProviders = (
|
||||
@@ -19,7 +21,7 @@ export const createAffineProviders = (
|
||||
createAffineWebSocketProvider(blockSuiteWorkspace),
|
||||
config.enableBroadCastChannelProvider &&
|
||||
createBroadCastChannelProvider(blockSuiteWorkspace),
|
||||
createIndexedDBProvider(blockSuiteWorkspace),
|
||||
createIndexedDBDownloadProvider(blockSuiteWorkspace),
|
||||
] as any[]
|
||||
).filter(v => Boolean(v));
|
||||
};
|
||||
|
||||
@@ -12,9 +12,15 @@ export const createAffineDownloadProvider = (
|
||||
): AffineDownloadProvider => {
|
||||
assertExists(blockSuiteWorkspace.id);
|
||||
const id = blockSuiteWorkspace.id;
|
||||
let connected = false;
|
||||
const callbacks = new Set<() => void>();
|
||||
return {
|
||||
flavour: 'affine-download',
|
||||
background: true,
|
||||
get connected() {
|
||||
return connected;
|
||||
},
|
||||
callbacks,
|
||||
connect: () => {
|
||||
providerLogger.info('connect download provider', id);
|
||||
if (hashMap.has(id)) {
|
||||
@@ -23,6 +29,7 @@ export const createAffineDownloadProvider = (
|
||||
blockSuiteWorkspace.doc,
|
||||
new Uint8Array(hashMap.get(id) as ArrayBuffer)
|
||||
);
|
||||
connected = true;
|
||||
return;
|
||||
}
|
||||
affineApis
|
||||
@@ -41,6 +48,7 @@ export const createAffineDownloadProvider = (
|
||||
},
|
||||
disconnect: () => {
|
||||
providerLogger.info('disconnect download provider', id);
|
||||
connected = false;
|
||||
},
|
||||
cleanup: () => {
|
||||
hashMap.delete(id);
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { websocketPrefixUrl } from '@affine/env';
|
||||
import { KeckProvider } from '@affine/workspace/affine/keck';
|
||||
import { getLoginStorage } from '@affine/workspace/affine/login';
|
||||
import type { AffineWebSocketProvider } from '@affine/workspace/type';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
|
||||
import type { BlockSuiteWorkspace } from '../../shared';
|
||||
import { providerLogger } from '../logger';
|
||||
|
||||
const createAffineWebSocketProvider = (
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||
): AffineWebSocketProvider => {
|
||||
let webSocketProvider: KeckProvider | null = null;
|
||||
return {
|
||||
flavour: 'affine-websocket',
|
||||
background: false,
|
||||
cleanup: () => {
|
||||
assertExists(webSocketProvider);
|
||||
webSocketProvider.destroy();
|
||||
webSocketProvider = null;
|
||||
},
|
||||
connect: () => {
|
||||
webSocketProvider = new KeckProvider(
|
||||
websocketPrefixUrl + '/api/sync/',
|
||||
blockSuiteWorkspace.id,
|
||||
blockSuiteWorkspace.doc,
|
||||
{
|
||||
params: { token: getLoginStorage()?.token ?? '' },
|
||||
awareness: blockSuiteWorkspace.awarenessStore.awareness,
|
||||
// we maintain broadcast channel by ourselves
|
||||
// @ts-expect-error
|
||||
disableBc: true,
|
||||
connect: false,
|
||||
}
|
||||
);
|
||||
providerLogger.info('connect', webSocketProvider.url);
|
||||
webSocketProvider.connect();
|
||||
},
|
||||
disconnect: () => {
|
||||
assertExists(webSocketProvider);
|
||||
providerLogger.info('disconnect', webSocketProvider.url);
|
||||
webSocketProvider.destroy();
|
||||
webSocketProvider = null;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export { createAffineWebSocketProvider };
|
||||
@@ -16,7 +16,7 @@ const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
}
|
||||
);
|
||||
|
||||
const page = blockSuiteWorkspace.createPage('page0');
|
||||
const page = blockSuiteWorkspace.createPage({ id: 'page0' });
|
||||
|
||||
const Editor: React.FC<{
|
||||
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconButton, Modal, ModalWrapper } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
|
||||
@@ -17,7 +17,7 @@ export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const user = useCurrentUser();
|
||||
|
||||
return (
|
||||
@@ -33,8 +33,8 @@ export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('Enable AFFiNE Cloud')}?</ContentTitle>
|
||||
<StyleTips>{t('Enable AFFiNE Cloud Description')}</StyleTips>
|
||||
<ContentTitle>{t['Enable AFFiNE Cloud']()}?</ContentTitle>
|
||||
<StyleTips>{t['Enable AFFiNE Cloud Description']()}</StyleTips>
|
||||
{/* <StyleTips>{t('Retain cached cloud data')}</StyleTips> */}
|
||||
<div>
|
||||
<StyleButton
|
||||
@@ -45,7 +45,7 @@ export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
|
||||
onConfirm();
|
||||
}}
|
||||
>
|
||||
{user ? t('Enable') : t('Sign in and Enable')}
|
||||
{user ? t.Enable() : t['Sign in and Enable']()}
|
||||
</StyleButton>
|
||||
<StyleButton
|
||||
shape="round"
|
||||
@@ -53,7 +53,7 @@ export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('Not now')}
|
||||
{t['Not now']()}
|
||||
</StyleButton>
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { MenuItem } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { ArrowRightSmallIcon, MoveToIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { PinboardMenu } from '../pinboard';
|
||||
import type { CommonMenuItemProps } from './types';
|
||||
|
||||
export type MoveToProps = CommonMenuItemProps<{
|
||||
dragId: string;
|
||||
dropId: string;
|
||||
}> & {
|
||||
metas: PageMeta[];
|
||||
currentMeta: PageMeta;
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const MoveTo = ({
|
||||
metas,
|
||||
currentMeta,
|
||||
blockSuiteWorkspace,
|
||||
onSelect,
|
||||
onItemClick,
|
||||
}: MoveToProps) => {
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||
const open = anchorEl !== null;
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
ref={ref}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setAnchorEl(ref.current);
|
||||
onItemClick?.();
|
||||
}}
|
||||
icon={<MoveToIcon />}
|
||||
endIcon={<ArrowRightSmallIcon />}
|
||||
data-testid="move-to-menu-item"
|
||||
>
|
||||
{t('Move to')}
|
||||
</MenuItem>
|
||||
<PinboardMenu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
// placement="left-start"
|
||||
metas={metas}
|
||||
currentMeta={currentMeta}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onPinboardClick={onSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FlexWrapper } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useAtomValue } from 'jotai';
|
||||
@@ -16,14 +16,14 @@ export const SearchContent = ({
|
||||
results: PageMeta[];
|
||||
onClick?: (dropId: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const record = useAtomValue(workspacePreferredModeAtom);
|
||||
|
||||
if (results.length) {
|
||||
return (
|
||||
<>
|
||||
<StyledMenuSubTitle>
|
||||
{t('Find results', { number: results.length })}
|
||||
{t['Find results']({ number: `${results.length}` })}
|
||||
</StyledMenuSubTitle>
|
||||
{results.map(meta => {
|
||||
return (
|
||||
@@ -45,7 +45,7 @@ export const SearchContent = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledMenuSubTitle>{t('Find 0 result')}</StyledMenuSubTitle>
|
||||
<StyledMenuSubTitle>{t['Find 0 result']()}</StyledMenuSubTitle>
|
||||
<FlexWrapper
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PureMenuProps } from '@affine/component';
|
||||
import { Input, PureMenu, TreeView } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { RemoveIcon, SearchIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
@@ -40,7 +40,7 @@ export const PinboardMenu = ({
|
||||
() => propsMetas.filter(m => m.id !== currentMeta.id),
|
||||
[currentMeta.id, propsMetas]
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const [query, setQuery] = useState('');
|
||||
const isSearching = query.length > 0;
|
||||
|
||||
@@ -92,7 +92,7 @@ export const PinboardMenu = ({
|
||||
<Input
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
placeholder={t('Move page to...')}
|
||||
placeholder={t['Move page to...']()}
|
||||
height={32}
|
||||
noBorder={true}
|
||||
onClick={e => e.stopPropagation()}
|
||||
@@ -121,9 +121,9 @@ export const PinboardMenu = ({
|
||||
}}
|
||||
>
|
||||
<RemoveIcon />
|
||||
{t('Remove from Pinboard')}
|
||||
{t['Remove from Pivots']()}
|
||||
</StyledPinboard>
|
||||
<p>{t('RFP')}</p>
|
||||
<p>{t['RFP']()}</p>
|
||||
</StyledMenuFooter>
|
||||
)}
|
||||
</PureMenu>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import { StyledPinboard } from '../styles';
|
||||
|
||||
export const EmptyItem = () => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<StyledPinboard disable={true} textWrap={true}>
|
||||
{t('Organize pages to build knowledge')}
|
||||
{t['Organize pages to build knowledge']()}
|
||||
</StyledPinboard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MenuItem, MuiClickAwayListener, PureMenu } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { CopyLink, MoveToTrash } from '@affine/component/page-list';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
MoreVerticalIcon,
|
||||
MoveToIcon,
|
||||
@@ -13,7 +14,6 @@ import { useMemo, useRef, useState } from 'react';
|
||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
||||
import { toast } from '../../../../utils';
|
||||
import { CopyLink, MoveToTrash } from '../../operation-menu-items';
|
||||
import { PinboardMenu } from '../pinboard-menu/';
|
||||
import { StyledOperationButton } from '../styles';
|
||||
|
||||
@@ -39,7 +39,7 @@ export const OperationButton = ({
|
||||
onMenuClose,
|
||||
onRename,
|
||||
}: OperationButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const timer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
@@ -88,7 +88,7 @@ export const OperationButton = ({
|
||||
width={256}
|
||||
anchorEl={anchorEl}
|
||||
open={operationMenuOpen}
|
||||
// placement="bottom-start"
|
||||
placement="bottom"
|
||||
zIndex={menuIndex}
|
||||
>
|
||||
<MenuItem
|
||||
@@ -100,7 +100,7 @@ export const OperationButton = ({
|
||||
}}
|
||||
icon={<PlusIcon />}
|
||||
>
|
||||
{t('Add a subpage inside')}
|
||||
{t['Add a subpage inside']()}
|
||||
</MenuItem>
|
||||
{!isRoot && (
|
||||
<MenuItem
|
||||
@@ -111,7 +111,7 @@ export const OperationButton = ({
|
||||
}}
|
||||
icon={<MoveToIcon />}
|
||||
>
|
||||
{t('Move to')}
|
||||
{t['Move to']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!isRoot && (
|
||||
@@ -124,7 +124,7 @@ export const OperationButton = ({
|
||||
}}
|
||||
icon={<PenIcon />}
|
||||
>
|
||||
{t('Rename')}
|
||||
{t['Rename']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!isRoot && (
|
||||
@@ -143,7 +143,7 @@ export const OperationButton = ({
|
||||
<PinboardMenu
|
||||
anchorEl={anchorEl}
|
||||
open={pinboardMenuOpen}
|
||||
// placement="bottom-start"
|
||||
placement="bottom"
|
||||
zIndex={menuIndex}
|
||||
metas={metas}
|
||||
currentMeta={currentMeta}
|
||||
@@ -152,9 +152,9 @@ export const OperationButton = ({
|
||||
/>
|
||||
<MoveToTrash.ConfirmModal
|
||||
open={confirmModalOpen}
|
||||
meta={currentMeta}
|
||||
title={currentMeta.title}
|
||||
onConfirm={() => {
|
||||
toast(t('Moved to Trash'));
|
||||
toast(t['Moved to Trash']());
|
||||
removeToTrash(currentMeta.id);
|
||||
onDelete();
|
||||
}}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { Tooltip } from '@affine/component';
|
||||
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
|
||||
import { getEnvironment } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
useGuideHidden,
|
||||
useGuideHiddenUntilNextUpdate,
|
||||
useUpdateTipsOnVersionChange,
|
||||
} from '../../../hooks/use-is-first-load';
|
||||
import { SidebarSwitchIcon } from './icons';
|
||||
import { StyledSidebarSwitch } from './style';
|
||||
type SidebarSwitchProps = {
|
||||
@@ -18,19 +13,15 @@ type SidebarSwitchProps = {
|
||||
};
|
||||
|
||||
// fixme: the following code is not correct, SSR will fail because hydrate will not match the client side render
|
||||
// in `StyledSidebarSwitch` component
|
||||
// in `StyledSidebarSwitch` a component
|
||||
export const SidebarSwitch = ({
|
||||
visible = true,
|
||||
tooltipContent,
|
||||
...props
|
||||
}: SidebarSwitchProps) => {
|
||||
useUpdateTipsOnVersionChange();
|
||||
const [open, setOpen] = useAtom(appSidebarOpenAtom);
|
||||
const [tooltipVisible, setTooltipVisible] = useState(false);
|
||||
const [guideHidden, setGuideHidden] = useGuideHidden();
|
||||
const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] =
|
||||
useGuideHiddenUntilNextUpdate();
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const checkIsMac = () => {
|
||||
const env = getEnvironment();
|
||||
return env.isBrowser && env.isMacOs;
|
||||
@@ -43,7 +34,7 @@ export const SidebarSwitch = ({
|
||||
}, []);
|
||||
|
||||
tooltipContent =
|
||||
tooltipContent || (open ? t('Collapse sidebar') : t('Expand sidebar'));
|
||||
tooltipContent || (open ? t['Collapse sidebar']() : t['Expand sidebar']());
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@@ -59,22 +50,7 @@ export const SidebarSwitch = ({
|
||||
onClick={useCallback(() => {
|
||||
setOpen(open => !open);
|
||||
setTooltipVisible(false);
|
||||
if (!guideHiddenUntilNextUpdate['quickSearchTips']) {
|
||||
setGuideHiddenUntilNextUpdate({
|
||||
...guideHiddenUntilNextUpdate,
|
||||
quickSearchTips: true,
|
||||
});
|
||||
setTimeout(() => {
|
||||
setGuideHidden({ ...guideHidden, quickSearchTips: false });
|
||||
}, 200);
|
||||
}
|
||||
}, [
|
||||
guideHidden,
|
||||
guideHiddenUntilNextUpdate,
|
||||
setGuideHidden,
|
||||
setGuideHiddenUntilNextUpdate,
|
||||
setOpen,
|
||||
])}
|
||||
}, [setOpen])}
|
||||
onMouseEnter={useCallback(() => {
|
||||
setTooltipVisible(true);
|
||||
}, [])}
|
||||
|
||||
@@ -21,7 +21,11 @@ export const TmpDisableAffineCloudModal: React.FC<
|
||||
TmpDisableAffineCloudModalProps
|
||||
> = ({ open, onClose }) => {
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Modal
|
||||
data-testid="disable-affine-cloud-modal"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalWrapper width={480}>
|
||||
<Header>
|
||||
<IconButton
|
||||
@@ -41,6 +45,9 @@ export const TmpDisableAffineCloudModal: React.FC<
|
||||
<a
|
||||
href="https://github.com/toeverything/AFFiNE/releases"
|
||||
target="_blank"
|
||||
style={{
|
||||
color: 'var(--affine-link-color)',
|
||||
}}
|
||||
>
|
||||
click here
|
||||
</a>
|
||||
|
||||
@@ -13,7 +13,7 @@ export const Content = styled('div')({
|
||||
padding: '0 40px',
|
||||
});
|
||||
|
||||
export const ContentTitle = styled('h1')(({ theme }) => {
|
||||
export const ContentTitle = styled('h1')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
lineHeight: '28px',
|
||||
@@ -21,7 +21,7 @@ export const ContentTitle = styled('h1')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleTips = styled('div')(({ theme }) => {
|
||||
export const StyleTips = styled('div')(() => {
|
||||
return {
|
||||
userSelect: 'none',
|
||||
margin: '20px 0',
|
||||
@@ -31,7 +31,7 @@ export const StyleTips = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleButton = styled(Button)(({ theme }) => {
|
||||
export const StyleButton = styled(Button)(() => {
|
||||
return {
|
||||
textAlign: 'center',
|
||||
margin: '20px 0',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconButton, Modal, ModalWrapper } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
|
||||
@@ -15,7 +15,7 @@ export type TransformWorkspaceToAffineModalProps = {
|
||||
export const TransformWorkspaceToAffineModal: React.FC<
|
||||
TransformWorkspaceToAffineModalProps
|
||||
> = ({ open, onClose, onConform }) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const user = useCurrentUser();
|
||||
|
||||
return (
|
||||
@@ -35,8 +35,8 @@ export const TransformWorkspaceToAffineModal: React.FC<
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('Enable AFFiNE Cloud')}?</ContentTitle>
|
||||
<StyleTips>{t('Enable AFFiNE Cloud Description')}</StyleTips>
|
||||
<ContentTitle>{t['Enable AFFiNE Cloud']()}?</ContentTitle>
|
||||
<StyleTips>{t['Enable AFFiNE Cloud Description']()}</StyleTips>
|
||||
{/* <StyleTips>{t('Retain cached cloud data')}</StyleTips> */}
|
||||
<div>
|
||||
<StyleButton
|
||||
@@ -61,7 +61,7 @@ export const TransformWorkspaceToAffineModal: React.FC<
|
||||
// setLoading(false);
|
||||
}}
|
||||
>
|
||||
{user ? t('Enable') : t('Sign in and Enable')}
|
||||
{user ? t['Enable']() : t['Sign in and Enable']()}
|
||||
</StyleButton>
|
||||
<StyleButton
|
||||
shape="round"
|
||||
@@ -69,7 +69,7 @@ export const TransformWorkspaceToAffineModal: React.FC<
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('Not now')}
|
||||
{t['Not now']()}
|
||||
</StyleButton>
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { SettingPanel, WorkspaceRegistry } from '@affine/workspace/type';
|
||||
import { settingPanel, WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import type { MouseEvent } from 'react';
|
||||
@@ -39,6 +39,7 @@ export type WorkspaceSettingDetailProps = {
|
||||
|
||||
export type PanelProps = WorkspaceSettingDetailProps;
|
||||
|
||||
type Name = 'General' | 'Sync' | 'Collaboration' | 'Publish' | 'Export';
|
||||
const panelMap = {
|
||||
[settingPanel.General]: {
|
||||
name: 'General',
|
||||
@@ -63,7 +64,7 @@ const panelMap = {
|
||||
},
|
||||
} satisfies {
|
||||
[Key in SettingPanel]: {
|
||||
name: string;
|
||||
name: Name;
|
||||
enable?: (flavour: WorkspaceFlavour) => boolean;
|
||||
ui: React.FC<PanelProps>;
|
||||
};
|
||||
@@ -96,7 +97,7 @@ export const WorkspaceSettingDetail: React.FC<
|
||||
if (!(currentTab in panelMap)) {
|
||||
throw new Error('Invalid activeTab: ' + currentTab);
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const workspaceId = workspace.id;
|
||||
useEffect(() => {
|
||||
if (isAffine && isOwner) {
|
||||
@@ -148,7 +149,7 @@ export const WorkspaceSettingDetail: React.FC<
|
||||
data-tab-key={key}
|
||||
onClick={handleTabClick}
|
||||
>
|
||||
{t(value.name)}
|
||||
{t[value.name]()}
|
||||
</WorkspaceSettingTagItem>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button, IconButton, Menu, MenuItem, Wrapper } from '@affine/component';
|
||||
import { config } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { PermissionType } from '@affine/workspace/affine/api';
|
||||
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
@@ -41,7 +41,7 @@ const AffineRemoteCollaborationPanel: React.FC<
|
||||
}
|
||||
> = ({ workspace }) => {
|
||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const { members, removeMember } = useMembers(workspace.id);
|
||||
return (
|
||||
<>
|
||||
@@ -49,11 +49,11 @@ const AffineRemoteCollaborationPanel: React.FC<
|
||||
<ul>
|
||||
<StyledMemberTitleContainer>
|
||||
<StyledMemberNameContainer>
|
||||
{t('Users')} (
|
||||
{t['Users']()} (
|
||||
<span data-testid="member-length">{members.length}</span>)
|
||||
</StyledMemberNameContainer>
|
||||
<StyledMemberRoleContainer>
|
||||
{t('Access level')}
|
||||
{t['Access level']()}
|
||||
</StyledMemberRoleContainer>
|
||||
<div style={{ width: '24px', paddingRight: '48px' }}></div>
|
||||
</StyledMemberTitleContainer>
|
||||
@@ -91,9 +91,9 @@ const AffineRemoteCollaborationPanel: React.FC<
|
||||
<StyledMemberRoleContainer>
|
||||
{member.accepted
|
||||
? member.type !== PermissionType.Owner
|
||||
? t('Member')
|
||||
: t('Owner')
|
||||
: t('Pending')}
|
||||
? t['Member']()
|
||||
: t['Owner']()
|
||||
: t['Pending']()}
|
||||
</StyledMemberRoleContainer>
|
||||
{member.type === PermissionType.Owner ? (
|
||||
<StyledMoreVerticalDiv />
|
||||
@@ -109,18 +109,18 @@ const AffineRemoteCollaborationPanel: React.FC<
|
||||
// @ts-ignore
|
||||
await removeMember(member.id);
|
||||
toast(
|
||||
t('Member has been removed', {
|
||||
t['Member has been removed']({
|
||||
name: user.name,
|
||||
})
|
||||
);
|
||||
}}
|
||||
icon={<DeleteTemporarilyIcon />}
|
||||
>
|
||||
{t('Remove from workspace')}
|
||||
{t['Remove from workspace']()}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
// placement="bottom-end"
|
||||
placement="bottom"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
@@ -145,7 +145,7 @@ const AffineRemoteCollaborationPanel: React.FC<
|
||||
data-testid="invite-members"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Invite Members')}
|
||||
{t['Invite Members']()}
|
||||
</Button>
|
||||
</StyledMemberButtonContainer>
|
||||
</StyledMemberContainer>
|
||||
@@ -168,11 +168,11 @@ const LocalCollaborationPanel: React.FC<
|
||||
workspace: LocalWorkspace;
|
||||
}
|
||||
> = ({ workspace, onTransferWorkspace }) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="42px">{t('Collaboration Description')}</Wrapper>
|
||||
<Wrapper marginBottom="42px">{t['Collaboration Description']()}</Wrapper>
|
||||
|
||||
<Button
|
||||
data-testid="local-workspace-enable-cloud-button"
|
||||
@@ -182,7 +182,7 @@ const LocalCollaborationPanel: React.FC<
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Enable AFFiNE Cloud')}
|
||||
{t['Enable AFFiNE Cloud']()}
|
||||
</Button>
|
||||
{config.enableLegacyCloud ? (
|
||||
<TransformWorkspaceToAffineModal
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
MuiAvatar,
|
||||
styled,
|
||||
} from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EmailIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
import { Suspense, useCallback, useState } from 'react';
|
||||
@@ -60,7 +60,7 @@ export const InviteMemberModal = ({
|
||||
const { inviteMember } = useMembers(workspaceId);
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [showMemberPreview, setShowMemberPreview] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const inputChange = useCallback((value: string) => {
|
||||
setEmail(value);
|
||||
}, []);
|
||||
@@ -77,7 +77,7 @@ export const InviteMemberModal = ({
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('Invite Members')}</ContentTitle>
|
||||
<ContentTitle>{t['Invite Members']()}</ContentTitle>
|
||||
<InviteBox>
|
||||
<Input
|
||||
data-testid="invite-member-input"
|
||||
@@ -90,7 +90,7 @@ export const InviteMemberModal = ({
|
||||
onBlur={useCallback(() => {
|
||||
setShowMemberPreview(false);
|
||||
}, [])}
|
||||
placeholder={t('Invite placeholder')}
|
||||
placeholder={t['Invite placeholder']()}
|
||||
/>
|
||||
{showMemberPreview && gmailReg.test(email) && (
|
||||
<Suspense fallback="loading...">
|
||||
@@ -112,7 +112,7 @@ export const InviteMemberModal = ({
|
||||
onInviteSuccess();
|
||||
}}
|
||||
>
|
||||
{t('Invite')}
|
||||
{t['Invite']()}
|
||||
</Button>
|
||||
</Footer>
|
||||
</ModalWrapper>
|
||||
@@ -150,7 +150,7 @@ const InviteBox = styled('div')({
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
const Members = styled('div')(({ theme }) => {
|
||||
const Members = styled('div')(() => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
@@ -178,7 +178,7 @@ const Members = styled('div')(({ theme }) => {
|
||||
// };
|
||||
// });
|
||||
|
||||
const Member = styled('div')(({ theme }) => {
|
||||
const Member = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
@@ -188,7 +188,7 @@ const Member = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
const MemberIcon = styled('div')(({ theme }) => {
|
||||
const MemberIcon = styled('div')(() => {
|
||||
return {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
|
||||
@@ -61,7 +61,7 @@ export const StyledMemberInfo = styled('div')(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledMemberName = styled('div')(({ theme }) => {
|
||||
export const StyledMemberName = styled('div')(() => {
|
||||
return {
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
@@ -70,7 +70,7 @@ export const StyledMemberName = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledMemberEmail = styled('div')(({ theme }) => {
|
||||
export const StyledMemberEmail = styled('div')(() => {
|
||||
return {
|
||||
fontWeight: '400',
|
||||
fontSize: '16px',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Button, Wrapper } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
export const ExportPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="42px"> {t('Export Description')}</Wrapper>
|
||||
<Wrapper marginBottom="42px"> {t['Export Description']()}</Wrapper>
|
||||
<Button
|
||||
type="light"
|
||||
shape="circle"
|
||||
@@ -14,7 +14,7 @@ export const ExportPanel = () => {
|
||||
window.apis.openSaveDBFileDialog();
|
||||
}}
|
||||
>
|
||||
{t('Export AFFiNE backup file')}
|
||||
{t['Export AFFiNE backup file']()}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, Input, Modal, ModalCloseButton } from '@affine/component';
|
||||
import { Trans, useTranslation } from '@affine/i18n';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useCallback, useState } from 'react';
|
||||
@@ -33,11 +34,11 @@ export const WorkspaceDeleteModal = ({
|
||||
);
|
||||
const [deleteStr, setDeleteStr] = useState<string>('');
|
||||
const allowDelete = deleteStr === workspaceName;
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
onDeleteWorkspace().then(() => {
|
||||
toast(t('Successfully deleted'), {
|
||||
toast(t['Successfully deleted'](), {
|
||||
portal: document.body,
|
||||
});
|
||||
});
|
||||
@@ -47,7 +48,7 @@ export const WorkspaceDeleteModal = ({
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<StyledModalHeader>{t('Delete Workspace')}?</StyledModalHeader>
|
||||
<StyledModalHeader>{t['Delete Workspace']()}?</StyledModalHeader>
|
||||
{workspace.flavour === WorkspaceFlavour.LOCAL ? (
|
||||
<StyledTextContent>
|
||||
<Trans i18nKey="Delete Workspace Description">
|
||||
@@ -80,7 +81,7 @@ export const WorkspaceDeleteModal = ({
|
||||
}}
|
||||
onChange={setDeleteStr}
|
||||
data-testid="delete-workspace-input"
|
||||
placeholder={t('Placeholder of delete workspace')}
|
||||
placeholder={t['Placeholder of delete workspace']()}
|
||||
value={deleteStr}
|
||||
width={315}
|
||||
height={42}
|
||||
@@ -88,7 +89,7 @@ export const WorkspaceDeleteModal = ({
|
||||
</StyledInputContent>
|
||||
<StyledButtonContent>
|
||||
<Button shape="circle" onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="delete-workspace-confirm-button"
|
||||
@@ -98,7 +99,7 @@ export const WorkspaceDeleteModal = ({
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t('Delete')}
|
||||
{t['Delete']()}
|
||||
</Button>
|
||||
</StyledButtonContent>
|
||||
</StyledModalWrapper>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
export const StyledModalWrapper = styled('div')(({ theme }) => {
|
||||
export const StyledModalWrapper = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
padding: '0px',
|
||||
@@ -36,7 +36,7 @@ export const StyledTextContent = styled('div')(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledInputContent = styled('div')(({ theme }) => {
|
||||
export const StyledInputContent = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button, FlexWrapper, MuiFade } from '@affine/component';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
@@ -38,7 +38,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
const [input, setInput] = useState<string>(name);
|
||||
const isOwner = useIsWorkspaceOwner(workspace);
|
||||
const [showEditInput, setShowEditInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const handleUpdateWorkspaceName = (name: string) => {
|
||||
setName(name);
|
||||
@@ -50,7 +50,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
return (
|
||||
<>
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Avatar')}</StyledSettingKey>
|
||||
<StyledSettingKey>{t['Workspace Avatar']()}</StyledSettingKey>
|
||||
<StyledAvatar disabled={!isOwner}>
|
||||
{isOwner ? (
|
||||
<Upload
|
||||
@@ -72,7 +72,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
</StyledRow>
|
||||
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Name')}</StyledSettingKey>
|
||||
<StyledSettingKey>{t['Workspace Name']()}</StyledSettingKey>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
<MuiFade in={!showEditInput}>
|
||||
@@ -84,7 +84,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
setShowEditInput(true);
|
||||
}}
|
||||
>
|
||||
{t('Edit')}
|
||||
{t['Edit']()}
|
||||
</StyledEditButton>
|
||||
)}
|
||||
</FlexWrapper>
|
||||
@@ -97,7 +97,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
width={284}
|
||||
height={38}
|
||||
value={input}
|
||||
placeholder={t('Workspace Name')}
|
||||
placeholder={t['Workspace Name']()}
|
||||
maxLength={50}
|
||||
minLength={0}
|
||||
onChange={newName => {
|
||||
@@ -114,7 +114,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
setShowEditInput(false);
|
||||
}}
|
||||
>
|
||||
{t('Confirm')}
|
||||
{t['Confirm']()}
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
@@ -125,7 +125,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
setShowEditInput(false);
|
||||
}}
|
||||
>
|
||||
{t('Cancel')}
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
</FlexWrapper>
|
||||
</MuiFade>
|
||||
@@ -165,29 +165,29 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledSettingKey>{t('Workspace Type')}</StyledSettingKey>
|
||||
<StyledSettingKey>{t['Workspace Type']()}</StyledSettingKey>
|
||||
{isOwner ? (
|
||||
workspace.flavour === WorkspaceFlavour.LOCAL ? (
|
||||
<StyledWorkspaceInfo>
|
||||
<LocalWorkspaceIcon />
|
||||
<span>{t('Local Workspace')}</span>
|
||||
<span>{t['Local Workspace']()}</span>
|
||||
</StyledWorkspaceInfo>
|
||||
) : (
|
||||
<StyledWorkspaceInfo>
|
||||
<CloudWorkspaceIcon />
|
||||
<span>{t('Cloud Workspace')}</span>
|
||||
<span>{t['Cloud Workspace']()}</span>
|
||||
</StyledWorkspaceInfo>
|
||||
)
|
||||
) : (
|
||||
<StyledWorkspaceInfo>
|
||||
<JoinedWorkspaceIcon />
|
||||
<span>{t('Joined Workspace')}</span>
|
||||
<span>{t['Joined Workspace']()}</span>
|
||||
</StyledWorkspaceInfo>
|
||||
)}
|
||||
</StyledRow>
|
||||
|
||||
<StyledRow>
|
||||
<StyledSettingKey> {t('Delete Workspace')}</StyledSettingKey>
|
||||
<StyledSettingKey> {t['Delete Workspace']()}</StyledSettingKey>
|
||||
{isOwner ? (
|
||||
<>
|
||||
<Button
|
||||
@@ -199,7 +199,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
setShowDelete(true);
|
||||
}}
|
||||
>
|
||||
{t('Delete Workspace')}
|
||||
{t['Delete Workspace']()}
|
||||
</Button>
|
||||
<WorkspaceDeleteModal
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
@@ -219,7 +219,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
setShowLeave(true);
|
||||
}}
|
||||
>
|
||||
{t('Leave Workspace')}
|
||||
{t['Leave Workspace']()}
|
||||
</Button>
|
||||
<WorkspaceLeave
|
||||
open={showLeave}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal } from '@affine/component';
|
||||
import { ModalCloseButton } from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import {
|
||||
StyledButtonContent,
|
||||
@@ -17,7 +17,7 @@ interface WorkspaceDeleteProps {
|
||||
|
||||
export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
|
||||
// const { leaveWorkSpace } = useWorkspaceHelper();
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const handleLeave = async () => {
|
||||
// await leaveWorkSpace();
|
||||
onClose();
|
||||
@@ -27,13 +27,13 @@ export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<StyledModalHeader>{t('Leave Workspace')}</StyledModalHeader>
|
||||
<StyledModalHeader>{t['Leave Workspace']()}</StyledModalHeader>
|
||||
<StyledTextContent>
|
||||
{t('Leave Workspace Description')}
|
||||
{t['Leave Workspace Description']()}
|
||||
</StyledTextContent>
|
||||
<StyledButtonContent>
|
||||
<Button shape="circle" onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleLeave}
|
||||
@@ -41,7 +41,7 @@ export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t('Leave')}
|
||||
{t['Leave']()}
|
||||
</Button>
|
||||
</StyledButtonContent>
|
||||
</StyledModalWrapper>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
export const StyledModalWrapper = styled('div')(({ theme }) => {
|
||||
export const StyledModalWrapper = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
padding: '0px',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
import { Input } from '@affine/component';
|
||||
|
||||
export const StyledInput = styled(Input)(({ theme }) => {
|
||||
export const StyledInput = styled(Input)(() => {
|
||||
return {
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
borderRadius: '10px',
|
||||
@@ -9,7 +9,7 @@ export const StyledInput = styled(Input)(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledWorkspaceInfo = styled('div')(({ theme }) => {
|
||||
export const StyledWorkspaceInfo = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('flex-start', 'center'),
|
||||
fontSize: '20px',
|
||||
@@ -48,7 +48,7 @@ export const StyledAvatar = styled('div')(
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledEditButton = styled('div')(({ theme }) => {
|
||||
export const StyledEditButton = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-primary-color)',
|
||||
cursor: 'pointer',
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Wrapper,
|
||||
} from '@affine/component';
|
||||
import { config } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { Box } from '@mui/material';
|
||||
@@ -41,20 +41,20 @@ const PublishPanelAffine: React.FC<PublishPanelAffineProps> = ({
|
||||
);
|
||||
}, []);
|
||||
const shareUrl = origin + '/public-workspace/' + workspace.id;
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const publishWorkspace = useToggleWorkspacePublish(workspace);
|
||||
const copyUrl = useCallback(() => {
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
toast(t('Copied link to clipboard'));
|
||||
toast(t['Copied link to clipboard']());
|
||||
}, [shareUrl, t]);
|
||||
|
||||
if (workspace.public) {
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="42px">{t('Published Description')}</Wrapper>
|
||||
<Wrapper marginBottom="42px">{t['Published Description']()}</Wrapper>
|
||||
|
||||
<Wrapper marginBottom="12px">
|
||||
<Content weight="500">{t('Share with link')}</Content>
|
||||
<Content weight="500">{t['Share with link']()}</Content>
|
||||
</Wrapper>
|
||||
<FlexWrapper>
|
||||
<Input
|
||||
@@ -69,7 +69,7 @@ const PublishPanelAffine: React.FC<PublishPanelAffineProps> = ({
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t('Copy Link')}
|
||||
{t['Copy Link']()}
|
||||
</Button>
|
||||
</FlexWrapper>
|
||||
<Button
|
||||
@@ -81,14 +81,14 @@ const PublishPanelAffine: React.FC<PublishPanelAffineProps> = ({
|
||||
shape="circle"
|
||||
style={{ marginTop: '38px' }}
|
||||
>
|
||||
{t('Stop publishing')}
|
||||
{t['Stop publishing']()}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="42px">{t('Publishing Description')}</Wrapper>
|
||||
<Wrapper marginBottom="42px">{t['Publishing Description']()}</Wrapper>
|
||||
<Button
|
||||
data-testid="publish-to-web-button"
|
||||
onClick={async () => {
|
||||
@@ -97,7 +97,7 @@ const PublishPanelAffine: React.FC<PublishPanelAffineProps> = ({
|
||||
type="light"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Publish to web')}
|
||||
{t['Publish to web']()}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
@@ -111,7 +111,7 @@ const PublishPanelLocal: React.FC<PublishPanelLocalProps> = ({
|
||||
workspace,
|
||||
onTransferWorkspace,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
@@ -120,7 +120,7 @@ const PublishPanelLocal: React.FC<PublishPanelLocalProps> = ({
|
||||
marginBottom: '42px',
|
||||
}}
|
||||
>
|
||||
{t('Publishing')}
|
||||
{t['Publishing']()}
|
||||
</Box>
|
||||
{/* TmpDisableAffineCloudModal */}
|
||||
|
||||
@@ -132,7 +132,7 @@ const PublishPanelLocal: React.FC<PublishPanelLocalProps> = ({
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Enable AFFiNE Cloud')}
|
||||
{t['Enable AFFiNE Cloud']()}
|
||||
</Button>
|
||||
{config.enableLegacyCloud ? (
|
||||
<EnableAffineCloudModal
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Content, FlexWrapper, styled } from '@affine/component';
|
||||
import { Trans, useTranslation } from '@affine/i18n';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
@@ -9,7 +10,7 @@ import { useCurrentUser } from '../../../../../hooks/current/use-current-user';
|
||||
import { WorkspaceAvatar } from '../../../../pure/footer';
|
||||
import type { PanelProps } from '../../index';
|
||||
|
||||
export const StyledWorkspaceName = styled('span')(({ theme }) => {
|
||||
export const StyledWorkspaceName = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '400',
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
@@ -25,7 +26,7 @@ export const SyncPanel: React.FC<PanelProps> = ({ workspace }) => {
|
||||
workspace.blockSuiteWorkspace
|
||||
);
|
||||
const user = useCurrentUser();
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<>
|
||||
<FlexWrapper alignItems="center" style={{ marginBottom: '12px' }}>
|
||||
@@ -37,7 +38,7 @@ export const SyncPanel: React.FC<PanelProps> = ({ workspace }) => {
|
||||
/>
|
||||
<StyledWorkspaceName>{name}</StyledWorkspaceName>
|
||||
|
||||
<Content weight={500}>{t('is a Cloud Workspace')}</Content>
|
||||
<Content weight={500}>{t['is a Cloud Workspace']()}</Content>
|
||||
</FlexWrapper>
|
||||
<Trans i18nKey="Cloud Workspace Description">
|
||||
All data will be synchronised and saved to the AFFiNE account
|
||||
|
||||
@@ -26,7 +26,7 @@ export const StyledSettingContent = styled('div')(() => {
|
||||
});
|
||||
|
||||
export const WorkspaceSettingTagItem = styled('li')<{ isActive?: boolean }>(
|
||||
({ theme, isActive }) => {
|
||||
({ isActive }) => {
|
||||
{
|
||||
return {
|
||||
display: 'flex',
|
||||
@@ -45,7 +45,7 @@ export const WorkspaceSettingTagItem = styled('li')<{ isActive?: boolean }>(
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledSettingKey = styled('div')(({ theme }) => {
|
||||
export const StyledSettingKey = styled('div')(() => {
|
||||
return {
|
||||
width: '140px',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
@@ -60,14 +60,14 @@ export const StyledRow = styled(FlexWrapper)(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledWorkspaceName = styled('span')(({ theme }) => {
|
||||
export const StyledWorkspaceName = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '400',
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledIndicator = styled('div')(({ theme }) => {
|
||||
export const StyledIndicator = styled('div')(() => {
|
||||
return {
|
||||
height: '2px',
|
||||
background: 'var(--affine-primary-color)',
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const pageListEmptyStyle = style({
|
||||
height: 'calc(100% - 52px)',
|
||||
});
|
||||
@@ -1,35 +1,171 @@
|
||||
import { Empty } from '@affine/component';
|
||||
import type { ListData, TrashListData } from '@affine/component/page-list';
|
||||
import { PageList, PageListTrashView } from '@affine/component/page-list';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import dayjs from 'dayjs';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { workspacePreferredModeAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import PageList from './page-list';
|
||||
import { toast } from '../../../utils';
|
||||
import { pageListEmptyStyle } from './index.css';
|
||||
|
||||
export type BlockSuitePageListProps = {
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
listType: 'all' | 'trash' | 'favorite' | 'shared' | 'public';
|
||||
isPublic?: true;
|
||||
onOpenPage: (pageId: string, newTab?: boolean) => void;
|
||||
};
|
||||
|
||||
const filter = {
|
||||
all: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
public: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => {
|
||||
const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id));
|
||||
return !parentMeta?.trash && pageMeta.trash;
|
||||
},
|
||||
favorite: (pageMeta: PageMeta) => pageMeta.favorite && !pageMeta.trash,
|
||||
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
|
||||
};
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
const formatDate = (date?: number | unknown) => {
|
||||
const dateStr =
|
||||
typeof date === 'number' ? dayjs(date).format('YYYY-MM-DD HH:mm') : '--';
|
||||
return dateStr;
|
||||
};
|
||||
|
||||
const PageListEmpty = (props: {
|
||||
listType: BlockSuitePageListProps['listType'];
|
||||
}) => {
|
||||
const { listType } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const getEmptyDescription = () => {
|
||||
if (listType === 'all') {
|
||||
return t['emptyAllPages']();
|
||||
}
|
||||
if (listType === 'favorite') {
|
||||
return t['emptyFavorite']();
|
||||
}
|
||||
if (listType === 'trash') {
|
||||
return t['emptyTrash']();
|
||||
}
|
||||
if (listType === 'shared') {
|
||||
return t['emptySharedPages']();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={pageListEmptyStyle}>
|
||||
<Empty description={getEmptyDescription()} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
||||
blockSuiteWorkspace,
|
||||
onOpenPage,
|
||||
listType,
|
||||
isPublic = false,
|
||||
}) => {
|
||||
return (
|
||||
<PageList
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onClickPage={onOpenPage}
|
||||
listType="all"
|
||||
/>
|
||||
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
const {
|
||||
toggleFavorite,
|
||||
removeToTrash,
|
||||
restoreFromTrash,
|
||||
permanentlyDeletePage,
|
||||
cancelPublicPage,
|
||||
} = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const t = useAFFiNEI18N();
|
||||
const list = useMemo(
|
||||
() => pageMetas.filter(pageMeta => filter[listType](pageMeta, pageMetas)),
|
||||
[pageMetas, listType]
|
||||
);
|
||||
};
|
||||
const record = useAtomValue(workspacePreferredModeAtom);
|
||||
if (list.length === 0) {
|
||||
return <PageListEmpty listType={listType} />;
|
||||
}
|
||||
|
||||
if (listType === 'trash') {
|
||||
const pageList: TrashListData[] = list.map(pageMeta => {
|
||||
return {
|
||||
icon:
|
||||
record[pageMeta.id] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />,
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
createDate: formatDate(pageMeta.createDate),
|
||||
updatedDate: formatDate(pageMeta.updatedDate),
|
||||
onClickPage: () => onOpenPage(pageMeta.id),
|
||||
onClickRestore: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
},
|
||||
onRestorePage: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
toast(t['restored']({ title: pageMeta.title || 'Untitled' }));
|
||||
},
|
||||
onPermanentlyDeletePage: () => {
|
||||
permanentlyDeletePage(pageMeta.id);
|
||||
toast(t['Permanently deleted']());
|
||||
},
|
||||
};
|
||||
});
|
||||
return <PageListTrashView list={pageList} />;
|
||||
}
|
||||
|
||||
const pageList: ListData[] = list.map(pageMeta => {
|
||||
return {
|
||||
icon:
|
||||
record[pageMeta.id] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />,
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
favorite: !!pageMeta.favorite,
|
||||
isPublicPage: !!pageMeta.isPublic,
|
||||
createDate: formatDate(pageMeta.createDate),
|
||||
updatedDate: formatDate(pageMeta.updatedDate),
|
||||
onClickPage: () => onOpenPage(pageMeta.id),
|
||||
onOpenPageInNewTab: () => onOpenPage(pageMeta.id, true),
|
||||
onClickRestore: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
},
|
||||
removeToTrash: () => {
|
||||
removeToTrash(pageMeta.id);
|
||||
toast(t['Successfully deleted']());
|
||||
},
|
||||
onRestorePage: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
toast(t['restored']({ title: pageMeta.title || 'Untitled' }));
|
||||
},
|
||||
bookmarkPage: () => {
|
||||
toggleFavorite(pageMeta.id);
|
||||
toast(
|
||||
pageMeta.favorite
|
||||
? t['Removed from Favorites']()
|
||||
: t['Added to Favorites']()
|
||||
);
|
||||
},
|
||||
onDisablePublicSharing: () => {
|
||||
cancelPublicPage(pageMeta.id);
|
||||
toast('Successfully disabled', {
|
||||
portal: document.body,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const BlockSuitePublicPageList: React.FC<BlockSuitePageListProps> = ({
|
||||
blockSuiteWorkspace,
|
||||
onOpenPage,
|
||||
}) => {
|
||||
return (
|
||||
<PageList
|
||||
isPublic={true}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onClickPage={onOpenPage}
|
||||
isPublicWorkspace={isPublic}
|
||||
list={pageList}
|
||||
listType={listType}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { TableCellProps } from '@affine/component';
|
||||
import { TableCell } from '@affine/component';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import dayjs from 'dayjs';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import React from 'react';
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
export const DateCell = ({
|
||||
pageMeta,
|
||||
dateKey,
|
||||
backupKey = '',
|
||||
...props
|
||||
}: {
|
||||
pageMeta: PageMeta;
|
||||
dateKey: keyof PageMeta;
|
||||
backupKey?: keyof PageMeta;
|
||||
} & Omit<TableCellProps, 'children'>) => {
|
||||
const value = pageMeta[dateKey] ?? pageMeta[backupKey];
|
||||
return (
|
||||
<TableCell ellipsis={true} {...props}>
|
||||
{typeof value === 'number'
|
||||
? dayjs(value).format('YYYY-MM-DD HH:mm')
|
||||
: '--'}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateCell;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Empty } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import React from 'react';
|
||||
export const PageListEmpty = (props: { listType?: string }) => {
|
||||
const { listType } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getEmptyDescription = () => {
|
||||
if (listType === 'all') {
|
||||
return t('emptyAllPages');
|
||||
}
|
||||
if (listType === 'favorite') {
|
||||
return t('emptyFavorite');
|
||||
}
|
||||
if (listType === 'trash') {
|
||||
return t('emptyTrash');
|
||||
}
|
||||
if (listType === 'shared') {
|
||||
return t('emptySharedPages');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: 'calc(100% - 52px)' }}>
|
||||
<Empty description={getEmptyDescription()} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageListEmpty;
|
||||
@@ -1,252 +0,0 @@
|
||||
import {
|
||||
Content,
|
||||
IconButton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import {
|
||||
EdgelessIcon,
|
||||
FavoritedIcon,
|
||||
FavoriteIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useMediaQuery, useTheme } from '@mui/material';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
||||
import { toast } from '../../../../utils';
|
||||
import DateCell from './DateCell';
|
||||
import Empty from './Empty';
|
||||
import { OperationCell, TrashOperationCell } from './OperationCell';
|
||||
import {
|
||||
StyledTableContainer,
|
||||
StyledTableRow,
|
||||
StyledTitleLink,
|
||||
StyledTitleWrapper,
|
||||
} from './styles';
|
||||
|
||||
export type FavoriteTagProps = {
|
||||
pageMeta: PageMeta;
|
||||
onClick: () => void;
|
||||
};
|
||||
const FavoriteTag: React.FC<FavoriteTagProps> = ({
|
||||
pageMeta: { favorite },
|
||||
onClick,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Tooltip
|
||||
content={favorite ? t('Favorited') : t('Favorite')}
|
||||
placement="top-start"
|
||||
>
|
||||
<IconButton
|
||||
iconSize={[20, 20]}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
toast(
|
||||
favorite ? t('Removed from Favorites') : t('Added to Favorites')
|
||||
);
|
||||
}}
|
||||
style={{
|
||||
color: favorite
|
||||
? 'var(--affine-primary-color)'
|
||||
: 'var(--affine-icon-color)',
|
||||
}}
|
||||
className={favorite ? '' : 'favorite-button'}
|
||||
>
|
||||
{favorite ? (
|
||||
<FavoritedIcon data-testid="favorited-icon" />
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
type PageListProps = {
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
isPublic?: boolean;
|
||||
listType?: 'all' | 'trash' | 'favorite' | 'shared';
|
||||
onClickPage: (pageId: string, newTab?: boolean) => void;
|
||||
};
|
||||
|
||||
const filter = {
|
||||
all: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => {
|
||||
const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id));
|
||||
return !parentMeta?.trash && pageMeta.trash;
|
||||
},
|
||||
favorite: (pageMeta: PageMeta) => pageMeta.favorite && !pageMeta.trash,
|
||||
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
|
||||
};
|
||||
|
||||
export const PageList: React.FC<PageListProps> = ({
|
||||
blockSuiteWorkspace,
|
||||
isPublic = false,
|
||||
listType,
|
||||
onClickPage,
|
||||
}) => {
|
||||
const pageList = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
const helper = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const { removeToTrash, restoreFromTrash } =
|
||||
useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const matches = useMediaQuery(theme.breakpoints.up('sm'));
|
||||
const isTrash = listType === 'trash';
|
||||
const isShared = listType === 'shared';
|
||||
const record = useAtomValue(workspacePreferredModeAtom);
|
||||
const list = useMemo(
|
||||
() =>
|
||||
pageList.filter(pageMeta =>
|
||||
filter[listType ?? 'all'](pageMeta, pageList)
|
||||
),
|
||||
[pageList, listType]
|
||||
);
|
||||
if (list.length === 0) {
|
||||
return <Empty listType={listType} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledTableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{matches && (
|
||||
<>
|
||||
<TableCell proportion={0.5}>{t('Title')}</TableCell>
|
||||
<TableCell proportion={0.2}>{t('Created')}</TableCell>
|
||||
<TableCell proportion={0.2}>
|
||||
{isTrash
|
||||
? t('Moved to Trash')
|
||||
: isShared
|
||||
? 'Shared'
|
||||
: t('Updated')}
|
||||
</TableCell>
|
||||
<TableCell proportion={0.1}></TableCell>
|
||||
</>
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{list.map((pageMeta, index) => {
|
||||
return (
|
||||
<StyledTableRow
|
||||
data-testid={`page-list-item-${pageMeta.id}`}
|
||||
key={`${pageMeta.id}-${index}`}
|
||||
>
|
||||
<TableCell
|
||||
onClick={() => {
|
||||
onClickPage(pageMeta.id);
|
||||
}}
|
||||
>
|
||||
<StyledTitleWrapper>
|
||||
<StyledTitleLink>
|
||||
{record[pageMeta.id] === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PageIcon />
|
||||
)}
|
||||
<Content ellipsis={true} color="inherit">
|
||||
{pageMeta.title || t('Untitled')}
|
||||
</Content>
|
||||
</StyledTitleLink>
|
||||
{listType && !isTrash && (
|
||||
<FavoriteTag
|
||||
onClick={() => {
|
||||
helper.setPageMeta(pageMeta.id, {
|
||||
favorite: !pageMeta.favorite,
|
||||
});
|
||||
}}
|
||||
pageMeta={pageMeta}
|
||||
/>
|
||||
)}
|
||||
</StyledTitleWrapper>
|
||||
</TableCell>
|
||||
{matches && (
|
||||
<>
|
||||
<DateCell
|
||||
pageMeta={pageMeta}
|
||||
dateKey="createDate"
|
||||
onClick={() => {
|
||||
onClickPage(pageMeta.id);
|
||||
}}
|
||||
/>
|
||||
<DateCell
|
||||
pageMeta={pageMeta}
|
||||
dateKey={isTrash ? 'trashDate' : 'updatedDate'}
|
||||
backupKey={isTrash ? 'trashDate' : 'createDate'}
|
||||
onClick={() => {
|
||||
onClickPage(pageMeta.id);
|
||||
}}
|
||||
/>
|
||||
{!isPublic && (
|
||||
<TableCell
|
||||
style={{ padding: 0 }}
|
||||
data-testid={`more-actions-${pageMeta.id}`}
|
||||
>
|
||||
{isTrash ? (
|
||||
<TrashOperationCell
|
||||
pageMeta={pageMeta}
|
||||
onPermanentlyDeletePage={pageId => {
|
||||
blockSuiteWorkspace.removePage(pageId);
|
||||
}}
|
||||
onRestorePage={() => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
}}
|
||||
onOpenPage={pageId => {
|
||||
onClickPage(pageId, false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<OperationCell
|
||||
pageMeta={pageMeta}
|
||||
metas={pageList}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onOpenPageInNewTab={pageId => {
|
||||
onClickPage(pageId, true);
|
||||
}}
|
||||
onToggleFavoritePage={(pageId: string) => {
|
||||
helper.setPageMeta(pageId, {
|
||||
favorite: !pageMeta.favorite,
|
||||
});
|
||||
}}
|
||||
onToggleTrashPage={(pageId, isTrash) => {
|
||||
if (isTrash) {
|
||||
removeToTrash(pageId);
|
||||
} else {
|
||||
restoreFromTrash(pageId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledTableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</StyledTableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageList;
|
||||
@@ -3,7 +3,7 @@ import { displayFlex, styled } from '@affine/component';
|
||||
export const StyledEditorModeSwitch = styled('div')<{
|
||||
switchLeft: boolean;
|
||||
showAlone?: boolean;
|
||||
}>(({ theme, switchLeft, showAlone }) => {
|
||||
}>(({ switchLeft, showAlone }) => {
|
||||
return {
|
||||
width: showAlone ? '40px' : '78px',
|
||||
height: '32px',
|
||||
@@ -21,7 +21,7 @@ export const StyledEditorModeSwitch = styled('div')<{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
boxShadow: 'var(--affine-shadow)',
|
||||
boxShadow: 'var(--affine-shadow-1)',
|
||||
borderRadius: '8px',
|
||||
zIndex: 1,
|
||||
position: 'absolute',
|
||||
@@ -40,7 +40,7 @@ export const StyledSwitchItem = styled('button')<{
|
||||
height: '24px',
|
||||
borderRadius: '8px',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
boxShadow: active ? 'var(--affine-shadow)' : 'none',
|
||||
boxShadow: active ? 'var(--affine-shadow-1)' : 'none',
|
||||
color: active ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)',
|
||||
display: hide ? 'none' : 'inline-flex',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -26,7 +26,7 @@ export default EditPage;
|
||||
const StyledEditPageButton = styled(
|
||||
TextButton,
|
||||
{}
|
||||
)(({ theme }) => {
|
||||
)(() => {
|
||||
return {
|
||||
border: '1px solid var(--affine-primary-color)',
|
||||
color: 'var(--affine-primary-color)',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// fixme(himself65): refactor this file
|
||||
import { FlexWrapper, IconButton, Menu, MenuItem } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { Export, MoveToTrash } from '@affine/component/page-list';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
EdgelessIcon,
|
||||
FavoritedIcon,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
||||
@@ -21,11 +23,41 @@ import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suit
|
||||
import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id';
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { toast } from '../../../../utils';
|
||||
import { Export, MoveToTrash } from '../../../affine/operation-menu-items';
|
||||
|
||||
export const EditorOptionMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
|
||||
import {
|
||||
StyledHorizontalDivider,
|
||||
StyledHorizontalDividerContainer,
|
||||
} from '../styles';
|
||||
import { LanguageMenu } from './LanguageMenu';
|
||||
const CommonMenu = () => {
|
||||
const content = (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MenuThemeModeSwitch />
|
||||
<LanguageMenu />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<FlexWrapper alignItems="center" justifyContent="center">
|
||||
<Menu
|
||||
width={276}
|
||||
content={content}
|
||||
placement="bottom"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
const PageMenu = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
const [pageId] = useCurrentPageId();
|
||||
@@ -35,55 +67,72 @@ export const EditorOptionMenu = () => {
|
||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const [record, set] = useAtom(workspacePreferredModeAtom);
|
||||
const mode = record[pageId] ?? 'page';
|
||||
assertExists(pageMeta);
|
||||
const { favorite } = pageMeta;
|
||||
|
||||
const favorite = pageMeta.favorite ?? false;
|
||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const [openConfirm, setOpenConfirm] = useState(false);
|
||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const EditMenu = (
|
||||
<>
|
||||
<MenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onClick={() => {
|
||||
setPageMeta(pageId, { favorite: !favorite });
|
||||
toast(
|
||||
favorite ? t('Removed from Favorites') : t('Added to Favorites')
|
||||
);
|
||||
}}
|
||||
icon={
|
||||
favorite ? (
|
||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{favorite ? t('Remove from favorites') : t('Add to Favorites')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={() => {
|
||||
set(record => ({
|
||||
...record,
|
||||
[pageId]: mode === 'page' ? 'edgeless' : 'page',
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t('Convert to ')}
|
||||
{mode === 'page' ? t('Edgeless') : t('Page')}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
{!pageMeta.isRootPinboard && (
|
||||
<MoveToTrash
|
||||
testId="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
<>
|
||||
<MenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onClick={() => {
|
||||
setPageMeta(pageId, { favorite: !favorite });
|
||||
toast(
|
||||
favorite
|
||||
? t['Removed from Favorites']()
|
||||
: t['Added to Favorites']()
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
icon={
|
||||
favorite ? (
|
||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||
) : (
|
||||
<FavoriteIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={() => {
|
||||
set(record => ({
|
||||
...record,
|
||||
[pageId]: mode === 'page' ? 'edgeless' : 'page',
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
{!pageMeta.isRootPinboard && (
|
||||
<MoveToTrash
|
||||
testId="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<StyledHorizontalDividerContainer>
|
||||
<StyledHorizontalDivider />
|
||||
</StyledHorizontalDividerContainer>
|
||||
</>
|
||||
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MenuThemeModeSwitch />
|
||||
<LanguageMenu />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -93,7 +142,7 @@ export const EditorOptionMenu = () => {
|
||||
<Menu
|
||||
width={276}
|
||||
content={EditMenu}
|
||||
// placement="bottom-end"
|
||||
placement="bottom"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
@@ -103,10 +152,10 @@ export const EditorOptionMenu = () => {
|
||||
</Menu>
|
||||
<MoveToTrash.ConfirmModal
|
||||
open={openConfirm}
|
||||
meta={pageMeta}
|
||||
title={pageMeta.title}
|
||||
onConfirm={() => {
|
||||
removeToTrash(pageMeta.id);
|
||||
toast(t('Moved to Trash'));
|
||||
toast(t['Moved to Trash']());
|
||||
}}
|
||||
onCancel={() => {
|
||||
setOpenConfirm(false);
|
||||
@@ -116,3 +165,7 @@ export const EditorOptionMenu = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const EditorOptionMenu = () => {
|
||||
const router = useRouter();
|
||||
return router.query.pageId ? <PageMenu /> : <CommonMenu />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { Button, displayFlex, Menu, MenuItem, styled } from '@affine/component';
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useI18N } from '@affine/i18n';
|
||||
import { ArrowDownSmallIcon, PublishIcon } from '@blocksuite/icons';
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const LanguageMenuContent: FC = () => {
|
||||
const i18n = useI18N();
|
||||
const changeLanguage = useCallback(
|
||||
(event: string) => {
|
||||
i18n.changeLanguage(event);
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<StyledListItem
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu: React.FC = () => {
|
||||
const i18n = useI18N();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledIconContainer>
|
||||
<PublishIcon />
|
||||
</StyledIconContainer>
|
||||
<StyledButtonContainer>
|
||||
<Menu
|
||||
content={(<LanguageMenuContent />) as ReactElement}
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
disablePortal={true}
|
||||
>
|
||||
<StyledButton
|
||||
icon={
|
||||
<StyledArrowDownContainer>
|
||||
<ArrowDownSmallIcon />
|
||||
</StyledArrowDownContainer>
|
||||
}
|
||||
iconPosition="end"
|
||||
noBorder={true}
|
||||
data-testid="language-menu-button"
|
||||
>
|
||||
<StyledCurrentLanguage>
|
||||
{currentLanguage?.originalName}
|
||||
</StyledCurrentLanguage>
|
||||
</StyledButton>
|
||||
</Menu>
|
||||
</StyledButtonContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledListItem = styled(MenuItem)(() => ({
|
||||
width: '132px',
|
||||
height: '38px',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
|
||||
const StyledContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '48px',
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
padding: '0 14px',
|
||||
};
|
||||
});
|
||||
const StyledIconContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '20px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
};
|
||||
});
|
||||
const StyledButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
borderRadius: '4px',
|
||||
border: `1px solid var(--affine-border-color)`,
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
marginLeft: '12px',
|
||||
};
|
||||
});
|
||||
const StyledButton = styled(Button)(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('space-between', 'center'),
|
||||
textTransform: 'capitalize',
|
||||
padding: '0',
|
||||
};
|
||||
});
|
||||
const StyledArrowDownContainer = styled('div')(() => {
|
||||
return {
|
||||
height: '32px',
|
||||
borderLeft: `1px solid var(--affine-border-color)`,
|
||||
backgroundColor: 'transparent',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
padding: '4px 6px',
|
||||
fontSize: '24px',
|
||||
};
|
||||
});
|
||||
const StyledCurrentLanguage = styled('div')(() => {
|
||||
return {
|
||||
marginLeft: '12px',
|
||||
color: 'var(--affine-text-color)',
|
||||
};
|
||||
});
|
||||
@@ -76,12 +76,12 @@ const LocalHeaderShareMenu: React.FC<BaseHeaderProps> = props => {
|
||||
},
|
||||
[helper]
|
||||
)}
|
||||
togglePagePublic={useCallback(async (page, isPublic) => {
|
||||
togglePagePublic={useCallback(async () => {
|
||||
// local workspace should not have public page
|
||||
throw new Error('unreachable');
|
||||
}, [])}
|
||||
toggleWorkspacePublish={useCallback(
|
||||
async (workspace, publish) => {
|
||||
async workspace => {
|
||||
assertEquals(workspace.flavour, WorkspaceFlavour.LOCAL);
|
||||
assertEquals(workspace.id, props.workspace.id);
|
||||
await helper.jumpToSubPath(workspace.id, WorkspaceSubPath.SETTING);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { displayFlex, IconButton, styled, Tooltip } from '@affine/component';
|
||||
import { config } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
getLoginStorage,
|
||||
setLoginStorage,
|
||||
@@ -23,7 +23,7 @@ import { affineAuth } from '../../../../plugins/affine';
|
||||
import type { AffineOfficialWorkspace } from '../../../../shared';
|
||||
import { TransformWorkspaceToAffineModal } from '../../../affine/transform-workspace-to-affine-modal';
|
||||
|
||||
const IconWrapper = styled('div')(({ theme }) => {
|
||||
const IconWrapper = styled('div')(() => {
|
||||
return {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
@@ -79,7 +79,7 @@ export const SyncUser = () => {
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const transformWorkspace = useTransformWorkspace();
|
||||
|
||||
if (!config.enableLegacyCloud) {
|
||||
@@ -89,7 +89,7 @@ export const SyncUser = () => {
|
||||
if (status === 'offline') {
|
||||
return (
|
||||
<Tooltip
|
||||
content={t('Please make sure you are online')}
|
||||
content={t['Please make sure you are online']()}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<IconWrapper>
|
||||
@@ -103,7 +103,7 @@ export const SyncUser = () => {
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
content={t('Saved then enable AFFiNE Cloud')}
|
||||
content={t['Saved then enable AFFiNE Cloud']()}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<IconButton
|
||||
@@ -156,7 +156,7 @@ export const SyncUser = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={t('Synced with AFFiNE Cloud')} placement="bottom-end">
|
||||
<Tooltip content={t['Synced with AFFiNE Cloud']()} placement="bottom-end">
|
||||
<IconWrapper>
|
||||
<CloudWorkspaceIcon />
|
||||
</IconWrapper>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Confirm } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -20,7 +20,7 @@ export const TrashButtonGroup = () => {
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const router = useRouter();
|
||||
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
|
||||
@@ -36,7 +36,7 @@ export const TrashButtonGroup = () => {
|
||||
restoreFromTrash(pageId);
|
||||
}}
|
||||
>
|
||||
{t('Restore it')}
|
||||
{t['Restore it']()}
|
||||
</Button>
|
||||
<Button
|
||||
bold={true}
|
||||
@@ -46,12 +46,12 @@ export const TrashButtonGroup = () => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Delete permanently')}
|
||||
{t['Delete permanently']()}
|
||||
</Button>
|
||||
<Confirm
|
||||
title={t('TrashButtonGroupTitle')}
|
||||
content={t('TrashButtonGroupDescription')}
|
||||
confirmText={t('Delete')}
|
||||
title={t['TrashButtonGroupTitle']()}
|
||||
content={t['TrashButtonGroupDescription']()}
|
||||
confirmText={t['Delete']()}
|
||||
confirmType="danger"
|
||||
open={open}
|
||||
onConfirm={() => {
|
||||
|
||||
@@ -16,7 +16,7 @@ export const UserAvatar = () => {
|
||||
<Menu
|
||||
width={276}
|
||||
content={EditMenu}
|
||||
// placement="bottom-end"
|
||||
placement="bottom"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
|
||||
@@ -1,50 +1,59 @@
|
||||
import { DarkModeIcon, LightModeIcon } from '@blocksuite/icons';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { StyledSwitchItem, StyledThemeModeSwitch } from './style';
|
||||
export const ThemeModeSwitch = () => {
|
||||
const { setTheme, resolvedTheme } = useTheme();
|
||||
import {
|
||||
StyledSwitchItem,
|
||||
StyledThemeButton,
|
||||
StyledThemeButtonContainer,
|
||||
StyledThemeModeContainer,
|
||||
StyledThemeModeSwitch,
|
||||
StyledVerticalDivider,
|
||||
} from './style';
|
||||
|
||||
useEffect(() => {
|
||||
if (environment.isDesktop) {
|
||||
window.apis?.onThemeChange(resolvedTheme === 'dark' ? 'dark' : 'light');
|
||||
}
|
||||
}, [resolvedTheme]);
|
||||
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
export const MenuThemeModeSwitch = () => {
|
||||
const { setTheme, resolvedTheme, theme } = useTheme();
|
||||
return (
|
||||
<StyledThemeModeSwitch
|
||||
data-testid="change-theme-container"
|
||||
onMouseEnter={() => {
|
||||
setIsHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsHover(false);
|
||||
}}
|
||||
>
|
||||
<StyledSwitchItem
|
||||
data-testid="change-theme-light"
|
||||
active={resolvedTheme === 'light'}
|
||||
isHover={isHover}
|
||||
onClick={() => {
|
||||
setTheme('light');
|
||||
}}
|
||||
>
|
||||
<LightModeIcon />
|
||||
</StyledSwitchItem>
|
||||
<StyledSwitchItem
|
||||
data-testid="change-theme-dark"
|
||||
active={resolvedTheme === 'dark'}
|
||||
isHover={isHover}
|
||||
onClick={() => {
|
||||
setTheme('dark');
|
||||
}}
|
||||
>
|
||||
<DarkModeIcon />
|
||||
</StyledSwitchItem>
|
||||
</StyledThemeModeSwitch>
|
||||
<StyledThemeModeContainer>
|
||||
<StyledThemeModeSwitch data-testid="change-theme-container" inMenu={true}>
|
||||
<StyledSwitchItem active={resolvedTheme === 'light'} inMenu={true}>
|
||||
<LightModeIcon />
|
||||
</StyledSwitchItem>
|
||||
<StyledSwitchItem active={resolvedTheme === 'dark'} inMenu={true}>
|
||||
<DarkModeIcon />
|
||||
</StyledSwitchItem>
|
||||
</StyledThemeModeSwitch>
|
||||
<StyledThemeButtonContainer>
|
||||
<StyledThemeButton
|
||||
data-testid="change-theme-light"
|
||||
active={theme === 'light'}
|
||||
onClick={() => {
|
||||
setTheme('light');
|
||||
}}
|
||||
>
|
||||
light
|
||||
</StyledThemeButton>
|
||||
<StyledVerticalDivider />
|
||||
<StyledThemeButton
|
||||
data-testid="change-theme-dark"
|
||||
active={theme === 'dark'}
|
||||
onClick={() => {
|
||||
setTheme('dark');
|
||||
}}
|
||||
>
|
||||
dark
|
||||
</StyledThemeButton>
|
||||
<StyledVerticalDivider />
|
||||
<StyledThemeButton
|
||||
active={theme === 'system'}
|
||||
onClick={() => {
|
||||
setTheme('system');
|
||||
}}
|
||||
>
|
||||
system
|
||||
</StyledThemeButton>
|
||||
</StyledThemeButtonContainer>
|
||||
</StyledThemeModeContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeModeSwitch;
|
||||
export default MenuThemeModeSwitch;
|
||||
|
||||
@@ -3,24 +3,66 @@ import { css, displayFlex, keyframes, styled } from '@affine/component';
|
||||
import spring, { toString } from 'css-spring';
|
||||
|
||||
const ANIMATE_DURATION = 400;
|
||||
|
||||
export const StyledThemeModeSwitch = styled('button')(({ theme }) => {
|
||||
export const StyledThemeModeContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '48px',
|
||||
borderRadius: '6px',
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '16px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
padding: '0 14px',
|
||||
};
|
||||
});
|
||||
export const StyledThemeButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
border: `1px solid var(--affine-border-color)`,
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
...displayFlex('space-evenly', 'center'),
|
||||
flexGrow: 1,
|
||||
marginLeft: '12px',
|
||||
};
|
||||
});
|
||||
export const StyledThemeButton = styled('button')<{
|
||||
active: boolean;
|
||||
}>(({ active }) => {
|
||||
return {
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
cursor: 'pointer',
|
||||
color: active ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)',
|
||||
};
|
||||
});
|
||||
export const StyledVerticalDivider = styled('div')(() => {
|
||||
return {
|
||||
width: '1px',
|
||||
height: '100%',
|
||||
borderLeft: `1px solid var(--affine-border-color)`,
|
||||
};
|
||||
});
|
||||
export const StyledThemeModeSwitch = styled('button')<{
|
||||
inMenu?: boolean;
|
||||
}>(({ inMenu }) => {
|
||||
return {
|
||||
width: inMenu ? '20px' : '32px',
|
||||
height: inMenu ? '20px' : '32px',
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
backgroundColor: 'transparent',
|
||||
position: 'relative',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '24px',
|
||||
fontSize: inMenu ? '20px' : '24px',
|
||||
};
|
||||
});
|
||||
export const StyledSwitchItem = styled('div')<{
|
||||
active: boolean;
|
||||
isHover: boolean;
|
||||
}>(({ active, isHover, theme }) => {
|
||||
isHover?: boolean;
|
||||
inMenu?: boolean;
|
||||
}>(({ active, isHover, inMenu }) => {
|
||||
const activeRaiseAnimate = toString(
|
||||
spring({ top: '0' }, { top: '-100%' }, { preset: 'gentle' })
|
||||
);
|
||||
@@ -58,8 +100,8 @@ export const StyledSwitchItem = styled('div')<{
|
||||
};
|
||||
return css`
|
||||
${css(displayFlex('center', 'center'))}
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width:${inMenu ? '20px' : '32px'} ;
|
||||
height: ${inMenu ? '20px' : '32px'} ;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
@@ -19,7 +19,6 @@ import { EditorOptionMenu } from './header-right-items/EditorOptionMenu';
|
||||
import EditPage from './header-right-items/EditPage';
|
||||
import { HeaderShareMenu } from './header-right-items/ShareMenu';
|
||||
import SyncUser from './header-right-items/SyncUser';
|
||||
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
|
||||
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
|
||||
import UserAvatar from './header-right-items/UserAvatar';
|
||||
import {
|
||||
@@ -66,7 +65,6 @@ export type BaseHeaderProps<
|
||||
export const enum HeaderRightItemName {
|
||||
EditorOptionMenu = 'editorOptionMenu',
|
||||
TrashButtonGroup = 'trashButtonGroup',
|
||||
ThemeModeSwitch = 'themeModeSwitch',
|
||||
SyncUser = 'syncUser',
|
||||
ShareMenu = 'shareMenu',
|
||||
EditPage = 'editPage',
|
||||
@@ -98,34 +96,28 @@ const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
||||
return !isPublic && !isPreview;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.ThemeModeSwitch]: {
|
||||
Component: ThemeModeSwitch,
|
||||
availableWhen: (_, currentPage) => {
|
||||
return currentPage?.meta.trash !== true;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.ShareMenu]: {
|
||||
Component: HeaderShareMenu,
|
||||
availableWhen: (workspace, currentPage, { isPublic, isPreview }) => {
|
||||
availableWhen: (workspace, currentPage) => {
|
||||
return workspace.flavour !== WorkspaceFlavour.PUBLIC && !!currentPage;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.EditPage]: {
|
||||
Component: EditPage,
|
||||
availableWhen: (workspace, currentPage, { isPublic, isPreview }) => {
|
||||
availableWhen: (workspace, currentPage, { isPublic }) => {
|
||||
return isPublic;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.UserAvatar]: {
|
||||
Component: UserAvatar,
|
||||
availableWhen: (workspace, currentPage, { isPublic, isPreview }) => {
|
||||
availableWhen: (workspace, currentPage, { isPublic }) => {
|
||||
return isPublic;
|
||||
},
|
||||
},
|
||||
[HeaderRightItemName.EditorOptionMenu]: {
|
||||
Component: EditorOptionMenu,
|
||||
availableWhen: (_, currentPage, { isPublic, isPreview }) => {
|
||||
return !!currentPage && !isPublic && !isPreview;
|
||||
return !isPublic && !isPreview;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -141,7 +133,7 @@ export const Header = forwardRef<
|
||||
setShowWarning(shouldShowWarning());
|
||||
}, []);
|
||||
const [open] = useAtom(appSidebarOpenAtom);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<StyledHeaderContainer
|
||||
@@ -164,7 +156,7 @@ export const Header = forwardRef<
|
||||
<Suspense>
|
||||
<SidebarSwitch
|
||||
visible={!open}
|
||||
tooltipContent={t('Expand sidebar')}
|
||||
tooltipContent={t['Expand sidebar']()}
|
||||
data-testid="sliderBar-arrowButton-expand"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
@@ -4,12 +4,12 @@ import { getEnvironment } from '@affine/env';
|
||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { HTMLAttributes, PropsWithChildren } from 'react';
|
||||
import { forwardRef, useCallback, useRef } from 'react';
|
||||
|
||||
import { currentEditorAtom, openQuickSearchModalAtom } from '../../../atoms';
|
||||
import { useGuideHidden } from '../../../hooks/use-is-first-load';
|
||||
import { guideQuickSearchTipsAtom } from '../../../atoms/guide';
|
||||
import { useElementResizeEffect } from '../../../hooks/use-workspaces';
|
||||
import { QuickSearchButton } from '../../pure/quick-search-button';
|
||||
import { EditorModeSwitch } from './editor-mode-switch';
|
||||
@@ -39,7 +39,6 @@ export const WorkspaceHeader = forwardRef<
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const title = pageMeta.title;
|
||||
const [isTipsHidden, setTipsHidden] = useGuideHidden();
|
||||
const isMac = () => {
|
||||
const env = getEnvironment();
|
||||
return env.isBrowser && env.isMacOs;
|
||||
@@ -47,14 +46,18 @@ export const WorkspaceHeader = forwardRef<
|
||||
|
||||
const popperRef: PopperProps['popperRef'] = useRef(null);
|
||||
|
||||
const [showQuickSearchTips, setShowQuickSearchTips] = useAtom(
|
||||
guideQuickSearchTipsAtom
|
||||
);
|
||||
|
||||
useElementResizeEffect(
|
||||
useAtomValue(currentEditorAtom),
|
||||
useCallback(() => {
|
||||
if (isTipsHidden.quickSearchTips || !popperRef.current) {
|
||||
if (showQuickSearchTips || !popperRef.current) {
|
||||
return;
|
||||
}
|
||||
popperRef.current.update();
|
||||
}, [isTipsHidden.quickSearchTips])
|
||||
}, [showQuickSearchTips])
|
||||
);
|
||||
|
||||
const TipsContent = (
|
||||
@@ -77,9 +80,7 @@ export const WorkspaceHeader = forwardRef<
|
||||
</div>
|
||||
<StyledQuickSearchTipButton
|
||||
data-testid="quick-search-got-it"
|
||||
onClick={() =>
|
||||
setTipsHidden({ ...isTipsHidden, quickSearchTips: true })
|
||||
}
|
||||
onClick={() => setShowQuickSearchTips(false)}
|
||||
>
|
||||
Got it
|
||||
</StyledQuickSearchTipButton>
|
||||
@@ -107,7 +108,7 @@ export const WorkspaceHeader = forwardRef<
|
||||
content={TipsContent}
|
||||
placement="bottom"
|
||||
popperRef={popperRef}
|
||||
open={!isTipsHidden.quickSearchTips}
|
||||
open={showQuickSearchTips}
|
||||
offset={[0, -5]}
|
||||
>
|
||||
<StyledSearchArrowWrapper>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const StyledHeaderContainer = styled('div')<{
|
||||
top: 0,
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
WebkitAppRegion: 'drag',
|
||||
zIndex: 1,
|
||||
zIndex: 'var(--affine-z-index-popover)',
|
||||
'@media (max-width: 768px)': {
|
||||
'&[data-open="true"]': {
|
||||
WebkitAppRegion: 'no-drag',
|
||||
@@ -23,22 +23,20 @@ export const StyledHeaderContainer = styled('div')<{
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledHeader = styled('div')<{ hasWarning: boolean }>(
|
||||
({ theme }) => {
|
||||
return {
|
||||
flexShrink: 0,
|
||||
height: '52px',
|
||||
width: '100%',
|
||||
padding: '0 20px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
zIndex: 99,
|
||||
position: 'relative',
|
||||
};
|
||||
}
|
||||
);
|
||||
export const StyledHeader = styled('div')<{ hasWarning: boolean }>(() => {
|
||||
return {
|
||||
flexShrink: 0,
|
||||
height: '52px',
|
||||
width: '100%',
|
||||
padding: '0 20px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
zIndex: 99,
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledTitleContainer = styled('div')(({ theme }) => ({
|
||||
export const StyledTitleContainer = styled('div')(() => ({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
|
||||
@@ -82,7 +80,7 @@ export const StyledHeaderRightSide = styled('div')({
|
||||
});
|
||||
|
||||
export const StyledBrowserWarning = styled('div')<{ show: boolean }>(
|
||||
({ theme, show }) => {
|
||||
({ show }) => {
|
||||
return {
|
||||
backgroundColor: 'var(--affine-background-warning-color)',
|
||||
color: 'var(--affine-background-warning-color)',
|
||||
@@ -95,7 +93,7 @@ export const StyledBrowserWarning = styled('div')<{ show: boolean }>(
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledCloseButton = styled('div')(({ theme }) => {
|
||||
export const StyledCloseButton = styled('div')(() => {
|
||||
return {
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
@@ -137,7 +135,7 @@ export const StyledSearchArrowWrapper = styled('div')(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => {
|
||||
export const StyledPageListTittleWrapper = styled(StyledTitle)(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
@@ -148,7 +146,7 @@ export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledQuickSearchTipButton = styled('div')(({ theme }) => {
|
||||
export const StyledQuickSearchTipButton = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
marginTop: '12px',
|
||||
@@ -169,3 +167,16 @@ export const StyledQuickSearchTipContent = styled('div')(() => {
|
||||
flexDirection: 'column',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledHorizontalDivider = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
borderTop: `1px solid var(--affine-border-color)`,
|
||||
};
|
||||
});
|
||||
export const StyledHorizontalDividerContainer = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
padding: '14px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getEnvironment } from '@affine/env';
|
||||
import { Trans, useTranslation } from '@affine/i18n';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@@ -23,7 +24,7 @@ export const shouldShowWarning = () => {
|
||||
};
|
||||
|
||||
export const OSWarningMessage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const [notChrome, setNotChrome] = useState(false);
|
||||
const [notGoodVersion, setNotGoodVersion] = useState(false);
|
||||
useEffect(() => {
|
||||
@@ -44,7 +45,7 @@ export const OSWarningMessage: React.FC = () => {
|
||||
</span>
|
||||
);
|
||||
} else if (notGoodVersion) {
|
||||
return <span>{t('upgradeBrowser')}</span>;
|
||||
return <span>{t['upgradeBrowser']()}</span>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { useBlockSuiteWorkspacePageTitle } from '@toeverything/hooks/use-block-suite-workspace-page-title';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import Head from 'next/head';
|
||||
@@ -34,7 +35,7 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
||||
isPreview,
|
||||
}) => {
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const page = blockSuiteWorkspace.getPage(pageId);
|
||||
const page = useBlockSuiteWorkspacePage(blockSuiteWorkspace, pageId);
|
||||
if (!page) {
|
||||
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
|
||||
}
|
||||
@@ -63,7 +64,7 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
||||
style={{
|
||||
height: 'calc(100% - 52px)',
|
||||
}}
|
||||
key={`${workspace.flavour}-${workspace.id}-${[pageId]}`}
|
||||
key={`${workspace.flavour}-${workspace.id}-${pageId}`}
|
||||
mode={isPublic ? 'page' : currentMode}
|
||||
page={page}
|
||||
onInit={useCallback(
|
||||
|
||||
45
apps/web/src/components/pure/OnboardingModal.tsx
Normal file
45
apps/web/src/components/pure/OnboardingModal.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { TourModal } from '@affine/component/tour-modal';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { openOnboardingModalAtom } from '../../atoms';
|
||||
import { guideOnboardingAtom } from '../../atoms/guide';
|
||||
|
||||
type OnboardingModalProps = {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
const getHelperGuide = (): { onBoarding: boolean } | null => {
|
||||
const helperGuide = localStorage.getItem('helper-guide');
|
||||
if (helperGuide) {
|
||||
return JSON.parse(helperGuide);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const OnboardingModal: React.FC<OnboardingModalProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
}) => {
|
||||
const [, setShowOnboarding] = useAtom(guideOnboardingAtom);
|
||||
const [, setOpenOnboarding] = useAtom(openOnboardingModalAtom);
|
||||
const onCloseTourModal = useCallback(() => {
|
||||
setShowOnboarding(false);
|
||||
onClose();
|
||||
}, [onClose, setShowOnboarding]);
|
||||
|
||||
const shouldShow = useMemo(() => {
|
||||
const helperGuide = getHelperGuide();
|
||||
return helperGuide?.onBoarding ?? true;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShow) {
|
||||
setOpenOnboarding(true);
|
||||
}
|
||||
}, [shouldShow, setOpenOnboarding]);
|
||||
return <TourModal open={open} onClose={onCloseTourModal} />;
|
||||
};
|
||||
|
||||
export default OnboardingModal;
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
ModalWrapper,
|
||||
styled,
|
||||
} from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
@@ -35,7 +35,7 @@ export const CreateWorkspaceModal = ({
|
||||
},
|
||||
[handleCreateWorkspace, workspaceName]
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper width={560} height={342} style={{ padding: '10px' }}>
|
||||
@@ -49,8 +49,8 @@ export const CreateWorkspaceModal = ({
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('New Workspace')}</ContentTitle>
|
||||
<p>{t('Workspace description')}</p>
|
||||
<ContentTitle>{t['New Workspace']()}</ContentTitle>
|
||||
<p>{t['Workspace description']()}</p>
|
||||
<Input
|
||||
ref={ref => {
|
||||
if (ref) {
|
||||
@@ -59,7 +59,7 @@ export const CreateWorkspaceModal = ({
|
||||
}}
|
||||
data-testid="create-workspace-input"
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={t('Set a Workspace name')}
|
||||
placeholder={t['Set a Workspace name']()}
|
||||
maxLength={15}
|
||||
minLength={0}
|
||||
onChange={value => {
|
||||
@@ -86,7 +86,7 @@ export const CreateWorkspaceModal = ({
|
||||
handleCreateWorkspace();
|
||||
}}
|
||||
>
|
||||
{t('Create')}
|
||||
{t['Create']()}
|
||||
</Button>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import type React from 'react';
|
||||
import { useRef } from 'react';
|
||||
@@ -17,7 +17,7 @@ export const Upload: React.FC<UploadProps> = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const input_ref = useRef<HTMLInputElement>(null);
|
||||
const _chooseFile = () => {
|
||||
if (input_ref.current) {
|
||||
@@ -37,7 +37,7 @@ export const Upload: React.FC<UploadProps> = ({
|
||||
};
|
||||
return (
|
||||
<UploadStyle onClick={_chooseFile}>
|
||||
{children ?? <Button>{t('Upload')}</Button>}
|
||||
{children ?? <Button>{t['Upload']()}</Button>}
|
||||
<input
|
||||
ref={input_ref}
|
||||
type="file"
|
||||
|
||||
@@ -2,13 +2,15 @@ import { FlexWrapper } from '@affine/component';
|
||||
import { IconButton } from '@affine/component';
|
||||
import { Tooltip } from '@affine/component';
|
||||
import { config } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { AccessTokenMessage } from '@affine/workspace/affine/login';
|
||||
import { CloudWorkspaceIcon, SignOutIcon } from '@blocksuite/icons';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import type React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
||||
import { stringToColour } from '../../../utils';
|
||||
import { StyledFooter, StyledSignInButton, StyleUserInfo } from './styles';
|
||||
|
||||
@@ -19,12 +21,8 @@ export type FooterProps = {
|
||||
};
|
||||
|
||||
export const Footer: React.FC<FooterProps> = ({ user, onLogin, onLogout }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!config.enableLegacyCloud) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const t = useAFFiNEI18N();
|
||||
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
||||
return (
|
||||
<StyledFooter data-testid="workspace-list-modal-footer">
|
||||
{user && (
|
||||
@@ -40,7 +38,7 @@ export const Footer: React.FC<FooterProps> = ({ user, onLogin, onLogout }) => {
|
||||
<p>{user.email}</p>
|
||||
</StyleUserInfo>
|
||||
</FlexWrapper>
|
||||
<Tooltip content={t('Sign out')} disablePortal={true}>
|
||||
<Tooltip content={t['Sign out']()} disablePortal={true}>
|
||||
<IconButton
|
||||
data-testid="workspace-list-modal-sign-out"
|
||||
onClick={() => {
|
||||
@@ -55,6 +53,7 @@ export const Footer: React.FC<FooterProps> = ({ user, onLogin, onLogout }) => {
|
||||
|
||||
{!user && (
|
||||
<StyledSignInButton
|
||||
data-testid="sign-in-button"
|
||||
noBorder
|
||||
bold
|
||||
icon={
|
||||
@@ -63,10 +62,14 @@ export const Footer: React.FC<FooterProps> = ({ user, onLogin, onLogout }) => {
|
||||
</div>
|
||||
}
|
||||
onClick={async () => {
|
||||
onLogin();
|
||||
if (!config.enableLegacyCloud) {
|
||||
setOpen(true);
|
||||
} else {
|
||||
onLogin();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('Sign in')}
|
||||
{t['Sign in']()}
|
||||
</StyledSignInButton>
|
||||
)}
|
||||
</StyledFooter>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
|
||||
export const StyledSplitLine = styled('div')(({ theme }) => {
|
||||
export const StyledSplitLine = styled('div')(() => {
|
||||
return {
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
@@ -15,7 +15,7 @@ export const StyledSplitLine = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceInfo = styled('div')(({ theme }) => {
|
||||
export const StyleWorkspaceInfo = styled('div')(() => {
|
||||
return {
|
||||
marginLeft: '15px',
|
||||
width: '202px',
|
||||
@@ -36,7 +36,7 @@ export const StyleWorkspaceInfo = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceTitle = styled('div')(({ theme }) => {
|
||||
export const StyleWorkspaceTitle = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
fontWeight: 600,
|
||||
@@ -54,7 +54,7 @@ export const StyledFooter = styled('div')({
|
||||
...displayFlex('space-between', 'center'),
|
||||
});
|
||||
|
||||
export const StyleUserInfo = styled('div')(({ theme }) => {
|
||||
export const StyleUserInfo = styled('div')(() => {
|
||||
return {
|
||||
textAlign: 'left',
|
||||
marginLeft: '16px',
|
||||
@@ -73,14 +73,14 @@ export const StyleUserInfo = styled('div')(({ theme }) => {
|
||||
export const StyledModalHeaderLeft = styled('div')(() => {
|
||||
return { ...displayFlex('flex-start', 'center') };
|
||||
});
|
||||
export const StyledModalTitle = styled('div')(({ theme }) => {
|
||||
export const StyledModalTitle = styled('div')(() => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledHelperContainer = styled('div')(({ theme }) => {
|
||||
export const StyledHelperContainer = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-icon-color)',
|
||||
marginLeft: '15px',
|
||||
@@ -128,7 +128,7 @@ export const StyledModalHeader = styled('div')(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInButton = styled(Button)(({ theme }) => {
|
||||
export const StyledSignInButton = styled(Button)(() => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
paddingLeft: 0,
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { MuiFade, Tooltip } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { getEnvironment } from '@affine/env';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon, NewIcon } from '@blocksuite/icons';
|
||||
import { useAtom } from 'jotai';
|
||||
import { lazy, Suspense, useState } from 'react';
|
||||
|
||||
import { openOnboardingModalAtom } from '../../../atoms';
|
||||
import { useCurrentMode } from '../../../hooks/current/use-current-mode';
|
||||
import { ShortcutsModal } from '../shortcuts-modal';
|
||||
import { ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
||||
import {
|
||||
@@ -11,23 +15,30 @@ import {
|
||||
StyledIsland,
|
||||
StyledTriggerWrapper,
|
||||
} from './style';
|
||||
|
||||
const env = getEnvironment();
|
||||
const ContactModal = lazy(() =>
|
||||
import('@affine/component/contact-modal').then(({ ContactModal }) => ({
|
||||
default: ContactModal,
|
||||
}))
|
||||
);
|
||||
|
||||
export type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts';
|
||||
const DEFAULT_SHOW_LIST: IslandItemNames[] = [
|
||||
'whatNew',
|
||||
'contact',
|
||||
'shortcuts',
|
||||
];
|
||||
const DESKTOP_SHOW_LIST: IslandItemNames[] = [...DEFAULT_SHOW_LIST, 'guide'];
|
||||
export type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts' | 'guide';
|
||||
export const HelpIsland = ({
|
||||
showList = ['whatNew', 'contact', 'shortcuts'],
|
||||
showList = env.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST,
|
||||
}: {
|
||||
showList?: IslandItemNames[];
|
||||
}) => {
|
||||
const mode = useCurrentMode();
|
||||
const [, setOpenOnboarding] = useAtom(openOnboardingModalAtom);
|
||||
const [spread, setShowSpread] = useState(false);
|
||||
// const { triggerShortcutsModal, triggerContactModal } = useModal();
|
||||
// const blockHub = useGlobalState(store => store.blockHub);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
//
|
||||
// useEffect(() => {
|
||||
// blockHub?.blockHubStatusUpdated.on(status => {
|
||||
@@ -53,12 +64,13 @@ export const HelpIsland = ({
|
||||
onClick={() => {
|
||||
setShowSpread(!spread);
|
||||
}}
|
||||
inEdgelessPage={mode === 'edgeless'}
|
||||
>
|
||||
<StyledAnimateWrapper
|
||||
style={{ height: spread ? `${showList.length * 44}px` : 0 }}
|
||||
>
|
||||
{showList.includes('whatNew') && (
|
||||
<Tooltip content={t("Discover what's new!")} placement="left-end">
|
||||
<Tooltip content={t["Discover what's new!"]()} placement="left-end">
|
||||
<StyledIconWrapper
|
||||
data-testid="right-bottom-change-log-icon"
|
||||
onClick={() => {
|
||||
@@ -73,7 +85,7 @@ export const HelpIsland = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('contact') && (
|
||||
<Tooltip content={t('Contact Us')} placement="left-end">
|
||||
<Tooltip content={t['Contact Us']()} placement="left-end">
|
||||
<StyledIconWrapper
|
||||
data-testid="right-bottom-contact-us-icon"
|
||||
onClick={() => {
|
||||
@@ -86,7 +98,7 @@ export const HelpIsland = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('shortcuts') && (
|
||||
<Tooltip content={t('Keyboard Shortcuts')} placement="left-end">
|
||||
<Tooltip content={t['Keyboard Shortcuts']()} placement="left-end">
|
||||
<StyledIconWrapper
|
||||
data-testid="shortcuts-icon"
|
||||
onClick={() => {
|
||||
@@ -98,9 +110,22 @@ export const HelpIsland = ({
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('guide') && (
|
||||
<Tooltip content={'Easy Guide'} placement="left-end">
|
||||
<StyledIconWrapper
|
||||
data-testid="easy-guide"
|
||||
onClick={() => {
|
||||
setShowSpread(false);
|
||||
setOpenOnboarding(true);
|
||||
}}
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
</StyledAnimateWrapper>
|
||||
|
||||
<Tooltip content={t('Help and Feedback')} placement="left-end">
|
||||
<Tooltip content={t['Help and Feedback']()} placement="left-end">
|
||||
<MuiFade in={!spread} data-testid="faq-icon">
|
||||
<StyledTriggerWrapper>
|
||||
<HelpIcon />
|
||||
|
||||
@@ -2,12 +2,17 @@ import { displayFlex, positionAbsolute, styled } from '@affine/component';
|
||||
|
||||
export const StyledIsland = styled('div')<{
|
||||
spread: boolean;
|
||||
}>(({ theme, spread }) => {
|
||||
inEdgelessPage?: boolean;
|
||||
}>(({ spread, inEdgelessPage }) => {
|
||||
return {
|
||||
transition: 'box-shadow 0.2s',
|
||||
width: '44px',
|
||||
position: 'relative',
|
||||
boxShadow: spread ? 'var(--affine-menu-shadow)' : 'unset',
|
||||
boxShadow: spread
|
||||
? 'var(--affine-menu-shadow)'
|
||||
: inEdgelessPage
|
||||
? 'var(--affine-menu-shadow)'
|
||||
: 'unset',
|
||||
padding: '0 4px 44px',
|
||||
borderRadius: '10px',
|
||||
background: spread
|
||||
@@ -15,6 +20,7 @@ export const StyledIsland = styled('div')<{
|
||||
: 'var(--affine-background-primary-color)',
|
||||
':hover': {
|
||||
background: spread ? null : 'var(--affine-white)',
|
||||
boxShadow: spread ? null : 'var(--affine-menu-shadow)',
|
||||
},
|
||||
'::after': {
|
||||
content: '""',
|
||||
@@ -31,7 +37,7 @@ export const StyledIsland = styled('div')<{
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledIconWrapper = styled('div')(({ theme }) => {
|
||||
export const StyledIconWrapper = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-icon-color)',
|
||||
...displayFlex('center', 'center'),
|
||||
@@ -57,7 +63,7 @@ export const StyledAnimateWrapper = styled('div')(() => ({
|
||||
|
||||
export const StyledTriggerWrapper = styled('div')<{
|
||||
spread?: boolean;
|
||||
}>(({ theme, spread }) => {
|
||||
}>(({ spread }) => {
|
||||
return {
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { styled } from '@affine/component';
|
||||
import { AffineLoading } from '@affine/component/affine-loading';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { memo, Suspense } from 'react';
|
||||
|
||||
export const Loading = memo(function Loading() {
|
||||
@@ -36,11 +36,11 @@ const StyledLoadingContainer = styled('div')(() => {
|
||||
});
|
||||
|
||||
export const PageLoading = ({ text }: { text?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<StyledLoadingContainer>
|
||||
<Loading />
|
||||
<h1>{text ? text : t('Loading')}</h1>
|
||||
<h1>{text ? text : t['Loading']()}</h1>
|
||||
</StyledLoadingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IconButton } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||
|
||||
const StyledIconButtonWithAnimate = styled(IconButton)(({ theme }) => {
|
||||
const StyledIconButtonWithAnimate = styled(IconButton)(() => {
|
||||
return {
|
||||
svg: {
|
||||
transition: 'transform 0.15s ease-in-out',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { initPage } from '@affine/env/blocksuite';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { PageBlockModel } from '@blocksuite/blocks';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import { assertEquals, nanoid } from '@blocksuite/store';
|
||||
@@ -27,7 +27,7 @@ export const Footer: React.FC<FooterProps> = ({
|
||||
router,
|
||||
}) => {
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToPage } = useRouterHelper(router);
|
||||
const MAX_QUERY_SHOW_LENGTH = 20;
|
||||
const normalizedQuery =
|
||||
@@ -60,9 +60,9 @@ export const Footer: React.FC<FooterProps> = ({
|
||||
<StyledModalFooterContent>
|
||||
<PlusIcon />
|
||||
{query ? (
|
||||
<span>{t('New Keyword Page', { query: normalizedQuery })}</span>
|
||||
<span>{t['New Keyword Page']({ query: normalizedQuery })}</span>
|
||||
) : (
|
||||
<span>{t('New Page')}</span>
|
||||
<span>{t['New Page']()}</span>
|
||||
)}
|
||||
</StyledModalFooterContent>
|
||||
</Command.Item>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { Command } from 'cmdk';
|
||||
@@ -41,7 +41,7 @@ export const PublishedResults: FC<PublishedResultsProps> = ({
|
||||
// router.push('/404');
|
||||
// });
|
||||
// }, [router, dataCenter, setPublishWorkspaceName]);
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
useEffect(() => {
|
||||
setResults(blockSuiteWorkspace.search(query));
|
||||
//Save the Map<BlockId, PageId> obtained from the search as state
|
||||
@@ -57,7 +57,7 @@ export const PublishedResults: FC<PublishedResultsProps> = ({
|
||||
{query ? (
|
||||
resultsPageMeta.length ? (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
heading={t['Find results']({ number: `${resultsPageMeta.length}` })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
@@ -85,7 +85,7 @@ export const PublishedResults: FC<PublishedResultsProps> = ({
|
||||
</Command.Group>
|
||||
) : (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<span>{t['Find 0 result']()}</span>
|
||||
<Image
|
||||
src="/imgs/no-result.svg"
|
||||
alt="no result"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
@@ -36,7 +36,7 @@ export const Results: FC<ResultsProps> = ({
|
||||
const List = useSwitchToConfig(blockSuiteWorkspace.id);
|
||||
|
||||
const recentlyViewed = useRecentlyViewed();
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToPage } = useRouterHelper(router);
|
||||
const results = blockSuiteWorkspace.search(query);
|
||||
|
||||
@@ -61,7 +61,7 @@ export const Results: FC<ResultsProps> = ({
|
||||
return (
|
||||
<>
|
||||
{recentlyViewedItem.length > 0 && (
|
||||
<Command.Group heading={t('Recent')}>
|
||||
<Command.Group heading={t['Recent']()}>
|
||||
{recentlyViewedItem.map(recent => {
|
||||
const page = pageList.find(page => recent.id === page.id);
|
||||
assertExists(page);
|
||||
@@ -87,7 +87,7 @@ export const Results: FC<ResultsProps> = ({
|
||||
})}
|
||||
</Command.Group>
|
||||
)}
|
||||
<Command.Group heading={t('Jump to')}>
|
||||
<Command.Group heading={t['Jump to']()}>
|
||||
{List.map(link => {
|
||||
return (
|
||||
<Command.Item
|
||||
@@ -112,7 +112,7 @@ export const Results: FC<ResultsProps> = ({
|
||||
if (!resultsPageMeta.length) {
|
||||
return (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<span>{t['Find 0 result']()}</span>
|
||||
<Image
|
||||
src="/imgs/no-result.svg"
|
||||
alt="no result"
|
||||
@@ -124,7 +124,7 @@ export const Results: FC<ResultsProps> = ({
|
||||
}
|
||||
return (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
heading={t['Find results']({ number: `${resultsPageMeta.length}` })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
DeleteTemporarilyIcon,
|
||||
FavoriteIcon,
|
||||
@@ -16,26 +16,26 @@ export const useSwitchToConfig = (
|
||||
href: string;
|
||||
icon: FC<SVGProps<SVGSVGElement>>;
|
||||
}[] => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('All pages'),
|
||||
title: t['All pages'](),
|
||||
href: pathGenerator.all(workspaceId),
|
||||
icon: FolderIcon,
|
||||
},
|
||||
{
|
||||
title: t('Favorites'),
|
||||
title: t['Favorites'](),
|
||||
href: pathGenerator.favorite(workspaceId),
|
||||
icon: FavoriteIcon,
|
||||
},
|
||||
{
|
||||
title: t('Workspace Settings'),
|
||||
title: t['Workspace Settings'](),
|
||||
href: pathGenerator.setting(workspaceId),
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: t('Trash'),
|
||||
title: t['Trash'](),
|
||||
href: pathGenerator.trash(workspaceId),
|
||||
icon: DeleteTemporarilyIcon,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Modal, ModalWrapper } from '@affine/component';
|
||||
import { getEnvironment } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Command } from 'cmdk';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import type React from 'react';
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { Footer } from './Footer';
|
||||
import { NavigationPath } from './navigation-path';
|
||||
import { PublishedResults } from './PublishedResults';
|
||||
import { Results } from './Results';
|
||||
import { SearchInput } from './SearchInput';
|
||||
@@ -45,7 +44,7 @@ export const QuickSearchModal: React.FC<QuickSearchModalProps> = ({
|
||||
router,
|
||||
blockSuiteWorkspace,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [loading, startTransition] = useTransition();
|
||||
const [query, _setQuery] = useState('');
|
||||
@@ -110,12 +109,12 @@ export const QuickSearchModal: React.FC<QuickSearchModalProps> = ({
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<NavigationPath
|
||||
{/* <NavigationPath
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
onJumpToPage={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
<Command
|
||||
shouldFilter={false}
|
||||
//Handle KeyboardEvent conflicts with blocksuite
|
||||
@@ -145,10 +144,10 @@ export const QuickSearchModal: React.FC<QuickSearchModalProps> = ({
|
||||
}}
|
||||
placeholder={
|
||||
isPublicWorkspace
|
||||
? t('Quick search placeholder2', {
|
||||
? t['Quick search placeholder2']({
|
||||
workspace: publishWorkspaceName,
|
||||
})
|
||||
: t('Quick search placeholder')
|
||||
: t['Quick search placeholder']()
|
||||
}
|
||||
/>
|
||||
<StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconButton, Tooltip, TreeView } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
ArrowRightSmallIcon,
|
||||
CollapseIcon,
|
||||
@@ -35,7 +35,7 @@ export const NavigationPath = ({
|
||||
}) => {
|
||||
const metas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const [openExtend, setOpenExtend] = useState(false);
|
||||
const pageId = propsPageId ?? router.query.pageId;
|
||||
@@ -59,7 +59,7 @@ export const NavigationPath = ({
|
||||
<>
|
||||
<StyledNavigationPathContainer data-testid="navigation-path">
|
||||
{openExtend ? (
|
||||
<span>{t('Navigation Path')}</span>
|
||||
<span>{t['Navigation Path']()}</span>
|
||||
) : (
|
||||
pathData.path.map((meta, index) => {
|
||||
const isLast = index === pathData.path.length - 1;
|
||||
@@ -96,7 +96,9 @@ export const NavigationPath = ({
|
||||
)}
|
||||
<Tooltip
|
||||
content={
|
||||
openExtend ? t('Back to Quick Search') : t('View Navigation Path')
|
||||
openExtend
|
||||
? t['Back to Quick Search']()
|
||||
: t['View Navigation Path']()
|
||||
}
|
||||
placement="top"
|
||||
disablePortal={true}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
|
||||
export const StyledNavigationPathContainer = styled('div')(({ theme }) => {
|
||||
export const StyledNavigationPathContainer = styled('div')(() => {
|
||||
return {
|
||||
height: '46px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
@@ -24,7 +24,7 @@ export const StyledNavigationPathContainer = styled('div')(({ theme }) => {
|
||||
});
|
||||
|
||||
export const StyledNavPathLink = styled('div')<{ active?: boolean }>(
|
||||
({ theme, active }) => {
|
||||
({ active }) => {
|
||||
return {
|
||||
color: active
|
||||
? 'var(--affine-text-primary-color)'
|
||||
@@ -45,7 +45,7 @@ export const StyledNavPathLink = styled('div')<{ active?: boolean }>(
|
||||
);
|
||||
|
||||
export const StyledNavPathExtendContainer = styled('div')<{ show: boolean }>(
|
||||
({ show, theme }) => {
|
||||
({ show }) => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
|
||||
export const StyledContent = styled('div')(({ theme }) => {
|
||||
export const StyledContent = styled('div')(() => {
|
||||
return {
|
||||
minHeight: '290px',
|
||||
maxHeight: '70vh',
|
||||
@@ -35,7 +35,7 @@ export const StyledContent = styled('div')(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledJumpTo = styled('div')(({ theme }) => {
|
||||
export const StyledJumpTo = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('center', 'start'),
|
||||
flexDirection: 'column',
|
||||
@@ -47,7 +47,7 @@ export const StyledJumpTo = styled('div')(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledNotFound = styled('div')(({ theme }) => {
|
||||
export const StyledNotFound = styled('div')(() => {
|
||||
return {
|
||||
width: '612px',
|
||||
...displayFlex('center', 'center'),
|
||||
@@ -68,7 +68,7 @@ export const StyledNotFound = styled('div')(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledInputContent = styled('div')(({ theme }) => {
|
||||
export const StyledInputContent = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('space-between', 'center'),
|
||||
input: {
|
||||
@@ -85,7 +85,7 @@ export const StyledInputContent = styled('div')(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledShortcut = styled('div')(({ theme }) => {
|
||||
export const StyledShortcut = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-placeholder-color)',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
@@ -93,7 +93,7 @@ export const StyledShortcut = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledLabel = styled('label')(({ theme }) => {
|
||||
export const StyledLabel = styled('label')(() => {
|
||||
return {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
@@ -109,7 +109,7 @@ export const StyledModalHeader = styled('div')(() => {
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
export const StyledModalDivider = styled('div')(({ theme }) => {
|
||||
export const StyledModalDivider = styled('div')(() => {
|
||||
return {
|
||||
width: 'auto',
|
||||
height: '0',
|
||||
@@ -118,7 +118,7 @@ export const StyledModalDivider = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalFooter = styled('div')(({ theme }) => {
|
||||
export const StyledModalFooter = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'inherit',
|
||||
lineHeight: '22px',
|
||||
@@ -142,7 +142,7 @@ export const StyledModalFooter = styled('div')(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledModalFooterContent = styled('button')(({ theme }) => {
|
||||
export const StyledModalFooterContent = styled('button')(() => {
|
||||
return {
|
||||
width: '600px',
|
||||
height: '32px',
|
||||
|
||||
@@ -1,90 +1,91 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
interface ShortcutTip {
|
||||
[x: string]: string;
|
||||
}
|
||||
export const useMacKeyboardShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return {
|
||||
[t('Undo')]: '⌘+Z',
|
||||
[t('Redo')]: '⌘+⇧+Z',
|
||||
[t('Bold')]: '⌘+B',
|
||||
[t('Italic')]: '⌘+I',
|
||||
[t('Underline')]: '⌘+U',
|
||||
[t('Strikethrough')]: '⌘+⇧+S',
|
||||
[t('Inline code')]: ' ⌘+E',
|
||||
[t('Code block')]: '⌘+⌥+C',
|
||||
[t('Link')]: '⌘+K',
|
||||
[t('Quick search')]: '⌘+K',
|
||||
[t('Body text')]: '⌘+⌥+0',
|
||||
[t('Heading', { number: '1' })]: '⌘+⌥+1',
|
||||
[t('Heading', { number: '2' })]: '⌘+⌥+2',
|
||||
[t('Heading', { number: '3' })]: '⌘+⌥+3',
|
||||
[t('Heading', { number: '4' })]: '⌘+⌥+4',
|
||||
[t('Heading', { number: '5' })]: '⌘+⌥+5',
|
||||
[t('Heading', { number: '6' })]: '⌘+⌥+6',
|
||||
[t('Increase indent')]: 'Tab',
|
||||
[t('Reduce indent')]: '⇧+Tab',
|
||||
[t['Undo']()]: '⌘+Z',
|
||||
[t['Redo']()]: '⌘+⇧+Z',
|
||||
[t['Bold']()]: '⌘+B',
|
||||
[t['Italic']()]: '⌘+I',
|
||||
[t['Underline']()]: '⌘+U',
|
||||
[t['Strikethrough']()]: '⌘+⇧+S',
|
||||
[t['Inline code']()]: ' ⌘+E',
|
||||
[t['Code block']()]: '⌘+⌥+C',
|
||||
[t['Link']()]: '⌘+K',
|
||||
[t['Quick search']()]: '⌘+K',
|
||||
[t['Body text']()]: '⌘+⌥+0',
|
||||
[t['Heading']({ number: '1' })]: '⌘+⌥+1',
|
||||
[t['Heading']({ number: '2' })]: '⌘+⌥+2',
|
||||
[t['Heading']({ number: '3' })]: '⌘+⌥+3',
|
||||
[t['Heading']({ number: '4' })]: '⌘+⌥+4',
|
||||
[t['Heading']({ number: '5' })]: '⌘+⌥+5',
|
||||
[t['Heading']({ number: '6' })]: '⌘+⌥+6',
|
||||
[t['Increase indent']()]: 'Tab',
|
||||
[t['Reduce indent']()]: '⇧+Tab',
|
||||
};
|
||||
};
|
||||
|
||||
export const useMacMarkdownShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return {
|
||||
[t('Bold')]: '**Text** ',
|
||||
[t('Italic')]: '*Text* ',
|
||||
[t('Underline')]: '~Text~ ',
|
||||
[t('Strikethrough')]: '~~Text~~ ',
|
||||
[t('Divider')]: '***',
|
||||
[t('Inline code')]: '`Text` ',
|
||||
[t('Code block')]: '``` Space',
|
||||
[t('Heading', { number: '1' })]: '# Text',
|
||||
[t('Heading', { number: '2' })]: '## Text',
|
||||
[t('Heading', { number: '3' })]: '### Text',
|
||||
[t('Heading', { number: '4' })]: '#### Text',
|
||||
[t('Heading', { number: '5' })]: '##### Text',
|
||||
[t('Heading', { number: '6' })]: '###### Text',
|
||||
[t['Bold']()]: '**Text** ',
|
||||
[t['Italic']()]: '*Text* ',
|
||||
[t['Underline']()]: '~Text~ ',
|
||||
[t['Strikethrough']()]: '~~Text~~ ',
|
||||
[t['Divider']()]: '***',
|
||||
[t['Inline code']()]: '`Text` ',
|
||||
[t['Code block']()]: '``` Space',
|
||||
[t['Heading']({ number: '1' })]: '# Text',
|
||||
[t['Heading']({ number: '2' })]: '## Text',
|
||||
[t['Heading']({ number: '3' })]: '### Text',
|
||||
[t['Heading']({ number: '4' })]: '#### Text',
|
||||
[t['Heading']({ number: '5' })]: '##### Text',
|
||||
[t['Heading']({ number: '6' })]: '###### Text',
|
||||
};
|
||||
};
|
||||
|
||||
export const useWindowsKeyboardShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return {
|
||||
[t('Undo')]: 'Ctrl+Z',
|
||||
[t('Redo')]: 'Ctrl+Y',
|
||||
[t('Bold')]: 'Ctrl+B',
|
||||
[t('Italic')]: 'Ctrl+I',
|
||||
[t('Underline')]: 'Ctrl+U',
|
||||
[t('Strikethrough')]: 'Ctrl+Shift+S',
|
||||
[t('Inline code')]: ' Ctrl+E',
|
||||
[t('Code block')]: 'Ctrl+Alt+C',
|
||||
[t('Link')]: 'Ctrl+K',
|
||||
[t('Quick search')]: 'Ctrl+K',
|
||||
[t('Body text')]: 'Ctrl+Shift+0',
|
||||
[t('Heading', { number: '1' })]: 'Ctrl+Shift+1',
|
||||
[t('Heading', { number: '2' })]: 'Ctrl+Shift+2',
|
||||
[t('Heading', { number: '3' })]: 'Ctrl+Shift+3',
|
||||
[t('Heading', { number: '4' })]: 'Ctrl+Shift+4',
|
||||
[t('Heading', { number: '5' })]: 'Ctrl+Shift+5',
|
||||
[t('Heading', { number: '6' })]: 'Ctrl+Shift+6',
|
||||
[t('Increase indent')]: 'Tab',
|
||||
[t('Reduce indent')]: 'Shift+Tab',
|
||||
[t['Undo']()]: 'Ctrl+Z',
|
||||
[t['Redo']()]: 'Ctrl+Y',
|
||||
[t['Bold']()]: 'Ctrl+B',
|
||||
[t['Italic']()]: 'Ctrl+I',
|
||||
[t['Underline']()]: 'Ctrl+U',
|
||||
[t['Strikethrough']()]: 'Ctrl+Shift+S',
|
||||
[t['Inline code']()]: ' Ctrl+E',
|
||||
[t['Code block']()]: 'Ctrl+Alt+C',
|
||||
[t['Link']()]: 'Ctrl+K',
|
||||
[t['Quick search']()]: 'Ctrl+K',
|
||||
[t['Body text']()]: 'Ctrl+Shift+0',
|
||||
[t['Heading']({ number: '1' })]: 'Ctrl+Shift+1',
|
||||
[t['Heading']({ number: '2' })]: 'Ctrl+Shift+2',
|
||||
[t['Heading']({ number: '3' })]: 'Ctrl+Shift+3',
|
||||
[t['Heading']({ number: '4' })]: 'Ctrl+Shift+4',
|
||||
[t['Heading']({ number: '5' })]: 'Ctrl+Shift+5',
|
||||
[t['Heading']({ number: '6' })]: 'Ctrl+Shift+6',
|
||||
[t['Increase indent']()]: 'Tab',
|
||||
[t['Reduce indent']()]: 'Shift+Tab',
|
||||
};
|
||||
};
|
||||
export const useWinMarkdownShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return {
|
||||
[t('Bold')]: '**Text** ',
|
||||
[t('Italic')]: '*Text* ',
|
||||
[t('Underline')]: '~Text~ ',
|
||||
[t('Strikethrough')]: '~~Text~~ ',
|
||||
[t('Divider')]: '***',
|
||||
[t('Inline code')]: '`Text` ',
|
||||
[t('Code block')]: '``` Text',
|
||||
[t('Heading', { number: '1' })]: '# Text',
|
||||
[t('Heading', { number: '2' })]: '## Text',
|
||||
[t('Heading', { number: '3' })]: '### Text',
|
||||
[t('Heading', { number: '4' })]: '#### Text',
|
||||
[t('Heading', { number: '5' })]: '##### Text',
|
||||
[t('Heading', { number: '6' })]: '###### Text',
|
||||
[t['Bold']()]: '**Text** ',
|
||||
[t['Italic']()]: '*Text* ',
|
||||
[t['Underline']()]: '~Text~ ',
|
||||
[t['Strikethrough']()]: '~~Text~~ ',
|
||||
[t['Divider']()]: '***',
|
||||
[t['Inline code']()]: '`Text` ',
|
||||
[t['Code block']()]: '``` Text',
|
||||
[t['Heading']({ number: '1' })]: '# Text',
|
||||
[t['Heading']({ number: '2' })]: '## Text',
|
||||
[t['Heading']({ number: '3' })]: '### Text',
|
||||
[t['Heading']({ number: '4' })]: '#### Text',
|
||||
[t['Heading']({ number: '5' })]: '##### Text',
|
||||
[t['Heading']({ number: '6' })]: '###### Text',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
MuiSlide,
|
||||
} from '@affine/component';
|
||||
import { getEnvironment } from '@affine/env';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
@@ -32,7 +32,7 @@ const checkIsMac = () => {
|
||||
};
|
||||
|
||||
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
const macMarkdownShortcuts = useMacMarkdownShortcuts();
|
||||
const winMarkdownShortcuts = useWinMarkdownShortcuts();
|
||||
const macKeyboardShortcuts = useMacKeyboardShortcuts();
|
||||
@@ -59,7 +59,7 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
<StyledModalHeader>
|
||||
<StyledTitle>
|
||||
<KeyboardIcon />
|
||||
{t('Shortcuts')}
|
||||
{t['Shortcuts']()}
|
||||
</StyledTitle>
|
||||
|
||||
<ModalCloseButton
|
||||
@@ -73,7 +73,7 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
/>
|
||||
</StyledModalHeader>
|
||||
<StyledSubTitle style={{ marginTop: 0 }}>
|
||||
{t('Keyboard Shortcuts')}
|
||||
{t['Keyboard Shortcuts']()}
|
||||
</StyledSubTitle>
|
||||
{Object.entries(keyboardShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
@@ -83,7 +83,7 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
<StyledSubTitle>{t('Markdown Syntax')}</StyledSubTitle>
|
||||
<StyledSubTitle>{t['Markdown Syntax']()}</StyledSubTitle>
|
||||
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<StyledListItem key={title}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
|
||||
export const StyledShortcutsModal = styled('div')(({ theme }) => ({
|
||||
export const StyledShortcutsModal = styled('div')(() => ({
|
||||
width: '288px',
|
||||
height: '74vh',
|
||||
paddingBottom: '28px',
|
||||
@@ -16,7 +16,7 @@ export const StyledShortcutsModal = styled('div')(({ theme }) => ({
|
||||
margin: 'auto',
|
||||
zIndex: 'var(--affine-z-index-modal)',
|
||||
}));
|
||||
export const StyledTitle = styled('div')(({ theme }) => ({
|
||||
export const StyledTitle = styled('div')(() => ({
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
@@ -28,7 +28,7 @@ export const StyledTitle = styled('div')(({ theme }) => ({
|
||||
color: 'var(--affine-primary-color)',
|
||||
},
|
||||
}));
|
||||
export const StyledSubTitle = styled('div')(({ theme }) => ({
|
||||
export const StyledSubTitle = styled('div')(() => ({
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
height: '34px',
|
||||
@@ -49,7 +49,7 @@ export const StyledModalHeader = styled('div')(() => ({
|
||||
transition: 'background-color 0.5s',
|
||||
}));
|
||||
|
||||
export const StyledListItem = styled('div')(({ theme }) => ({
|
||||
export const StyledListItem = styled('div')(() => ({
|
||||
height: '34px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { AccessTokenMessage } from '@affine/workspace/affine/login';
|
||||
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
@@ -15,7 +15,6 @@ import { useCallback } from 'react';
|
||||
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
import { Footer } from '../footer';
|
||||
import { LanguageMenu } from './language-menu';
|
||||
import {
|
||||
StyledCreateWorkspaceCard,
|
||||
StyledHelperContainer,
|
||||
@@ -24,7 +23,6 @@ import {
|
||||
StyledModalHeaderLeft,
|
||||
StyledModalTitle,
|
||||
StyledOperationWrapper,
|
||||
StyledSplitLine,
|
||||
StyleWorkspaceAdd,
|
||||
StyleWorkspaceInfo,
|
||||
StyleWorkspaceTitle,
|
||||
@@ -59,7 +57,7 @@ export const WorkspaceListModal = ({
|
||||
currentWorkspaceId,
|
||||
onMoveWorkspace,
|
||||
}: WorkspaceModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
@@ -73,9 +71,9 @@ export const WorkspaceListModal = ({
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledModalTitle>{t('My Workspaces')}</StyledModalTitle>
|
||||
<StyledModalTitle>{t['My Workspaces']()}</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t('Workspace description')}
|
||||
content={t['Workspace description']()}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
@@ -86,8 +84,6 @@ export const WorkspaceListModal = ({
|
||||
</StyledModalHeaderLeft>
|
||||
|
||||
<StyledOperationWrapper>
|
||||
<LanguageMenu />
|
||||
<StyledSplitLine />
|
||||
<ModalCloseButton
|
||||
data-testid="close-workspace-modal"
|
||||
onClick={() => {
|
||||
@@ -128,8 +124,8 @@ export const WorkspaceListModal = ({
|
||||
</StyleWorkspaceAdd>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>{t('New Workspace')}</StyleWorkspaceTitle>
|
||||
<p>{t('Create Or Import')}</p>
|
||||
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
|
||||
<p>{t['Create Or Import']()}</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCreateWorkspaceCard>
|
||||
</StyledModalContent>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Button, Menu, MenuItem, styled } from '@affine/component';
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const LanguageMenuContent: FC = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const changeLanguage = useCallback(
|
||||
(event: string) => {
|
||||
i18n.changeLanguage(event);
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<ListItem
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu: React.FC = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
content={(<LanguageMenuContent />) as ReactElement}
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button
|
||||
icon={<ArrowDownSmallIcon />}
|
||||
iconPosition="end"
|
||||
noBorder={true}
|
||||
style={{ textTransform: 'capitalize', padding: '0 12px' }}
|
||||
data-testid="language-menu-button"
|
||||
>
|
||||
{currentLanguage?.originalName}
|
||||
</Button>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItem = styled(MenuItem)(({ theme }) => ({
|
||||
height: '38px',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
textTransform: 'capitalize',
|
||||
padding: '0 24px',
|
||||
}));
|
||||
@@ -1,6 +1,6 @@
|
||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
|
||||
export const StyledSplitLine = styled('div')(({ theme }) => {
|
||||
export const StyledSplitLine = styled('div')(() => {
|
||||
return {
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
@@ -9,7 +9,7 @@ export const StyledSplitLine = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceInfo = styled('div')(({ theme }) => {
|
||||
export const StyleWorkspaceInfo = styled('div')(() => {
|
||||
return {
|
||||
marginLeft: '15px',
|
||||
width: '202px',
|
||||
@@ -30,7 +30,7 @@ export const StyleWorkspaceInfo = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceTitle = styled('div')(({ theme }) => {
|
||||
export const StyleWorkspaceTitle = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
fontWeight: 600,
|
||||
@@ -41,13 +41,13 @@ export const StyleWorkspaceTitle = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCreateWorkspaceCard = styled('div')(({ theme }) => {
|
||||
export const StyledCreateWorkspaceCard = styled('div')(() => {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '124px',
|
||||
cursor: 'pointer',
|
||||
padding: '16px',
|
||||
boxShadow: 'var(--affine-shadow)',
|
||||
boxShadow: 'var(--affine-shadow-1)',
|
||||
borderRadius: '12px',
|
||||
transition: 'all .1s',
|
||||
background: 'var(--affine-white-80)',
|
||||
@@ -68,14 +68,14 @@ export const StyledCreateWorkspaceCard = styled('div')(({ theme }) => {
|
||||
export const StyledModalHeaderLeft = styled('div')(() => {
|
||||
return { ...displayFlex('flex-start', 'center') };
|
||||
});
|
||||
export const StyledModalTitle = styled('div')(({ theme }) => {
|
||||
export const StyledModalTitle = styled('div')(() => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledHelperContainer = styled('div')(({ theme }) => {
|
||||
export const StyledHelperContainer = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-icon-color)',
|
||||
marginLeft: '15px',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { displayFlex, textEllipsis } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
export const StyledSelectorContainer = styled('div')(({ theme }) => {
|
||||
export const StyledSelectorContainer = styled('div')(() => {
|
||||
return {
|
||||
height: '58px',
|
||||
display: 'flex',
|
||||
@@ -32,7 +32,7 @@ export const StyledWorkspaceName = styled('div')(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledWorkspaceStatus = styled('div')(({ theme }) => {
|
||||
export const StyledWorkspaceStatus = styled('div')(() => {
|
||||
return {
|
||||
height: '22px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
|
||||
@@ -1,30 +1,15 @@
|
||||
import ChangeLogComponent from '@affine/component/changeLog';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
useGuideHidden,
|
||||
useGuideHiddenUntilNextUpdate,
|
||||
} from '../../../../hooks/use-is-first-load';
|
||||
import { guideChangeLogAtom } from '../../../../atoms/guide';
|
||||
|
||||
export const ChangeLog = () => {
|
||||
const [guideHidden, setGuideHidden] = useGuideHidden();
|
||||
const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] =
|
||||
useGuideHiddenUntilNextUpdate();
|
||||
const [showChangeLogTips, setShowChangeLogTips] = useAtom(guideChangeLogAtom);
|
||||
const onCloseWhatsNew = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
setGuideHiddenUntilNextUpdate({
|
||||
...guideHiddenUntilNextUpdate,
|
||||
changeLog: true,
|
||||
});
|
||||
setGuideHidden({ ...guideHidden, changeLog: true });
|
||||
}, 300);
|
||||
}, [
|
||||
guideHidden,
|
||||
guideHiddenUntilNextUpdate,
|
||||
setGuideHidden,
|
||||
setGuideHiddenUntilNextUpdate,
|
||||
]);
|
||||
if (guideHiddenUntilNextUpdate.changeLog) {
|
||||
setShowChangeLogTips(false);
|
||||
}, [setShowChangeLogTips]);
|
||||
if (!showChangeLogTips) {
|
||||
return <></>;
|
||||
}
|
||||
return <ChangeLogComponent onCloseWhatsNew={onCloseWhatsNew} />;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import { StyledCollapseItem } from '../shared-styles';
|
||||
|
||||
export const EmptyItem = () => {
|
||||
const { t } = useTranslation();
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<StyledCollapseItem disable={true} textWrap={true}>
|
||||
{t('Favorite pages for easy access')}
|
||||
{t['Favorite pages for easy access']()}
|
||||
</StyledCollapseItem>
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user