diff --git a/.eslintignore b/.eslintignore
index 42871b1461..42a9f2783a 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -6,3 +6,4 @@ storybook-static
affine-out
_next
lib
+.eslintrc.js
diff --git a/.eslintrc.js b/.eslintrc.js
index c09b044b12..4e46209668 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,3 +1,5 @@
+const { readdirSync, statSync } = require('fs');
+
const createPattern = packageName => [
{
group: ['**/dist', '**/dist/**'],
@@ -21,22 +23,14 @@ const createPattern = packageName => [
},
];
-const allPackages = [
- 'cli',
- 'component',
- 'debug',
- 'env',
- 'graphql',
- 'hooks',
- 'i18n',
- 'jotai',
- 'native',
- 'plugin-infra',
- 'templates',
- 'theme',
- 'workspace',
- 'y-indexeddb',
-];
+const pkgs = readdirSync('./packages').filter(pkg => {
+ return statSync(`./packages/${pkg}`).isDirectory();
+});
+const apps = readdirSync('./apps').filter(pkg => {
+ return statSync(`./apps/${pkg}`).isDirectory();
+});
+
+const allPackages = pkgs.concat(apps);
/**
* @type {import('eslint').Linter.Config}
@@ -67,6 +61,7 @@ const config = {
},
ecmaVersion: 'latest',
sourceType: 'module',
+ project: './tsconfig.eslint.json',
},
plugins: [
'react',
@@ -83,7 +78,7 @@ const config = {
'no-cond-assign': 'off',
'react/prop-types': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
- '@typescript-eslint/no-non-null-assertion': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unused-vars': [
@@ -136,6 +131,12 @@ const config = {
'@typescript-eslint/no-var-requires': 0,
},
},
+ {
+ files: ['**/__tests__/**/*', '**/*.stories.tsx'],
+ rules: {
+ '@typescript-eslint/no-non-null-assertion': 0,
+ },
+ },
...allPackages.map(pkg => ({
files: [`packages/${pkg}/src/**/*.ts`, `packages/${pkg}/src/**/*.tsx`],
rules: {
diff --git a/apps/electron/layers/main/src/db/base-db-adapter.ts b/apps/electron/layers/main/src/db/base-db-adapter.ts
index 485214a602..60a6693621 100644
--- a/apps/electron/layers/main/src/db/base-db-adapter.ts
+++ b/apps/electron/layers/main/src/db/base-db-adapter.ts
@@ -10,9 +10,7 @@ export abstract class BaseSQLiteAdapter {
db: SqliteConnection | null = null;
abstract role: string;
- constructor(public readonly path: string) {
- logger.info(`[SQLiteAdapter]`, 'path:', path);
- }
+ constructor(public readonly path: string) {}
async connectIfNeeded() {
if (!this.db) {
diff --git a/apps/electron/layers/main/src/db/ensure-db.ts b/apps/electron/layers/main/src/db/ensure-db.ts
index eaa5f17af3..36a9e2851a 100644
--- a/apps/electron/layers/main/src/db/ensure-db.ts
+++ b/apps/electron/layers/main/src/db/ensure-db.ts
@@ -100,6 +100,7 @@ function getWorkspaceDB$(id: string) {
)
);
}
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return db$Map.get(id)!;
}
diff --git a/apps/electron/layers/main/src/db/secondary-db.ts b/apps/electron/layers/main/src/db/secondary-db.ts
index 35b487337e..66a0ee746d 100644
--- a/apps/electron/layers/main/src/db/secondary-db.ts
+++ b/apps/electron/layers/main/src/db/secondary-db.ts
@@ -107,7 +107,6 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
return;
}
this.firstConnected = true;
- const { db } = this;
const onUpstreamUpdate = (update: Uint8Array, origin: YOrigin) => {
if (origin === 'renderer') {
@@ -118,8 +117,8 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
const onSelfUpdate = (update: Uint8Array, origin: YOrigin) => {
// for self update from upstream, we need to push it to external DB
- if (origin === 'upstream') {
- this.addUpdateToUpdateQueue(db!, update);
+ if (origin === 'upstream' && this.db) {
+ this.addUpdateToUpdateQueue(this.db, update);
}
if (origin === 'self') {
diff --git a/apps/electron/layers/main/src/updater/electron-updater.ts b/apps/electron/layers/main/src/updater/electron-updater.ts
index f1e50f97d4..8f0e864621 100644
--- a/apps/electron/layers/main/src/updater/electron-updater.ts
+++ b/apps/electron/layers/main/src/updater/electron-updater.ts
@@ -67,7 +67,7 @@ export const registerUpdater = async () => {
// register events for checkForUpdatesAndNotify
_autoUpdater.on('update-available', info => {
if (allowAutoUpdate) {
- _autoUpdater!.downloadUpdate();
+ _autoUpdater?.downloadUpdate();
logger.info('Update available, downloading...', info);
}
updaterSubjects.updateAvailable.next({
diff --git a/apps/server/src/utils/nestjs.ts b/apps/server/src/utils/nestjs.ts
index f1f18beefe..27129a2dee 100644
--- a/apps/server/src/utils/nestjs.ts
+++ b/apps/server/src/utils/nestjs.ts
@@ -14,7 +14,7 @@ export function getRequestResponseFromContext(context: ExecutionContext) {
}>();
return {
req: gqlContext.req,
- res: gqlContext.req.res!,
+ res: gqlContext.req.res,
};
}
case 'http': {
@@ -37,7 +37,7 @@ export function getRequestResponseFromHost(host: ArgumentsHost) {
}>();
return {
req: gqlContext.req,
- res: gqlContext.req.res!,
+ res: gqlContext.req.res,
};
}
case 'http': {
diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.tsx b/packages/component/src/components/app-sidebar/app-updater-button/index.tsx
index ba343afbb4..b58e51ea50 100644
--- a/packages/component/src/components/app-sidebar/app-updater-button/index.tsx
+++ b/packages/component/src/components/app-sidebar/app-updater-button/index.tsx
@@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
import clsx from 'clsx';
import { atom, useAtomValue, useSetAtom } from 'jotai';
-import { startTransition } from 'react';
+import { startTransition, useCallback } from 'react';
import * as styles from './index.css';
import {
@@ -50,53 +50,67 @@ export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
const downloadProgress = useAtomValue(downloadProgressAtom);
const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom);
- const onDismissCurrentChangelog = () => {
+ const onDismissCurrentChangelog = useCallback(() => {
+ if (!currentVersion) {
+ return;
+ }
startTransition(() =>
setChangelogCheckAtom(mapping => {
return {
...mapping,
- [currentVersion!]: true,
+ [currentVersion]: true,
};
})
);
- };
+ }, [currentVersion, setChangelogCheckAtom]);
+ const onClickUpdate = useCallback(() => {
+ if (updateReady) {
+ window.apis?.updater.quitAndInstall();
+ } else if (updateAvailable) {
+ if (updateAvailable.allowAutoUpdate) {
+ // wait for download to finish
+ } else {
+ window.open(
+ `https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`,
+ '_blank'
+ );
+ }
+ } else if (currentChangelogUnread) {
+ window.open(config.changelogUrl, '_blank');
+ onDismissCurrentChangelog();
+ } else {
+ throw new Unreachable();
+ }
+ }, [
+ currentChangelogUnread,
+ currentVersion,
+ onDismissCurrentChangelog,
+ updateAvailable,
+ updateReady,
+ ]);
if (!updateAvailable && !currentChangelogUnread) {
return null;
}
+ const updateAvailableNode = updateAvailable
+ ? updateAvailable.allowAutoUpdate
+ ? renderUpdateAvailableAllowAutoUpdate()
+ : renderUpdateAvailableNotAllowAutoUpdate()
+ : null;
+ const whatsNew =
+ !updateAvailable && currentChangelogUnread ? renderWhatsNew() : null;
+
return (
diff --git a/packages/component/src/components/page-list/operation-menu-items/export.tsx b/packages/component/src/components/page-list/operation-menu-items/export.tsx
index b827cf16b6..42606ea9ea 100644
--- a/packages/component/src/components/page-list/operation-menu-items/export.tsx
+++ b/packages/component/src/components/page-list/operation-menu-items/export.tsx
@@ -8,7 +8,7 @@ import {
ExportToMarkdownIcon,
ExportToPdfIcon,
} from '@blocksuite/icons';
-import { useRef } from 'react';
+import { useCallback, useRef } from 'react';
import { Menu, MenuItem } from '../../..';
import type { CommonMenuItemProps } from './types';
@@ -18,35 +18,39 @@ const ExportToPdfMenuItem = ({
}: CommonMenuItemProps<{ type: 'pdf' }>) => {
const t = useAFFiNEI18N();
const contentParserRef = useRef();
- return (
- <>
- {globalThis.currentEditor!.mode === 'page' && (
-
- )}
- >
- );
+ const { currentEditor } = globalThis;
+ const onClickDownloadPDF = useCallback(() => {
+ if (!currentEditor) {
+ return;
+ }
+ const contentParser =
+ contentParserRef.current ??
+ (contentParserRef.current = new ContentParser(currentEditor.page));
+
+ window.apis?.export
+ .savePDFFileAs(
+ (currentEditor.page.root as PageBlockModel).title.toString()
+ )
+ .then(result => {
+ if (result !== undefined) {
+ return;
+ }
+ contentParser.exportPdf();
+ onSelect?.({ type: 'pdf' });
+ });
+ }, [currentEditor, onSelect]);
+ if (currentEditor && currentEditor.mode === 'page') {
+ return (
+ }
+ >
+ {t['Export to PDF']()}
+
+ );
+ }
+ return null;
};
const ExportToHtmlMenuItem = ({
@@ -54,19 +58,22 @@ const ExportToHtmlMenuItem = ({
}: CommonMenuItemProps<{ type: 'html' }>) => {
const t = useAFFiNEI18N();
const contentParserRef = useRef();
+ const { currentEditor } = globalThis;
+ const onClickExportHtml = useCallback(() => {
+ if (!currentEditor) {
+ return;
+ }
+ if (!contentParserRef.current) {
+ contentParserRef.current = new ContentParser(currentEditor.page);
+ }
+ contentParserRef.current.exportHtml();
+ onSelect?.({ type: 'html' });
+ }, [onSelect, currentEditor]);
return (
<>