mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-10 19:38:39 +00:00
Compare commits
13 Commits
v0.7.0-can
...
v0.7.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
557a7c3360 | ||
|
|
44580f6af0 | ||
|
|
5d75ceeeb5 | ||
|
|
8d5330df74 | ||
|
|
761965240d | ||
|
|
ad32ed5dd5 | ||
|
|
6a4f70cf43 | ||
|
|
3996955e3b | ||
|
|
1c8f1a05d0 | ||
|
|
bbac03107e | ||
|
|
32f064c2de | ||
|
|
39704bc812 | ||
|
|
a421265483 |
52
.eslintrc.js
52
.eslintrc.js
@@ -80,15 +80,19 @@ const config = {
|
||||
'react',
|
||||
'@typescript-eslint',
|
||||
'simple-import-sort',
|
||||
'sonarjs',
|
||||
'import',
|
||||
'unused-imports',
|
||||
'unicorn',
|
||||
],
|
||||
rules: {
|
||||
'array-callback-return': 'error',
|
||||
'no-undef': 'off',
|
||||
'no-empty': 'off',
|
||||
'no-func-assign': 'off',
|
||||
'no-cond-assign': 'off',
|
||||
'no-constant-binary-expression': 'error',
|
||||
'no-constructor-return': 'error',
|
||||
'react/prop-types': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
@@ -138,6 +142,21 @@ const config = {
|
||||
ignore: ['^\\[[a-zA-Z0-9-_]+\\]\\.tsx$'],
|
||||
},
|
||||
],
|
||||
'sonarjs/no-all-duplicated-branches': 'error',
|
||||
'sonarjs/no-element-overwrite': 'error',
|
||||
'sonarjs/no-empty-collection': 'error',
|
||||
'sonarjs/no-extra-arguments': 'error',
|
||||
'sonarjs/no-identical-conditions': 'error',
|
||||
'sonarjs/no-identical-expressions': 'error',
|
||||
'sonarjs/no-ignored-return': 'error',
|
||||
'sonarjs/no-one-iteration-loop': 'error',
|
||||
'sonarjs/no-use-of-empty-return-value': 'error',
|
||||
'sonarjs/non-existent-operator': 'error',
|
||||
'sonarjs/no-collapsible-if': 'error',
|
||||
'sonarjs/no-same-line-conditional': 'error',
|
||||
'sonarjs/no-duplicated-branches': 'error',
|
||||
'sonarjs/no-collection-size-mischeck': 'error',
|
||||
'sonarjs/no-useless-catch': 'error',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@@ -152,6 +171,27 @@ const config = {
|
||||
'@typescript-eslint/no-var-requires': 0,
|
||||
},
|
||||
},
|
||||
...allPackages.map(pkg => ({
|
||||
files: [`${pkg}/src/**/*.ts`, `${pkg}/src/**/*.tsx`],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, './tsconfig.eslint.json'),
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: createPattern(pkg),
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-floating-promises': [
|
||||
'error',
|
||||
{
|
||||
ignoreVoid: false,
|
||||
ignoreIIFE: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/**/*',
|
||||
@@ -173,19 +213,9 @@ const config = {
|
||||
'ts-check': false,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-floating-promises': 0,
|
||||
},
|
||||
},
|
||||
...allPackages.map(pkg => ({
|
||||
files: [`${pkg}/src/**/*.ts`, `${pkg}/src/**/*.tsx`],
|
||||
rules: {
|
||||
'@typescript-eslint/no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: createPattern(pkg),
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const { z } = require('zod');
|
||||
|
||||
const {
|
||||
@@ -51,8 +52,6 @@ module.exports = {
|
||||
teamId: process.env.APPLE_TEAM_ID,
|
||||
}
|
||||
: undefined,
|
||||
// do we need the following line?
|
||||
extraResource: ['./resources/app-update.yml'],
|
||||
},
|
||||
makers: [
|
||||
{
|
||||
@@ -104,6 +103,27 @@ module.exports = {
|
||||
// so stable and canary will not share the same app data
|
||||
packageJson.productName = productName;
|
||||
},
|
||||
prePackage: async () => {
|
||||
const { rm, cp } = require('node:fs/promises');
|
||||
const { resolve } = require('node:path');
|
||||
|
||||
await rm(
|
||||
resolve(__dirname, './node_modules/@toeverything/plugin-infra'),
|
||||
{
|
||||
recursive: true,
|
||||
force: true,
|
||||
}
|
||||
);
|
||||
|
||||
await cp(
|
||||
resolve(__dirname, '../../packages/plugin-infra'),
|
||||
resolve(__dirname, './node_modules/@toeverything/plugin-infra'),
|
||||
{
|
||||
recursive: true,
|
||||
force: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
generateAssets: async (_, platform, arch) => {
|
||||
if (process.env.SKIP_GENERATE_ASSETS) {
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.12",
|
||||
"version": "0.7.0-canary.15",
|
||||
"author": "affine",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -29,6 +29,10 @@
|
||||
"devDependencies": {
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@electron-forge/cli": "^6.1.1",
|
||||
"@electron-forge/core": "^6.1.1",
|
||||
"@electron-forge/core-utils": "^6.1.1",
|
||||
@@ -48,6 +52,7 @@
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esbuild": "^0.17.19",
|
||||
"fs-extra": "^11.1.1",
|
||||
"jotai": "^2.1.1",
|
||||
"playwright": "=1.33.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"undici": "^5.22.1",
|
||||
@@ -57,8 +62,8 @@
|
||||
"dependencies": {
|
||||
"@toeverything/plugin-infra": "workspace:*",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"electron-updater": "^5.3.0",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
@@ -63,7 +63,7 @@ export const config = () => {
|
||||
bundle: true,
|
||||
target: `node${NODE_MAJOR_VERSION}`,
|
||||
platform: 'node',
|
||||
external: ['electron', 'electron-updater', '@toeverything/plugin-infra'],
|
||||
external: ['@toeverything/plugin-infra', 'async-call-rpc'],
|
||||
define: define,
|
||||
format: 'cjs',
|
||||
loader: {
|
||||
|
||||
23
apps/electron/scripts/generate-assets.mjs
Normal file → Executable file
23
apps/electron/scripts/generate-assets.mjs
Normal file → Executable file
@@ -32,10 +32,6 @@ if (releaseVersionEnv && electronPackageJson.version !== releaseVersionEnv) {
|
||||
}
|
||||
// copy web dist files to electron dist
|
||||
|
||||
// step 1: clean up
|
||||
await cleanup();
|
||||
echo('Clean up done');
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
$.shell = 'powershell.exe';
|
||||
$.prefix = '';
|
||||
@@ -43,11 +39,11 @@ if (process.platform === 'win32') {
|
||||
|
||||
cd(repoRootDir);
|
||||
|
||||
// step 2: build web (nextjs) dist
|
||||
// step 1: build web (nextjs) dist
|
||||
if (!process.env.SKIP_WEB_BUILD) {
|
||||
process.env.ENABLE_LEGACY_PROVIDER = 'false';
|
||||
await $`yarn build`;
|
||||
await $`yarn export`;
|
||||
await $`yarn nx build @affine/web`;
|
||||
await $`yarn nx export @affine/web`;
|
||||
|
||||
// step 1.5: amend sourceMappingURL to allow debugging in devtools
|
||||
await glob('**/*.{js,css}', { cwd: affineWebOutDir }).then(files => {
|
||||
@@ -67,7 +63,7 @@ if (!process.env.SKIP_WEB_BUILD) {
|
||||
await fs.move(affineWebOutDir, publicAffineOutDir, { overwrite: true });
|
||||
}
|
||||
|
||||
// step 3: update app-updater.yml content with build type in resources folder
|
||||
// step 2: update app-updater.yml content with build type in resources folder
|
||||
if (process.env.BUILD_TYPE === 'internal') {
|
||||
const appUpdaterYml = path.join(publicDistDir, 'app-update.yml');
|
||||
const appUpdaterYmlContent = await fs.readFile(appUpdaterYml, 'utf-8');
|
||||
@@ -77,14 +73,3 @@ if (process.env.BUILD_TYPE === 'internal') {
|
||||
);
|
||||
await fs.writeFile(appUpdaterYml, newAppUpdaterYmlContent);
|
||||
}
|
||||
|
||||
/// --------
|
||||
/// --------
|
||||
/// --------
|
||||
async function cleanup() {
|
||||
if (!process.env.SKIP_WEB_BUILD) {
|
||||
await fs.emptyDir(publicAffineOutDir);
|
||||
}
|
||||
await fs.remove(path.join(electronRootDir, 'dist'));
|
||||
await fs.remove(path.join(electronRootDir, 'out'));
|
||||
}
|
||||
|
||||
@@ -110,7 +110,9 @@ export async function saveDBFileAs(
|
||||
|
||||
await fs.copyFile(db.path, filePath);
|
||||
logger.log('saved', filePath);
|
||||
mainRPC.showItemInFolder(filePath);
|
||||
mainRPC.showItemInFolder(filePath).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
return { filePath };
|
||||
} catch (err) {
|
||||
logger.error('saveDBFileAs', err);
|
||||
|
||||
@@ -64,7 +64,9 @@ function setupRendererConnection(rendererPort: Electron.MessagePortMain) {
|
||||
for (const [key, eventRegister] of Object.entries(namespaceEvents)) {
|
||||
const subscription = eventRegister((...args: any[]) => {
|
||||
const chan = `${namespace}:${key}`;
|
||||
rpc.postEvent(chan, ...args);
|
||||
rpc.postEvent(chan, ...args).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
process.on('exit', () => {
|
||||
subscription();
|
||||
|
||||
@@ -60,9 +60,9 @@ app.on('activate', restoreOrCreateWindow);
|
||||
app
|
||||
.whenReady()
|
||||
.then(registerProtocol)
|
||||
.then(registerPlugin)
|
||||
.then(registerHandlers)
|
||||
.then(registerEvents)
|
||||
.then(registerPlugin)
|
||||
.then(ensureHelperProcess)
|
||||
.then(restoreOrCreateWindow)
|
||||
.then(createApplicationMenu)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
|
||||
import { logger } from '@affine/electron/main/logger';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
@@ -13,24 +14,31 @@ declare global {
|
||||
var asyncCall: Record<string, (...args: any) => PromiseLike<any>>;
|
||||
}
|
||||
|
||||
export async function registerPlugin() {
|
||||
export function registerPlugin() {
|
||||
const pluginWorkerPath = join(__dirname, './workers/plugin.worker.js');
|
||||
const asyncCall = AsyncCall<
|
||||
Record<string, (...args: any) => PromiseLike<any>>
|
||||
>(
|
||||
{},
|
||||
{
|
||||
log: (...args: any[]) => {
|
||||
logger.log('Plugin Worker', ...args);
|
||||
},
|
||||
},
|
||||
{
|
||||
channel: new MessageEventChannel(new Worker(pluginWorkerPath)),
|
||||
}
|
||||
);
|
||||
globalThis.asyncCall = asyncCall;
|
||||
await import('@toeverything/plugin-infra/manager').then(
|
||||
({ rootStore, affinePluginsAtom }) => {
|
||||
logger.info('import plugin manager');
|
||||
import('@toeverything/plugin-infra/manager')
|
||||
.then(({ rootStore, affinePluginsAtom }) => {
|
||||
logger.info('import plugin manager');
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, './plugins'),
|
||||
'./bookmark-block/index.mjs'
|
||||
);
|
||||
import('file://' + bookmarkPluginPath);
|
||||
logger.info('bookmark plugin path:', bookmarkPluginPath);
|
||||
import(bookmarkPluginPath);
|
||||
let dispose: () => void = () => {
|
||||
// noop
|
||||
};
|
||||
@@ -38,7 +46,9 @@ export async function registerPlugin() {
|
||||
dispose();
|
||||
const plugins = rootStore.get(affinePluginsAtom);
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
logger.info('register plugin', plugin.definition.id);
|
||||
plugin.definition.commands.forEach(command => {
|
||||
logger.info('register plugin command', command);
|
||||
ipcMain.handle(command, (event, ...args) =>
|
||||
asyncCall[command](...args)
|
||||
);
|
||||
@@ -47,11 +57,14 @@ export async function registerPlugin() {
|
||||
dispose = () => {
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
plugin.definition.commands.forEach(command => {
|
||||
logger.info('unregister plugin command', command);
|
||||
ipcMain.removeHandler(command);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('import plugin manager error', error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,33 +11,59 @@ if (!parentPort) {
|
||||
throw new Error('parentPort is undefined');
|
||||
}
|
||||
|
||||
AsyncCall(commandProxy, {
|
||||
const mainThread = AsyncCall<{
|
||||
log: (...args: any[]) => Promise<void>;
|
||||
}>(commandProxy, {
|
||||
channel: new MessageEventChannel(parentPort),
|
||||
});
|
||||
|
||||
import('@toeverything/plugin-infra/manager').then(
|
||||
({ rootStore, affinePluginsAtom }) => {
|
||||
globalThis.console.log = mainThread.log;
|
||||
globalThis.console.error = mainThread.log;
|
||||
globalThis.console.info = mainThread.log;
|
||||
globalThis.console.debug = mainThread.log;
|
||||
globalThis.console.warn = mainThread.log;
|
||||
|
||||
console.log('import plugin infra');
|
||||
|
||||
import('@toeverything/plugin-infra/manager')
|
||||
.then(({ rootStore, affinePluginsAtom }) => {
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, '../plugins'),
|
||||
'./bookmark-block/index.mjs'
|
||||
);
|
||||
|
||||
import('file://' + bookmarkPluginPath);
|
||||
console.log('import bookmark plugin', bookmarkPluginPath);
|
||||
|
||||
import(bookmarkPluginPath).catch(console.log);
|
||||
rootStore.sub(affinePluginsAtom, () => {
|
||||
const plugins = rootStore.get(affinePluginsAtom);
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
console.log('handle plugin', plugin.definition.id);
|
||||
if (plugin.serverAdapter) {
|
||||
plugin.serverAdapter({
|
||||
registerCommand: (command, fn) => {
|
||||
console.log('register command', command);
|
||||
commandProxy[command] = fn;
|
||||
},
|
||||
unregisterCommand: command => {
|
||||
delete commandProxy[command];
|
||||
},
|
||||
});
|
||||
try {
|
||||
plugin.serverAdapter({
|
||||
registerCommand: (command, fn) => {
|
||||
console.log('register command', command);
|
||||
commandProxy[command] = fn;
|
||||
},
|
||||
unregisterCommand: command => {
|
||||
console.log('unregister command', command);
|
||||
delete commandProxy[command];
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'error when handle plugin',
|
||||
plugin.definition.id,
|
||||
`${e}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log('no server adapter, skipping.');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
@@ -50,4 +50,6 @@ import { contextBridge, ipcRenderer } from 'electron';
|
||||
} catch (error) {
|
||||
console.error('Failed to expose affine APIs to window object!', error);
|
||||
}
|
||||
})();
|
||||
})().catch(err => {
|
||||
console.error('Failed to bootstrap preload script!', err);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.12",
|
||||
"version": "0.7.0-canary.15",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/web",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.12",
|
||||
"version": "0.7.0-canary.15",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
||||
@@ -75,7 +75,9 @@ export function useHistoryAtom() {
|
||||
if (forward) {
|
||||
const target = Math.min(prev.stack.length - 1, prev.current + 1);
|
||||
const url = prev.stack[target];
|
||||
void router.push(url);
|
||||
router.push(url).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
return {
|
||||
...prev,
|
||||
current: target,
|
||||
@@ -84,7 +86,9 @@ export function useHistoryAtom() {
|
||||
} else {
|
||||
const target = Math.max(0, prev.current - 1);
|
||||
const url = prev.stack[target];
|
||||
void router.push(url);
|
||||
router.push(url).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
return {
|
||||
...prev,
|
||||
current: target,
|
||||
|
||||
@@ -49,19 +49,24 @@ rootWorkspacesMetadataAtom.onMount = setAtom => {
|
||||
}, 0);
|
||||
|
||||
if (environment.isDesktop) {
|
||||
window.apis?.workspace.list().then(workspaceIDs => {
|
||||
if (abortController.signal.aborted) return;
|
||||
const newMetadata = workspaceIDs.map(w => ({
|
||||
id: w[0],
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
}));
|
||||
setAtom(metadata => {
|
||||
return [
|
||||
...metadata,
|
||||
...newMetadata.filter(m => !metadata.find(m2 => m2.id === m.id)),
|
||||
];
|
||||
window.apis?.workspace
|
||||
.list()
|
||||
.then(workspaceIDs => {
|
||||
if (abortController.signal.aborted) return;
|
||||
const newMetadata = workspaceIDs.map(w => ({
|
||||
id: w[0],
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
}));
|
||||
setAtom(metadata => {
|
||||
return [
|
||||
...metadata,
|
||||
...newMetadata.filter(m => !metadata.find(m2 => m2.id === m.id)),
|
||||
];
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -116,9 +116,14 @@ const useDefaultDBLocation = () => {
|
||||
const [defaultDBLocation, setDefaultDBLocation] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
window.apis?.db.getDefaultStorageLocation().then(dir => {
|
||||
setDefaultDBLocation(dir);
|
||||
});
|
||||
window.apis?.db
|
||||
.getDefaultStorageLocation()
|
||||
.then(dir => {
|
||||
setDefaultDBLocation(dir);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return defaultDBLocation;
|
||||
@@ -281,7 +286,9 @@ export const CreateWorkspaceModal = ({
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
})();
|
||||
})().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
} else if (mode === 'new') {
|
||||
setStep(environment.isDesktop ? 'set-db-location' : 'name-workspace');
|
||||
} else {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-s
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import clsx from 'clsx';
|
||||
import type React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { useIsWorkspaceOwner } from '../../../../../hooks/affine/use-is-workspace-owner';
|
||||
import { Upload } from '../../../../pure/file-upload';
|
||||
@@ -27,9 +27,14 @@ const useShowOpenDBFile = (workspaceId: string) => {
|
||||
const [show, setShow] = useState(false);
|
||||
useEffect(() => {
|
||||
if (window.apis && window.events && environment.isDesktop) {
|
||||
window.apis.workspace.getMeta(workspaceId).then(meta => {
|
||||
setShow(!!meta.secondaryDBPath);
|
||||
});
|
||||
window.apis.workspace
|
||||
.getMeta(workspaceId)
|
||||
.then(meta => {
|
||||
setShow(!!meta.secondaryDBPath);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
return window.events.workspace.onMetaChange((newMeta: any) => {
|
||||
if (newMeta.workspaceId === workspaceId) {
|
||||
const meta = newMeta.meta;
|
||||
@@ -54,35 +59,11 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
const isOwner = useIsWorkspaceOwner(workspace);
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const showOpenFolder = useShowOpenDBFile(workspace.id);
|
||||
|
||||
const handleUpdateWorkspaceName = (name: string) => {
|
||||
setName(name);
|
||||
toast(t['Update workspace name success']());
|
||||
};
|
||||
|
||||
const [moveToInProgress, setMoveToInProgress] = useState<boolean>(false);
|
||||
|
||||
const handleMoveTo = async () => {
|
||||
if (moveToInProgress) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setMoveToInProgress(true);
|
||||
const result = await window.apis?.dialog.moveDBFile(workspace.id);
|
||||
if (!result?.error && !result?.canceled) {
|
||||
toast(t['Move folder success']());
|
||||
} else if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
}
|
||||
} catch (err) {
|
||||
toast(t['UNKNOWN_ERROR']());
|
||||
} finally {
|
||||
setMoveToInProgress(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [, update] = useBlockSuiteWorkspaceAvatarUrl(
|
||||
workspace.blockSuiteWorkspace
|
||||
);
|
||||
@@ -137,9 +118,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
placeholder={t['Workspace Name']()}
|
||||
maxLength={64}
|
||||
minLength={0}
|
||||
onChange={newName => {
|
||||
setInput(newName);
|
||||
}}
|
||||
onChange={setInput}
|
||||
></StyledInput>
|
||||
</div>
|
||||
|
||||
@@ -158,63 +137,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{environment.isDesktop && (
|
||||
<div className={style.row}>
|
||||
<div className={style.col}>
|
||||
<div className={style.settingItemLabel}>
|
||||
{t['Storage Folder']()}
|
||||
</div>
|
||||
<div className={style.settingItemLabelHint}>
|
||||
{t['Storage Folder Hint']()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style.col}>
|
||||
{showOpenFolder && (
|
||||
<div
|
||||
className={style.storageTypeWrapper}
|
||||
onClick={() => {
|
||||
if (environment.isDesktop) {
|
||||
window.apis?.dialog.revealDBFile(workspace.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FolderIcon color="var(--affine-primary-color)" />
|
||||
<div className={style.storageTypeLabelWrapper}>
|
||||
<div className={style.storageTypeLabel}>
|
||||
{t['Open folder']()}
|
||||
</div>
|
||||
<div className={style.storageTypeLabelHint}>
|
||||
{t['Open folder hint']()}
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightSmallIcon color="var(--affine-primary-color)" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
data-testid="move-folder"
|
||||
data-disabled={moveToInProgress}
|
||||
className={style.storageTypeWrapper}
|
||||
onClick={handleMoveTo}
|
||||
>
|
||||
<MoveToIcon color="var(--affine-primary-color)" />
|
||||
<div className={style.storageTypeLabelWrapper}>
|
||||
<div className={style.storageTypeLabel}>
|
||||
{t['Move folder']()}
|
||||
</div>
|
||||
<div className={style.storageTypeLabelHint}>
|
||||
{t['Move folder hint']()}
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightSmallIcon color="var(--affine-primary-color)" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.col}></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DesktopClientOnly workspaceId={workspace.id} />
|
||||
<div className={style.row}>
|
||||
<div className={style.col}>
|
||||
<div className={style.settingItemLabel}>
|
||||
@@ -273,3 +196,81 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function DesktopClientOnly({ workspaceId }: { workspaceId: string }) {
|
||||
const t = useAFFiNEI18N();
|
||||
const showOpenFolder = useShowOpenDBFile(workspaceId);
|
||||
const onRevealDBFile = useCallback(() => {
|
||||
if (environment.isDesktop) {
|
||||
window.apis?.dialog.revealDBFile(workspaceId).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}, [workspaceId]);
|
||||
const [moveToInProgress, setMoveToInProgress] = useState<boolean>(false);
|
||||
const handleMoveTo = useCallback(() => {
|
||||
if (moveToInProgress) {
|
||||
return;
|
||||
}
|
||||
setMoveToInProgress(true);
|
||||
window.apis?.dialog
|
||||
.moveDBFile(workspaceId)
|
||||
.then(result => {
|
||||
if (!result?.error && !result?.canceled) {
|
||||
toast(t['Move folder success']());
|
||||
} else if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast(t['UNKNOWN_ERROR']());
|
||||
})
|
||||
.finally(() => {
|
||||
setMoveToInProgress(false);
|
||||
});
|
||||
}, [moveToInProgress, t, workspaceId]);
|
||||
const openFolderNode = showOpenFolder ? (
|
||||
<div className={style.storageTypeWrapper} onClick={onRevealDBFile}>
|
||||
<FolderIcon color="var(--affine-primary-color)" />
|
||||
<div className={style.storageTypeLabelWrapper}>
|
||||
<div className={style.storageTypeLabel}>{t['Open folder']()}</div>
|
||||
<div className={style.storageTypeLabelHint}>
|
||||
{t['Open folder hint']()}
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightSmallIcon color="var(--affine-primary-color)" />
|
||||
</div>
|
||||
) : null;
|
||||
return (
|
||||
<div className={style.row}>
|
||||
<div className={style.col}>
|
||||
<div className={style.settingItemLabel}>{t['Storage Folder']()}</div>
|
||||
<div className={style.settingItemLabelHint}>
|
||||
{t['Storage Folder Hint']()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style.col}>
|
||||
{openFolderNode}
|
||||
|
||||
<div
|
||||
data-testid="move-folder"
|
||||
data-disabled={moveToInProgress}
|
||||
className={style.storageTypeWrapper}
|
||||
onClick={handleMoveTo}
|
||||
>
|
||||
<MoveToIcon color="var(--affine-primary-color)" />
|
||||
<div className={style.storageTypeLabelWrapper}>
|
||||
<div className={style.storageTypeLabel}>{t['Move folder']()}</div>
|
||||
<div className={style.storageTypeLabelHint}>
|
||||
{t['Move folder hint']()}
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightSmallIcon color="var(--affine-primary-color)" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.col}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ const LanguageMenuContent: FC = () => {
|
||||
const i18n = useI18N();
|
||||
const changeLanguage = useCallback(
|
||||
(event: string) => {
|
||||
void i18n.changeLanguage(event);
|
||||
i18n.changeLanguage(event).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { PluginUIAdapter } from '@toeverything/plugin-infra/type';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||
import { forwardRef, memo, useEffect, useMemo, useState } from 'react';
|
||||
import { noop } from 'rxjs';
|
||||
|
||||
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||
import { contentLayoutAtom } from '../../../atoms/layout';
|
||||
@@ -104,27 +105,21 @@ const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
||||
<button
|
||||
data-type="minimize"
|
||||
className={styles.windowAppControl}
|
||||
onClick={() => {
|
||||
window.apis?.ui.handleMinimizeApp();
|
||||
}}
|
||||
onClick={window.apis?.ui.handleMinimizeApp ?? noop}
|
||||
>
|
||||
<MinusIcon />
|
||||
</button>
|
||||
<button
|
||||
data-type="maximize"
|
||||
className={styles.windowAppControl}
|
||||
onClick={() => {
|
||||
window.apis?.ui.handleMaximizeApp();
|
||||
}}
|
||||
onClick={window.apis?.ui.handleMaximizeApp ?? noop}
|
||||
>
|
||||
<RoundedRectangleIcon />
|
||||
</button>
|
||||
<button
|
||||
data-type="close"
|
||||
className={styles.windowAppControl}
|
||||
onClick={() => {
|
||||
window.apis?.ui.handleCloseApp();
|
||||
}}
|
||||
onClick={window.apis?.ui.handleCloseApp ?? noop}
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
|
||||
@@ -109,7 +109,7 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
);
|
||||
const disposes = uiDecorators.map(ui => ui(editor));
|
||||
return () => {
|
||||
disposes.map(fn => fn());
|
||||
disposes.forEach(fn => fn());
|
||||
dispose();
|
||||
};
|
||||
},
|
||||
|
||||
@@ -54,7 +54,9 @@ export const Footer: React.FC<FooterProps> = ({
|
||||
title: query,
|
||||
});
|
||||
onClose();
|
||||
void jumpToPage(blockSuiteWorkspace.id, page.id);
|
||||
jumpToPage(blockSuiteWorkspace.id, page.id).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [blockSuiteWorkspace, createPage, jumpToPage, onClose, query])}
|
||||
>
|
||||
<StyledModalFooterContent>
|
||||
|
||||
@@ -107,7 +107,9 @@ export const RootAppSidebar = ({
|
||||
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);
|
||||
useEffect(() => {
|
||||
if (environment.isDesktop && typeof sidebarOpen === 'boolean') {
|
||||
window.apis?.ui.handleSidebarVisibilityChange(sidebarOpen);
|
||||
window.apis?.ui.handleSidebarVisibilityChange(sidebarOpen).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}, [sidebarOpen]);
|
||||
|
||||
|
||||
@@ -80,15 +80,10 @@ export function WorkspaceHeader({
|
||||
{t['Workspace Settings']()}
|
||||
</WorkspaceTitle>
|
||||
);
|
||||
} else if (currentEntry.subPath === WorkspaceSubPath.SHARED) {
|
||||
return (
|
||||
<WorkspaceModeFilterTab
|
||||
workspace={currentWorkspace}
|
||||
currentPage={null}
|
||||
isPublic={false}
|
||||
/>
|
||||
);
|
||||
} else if (currentEntry.subPath === WorkspaceSubPath.TRASH) {
|
||||
} else if (
|
||||
currentEntry.subPath === WorkspaceSubPath.SHARED ||
|
||||
currentEntry.subPath === WorkspaceSubPath.TRASH
|
||||
) {
|
||||
return (
|
||||
<WorkspaceModeFilterTab
|
||||
workspace={currentWorkspace}
|
||||
|
||||
@@ -174,7 +174,9 @@ export const CurrentWorkspaceContext = ({
|
||||
useEffect(() => {
|
||||
const id = setTimeout(() => {
|
||||
if (!exist) {
|
||||
void push('/');
|
||||
push('/').catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
globalThis.HALTING_PROBLEM_TIMEOUT <<= 1;
|
||||
}
|
||||
}, globalThis.HALTING_PROBLEM_TIMEOUT);
|
||||
@@ -319,7 +321,9 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
||||
}
|
||||
if (!router.query.pageId) {
|
||||
setCurrentPageId(pageId);
|
||||
void jumpToPage(currentWorkspace.id, pageId);
|
||||
jumpToPage(currentWorkspace.id, pageId).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
@@ -353,7 +357,9 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
||||
}
|
||||
);
|
||||
setCurrentPageId(currentPageId);
|
||||
void jumpToPage(currentWorkspace.id, page.id);
|
||||
jumpToPage(currentWorkspace.id, page.id).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
currentPageId,
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import React, { lazy, Suspense, useEffect, useMemo } from 'react';
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
|
||||
import { AffineErrorBoundary } from '../components/affine/affine-error-eoundary';
|
||||
import { MessageCenter } from '../components/pure/message-center';
|
||||
@@ -41,6 +41,11 @@ const DebugProvider = ({ children }: PropsWithChildren): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
const i18n = createI18n();
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('Runtime Preset', config);
|
||||
}
|
||||
|
||||
const App = function App({
|
||||
Component,
|
||||
pageProps,
|
||||
@@ -49,14 +54,6 @@ const App = function App({
|
||||
emotionCache?: EmotionCache;
|
||||
}) {
|
||||
const getLayout = Component.getLayout || EmptyLayout;
|
||||
const i18n = useMemo(() => createI18n(), []);
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// I know what I'm doing
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
console.log('Runtime Preset', config);
|
||||
}, []);
|
||||
}
|
||||
|
||||
return (
|
||||
<CacheProvider value={emotionCache}>
|
||||
|
||||
@@ -39,21 +39,31 @@ const IndexPageInner = () => {
|
||||
nonTrashPages.at(0)?.id;
|
||||
if (pageId) {
|
||||
logger.debug('Found target workspace. Jump to page', pageId);
|
||||
void jumpToPage(targetWorkspace.id, pageId, RouteLogic.REPLACE);
|
||||
jumpToPage(targetWorkspace.id, pageId, RouteLogic.REPLACE).catch(
|
||||
err => {
|
||||
console.error(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const clearId = setTimeout(() => {
|
||||
dispose.dispose();
|
||||
logger.debug('Found target workspace. Jump to all pages');
|
||||
void jumpToSubPath(
|
||||
jumpToSubPath(
|
||||
targetWorkspace.id,
|
||||
WorkspaceSubPath.ALL,
|
||||
RouteLogic.REPLACE
|
||||
);
|
||||
).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, 1000);
|
||||
const dispose =
|
||||
targetWorkspace.blockSuiteWorkspace.slots.pageAdded.once(pageId => {
|
||||
clearTimeout(clearId);
|
||||
void jumpToPage(targetWorkspace.id, pageId, RouteLogic.REPLACE);
|
||||
jumpToPage(targetWorkspace.id, pageId, RouteLogic.REPLACE).catch(
|
||||
err => {
|
||||
console.error(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
return () => {
|
||||
clearTimeout(clearId);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { atomWithStorage } from 'jotai/utils';
|
||||
import Head from 'next/head';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { getUIAdapter } from '../../../adapters/workspace';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
@@ -30,7 +30,7 @@ function useTabRouterSync(
|
||||
router: NextRouter,
|
||||
currentTab: SettingPanel,
|
||||
setCurrentTab: (tab: SettingPanel) => void
|
||||
) {
|
||||
): void {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
@@ -39,37 +39,30 @@ function useTabRouterSync(
|
||||
? router.query.currentTab
|
||||
: null;
|
||||
if (
|
||||
queryCurrentTab !== null &&
|
||||
settingPanelValues.indexOf(queryCurrentTab as SettingPanel) === -1
|
||||
(queryCurrentTab !== null &&
|
||||
settingPanelValues.indexOf(queryCurrentTab as SettingPanel) === -1) ||
|
||||
settingPanelValues.indexOf(currentTab as SettingPanel) === -1
|
||||
) {
|
||||
setCurrentTab(settingPanel.General);
|
||||
void router.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: settingPanel.General,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else if (settingPanelValues.indexOf(currentTab as SettingPanel) === -1) {
|
||||
setCurrentTab(settingPanel.General);
|
||||
void router.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: settingPanel.General,
|
||||
},
|
||||
});
|
||||
return;
|
||||
router
|
||||
.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: settingPanel.General,
|
||||
},
|
||||
})
|
||||
.catch(console.error);
|
||||
} else if (queryCurrentTab !== currentTab) {
|
||||
void router.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: currentTab,
|
||||
},
|
||||
});
|
||||
return;
|
||||
router
|
||||
.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: currentTab,
|
||||
},
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,20 +71,24 @@ const SettingPage: NextPageWithLayout = () => {
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const t = useAFFiNEI18N();
|
||||
const [currentTab, setCurrentTab] = useAtom(settingPanelAtom);
|
||||
useEffect(() => {});
|
||||
const onChangeTab = useCallback(
|
||||
(tab: SettingPanel) => {
|
||||
setCurrentTab(tab as SettingPanel);
|
||||
void router.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: tab,
|
||||
},
|
||||
});
|
||||
router
|
||||
.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
currentTab: tab,
|
||||
},
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
[router, setCurrentTab]
|
||||
);
|
||||
|
||||
useTabRouterSync(router, currentTab, setCurrentTab);
|
||||
|
||||
const helper = useAppHelper();
|
||||
@@ -102,11 +99,11 @@ const SettingPage: NextPageWithLayout = () => {
|
||||
return helper.deleteWorkspace(workspaceId);
|
||||
}, [currentWorkspace, helper]);
|
||||
const onTransformWorkspace = useOnTransformWorkspace();
|
||||
if (!router.isReady) {
|
||||
return <PageLoading />;
|
||||
} else if (currentWorkspace === null) {
|
||||
return <PageLoading />;
|
||||
} else if (settingPanelValues.indexOf(currentTab as SettingPanel) === -1) {
|
||||
if (
|
||||
!router.isReady ||
|
||||
currentWorkspace === null ||
|
||||
settingPanelValues.indexOf(currentTab as SettingPanel) === -1
|
||||
) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
const { SettingsDetail, Header } = getUIAdapter(currentWorkspace.flavour);
|
||||
|
||||
@@ -31,9 +31,7 @@ const TrashPage: NextPageWithLayout = () => {
|
||||
},
|
||||
[currentWorkspace, jumpToPage]
|
||||
);
|
||||
if (!router.isReady) {
|
||||
return <PageLoading />;
|
||||
} else if (currentWorkspace === null) {
|
||||
if (!router.isReady || currentWorkspace === null) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
// todo(himself65): refactor to plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/monorepo",
|
||||
"version": "0.7.0-canary.12",
|
||||
"version": "0.7.0-canary.15",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
@@ -72,6 +72,7 @@
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.19.0",
|
||||
"eslint-plugin-unicorn": "^47.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"fake-indexeddb": "4.0.1",
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"dependencies": {
|
||||
"dotenv": "^16.1.4"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -66,5 +66,5 @@
|
||||
"vite": "^4.3.9",
|
||||
"yjs": "^13.6.1"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ export const updateAvailableAtom = atomWithObservable(() => {
|
||||
return rpcToObservable(null as any | null, {
|
||||
event: window.events?.updater.onUpdateAvailable,
|
||||
onSubscribe: () => {
|
||||
window.apis?.updater.checkForUpdatesAndNotify();
|
||||
window.apis?.updater.checkForUpdatesAndNotify().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,7 +65,10 @@ export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
|
||||
}, [currentVersion, setChangelogCheckAtom]);
|
||||
const onClickUpdate = useCallback(() => {
|
||||
if (updateReady) {
|
||||
window.apis?.updater.quitAndInstall();
|
||||
window.apis?.updater.quitAndInstall().catch(err => {
|
||||
// TODO: add error toast here
|
||||
console.error(err);
|
||||
});
|
||||
} else if (updateAvailable) {
|
||||
if (updateAvailable.allowAutoUpdate) {
|
||||
// wait for download to finish
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import type { MouseEvent as ReactMouseEvent, RefObject } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
interface UseZoomControlsProps {
|
||||
zoomRef: RefObject<HTMLDivElement>;
|
||||
imageRef: RefObject<HTMLImageElement>;
|
||||
}
|
||||
|
||||
export const useZoomControls = ({
|
||||
zoomRef,
|
||||
imageRef,
|
||||
}: UseZoomControlsProps) => {
|
||||
const [currentScale, setCurrentScale] = useState<number>(0.5);
|
||||
const [isZoomedBigger, setIsZoomedBigger] = useState<boolean>(false);
|
||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||
const [mouseX, setMouseX] = useState<number>(0);
|
||||
const [mouseY, setMouseY] = useState<number>(0);
|
||||
const [dragBeforeX, setDragBeforeX] = useState<number>(0);
|
||||
const [dragBeforeY, setDragBeforeY] = useState<number>(0);
|
||||
const [imagePos, setImagePos] = useState<{ x: number; y: number }>({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const zoomIn = useCallback(() => {
|
||||
const image = imageRef.current;
|
||||
|
||||
if (image && currentScale < 2) {
|
||||
const newScale = currentScale + 0.1;
|
||||
setCurrentScale(newScale);
|
||||
image.style.width = `${image.naturalWidth * newScale}px`;
|
||||
image.style.height = `${image.naturalHeight * newScale}px`;
|
||||
}
|
||||
}, [imageRef, currentScale]);
|
||||
|
||||
const zoomOut = useCallback(() => {
|
||||
const image = imageRef.current;
|
||||
if (image && currentScale > 0.5) {
|
||||
const newScale = currentScale - 0.1;
|
||||
setCurrentScale(newScale);
|
||||
image.style.width = `${image.naturalWidth * newScale}px`;
|
||||
image.style.height = `${image.naturalHeight * newScale}px`;
|
||||
if (!isZoomedBigger) {
|
||||
image.style.transform = `translate(0px, 0px)`;
|
||||
}
|
||||
}
|
||||
}, [imageRef, currentScale, isZoomedBigger]);
|
||||
|
||||
const resetZoom = useCallback(() => {
|
||||
const image = imageRef.current;
|
||||
if (image) {
|
||||
const newScale = 0.5;
|
||||
setCurrentScale(newScale);
|
||||
image.style.width = `${image.naturalWidth * newScale}px`;
|
||||
image.style.height = `${image.naturalHeight * newScale}px`;
|
||||
image.style.transform = `translate(0px, 0px)`;
|
||||
setImagePos({ x: 0, y: 0 });
|
||||
}
|
||||
}, [imageRef]);
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(event: ReactMouseEvent) => {
|
||||
event?.preventDefault();
|
||||
setIsDragging(true);
|
||||
const image = imageRef.current;
|
||||
if (image && isZoomedBigger) {
|
||||
image.style.cursor = 'grab';
|
||||
const rect = image.getBoundingClientRect();
|
||||
setDragBeforeX(rect.left);
|
||||
setDragBeforeY(rect.top);
|
||||
setMouseX(event.clientX);
|
||||
setMouseY(event.clientY);
|
||||
}
|
||||
},
|
||||
[imageRef, isZoomedBigger]
|
||||
);
|
||||
|
||||
const handleDrag = useCallback(
|
||||
(event: ReactMouseEvent) => {
|
||||
event?.preventDefault();
|
||||
const image = imageRef.current;
|
||||
|
||||
if (isDragging && image && isZoomedBigger) {
|
||||
image.style.cursor = 'grabbing';
|
||||
const currentX = imagePos.x;
|
||||
const currentY = imagePos.y;
|
||||
const newPosX = currentX + event.clientX - mouseX;
|
||||
const newPosY = currentY + event.clientY - mouseY;
|
||||
image.style.transform = `translate(${newPosX}px, ${newPosY}px)`;
|
||||
}
|
||||
},
|
||||
[
|
||||
imagePos.x,
|
||||
imagePos.y,
|
||||
imageRef,
|
||||
isDragging,
|
||||
isZoomedBigger,
|
||||
mouseX,
|
||||
mouseY,
|
||||
]
|
||||
);
|
||||
|
||||
const dragEndImpl = useCallback(() => {
|
||||
setIsDragging(false);
|
||||
|
||||
const image = imageRef.current;
|
||||
if (image && isZoomedBigger && isDragging) {
|
||||
image.style.cursor = 'pointer';
|
||||
const rect = image.getBoundingClientRect();
|
||||
const newPos = { x: rect.left, y: rect.top };
|
||||
const currentX = imagePos.x;
|
||||
const currentY = imagePos.y;
|
||||
const newPosX = currentX + newPos.x - dragBeforeX;
|
||||
const newPosY = currentY + newPos.y - dragBeforeY;
|
||||
setImagePos({ x: newPosX, y: newPosY });
|
||||
}
|
||||
}, [
|
||||
dragBeforeX,
|
||||
dragBeforeY,
|
||||
imagePos.x,
|
||||
imagePos.y,
|
||||
imageRef,
|
||||
isDragging,
|
||||
isZoomedBigger,
|
||||
]);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: ReactMouseEvent) => {
|
||||
event.preventDefault();
|
||||
dragEndImpl();
|
||||
},
|
||||
[dragEndImpl]
|
||||
);
|
||||
|
||||
const handleMouseUp = useCallback(() => {
|
||||
if (isDragging) {
|
||||
dragEndImpl();
|
||||
}
|
||||
}, [isDragging, dragEndImpl]);
|
||||
|
||||
const checkZoomSize = useCallback(() => {
|
||||
const { current: zoomArea } = zoomRef;
|
||||
if (zoomArea) {
|
||||
const image = zoomArea.querySelector('img');
|
||||
if (image) {
|
||||
const zoomedWidth = image.naturalWidth * currentScale;
|
||||
const zoomedHeight = image.naturalHeight * currentScale;
|
||||
const containerWidth = window.innerWidth;
|
||||
const containerHeight = window.innerHeight;
|
||||
setIsZoomedBigger(
|
||||
zoomedWidth > containerWidth || zoomedHeight > containerHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [currentScale, zoomRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = (event: WheelEvent) => {
|
||||
const { deltaY } = event;
|
||||
if (deltaY > 0) {
|
||||
zoomOut();
|
||||
} else if (deltaY < 0) {
|
||||
zoomIn();
|
||||
}
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
checkZoomSize();
|
||||
};
|
||||
|
||||
checkZoomSize();
|
||||
|
||||
window.addEventListener('wheel', handleScroll, { passive: false });
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('wheel', handleScroll);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
window.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
}, [zoomIn, zoomOut, checkZoomSize, handleMouseUp]);
|
||||
|
||||
return {
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
resetZoom,
|
||||
isZoomedBigger,
|
||||
currentScale,
|
||||
handleDragStart,
|
||||
handleDrag,
|
||||
handleDragEnd,
|
||||
};
|
||||
};
|
||||
@@ -11,7 +11,8 @@ export const imagePreviewModalStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'var(--affine-background-modal-color)',
|
||||
// background: 'var(--affine-background-modal-color)',
|
||||
background: 'rgba(0,0,0,0.75)',
|
||||
});
|
||||
|
||||
export const imagePreviewModalCloseButtonStyle = style({
|
||||
@@ -20,11 +21,9 @@ export const imagePreviewModalCloseButtonStyle = style({
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
||||
height: '36px',
|
||||
width: '36px',
|
||||
borderRadius: '10px',
|
||||
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
background: 'var(--affine-white)',
|
||||
@@ -33,37 +32,97 @@ export const imagePreviewModalCloseButtonStyle = style({
|
||||
cursor: 'pointer',
|
||||
color: 'var(--affine-icon-color)',
|
||||
transition: 'background 0.2s ease-in-out',
|
||||
zIndex: 1,
|
||||
});
|
||||
|
||||
export const imagePreviewModalGoStyle = style({
|
||||
height: '50%',
|
||||
color: 'var(--affine-white)',
|
||||
position: 'absolute',
|
||||
fontSize: '60px',
|
||||
lineHeight: '60px',
|
||||
fontWeight: 'bold',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
opacity: '0.2',
|
||||
padding: '0 15px',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
|
||||
export const imageNavigationControlStyle = style({
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
zIndex: 0,
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const imagePreviewModalContainerStyle = style({
|
||||
position: 'absolute',
|
||||
top: '20%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
zIndex: 1,
|
||||
'@media': {
|
||||
'screen and (max-width: 768px)': {
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const imagePreviewModalImageStyle = style({
|
||||
background: 'transparent',
|
||||
maxWidth: '686px',
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'center',
|
||||
borderRadius: '4px',
|
||||
export const imagePreviewModalCenterStyle = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const imagePreviewModalActionsStyle = style({
|
||||
position: 'absolute',
|
||||
export const imagePreviewModalCaptionStyle = style({
|
||||
color: 'var(--affine-white)',
|
||||
marginTop: '24px',
|
||||
'@media': {
|
||||
'screen and (max-width: 768px)': {
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const imagePreviewActionBarStyle = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '16px 0',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '2px 2px 4px rgba(0, 0, 0, 0.3)',
|
||||
maxWidth: 'max-content',
|
||||
});
|
||||
|
||||
export const groupStyle = style({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
borderLeft: '1px solid #E3E2E4',
|
||||
});
|
||||
|
||||
export const buttonStyle = style({
|
||||
paddingLeft: '10px',
|
||||
paddingRight: '10px',
|
||||
});
|
||||
|
||||
export const scaleIndicatorStyle = style({
|
||||
margin: '0 8px',
|
||||
});
|
||||
|
||||
export const imageBottomContainerStyle = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
position: 'fixed',
|
||||
bottom: '28px',
|
||||
background: 'var(--affine-white)',
|
||||
zIndex: baseTheme.zIndexModal + 1,
|
||||
});
|
||||
|
||||
export const captionStyle = style({
|
||||
maxWidth: '686px',
|
||||
color: 'var(--affine-white)',
|
||||
background: 'rgba(0,0,0,0.75)',
|
||||
padding: '10px',
|
||||
marginBottom: '21px',
|
||||
});
|
||||
|
||||
@@ -3,19 +3,40 @@ import '@blocksuite/blocks';
|
||||
|
||||
import type { EmbedBlockModel } from '@blocksuite/blocks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
ArrowLeftSmallIcon,
|
||||
ArrowRightSmallIcon,
|
||||
CopyIcon,
|
||||
DeleteIcon,
|
||||
DownloadIcon,
|
||||
MinusIcon,
|
||||
PlusIcon,
|
||||
ViewBarIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import { Suspense, useCallback } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '../../ui/button/button';
|
||||
import { useZoomControls } from './hooks/use-zoom';
|
||||
import {
|
||||
buttonStyle,
|
||||
captionStyle,
|
||||
groupStyle,
|
||||
imageBottomContainerStyle,
|
||||
imageNavigationControlStyle,
|
||||
imagePreviewActionBarStyle,
|
||||
imagePreviewModalCaptionStyle,
|
||||
imagePreviewModalCenterStyle,
|
||||
imagePreviewModalCloseButtonStyle,
|
||||
imagePreviewModalContainerStyle,
|
||||
imagePreviewModalGoStyle,
|
||||
imagePreviewModalImageStyle,
|
||||
imagePreviewModalStyle,
|
||||
scaleIndicatorStyle,
|
||||
} from './index.css';
|
||||
import { previewBlockIdAtom } from './index.jotai';
|
||||
|
||||
@@ -31,38 +52,46 @@ const ImagePreviewModalImpl = (
|
||||
}
|
||||
): ReactElement | null => {
|
||||
const [blockId, setBlockId] = useAtom(previewBlockIdAtom);
|
||||
|
||||
const [bIsActionBarVisible, setBIsActionBarVisible] = useState(false);
|
||||
const [caption, setCaption] = useState(() => {
|
||||
const page = props.workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(props.blockId) as EmbedBlockModel | null;
|
||||
const block = page.getBlockById(props.blockId) as EmbedBlockModel;
|
||||
assertExists(block);
|
||||
return block.caption;
|
||||
return block?.caption;
|
||||
});
|
||||
useEffect(() => {
|
||||
const page = props.workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(props.blockId) as EmbedBlockModel | null;
|
||||
const block = page.getBlockById(props.blockId) as EmbedBlockModel;
|
||||
assertExists(block);
|
||||
const disposable = block.propsUpdated.on(() => {
|
||||
setCaption(block.caption);
|
||||
});
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
setCaption(block?.caption);
|
||||
}, [props.blockId, props.pageId, props.workspace]);
|
||||
const { data } = useSWR(['workspace', 'embed', props.pageId, props.blockId], {
|
||||
fetcher: ([_, __, pageId, blockId]) => {
|
||||
const page = props.workspace.getPage(pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(blockId) as EmbedBlockModel | null;
|
||||
const block = page.getBlockById(blockId) as EmbedBlockModel;
|
||||
assertExists(block);
|
||||
return props.workspace.blobs.get(block.sourceId);
|
||||
return props.workspace.blobs.get(block?.sourceId);
|
||||
},
|
||||
suspense: true,
|
||||
});
|
||||
const zoomRef = useRef<HTMLDivElement | null>(null);
|
||||
const imageRef = useRef<HTMLImageElement | null>(null);
|
||||
const {
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
isZoomedBigger,
|
||||
handleDrag,
|
||||
handleDragStart,
|
||||
handleDragEnd,
|
||||
resetZoom,
|
||||
currentScale,
|
||||
} = useZoomControls({ zoomRef, imageRef });
|
||||
const [prevData, setPrevData] = useState<string | null>(() => data);
|
||||
const [url, setUrl] = useState<string | null>(null);
|
||||
const imageRef = useRef<HTMLImageElement>(null);
|
||||
if (prevData !== data) {
|
||||
if (url) {
|
||||
URL.revokeObjectURL(url);
|
||||
@@ -76,8 +105,231 @@ const ImagePreviewModalImpl = (
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
const nextImageHandler = (blockId: string | null) => {
|
||||
assertExists(blockId);
|
||||
const workspace = props.workspace;
|
||||
|
||||
const page = workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(blockId);
|
||||
assertExists(block);
|
||||
const nextBlock = page
|
||||
.getNextSiblings(block)
|
||||
.find(
|
||||
(block): block is EmbedBlockModel => block.flavour === 'affine:embed'
|
||||
);
|
||||
if (nextBlock) {
|
||||
setBlockId(nextBlock.id);
|
||||
const image = imageRef.current;
|
||||
resetZoom();
|
||||
if (image) {
|
||||
image.style.width = '50%'; // Reset the width to its original size
|
||||
image.style.height = 'auto'; // Reset the height to maintain aspect ratio
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const previousImageHandler = (blockId: string | null) => {
|
||||
assertExists(blockId);
|
||||
const workspace = props.workspace;
|
||||
const page = workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(blockId);
|
||||
assertExists(block);
|
||||
const prevBlock = page
|
||||
.getPreviousSiblings(block)
|
||||
.findLast(
|
||||
(block): block is EmbedBlockModel => block.flavour === 'affine:embed'
|
||||
);
|
||||
if (prevBlock) {
|
||||
setBlockId(prevBlock.id);
|
||||
const image = imageRef.current;
|
||||
if (image) {
|
||||
resetZoom();
|
||||
image.style.width = '50%'; // Reset the width to its original size
|
||||
image.style.height = 'auto'; // Reset the height to maintain aspect ratio
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteHandler = (blockId: string) => {
|
||||
const workspace = props.workspace;
|
||||
|
||||
const page = workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(blockId);
|
||||
assertExists(block);
|
||||
if (
|
||||
page
|
||||
.getPreviousSiblings(block)
|
||||
.findLast(
|
||||
(block): block is EmbedBlockModel => block.flavour === 'affine:embed'
|
||||
)
|
||||
) {
|
||||
const prevBlock = page
|
||||
.getPreviousSiblings(block)
|
||||
.findLast(
|
||||
(block): block is EmbedBlockModel => block.flavour === 'affine:embed'
|
||||
);
|
||||
if (prevBlock) {
|
||||
setBlockId(prevBlock.id);
|
||||
const image = imageRef.current;
|
||||
resetZoom();
|
||||
if (image) {
|
||||
image.style.width = '100%'; // Reset the width to its original size
|
||||
image.style.height = 'auto'; // Reset the height to maintain aspect ratio
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
page
|
||||
.getNextSiblings(block)
|
||||
.find(
|
||||
(block): block is EmbedBlockModel => block.flavour === 'affine:embed'
|
||||
)
|
||||
) {
|
||||
const nextBlock = page
|
||||
.getNextSiblings(block)
|
||||
.find(
|
||||
(block): block is EmbedBlockModel => block.flavour === 'affine:embed'
|
||||
);
|
||||
if (nextBlock) {
|
||||
const image = imageRef.current;
|
||||
resetZoom();
|
||||
if (image) {
|
||||
image.style.width = '100%'; // Reset the width to its original size
|
||||
image.style.height = 'auto'; // Reset the height to maintain aspect ratio
|
||||
}
|
||||
setBlockId(nextBlock.id);
|
||||
}
|
||||
} else {
|
||||
props.onClose();
|
||||
}
|
||||
page.deleteBlock(block);
|
||||
};
|
||||
|
||||
let actionbarTimeout: NodeJS.Timeout;
|
||||
|
||||
const downloadHandler = async (blockId: string | null) => {
|
||||
const workspace = props.workspace;
|
||||
const page = workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
if (typeof blockId === 'string') {
|
||||
const block = page.getBlockById(blockId) as EmbedBlockModel;
|
||||
assertExists(block);
|
||||
const store = await block.page.blobs;
|
||||
const url = store?.get(block.sourceId);
|
||||
const img = await url;
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
const arrayBuffer = await img.arrayBuffer();
|
||||
const buffer = new Uint8Array(arrayBuffer);
|
||||
let fileType: string;
|
||||
if (
|
||||
buffer[0] === 0x47 &&
|
||||
buffer[1] === 0x49 &&
|
||||
buffer[2] === 0x46 &&
|
||||
buffer[3] === 0x38
|
||||
) {
|
||||
fileType = 'image/gif';
|
||||
} else if (
|
||||
buffer[0] === 0x89 &&
|
||||
buffer[1] === 0x50 &&
|
||||
buffer[2] === 0x4e &&
|
||||
buffer[3] === 0x47
|
||||
) {
|
||||
fileType = 'image/png';
|
||||
} else if (
|
||||
buffer[0] === 0xff &&
|
||||
buffer[1] === 0xd8 &&
|
||||
buffer[2] === 0xff &&
|
||||
buffer[3] === 0xe0
|
||||
) {
|
||||
fileType = 'image/jpeg';
|
||||
} else {
|
||||
// unknown, fallback to png
|
||||
console.error('unknown image type');
|
||||
fileType = 'image/png';
|
||||
}
|
||||
const downloadUrl = URL.createObjectURL(
|
||||
new Blob([arrayBuffer], { type: fileType })
|
||||
);
|
||||
const a = document.createElement('a');
|
||||
const event = new MouseEvent('click');
|
||||
a.download = block.id;
|
||||
a.href = downloadUrl;
|
||||
a.dispatchEvent(event);
|
||||
|
||||
// cleanup
|
||||
a.remove();
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
clearTimeout(actionbarTimeout);
|
||||
setBIsActionBarVisible(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
actionbarTimeout = setTimeout(() => {
|
||||
setBIsActionBarVisible(false);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-testid="image-preview-modal" className={imagePreviewModalStyle}>
|
||||
<div className={imageNavigationControlStyle}>
|
||||
<span
|
||||
className={imagePreviewModalGoStyle}
|
||||
style={{
|
||||
left: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
previousImageHandler(blockId);
|
||||
}}
|
||||
>
|
||||
❮
|
||||
</span>
|
||||
<span
|
||||
className={imagePreviewModalGoStyle}
|
||||
style={{
|
||||
right: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
nextImageHandler(blockId);
|
||||
}}
|
||||
>
|
||||
❯
|
||||
</span>
|
||||
</div>
|
||||
<div className={imagePreviewModalContainerStyle}>
|
||||
<div
|
||||
className={clsx('zoom-area', { 'zoomed-bigger': isZoomedBigger })}
|
||||
ref={zoomRef}
|
||||
>
|
||||
<div className={imagePreviewModalCenterStyle}>
|
||||
<img
|
||||
data-blob-id={props.blockId}
|
||||
src={url}
|
||||
alt={caption}
|
||||
ref={imageRef}
|
||||
draggable={isZoomedBigger}
|
||||
onMouseDown={handleDragStart}
|
||||
onMouseMove={handleDrag}
|
||||
onMouseUp={handleDragEnd}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
width={'50%'}
|
||||
/>
|
||||
{isZoomedBigger ? null : (
|
||||
<p className={imagePreviewModalCaptionStyle}>{caption}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
props.onClose();
|
||||
@@ -99,67 +351,122 @@ const ImagePreviewModalImpl = (
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<span
|
||||
className={imagePreviewModalGoStyle}
|
||||
style={{
|
||||
left: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
const workspace = props.workspace;
|
||||
|
||||
const page = workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(blockId);
|
||||
assertExists(block);
|
||||
const prevBlock = page
|
||||
.getPreviousSiblings(block)
|
||||
.findLast(
|
||||
(block): block is EmbedBlockModel =>
|
||||
block.flavour === 'affine:embed'
|
||||
);
|
||||
if (prevBlock) {
|
||||
setBlockId(prevBlock.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
❮
|
||||
</span>
|
||||
<div className={imagePreviewModalContainerStyle}>
|
||||
<img
|
||||
data-blob-id={props.blockId}
|
||||
alt={caption}
|
||||
className={imagePreviewModalImageStyle}
|
||||
ref={imageRef}
|
||||
src={url}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className={imagePreviewModalGoStyle}
|
||||
style={{
|
||||
right: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
const workspace = props.workspace;
|
||||
|
||||
const page = workspace.getPage(props.pageId);
|
||||
assertExists(page);
|
||||
const block = page.getBlockById(blockId);
|
||||
assertExists(block);
|
||||
const nextBlock = page
|
||||
.getNextSiblings(block)
|
||||
.find(
|
||||
(block): block is EmbedBlockModel =>
|
||||
block.flavour === 'affine:embed'
|
||||
);
|
||||
if (nextBlock) {
|
||||
setBlockId(nextBlock.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
❯
|
||||
</span>
|
||||
{bIsActionBarVisible ? (
|
||||
<div
|
||||
className={imageBottomContainerStyle}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{isZoomedBigger && caption !== '' ? (
|
||||
<p className={captionStyle}>{caption}</p>
|
||||
) : null}
|
||||
<div className={imagePreviewActionBarStyle}>
|
||||
<div>
|
||||
<Button
|
||||
icon={<ArrowLeftSmallIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
previousImageHandler(blockId);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<ArrowRightSmallIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
nextImageHandler(blockId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={groupStyle}>
|
||||
<Button
|
||||
icon={<ViewBarIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={() => resetZoom()}
|
||||
/>
|
||||
<Button
|
||||
icon={<MinusIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={zoomOut}
|
||||
/>
|
||||
<span className={scaleIndicatorStyle}>{`${(
|
||||
currentScale * 100
|
||||
).toFixed(0)}%`}</span>
|
||||
<Button
|
||||
icon={<PlusIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={() => zoomIn()}
|
||||
/>
|
||||
</div>
|
||||
<div className={groupStyle}>
|
||||
<Button
|
||||
icon={<DownloadIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
downloadHandler(blockId).catch(err => {
|
||||
console.error('Could not download image', err);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<CopyIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={() => {
|
||||
if (!imageRef.current) {
|
||||
return;
|
||||
}
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = imageRef.current.naturalWidth;
|
||||
canvas.height = imageRef.current.naturalHeight;
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) {
|
||||
console.warn('Could not get canvas context');
|
||||
return;
|
||||
}
|
||||
context.drawImage(imageRef.current, 0, 0);
|
||||
canvas.toBlob(blob => {
|
||||
if (!blob) {
|
||||
console.warn('Could not get blob');
|
||||
return;
|
||||
}
|
||||
const dataUrl = URL.createObjectURL(blob);
|
||||
navigator.clipboard
|
||||
.write([new ClipboardItem({ 'image/png': blob })])
|
||||
.then(() => {
|
||||
console.log('Image copied to clipboard');
|
||||
URL.revokeObjectURL(dataUrl);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(
|
||||
'Error copying image to clipboard',
|
||||
error
|
||||
);
|
||||
URL.revokeObjectURL(dataUrl);
|
||||
});
|
||||
}, 'image/png');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={groupStyle}>
|
||||
<Button
|
||||
icon={<DeleteIcon />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
onClick={() => blockId && deleteHandler(blockId)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,8 +9,15 @@ export const CopyLink = ({ onItemClick, onSelect }: CommonMenuItemProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const copyUrl = useCallback(() => {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
toast(t['Copied link to clipboard']());
|
||||
navigator.clipboard
|
||||
.writeText(window.location.href)
|
||||
.then(() => {
|
||||
toast(t['Copied link to clipboard']());
|
||||
})
|
||||
.catch(err => {
|
||||
// TODO add error toast here
|
||||
console.error(err);
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,8 +35,13 @@ const ExportToPdfMenuItem = ({
|
||||
if (result !== undefined) {
|
||||
return;
|
||||
}
|
||||
contentParser.exportPdf();
|
||||
return contentParser.exportPdf();
|
||||
})
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'pdf' });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [currentEditor, onSelect]);
|
||||
if (currentEditor && currentEditor.mode === 'page') {
|
||||
@@ -66,7 +71,9 @@ const ExportToHtmlMenuItem = ({
|
||||
if (!contentParserRef.current) {
|
||||
contentParserRef.current = new ContentParser(currentEditor.page);
|
||||
}
|
||||
contentParserRef.current.exportHtml();
|
||||
contentParserRef.current.exportHtml().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
onSelect?.({ type: 'html' });
|
||||
}, [onSelect, currentEditor]);
|
||||
return (
|
||||
@@ -123,7 +130,9 @@ const ExportToMarkdownMenuItem = ({
|
||||
if (!contentParserRef.current) {
|
||||
contentParserRef.current = new ContentParser(currentEditor.page);
|
||||
}
|
||||
contentParserRef.current.exportMarkdown();
|
||||
contentParserRef.current.exportMarkdown().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
onSelect?.({ type: 'markdown' });
|
||||
}, [onSelect, currentEditor]);
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,11 @@ const DesktopThemeSync = memo(function DesktopThemeSync() {
|
||||
const onceRef = useRef(false);
|
||||
if (lastThemeRef.current !== theme || !onceRef.current) {
|
||||
if (environment.isDesktop && theme) {
|
||||
window.apis?.ui.handleThemeChange(theme as 'dark' | 'light' | 'system');
|
||||
window.apis?.ui
|
||||
.handleThemeChange(theme as 'dark' | 'light' | 'system')
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
lastThemeRef.current = theme;
|
||||
onceRef.current = true;
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.8"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
2
packages/env/package.json
vendored
2
packages/env/package.json
vendored
@@ -28,5 +28,5 @@
|
||||
"dependencies": {
|
||||
"lit": "^2.7.5"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
2
packages/env/src/blocksuite/index.ts
vendored
2
packages/env/src/blocksuite/index.ts
vendored
@@ -3,7 +3,7 @@ import type { Page } from '@blocksuite/store';
|
||||
export async function initPageWithPreloading(page: Page) {
|
||||
const workspace = page.workspace;
|
||||
const { data } = await import('@affine/templates/preloading.json');
|
||||
await workspace.importPageSnapshot(data['space:Qmo9-1SGTB'], page.id);
|
||||
await workspace.importPageSnapshot(data['space:hello-world'], page.id);
|
||||
}
|
||||
|
||||
export function initEmptyPage(page: Page): void {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/graphql",
|
||||
"version": "0.7.0-canary.12",
|
||||
"version": "0.7.0-canary.15",
|
||||
"description": "Autogenerated GraphQL client for affine.pro",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@toeverything/y-indexeddb": "workspace:*"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-dts": "^2.3.0"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"@blocksuite/store": "*",
|
||||
"lottie-web": "*"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@
|
||||
"test": "cross-env TS_NODE_TRANSPILE_ONLY=1 TS_NODE_PROJECT=./tsconfig.json node --test --loader ts-node/esm --experimental-specifier-resolution=node ./__tests__/**/*.mts",
|
||||
"version": "napi version"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly"
|
||||
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"jotai": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jotai": "^2.1.1",
|
||||
@@ -39,5 +40,5 @@
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -32,15 +32,21 @@ export function definePlugin<ID extends string>(
|
||||
|
||||
if (isServer) {
|
||||
if (serverAdapter) {
|
||||
serverAdapter.load().then(({ default: adapter }) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
serverAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
});
|
||||
console.log('register server adapter');
|
||||
serverAdapter
|
||||
.load()
|
||||
.then(({ default: adapter }) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
serverAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
} else if (isClient) {
|
||||
if (blockSuiteAdapter) {
|
||||
|
||||
@@ -47,5 +47,5 @@
|
||||
"@blocksuite/lit": "*",
|
||||
"@blocksuite/store": "*"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"./*.md": "./*.md",
|
||||
"./preloading.json": "./preloading.json"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
"version": 1,
|
||||
"data": {
|
||||
"space:meta": {
|
||||
"name": "Demo Workspace",
|
||||
"pages": [
|
||||
{
|
||||
"id": "Qmo9-1SGTB",
|
||||
"id": "hello-world",
|
||||
"title": "AFFiNE - not just a note taking app",
|
||||
"createDate": 1685676956753,
|
||||
"subpageIds": []
|
||||
"createDate": 1686627549555,
|
||||
"subpageIds": [],
|
||||
"jumpOnce": false,
|
||||
"updatedDate": 1686627553606
|
||||
}
|
||||
],
|
||||
"versions": {
|
||||
@@ -24,15 +27,15 @@
|
||||
"affine:database": 1
|
||||
}
|
||||
},
|
||||
"space:Qmo9-1SGTB": {
|
||||
"space:hello-world": {
|
||||
"_9D75ibqvd": {
|
||||
"sys:id": "_9D75ibqvd",
|
||||
"sys:flavour": "affine:page",
|
||||
"sys:children": [
|
||||
"4-IQVC-U5A",
|
||||
"Y4oz3g1LB6",
|
||||
"VNbg-Wz6Vs",
|
||||
"V7dUwRJxpY",
|
||||
"Y4oz3g1LB6",
|
||||
"1cFTd-rwDr",
|
||||
"A2hTNhHJSo",
|
||||
"JSoC9zIZDz",
|
||||
@@ -57,15 +60,15 @@
|
||||
],
|
||||
"prop:background": "--affine-background-secondary-color",
|
||||
"prop:index": "a2",
|
||||
"prop:xywh": "[866.5055790704506,86.14140870095756,326.7016703680564,500]"
|
||||
"prop:xywh": "[866.5055790704506,86.14140870095756,326.7016703680564,356]"
|
||||
},
|
||||
"VNbg-Wz6Vs": {
|
||||
"sys:id": "VNbg-Wz6Vs",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["Prrnq7ruGC", "6rgGQmAmfb", "KRefAQRNnh"],
|
||||
"sys:children": ["hiVj6pUziI", "6rgGQmAmfb", "KRefAQRNnh"],
|
||||
"prop:index": "a0",
|
||||
"prop:background": "--affine-tag-green",
|
||||
"prop:xywh": "[1211.9913594556406,86.0748937043449,552.5830233754074,572]"
|
||||
"prop:xywh": "[1211.9913594556406,86.0748937043449,552.5830233754074,796]"
|
||||
},
|
||||
"V7dUwRJxpY": {
|
||||
"sys:id": "V7dUwRJxpY",
|
||||
@@ -5907,16 +5910,16 @@
|
||||
"Y4oz3g1LB6": {
|
||||
"sys:id": "Y4oz3g1LB6",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["aPk03I3k9L"],
|
||||
"prop:xywh": "[855.7586305793726,-38.93174493636967,1488.043436415603,112]",
|
||||
"sys:children": ["Prrnq7ruGC"],
|
||||
"prop:xywh": "[855.7586305793726,-38.93174493636967,1488.043436415603,104]",
|
||||
"prop:index": "a0",
|
||||
"prop:background": "--affine-tag-yellow"
|
||||
},
|
||||
"1cFTd-rwDr": {
|
||||
"sys:id": "1cFTd-rwDr",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["2NKIdhpZZy", "4_plt-pF5i", "5O-z_KtfdV"],
|
||||
"prop:xywh": "[1782.1527708473825,83.59728260421294,554.4267667459387,573]",
|
||||
"sys:children": ["aPk03I3k9L", "4_plt-pF5i", "5O-z_KtfdV"],
|
||||
"prop:xywh": "[1782.1527708473825,83.59728260421294,554.4267667459387,668]",
|
||||
"prop:index": "a0",
|
||||
"prop:background": "--affine-tag-blue"
|
||||
},
|
||||
@@ -5924,7 +5927,7 @@
|
||||
"sys:id": "A2hTNhHJSo",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["1t5gAmmDk1", "Ru5RZxl2cw", "m09CQTVNta"],
|
||||
"prop:xywh": "[1862.2162713385649,667.8687461185463,471.2475208977704,569]",
|
||||
"prop:xywh": "[1862.2162713385649,667.8687461185463,471.2475208977704,724]",
|
||||
"prop:index": "a0",
|
||||
"prop:background": "--affine-tag-red"
|
||||
},
|
||||
@@ -5934,13 +5937,13 @@
|
||||
"sys:children": ["RYwAAsT0jt", "yui0v4a-DG", "CuW0As_WD5"],
|
||||
"prop:index": "a0",
|
||||
"prop:background": "--affine-tag-purple",
|
||||
"prop:xywh": "[1310.31891616522,676.1486476697154,537.1322406806248,561]"
|
||||
"prop:xywh": "[1310.31891616522,676.1486476697154,537.1322406806248,724]"
|
||||
},
|
||||
"Vq_8QO3ruz": {
|
||||
"sys:id": "Vq_8QO3ruz",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["Z6tZpqYD1i", "cSq4fYa62E", "e-ekSJPKh0"],
|
||||
"prop:xywh": "[824.3933427871591,677.7709857969486,475.6451530544002,542]",
|
||||
"prop:xywh": "[824.3933427871591,677.7709857969486,475.6451530544002,724]",
|
||||
"prop:index": "a0",
|
||||
"prop:background": "--affine-tag-blue"
|
||||
},
|
||||
@@ -5951,17 +5954,16 @@
|
||||
"YQPpZUitL9",
|
||||
"jNb1ieggGw",
|
||||
"yln9MU-iVm",
|
||||
"z1fWcxw9qe",
|
||||
"rY1fVETRzE"
|
||||
],
|
||||
"prop:index": "a0",
|
||||
"prop:background": "--affine-tag-green",
|
||||
"prop:xywh": "[817.0745095091336,1272.4084873924633,586.3018678030073,672]"
|
||||
"prop:xywh": "[817.0745095091336,1272.4084873924633,586.3018678030073,922]"
|
||||
},
|
||||
"3An3wRFKN_": {
|
||||
"sys:id": "3An3wRFKN_",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["3MnKwqEw_Q"],
|
||||
"sys:children": ["KZrhdN52ZD", "3MnKwqEw_Q"],
|
||||
"prop:xywh": "[-264.94381566608683,389.00823320424837,494.2811077047478,314]",
|
||||
"prop:index": "a2",
|
||||
"prop:background": "--affine-background-secondary-color"
|
||||
@@ -5970,7 +5972,7 @@
|
||||
"sys:id": "U2hR9Lu1E7",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["LYes52XNDN"],
|
||||
"prop:xywh": "[2918.2644723261433,881.0630462339941,539.4086027654356,439]",
|
||||
"prop:xywh": "[2918.2644723261433,881.0630462339941,539.4086027654356,655]",
|
||||
"prop:index": "a2",
|
||||
"prop:background": "--affine-background-secondary-color"
|
||||
},
|
||||
@@ -5978,7 +5980,7 @@
|
||||
"sys:id": "nOERveFG0j",
|
||||
"sys:flavour": "affine:frame",
|
||||
"sys:children": ["SjyfxmcAjc"],
|
||||
"prop:xywh": "[2919.8341116576826,1349.0080470072992,535.7138283708327,451]",
|
||||
"prop:xywh": "[2919.8341116576826,1349.0080470072992,535.7138283708327,632]",
|
||||
"prop:index": "a2",
|
||||
"prop:background": "--affine-background-secondary-color"
|
||||
},
|
||||
@@ -6058,8 +6060,17 @@
|
||||
"sys:id": "Prrnq7ruGC",
|
||||
"sys:flavour": "affine:paragraph",
|
||||
"sys:children": [],
|
||||
"prop:type": "h3",
|
||||
"prop:text": [{ "insert": "Create Your Workspace" }]
|
||||
"prop:type": "h1",
|
||||
"prop:text": [
|
||||
{ "insert": "Let's " },
|
||||
{ "insert": "Write", "attributes": { "bold": true } },
|
||||
{ "insert": ", " },
|
||||
{ "insert": "Draw", "attributes": { "bold": true } },
|
||||
{ "insert": " and " },
|
||||
{ "insert": "Plan", "attributes": { "bold": true } },
|
||||
{ "insert": " with " },
|
||||
{ "insert": "AFFiNE", "attributes": { "bold": true } }
|
||||
]
|
||||
},
|
||||
"6rgGQmAmfb": {
|
||||
"sys:id": "6rgGQmAmfb",
|
||||
@@ -6076,22 +6087,6 @@
|
||||
"sys:id": "aPk03I3k9L",
|
||||
"sys:flavour": "affine:paragraph",
|
||||
"sys:children": [],
|
||||
"prop:type": "h2",
|
||||
"prop:text": [
|
||||
{ "insert": "Let's " },
|
||||
{ "insert": "Write", "attributes": { "bold": true } },
|
||||
{ "insert": ", " },
|
||||
{ "insert": "Draw", "attributes": { "bold": true } },
|
||||
{ "insert": " and " },
|
||||
{ "insert": "Plan", "attributes": { "bold": true } },
|
||||
{ "insert": " with " },
|
||||
{ "insert": "AFFiNE", "attributes": { "bold": true } }
|
||||
]
|
||||
},
|
||||
"2NKIdhpZZy": {
|
||||
"sys:id": "2NKIdhpZZy",
|
||||
"sys:flavour": "affine:paragraph",
|
||||
"sys:children": [],
|
||||
"prop:type": "h3",
|
||||
"prop:text": [{ "insert": "Organise Your Pages" }]
|
||||
},
|
||||
@@ -6196,8 +6191,8 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"z1fWcxw9qe": {
|
||||
"sys:id": "z1fWcxw9qe",
|
||||
"KZrhdN52ZD": {
|
||||
"sys:id": "KZrhdN52ZD",
|
||||
"sys:flavour": "affine:divider",
|
||||
"sys:children": []
|
||||
},
|
||||
@@ -6321,8 +6316,8 @@
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"rY1fVETRzE": {
|
||||
"sys:id": "rY1fVETRzE",
|
||||
"5O-z_KtfdV": {
|
||||
"sys:id": "5O-z_KtfdV",
|
||||
"sys:flavour": "affine:embed",
|
||||
"sys:children": [],
|
||||
"prop:type": "image",
|
||||
@@ -6331,8 +6326,8 @@
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"e-ekSJPKh0": {
|
||||
"sys:id": "e-ekSJPKh0",
|
||||
"m09CQTVNta": {
|
||||
"sys:id": "m09CQTVNta",
|
||||
"sys:flavour": "affine:embed",
|
||||
"sys:children": [],
|
||||
"prop:type": "image",
|
||||
@@ -6351,8 +6346,8 @@
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"5O-z_KtfdV": {
|
||||
"sys:id": "5O-z_KtfdV",
|
||||
"e-ekSJPKh0": {
|
||||
"sys:id": "e-ekSJPKh0",
|
||||
"sys:flavour": "affine:embed",
|
||||
"sys:children": [],
|
||||
"prop:type": "image",
|
||||
@@ -6361,8 +6356,8 @@
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"m09CQTVNta": {
|
||||
"sys:id": "m09CQTVNta",
|
||||
"rY1fVETRzE": {
|
||||
"sys:id": "rY1fVETRzE",
|
||||
"sys:flavour": "affine:embed",
|
||||
"sys:children": [],
|
||||
"prop:type": "image",
|
||||
@@ -6371,25 +6366,32 @@
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"SjyfxmcAjc": {
|
||||
"sys:id": "SjyfxmcAjc",
|
||||
"sys:flavour": "affine:embed",
|
||||
"sys:children": [],
|
||||
"prop:type": "image",
|
||||
"prop:sourceId": "https://cdn.affine.pro/047ebf2c9a5c7c9d8521c2ea5e6140ff7732ef9e28a9f944e9bf3ca4.png",
|
||||
"prop:caption": "",
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"LYes52XNDN": {
|
||||
"sys:id": "LYes52XNDN",
|
||||
"sys:flavour": "affine:embed",
|
||||
"sys:children": [],
|
||||
"prop:type": "image",
|
||||
"prop:sourceId": "https://cdn.affine.pro/047ebf2c9a5c7c9d8521c2ea5e6140ff7732ef9e28a9f944e9bf3ca4.png",
|
||||
"prop:caption": "",
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"SjyfxmcAjc": {
|
||||
"sys:id": "SjyfxmcAjc",
|
||||
"sys:flavour": "affine:embed",
|
||||
"sys:children": [],
|
||||
"prop:type": "image",
|
||||
"prop:sourceId": "https://cdn.affine.pro/6aa785ee927547ce9dd9d7b43e01eac948337fe57571443e87bc3a60.png",
|
||||
"prop:caption": "",
|
||||
"prop:width": 0,
|
||||
"prop:height": 0
|
||||
},
|
||||
"hiVj6pUziI": {
|
||||
"sys:id": "hiVj6pUziI",
|
||||
"sys:flavour": "affine:paragraph",
|
||||
"sys:children": [],
|
||||
"prop:text": [{ "insert": "Create Your Workspace" }],
|
||||
"prop:type": "h3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,5 +40,5 @@
|
||||
"next": "=13.4.2",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -207,10 +207,12 @@ export function createAffineAuth(prefix = '/') {
|
||||
}),
|
||||
}).then(r => r.json()) as Promise<LoginResponse>;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error) {
|
||||
if (error.code === 'auth/popup-closed-by-user') {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
error instanceof Error &&
|
||||
'code' in error &&
|
||||
error.code === 'auth/popup-closed-by-user'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
logger.error('Failed to sign in', error);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,9 @@ const createSQLiteProvider = (
|
||||
if (origin === sqliteOrigin) {
|
||||
return;
|
||||
}
|
||||
apis.db.applyDocUpdate(blockSuiteWorkspace.id, update);
|
||||
apis.db.applyDocUpdate(blockSuiteWorkspace.id, update).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
let unsubscribe = () => {};
|
||||
@@ -247,7 +249,7 @@ const createSQLiteDBDownloadProvider = (
|
||||
const diff = Y.encodeStateAsUpdate(blockSuiteWorkspace.doc, updates);
|
||||
|
||||
// also apply updates to sqlite
|
||||
apis.db.applyDocUpdate(blockSuiteWorkspace.id, diff);
|
||||
await apis.db.applyDocUpdate(blockSuiteWorkspace.id, diff);
|
||||
|
||||
const bs = blockSuiteWorkspace.blobs;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@toeverything/y-indexeddb",
|
||||
"type": "module",
|
||||
"version": "0.7.0-canary.12",
|
||||
"version": "0.7.0-canary.15",
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
},
|
||||
@@ -28,8 +28,8 @@
|
||||
"idb": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230613142146-d72d4600-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230613142146-d72d4600-nightly",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-dts": "^2.3.0",
|
||||
"y-indexeddb": "^9.0.11"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { assertExists, uuidv4, Workspace } from '@blocksuite/store';
|
||||
import { openDB } from 'idb';
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
@@ -24,6 +24,21 @@ import {
|
||||
setMergeCount,
|
||||
} from '../index';
|
||||
|
||||
function initEmptyPage(page: Page) {
|
||||
const pageBlockId = page.addBlock('affine:page', {
|
||||
title: new page.Text(''),
|
||||
});
|
||||
const surfaceBlockId = page.addBlock('affine:surface', {}, pageBlockId);
|
||||
const frameBLockId = page.addBlock('affine:frame', {}, pageBlockId);
|
||||
const paragraphBlockId = page.addBlock('affine:paragraph', {}, frameBLockId);
|
||||
return {
|
||||
pageBlockId,
|
||||
surfaceBlockId,
|
||||
frameBLockId,
|
||||
paragraphBlockId,
|
||||
};
|
||||
}
|
||||
|
||||
async function getUpdates(id: string): Promise<Uint8Array[]> {
|
||||
const db = await openDB(rootDBName, dbVersion);
|
||||
const store = await db
|
||||
@@ -73,7 +88,8 @@ describe('indexeddb provider', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
const page = workspace.createPage('page0');
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
await page.waitForLoaded();
|
||||
const pageBlockId = page.addBlock('affine:page', { title: '' });
|
||||
const frameId = page.addBlock('affine:frame', {}, pageBlockId);
|
||||
page.addBlock('affine:paragraph', {}, frameId);
|
||||
@@ -143,7 +159,8 @@ describe('indexeddb provider', () => {
|
||||
provider.disconnect();
|
||||
expect(provider.connected).toBe(false);
|
||||
{
|
||||
const page = workspace.createPage('page0');
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
await page.waitForLoaded();
|
||||
const pageBlockId = page.addBlock('affine:page', { title: '' });
|
||||
const frameId = page.addBlock('affine:frame', {}, pageBlockId);
|
||||
page.addBlock('affine:paragraph', {}, frameId);
|
||||
@@ -214,10 +231,11 @@ describe('indexeddb provider', () => {
|
||||
);
|
||||
provider.connect();
|
||||
{
|
||||
const page = workspace.createPage('page0');
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
await page.waitForLoaded();
|
||||
const pageBlockId = page.addBlock('affine:page', { title: '' });
|
||||
const frameId = page.addBlock('affine:frame', {}, pageBlockId);
|
||||
for (let i = 0; i < 100; i++) {
|
||||
for (let i = 0; i < 99; i++) {
|
||||
page.addBlock('affine:paragraph', {}, frameId);
|
||||
}
|
||||
}
|
||||
@@ -372,9 +390,89 @@ describe('milestone', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('subDoc', () => {
|
||||
test('basic', async () => {
|
||||
let json1: any, json2: any;
|
||||
{
|
||||
const doc = new Doc();
|
||||
const map = doc.getMap();
|
||||
const subDoc = new Doc();
|
||||
subDoc.load();
|
||||
map.set('1', subDoc);
|
||||
map.set('2', 'test');
|
||||
const provider = createIndexedDBProvider('test', doc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
provider.disconnect();
|
||||
json1 = doc.toJSON();
|
||||
}
|
||||
{
|
||||
const doc = new Doc();
|
||||
const provider = createIndexedDBProvider('test', doc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
const map = doc.getMap();
|
||||
const subDoc = map.get('1') as Doc;
|
||||
subDoc.load();
|
||||
provider.disconnect();
|
||||
json2 = doc.toJSON();
|
||||
}
|
||||
expect(json1['']['1'].toJSON()).toEqual(json2['']['1'].toJSON());
|
||||
expect(json1['']['2']).toEqual(json2['']['2']);
|
||||
});
|
||||
|
||||
test('blocksuite', async () => {
|
||||
const page0 = workspace.createPage({
|
||||
id: 'page0',
|
||||
});
|
||||
await page0.waitForLoaded();
|
||||
const { paragraphBlockId: paragraphBlockIdPage1 } = initEmptyPage(page0);
|
||||
const provider = createIndexedDBProvider(
|
||||
workspace.id,
|
||||
workspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
provider.connect();
|
||||
const page1 = workspace.createPage({
|
||||
id: 'page1',
|
||||
});
|
||||
await page1.waitForLoaded();
|
||||
const { paragraphBlockId: paragraphBlockIdPage2 } = initEmptyPage(page1);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
provider.disconnect();
|
||||
{
|
||||
const newWorkspace = new Workspace({
|
||||
id,
|
||||
isSSR: true,
|
||||
});
|
||||
newWorkspace.register(AffineSchemas).register(__unstableSchemas);
|
||||
const provider = createIndexedDBProvider(
|
||||
newWorkspace.id,
|
||||
newWorkspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
const page0 = newWorkspace.getPage('page0') as Page;
|
||||
await page0.waitForLoaded();
|
||||
{
|
||||
const block = page0.getBlockById(paragraphBlockIdPage1);
|
||||
assertExists(block);
|
||||
}
|
||||
const page1 = newWorkspace.getPage('page1') as Page;
|
||||
await page1.waitForLoaded();
|
||||
{
|
||||
const block = page1.getBlockById(paragraphBlockIdPage2);
|
||||
assertExists(block);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('utils', () => {
|
||||
test('download binary', async () => {
|
||||
const page = workspace.createPage('page0');
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
await page.waitForLoaded();
|
||||
initEmptyPage(page);
|
||||
const provider = createIndexedDBProvider(
|
||||
workspace.id,
|
||||
@@ -397,7 +495,12 @@ describe('utils', () => {
|
||||
applyUpdate(newWorkspace.doc, update);
|
||||
await new Promise<void>(resolve =>
|
||||
setTimeout(() => {
|
||||
expect(workspace.doc.toJSON()).toEqual(newWorkspace.doc.toJSON());
|
||||
expect(workspace.doc.toJSON()['meta']).toEqual(
|
||||
newWorkspace.doc.toJSON()['meta']
|
||||
);
|
||||
expect(Object.keys(workspace.doc.toJSON()['spaces'])).toEqual(
|
||||
Object.keys(newWorkspace.doc.toJSON()['spaces'])
|
||||
);
|
||||
resolve();
|
||||
}, 0)
|
||||
);
|
||||
|
||||
@@ -142,6 +142,12 @@ export const getMilestones = async (
|
||||
return milestone.milestone;
|
||||
};
|
||||
|
||||
type SubDocsEvent = {
|
||||
added: Set<Doc>;
|
||||
removed: Set<Doc>;
|
||||
loaded: Set<Doc>;
|
||||
};
|
||||
|
||||
export const createIndexedDBProvider = (
|
||||
id: string,
|
||||
doc: Doc,
|
||||
@@ -151,62 +157,175 @@ export const createIndexedDBProvider = (
|
||||
let reject: (reason?: unknown) => void;
|
||||
let early = true;
|
||||
let connected = false;
|
||||
|
||||
async function handleUpdate(update: Uint8Array, origin: unknown) {
|
||||
const db = await dbPromise;
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
if (origin === indexeddbOrigin) {
|
||||
return;
|
||||
}
|
||||
const store = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
let data = await store.get(id);
|
||||
if (!data) {
|
||||
data = {
|
||||
id,
|
||||
updates: [],
|
||||
};
|
||||
}
|
||||
data.updates.push({
|
||||
timestamp: Date.now(),
|
||||
update,
|
||||
});
|
||||
if (data.updates.length > mergeCount) {
|
||||
const updates = data.updates.map(({ update }) => update);
|
||||
const doc = new Doc();
|
||||
doc.transact(() => {
|
||||
updates.forEach(update => {
|
||||
applyUpdate(doc, update, indexeddbOrigin);
|
||||
});
|
||||
}, indexeddbOrigin);
|
||||
|
||||
const update = encodeStateAsUpdate(doc);
|
||||
data = {
|
||||
id,
|
||||
updates: [
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update,
|
||||
},
|
||||
],
|
||||
};
|
||||
await writeOperation(store.put(data));
|
||||
} else {
|
||||
await writeOperation(store.put(data));
|
||||
}
|
||||
}
|
||||
|
||||
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
|
||||
upgrade: upgradeDB,
|
||||
});
|
||||
const handleDestroy = async () => {
|
||||
connected = true;
|
||||
const db = await dbPromise;
|
||||
db.close();
|
||||
|
||||
const updateHandlerMap = new WeakMap<
|
||||
Doc,
|
||||
(update: Uint8Array, origin: unknown) => void
|
||||
>();
|
||||
const destroyHandlerMap = new WeakMap<Doc, () => void>();
|
||||
const subDocsHandlerMap = new WeakMap<Doc, (event: SubDocsEvent) => void>();
|
||||
|
||||
const createOrGetHandleUpdate = (id: string, doc: Doc) => {
|
||||
if (updateHandlerMap.has(doc)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return updateHandlerMap.get(doc)!;
|
||||
}
|
||||
const fn = async function handleUpdate(
|
||||
update: Uint8Array,
|
||||
origin: unknown
|
||||
) {
|
||||
const db = await dbPromise;
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
if (origin === indexeddbOrigin) {
|
||||
return;
|
||||
}
|
||||
const store = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
let data = await store.get(id);
|
||||
if (!data) {
|
||||
data = {
|
||||
id,
|
||||
updates: [],
|
||||
};
|
||||
}
|
||||
data.updates.push({
|
||||
timestamp: Date.now(),
|
||||
update,
|
||||
});
|
||||
if (data.updates.length > mergeCount) {
|
||||
const updates = data.updates.map(({ update }) => update);
|
||||
const doc = new Doc();
|
||||
doc.transact(() => {
|
||||
updates.forEach(update => {
|
||||
applyUpdate(doc, update, indexeddbOrigin);
|
||||
});
|
||||
}, indexeddbOrigin);
|
||||
|
||||
const update = encodeStateAsUpdate(doc);
|
||||
data = {
|
||||
id,
|
||||
updates: [
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update,
|
||||
},
|
||||
],
|
||||
};
|
||||
await writeOperation(store.put(data));
|
||||
} else {
|
||||
await writeOperation(store.put(data));
|
||||
}
|
||||
};
|
||||
updateHandlerMap.set(doc, fn);
|
||||
return fn;
|
||||
};
|
||||
|
||||
/* deepscan-disable UNUSED_PARAM */
|
||||
const createOrGetHandleDestroy = (_: string, doc: Doc) => {
|
||||
if (destroyHandlerMap.has(doc)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return destroyHandlerMap.get(doc)!;
|
||||
}
|
||||
const fn = async function handleDestroy() {
|
||||
const db = await dbPromise;
|
||||
db.close();
|
||||
};
|
||||
destroyHandlerMap.set(doc, fn);
|
||||
return fn;
|
||||
};
|
||||
|
||||
/* deepscan-disable UNUSED_PARAM */
|
||||
const createOrGetHandleSubDocs = (_: string, doc: Doc) => {
|
||||
if (subDocsHandlerMap.has(doc)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return subDocsHandlerMap.get(doc)!;
|
||||
}
|
||||
const fn = async function handleSubDocs(event: SubDocsEvent) {
|
||||
event.removed.forEach(doc => {
|
||||
unTrackDoc(doc.guid, doc);
|
||||
});
|
||||
event.loaded.forEach(doc => {
|
||||
trackDoc(doc.guid, doc);
|
||||
});
|
||||
};
|
||||
subDocsHandlerMap.set(doc, fn);
|
||||
return fn;
|
||||
};
|
||||
|
||||
function trackDoc(id: string, doc: Doc) {
|
||||
doc.on('update', createOrGetHandleUpdate(id, doc));
|
||||
doc.on('destroy', createOrGetHandleDestroy(id, doc));
|
||||
doc.on('subdocs', createOrGetHandleSubDocs(id, doc));
|
||||
}
|
||||
|
||||
function unTrackDoc(id: string, doc: Doc) {
|
||||
doc.subdocs.forEach(doc => {
|
||||
unTrackDoc(doc.guid, doc);
|
||||
});
|
||||
doc.off('update', createOrGetHandleUpdate(id, doc));
|
||||
doc.off('destroy', createOrGetHandleDestroy(id, doc));
|
||||
doc.off('subdocs', createOrGetHandleSubDocs(id, doc));
|
||||
}
|
||||
|
||||
async function saveDocOperation(id: string, doc: Doc) {
|
||||
const db = await dbPromise;
|
||||
const store = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
const data = await store.get(id);
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
await writeOperation(
|
||||
db.put('workspace', {
|
||||
id,
|
||||
updates: [
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update: encodeStateAsUpdate(doc),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const updates = data.updates.map(({ update }) => update);
|
||||
const fakeDoc = new Doc();
|
||||
fakeDoc.transact(() => {
|
||||
updates.forEach(update => {
|
||||
applyUpdate(fakeDoc, update);
|
||||
});
|
||||
}, indexeddbOrigin);
|
||||
const newUpdate = diffUpdate(
|
||||
encodeStateAsUpdate(doc),
|
||||
encodeStateAsUpdate(fakeDoc)
|
||||
);
|
||||
await writeOperation(
|
||||
store.put({
|
||||
...data,
|
||||
updates: [
|
||||
...data.updates,
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update: newUpdate,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
doc.transact(() => {
|
||||
updates.forEach(update => {
|
||||
applyUpdate(doc, update);
|
||||
});
|
||||
}, indexeddbOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
const apis = {
|
||||
connect: async () => {
|
||||
if (connected) return;
|
||||
@@ -217,60 +336,23 @@ export const createIndexedDBProvider = (
|
||||
reject = _reject;
|
||||
});
|
||||
connected = true;
|
||||
doc.on('update', handleUpdate);
|
||||
doc.on('destroy', handleDestroy);
|
||||
// only run promise below, otherwise the logic is incorrect
|
||||
trackDoc(id, doc);
|
||||
// only the runs `await` below, otherwise the logic is incorrect
|
||||
const db = await dbPromise;
|
||||
await tryMigrate(db, id, dbName);
|
||||
const store = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
const data = await store.get(id);
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
await writeOperation(
|
||||
db.put('workspace', {
|
||||
id,
|
||||
updates: [
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update: encodeStateAsUpdate(doc),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const updates = data.updates.map(({ update }) => update);
|
||||
const fakeDoc = new Doc();
|
||||
fakeDoc.transact(() => {
|
||||
updates.forEach(update => {
|
||||
applyUpdate(fakeDoc, update);
|
||||
});
|
||||
}, indexeddbOrigin);
|
||||
const newUpdate = diffUpdate(
|
||||
encodeStateAsUpdate(doc),
|
||||
encodeStateAsUpdate(fakeDoc)
|
||||
);
|
||||
await writeOperation(
|
||||
store.put({
|
||||
...data,
|
||||
updates: [
|
||||
...data.updates,
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update: newUpdate,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
doc.transact(() => {
|
||||
updates.forEach(update => {
|
||||
applyUpdate(doc, update);
|
||||
});
|
||||
}, indexeddbOrigin);
|
||||
const docs: [string, Doc][] = [];
|
||||
docs.push([id, doc]);
|
||||
while (docs.length > 0) {
|
||||
const [id, doc] = docs.pop() as [string, Doc];
|
||||
await saveDocOperation(id, doc);
|
||||
doc.subdocs.forEach(doc => {
|
||||
docs.push([doc.guid, doc]);
|
||||
});
|
||||
}
|
||||
|
||||
early = false;
|
||||
resolve();
|
||||
},
|
||||
@@ -279,8 +361,7 @@ export const createIndexedDBProvider = (
|
||||
if (early) {
|
||||
reject(new EarlyDisconnectError());
|
||||
}
|
||||
doc.off('update', handleUpdate);
|
||||
doc.off('destroy', handleDestroy);
|
||||
unTrackDoc(id, doc);
|
||||
},
|
||||
async cleanup() {
|
||||
if (connected) {
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "../env"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -12,47 +12,48 @@ import { openAIApiKeyAtom, useChatAtoms } from '../core/hooks';
|
||||
import { detailContentActionsStyle, detailContentStyle } from './index.css';
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
import('@blocksuite/blocks').then(({ FormatQuickBar }) => {
|
||||
FormatQuickBar.customElements.push((_page, getSelection) => {
|
||||
const div = document.createElement('div');
|
||||
const root = createRoot(div);
|
||||
import('@blocksuite/blocks')
|
||||
.then(({ FormatQuickBar }) => {
|
||||
FormatQuickBar.customElements.push((_page, getSelection) => {
|
||||
const div = document.createElement('div');
|
||||
const root = createRoot(div);
|
||||
|
||||
const AskAI = (): ReactElement => {
|
||||
const { conversationAtom } = useChatAtoms();
|
||||
const call = useSetAtom(conversationAtom);
|
||||
const AskAI = (): ReactElement => {
|
||||
const { conversationAtom } = useChatAtoms();
|
||||
const call = useSetAtom(conversationAtom);
|
||||
const onClickAskAI = useCallback(() => {
|
||||
const selection = getSelection();
|
||||
if (selection != null) {
|
||||
const text = selection.models
|
||||
.map(model => {
|
||||
return model.text?.toString();
|
||||
})
|
||||
.filter((v): v is string => Boolean(v))
|
||||
.join('\n');
|
||||
console.log('selected text:', text);
|
||||
call(
|
||||
`I selected some text from the document: \n"${text}."`
|
||||
).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}, [call]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
const selection = getSelection();
|
||||
if (selection != null) {
|
||||
const text = selection.models
|
||||
.map(model => {
|
||||
return model.text?.toString();
|
||||
})
|
||||
.filter((v): v is string => Boolean(v))
|
||||
.join('\n');
|
||||
console.log('selected text:', text);
|
||||
void call(
|
||||
`I selected some text from the document: \n"${text}."`
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Ask AI
|
||||
</div>
|
||||
return <div onClick={onClickAskAI}>Ask AI</div>;
|
||||
};
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<Provider store={rootStore}>
|
||||
<AskAI />
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
};
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<Provider store={rootStore}>
|
||||
<AskAI />
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
return div;
|
||||
return div;
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const Actions = () => {
|
||||
|
||||
@@ -49,9 +49,14 @@ const getConversationAtom = (chat: ConversationChain) => {
|
||||
throw new Error();
|
||||
}
|
||||
const memory = chat.memory as BufferMemory;
|
||||
void memory.chatHistory.getMessages().then(messages => {
|
||||
setAtom(messages);
|
||||
});
|
||||
memory.chatHistory
|
||||
.getMessages()
|
||||
.then(messages => {
|
||||
setAtom(messages);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
const llmStart = (): void => {
|
||||
setAtom(conversations => [...conversations, new AIChatMessage('')]);
|
||||
};
|
||||
@@ -86,9 +91,14 @@ const getConversationAtom = (chat: ConversationChain) => {
|
||||
});
|
||||
// refresh messages
|
||||
const memory = chat.memory as BufferMemory;
|
||||
void memory.chatHistory.getMessages().then(messages => {
|
||||
set(conversationBaseAtom, messages);
|
||||
});
|
||||
memory.chatHistory
|
||||
.getMessages()
|
||||
.then(messages => {
|
||||
set(conversationBaseAtom, messages);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
conversationWeakMap.set(chat, conversationAtom);
|
||||
|
||||
2
tests/fixtures/package.json
vendored
2
tests/fixtures/package.json
vendored
@@ -3,5 +3,5 @@
|
||||
"exports": {
|
||||
"./*": "./*"
|
||||
},
|
||||
"version": "0.7.0-canary.12"
|
||||
"version": "0.7.0-canary.15"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine-test/kit",
|
||||
"private": true,
|
||||
"version": "0.7.0-canary.12",
|
||||
"version": "0.7.0-canary.15",
|
||||
"exports": {
|
||||
"./playwright": "./playwright.ts"
|
||||
},
|
||||
|
||||
330
yarn.lock
330
yarn.lock
@@ -158,6 +158,10 @@ __metadata:
|
||||
dependencies:
|
||||
"@affine-test/kit": "workspace:*"
|
||||
"@affine/native": "workspace:*"
|
||||
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
"@electron-forge/cli": ^6.1.1
|
||||
"@electron-forge/core": ^6.1.1
|
||||
"@electron-forge/core-utils": ^6.1.1
|
||||
@@ -172,7 +176,6 @@ __metadata:
|
||||
"@types/fs-extra": ^11.0.1
|
||||
"@types/uuid": ^9.0.1
|
||||
async-call-rpc: ^6.3.1
|
||||
cheerio: ^1.0.0-rc.12
|
||||
cross-env: 7.0.3
|
||||
electron: =25.0.1
|
||||
electron-log: ^5.0.0-beta.24
|
||||
@@ -181,6 +184,8 @@ __metadata:
|
||||
electron-window-state: ^5.0.3
|
||||
esbuild: ^0.17.19
|
||||
fs-extra: ^11.1.1
|
||||
jotai: ^2.1.1
|
||||
link-preview-js: ^3.0.4
|
||||
lodash-es: ^4.17.21
|
||||
nanoid: ^4.0.2
|
||||
playwright: =1.33.0
|
||||
@@ -296,6 +301,7 @@ __metadata:
|
||||
eslint-plugin-react: ^7.32.2
|
||||
eslint-plugin-react-hooks: ^4.6.0
|
||||
eslint-plugin-simple-import-sort: ^10.0.0
|
||||
eslint-plugin-sonarjs: ^0.19.0
|
||||
eslint-plugin-unicorn: ^47.0.0
|
||||
eslint-plugin-unused-imports: ^2.0.0
|
||||
fake-indexeddb: 4.0.1
|
||||
@@ -2605,6 +2611,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.14.0":
|
||||
version: 7.22.5
|
||||
resolution: "@babel/runtime@npm:7.22.5"
|
||||
dependencies:
|
||||
regenerator-runtime: ^0.13.11
|
||||
checksum: 12a50b7de2531beef38840d17af50c55a094253697600cee255311222390c68eed704829308d4fd305e1b3dfbce113272e428e9d9d45b1730e0fede997eaceb1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.21.9, @babel/template@npm:^7.3.3":
|
||||
version: 7.21.9
|
||||
resolution: "@babel/template@npm:7.21.9"
|
||||
@@ -2713,6 +2728,32 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/blocks@npm:0.0.0-20230613142146-d72d4600-nightly":
|
||||
version: 0.0.0-20230613142146-d72d4600-nightly
|
||||
resolution: "@blocksuite/blocks@npm:0.0.0-20230613142146-d72d4600-nightly"
|
||||
dependencies:
|
||||
"@blocksuite/connector": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@blocksuite/global": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@blocksuite/phasor": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@blocksuite/virgo": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@floating-ui/dom": ^1.2.9
|
||||
hotkeys-js: ^3.10.1
|
||||
html-to-image: ^1.11.11
|
||||
jspdf: ^2.5.1
|
||||
jszip: ^3.10.1
|
||||
lit: ^2.7.3
|
||||
marked: ^4.2.12
|
||||
shiki: ^0.14.1
|
||||
turndown: ^7.1.1
|
||||
zod: ^3.21.4
|
||||
peerDependencies:
|
||||
"@blocksuite/lit": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@blocksuite/store": 0.0.0-20230613142146-d72d4600-nightly
|
||||
yjs: ^13
|
||||
checksum: 949d37335c16b6c6fd224a9ce6f0e356d500e139010706351a277f5ec69ebab309016267d8ecbb8d95adcae8d0edad2c99fb763f3caf158cf28d6bb12b8613d3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/connector@npm:0.0.0-20230607055421-9b20fcaf-nightly":
|
||||
version: 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
resolution: "@blocksuite/connector@npm:0.0.0-20230607055421-9b20fcaf-nightly"
|
||||
@@ -2720,6 +2761,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/connector@npm:0.0.0-20230613142146-d72d4600-nightly":
|
||||
version: 0.0.0-20230613142146-d72d4600-nightly
|
||||
resolution: "@blocksuite/connector@npm:0.0.0-20230613142146-d72d4600-nightly"
|
||||
checksum: c5fcbae09c72cd61ecf8d4366c720edad7704f7737d0635e420fc0359ab9f4fe5a964581eee8e908ac1cab23abfba8bd770490bf674a594dc53624d6da775d5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/editor@npm:0.0.0-20230607055421-9b20fcaf-nightly":
|
||||
version: 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
resolution: "@blocksuite/editor@npm:0.0.0-20230607055421-9b20fcaf-nightly"
|
||||
@@ -2752,6 +2800,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/global@npm:0.0.0-20230613142146-d72d4600-nightly":
|
||||
version: 0.0.0-20230613142146-d72d4600-nightly
|
||||
resolution: "@blocksuite/global@npm:0.0.0-20230613142146-d72d4600-nightly"
|
||||
dependencies:
|
||||
ansi-colors: ^4.1.3
|
||||
zod: ^3.21.4
|
||||
peerDependencies:
|
||||
lit: ^2.7
|
||||
peerDependenciesMeta:
|
||||
lit:
|
||||
optional: true
|
||||
checksum: 45ccaa08c14787da8e3be8cbde9f01a34afea8874da6eaf7900961f953bbc0e7bab13866037d1504a1e983a8cc9e0735f5d9b1e074f6e400b27f7efae00292dd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/icons@npm:^2.1.19":
|
||||
version: 2.1.19
|
||||
resolution: "@blocksuite/icons@npm:2.1.19"
|
||||
@@ -2788,6 +2851,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/phasor@npm:0.0.0-20230613142146-d72d4600-nightly":
|
||||
version: 0.0.0-20230613142146-d72d4600-nightly
|
||||
resolution: "@blocksuite/phasor@npm:0.0.0-20230613142146-d72d4600-nightly"
|
||||
dependencies:
|
||||
"@blocksuite/global": 0.0.0-20230613142146-d72d4600-nightly
|
||||
fractional-indexing: ^3.2.0
|
||||
roughjs: ^4.5.2
|
||||
peerDependencies:
|
||||
nanoid: ^4
|
||||
yjs: ^13
|
||||
checksum: 24dbb806ab928c4b37dcf94ea14fbd1f23dfcf63c42e42b378ca78baa51ec713556fc4a6553c5bf505d59309cd29079ffa8abb575a7c4f04cbef2202714f07c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/store@npm:0.0.0-20230607055421-9b20fcaf-nightly":
|
||||
version: 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
resolution: "@blocksuite/store@npm:0.0.0-20230607055421-9b20fcaf-nightly"
|
||||
@@ -2812,6 +2889,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/store@npm:0.0.0-20230613142146-d72d4600-nightly":
|
||||
version: 0.0.0-20230613142146-d72d4600-nightly
|
||||
resolution: "@blocksuite/store@npm:0.0.0-20230613142146-d72d4600-nightly"
|
||||
dependencies:
|
||||
"@blocksuite/global": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@blocksuite/virgo": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@types/flexsearch": ^0.7.3
|
||||
buffer: ^6.0.3
|
||||
flexsearch: 0.7.21
|
||||
idb-keyval: ^6.2.0
|
||||
ky: ^0.33.3
|
||||
lib0: ^0.2.74
|
||||
merge: ^2.1.1
|
||||
minimatch: ^9.0.0
|
||||
nanoid: ^4.0.1
|
||||
y-protocols: ^1.0.5
|
||||
y-webrtc: ^10.2.5
|
||||
zod: ^3.21.4
|
||||
peerDependencies:
|
||||
yjs: ^13
|
||||
checksum: 1e6673b4c90ceb73176c552528d174126c036b445cc81c649e4e2fb694b87adbe12dcc3675117a088f5aadbe0410e380601914d05e26334879e12135c0843b17
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/virgo@npm:0.0.0-20230607055421-9b20fcaf-nightly":
|
||||
version: 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
resolution: "@blocksuite/virgo@npm:0.0.0-20230607055421-9b20fcaf-nightly"
|
||||
@@ -2825,6 +2926,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/virgo@npm:0.0.0-20230613142146-d72d4600-nightly":
|
||||
version: 0.0.0-20230613142146-d72d4600-nightly
|
||||
resolution: "@blocksuite/virgo@npm:0.0.0-20230613142146-d72d4600-nightly"
|
||||
dependencies:
|
||||
"@blocksuite/global": 0.0.0-20230613142146-d72d4600-nightly
|
||||
zod: ^3.21.4
|
||||
peerDependencies:
|
||||
lit: ^2.7
|
||||
yjs: ^13
|
||||
checksum: f3b5ee207b90d7f960eb10c37eb6866f06c0a2ad1e8e8bc46cc131ef2958f70a5b78d3dbafb7b19063b624acb898486a9b1f7fd7f83aa3f6253904b23f3eab47
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@clack/core@npm:^0.3.2":
|
||||
version: 0.3.2
|
||||
resolution: "@clack/core@npm:0.3.2"
|
||||
@@ -4504,6 +4618,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/core@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@floating-ui/core@npm:1.3.0"
|
||||
checksum: 51d8acc9fd720cb217cae7074f5f923bf2b6e0bb4f228e03077254d0f6e49f9753b34a14b9abb1f11671c3b61284c487ab27c4ba1843bcd4397e7d72dc7d4a59
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/dom@npm:^1.2.1":
|
||||
version: 1.2.9
|
||||
resolution: "@floating-ui/dom@npm:1.2.9"
|
||||
@@ -4513,6 +4634,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/dom@npm:^1.2.9":
|
||||
version: 1.3.0
|
||||
resolution: "@floating-ui/dom@npm:1.3.0"
|
||||
dependencies:
|
||||
"@floating-ui/core": ^1.3.0
|
||||
checksum: 39f92b3ce6de5d60a1cea951cbee22d0a9670fe4499b0128f0868a697d9288989994394d90cb99c3d1485aa432621e064d6b3c52914042a7d8d3c05897fb185d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/react-dom@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@floating-ui/react-dom@npm:1.3.0"
|
||||
@@ -9277,8 +9407,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@toeverything/y-indexeddb@workspace:packages/y-indexeddb"
|
||||
dependencies:
|
||||
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||
"@blocksuite/blocks": 0.0.0-20230613142146-d72d4600-nightly
|
||||
"@blocksuite/store": 0.0.0-20230613142146-d72d4600-nightly
|
||||
idb: ^7.1.1
|
||||
vite: ^4.3.9
|
||||
vite-plugin-dts: ^2.3.0
|
||||
@@ -9901,6 +10031,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/raf@npm:^3.4.0":
|
||||
version: 3.4.0
|
||||
resolution: "@types/raf@npm:3.4.0"
|
||||
checksum: d93e9b5244a081c64708b8918ef7e56936d6ef0144925b189e67d34127c0cb3a73fcf6866ab312db156554a66c26609dd056da5f7302f6658c049d6a37ed5f56
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/range-parser@npm:*":
|
||||
version: 1.2.4
|
||||
resolution: "@types/range-parser@npm:1.2.4"
|
||||
@@ -11515,6 +11652,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"atob@npm:^2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "atob@npm:2.1.2"
|
||||
bin:
|
||||
atob: bin/atob.js
|
||||
checksum: dfeeeb70090c5ebea7be4b9f787f866686c645d9f39a0d184c817252d0cf08455ed25267d79c03254d3be1f03ac399992a792edcd5ffb9c91e097ab5ef42833a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"author-regex@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "author-regex@npm:1.0.0"
|
||||
@@ -11840,6 +11986,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"base64-arraybuffer@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "base64-arraybuffer@npm:1.0.2"
|
||||
checksum: 15e6400d2d028bf18be4ed97702b11418f8f8779fb8c743251c863b726638d52f69571d4cc1843224da7838abef0949c670bde46936663c45ad078e89fee5c62
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "base64-js@npm:1.5.1"
|
||||
@@ -12080,6 +12233,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"btoa@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "btoa@npm:1.2.1"
|
||||
bin:
|
||||
btoa: bin/btoa.js
|
||||
checksum: afbf004fb1b1d530e053ffa66ef5bd3878b101c59d808ac947fcff96810b4452abba2b54be687adadea2ba9efc7af48b04228742789bf824ef93f103767e690c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-crc32@npm:~0.2.3":
|
||||
version: 0.2.13
|
||||
resolution: "buffer-crc32@npm:0.2.13"
|
||||
@@ -12348,6 +12510,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"canvg@npm:^3.0.6":
|
||||
version: 3.0.10
|
||||
resolution: "canvg@npm:3.0.10"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.12.5
|
||||
"@types/raf": ^3.4.0
|
||||
core-js: ^3.8.3
|
||||
raf: ^3.4.1
|
||||
regenerator-runtime: ^0.13.7
|
||||
rgbcolor: ^1.0.1
|
||||
stackblur-canvas: ^2.0.0
|
||||
svg-pathdata: ^6.0.3
|
||||
checksum: 2cfd86bcb9b56b43a97745cc672e696169b4c09e8850fb4f27bec5ebf173179d16feb594224d643a32f1ce01e47b55d44e0058419114d48d34f12c2452c65927
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"capital-case@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "capital-case@npm:1.0.4"
|
||||
@@ -12555,21 +12733,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cheerio@npm:^1.0.0-rc.12":
|
||||
version: 1.0.0-rc.12
|
||||
resolution: "cheerio@npm:1.0.0-rc.12"
|
||||
dependencies:
|
||||
cheerio-select: ^2.1.0
|
||||
dom-serializer: ^2.0.0
|
||||
domhandler: ^5.0.3
|
||||
domutils: ^3.0.1
|
||||
htmlparser2: ^8.0.1
|
||||
parse5: ^7.0.0
|
||||
parse5-htmlparser2-tree-adapter: ^7.0.0
|
||||
checksum: 5d4c1b7a53cf22d3a2eddc0aff70cf23cbb30d01a4c79013e703a012475c02461aa1fcd99127e8d83a02216386ed6942b2c8103845fd0812300dd199e6e7e054
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chokidar@npm:3.5.3, chokidar@npm:^3.4.2, chokidar@npm:^3.5.2, chokidar@npm:^3.5.3":
|
||||
version: 3.5.3
|
||||
resolution: "chokidar@npm:3.5.3"
|
||||
@@ -13291,6 +13454,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"core-js@npm:^3.6.0, core-js@npm:^3.8.3":
|
||||
version: 3.31.0
|
||||
resolution: "core-js@npm:3.31.0"
|
||||
checksum: f7cf9b3010f7ca99c026d95b61743baca1a85512742ed2b67e8f65a72ac4f4fe0b90b00057783e886bdd39d3a295f42f845d33e7cba3973ed263df978343ab79
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"core-util-is@npm:~1.0.0":
|
||||
version: 1.0.3
|
||||
resolution: "core-util-is@npm:1.0.3"
|
||||
@@ -13431,6 +13601,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-line-break@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "css-line-break@npm:2.1.0"
|
||||
dependencies:
|
||||
utrie: ^1.0.2
|
||||
checksum: 37b1fe632b03be7a287cd394cef8b5285666343443125c510df9cfb6a4734a2c71e154ec8f7bbff72d7c339e1e5872989b1c52d86162aed27d6cc114725bb4d0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-select@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "css-select@npm:5.1.0"
|
||||
@@ -14281,6 +14460,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dompurify@npm:^2.2.0":
|
||||
version: 2.4.5
|
||||
resolution: "dompurify@npm:2.4.5"
|
||||
checksum: d6d3c3b320f15cdb5b26aa1902c3275a3ab2c3705a9df4420bb94691d7c4df67959ec7b91e486c308320791b0ee000456f042734c45d76721e61c2768eac706e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domutils@npm:^1.5.1":
|
||||
version: 1.7.0
|
||||
resolution: "domutils@npm:1.7.0"
|
||||
@@ -15275,6 +15461,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-sonarjs@npm:^0.19.0":
|
||||
version: 0.19.0
|
||||
resolution: "eslint-plugin-sonarjs@npm:0.19.0"
|
||||
peerDependencies:
|
||||
eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
checksum: 893640583f62ce55584c6ddd481aa0fd6fa15fe0fffc32ac92b17f3fadde8eaf32414183bb80b612455212e9bb14400236398af6279ca04e8992f008e011926c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-unicorn@npm:^47.0.0":
|
||||
version: 47.0.0
|
||||
resolution: "eslint-plugin-unicorn@npm:47.0.0"
|
||||
@@ -15931,6 +16126,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fflate@npm:^0.4.8":
|
||||
version: 0.4.8
|
||||
resolution: "fflate@npm:0.4.8"
|
||||
checksum: 29d8cbe44d5e7f53e7f5a160ac7f9cc025480c7b3bfd85c5f898cbe20dfa2dad4732daa534982664bf30b35896a90af44ea33ede5d94c5ffd1b8b0c0a0a56ca2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fflate@npm:^0.7.4":
|
||||
version: 0.7.4
|
||||
resolution: "fflate@npm:0.7.4"
|
||||
@@ -17484,6 +17686,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html2canvas@npm:^1.0.0-rc.5":
|
||||
version: 1.4.1
|
||||
resolution: "html2canvas@npm:1.4.1"
|
||||
dependencies:
|
||||
css-line-break: ^2.1.0
|
||||
text-segmentation: ^1.0.3
|
||||
checksum: c134324af57f3262eecf982e436a4843fded3c6cf61954440ffd682527e4dd350e0c2fafd217c0b6f9a455fe345d0c67b4505689796ab160d4ca7c91c3766739
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"htmlparser2@npm:^3.9.2":
|
||||
version: 3.10.1
|
||||
resolution: "htmlparser2@npm:3.10.1"
|
||||
@@ -19711,6 +19923,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jspdf@npm:^2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "jspdf@npm:2.5.1"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.14.0
|
||||
atob: ^2.1.2
|
||||
btoa: ^1.2.1
|
||||
canvg: ^3.0.6
|
||||
core-js: ^3.6.0
|
||||
dompurify: ^2.2.0
|
||||
fflate: ^0.4.8
|
||||
html2canvas: ^1.0.0-rc.5
|
||||
dependenciesMeta:
|
||||
canvg:
|
||||
optional: true
|
||||
core-js:
|
||||
optional: true
|
||||
dompurify:
|
||||
optional: true
|
||||
html2canvas:
|
||||
optional: true
|
||||
checksum: 9ecdccc50678cd780f0995157618630ca0da65576835983232d48001aab0b29e51af765e078808526d5e5e2e1ebf3cee460e03eaf590f875d160f2e0cb614a1e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.3":
|
||||
version: 3.3.3
|
||||
resolution: "jsx-ast-utils@npm:3.3.3"
|
||||
@@ -22813,6 +23050,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"performance-now@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "performance-now@npm:2.1.0"
|
||||
checksum: 534e641aa8f7cba160f0afec0599b6cecefbb516a2e837b512be0adbe6c1da5550e89c78059c7fabc5c9ffdf6627edabe23eb7c518c4500067a898fa65c2b550
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"picocolors@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "picocolors@npm:1.0.0"
|
||||
@@ -23534,6 +23778,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"raf@npm:^3.4.1":
|
||||
version: 3.4.1
|
||||
resolution: "raf@npm:3.4.1"
|
||||
dependencies:
|
||||
performance-now: ^2.1.0
|
||||
checksum: 50ba284e481c8185dbcf45fc4618ba3aec580bb50c9121385d5698cb6012fe516d2015b1df6dd407a7b7c58d44be8086108236affbce1861edd6b44637c8cd52
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ramda@npm:0.29.0":
|
||||
version: 0.29.0
|
||||
resolution: "ramda@npm:0.29.0"
|
||||
@@ -24225,7 +24478,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.9":
|
||||
"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.7, regenerator-runtime@npm:^0.13.9":
|
||||
version: 0.13.11
|
||||
resolution: "regenerator-runtime@npm:0.13.11"
|
||||
checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4
|
||||
@@ -24644,6 +24897,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rgbcolor@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "rgbcolor@npm:1.0.1"
|
||||
checksum: bd062ac007a3e979e2f83dc69feb3cc4f9bca7d8631899548394160e30c47e4f7e52b31aa3f66a69061ad56e899e812ec52f5c33686c085d72c9b3d22faed1c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rimraf@npm:^2.6.1":
|
||||
version: 2.7.1
|
||||
resolution: "rimraf@npm:2.7.1"
|
||||
@@ -25549,6 +25809,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stackblur-canvas@npm:^2.0.0":
|
||||
version: 2.6.0
|
||||
resolution: "stackblur-canvas@npm:2.6.0"
|
||||
checksum: 4356b3362773ff9511a8cea715ceda94e45c4e8c34276ddc7e71f2817467b09f66d6bcb299340a661d8ddc053da682aa4d93080ea97492514028762c2ab88e8d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stacktrace-parser@npm:^0.1.10":
|
||||
version: 0.1.10
|
||||
resolution: "stacktrace-parser@npm:0.1.10"
|
||||
@@ -26082,6 +26349,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"svg-pathdata@npm:^6.0.3":
|
||||
version: 6.0.3
|
||||
resolution: "svg-pathdata@npm:6.0.3"
|
||||
checksum: f0e55be50c654be5d259d70945ed7e5354bf78e51c6039b4045d9f7c49d703a0c33dda36751815aec2824d046c417c35226e7491246ffff3e9164735ea428446
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"swap-case@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "swap-case@npm:2.0.2"
|
||||
@@ -26311,6 +26585,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"text-segmentation@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "text-segmentation@npm:1.0.3"
|
||||
dependencies:
|
||||
utrie: ^1.0.2
|
||||
checksum: 2e24632d59567c55ab49ac324815e2f7a8043e63e26b109636322ac3e30692cee8679a448fd5d0f0598a345f407afd0e34ba612e22524cf576d382d84058c013
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"text-table@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "text-table@npm:0.2.0"
|
||||
@@ -27364,6 +27647,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"utrie@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "utrie@npm:1.0.2"
|
||||
dependencies:
|
||||
base64-arraybuffer: ^1.0.2
|
||||
checksum: c96fbb7d4d8855a154327da0b18e39b7511cc70a7e4bcc3658e24f424bb884312d72b5ba777500b8858e34d365dc6b1a921dc5ca2f0d341182519c6b78e280a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uuid@npm:9.0.0, uuid@npm:^9.0.0":
|
||||
version: 9.0.0
|
||||
resolution: "uuid@npm:9.0.0"
|
||||
|
||||
Reference in New Issue
Block a user