Compare commits

...

13 Commits

Author SHA1 Message Date
himself65
557a7c3360 v0.7.0-canary.15 2023-06-15 00:44:08 +08:00
Himself65
44580f6af0 fix(electron): bookmark plugin wound not work (#2776) 2023-06-15 00:43:28 +08:00
Himself65
5d75ceeeb5 feat: support sub-doc feature (#2774) 2023-06-14 23:22:35 +08:00
himself65
8d5330df74 v0.7.0-canary.14 2023-06-14 18:41:11 +08:00
Himself65
761965240d fix: build layer (#2769) 2023-06-14 18:40:13 +08:00
danielchim
ad32ed5dd5 feat: image-preview (#2720)
Co-authored-by: Himself65 <himself65@outlook.com>
2023-06-14 04:20:29 +00:00
LongYinan
6a4f70cf43 fix(electron): install missing dependencies (#2765) 2023-06-14 11:11:45 +08:00
LongYinan
3996955e3b fix: add eslint-plugin-sonarjs and rules (#2767) 2023-06-14 10:45:14 +08:00
LongYinan
1c8f1a05d0 fix: add @typescript-eslint/no-floating-promises rule (#2764)
Co-authored-by: himself65 <himself65@outlook.com>
2023-06-13 06:55:23 +00:00
Flrande
bbac03107e fix: preloading gif order (#2760)
Co-authored-by: Himself65 <himself65@outlook.com>
2023-06-13 14:46:52 +08:00
himself65
32f064c2de v0.7.0-canary.13 2023-06-13 14:13:38 +08:00
himself65
39704bc812 build: fix generate-assets.mjs 2023-06-13 14:12:08 +08:00
Himself65
a421265483 fix: remove unused hooks (#2762) 2023-06-13 12:18:32 +08:00
67 changed files with 1812 additions and 623 deletions

View File

@@ -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),
},
],
},
})),
],
};

View File

@@ -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;

View File

@@ -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",

View File

@@ -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
View 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'));
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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)

View File

@@ -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);
});
}

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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,

View File

@@ -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 () => {

View File

@@ -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 {

View File

@@ -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>
);
}

View File

@@ -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]
);

View File

@@ -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>

View File

@@ -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();
};
},

View File

@@ -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>

View File

@@ -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]);

View File

@@ -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}

View File

@@ -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,

View File

@@ -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}>

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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",

View File

@@ -15,5 +15,5 @@
"dependencies": {
"dotenv": "^16.1.4"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -66,5 +66,5 @@
"vite": "^4.3.9",
"yjs": "^13.6.1"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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);
});
},
});
});

View File

@@ -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

View File

@@ -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,
};
};

View File

@@ -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',
});

View File

@@ -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>
);
};

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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;

View File

@@ -8,5 +8,5 @@
"devDependencies": {
"@types/debug": "^4.1.8"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -28,5 +28,5 @@
"dependencies": {
"lit": "^2.7.5"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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 {

View File

@@ -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",

View File

@@ -8,5 +8,5 @@
"@affine/env": "workspace:*",
"@toeverything/y-indexeddb": "workspace:*"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -21,5 +21,5 @@
"@blocksuite/store": "*",
"lottie-web": "*"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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) {

View File

@@ -47,5 +47,5 @@
"@blocksuite/lit": "*",
"@blocksuite/store": "*"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -6,5 +6,5 @@
"./*.md": "./*.md",
"./preloading.json": "./preloading.json"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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"
}
}
}

View File

@@ -40,5 +40,5 @@
"next": "=13.4.2",
"ws": "^8.13.0"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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"

View File

@@ -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)
);

View File

@@ -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) {

View File

@@ -9,9 +9,6 @@
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "../env"
}
]
}

View File

@@ -21,5 +21,5 @@
"react": "*",
"react-dom": "*"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -28,5 +28,5 @@
"react": "*",
"react-dom": "*"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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 = () => {

View File

@@ -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);

View File

@@ -3,5 +3,5 @@
"exports": {
"./*": "./*"
},
"version": "0.7.0-canary.12"
"version": "0.7.0-canary.15"
}

View File

@@ -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
View File

@@ -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"