From aaaed5ac14709e46876ac43fa4ec0c341c717624 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 28 Nov 2023 06:59:48 +0000 Subject: [PATCH 01/35] fix(core): nx cache configuration (#5065) --- .github/workflows/deploy.yml | 6 ++---- packages/frontend/core/project.json | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ce0874e734..e12cf1629f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,7 +9,6 @@ on: default: canary env: - BUILD_TYPE: canary APP_NAME: affine NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} @@ -17,7 +16,6 @@ jobs: build-server: name: Build Server runs-on: ubuntu-latest - environment: ${{ github.event.inputs.flavor }} steps: - uses: actions/checkout@v4 - name: Setup Node.js @@ -48,7 +46,7 @@ jobs: R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - BUILD_TYPE_OVERRIDE: ${{ github.event.inputs.flavor }} + BUILD_TYPE: ${{ github.event.inputs.flavor }} SHOULD_REPORT_TRACE: true TRACE_REPORT_ENDPOINT: ${{ secrets.TRACE_REPORT_ENDPOINT }} CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }} @@ -205,7 +203,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Deploy to dev + - name: Deploy to ${{ github.event.inputs.flavor }} uses: ./.github/actions/deploy with: build-type: ${{ github.event.inputs.flavor }} diff --git a/packages/frontend/core/project.json b/packages/frontend/core/project.json index ee354d7c39..ae85c4b3a4 100644 --- a/packages/frontend/core/project.json +++ b/packages/frontend/core/project.json @@ -25,6 +25,9 @@ { "env": "BUILD_TYPE" }, + { + "env": "BUILD_TYPE_OVERRIDE" + }, { "env": "PERFSEE_TOKEN" }, @@ -48,6 +51,24 @@ }, { "env": "DISABLE_DEV_OVERLAY" + }, + { + "env": "CAPTCHA_SITE_KEY" + }, + { + "env": "SHOULD_REPORT_TRACE" + }, + { + "env": "TRACE_REPORT_ENDPOINT" + }, + { + "env": "R2_ACCOUNT_ID" + }, + { + "env": "R2_ACCESS_KEY_ID" + }, + { + "env": "R2_SECRET_ACCESS_KEY" } ], "options": { From 0ee2ecc1e941852934c8ffdba67c9424c67ee7aa Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 28 Nov 2023 06:59:50 +0000 Subject: [PATCH 02/35] ci: always perform fresh build in deployment job (#5066) --- .github/workflows/deploy.yml | 2 +- .github/workflows/nightly-build.yml | 5 +++-- .github/workflows/release-desktop-app.yml | 1 + packages/frontend/electron/scripts/generate-assets.ts | 5 ++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e12cf1629f..f956b50864 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,7 +41,7 @@ jobs: - name: Build Plugins run: yarn run build:plugins - name: Build Core - run: yarn nx build @affine/core + run: yarn nx build @affine/core --skip-nx-cache env: R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 6a82abe5bf..84dce785b9 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -65,14 +65,15 @@ jobs: - name: Replace Version run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }} - name: generate-assets - working-directory: packages/frontend/electron - run: yarn generate-assets + run: yarn workspace @affine/electron generate-assets env: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }} + SKIP_PLUGIN_BUILD: 'true' + SKIP_NX_CACHE: 'true' - name: Upload core artifact uses: actions/upload-artifact@v3 diff --git a/.github/workflows/release-desktop-app.yml b/.github/workflows/release-desktop-app.yml index 33abe80447..08723c41fa 100644 --- a/.github/workflows/release-desktop-app.yml +++ b/.github/workflows/release-desktop-app.yml @@ -67,6 +67,7 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} RELEASE_VERSION: ${{ github.event.inputs.version || steps.get-canary-version.outputs.RELEASE_VERSION }} SKIP_PLUGIN_BUILD: 'true' + SKIP_NX_CACHE: 'true' - name: Upload core artifact uses: actions/upload-artifact@v3 diff --git a/packages/frontend/electron/scripts/generate-assets.ts b/packages/frontend/electron/scripts/generate-assets.ts index 20c9d38f5c..0284274d21 100755 --- a/packages/frontend/electron/scripts/generate-assets.ts +++ b/packages/frontend/electron/scripts/generate-assets.ts @@ -39,6 +39,9 @@ process.env.DISTRIBUTION = 'desktop'; const cwd = repoRootDir; +const { SKIP_NX_CACHE } = process.env; +const nxFlag = SKIP_NX_CACHE ? '--skip-nx-cache' : ''; + if (!process.env.SKIP_PLUGIN_BUILD) { spawnSync('yarn', ['build:plugins'], { stdio: 'inherit', @@ -49,7 +52,7 @@ if (!process.env.SKIP_PLUGIN_BUILD) { // step 1: build web dist if (!process.env.SKIP_WEB_BUILD) { - spawnSync('yarn', ['nx', 'build', '@affine/core'], { + spawnSync('yarn', ['nx', 'build', '@affine/core', nxFlag], { stdio: 'inherit', env: process.env, cwd, From 890e3e4435f92874265a697325dda3c9b339425a Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Tue, 28 Nov 2023 08:01:18 +0000 Subject: [PATCH 03/35] fix(core): should not refetch avatar when url does not change (#5070) blob resources are in fact immutable. We do not need to refetch it by swr policies (mount/visibilitychange/etc) --- .../hooks/src/use-block-suite-workspace-avatar-url.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/frontend/hooks/src/use-block-suite-workspace-avatar-url.ts b/packages/frontend/hooks/src/use-block-suite-workspace-avatar-url.ts index efbff4e7b4..6da97d7ec5 100644 --- a/packages/frontend/hooks/src/use-block-suite-workspace-avatar-url.ts +++ b/packages/frontend/hooks/src/use-block-suite-workspace-avatar-url.ts @@ -2,7 +2,7 @@ import { assertExists } from '@blocksuite/global/utils'; import type { Workspace } from '@blocksuite/store'; import reduce from 'image-blob-reduce'; import { useCallback, useEffect, useState } from 'react'; -import useSWR from 'swr'; +import useSWRImmutable from 'swr/immutable'; // validate and reduce image size and return as file export const validateAndReduceImage = async (file: File): Promise => { @@ -52,7 +52,7 @@ export function useBlockSuiteWorkspaceAvatarUrl( if (url !== blockSuiteWorkspace.meta.avatar) { set(blockSuiteWorkspace.meta.avatar); } - const { data: avatar, mutate } = useSWR(url, { + const { data: avatar, mutate } = useSWRImmutable(url, { fetcher: async avatar => { assertExists(blockSuiteWorkspace); const blobs = blockSuiteWorkspace.blob; @@ -62,8 +62,7 @@ export function useBlockSuiteWorkspaceAvatarUrl( } return null; }, - suspense: true, - fallbackData: null, + suspense: false, }); const setAvatar = useCallback( From c1cf25d2d548c355eabd471458188807e951ad10 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 28 Nov 2023 08:55:50 +0000 Subject: [PATCH 04/35] style: add no-self-compare rule (#5092) --- .eslintrc.js | 1 + .../workspace/src/providers/awareness/affine/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 31e36813f7..73b7e97c36 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -126,6 +126,7 @@ const config = { 'no-cond-assign': 'off', 'no-constant-binary-expression': 'error', 'no-constructor-return': 'error', + 'no-self-compare': 'error', 'react/prop-types': 'off', 'react/jsx-no-useless-fragment': 'error', '@typescript-eslint/consistent-type-imports': 'error', diff --git a/packages/frontend/workspace/src/providers/awareness/affine/index.ts b/packages/frontend/workspace/src/providers/awareness/affine/index.ts index 41d652e983..d6d0b9d393 100644 --- a/packages/frontend/workspace/src/providers/awareness/affine/index.ts +++ b/packages/frontend/workspace/src/providers/awareness/affine/index.ts @@ -24,13 +24,13 @@ export function createAffineAwarenessProvider( const socket = getIoManager().socket('/'); const awarenessBroadcast = ({ - workspaceId, + workspaceId: wsId, awarenessUpdate, }: { workspaceId: string; awarenessUpdate: string; }) => { - if (workspaceId !== workspaceId) { + if (wsId !== workspaceId) { return; } applyAwarenessUpdate( From 469a18f794f8d8371e712fb187c2cc2378c14714 Mon Sep 17 00:00:00 2001 From: JimmFly <447268514@qq.com> Date: Wed, 29 Nov 2023 02:12:13 +0000 Subject: [PATCH 05/35] feat(component): default collapse and float sidebar on mobile (#5077) work for #4843 https://github.com/toeverything/AFFiNE/assets/102217452/c658dbab-4db8-4065-a3a6-3e20274b6cb9 --- .../component/src/components/app-sidebar/index.jotai.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/frontend/component/src/components/app-sidebar/index.jotai.ts b/packages/frontend/component/src/components/app-sidebar/index.jotai.ts index 696e8620f4..f645cc33f4 100644 --- a/packages/frontend/component/src/components/app-sidebar/index.jotai.ts +++ b/packages/frontend/component/src/components/app-sidebar/index.jotai.ts @@ -2,8 +2,10 @@ import { atom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; export const APP_SIDEBAR_OPEN = 'app-sidebar-open'; -export const appSidebarOpenAtom = atomWithStorage(APP_SIDEBAR_OPEN, true); -export const appSidebarFloatingAtom = atom(false); +export const isMobile = window.innerWidth < 768; + +export const appSidebarOpenAtom = atomWithStorage(APP_SIDEBAR_OPEN, !isMobile); +export const appSidebarFloatingAtom = atom(isMobile); export const appSidebarResizingAtom = atom(false); export const appSidebarWidthAtom = atomWithStorage( From c78eb965079a66185533a71115b42afb5b549d36 Mon Sep 17 00:00:00 2001 From: JimmFly <447268514@qq.com> Date: Wed, 29 Nov 2023 02:35:12 +0000 Subject: [PATCH 06/35] feat(core): show searched result with results group and add duplicate commands (#5073) [TOV-65](https://linear.app/affine-design/issue/TOV-65/should-show-searched-result-without-categories) https://github.com/toeverything/AFFiNE/assets/102217452/50fba70b-7efa-4e47-ba8a-de21e400166c --- .../operation-menu.tsx | 48 +++--------------- .../block-suite-page-list/utils.tsx | 6 +++ .../core/src/components/pure/cmdk/main.css.ts | 16 +++--- .../core/src/components/pure/cmdk/main.tsx | 50 ++++++++++++++----- .../affine/use-block-suite-meta-helper.ts | 44 +++++++++++++++- ...se-register-blocksuite-editor-commands.tsx | 16 +++++- packages/frontend/i18n/src/resources/en.json | 1 + 7 files changed, 119 insertions(+), 62 deletions(-) diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx index 5d19c820da..2fa9a63ad9 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx @@ -20,23 +20,15 @@ import { MenuItem, MenuSeparator, } from '@toeverything/components/menu'; -import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; -import { - useBlockSuitePageMeta, - usePageMetaHelper, -} from '@toeverything/hooks/use-block-suite-page-meta'; -import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper'; -import { useAtomValue, useSetAtom } from 'jotai'; +import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta'; +import { useAtomValue } from 'jotai'; import { useCallback, useRef, useState } from 'react'; -import { applyUpdate, encodeStateAsUpdate } from 'yjs'; -import { setPageModeAtom } from '../../../atoms'; import { currentModeAtom } from '../../../atoms/mode'; import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper'; import { useExportPage } from '../../../hooks/affine/use-export-page'; import { useTrashModalHelper } from '../../../hooks/affine/use-trash-modal-helper'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; -import { useNavigateHelper } from '../../../hooks/use-navigate-helper'; import { toast } from '../../../utils'; import { PageHistoryModal } from '../../affine/page-history-modal/history-modal'; import { HeaderDropDownButton } from '../../pure/header-drop-down-button'; @@ -50,7 +42,6 @@ type PageMenuProps = { export const PageMenu = ({ rename, pageId }: PageMenuProps) => { const t = useAFFiNEI18N(); const ref = useRef(null); - const { openPage } = useNavigateHelper(); // fixme(himself65): remove these hooks ASAP const [workspace] = useCurrentWorkspace(); @@ -64,11 +55,9 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => { const currentMode = useAtomValue(currentModeAtom); const favorite = pageMeta.favorite ?? false; - const { setPageMeta, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace); - const { togglePageMode, toggleFavorite } = + const { togglePageMode, toggleFavorite, duplicate } = useBlockSuiteMetaHelper(blockSuiteWorkspace); const { importFile } = usePageHelper(blockSuiteWorkspace); - const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace); const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace); const [historyModalOpen, setHistoryModalOpen] = useState(false); @@ -107,34 +96,11 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => { }; const exportHandler = useExportPage(currentPage); - const setPageMode = useSetAtom(setPageModeAtom); - const duplicate = useAsyncCallback(async () => { - const currentPageMeta = currentPage.meta; - const newPage = createPage(); - await newPage.waitForLoaded(); + const handleDuplicate = useCallback(() => { + duplicate(pageId); + }, [duplicate, pageId]); - const update = encodeStateAsUpdate(currentPage.spaceDoc); - applyUpdate(newPage.spaceDoc, update); - - setPageMeta(newPage.id, { - tags: currentPageMeta.tags, - favorite: currentPageMeta.favorite, - }); - setPageMode(newPage.id, currentMode); - setPageTitle(newPage.id, `${currentPageMeta.title}(1)`); - openPage(blockSuiteWorkspace.id, newPage.id); - }, [ - blockSuiteWorkspace.id, - createPage, - currentMode, - currentPage.meta, - currentPage.spaceDoc, - openPage, - setPageMeta, - setPageMode, - setPageTitle, - ]); const EditMenu = ( <> { } data-testid="editor-option-menu-duplicate" - onSelect={duplicate} + onSelect={handleDuplicate} style={menuItemStyle} > {t['com.affine.header.option.duplicate']()} diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx index ea0287e157..d0f3f7b467 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx @@ -14,11 +14,14 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => { const { openPage, jumpToSubPath } = useNavigateHelper(); const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace); const pageSettings = useAtomValue(pageSettingsAtom); + const isPreferredEdgeless = useCallback( (pageId: string) => pageSettings[pageId]?.mode === 'edgeless', [pageSettings] ); + const setPageMode = useSetAtom(setPageModeAtom); + const createPageAndOpen = useCallback( (mode?: 'page' | 'edgeless') => { const page = createPage(); @@ -31,9 +34,11 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => { }, [blockSuiteWorkspace.id, createPage, openPage, setPageMode] ); + const createEdgelessAndOpen = useCallback(() => { return createPageAndOpen('edgeless'); }, [createPageAndOpen]); + const importFileAndOpen = useAsyncCallback(async () => { const { showImportModal } = await import('@blocksuite/blocks'); const onSuccess = (pageIds: string[], isWorkspaceFile: boolean) => { @@ -55,6 +60,7 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => { }; showImportModal({ workspace: blockSuiteWorkspace, onSuccess }); }, [blockSuiteWorkspace, openPage, jumpToSubPath]); + return useMemo(() => { return { createPage: createPageAndOpen, diff --git a/packages/frontend/core/src/components/pure/cmdk/main.css.ts b/packages/frontend/core/src/components/pure/cmdk/main.css.ts index fc23684cb1..a0d9264b37 100644 --- a/packages/frontend/core/src/components/pure/cmdk/main.css.ts +++ b/packages/frontend/core/src/components/pure/cmdk/main.css.ts @@ -12,6 +12,7 @@ export const searchInput = style({ color: 'var(--affine-text-primary-color)', fontSize: 'var(--affine-font-h-5)', padding: '21px 24px', + marginBottom: '8px', width: '100%', borderBottom: '1px solid var(--affine-border-color)', flexShrink: 0, @@ -114,13 +115,6 @@ globalStyle(`${root} [cmdk-group][hidden]`, { display: 'none', }); -globalStyle( - `${root} [cmdk-group]:not([hidden]):first-of-type [cmdk-group-heading]`, - { - paddingTop: 16, - } -); - globalStyle(`${root} [cmdk-list]`, { maxHeight: 400, minHeight: 120, @@ -187,3 +181,11 @@ globalStyle( color: 'var(--affine-error-color)', } ); + +export const resultGroupHeader = style({ + padding: '8px', + color: 'var(--affine-text-secondary-color)', + fontSize: 'var(--affine-font-xs)', + fontWeight: 600, + lineHeight: '1.67', +}); diff --git a/packages/frontend/core/src/components/pure/cmdk/main.tsx b/packages/frontend/core/src/components/pure/cmdk/main.tsx index 4252265658..918b78e539 100644 --- a/packages/frontend/core/src/components/pure/cmdk/main.tsx +++ b/packages/frontend/core/src/components/pure/cmdk/main.tsx @@ -1,11 +1,12 @@ import { Command } from '@affine/cmdk'; +import { useCommandState } from '@affine/cmdk'; import { formatDate } from '@affine/component/page-list'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import type { PageMeta } from '@blocksuite/store'; import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import type { CommandCategory } from '@toeverything/infra/command'; import clsx from 'clsx'; -import { useAtom } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import { Suspense, useLayoutEffect, useMemo, useState } from 'react'; import { @@ -71,7 +72,7 @@ const QuickSearchGroup = ({ ); return ( - + {commands.map(command => { const label = typeof command.label === 'string' @@ -129,18 +130,43 @@ const QuickSearchCommands = ({ }: { onOpenChange?: (open: boolean) => void; }) => { + const t = useAFFiNEI18N(); const groups = useCMDKCommandGroups(); - return groups.map(([category, commands]) => { - return ( - - ); - }); + const query = useAtomValue(cmdkQueryAtom); + const resultCount = useCommandState(state => state.filtered.count); + const resultGroupHeader = useMemo(() => { + if (query) { + return ( +
+ { + // hack: use resultCount to determine if it is creation or results + // because the creation(as 2 results) is always shown at the top when there is no result + resultCount === 2 + ? t['com.affine.cmdk.affine.category.affine.creation']() + : t['com.affine.cmdk.affine.category.results']() + } +
+ ); + } + return null; + }, [query, resultCount, t]); + + return ( + <> + {resultGroupHeader} + {groups.map(([category, commands]) => { + return ( + + ); + })} + + ); }; export const CMDKContainer = ({ diff --git a/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts b/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts index 53dd028296..2380b239fb 100644 --- a/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts +++ b/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts @@ -1,25 +1,31 @@ +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useBlockSuitePageMeta, usePageMetaHelper, } from '@toeverything/hooks/use-block-suite-page-meta'; +import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper'; import { useAtomValue, useSetAtom } from 'jotai'; import { useCallback } from 'react'; +import { applyUpdate, encodeStateAsUpdate } from 'yjs'; import { setPageModeAtom } from '../../atoms'; import { currentModeAtom } from '../../atoms/mode'; import type { BlockSuiteWorkspace } from '../../shared'; import { getWorkspaceSetting } from '../../utils/workspace-setting'; +import { useNavigateHelper } from '../use-navigate-helper'; import { useReferenceLinkHelper } from './use-reference-link-helper'; export function useBlockSuiteMetaHelper( blockSuiteWorkspace: BlockSuiteWorkspace ) { - const { setPageMeta, getPageMeta, setPageReadonly } = + const { setPageMeta, getPageMeta, setPageReadonly, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace); const { addReferenceLink } = useReferenceLinkHelper(blockSuiteWorkspace); const metas = useBlockSuitePageMeta(blockSuiteWorkspace); const setPageMode = useSetAtom(setPageModeAtom); const currentMode = useAtomValue(currentModeAtom); + const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace); + const { openPage } = useNavigateHelper(); const switchToPageMode = useCallback( (pageId: string) => { @@ -140,6 +146,40 @@ export function useBlockSuiteMetaHelper( [setPageMeta] ); + const duplicate = useAsyncCallback( + async (pageId: string) => { + const currentPageMeta = getPageMeta(pageId); + const newPage = createPage(); + const currentPage = blockSuiteWorkspace.getPage(pageId); + + await newPage.waitForLoaded(); + if (!currentPageMeta || !currentPage) { + return; + } + + const update = encodeStateAsUpdate(currentPage.spaceDoc); + applyUpdate(newPage.spaceDoc, update); + + setPageMeta(newPage.id, { + tags: currentPageMeta.tags, + favorite: currentPageMeta.favorite, + }); + setPageMode(newPage.id, currentMode); + setPageTitle(newPage.id, `${currentPageMeta.title}(1)`); + openPage(blockSuiteWorkspace.id, newPage.id); + }, + [ + blockSuiteWorkspace, + createPage, + currentMode, + getPageMeta, + openPage, + setPageMeta, + setPageMode, + setPageTitle, + ] + ); + return { switchToPageMode, switchToEdgelessMode, @@ -155,5 +195,7 @@ export function useBlockSuiteMetaHelper( removeToTrash, restoreFromTrash, permanentlyDeletePage, + + duplicate, }; } diff --git a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx index 62ddb8b48b..73a8b53e62 100644 --- a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx +++ b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx @@ -41,7 +41,7 @@ export function useRegisterBlocksuiteEditorCommands( })); }, [pageId, setPageHistoryModalState]); - const { togglePageMode, toggleFavorite, restoreFromTrash } = + const { togglePageMode, toggleFavorite, restoreFromTrash, duplicate } = useBlockSuiteMetaHelper(blockSuiteWorkspace); const exportHandler = useExportPage(currentPage); const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace); @@ -125,6 +125,19 @@ export function useRegisterBlocksuiteEditorCommands( }) ); + unsubs.push( + registerAffineCommand({ + id: `editor:${mode}-duplicate`, + preconditionStrategy, + category: `editor:${mode}`, + icon: mode === 'page' ? : , + label: t['com.affine.header.option.duplicate'](), + run() { + duplicate(pageId); + }, + }) + ); + unsubs.push( registerAffineCommand({ id: `editor:${mode}-export-to-pdf`, @@ -234,5 +247,6 @@ export function useRegisterBlocksuiteEditorCommands( trash, isCloudWorkspace, openHistoryModal, + duplicate, ]); } diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 440de930e6..0af0b9fc35 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -486,6 +486,7 @@ "com.affine.cmdk.affine.category.editor.edgeless": "Edgeless Commands", "com.affine.cmdk.affine.category.editor.insert-object": "Insert Object", "com.affine.cmdk.affine.category.editor.page": "Page Commands", + "com.affine.cmdk.affine.category.results": "Results", "com.affine.cmdk.affine.client-border-style.to": "Change Client Border Style to", "com.affine.cmdk.affine.color-mode.to": "Change Colour Mode to", "com.affine.cmdk.affine.color-scheme.to": "Change Colour Scheme to", From b73e87e4adc7bc2f8b7d674067c6a294e83cf12b Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:23 +0000 Subject: [PATCH 07/35] chore: upgrade typescript-eslint (#5110) --- package.json | 5 +- yarn.lock | 265 +++++++++++---------------------------------------- 2 files changed, 58 insertions(+), 212 deletions(-) diff --git a/package.json b/package.json index 7497d27ef0..9063335eba 100644 --- a/package.json +++ b/package.json @@ -71,8 +71,8 @@ "@types/affine__env": "workspace:*", "@types/eslint": "^8.44.7", "@types/node": "^20.9.3", - "@typescript-eslint/eslint-plugin": "^6.12.0", - "@typescript-eslint/parser": "^6.12.0", + "@typescript-eslint/eslint-plugin": "^6.13.1", + "@typescript-eslint/parser": "^6.13.1", "@vanilla-extract/vite-plugin": "^3.9.2", "@vanilla-extract/webpack-plugin": "^2.3.1", "@vitejs/plugin-react-swc": "^3.5.0", @@ -82,7 +82,6 @@ "eslint": "^8.54.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-i": "^2.29.0", - "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-simple-import-sort": "^10.0.0", diff --git a/yarn.lock b/yarn.lock index e2b2110adb..fa651662aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -584,8 +584,8 @@ __metadata: "@types/affine__env": "workspace:*" "@types/eslint": "npm:^8.44.7" "@types/node": "npm:^20.9.3" - "@typescript-eslint/eslint-plugin": "npm:^6.12.0" - "@typescript-eslint/parser": "npm:^6.12.0" + "@typescript-eslint/eslint-plugin": "npm:^6.13.1" + "@typescript-eslint/parser": "npm:^6.13.1" "@vanilla-extract/vite-plugin": "npm:^3.9.2" "@vanilla-extract/webpack-plugin": "npm:^2.3.1" "@vitejs/plugin-react-swc": "npm:^3.5.0" @@ -595,7 +595,6 @@ __metadata: eslint: "npm:^8.54.0" eslint-config-prettier: "npm:^9.0.0" eslint-plugin-i: "npm:^2.29.0" - eslint-plugin-prettier: "npm:^5.0.1" eslint-plugin-react: "npm:^7.33.2" eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-simple-import-sort: "npm:^10.0.0" @@ -9311,20 +9310,6 @@ __metadata: languageName: node linkType: hard -"@pkgr/utils@npm:^2.3.1": - version: 2.4.2 - resolution: "@pkgr/utils@npm:2.4.2" - dependencies: - cross-spawn: "npm:^7.0.3" - fast-glob: "npm:^3.3.0" - is-glob: "npm:^4.0.3" - open: "npm:^9.1.0" - picocolors: "npm:^1.0.0" - tslib: "npm:^2.6.0" - checksum: f0b0b305a83bd65fac5637d28ad3e33f19194043e03ceef6b4e13d260bfa2678b73df76dc56ed906469ffe0494d4bd214e6b92ca80684f38547982edf982dd15 - languageName: node - linkType: hard - "@playwright/test@npm:^1.39.0, @playwright/test@npm:^1.40.0": version: 1.40.0 resolution: "@playwright/test@npm:1.40.0" @@ -14306,15 +14291,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/eslint-plugin@npm:6.12.0" +"@typescript-eslint/eslint-plugin@npm:^6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/eslint-plugin@npm:6.13.1" dependencies: "@eslint-community/regexpp": "npm:^4.5.1" - "@typescript-eslint/scope-manager": "npm:6.12.0" - "@typescript-eslint/type-utils": "npm:6.12.0" - "@typescript-eslint/utils": "npm:6.12.0" - "@typescript-eslint/visitor-keys": "npm:6.12.0" + "@typescript-eslint/scope-manager": "npm:6.13.1" + "@typescript-eslint/type-utils": "npm:6.13.1" + "@typescript-eslint/utils": "npm:6.13.1" + "@typescript-eslint/visitor-keys": "npm:6.13.1" debug: "npm:^4.3.4" graphemer: "npm:^1.4.0" ignore: "npm:^5.2.4" @@ -14327,44 +14312,44 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 1b9d2bb88f3e793067d7ec1e24e11b9d22891314bd5ebdef80a11a0ddde19f5c052b341e2f2c8a466b3af48e492f1028023566feaeb10a826d3928380c3d3d88 + checksum: cae42c77404d8e6c149d68aca75bb3ce83cff5de8713d82e87e93bafae2839f29d261bc75b68f315b6b23858a85ea2f22ed8468cf5c7331e8330f7cee2129522 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/parser@npm:6.12.0" +"@typescript-eslint/parser@npm:^6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/parser@npm:6.13.1" dependencies: - "@typescript-eslint/scope-manager": "npm:6.12.0" - "@typescript-eslint/types": "npm:6.12.0" - "@typescript-eslint/typescript-estree": "npm:6.12.0" - "@typescript-eslint/visitor-keys": "npm:6.12.0" + "@typescript-eslint/scope-manager": "npm:6.13.1" + "@typescript-eslint/types": "npm:6.13.1" + "@typescript-eslint/typescript-estree": "npm:6.13.1" + "@typescript-eslint/visitor-keys": "npm:6.13.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 2e33b581bcf882336bd4734e90a90dc3618960f8c07f5f7d16e4f3a0f00af97d3b3c8adc366170e1d9c8afd922068b3cfc5e9e997fd4ca6ebcb7c46a9e5b30a1 + checksum: cdc328d157a8b8a6babad88360451c177ea41666e2150f3822a474ed287a12336517dccf9f475f75a007d4aa622cb74f1442f17d17b87e19cc2c839784742351 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/scope-manager@npm:6.12.0" +"@typescript-eslint/scope-manager@npm:6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/scope-manager@npm:6.13.1" dependencies: - "@typescript-eslint/types": "npm:6.12.0" - "@typescript-eslint/visitor-keys": "npm:6.12.0" - checksum: 46c4a5575fbbb70a800934c93e89795cceef268a140b786a8d22615a0577a5356e42e316dfb23dbb43cec7271b480e712e3127ba33642040bd292fbb6a5de278 + "@typescript-eslint/types": "npm:6.13.1" + "@typescript-eslint/visitor-keys": "npm:6.13.1" + checksum: f728dbd995c58fadfc390411fe31b1b8a729b1c85ecf0ae2fe70f97f613298feab78c05bc180e03612f595b24cf0090476a0b8234c617b3edf1dae568342a7cf languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/type-utils@npm:6.12.0" +"@typescript-eslint/type-utils@npm:6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/type-utils@npm:6.13.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:6.12.0" - "@typescript-eslint/utils": "npm:6.12.0" + "@typescript-eslint/typescript-estree": "npm:6.13.1" + "@typescript-eslint/utils": "npm:6.13.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.0.1" peerDependencies: @@ -14372,7 +14357,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: e92709a0ea5d5aee86def3da40fe4190235d3560f41e77a73d4bc10f6f59e0df367d5a1263d4e05aa44af4deb158ca6f37b09e483248e341a38fd5e2e8b70f72 + checksum: 484e5f74fc604b24687fe6426e650f40f679d62216ee5e45bf6d1f91edd60cd8deef747ca43f7dc3c22b2b76f030477603c82559e44c3f2fb5c8877a0c65aefa languageName: node linkType: hard @@ -14390,19 +14375,19 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/types@npm:6.12.0" - checksum: e52f12d01e2f543927fde985d709616dec1ef310da3a00e3d239874752ba7635e04d325e2a7cf6403d19977282f15fed7629d2477aeeb57df9140fa424f530fe +"@typescript-eslint/types@npm:6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/types@npm:6.13.1" + checksum: 350c7f847052f7c24629d41645c02be152c512f3e5c21a79f53c04821b1fff3019416b18d9b72e5ca5c3c5f62f210301f2bb69080b84e67fe83af27751a7af18 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.12.0" +"@typescript-eslint/typescript-estree@npm:6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/typescript-estree@npm:6.13.1" dependencies: - "@typescript-eslint/types": "npm:6.12.0" - "@typescript-eslint/visitor-keys": "npm:6.12.0" + "@typescript-eslint/types": "npm:6.13.1" + "@typescript-eslint/visitor-keys": "npm:6.13.1" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" @@ -14411,7 +14396,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 16f327faf736becb145894380e059a68a993b14fdf6dab50c5b79ff3c027a1e1a61274742f44f6ecd9ebbfadfc55559f94fad52e1596e1ed2656a3053367de85 + checksum: 1df965c5b202664da1a4a1ffc51bda3d85e581d8c206cd4be63653e2558775104258f6071e1f35a269619ebfb81bd18ee74e3fcb724fed15d3a2577d0ee5a34f languageName: node linkType: hard @@ -14451,20 +14436,20 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/utils@npm:6.12.0" +"@typescript-eslint/utils@npm:6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/utils@npm:6.13.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" "@types/json-schema": "npm:^7.0.12" "@types/semver": "npm:^7.5.0" - "@typescript-eslint/scope-manager": "npm:6.12.0" - "@typescript-eslint/types": "npm:6.12.0" - "@typescript-eslint/typescript-estree": "npm:6.12.0" + "@typescript-eslint/scope-manager": "npm:6.13.1" + "@typescript-eslint/types": "npm:6.13.1" + "@typescript-eslint/typescript-estree": "npm:6.13.1" semver: "npm:^7.5.4" peerDependencies: eslint: ^7.0.0 || ^8.0.0 - checksum: 84091ddc0f0cceb5d0a2e366139d65413867cf648f805355ab4a42ee273cdd691b9083084d1c1feb9cb3c1934c1ed338fbf92146c738a96b84de3d2ec2dfdec5 + checksum: 6fab1122071c7a2da959dcf16cdd723a65bd8ba8e55af9cea11bb1707c4d047e94c3daaed2ab504cdbd2ca0d052f2a33de5290b28de0277ba00210569673ac9b languageName: node linkType: hard @@ -14488,13 +14473,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:6.12.0": - version: 6.12.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.12.0" +"@typescript-eslint/visitor-keys@npm:6.13.1": + version: 6.13.1 + resolution: "@typescript-eslint/visitor-keys@npm:6.13.1" dependencies: - "@typescript-eslint/types": "npm:6.12.0" + "@typescript-eslint/types": "npm:6.13.1" eslint-visitor-keys: "npm:^3.4.1" - checksum: edf3537c8176059e8fdea680c10f85a635e427fb5caa6f88473077f50edbec7b011b0dc1e4499543519085559268d30a166b1cb160d30a1315ef818fc181a6a4 + checksum: 27ccc4bfe940e50b0977838356b7feda95b68754fa544a988588a159a2619eb04d07c67e55d16bfea1b0dc6184a7fb5daff1366b266c9f5fd19d72831dea6163 languageName: node linkType: hard @@ -16754,15 +16739,6 @@ __metadata: languageName: node linkType: hard -"bundle-name@npm:^3.0.0": - version: 3.0.0 - resolution: "bundle-name@npm:3.0.0" - dependencies: - run-applescript: "npm:^5.0.0" - checksum: edf2b1fbe6096ed32e7566947ace2ea937ee427391744d7510a2880c4b9a5b3543d3f6c551236a29e5c87d3195f8e2912516290e638c15bcbede7b37cc375615 - languageName: node - linkType: hard - "busboy@npm:^1.0.0, busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -18719,7 +18695,7 @@ __metadata: languageName: node linkType: hard -"default-browser-id@npm:3.0.0, default-browser-id@npm:^3.0.0": +"default-browser-id@npm:3.0.0": version: 3.0.0 resolution: "default-browser-id@npm:3.0.0" dependencies: @@ -18729,18 +18705,6 @@ __metadata: languageName: node linkType: hard -"default-browser@npm:^4.0.0": - version: 4.0.0 - resolution: "default-browser@npm:4.0.0" - dependencies: - bundle-name: "npm:^3.0.0" - default-browser-id: "npm:^3.0.0" - execa: "npm:^7.1.1" - titleize: "npm:^3.0.0" - checksum: 40c5af984799042b140300be5639c9742599bda76dc9eba5ac9ad5943c83dd36cebc4471eafcfddf8e0ec817166d5ba89d56f08e66a126c7c7908a179cead1a7 - languageName: node - linkType: hard - "default-gateway@npm:^6.0.3": version: 6.0.3 resolution: "default-gateway@npm:6.0.3" @@ -18793,13 +18757,6 @@ __metadata: languageName: node linkType: hard -"define-lazy-prop@npm:^3.0.0": - version: 3.0.0 - resolution: "define-lazy-prop@npm:3.0.0" - checksum: f28421cf9ee86eecaf5f3b8fe875f13d7009c2625e97645bfff7a2a49aca678270b86c39f9c32939e5ca7ab96b551377ed4139558c795e076774287ad3af1aa4 - languageName: node - linkType: hard - "define-properties@npm:@nolyfill/define-properties@latest": version: 1.0.24 resolution: "@nolyfill/define-properties@npm:1.0.24" @@ -20366,25 +20323,6 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-prettier@npm:^5.0.1": - version: 5.0.1 - resolution: "eslint-plugin-prettier@npm:5.0.1" - dependencies: - prettier-linter-helpers: "npm:^1.0.0" - synckit: "npm:^0.8.5" - peerDependencies: - "@types/eslint": ">=8.0.0" - eslint: ">=8.0.0" - prettier: ">=3.0.0" - peerDependenciesMeta: - "@types/eslint": - optional: true - eslint-config-prettier: - optional: true - checksum: 1a43dcca90f61fde0167347e0e870b579835ba6b9d697a862c29c76097a7bb0e8f07a7cf88be33517ca11203d9d4aa335d794c377640c2fe5acd06871db67d34 - languageName: node - linkType: hard - "eslint-plugin-react-hooks@npm:^4.6.0": version: 4.6.0 resolution: "eslint-plugin-react-hooks@npm:4.6.0" @@ -20765,23 +20703,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^7.1.1": - version: 7.2.0 - resolution: "execa@npm:7.2.0" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^6.0.1" - human-signals: "npm:^4.3.0" - is-stream: "npm:^3.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^5.1.0" - onetime: "npm:^6.0.0" - signal-exit: "npm:^3.0.7" - strip-final-newline: "npm:^3.0.0" - checksum: 473feff60f9d4dbe799225948de48b5158c1723021d19c4b982afe37bcd111ae84e1b4c9dfe967fae5101b0894b1a62e4dd564a286dfa3e46d7b0cfdbf7fe62b - languageName: node - linkType: hard - "exit-hook@npm:^2.2.1": version: 2.2.1 resolution: "exit-hook@npm:2.2.1" @@ -20964,7 +20885,7 @@ __metadata: languageName: node linkType: hard -"fast-diff@npm:^1.1.2, fast-diff@npm:^1.2.0": +"fast-diff@npm:^1.2.0": version: 1.3.0 resolution: "fast-diff@npm:1.3.0" checksum: 9e57415bc69cd6efcc720b3b8fe9fdaf42dcfc06f86f0f45378b1fa512598a8aac48aa3928c8751d58e2f01bb4ba4f07e4f3d9bc0d57586d45f1bd1e872c6cde @@ -22064,7 +21985,7 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": +"get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" checksum: 781266d29725f35c59f1d214aedc92b0ae855800a980800e2923b3fbc4e56b3cb6e462c42e09a1cf1a00c64e056a78fa407cbe06c7c92b7e5cd49b4b85c2a497 @@ -23122,13 +23043,6 @@ __metadata: languageName: node linkType: hard -"human-signals@npm:^4.3.0": - version: 4.3.1 - resolution: "human-signals@npm:4.3.1" - checksum: fa59894c358fe9f2b5549be2fb083661d5e1dff618d3ac70a49ca73495a72e873fbf6c0878561478e521e17d498292746ee391791db95ffe5747bfb5aef8765b - languageName: node - linkType: hard - "human-signals@npm:^5.0.0": version: 5.0.0 resolution: "human-signals@npm:5.0.0" @@ -23611,15 +23525,6 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^3.0.0": - version: 3.0.0 - resolution: "is-docker@npm:3.0.0" - bin: - is-docker: cli.js - checksum: b698118f04feb7eaf3338922bd79cba064ea54a1c3db6ec8c0c8d8ee7613e7e5854d802d3ef646812a8a3ace81182a085dfa0a71cc68b06f3fa794b9783b3c90 - languageName: node - linkType: hard - "is-error@npm:^2.2.2": version: 2.2.2 resolution: "is-error@npm:2.2.2" @@ -23685,17 +23590,6 @@ __metadata: languageName: node linkType: hard -"is-inside-container@npm:^1.0.0": - version: 1.0.0 - resolution: "is-inside-container@npm:1.0.0" - dependencies: - is-docker: "npm:^3.0.0" - bin: - is-inside-container: cli.js - checksum: c50b75a2ab66ab3e8b92b3bc534e1ea72ca25766832c0623ac22d134116a98bcf012197d1caabe1d1c4bd5f84363d4aa5c36bb4b585fbcaf57be172cd10a1a03 - languageName: node - linkType: hard - "is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" @@ -28689,18 +28583,6 @@ __metadata: languageName: node linkType: hard -"open@npm:^9.1.0": - version: 9.1.0 - resolution: "open@npm:9.1.0" - dependencies: - default-browser: "npm:^4.0.0" - define-lazy-prop: "npm:^3.0.0" - is-inside-container: "npm:^1.0.0" - is-wsl: "npm:^2.2.0" - checksum: b45bcc7a6795804a2f560f0ca9f5e5344114bc40754d10c28a811c0c8f7027356979192931a6a7df2ab9e5bab3058988c99ae55f4fb71db2ce9fc77c40f619aa - languageName: node - linkType: hard - "openai@npm:~4.4.0": version: 4.4.0 resolution: "openai@npm:4.4.0" @@ -30092,15 +29974,6 @@ __metadata: languageName: node linkType: hard -"prettier-linter-helpers@npm:^1.0.0": - version: 1.0.0 - resolution: "prettier-linter-helpers@npm:1.0.0" - dependencies: - fast-diff: "npm:^1.1.2" - checksum: 00ce8011cf6430158d27f9c92cfea0a7699405633f7f1d4a45f07e21bf78e99895911cbcdc3853db3a824201a7c745bd49bfea8abd5fb9883e765a90f74f8392 - languageName: node - linkType: hard - "prettier@npm:*, prettier@npm:^3.0.3, prettier@npm:^3.1.0": version: 3.1.0 resolution: "prettier@npm:3.1.0" @@ -31878,15 +31751,6 @@ __metadata: languageName: node linkType: hard -"run-applescript@npm:^5.0.0": - version: 5.0.0 - resolution: "run-applescript@npm:5.0.0" - dependencies: - execa: "npm:^5.0.0" - checksum: d00c2dbfa5b2d774de7451194b8b125f40f65fc183de7d9dcae97f57f59433586d3c39b9001e111c38bfa24c3436c99df1bb4066a2a0c90d39a8c4cd6889af77 - languageName: node - linkType: hard - "run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -33484,16 +33348,6 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.8.5": - version: 0.8.5 - resolution: "synckit@npm:0.8.5" - dependencies: - "@pkgr/utils": "npm:^2.3.1" - tslib: "npm:^2.5.0" - checksum: fb6798a2db2650ca3a2435ad32d4fc14842da807993a1a350b64d267e0e770aa7f26492b119aa7500892d3d07a5af1eec7bfbd6e23a619451558be0f226a6094 - languageName: node - linkType: hard - "systeminformation@npm:^5.0.0": version: 5.21.17 resolution: "systeminformation@npm:5.21.17" @@ -33840,13 +33694,6 @@ __metadata: languageName: node linkType: hard -"titleize@npm:^3.0.0": - version: 3.0.0 - resolution: "titleize@npm:3.0.0" - checksum: 71fbbeabbfb36ccd840559f67f21e356e1d03da2915b32d2ae1a60ddcc13a124be2739f696d2feb884983441d159a18649e8d956648d591bdad35c430a6b6d28 - languageName: node - linkType: hard - "tmp-promise@npm:^3.0.2": version: 3.0.3 resolution: "tmp-promise@npm:3.0.3" @@ -34121,7 +33968,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.6.2, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2": +"tslib@npm:2.6.2, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca From a843dcd85182b75ebf199e7a1c11f5989648ad19 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:27 +0000 Subject: [PATCH 08/35] fix: resolve cycle imports and prevent it by oxlint (#5103) --- .eslintrc.js | 1 + .github/workflows/build.yml | 4 +- .husky/pre-commit | 21 +- package.json | 4 +- packages/backend/server/src/modules/config.ts | 2 +- packages/backend/server/src/modules/index.ts | 4 +- .../src/components/page-list/page-group.tsx | 90 +- .../page-list/pages-to-page-group.tsx | 85 ++ .../src/components/page-list/scoped-atoms.tsx | 2 +- .../page-list/view/create-collection.tsx | 44 - .../view/edit-collection/edit-collection.tsx | 2 +- .../page-list/view/edit-collection/hooks.tsx | 53 +- .../view/edit-collection/pages-mode.tsx | 11 +- .../view/edit-collection/select-page.tsx | 6 +- .../view/edit-collection/use-filter.tsx | 40 + .../view/edit-collection/use-search.tsx | 13 + .../src/components/page-list/view/index.ts | 1 + .../view/save-as-collection-button.tsx | 46 + .../page-list/view/use-edit-collection.tsx | 13 +- .../component/src/ui/skeleton/skeleton.tsx | 2 +- .../workspace/src/providers/sync/consts.ts | 7 + .../workspace/src/providers/sync/engine.ts | 9 +- .../workspace/src/providers/sync/index.ts | 1 + .../workspace/src/providers/sync/peer.ts | 2 +- yarn.lock | 880 ++---------------- 25 files changed, 309 insertions(+), 1034 deletions(-) create mode 100644 packages/frontend/component/src/components/page-list/pages-to-page-group.tsx create mode 100644 packages/frontend/component/src/components/page-list/view/edit-collection/use-filter.tsx create mode 100644 packages/frontend/component/src/components/page-list/view/edit-collection/use-search.tsx create mode 100644 packages/frontend/component/src/components/page-list/view/save-as-collection-button.tsx create mode 100644 packages/frontend/workspace/src/providers/sync/consts.ts diff --git a/.eslintrc.js b/.eslintrc.js index 73b7e97c36..0782ae8893 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -205,6 +205,7 @@ const config = { }, ], 'unicorn/no-unnecessary-await': 'error', + 'unicorn/no-useless-fallback-in-spread': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', 'sonarjs/no-empty-collection': 'error', diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bd2266a4f..ee01a2e891 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v4 - name: Run oxlint # oxlint is fast, so wrong code will fail quickly - run: yarn dlx oxlint@latest . + run: yarn dlx $(node -e "console.log(require('./package.json').scripts['lint:ox'])") - name: Setup Node.js uses: ./.github/actions/setup-node with: @@ -58,8 +58,6 @@ jobs: run: | git checkout .yarnrc.yml yarn lint:prettier - - name: Run circular - run: yarn circular - name: Run Type Check run: yarn typecheck diff --git a/.husky/pre-commit b/.husky/pre-commit index 0a0d459b28..76188b6556 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,23 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -# check lockfile is up to date -yarn install --mode=skip-build --inline-builds --immutable - -# build infra code -yarn -T run build:infra - -# generate prisma client type -yarn workspace @affine/server prisma generate - -# generate i18n -yarn i18n-codegen gen - -# lint staged files -yarn exec lint-staged - -# type check -yarn typecheck - -# circular dependency check -yarn circular +yarn lint:ox diff --git a/package.json b/package.json index 9063335eba..80a0b4aec9 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "lint:eslint:fix": "yarn lint:eslint --fix", "lint:prettier": "prettier --ignore-unknown --cache --check .", "lint:prettier:fix": "prettier --ignore-unknown --cache --write .", + "lint:ox": "oxlint --deny-warnings --import-plugin -D correctness -D nursery -A no-undef -A consistent-type-exports -A default -A named -A ban-ts-comment", "lint": "yarn lint:eslint && yarn lint:prettier", "lint:fix": "yarn lint:eslint:fix && yarn lint:prettier:fix", "test": "vitest --run", @@ -49,7 +50,6 @@ "eslint --cache --fix" ], "*.toml": [ - "prettier --ignore-unknown --write", "taplo format" ] }, @@ -93,12 +93,12 @@ "happy-dom": "^12.10.3", "husky": "^8.0.3", "lint-staged": "^15.1.0", - "madge": "^6.1.0", "msw": "^2.0.8", "nanoid": "^5.0.3", "nx": "^17.1.3", "nx-cloud": "^16.5.2", "nyc": "^15.1.0", + "oxlint": "^0.0.18", "prettier": "^3.1.0", "semver": "^7.5.4", "serve": "^14.2.1", diff --git a/packages/backend/server/src/modules/config.ts b/packages/backend/server/src/modules/config.ts index d0fb9e69d4..be9261f2a1 100644 --- a/packages/backend/server/src/modules/config.ts +++ b/packages/backend/server/src/modules/config.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { Field, ObjectType, Query } from '@nestjs/graphql'; -import { SERVER_FLAVOR } from '../modules'; +export const { SERVER_FLAVOR } = process.env; @ObjectType() export class ServerConfigType { diff --git a/packages/backend/server/src/modules/index.ts b/packages/backend/server/src/modules/index.ts index a16215c68e..d021dd8bfa 100644 --- a/packages/backend/server/src/modules/index.ts +++ b/packages/backend/server/src/modules/index.ts @@ -3,7 +3,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter'; import { ScheduleModule } from '@nestjs/schedule'; import { GqlModule } from '../graphql.module'; -import { ServerConfigModule } from './config'; +import { SERVER_FLAVOR, ServerConfigModule } from './config'; import { DocModule } from './doc'; import { PaymentModule } from './payment'; import { SelfHostedModule } from './self-hosted'; @@ -11,8 +11,6 @@ import { SyncModule } from './sync'; import { UsersModule } from './users'; import { WorkspaceModule } from './workspaces'; -const { SERVER_FLAVOR } = process.env; - const BusinessModules: (Type | DynamicModule)[] = [ EventEmitterModule.forRoot({ global: true, diff --git a/packages/frontend/component/src/components/page-list/page-group.tsx b/packages/frontend/component/src/components/page-list/page-group.tsx index 87963a9216..77dc4ba191 100644 --- a/packages/frontend/component/src/components/page-list/page-group.tsx +++ b/packages/frontend/component/src/components/page-list/page-group.tsx @@ -1,5 +1,4 @@ import type { Tag } from '@affine/env/filter'; -import { Trans } from '@affine/i18n'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { assertExists } from '@blocksuite/global/utils'; import { EdgelessIcon, PageIcon, ToggleCollapseIcon } from '@blocksuite/icons'; @@ -19,93 +18,8 @@ import { useAtom, useAtomValue, } from './scoped-atoms'; -import type { - PageGroupDefinition, - PageGroupProps, - PageListItemProps, - PageListProps, -} from './types'; -import { type DateKey } from './types'; -import { betweenDaysAgo, shallowEqual, withinDaysAgo } from './utils'; - -// todo: optimize date matchers -const getDateGroupDefinitions = (key: DateKey): PageGroupDefinition[] => [ - { - id: 'today', - label: , - match: item => withinDaysAgo(new Date(item[key] ?? item.createDate), 1), - }, - { - id: 'yesterday', - label: , - match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 1, 2), - }, - { - id: 'last7Days', - label: , - match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 2, 7), - }, - { - id: 'last30Days', - label: , - match: item => - betweenDaysAgo(new Date(item[key] ?? item.createDate), 7, 30), - }, - { - id: 'moreThan30Days', - label: , - match: item => !withinDaysAgo(new Date(item[key] ?? item.createDate), 30), - }, -]; - -const pageGroupDefinitions = { - createDate: getDateGroupDefinitions('createDate'), - updatedDate: getDateGroupDefinitions('updatedDate'), - // add more here later - // todo: some page group definitions maybe dynamic -}; - -export function pagesToPageGroups( - pages: PageMeta[], - key?: DateKey -): PageGroupProps[] { - if (!key) { - return [ - { - id: 'all', - items: pages, - allItems: pages, - }, - ]; - } - - // assume pages are already sorted, we will use the page order to determine the group order - const groupDefs = pageGroupDefinitions[key]; - const groups: PageGroupProps[] = []; - - for (const page of pages) { - // for a single page, there could be multiple groups that it belongs to - const matchedGroups = groupDefs.filter(def => def.match(page)); - for (const groupDef of matchedGroups) { - const group = groups.find(g => g.id === groupDef.id); - if (group) { - group.items.push(page); - } else { - const label = - typeof groupDef.label === 'function' - ? groupDef.label() - : groupDef.label; - groups.push({ - id: groupDef.id, - label: label, - items: [page], - allItems: pages, - }); - } - } - } - return groups; -} +import type { PageGroupProps, PageListItemProps, PageListProps } from './types'; +import { shallowEqual } from './utils'; export const PageGroupHeader = ({ id, items, label }: PageGroupProps) => { const [collapseState, setCollapseState] = useAtom(pageGroupCollapseStateAtom); diff --git a/packages/frontend/component/src/components/page-list/pages-to-page-group.tsx b/packages/frontend/component/src/components/page-list/pages-to-page-group.tsx new file mode 100644 index 0000000000..cb272368c5 --- /dev/null +++ b/packages/frontend/component/src/components/page-list/pages-to-page-group.tsx @@ -0,0 +1,85 @@ +import { Trans } from '@affine/i18n'; +import type { PageMeta } from '@blocksuite/store'; + +import type { PageGroupDefinition, PageGroupProps } from './types'; +import { type DateKey } from './types'; +import { betweenDaysAgo, withinDaysAgo } from './utils'; + +// todo: optimize date matchers +const getDateGroupDefinitions = (key: DateKey): PageGroupDefinition[] => [ + { + id: 'today', + label: , + match: item => withinDaysAgo(new Date(item[key] ?? item.createDate), 1), + }, + { + id: 'yesterday', + label: , + match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 1, 2), + }, + { + id: 'last7Days', + label: , + match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 2, 7), + }, + { + id: 'last30Days', + label: , + match: item => + betweenDaysAgo(new Date(item[key] ?? item.createDate), 7, 30), + }, + { + id: 'moreThan30Days', + label: , + match: item => !withinDaysAgo(new Date(item[key] ?? item.createDate), 30), + }, +]; + +const pageGroupDefinitions = { + createDate: getDateGroupDefinitions('createDate'), + updatedDate: getDateGroupDefinitions('updatedDate'), + // add more here later + // todo: some page group definitions maybe dynamic +}; + +export function pagesToPageGroups( + pages: PageMeta[], + key?: DateKey +): PageGroupProps[] { + if (!key) { + return [ + { + id: 'all', + items: pages, + allItems: pages, + }, + ]; + } + + // assume pages are already sorted, we will use the page order to determine the group order + const groupDefs = pageGroupDefinitions[key]; + const groups: PageGroupProps[] = []; + + for (const page of pages) { + // for a single page, there could be multiple groups that it belongs to + const matchedGroups = groupDefs.filter(def => def.match(page)); + for (const groupDef of matchedGroups) { + const group = groups.find(g => g.id === groupDef.id); + if (group) { + group.items.push(page); + } else { + const label = + typeof groupDef.label === 'function' + ? groupDef.label() + : groupDef.label; + groups.push({ + id: groupDef.id, + label: label, + items: [page], + allItems: pages, + }); + } + } + } + return groups; +} diff --git a/packages/frontend/component/src/components/page-list/scoped-atoms.tsx b/packages/frontend/component/src/components/page-list/scoped-atoms.tsx index 9ad94f539e..bed647ba8c 100644 --- a/packages/frontend/component/src/components/page-list/scoped-atoms.tsx +++ b/packages/frontend/component/src/components/page-list/scoped-atoms.tsx @@ -4,7 +4,7 @@ import { atom } from 'jotai'; import { selectAtom } from 'jotai/utils'; import { createIsolation } from 'jotai-scope'; -import { pagesToPageGroups } from './page-group'; +import { pagesToPageGroups } from './pages-to-page-group'; import type { PageListProps, PageMetaRecord, diff --git a/packages/frontend/component/src/components/page-list/view/create-collection.tsx b/packages/frontend/component/src/components/page-list/view/create-collection.tsx index 32f4f37fd5..a769e1c078 100644 --- a/packages/frontend/component/src/components/page-list/view/create-collection.tsx +++ b/packages/frontend/component/src/components/page-list/view/create-collection.tsx @@ -1,13 +1,6 @@ -import { - createEmptyCollection, - useEditCollectionName, -} from '@affine/component/page-list'; -import type { Collection } from '@affine/env/filter'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { SaveIcon } from '@blocksuite/icons'; import { Button } from '@toeverything/components/button'; import { Modal } from '@toeverything/components/modal'; -import { nanoid } from 'nanoid'; import { useCallback, useMemo, useState } from 'react'; import Input from '../../../ui/input'; @@ -127,40 +120,3 @@ export const CreateCollection = ({ ); }; - -interface SaveAsCollectionButtonProps { - onConfirm: (collection: Collection) => Promise; -} - -export const SaveAsCollectionButton = ({ - onConfirm, -}: SaveAsCollectionButtonProps) => { - const t = useAFFiNEI18N(); - const { open, node } = useEditCollectionName({ - title: t['com.affine.editCollection.saveCollection'](), - showTips: true, - }); - const handleClick = useCallback(() => { - open('') - .then(name => { - return onConfirm(createEmptyCollection(nanoid(), { name })); - }) - .catch(err => { - console.error(err); - }); - }, [open, onConfirm]); - return ( - <> - - {node} - - ); -}; diff --git a/packages/frontend/component/src/components/page-list/view/edit-collection/edit-collection.tsx b/packages/frontend/component/src/components/page-list/view/edit-collection/edit-collection.tsx index 4cf17f9b32..ad91403861 100644 --- a/packages/frontend/component/src/components/page-list/view/edit-collection/edit-collection.tsx +++ b/packages/frontend/component/src/components/page-list/view/edit-collection/edit-collection.tsx @@ -6,7 +6,7 @@ import { Button } from '@toeverything/components/button'; import { Modal } from '@toeverything/components/modal'; import { type ReactNode, useCallback, useMemo, useState } from 'react'; -import { RadioButton, RadioButtonGroup } from '../../../../index'; +import { RadioButton, RadioButtonGroup } from '../../../../ui/button'; import * as styles from './edit-collection.css'; import { PagesMode } from './pages-mode'; import { RulesMode } from './rules-mode'; diff --git a/packages/frontend/component/src/components/page-list/view/edit-collection/hooks.tsx b/packages/frontend/component/src/components/page-list/view/edit-collection/hooks.tsx index 55ba854682..58fde6a7ed 100644 --- a/packages/frontend/component/src/components/page-list/view/edit-collection/hooks.tsx +++ b/packages/frontend/component/src/components/page-list/view/edit-collection/hooks.tsx @@ -1,12 +1,7 @@ -import { - type AllPageListConfig, - filterPageByRules, -} from '@affine/component/page-list'; -import type { Filter } from '@affine/env/filter'; -import type { PageMeta } from '@blocksuite/store'; import { Modal } from '@toeverything/components/modal'; -import { type MouseEvent, useCallback, useState } from 'react'; +import { useCallback, useState } from 'react'; +import type { AllPageListConfig } from './edit-collection'; import { SelectPage } from './select-page'; export const useSelectPage = ({ allPageListConfig, @@ -60,47 +55,3 @@ export const useSelectPage = ({ }), }; }; -export const useFilter = (list: PageMeta[]) => { - const [filters, changeFilters] = useState([]); - const [showFilter, setShowFilter] = useState(false); - const clickFilter = useCallback( - (e: MouseEvent) => { - if (showFilter || filters.length !== 0) { - e.stopPropagation(); - e.preventDefault(); - setShowFilter(!showFilter); - } - }, - [filters.length, showFilter] - ); - const onCreateFilter = useCallback( - (filter: Filter) => { - changeFilters([...filters, filter]); - setShowFilter(true); - }, - [filters] - ); - return { - showFilter, - filters, - updateFilters: changeFilters, - clickFilter, - createFilter: onCreateFilter, - filteredList: list.filter(v => { - if (v.trash) { - return false; - } - return filterPageByRules(filters, [], v); - }), - }; -}; -export const useSearch = (list: PageMeta[]) => { - const [value, onChange] = useState(''); - return { - searchText: value, - updateSearchText: onChange, - searchedList: value - ? list.filter(v => v.title.toLowerCase().includes(value.toLowerCase())) - : list, - }; -}; diff --git a/packages/frontend/component/src/components/page-list/view/edit-collection/pages-mode.tsx b/packages/frontend/component/src/components/page-list/view/edit-collection/pages-mode.tsx index 358f5b460b..4f25b105e7 100644 --- a/packages/frontend/component/src/components/page-list/view/edit-collection/pages-mode.tsx +++ b/packages/frontend/component/src/components/page-list/view/edit-collection/pages-mode.tsx @@ -1,8 +1,3 @@ -import { - type AllPageListConfig, - FilterList, - VirtualizedPageList, -} from '@affine/component/page-list'; import type { Collection } from '@affine/env/filter'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { FilterIcon } from '@blocksuite/icons'; @@ -11,10 +6,14 @@ import { Menu } from '@toeverything/components/menu'; import clsx from 'clsx'; import { type ReactNode, useCallback } from 'react'; +import { FilterList } from '../../filter/filter-list'; import { VariableSelect } from '../../filter/vars'; +import { VirtualizedPageList } from '../../virtualized-page-list'; +import type { AllPageListConfig } from './edit-collection'; import * as styles from './edit-collection.css'; -import { useFilter, useSearch } from './hooks'; import { EmptyList } from './select-page'; +import { useFilter } from './use-filter'; +import { useSearch } from './use-search'; export const PagesMode = ({ switchMode, diff --git a/packages/frontend/component/src/components/page-list/view/edit-collection/select-page.tsx b/packages/frontend/component/src/components/page-list/view/edit-collection/select-page.tsx index 86a5f839f1..f4a4e7ffcf 100644 --- a/packages/frontend/component/src/components/page-list/view/edit-collection/select-page.tsx +++ b/packages/frontend/component/src/components/page-list/view/edit-collection/select-page.tsx @@ -6,13 +6,15 @@ import { Menu } from '@toeverything/components/menu'; import clsx from 'clsx'; import { useCallback, useState } from 'react'; -import { VirtualizedPageList } from '../..'; import { FilterList } from '../../filter'; import { VariableSelect } from '../../filter/vars'; +import { VirtualizedPageList } from '../../virtualized-page-list'; import { AffineShapeIcon } from '../affine-shape'; import type { AllPageListConfig } from './edit-collection'; import * as styles from './edit-collection.css'; -import { useFilter, useSearch } from './hooks'; +import { useFilter } from './use-filter'; +import { useSearch } from './use-search'; + export const SelectPage = ({ allPageListConfig, init, diff --git a/packages/frontend/component/src/components/page-list/view/edit-collection/use-filter.tsx b/packages/frontend/component/src/components/page-list/view/edit-collection/use-filter.tsx new file mode 100644 index 0000000000..8775c5a064 --- /dev/null +++ b/packages/frontend/component/src/components/page-list/view/edit-collection/use-filter.tsx @@ -0,0 +1,40 @@ +import type { Filter } from '@affine/env/filter'; +import type { PageMeta } from '@blocksuite/store'; +import { type MouseEvent, useCallback, useState } from 'react'; + +import { filterPageByRules } from '../../use-collection-manager'; + +export const useFilter = (list: PageMeta[]) => { + const [filters, changeFilters] = useState([]); + const [showFilter, setShowFilter] = useState(false); + const clickFilter = useCallback( + (e: MouseEvent) => { + if (showFilter || filters.length !== 0) { + e.stopPropagation(); + e.preventDefault(); + setShowFilter(!showFilter); + } + }, + [filters.length, showFilter] + ); + const onCreateFilter = useCallback( + (filter: Filter) => { + changeFilters([...filters, filter]); + setShowFilter(true); + }, + [filters] + ); + return { + showFilter, + filters, + updateFilters: changeFilters, + clickFilter, + createFilter: onCreateFilter, + filteredList: list.filter(v => { + if (v.trash) { + return false; + } + return filterPageByRules(filters, [], v); + }), + }; +}; diff --git a/packages/frontend/component/src/components/page-list/view/edit-collection/use-search.tsx b/packages/frontend/component/src/components/page-list/view/edit-collection/use-search.tsx new file mode 100644 index 0000000000..55e63e8ec1 --- /dev/null +++ b/packages/frontend/component/src/components/page-list/view/edit-collection/use-search.tsx @@ -0,0 +1,13 @@ +import type { PageMeta } from '@blocksuite/store'; +import { useState } from 'react'; + +export const useSearch = (list: PageMeta[]) => { + const [value, onChange] = useState(''); + return { + searchText: value, + updateSearchText: onChange, + searchedList: value + ? list.filter(v => v.title.toLowerCase().includes(value.toLowerCase())) + : list, + }; +}; diff --git a/packages/frontend/component/src/components/page-list/view/index.ts b/packages/frontend/component/src/components/page-list/view/index.ts index 8c130dca83..35f615cd90 100644 --- a/packages/frontend/component/src/components/page-list/view/index.ts +++ b/packages/frontend/component/src/components/page-list/view/index.ts @@ -4,4 +4,5 @@ export * from './collection-list'; export * from './collection-operations'; export * from './create-collection'; export * from './edit-collection/edit-collection'; +export * from './save-as-collection-button'; export * from './use-edit-collection'; diff --git a/packages/frontend/component/src/components/page-list/view/save-as-collection-button.tsx b/packages/frontend/component/src/components/page-list/view/save-as-collection-button.tsx new file mode 100644 index 0000000000..bcdd35c44f --- /dev/null +++ b/packages/frontend/component/src/components/page-list/view/save-as-collection-button.tsx @@ -0,0 +1,46 @@ +import type { Collection } from '@affine/env/filter'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { SaveIcon } from '@blocksuite/icons'; +import { Button } from '@toeverything/components/button'; +import { nanoid } from 'nanoid'; +import { useCallback } from 'react'; + +import { createEmptyCollection } from '../use-collection-manager'; +import { useEditCollectionName } from './use-edit-collection'; + +interface SaveAsCollectionButtonProps { + onConfirm: (collection: Collection) => Promise; +} + +export const SaveAsCollectionButton = ({ + onConfirm, +}: SaveAsCollectionButtonProps) => { + const t = useAFFiNEI18N(); + const { open, node } = useEditCollectionName({ + title: t['com.affine.editCollection.saveCollection'](), + showTips: true, + }); + const handleClick = useCallback(() => { + open('') + .then(name => { + return onConfirm(createEmptyCollection(nanoid(), { name })); + }) + .catch(err => { + console.error(err); + }); + }, [open, onConfirm]); + return ( + <> + + {node} + + ); +}; diff --git a/packages/frontend/component/src/components/page-list/view/use-edit-collection.tsx b/packages/frontend/component/src/components/page-list/view/use-edit-collection.tsx index 901ec58beb..063e5cf19a 100644 --- a/packages/frontend/component/src/components/page-list/view/use-edit-collection.tsx +++ b/packages/frontend/component/src/components/page-list/view/use-edit-collection.tsx @@ -1,12 +1,13 @@ -import { - type AllPageListConfig, - CreateCollectionModal, - EditCollectionModal, - type EditCollectionMode, -} from '@affine/component/page-list'; import type { Collection } from '@affine/env/filter'; import { useCallback, useState } from 'react'; +import { CreateCollectionModal } from './create-collection'; +import { + type AllPageListConfig, + EditCollectionModal, + type EditCollectionMode, +} from './edit-collection/edit-collection'; + export const useEditCollection = (config: AllPageListConfig) => { const [data, setData] = useState<{ collection: Collection; diff --git a/packages/frontend/component/src/ui/skeleton/skeleton.tsx b/packages/frontend/component/src/ui/skeleton/skeleton.tsx index fef3285905..404e9817a7 100644 --- a/packages/frontend/component/src/ui/skeleton/skeleton.tsx +++ b/packages/frontend/component/src/ui/skeleton/skeleton.tsx @@ -29,7 +29,7 @@ export const Skeleton = ({ const style = { width, height, - ...(_style || {}), + ..._style, }; return ( diff --git a/packages/frontend/workspace/src/providers/sync/consts.ts b/packages/frontend/workspace/src/providers/sync/consts.ts new file mode 100644 index 0000000000..4ddda3db06 --- /dev/null +++ b/packages/frontend/workspace/src/providers/sync/consts.ts @@ -0,0 +1,7 @@ +export const MANUALLY_STOP = 'manually-stop'; + +export enum SyncEngineStep { + Stopped = 0, + Syncing = 1, + Synced = 2, +} diff --git a/packages/frontend/workspace/src/providers/sync/engine.ts b/packages/frontend/workspace/src/providers/sync/engine.ts index ae723708c6..120dc97739 100644 --- a/packages/frontend/workspace/src/providers/sync/engine.ts +++ b/packages/frontend/workspace/src/providers/sync/engine.ts @@ -3,16 +3,9 @@ import { Slot } from '@blocksuite/global/utils'; import type { Doc } from 'yjs'; import type { Storage } from '../storage'; +import { MANUALLY_STOP, SyncEngineStep } from './consts'; import { SyncPeer, type SyncPeerStatus, SyncPeerStep } from './peer'; -export const MANUALLY_STOP = 'manually-stop'; - -export enum SyncEngineStep { - Stopped = 0, - Syncing = 1, - Synced = 2, -} - export interface SyncEngineStatus { step: SyncEngineStep; local: SyncPeerStatus | null; diff --git a/packages/frontend/workspace/src/providers/sync/index.ts b/packages/frontend/workspace/src/providers/sync/index.ts index 882fbe154a..b741931973 100644 --- a/packages/frontend/workspace/src/providers/sync/index.ts +++ b/packages/frontend/workspace/src/providers/sync/index.ts @@ -14,5 +14,6 @@ * */ +export * from './consts'; export * from './engine'; export * from './peer'; diff --git a/packages/frontend/workspace/src/providers/sync/peer.ts b/packages/frontend/workspace/src/providers/sync/peer.ts index ecca8106a7..a0c270e801 100644 --- a/packages/frontend/workspace/src/providers/sync/peer.ts +++ b/packages/frontend/workspace/src/providers/sync/peer.ts @@ -7,7 +7,7 @@ import { applyUpdate, encodeStateAsUpdate, encodeStateVector } from 'yjs'; import { mergeUpdates, type Storage } from '../storage'; import { AsyncQueue } from '../utils/async-queue'; import { throwIfAborted } from '../utils/throw-if-aborted'; -import { MANUALLY_STOP } from './engine'; +import { MANUALLY_STOP } from './consts'; export enum SyncPeerStep { Stopped = 0, diff --git a/yarn.lock b/yarn.lock index fa651662aa..f56433d137 100644 --- a/yarn.lock +++ b/yarn.lock @@ -606,12 +606,12 @@ __metadata: happy-dom: "npm:^12.10.3" husky: "npm:^8.0.3" lint-staged: "npm:^15.1.0" - madge: "npm:^6.1.0" msw: "npm:^2.0.8" nanoid: "npm:^5.0.3" nx: "npm:^17.1.3" nx-cloud: "npm:^16.5.2" nyc: "npm:^15.1.0" + oxlint: "npm:^0.0.18" prettier: "npm:^3.1.0" semver: "npm:^7.5.4" serve: "npm:^14.2.1" @@ -2748,7 +2748,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.0.0, @babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.4, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.22.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.3, @babel/parser@npm:^7.23.4": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.22.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.3, @babel/parser@npm:^7.23.4": version: 7.23.4 resolution: "@babel/parser@npm:7.23.4" bin: @@ -4501,16 +4501,6 @@ __metadata: languageName: node linkType: hard -"@dependents/detective-less@npm:^3.0.1": - version: 3.0.2 - resolution: "@dependents/detective-less@npm:3.0.2" - dependencies: - gonzales-pe: "npm:^4.3.0" - node-source-walk: "npm:^5.0.1" - checksum: 2c263ab64fcd1f76117bc35f2b29a150c64bd2b105c96a909a63ce2f2baf07efd93d9ae80e612161d003fb71fbe46598292375f5cc3f447a1b83cfb545dc8f8f - languageName: node - linkType: hard - "@discoveryjs/json-ext@npm:^0.5.0, @discoveryjs/json-ext@npm:^0.5.3": version: 0.5.7 resolution: "@discoveryjs/json-ext@npm:0.5.7" @@ -9144,6 +9134,48 @@ __metadata: languageName: node linkType: hard +"@oxlint/darwin-arm64@npm:0.0.18": + version: 0.0.18 + resolution: "@oxlint/darwin-arm64@npm:0.0.18" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxlint/darwin-x64@npm:0.0.18": + version: 0.0.18 + resolution: "@oxlint/darwin-x64@npm:0.0.18" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxlint/linux-arm64@npm:0.0.18": + version: 0.0.18 + resolution: "@oxlint/linux-arm64@npm:0.0.18" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@oxlint/linux-x64@npm:0.0.18": + version: 0.0.18 + resolution: "@oxlint/linux-x64@npm:0.0.18" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@oxlint/win32-arm64@npm:0.0.18": + version: 0.0.18 + resolution: "@oxlint/win32-arm64@npm:0.0.18" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxlint/win32-x64@npm:0.0.18": + version: 0.0.18 + resolution: "@oxlint/win32-x64@npm:0.0.18" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@panva/hkdf@npm:^1.0.2, @panva/hkdf@npm:^1.1.1": version: 1.1.1 resolution: "@panva/hkdf@npm:1.1.1" @@ -13718,13 +13750,6 @@ __metadata: languageName: node linkType: hard -"@types/json5@npm:^0.0.29": - version: 0.0.29 - resolution: "@types/json5@npm:0.0.29" - checksum: 4e5aed58cabb2bbf6f725da13421aa50a49abb6bc17bfab6c31b8774b073fa7b50d557c61f961a09a85f6056151190f8ac95f13f5b48136ba5841f7d4484ec56 - languageName: node - linkType: hard - "@types/jsonfile@npm:*": version: 6.1.4 resolution: "@types/jsonfile@npm:6.1.4" @@ -14361,20 +14386,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/types@npm:4.33.0" - checksum: c7ec55e6aef47ac49f712da52968c825552a23c59717f338f08c480e5336d592a689a324d21e2d370f48713a1302d7c78c14e6250bcea9b91f0b62ab6b2db3e1 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/types@npm:5.62.0" - checksum: 24e8443177be84823242d6729d56af2c4b47bfc664dd411a1d730506abf2150d6c31bdefbbc6d97c8f91043e3a50e0c698239dcb145b79bb6b0c34469aaf6c45 - languageName: node - linkType: hard - "@typescript-eslint/types@npm:6.13.1": version: 6.13.1 resolution: "@typescript-eslint/types@npm:6.13.1" @@ -14400,42 +14411,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:^4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/typescript-estree@npm:4.33.0" - dependencies: - "@typescript-eslint/types": "npm:4.33.0" - "@typescript-eslint/visitor-keys": "npm:4.33.0" - debug: "npm:^4.3.1" - globby: "npm:^11.0.3" - is-glob: "npm:^4.0.1" - semver: "npm:^7.3.5" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: e9409ec992d9d3c44efd53106bf63a6d5f6041e9dc359d54ea4b5f6787404cf44057cbd3090e9cc45ed4157dc631e398079f954490e50a4608cea1fbf9e69226 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:^5.55.0": - version: 5.62.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 06c975eb5f44b43bd19fadc2e1023c50cf87038fe4c0dd989d4331c67b3ff509b17fa60a3251896668ab4d7322bdc56162a9926971218d2e1a1874d2bef9a52e - languageName: node - linkType: hard - "@typescript-eslint/utils@npm:6.13.1": version: 6.13.1 resolution: "@typescript-eslint/utils@npm:6.13.1" @@ -14453,26 +14428,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/visitor-keys@npm:4.33.0" - dependencies: - "@typescript-eslint/types": "npm:4.33.0" - eslint-visitor-keys: "npm:^2.0.0" - checksum: b28cd3f2c5cd919587d576e0cec8a95ea6c12a2e22dfec59ce70714efad4a7af4d467f9f964dd4ba1a4ad82d4a8c680a6d397e621b9be9d1df89b3a95380813f - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - eslint-visitor-keys: "npm:^3.3.0" - checksum: dc613ab7569df9bbe0b2ca677635eb91839dfb2ca2c6fa47870a5da4f160db0b436f7ec0764362e756d4164e9445d49d5eb1ff0b87f4c058946ae9d8c92eb388 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:6.13.1": version: 6.13.1 resolution: "@typescript-eslint/visitor-keys@npm:6.13.1" @@ -15593,13 +15548,6 @@ __metadata: languageName: node linkType: hard -"any-promise@npm:^1.1.0": - version: 1.3.0 - resolution: "any-promise@npm:1.3.0" - checksum: 6737469ba353b5becf29e4dc3680736b9caa06d300bda6548812a8fee63ae7d336d756f88572fa6b5219aed36698d808fa55f62af3e7e6845c7a1dc77d240edb - languageName: node - linkType: hard - "anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -15610,13 +15558,6 @@ __metadata: languageName: node linkType: hard -"app-module-path@npm:^2.2.0": - version: 2.2.0 - resolution: "app-module-path@npm:2.2.0" - checksum: 9ed8c6ce6247a6b5d556039f29b4610869237bbb5b8f3d905b22bd2d314c30efcc0fb70c2626d7461ecc52ec7edec9908f660d0938d2bea5b8cfc6868a28806f - languageName: node - linkType: hard - "app-root-dir@npm:^1.0.2": version: 1.0.2 resolution: "app-root-dir@npm:1.0.2" @@ -15913,27 +15854,6 @@ __metadata: languageName: node linkType: hard -"ast-module-types@npm:^2.7.1": - version: 2.7.1 - resolution: "ast-module-types@npm:2.7.1" - checksum: f5ff6a8aec1ac93533bb5af6ab3921b7eb9f91032f6fdd7cb08e6917005649dc8d5dcdfc98016b7125450db6028da22ae6990275e0ecd6d1fcfe7f1cf5c83b8a - languageName: node - linkType: hard - -"ast-module-types@npm:^3.0.0": - version: 3.0.0 - resolution: "ast-module-types@npm:3.0.0" - checksum: 9cf2e1c566ff88347762ba177dbe8a8bd3e59f366052d45c0a3b1afde2db2228a1c56f05a6e4d7fbfa219de26ae64831a138ff7533269310b13bb9e3a0e84c9f - languageName: node - linkType: hard - -"ast-module-types@npm:^4.0.0": - version: 4.0.0 - resolution: "ast-module-types@npm:4.0.0" - checksum: 06cf93519ef353bb580d90d87f0a9501f39ef99f79784e5291e59e30764c07b383018c153ff4020e9d1409030e9affd438dfca76c28d38400ba729addba8a54a - languageName: node - linkType: hard - "ast-types@npm:0.15.2": version: 0.15.2 resolution: "ast-types@npm:0.15.2" @@ -17590,7 +17510,7 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 @@ -17694,7 +17614,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.16.0, commander@npm:^2.20.0, commander@npm:^2.20.3, commander@npm:^2.8.1": +"commander@npm:^2.20.0, commander@npm:^2.20.3": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 90c5b6898610cd075984c58c4f88418a4fb44af08c1b1415e9854c03171bec31b336b7f3e4cefe33de994b3f12b03c5e2d638da4316df83593b9e82554e7e95b @@ -17743,7 +17663,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^9.0.0, commander@npm:^9.4.1, commander@npm:^9.5.0": +"commander@npm:^9.0.0, commander@npm:^9.4.1": version: 9.5.0 resolution: "commander@npm:9.5.0" checksum: 41c49b3d0f94a1fbeb0463c85b13f15aa15a9e0b4d5e10a49c0a1d58d4489b549d62262b052ae0aa6cfda53299bee487bfe337825df15e342114dde543f82906 @@ -18831,21 +18751,6 @@ __metadata: languageName: node linkType: hard -"dependency-tree@npm:^9.0.0": - version: 9.0.0 - resolution: "dependency-tree@npm:9.0.0" - dependencies: - commander: "npm:^2.20.3" - debug: "npm:^4.3.1" - filing-cabinet: "npm:^3.0.1" - precinct: "npm:^9.0.0" - typescript: "npm:^4.0.0" - bin: - dependency-tree: bin/cli.js - checksum: 7ffc6009bf6eeea712cead1c18d57f87e2d9c0ca5a3327b12daf368de5350f85c613558ae4274e7b49b23010533746917d84ad75dd4fd4d731d61a2eaaf9e310 - languageName: node - linkType: hard - "dequal@npm:2.0.3, dequal@npm:^2.0.0, dequal@npm:^2.0.2, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" @@ -18926,191 +18831,6 @@ __metadata: languageName: node linkType: hard -"detective-amd@npm:^3.1.0": - version: 3.1.2 - resolution: "detective-amd@npm:3.1.2" - dependencies: - ast-module-types: "npm:^3.0.0" - escodegen: "npm:^2.0.0" - get-amd-module-type: "npm:^3.0.0" - node-source-walk: "npm:^4.2.0" - bin: - detective-amd: bin/cli.js - checksum: 0b71555edad8e85c9a2ae85e2799d5faf2bdfe0de969587c9288ca76e717494678e34f444dffe32ffdd432e85ce50ca7017a5d0441a4855677a45a40c4590c74 - languageName: node - linkType: hard - -"detective-amd@npm:^4.0.1, detective-amd@npm:^4.1.0": - version: 4.2.0 - resolution: "detective-amd@npm:4.2.0" - dependencies: - ast-module-types: "npm:^4.0.0" - escodegen: "npm:^2.0.0" - get-amd-module-type: "npm:^4.1.0" - node-source-walk: "npm:^5.0.1" - bin: - detective-amd: bin/cli.js - checksum: c1e829a3202045796105680c9fe90ac61f63b0ccecc12cc30c7204c9e7ec22a4e2c3e2357719b9346a4e3579eba778cdce9a050e642938e2a4c8b57b091278e4 - languageName: node - linkType: hard - -"detective-cjs@npm:^3.1.1": - version: 3.1.3 - resolution: "detective-cjs@npm:3.1.3" - dependencies: - ast-module-types: "npm:^3.0.0" - node-source-walk: "npm:^4.0.0" - checksum: a691cb4afbbfea59d9aae0ee00752ec1a825a7ef18fc9178b53664975f162f3b537268590def009d9ce1cccfc5bc4f38cf775df08d0872aaacc05d96c72de85a - languageName: node - linkType: hard - -"detective-cjs@npm:^4.0.0, detective-cjs@npm:^4.1.0": - version: 4.1.0 - resolution: "detective-cjs@npm:4.1.0" - dependencies: - ast-module-types: "npm:^4.0.0" - node-source-walk: "npm:^5.0.1" - checksum: 17e40183959e9f377333a9fd03dcf4cbabf1b7a9f588882311066ecaaad68ad16765a7b63ffc096fc91d2a3c14ac044ed1823257c76105c9cb96dfc141a806e2 - languageName: node - linkType: hard - -"detective-es6@npm:^2.2.1": - version: 2.2.2 - resolution: "detective-es6@npm:2.2.2" - dependencies: - node-source-walk: "npm:^4.0.0" - checksum: 9ee9909c089f5dcd1f89eccd347d509197996280ba24e2e08742bbc5ca3eef655ff07b4edfd76b52d6b4376ba03b8ec17d621c9f9c4382a6ba233dc1b1d00d33 - languageName: node - linkType: hard - -"detective-es6@npm:^3.0.0, detective-es6@npm:^3.0.1": - version: 3.0.1 - resolution: "detective-es6@npm:3.0.1" - dependencies: - node-source-walk: "npm:^5.0.0" - checksum: 881a0c16b49504c212e61a521231ebbb4299a6102b178230959c74d2ca22d5f7538dfaf9518d01fb568ff93eadcf61d865d4428c9fed893dd4c91a7f29d515c5 - languageName: node - linkType: hard - -"detective-less@npm:^1.0.2": - version: 1.0.2 - resolution: "detective-less@npm:1.0.2" - dependencies: - debug: "npm:^4.0.0" - gonzales-pe: "npm:^4.2.3" - node-source-walk: "npm:^4.0.0" - checksum: 858936fbad87423bd5d7502ff5fafca023e7c99e4006ed01b31c12c4b5ff8697edce91419798479d857efec68ee8f022fcac64de5530db6a64012be600a2249e - languageName: node - linkType: hard - -"detective-postcss@npm:^4.0.0": - version: 4.0.0 - resolution: "detective-postcss@npm:4.0.0" - dependencies: - debug: "npm:^4.1.1" - is-url: "npm:^1.2.4" - postcss: "npm:^8.1.7" - postcss-values-parser: "npm:^2.0.1" - checksum: 9bd5d35554bf51f255db2640f14745f83c10352ba4c4f499096ee4f21547fd7fab501f3dc4017aa7d3bee3b1b455f1162a8067d1810c62d4b1806c96bd6c238e - languageName: node - linkType: hard - -"detective-postcss@npm:^6.1.0, detective-postcss@npm:^6.1.1": - version: 6.1.3 - resolution: "detective-postcss@npm:6.1.3" - dependencies: - is-url: "npm:^1.2.4" - postcss: "npm:^8.4.23" - postcss-values-parser: "npm:^6.0.2" - checksum: ee6e07fed20ac93a6ba84736b9c586a942a4a6b2df173f963f95ea753380c99e4a606da22b8d9e8407c50e356f3d893a127eb68cf84c97233a209e9fbbadb026 - languageName: node - linkType: hard - -"detective-sass@npm:^3.0.1": - version: 3.0.2 - resolution: "detective-sass@npm:3.0.2" - dependencies: - gonzales-pe: "npm:^4.3.0" - node-source-walk: "npm:^4.0.0" - checksum: 7489e5ae7dbed2eba89855cea21ad32321e8e92bd9f2d3b925e7feec0dd9aa8b4b865296525275938e573a3be9759715490038103cbc970570a1c48c4f2fd23d - languageName: node - linkType: hard - -"detective-sass@npm:^4.0.1, detective-sass@npm:^4.1.1": - version: 4.1.3 - resolution: "detective-sass@npm:4.1.3" - dependencies: - gonzales-pe: "npm:^4.3.0" - node-source-walk: "npm:^5.0.1" - checksum: 91681e90037cc935f38b2867fab2aa5585848491b3a269dfb44b37721146ff83f57a540d964b15db22dc1f232623568bedfd13470ec7363e6111991d4d3fe573 - languageName: node - linkType: hard - -"detective-scss@npm:^2.0.1": - version: 2.0.2 - resolution: "detective-scss@npm:2.0.2" - dependencies: - gonzales-pe: "npm:^4.3.0" - node-source-walk: "npm:^4.0.0" - checksum: 515ff1b8946ec92baead48ef435efe1ea0f33ee1d98a7537dd700f1d06dd192f9ea0971c10343adcb08b561ab296d01c18a1f62d0b63163a8f4c09885a956e1a - languageName: node - linkType: hard - -"detective-scss@npm:^3.0.0, detective-scss@npm:^3.0.1": - version: 3.1.1 - resolution: "detective-scss@npm:3.1.1" - dependencies: - gonzales-pe: "npm:^4.3.0" - node-source-walk: "npm:^5.0.1" - checksum: 3d9c0468216c822c25572e700b9aba1e2e2797d336b6b84fd455d83ce849263324855008d1e58d6ccdf9c7a4f099e31277b99e885407cd19674e0bb10fc458cd - languageName: node - linkType: hard - -"detective-stylus@npm:^1.0.0": - version: 1.0.3 - resolution: "detective-stylus@npm:1.0.3" - checksum: 2723da93545f3a55a2a7eaa76b50712457af3c93c2b003e95d02f4c240d5e5206a5df99209a4f5b54128c11fc4270c2de1d7316b4f7d02b359483ae74f5a6637 - languageName: node - linkType: hard - -"detective-stylus@npm:^2.0.1": - version: 2.0.1 - resolution: "detective-stylus@npm:2.0.1" - checksum: c701ba6df3e6b5346aa5dd37b8329a9069a20fd7d075933e2e3b819a75922a2adab809143591151e7337183d59c980e6bc64ad6e51ce96de864575221c1b9506 - languageName: node - linkType: hard - -"detective-stylus@npm:^3.0.0": - version: 3.0.0 - resolution: "detective-stylus@npm:3.0.0" - checksum: e82eda490406d289f7b22050423ad69eb1c0f0d88414adaa292de4ab533be3c50d4cf512a9fefba426f3ad20789f0c0db3b0d32f70162112ca89034bbc5ca9d3 - languageName: node - linkType: hard - -"detective-typescript@npm:^7.0.0": - version: 7.0.2 - resolution: "detective-typescript@npm:7.0.2" - dependencies: - "@typescript-eslint/typescript-estree": "npm:^4.33.0" - ast-module-types: "npm:^2.7.1" - node-source-walk: "npm:^4.2.0" - typescript: "npm:^3.9.10" - checksum: 77703410baa242029dc5e7d02cca7a26278dea498ec1c3320f92efa08a85263affc3b102fc2b09952ece1d2c851a3808733d7bfa9ed11944a7c0f39920e33ec9 - languageName: node - linkType: hard - -"detective-typescript@npm:^9.0.0, detective-typescript@npm:^9.1.1": - version: 9.1.1 - resolution: "detective-typescript@npm:9.1.1" - dependencies: - "@typescript-eslint/typescript-estree": "npm:^5.55.0" - ast-module-types: "npm:^4.0.0" - node-source-walk: "npm:^5.0.1" - typescript: "npm:^4.9.5" - checksum: 5f50801f622740d4e9d724ce04518ceb81591215bf18c18c5d22f6f3948df49dfb0a8bbe3596dac47220a37028bc2879ccd7a968f265217c9855817bda4622f5 - languageName: node - linkType: hard - "devlop@npm:^1.0.0, devlop@npm:^1.1.0": version: 1.1.0 resolution: "devlop@npm:1.1.0" @@ -19762,7 +19482,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.8.3": +"enhanced-resolve@npm:^5.15.0": version: 5.15.0 resolution: "enhanced-resolve@npm:5.15.0" dependencies: @@ -20252,7 +19972,7 @@ __metadata: languageName: node linkType: hard -"escodegen@npm:^2.0.0, escodegen@npm:^2.1.0": +"escodegen@npm:^2.1.0": version: 2.1.0 resolution: "escodegen@npm:2.1.0" dependencies: @@ -20459,13 +20179,6 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^2.0.0": - version: 2.1.0 - resolution: "eslint-visitor-keys@npm:2.1.0" - checksum: db4547eef5039122d518fa307e938ceb8589da5f6e8f5222efaf14dd62f748ce82e2d2becd3ff9412a50350b726bda95dbea8515a471074547daefa58aee8735 - languageName: node - linkType: hard - "eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" @@ -21171,29 +20884,6 @@ __metadata: languageName: node linkType: hard -"filing-cabinet@npm:^3.0.1": - version: 3.3.1 - resolution: "filing-cabinet@npm:3.3.1" - dependencies: - app-module-path: "npm:^2.2.0" - commander: "npm:^2.20.3" - debug: "npm:^4.3.3" - enhanced-resolve: "npm:^5.8.3" - is-relative-path: "npm:^1.0.2" - module-definition: "npm:^3.3.1" - module-lookup-amd: "npm:^7.0.1" - resolve: "npm:^1.21.0" - resolve-dependency-path: "npm:^2.0.0" - sass-lookup: "npm:^3.0.0" - stylus-lookup: "npm:^3.0.1" - tsconfig-paths: "npm:^3.10.1" - typescript: "npm:^3.9.7" - bin: - filing-cabinet: bin/cli.js - checksum: c4e71efffa1714e2fe862fa7741446a6307ce03a27f01faed3cc4969c9fc395dddd4d1e095569ce0bf6b6c95826f2665883465971f3346ba179f815b888e8151 - languageName: node - linkType: hard - "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -21361,13 +21051,6 @@ __metadata: languageName: node linkType: hard -"flatten@npm:^1.0.2": - version: 1.0.3 - resolution: "flatten@npm:1.0.3" - checksum: 5c57379816f1692aaa79fbc6390e0a0644e5e8442c5783ed57c6d315468eddbc53a659eaa03c9bb1e771b0f4a9bd8dd8a2620286bf21fd6538a7857321fdfb20 - languageName: node - linkType: hard - "flexsearch@npm:0.7.21": version: 0.7.21 resolution: "flexsearch@npm:0.7.21" @@ -21836,26 +21519,6 @@ __metadata: languageName: node linkType: hard -"get-amd-module-type@npm:^3.0.0": - version: 3.0.2 - resolution: "get-amd-module-type@npm:3.0.2" - dependencies: - ast-module-types: "npm:^3.0.0" - node-source-walk: "npm:^4.2.2" - checksum: dd41d3d966677ee03b2f51ffe0e9cd1e37df4757ec490c31d67840042d6dc3173c019ec82d03ed12282338239589b3380dac6c59508b4f4ab802f28f1c9c8a8d - languageName: node - linkType: hard - -"get-amd-module-type@npm:^4.1.0": - version: 4.1.0 - resolution: "get-amd-module-type@npm:4.1.0" - dependencies: - ast-module-types: "npm:^4.0.0" - node-source-walk: "npm:^5.0.1" - checksum: b12fe94eeee65df44010d7046f5435661d220b1f8132fbc11e525f2eab4ef2d9e6114c439e9905621f71449e5e43f588f21d29243a53bef36bcfcf5252481b6d - languageName: node - linkType: hard - "get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -21924,13 +21587,6 @@ __metadata: languageName: node linkType: hard -"get-own-enumerable-property-symbols@npm:^3.0.0": - version: 3.0.2 - resolution: "get-own-enumerable-property-symbols@npm:3.0.2" - checksum: 8f0331f14159f939830884799f937343c8c0a2c330506094bc12cbee3665d88337fe97a4ea35c002cc2bdba0f5d9975ad7ec3abb925015cdf2a93e76d4759ede - languageName: node - linkType: hard - "get-package-info@npm:^1.0.0": version: 1.0.0 resolution: "get-package-info@npm:1.0.0" @@ -22293,17 +21949,6 @@ __metadata: languageName: node linkType: hard -"gonzales-pe@npm:^4.2.3, gonzales-pe@npm:^4.3.0": - version: 4.3.0 - resolution: "gonzales-pe@npm:4.3.0" - dependencies: - minimist: "npm:^1.2.5" - bin: - gonzales: bin/gonzales.js - checksum: d1676546bcaa4cb1c6c1fc5de5d62e85960665a13a4c489b02baeb58a10c53a249beef05ceaf21ea801813a559ff17d7b61158aa417211c135bcb8bdcb1701ca - languageName: node - linkType: hard - "google-auth-library@npm:^7.0.0, google-auth-library@npm:^7.0.2, google-auth-library@npm:^7.14.0": version: 7.14.1 resolution: "google-auth-library@npm:7.14.1" @@ -23270,13 +22915,6 @@ __metadata: languageName: node linkType: hard -"indexes-of@npm:^1.0.1": - version: 1.0.1 - resolution: "indexes-of@npm:1.0.1" - checksum: 4f9799b1739a62f3e02d09f6f4162cf9673025282af7fa36e790146e7f4e216dad3e776a25b08536c093209c9fcb5ea7bd04b082d42686a45f58ff401d6da32e - languageName: node - linkType: hard - "infer-owner@npm:^1.0.4": version: 1.0.4 resolution: "infer-owner@npm:1.0.4" @@ -23657,13 +23295,6 @@ __metadata: languageName: node linkType: hard -"is-obj@npm:^1.0.1": - version: 1.0.1 - resolution: "is-obj@npm:1.0.1" - checksum: 3ccf0efdea12951e0b9c784e2b00e77e87b2f8bd30b42a498548a8afcc11b3287342a2030c308e473e93a7a19c9ea7854c99a8832a476591c727df2a9c79796c - languageName: node - linkType: hard - "is-obj@npm:^2.0.0": version: 2.0.0 resolution: "is-obj@npm:2.0.0" @@ -23743,20 +23374,6 @@ __metadata: languageName: node linkType: hard -"is-regexp@npm:^1.0.0": - version: 1.0.0 - resolution: "is-regexp@npm:1.0.0" - checksum: be692828e24cba479ec33644326fa98959ec68ba77965e0291088c1a741feaea4919d79f8031708f85fd25e39de002b4520622b55460660b9c369e6f7187faef - languageName: node - linkType: hard - -"is-relative-path@npm:^1.0.2": - version: 1.0.2 - resolution: "is-relative-path@npm:1.0.2" - checksum: 6c2ccffd3c0a3e3990535d0571370883d558b825b51940085f3446ec338857f4552f44521dfec3e83b7e067de08c0b0369de290208a91200bcae2c569533e340 - languageName: node - linkType: hard - "is-relative@npm:^1.0.0": version: 1.0.0 resolution: "is-relative@npm:1.0.0" @@ -23860,20 +23477,6 @@ __metadata: languageName: node linkType: hard -"is-url-superb@npm:^4.0.0": - version: 4.0.0 - resolution: "is-url-superb@npm:4.0.0" - checksum: fd55e91c96349acb0d688f95fcb1ac67450e5db934976e3a8ff13ef446841e779a6f4d18b15f02331f05a3429c8fdaba2382ac1ab444059e86e9ffcde1ec8db0 - languageName: node - linkType: hard - -"is-url@npm:^1.2.4": - version: 1.2.4 - resolution: "is-url@npm:1.2.4" - checksum: 100e74b3b1feab87a43ef7653736e88d997eb7bd32e71fd3ebc413e58c1cbe56269699c776aaea84244b0567f2a7d68dfaa512a062293ed2f9fdecb394148432 - languageName: node - linkType: hard - "is-windows@npm:^0.2.0": version: 0.2.0 resolution: "is-windows@npm:0.2.0" @@ -24978,17 +24581,6 @@ __metadata: languageName: node linkType: hard -"json5@npm:^1.0.2": - version: 1.0.2 - resolution: "json5@npm:1.0.2" - dependencies: - minimist: "npm:^1.2.0" - bin: - json5: lib/cli.js - checksum: a78d812dbbd5642c4f637dd130954acfd231b074965871c3e28a5bbd571f099d623ecf9161f1960c4ddf68e0cc98dee8bebfdb94a71ad4551f85a1afc94b63f6 - languageName: node - linkType: hard - "json5@npm:^2.1.2, json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -26184,43 +25776,6 @@ __metadata: languageName: node linkType: hard -"madge@npm:^6.1.0": - version: 6.1.0 - resolution: "madge@npm:6.1.0" - dependencies: - chalk: "npm:^4.1.1" - commander: "npm:^7.2.0" - commondir: "npm:^1.0.1" - debug: "npm:^4.3.1" - dependency-tree: "npm:^9.0.0" - detective-amd: "npm:^4.0.1" - detective-cjs: "npm:^4.0.0" - detective-es6: "npm:^3.0.0" - detective-less: "npm:^1.0.2" - detective-postcss: "npm:^6.1.0" - detective-sass: "npm:^4.0.1" - detective-scss: "npm:^3.0.0" - detective-stylus: "npm:^2.0.1" - detective-typescript: "npm:^9.0.0" - ora: "npm:^5.4.1" - pluralize: "npm:^8.0.0" - precinct: "npm:^8.1.0" - pretty-ms: "npm:^7.0.1" - rc: "npm:^1.2.7" - stream-to-array: "npm:^2.3.0" - ts-graphviz: "npm:^1.5.0" - walkdir: "npm:^0.4.1" - peerDependencies: - typescript: ^3.9.5 || ^4.9.5 || ^5 - peerDependenciesMeta: - typescript: - optional: true - bin: - madge: bin/cli.js - checksum: 80d3d549135e302109a5b98ab6cb088b44579779067365008368a042578bca0dfb1a76c697a1e9f63d5d2037724628e66b9dbd3efb0126555dd9d626a865d6d0 - languageName: node - linkType: hard - "magic-string@npm:0.27.0, magic-string@npm:^0.27.0": version: 0.27.0 resolution: "magic-string@npm:0.27.0" @@ -27525,30 +27080,6 @@ __metadata: languageName: node linkType: hard -"module-definition@npm:^3.3.1": - version: 3.4.0 - resolution: "module-definition@npm:3.4.0" - dependencies: - ast-module-types: "npm:^3.0.0" - node-source-walk: "npm:^4.0.0" - bin: - module-definition: bin/cli.js - checksum: 5cbfd38aab1a9169b5c31924e208e430a87a1b1512ab9736a9a368d950e3cc8e2f5cf642e37fe74123e25402cae50bfb8fdf1f5f0fd3d4d9270df705a2360bfa - languageName: node - linkType: hard - -"module-definition@npm:^4.1.0": - version: 4.1.0 - resolution: "module-definition@npm:4.1.0" - dependencies: - ast-module-types: "npm:^4.0.0" - node-source-walk: "npm:^5.0.1" - bin: - module-definition: bin/cli.js - checksum: d9b6397c9ba04b08bc035fd87a3652900530b9a5d6e5263f8a1e05c927dfc103fdffcecd7071a9fd6cd7813fc9feafbbe828f5277e5b706e5de82831153ef0fb - languageName: node - linkType: hard - "module-details-from-path@npm:^1.0.3": version: 1.0.3 resolution: "module-details-from-path@npm:1.0.3" @@ -27556,21 +27087,6 @@ __metadata: languageName: node linkType: hard -"module-lookup-amd@npm:^7.0.1": - version: 7.0.1 - resolution: "module-lookup-amd@npm:7.0.1" - dependencies: - commander: "npm:^2.8.1" - debug: "npm:^4.1.0" - glob: "npm:^7.1.6" - requirejs: "npm:^2.3.5" - requirejs-config-file: "npm:^4.0.0" - bin: - lookup-amd: bin/cli.js - checksum: dadf8c602dd970e211136b22271231a853f76ec0b857a3bfa765e52a1f45f313a8e8bc7e1961f96bb1eced14e557cdbc798b37f5c17f0235cf29155617f4e7a9 - languageName: node - linkType: hard - "mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" @@ -28048,24 +27564,6 @@ __metadata: languageName: node linkType: hard -"node-source-walk@npm:^4.0.0, node-source-walk@npm:^4.2.0, node-source-walk@npm:^4.2.2": - version: 4.3.0 - resolution: "node-source-walk@npm:4.3.0" - dependencies: - "@babel/parser": "npm:^7.0.0" - checksum: 3145f4b907624b42bf7847d19937e33a1c3946b960ebcf5ac7eded9ba187ae00edec143946ef4a8f04835c31688f06e8341e0a7b4962a41d4d56bd5ab0a83ed5 - languageName: node - linkType: hard - -"node-source-walk@npm:^5.0.0, node-source-walk@npm:^5.0.1": - version: 5.0.2 - resolution: "node-source-walk@npm:5.0.2" - dependencies: - "@babel/parser": "npm:^7.21.4" - checksum: cbb474a80a7c4576ed94bf4c999be889a85ad1177a0a80019714adece1f5ca70e6a5f728b6828afb4ea00ca524c3ea65fc24d7405b921b754bfb596553f78e7b - languageName: node - linkType: hard - "nodemailer@npm:^6.9.7": version: 6.9.7 resolution: "nodemailer@npm:6.9.7" @@ -28695,6 +28193,35 @@ __metadata: languageName: node linkType: hard +"oxlint@npm:^0.0.18": + version: 0.0.18 + resolution: "oxlint@npm:0.0.18" + dependencies: + "@oxlint/darwin-arm64": "npm:0.0.18" + "@oxlint/darwin-x64": "npm:0.0.18" + "@oxlint/linux-arm64": "npm:0.0.18" + "@oxlint/linux-x64": "npm:0.0.18" + "@oxlint/win32-arm64": "npm:0.0.18" + "@oxlint/win32-x64": "npm:0.0.18" + dependenciesMeta: + "@oxlint/darwin-arm64": + optional: true + "@oxlint/darwin-x64": + optional: true + "@oxlint/linux-arm64": + optional: true + "@oxlint/linux-x64": + optional: true + "@oxlint/win32-arm64": + optional: true + "@oxlint/win32-x64": + optional: true + bin: + oxlint: bin/oxlint + checksum: c372394c27856e58f2a622dbcef45e72a9df10f6ba2a1b882732356b8982a0bcdf875019b28211d480ac07e4e77bb2870621c23c595addf74e82ee603e00acc0 + languageName: node + linkType: hard + "p-cancelable@npm:^2.0.0": version: 2.1.1 resolution: "p-cancelable@npm:2.1.1" @@ -29016,13 +28543,6 @@ __metadata: languageName: node linkType: hard -"parse-ms@npm:^2.1.0": - version: 2.1.0 - resolution: "parse-ms@npm:2.1.0" - checksum: 517eab80cdb9df6ae22a8fad944bfb4289482699bcde5211a1c127091dfea33c3dcb217246b188865fc32e998bcee815bfa4a863f41e3b2d0bcc69f34ef1a543 - languageName: node - linkType: hard - "parse-ms@npm:^3.0.0": version: 3.0.0 resolution: "parse-ms@npm:3.0.0" @@ -29851,31 +29371,7 @@ __metadata: languageName: node linkType: hard -"postcss-values-parser@npm:^2.0.1": - version: 2.0.1 - resolution: "postcss-values-parser@npm:2.0.1" - dependencies: - flatten: "npm:^1.0.2" - indexes-of: "npm:^1.0.1" - uniq: "npm:^1.0.1" - checksum: f69211656b90a9d2f5efb1ec4a9e5f076f968b0c1169bfddc16aa0dce2e3a2853d7875e4d8058652e9b65d084fab93bfee8da389f6a2145ac547ee9ab0a03456 - languageName: node - linkType: hard - -"postcss-values-parser@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-values-parser@npm:6.0.2" - dependencies: - color-name: "npm:^1.1.4" - is-url-superb: "npm:^4.0.0" - quote-unquote: "npm:^1.0.0" - peerDependencies: - postcss: ^8.2.9 - checksum: ff2fa096896f1c33f7531e814b8d01e785bd99d672c1597d5c5d8c2409b30b8146be6565f6269c952d1f03d626f00ae3f1afb8308cc772c08b323abee23c9a42 - languageName: node - linkType: hard - -"postcss@npm:^8.1.7, postcss@npm:^8.3.6, postcss@npm:^8.4.21, postcss@npm:^8.4.23, postcss@npm:^8.4.27, postcss@npm:^8.4.31": +"postcss@npm:^8.3.6, postcss@npm:^8.4.21, postcss@npm:^8.4.27, postcss@npm:^8.4.31": version: 8.4.31 resolution: "postcss@npm:8.4.31" dependencies: @@ -29922,51 +29418,6 @@ __metadata: languageName: node linkType: hard -"precinct@npm:^8.1.0": - version: 8.3.1 - resolution: "precinct@npm:8.3.1" - dependencies: - commander: "npm:^2.20.3" - debug: "npm:^4.3.3" - detective-amd: "npm:^3.1.0" - detective-cjs: "npm:^3.1.1" - detective-es6: "npm:^2.2.1" - detective-less: "npm:^1.0.2" - detective-postcss: "npm:^4.0.0" - detective-sass: "npm:^3.0.1" - detective-scss: "npm:^2.0.1" - detective-stylus: "npm:^1.0.0" - detective-typescript: "npm:^7.0.0" - module-definition: "npm:^3.3.1" - node-source-walk: "npm:^4.2.0" - bin: - precinct: bin/cli.js - checksum: 46ec2cd2e8af5386b1b0bf47fb264b288be39895dc6e33b9537d795d57c7b0a97f349c217edfa0bf584fef1031b4b0a5b30d1ad74fbe9833644adad6e325bc9e - languageName: node - linkType: hard - -"precinct@npm:^9.0.0": - version: 9.2.1 - resolution: "precinct@npm:9.2.1" - dependencies: - "@dependents/detective-less": "npm:^3.0.1" - commander: "npm:^9.5.0" - detective-amd: "npm:^4.1.0" - detective-cjs: "npm:^4.1.0" - detective-es6: "npm:^3.0.1" - detective-postcss: "npm:^6.1.1" - detective-sass: "npm:^4.1.1" - detective-scss: "npm:^3.0.1" - detective-stylus: "npm:^3.0.0" - detective-typescript: "npm:^9.1.1" - module-definition: "npm:^4.1.0" - node-source-walk: "npm:^5.0.1" - bin: - precinct: bin/cli.js - checksum: f208d2e3c5d7c7b3834f70f761d0255680f76268d3835cd498006bc5dafa602aaecda0b2e68868ed679ec330fddaee0b662ba862d6eacd7282760dc0bc5f1b35 - languageName: node - linkType: hard - "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -30050,15 +29501,6 @@ __metadata: languageName: node linkType: hard -"pretty-ms@npm:^7.0.1": - version: 7.0.1 - resolution: "pretty-ms@npm:7.0.1" - dependencies: - parse-ms: "npm:^2.1.0" - checksum: a39aac23cc7dae7a94c70518ab8b6c6db0894a7b84c81ee7abc8778c5ec8bae2d1e71ba991ff641732b38433724bfbdbb37bd3a00418637f797c072e06fe8b4c - languageName: node - linkType: hard - "pretty-ms@npm:^8.0.0": version: 8.0.0 resolution: "pretty-ms@npm:8.0.0" @@ -30421,13 +29863,6 @@ __metadata: languageName: node linkType: hard -"quote-unquote@npm:^1.0.0": - version: 1.0.0 - resolution: "quote-unquote@npm:1.0.0" - checksum: 955a2ead534f5b6a3f8d4dc5a4b95ac6468213d3fb11f8c1592a0a56345c45a3d14d5ca04d3de2bc9891493fcac38c03dfa91c48a6159aef50124e9c5afcea49 - languageName: node - linkType: hard - "ramda@npm:0.29.0": version: 0.29.0 resolution: "ramda@npm:0.29.0" @@ -30504,7 +29939,7 @@ __metadata: languageName: node linkType: hard -"rc@npm:^1.0.1, rc@npm:^1.1.6, rc@npm:^1.2.7": +"rc@npm:^1.0.1, rc@npm:^1.1.6": version: 1.2.8 resolution: "rc@npm:1.2.8" dependencies: @@ -31389,26 +30824,6 @@ __metadata: languageName: node linkType: hard -"requirejs-config-file@npm:^4.0.0": - version: 4.0.0 - resolution: "requirejs-config-file@npm:4.0.0" - dependencies: - esprima: "npm:^4.0.0" - stringify-object: "npm:^3.2.1" - checksum: 9fc55813ca8627cc4d3704622702b6b119a222c97825fc61459727d5f9ef9435e4fd34ee03335dc119c66e834d7963fdfe43d9a4df99448d43219587c624ba4e - languageName: node - linkType: hard - -"requirejs@npm:^2.3.5": - version: 2.3.6 - resolution: "requirejs@npm:2.3.6" - bin: - r.js: ./bin/r.js - r_js: ./bin/r.js - checksum: 808540b0a2374cf19bf00d13036a90f94aac92984a9be0f1fa642266d0ee467db0b1aa9d85d567e0da71b54294f5feb92e13ac62bae7f85a0b3ac3ab393b05d4 - languageName: node - linkType: hard - "requires-port@npm:^1.0.0": version: 1.0.0 resolution: "requires-port@npm:1.0.0" @@ -31432,13 +30847,6 @@ __metadata: languageName: node linkType: hard -"resolve-dependency-path@npm:^2.0.0": - version: 2.0.0 - resolution: "resolve-dependency-path@npm:2.0.0" - checksum: f7511412546276473bbe0182730baf9bda08a437425f817b1d944ecac4c0a137129c544bf60ca5928b1b89f7124855408c257a12e1b604ebbffeb997bbb81a65 - languageName: node - linkType: hard - "resolve-dir@npm:^0.1.0": version: 0.1.1 resolution: "resolve-dir@npm:0.1.1" @@ -31505,7 +30913,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.21.0, resolve@npm:^1.22.1, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:~1.22.1": +"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:~1.22.1": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -31541,7 +30949,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.12.0#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.21.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin": +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.12.0#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -31806,17 +31214,6 @@ __metadata: languageName: node linkType: hard -"sass-lookup@npm:^3.0.0": - version: 3.0.0 - resolution: "sass-lookup@npm:3.0.0" - dependencies: - commander: "npm:^2.16.0" - bin: - sass-lookup: bin/cli.js - checksum: 010b6ae2ee42554ef567a433a1febb9979e91ea99bfacc514e886cf966adba76cd075126d4eac3d557377c30ce2477825bd3d9b59a326571f295c99373a342da - languageName: node - linkType: hard - "sax@npm:^1.2.4": version: 1.3.0 resolution: "sax@npm:1.3.0" @@ -32832,15 +32229,6 @@ __metadata: languageName: node linkType: hard -"stream-to-array@npm:^2.3.0": - version: 2.3.0 - resolution: "stream-to-array@npm:2.3.0" - dependencies: - any-promise: "npm:^1.1.0" - checksum: 7feaf63b38399b850615e6ffcaa951e96e4c8f46745dbce4b553a94c5dc43966933813747014935a3ff97793e7f30a65270bde19f82b2932871a1879229a77cf - languageName: node - linkType: hard - "streamsearch@npm:^1.1.0": version: 1.1.0 resolution: "streamsearch@npm:1.1.0" @@ -32963,17 +32351,6 @@ __metadata: languageName: node linkType: hard -"stringify-object@npm:^3.2.1": - version: 3.3.0 - resolution: "stringify-object@npm:3.3.0" - dependencies: - get-own-enumerable-property-symbols: "npm:^3.0.0" - is-obj: "npm:^1.0.1" - is-regexp: "npm:^1.0.0" - checksum: 973782f09a3df3f39a2cf07dbf43fb9ba6cb32976f3616cd0f6c10e0a5c5415dd72b7b700e72920e8da2bf57c3001b8e37b5af7174bab9a748ce0416989e19b1 - languageName: node - linkType: hard - "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -33155,18 +32532,6 @@ __metadata: languageName: node linkType: hard -"stylus-lookup@npm:^3.0.1": - version: 3.0.2 - resolution: "stylus-lookup@npm:3.0.2" - dependencies: - commander: "npm:^2.8.1" - debug: "npm:^4.1.0" - bin: - stylus-lookup: bin/cli.js - checksum: 94da8b81ef16f73eae0dc734b18165e6f84d41960bc85fb39d9b028f4b6d8c4c2983ce52492751a3eb2e8e65e3e1507adc6739cc3c7893754672546c6af22f62 - languageName: node - linkType: hard - "subscriptions-transport-ws@npm:0.11.0": version: 0.11.0 resolution: "subscriptions-transport-ws@npm:0.11.0" @@ -33879,13 +33244,6 @@ __metadata: languageName: node linkType: hard -"ts-graphviz@npm:^1.5.0": - version: 1.8.1 - resolution: "ts-graphviz@npm:1.8.1" - checksum: 4936292e2b6104363b2d37f995fea766f8aefdabaecae10c963785e0c0b09da35bce78f961ac48897278f4f2148c2d71c7eeafaedcf273d8af06971ae5a01abc - languageName: node - linkType: hard - "ts-log@npm:^2.2.3": version: 2.2.5 resolution: "ts-log@npm:2.2.5" @@ -33945,18 +33303,6 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths@npm:^3.10.1": - version: 3.14.2 - resolution: "tsconfig-paths@npm:3.14.2" - dependencies: - "@types/json5": "npm:^0.0.29" - json5: "npm:^1.0.2" - minimist: "npm:^1.2.6" - strip-bom: "npm:^3.0.0" - checksum: 17f23e98612a60cf23b80dc1d3b7b840879e41fcf603868fc3618a30f061ac7b463ef98cad8c28b68733b9bfe0cc40ffa2bcf29e94cf0d26e4f6addf7ac8527d - languageName: node - linkType: hard - "tsconfig-paths@npm:^4.1.2": version: 4.2.0 resolution: "tsconfig-paths@npm:4.2.0" @@ -33975,7 +33321,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.11.1, tslib@npm:^1.13.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0": +"tslib@npm:^1.11.1, tslib@npm:^1.13.0, tslib@npm:^1.9.0": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb @@ -33989,17 +33335,6 @@ __metadata: languageName: node linkType: hard -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: ea036bec1dd024e309939ffd49fda7a351c0e87a1b8eb049570dd119d447250e2c56e0e6c00554e8205760e7417793fdebff752a46e573fbe07d4f375502a5b2 - languageName: node - linkType: hard - "typanion@npm:^3.8.0": version: 3.14.0 resolution: "typanion@npm:3.14.0" @@ -34119,26 +33454,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^3.9.10, typescript@npm:^3.9.7": - version: 3.9.10 - resolution: "typescript@npm:3.9.10" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: f86a085eea24fdd665850c6be4dd69c7a71fe3d27ecc712be88cdc7a52d866f4f2416ad91c554df60f499dedcf288a24b3d8052e502833d8acc661a9d21bb98f - languageName: node - linkType: hard - -"typescript@npm:^4.0.0, typescript@npm:^4.9.5": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 458f7220ab11e0fc191514cc41be1707645ec9a8c2d609448a448e18c522cef9646f58728f6811185a4c35613dacdf6c98cf8965c88b3541d0288c47291e4300 - languageName: node - linkType: hard - "typescript@npm:^5.2.2, typescript@npm:^5.3.2": version: 5.3.2 resolution: "typescript@npm:5.3.2" @@ -34159,26 +33474,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^3.9.10#optional!builtin, typescript@patch:typescript@npm%3A^3.9.7#optional!builtin": - version: 3.9.10 - resolution: "typescript@patch:typescript@npm%3A3.9.10#optional!builtin::version=3.9.10&hash=3bd3d3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 77024c200e1f80d95185d5a973bcc4b0ecc12a77851390b1382e372195313769373a4b8b1642df4a8d4ae1df5911dcd9bde111cf6755eb4b6091f374e97c6dc5 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^4.0.0#optional!builtin, typescript@patch:typescript@npm%3A^4.9.5#optional!builtin": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 5659316360b5cc2d6f5931b346401fa534107b68b60179cf14970e27978f0936c1d5c46f4b5b8175f8cba0430f522b3ce355b4b724c0ea36ce6c0347fab25afd - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A^5.3.2#optional!builtin": version: 5.3.2 resolution: "typescript@patch:typescript@npm%3A5.3.2#optional!builtin::version=5.3.2&hash=29ae49" @@ -34314,13 +33609,6 @@ __metadata: languageName: node linkType: hard -"uniq@npm:^1.0.1": - version: 1.0.1 - resolution: "uniq@npm:1.0.1" - checksum: 8206535f83745ea83f9da7035f3b983fd6ed5e35b8ed7745441944e4065b616bc67cf0d0a23a86b40ee0074426f0607f0a138f9b78e124eb6a7a6a6966055709 - languageName: node - linkType: hard - "unique-filename@npm:^2.0.0": version: 2.0.1 resolution: "unique-filename@npm:2.0.1" @@ -35222,7 +34510,7 @@ __metadata: languageName: node linkType: hard -"walkdir@npm:^0.4.0, walkdir@npm:^0.4.1": +"walkdir@npm:^0.4.0": version: 0.4.1 resolution: "walkdir@npm:0.4.1" checksum: 54cbe7afc5fb811a55748b0bfa077a9a4aa43f568eb5857db9785af9728e1ad8b1ecf6b9ce6f14b405c6124939a92522e36aaa0397f3f52a9a7a08496f2eebe1 From 923844f3024ebb5c36e0e30a82bca502284dd009 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:31 +0000 Subject: [PATCH 09/35] fix: add eqeqeq lint rule (#5106) --- .eslintrc.js | 1 + packages/backend/server/src/modules/doc/manager.ts | 2 +- packages/common/infra/src/blocksuite/migration/subdoc.ts | 2 +- .../src/components/page-list/filter/logical/typesystem.ts | 2 +- .../component/src/components/page-list/filter/vars.tsx | 4 ++-- .../frontend/component/src/components/page-list/utils.tsx | 6 +++--- .../setting-modal/general-setting/plans/plan-card.tsx | 2 +- .../workspace-slider-bar/collections/collections-list.tsx | 2 +- packages/frontend/core/src/layouts/workspace-layout.tsx | 3 ++- .../electron/src/main/updater/custom-github-provider.ts | 4 ++-- packages/frontend/workspace/src/providers/sync/engine.ts | 4 ++-- tests/affine-local/e2e/quick-search.spec.ts | 2 +- 12 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0782ae8893..0c55bc1e06 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -127,6 +127,7 @@ const config = { 'no-constant-binary-expression': 'error', 'no-constructor-return': 'error', 'no-self-compare': 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], 'react/prop-types': 'off', 'react/jsx-no-useless-fragment': 'error', '@typescript-eslint/consistent-type-imports': 'error', diff --git a/packages/backend/server/src/modules/doc/manager.ts b/packages/backend/server/src/modules/doc/manager.ts index e531f6057e..a31bc1a841 100644 --- a/packages/backend/server/src/modules/doc/manager.ts +++ b/packages/backend/server/src/modules/doc/manager.ts @@ -42,7 +42,7 @@ function compare(yBinary: Buffer, jwstBinary: Buffer, strict = false): boolean { function isEmptyBuffer(buf: Buffer): boolean { return ( - buf.length == 0 || + buf.length === 0 || // 0x0000 (buf.length === 2 && buf[0] === 0 && buf[1] === 0) ); diff --git a/packages/common/infra/src/blocksuite/migration/subdoc.ts b/packages/common/infra/src/blocksuite/migration/subdoc.ts index c8acf278cb..da77756cc2 100644 --- a/packages/common/infra/src/blocksuite/migration/subdoc.ts +++ b/packages/common/infra/src/blocksuite/migration/subdoc.ts @@ -50,7 +50,7 @@ function migrateDatabase(data: YMap) { update: (cell: { id: string; value: unknown }) => void ) => { Object.values(cells).forEach(row => { - if (row[id] != null) { + if (row[id] !== null && row[id] !== undefined) { update(row[id]); } }); diff --git a/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts b/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts index 4b0e0a8533..5458e02e2b 100644 --- a/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts +++ b/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts @@ -195,7 +195,7 @@ export class Typesystem { ): boolean { if (superType.type === 'typeRef') { // TODO both are ref - if (context && sub.type != 'typeRef') { + if (context && sub.type !== 'typeRef') { context[superType.name] = sub; } // TODO bound diff --git a/packages/frontend/component/src/components/page-list/filter/vars.tsx b/packages/frontend/component/src/components/page-list/filter/vars.tsx index 9b3c5fc2e9..bf4af661af 100644 --- a/packages/frontend/component/src/components/page-list/filter/vars.tsx +++ b/packages/frontend/component/src/components/page-list/filter/vars.tsx @@ -141,7 +141,7 @@ filterMatcher.register( name: 'is', defaultArgs: () => [true], impl: (value, target) => { - return value == target; + return value === target; }, } ); @@ -209,7 +209,7 @@ filterMatcher.register( defaultArgs: () => [], impl: tags => { const safeTags = safeArray(tags); - return safeTags.length == 0; + return safeTags.length === 0; }, } ); diff --git a/packages/frontend/component/src/components/page-list/utils.tsx b/packages/frontend/component/src/components/page-list/utils.tsx index 8acf0002eb..1c33f2fd1f 100644 --- a/packages/frontend/component/src/components/page-list/utils.tsx +++ b/packages/frontend/component/src/components/page-list/utils.tsx @@ -10,9 +10,9 @@ import * as styles from './page-list.css'; export function isToday(date: Date): boolean { const today = new Date(); return ( - date.getDate() == today.getDate() && - date.getMonth() == today.getMonth() && - date.getFullYear() == today.getFullYear() + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() ); } diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx index 16074b71dc..f8f415ee69 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx @@ -179,7 +179,7 @@ export const PlanCard = (props: PlanCardProps) => { {detail.benefits.map((content, i) => (
- {detail.type == 'dynamic' ? ( + {detail.type === 'dynamic' ? ( ) : ( { await setting.updateCollection({ ...collection, - allowList: collection.allowList?.filter(v => v != id), + allowList: collection.allowList?.filter(v => v !== id), }); }, [collection, setting] diff --git a/packages/frontend/core/src/layouts/workspace-layout.tsx b/packages/frontend/core/src/layouts/workspace-layout.tsx index 12bbf066a6..3c4a8d3498 100644 --- a/packages/frontend/core/src/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/layouts/workspace-layout.tsx @@ -194,7 +194,8 @@ export const WorkspaceLayoutInner = ({ const blockVersions = meta.get('blockVersions'); if ( !(blockVersions instanceof YMap) && - blockVersions != null && + blockVersions !== null && + blockVersions !== undefined && typeof blockVersions === 'object' ) { meta.set( diff --git a/packages/frontend/electron/src/main/updater/custom-github-provider.ts b/packages/frontend/electron/src/main/updater/custom-github-provider.ts index 55bf60102f..9615b3c550 100644 --- a/packages/frontend/electron/src/main/updater/custom-github-provider.ts +++ b/packages/frontend/electron/src/main/updater/custom-github-provider.ts @@ -105,7 +105,7 @@ export class CustomGitHubProvider extends BaseGitHubProvider { ); } - if (tag == null) { + if (tag === null || tag === undefined) { throw newError( `No published versions on GitHub`, 'ERR_UPDATER_NO_PUBLISHED_VERSIONS' @@ -154,7 +154,7 @@ export class CustomGitHubProvider extends BaseGitHubProvider { } const result = parseUpdateInfo(rawData, channelFile, channelFileUrl); - if (result.releaseName == null) { + if (result.releaseName === null) { result.releaseName = latestRelease.elementValueOrEmpty('title'); } diff --git a/packages/frontend/workspace/src/providers/sync/engine.ts b/packages/frontend/workspace/src/providers/sync/engine.ts index 120dc97739..85221587de 100644 --- a/packages/frontend/workspace/src/providers/sync/engine.ts +++ b/packages/frontend/workspace/src/providers/sync/engine.ts @@ -186,13 +186,13 @@ export class SyncEngine { } async waitForSynced(abort?: AbortSignal) { - if (this.status.step == SyncEngineStep.Synced) { + if (this.status.step === SyncEngineStep.Synced) { return; } else { return Promise.race([ new Promise(resolve => { this.onStatusChange.on(status => { - if (status.step == SyncEngineStep.Synced) { + if (status.step === SyncEngineStep.Synced) { resolve(); } }); diff --git a/tests/affine-local/e2e/quick-search.spec.ts b/tests/affine-local/e2e/quick-search.spec.ts index cb5ceb836b..bc53c0cd9e 100644 --- a/tests/affine-local/e2e/quick-search.spec.ts +++ b/tests/affine-local/e2e/quick-search.spec.ts @@ -65,7 +65,7 @@ async function waitForScrollToFinish(page: Page) { let lastScrollTop: number; const interval = setInterval(() => { const { scrollTop } = document.documentElement; - if (scrollTop != lastScrollTop) { + if (scrollTop !== lastScrollTop) { lastScrollTop = scrollTop; } else { clearInterval(interval); From 123f091e5b8904e55b39de3af041a803cb274f22 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:35 +0000 Subject: [PATCH 10/35] fix: add prefer-dom-node-dataset rule (#5107) --- .eslintrc.js | 1 + .../affine/setting-modal/general-setting/plans/index.tsx | 3 +-- .../plugins/image-preview/src/component/index.jotai.ts | 2 +- tests/affine-desktop/e2e/basic.spec.ts | 8 ++------ tests/affine-local/e2e/all-page.spec.ts | 2 +- tests/affine-local/e2e/image-preview.spec.ts | 1 + tests/affine-local/e2e/settings.spec.ts | 8 ++------ tests/affine-local/e2e/theme.spec.ts | 8 ++------ tests/kit/utils/filter.ts | 2 ++ 9 files changed, 13 insertions(+), 22 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0c55bc1e06..39d04c0798 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -207,6 +207,7 @@ const config = { ], 'unicorn/no-unnecessary-await': 'error', 'unicorn/no-useless-fallback-in-spread': 'error', + 'unicorn/prefer-dom-node-dataset': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', 'sonarjs/no-empty-collection': 'error', diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx index c7cda5e661..ff6f8555ba 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx @@ -84,8 +84,7 @@ const Settings = () => { scrollWrapper.current.getBoundingClientRect().left - parseInt(wrapperComputedStyle.paddingLeft) : 0; - const appeared = - scrollWrapper.current.getAttribute('data-appeared') === 'true'; + const appeared = scrollWrapper.current.dataset.appeared === 'true'; const animationFrameId = requestAnimationFrame(() => { scrollWrapper.current?.scrollTo({ behavior: appeared ? 'smooth' : 'instant', diff --git a/packages/plugins/image-preview/src/component/index.jotai.ts b/packages/plugins/image-preview/src/component/index.jotai.ts index 67aa51d95d..0322bc78b6 100644 --- a/packages/plugins/image-preview/src/component/index.jotai.ts +++ b/packages/plugins/image-preview/src/component/index.jotai.ts @@ -9,7 +9,7 @@ previewBlockIdAtom.onMount = set => { if (target?.tagName === 'IMG') { const imageBlock = target.closest('affine-image'); if (imageBlock) { - const blockId = imageBlock.getAttribute('data-block-id'); + const blockId = imageBlock.dataset.blockId; if (!blockId) return; set(blockId); } diff --git a/tests/affine-desktop/e2e/basic.spec.ts b/tests/affine-desktop/e2e/basic.spec.ts index c77223a573..5ff31caf87 100644 --- a/tests/affine-desktop/e2e/basic.spec.ts +++ b/tests/affine-desktop/e2e/basic.spec.ts @@ -114,9 +114,7 @@ test('clientBorder value should disable by default on window', async ({ test('app theme', async ({ page, electronApp }) => { const root = page.locator('html'); { - const themeMode = await root.evaluate(element => - element.getAttribute('data-theme') - ); + const themeMode = await root.evaluate(element => element.dataset.theme); expect(themeMode).toBe('light'); const theme = await electronApp.evaluate(({ nativeTheme }) => { @@ -131,9 +129,7 @@ test('app theme', async ({ page, electronApp }) => { await page.getByTestId('appearance-panel-trigger').click(); await page.waitForTimeout(50); await page.getByTestId('dark-theme-trigger').click(); - const themeMode = await root.evaluate(element => - element.getAttribute('data-theme') - ); + const themeMode = await root.evaluate(element => element.dataset.theme); expect(themeMode).toBe('dark'); const theme = await electronApp.evaluate(({ nativeTheme }) => { return nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; diff --git a/tests/affine-local/e2e/all-page.spec.ts b/tests/affine-local/e2e/all-page.spec.ts index 488abb2657..5ed1c78770 100644 --- a/tests/affine-local/e2e/all-page.spec.ts +++ b/tests/affine-local/e2e/all-page.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/prefer-dom-node-dataset */ import { test } from '@affine-test/kit/playwright'; import { changeFilter, @@ -307,7 +308,6 @@ test('select a group of items by clicking "Select All" in group header', async ( const selectedGroupItemTotalCount = await page .locator('[data-testid="page-list-group-header"]') .getAttribute('data-group-items-count'); - expect(selectedItemCount).toBe(selectedGroupItemTotalCount); // check the selected count is equal to the one displayed in the floating toolbar diff --git a/tests/affine-local/e2e/image-preview.spec.ts b/tests/affine-local/e2e/image-preview.spec.ts index a0a62666b8..118952323a 100644 --- a/tests/affine-local/e2e/image-preview.spec.ts +++ b/tests/affine-local/e2e/image-preview.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/prefer-dom-node-dataset */ import { openHomePage } from '@affine-test/kit/utils/load-page'; import { clickNewPageButton, diff --git a/tests/affine-local/e2e/settings.spec.ts b/tests/affine-local/e2e/settings.spec.ts index 2b3c65d059..ba5e866aa2 100644 --- a/tests/affine-local/e2e/settings.spec.ts +++ b/tests/affine-local/e2e/settings.spec.ts @@ -49,15 +49,11 @@ test('Change theme', async ({ page }) => { const root = page.locator('html'); await page.getByTestId('light-theme-trigger').click(); - const lightMode = await root.evaluate(element => - element.getAttribute('data-theme') - ); + const lightMode = await root.evaluate(element => element.dataset.theme); expect(lightMode).toBe('light'); await page.getByTestId('dark-theme-trigger').click(); - const darkMode = await root.evaluate(element => - element.getAttribute('data-theme') - ); + const darkMode = await root.evaluate(element => element.dataset.theme); expect(darkMode).toBe('dark'); }); diff --git a/tests/affine-local/e2e/theme.spec.ts b/tests/affine-local/e2e/theme.spec.ts index 2e4d3c39b2..ae383963f5 100644 --- a/tests/affine-local/e2e/theme.spec.ts +++ b/tests/affine-local/e2e/theme.spec.ts @@ -14,9 +14,7 @@ test('default white', async ({ browser }) => { await openHomePage(page); await waitForEditorLoad(page); const root = page.locator('html'); - const themeMode = await root.evaluate(element => - element.getAttribute('data-theme') - ); + const themeMode = await root.evaluate(element => element.dataset.theme); expect(themeMode).toBe('light'); await page.screenshot({ path: resolve(testResultDir, 'affine-light-theme.png'), @@ -25,9 +23,7 @@ test('default white', async ({ browser }) => { await page.getByTestId('appearance-panel-trigger').click(); await page.waitForTimeout(50); await page.getByTestId('dark-theme-trigger').click(); - const darkMode = await root.evaluate(element => - element.getAttribute('data-theme') - ); + const darkMode = await root.evaluate(element => element.dataset.theme); expect(darkMode).toBe('dark'); await page.screenshot({ path: resolve(testResultDir, 'affine-dark-theme.png'), diff --git a/tests/kit/utils/filter.ts b/tests/kit/utils/filter.ts index dc1edaf81b..e4809fd622 100644 --- a/tests/kit/utils/filter.ts +++ b/tests/kit/utils/filter.ts @@ -48,6 +48,8 @@ export const getPagesCount = async (page: Page) => { return 0; } + // locator is not a HTMLElement, so we can't use dataset + // eslint-disable-next-line unicorn/prefer-dom-node-dataset const count = await locator.getAttribute('data-total-count'); return count ? parseInt(count) : 0; }; From 23cfc58fe25fda35a7ff1c9c464a861fea00bcb3 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:39 +0000 Subject: [PATCH 11/35] fix: add prefer-dom-node-append rule (#5108) --- .eslintrc.js | 1 + .../frontend/component/src/components/block-hub/index.tsx | 2 +- .../component/src/components/block-suite-editor/index.tsx | 2 +- packages/frontend/component/src/ui/checkbox/checkbox.tsx | 2 +- packages/frontend/component/src/ui/toast/toast.ts | 4 ++-- packages/frontend/core/src/bootstrap/register-plugins.ts | 2 +- packages/frontend/core/src/components/page-detail-editor.tsx | 4 ++-- .../frontend/core/src/components/pure/plugin-header/index.tsx | 2 +- packages/plugins/image-preview/src/component/index.tsx | 2 +- packages/plugins/outline/src/app.tsx | 2 +- 10 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 39d04c0798..6bc6f82d9c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -208,6 +208,7 @@ const config = { 'unicorn/no-unnecessary-await': 'error', 'unicorn/no-useless-fallback-in-spread': 'error', 'unicorn/prefer-dom-node-dataset': 'error', + 'unicorn/prefer-dom-node-append': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', 'sonarjs/no-empty-collection': 'error', diff --git a/packages/frontend/component/src/components/block-hub/index.tsx b/packages/frontend/component/src/components/block-hub/index.tsx index b6157b0521..d189399a9e 100644 --- a/packages/frontend/component/src/components/block-hub/index.tsx +++ b/packages/frontend/component/src/components/block-hub/index.tsx @@ -12,7 +12,7 @@ export const RootBlockHub = () => { if (div.hasChildNodes()) { div.removeChild(div.firstChild as ChildNode); } - div.appendChild(blockHub); + div.append(blockHub); } } }, [blockHub]); diff --git a/packages/frontend/component/src/components/block-suite-editor/index.tsx b/packages/frontend/component/src/components/block-suite-editor/index.tsx index 450a73382d..3e6d433ab0 100644 --- a/packages/frontend/component/src/components/block-suite-editor/index.tsx +++ b/packages/frontend/component/src/components/block-suite-editor/index.tsx @@ -134,7 +134,7 @@ const BlockSuiteEditorImpl = ({ if (!container) { return; } - container.appendChild(editor); + container.append(editor); return () => { container.removeChild(editor); }; diff --git a/packages/frontend/component/src/ui/checkbox/checkbox.tsx b/packages/frontend/component/src/ui/checkbox/checkbox.tsx index 3acb39bf89..b54c245006 100644 --- a/packages/frontend/component/src/ui/checkbox/checkbox.tsx +++ b/packages/frontend/component/src/ui/checkbox/checkbox.tsx @@ -84,7 +84,7 @@ export const playCheckAnimation = async (refElement: Element) => { border-radius: 50%; font-size: inherit; `; - refElement.appendChild(sparkingEl); + refElement.append(sparkingEl); await sparkingEl.animate( [ diff --git a/packages/frontend/component/src/ui/toast/toast.ts b/packages/frontend/component/src/ui/toast/toast.ts index 794ae40491..56f86d9712 100644 --- a/packages/frontend/component/src/ui/toast/toast.ts +++ b/packages/frontend/component/src/ui/toast/toast.ts @@ -46,7 +46,7 @@ const createToastContainer = (portal?: HTMLElement) => { data-testid="affine-toast-container" >
`; const element = htmlToElement(template); - portal.appendChild(element); + portal.append(element); return element; }; @@ -98,7 +98,7 @@ const createAndShowNewToast = ( const toastElement = htmlToElement(toastTemplate); // message is not trusted toastElement.textContent = message; - ToastContainer.appendChild(toastElement); + ToastContainer.append(toastElement); logger.debug(`toast with message: "${message}"`); window.dispatchEvent( new CustomEvent('affine-toast:emit', { detail: message }) diff --git a/packages/frontend/core/src/bootstrap/register-plugins.ts b/packages/frontend/core/src/bootstrap/register-plugins.ts index 1af4e775cc..be6cf1c998 100644 --- a/packages/frontend/core/src/bootstrap/register-plugins.ts +++ b/packages/frontend/core/src/bootstrap/register-plugins.ts @@ -106,7 +106,7 @@ export async function bootstrapPluginSystem( const style = document.createElement('style'); style.setAttribute('plugin-id', pluginName); style.textContent = text; - document.head.appendChild(style); + document.head.append(style); }); } return null; diff --git a/packages/frontend/core/src/components/page-detail-editor.tsx b/packages/frontend/core/src/components/page-detail-editor.tsx index 8447ca0d73..f61d623768 100644 --- a/packages/frontend/core/src/components/page-detail-editor.tsx +++ b/packages/frontend/core/src/components/page-detail-editor.tsx @@ -174,7 +174,7 @@ const EditorWrapper = memo(function EditorWrapper({ div.setAttribute('plugin-id', id); const cleanup = editorItem(div, editor); assertExists(parent); - document.body.appendChild(div); + document.body.append(div); return () => { cleanup(); document.body.removeChild(div); @@ -235,7 +235,7 @@ const PluginContentAdapter = memo( } const div = document.createElement('div'); const cleanup = windowItem(div); - root.appendChild(div); + root.append(div); if (abortController.signal.aborted) { cleanup(); root.removeChild(div); diff --git a/packages/frontend/core/src/components/pure/plugin-header/index.tsx b/packages/frontend/core/src/components/pure/plugin-header/index.tsx index 3484f3b735..1080b39eaa 100644 --- a/packages/frontend/core/src/components/pure/plugin-header/index.tsx +++ b/packages/frontend/core/src/components/pure/plugin-header/index.tsx @@ -25,7 +25,7 @@ export const PluginHeader = () => { div.setAttribute('plugin-id', pluginName); startTransition(() => { const cleanup = create(div); - root.appendChild(div); + root.append(div); addCleanup(pluginName, () => { pluginsRef.current = pluginsRef.current.filter( name => name !== pluginName diff --git a/packages/plugins/image-preview/src/component/index.tsx b/packages/plugins/image-preview/src/component/index.tsx index c66f1746b0..0b3b52a1b2 100644 --- a/packages/plugins/image-preview/src/component/index.tsx +++ b/packages/plugins/image-preview/src/component/index.tsx @@ -231,7 +231,7 @@ const ImagePreviewModalImpl = ( const a = document.createElement('a'); a.href = downloadUrl; a.download = block.id ?? 'image'; - document.body.appendChild(a); + document.body.append(a); a.click(); document.body.removeChild(a); } diff --git a/packages/plugins/outline/src/app.tsx b/packages/plugins/outline/src/app.tsx index 1f54ad30be..caeb0eaa1a 100644 --- a/packages/plugins/outline/src/app.tsx +++ b/packages/plugins/outline/src/app.tsx @@ -40,7 +40,7 @@ const Outline = () => { ref={useCallback((container: HTMLDivElement | null) => { if (container) { assertExists(tocPanelRef.current); - container.appendChild(tocPanelRef.current); + container.append(tocPanelRef.current); } }, [])} /> From d2670297618a680099ab17bcac40fb2182d3ef72 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:43 +0000 Subject: [PATCH 12/35] fix: add prefer-array-some rule (#5109) --- .eslintrc.js | 1 + package.json | 2 +- .../core/src/components/affine/setting-modal/index.tsx | 2 +- packages/frontend/i18n/src/index.ts | 4 ++-- packages/plugins/image-preview/src/component/index.tsx | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 6bc6f82d9c..fc7888a091 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -209,6 +209,7 @@ const config = { 'unicorn/no-useless-fallback-in-spread': 'error', 'unicorn/prefer-dom-node-dataset': 'error', 'unicorn/prefer-dom-node-append': 'error', + 'unicorn/prefer-array-some': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', 'sonarjs/no-empty-collection': 'error', diff --git a/package.json b/package.json index 80a0b4aec9..5a18431c61 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "lint:eslint:fix": "yarn lint:eslint --fix", "lint:prettier": "prettier --ignore-unknown --cache --check .", "lint:prettier:fix": "prettier --ignore-unknown --cache --write .", - "lint:ox": "oxlint --deny-warnings --import-plugin -D correctness -D nursery -A no-undef -A consistent-type-exports -A default -A named -A ban-ts-comment", + "lint:ox": "oxlint --deny-warnings --import-plugin -D correctness -D nursery -D prefer-array-some -A no-undef -A consistent-type-exports -A default -A named -A ban-ts-comment", "lint": "yarn lint:eslint && yarn lint:prettier", "lint:fix": "yarn lint:eslint:fix && yarn lint:prettier:fix", "test": "vitest --run", diff --git a/packages/frontend/core/src/components/affine/setting-modal/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/index.tsx index 2a217fb5f4..075a78b321 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/index.tsx @@ -130,7 +130,7 @@ export const SettingModal = ({ ) : null} - {generalSettingList.find(v => v.key === activeTab) ? ( + {generalSettingList.some(v => v.key === activeTab) ? ( ) : null} {activeTab === 'account' && loginStatus === 'authenticated' ? ( diff --git a/packages/frontend/i18n/src/index.ts b/packages/frontend/i18n/src/index.ts index c6988d6098..8987941ef5 100644 --- a/packages/frontend/i18n/src/index.ts +++ b/packages/frontend/i18n/src/index.ts @@ -57,9 +57,9 @@ const standardizeLocale = (language: string) => { } else if (language.slice(0, 2).toLowerCase() === 'zh') { language = 'zh-Hant'; } - if (LOCALES.find(locale => locale.tag === language)) return language; + if (LOCALES.some(locale => locale.tag === language)) return language; if ( - LOCALES.find(locale => locale.tag === language.slice(0, 2).toLowerCase()) + LOCALES.some(locale => locale.tag === language.slice(0, 2).toLowerCase()) ) { return language.slice(0, 2).toLowerCase(); } diff --git a/packages/plugins/image-preview/src/component/index.tsx b/packages/plugins/image-preview/src/component/index.tsx index 0b3b52a1b2..8b5e62ff1d 100644 --- a/packages/plugins/image-preview/src/component/index.tsx +++ b/packages/plugins/image-preview/src/component/index.tsx @@ -143,7 +143,7 @@ const ImagePreviewModalImpl = ( if ( page .getPreviousSiblings(block) - .findLast( + .some( (block): block is ImageBlockModel => block.flavour === 'affine:image' ) @@ -160,7 +160,7 @@ const ImagePreviewModalImpl = ( } else if ( page .getNextSiblings(block) - .find( + .some( (block): block is ImageBlockModel => block.flavour === 'affine:image' ) From 45690c2756fa222b22d7527bfbfec33632426374 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:46 +0000 Subject: [PATCH 13/35] fix: add no-useless-promise-resolve-reject rule (#5111) --- .eslintrc.js | 1 + package.json | 2 +- packages/backend/server/tests/workspace-usage.spec.ts | 4 +--- packages/frontend/core/src/bootstrap/register-plugins.ts | 6 +++--- packages/frontend/graphql/src/fetcher.ts | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fc7888a091..fd49734c02 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -210,6 +210,7 @@ const config = { 'unicorn/prefer-dom-node-dataset': 'error', 'unicorn/prefer-dom-node-append': 'error', 'unicorn/prefer-array-some': 'error', + 'unicorn/no-useless-promise-resolve-reject': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', 'sonarjs/no-empty-collection': 'error', diff --git a/package.json b/package.json index 5a18431c61..78cf773d3b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "lint:eslint:fix": "yarn lint:eslint --fix", "lint:prettier": "prettier --ignore-unknown --cache --check .", "lint:prettier:fix": "prettier --ignore-unknown --cache --write .", - "lint:ox": "oxlint --deny-warnings --import-plugin -D correctness -D nursery -D prefer-array-some -A no-undef -A consistent-type-exports -A default -A named -A ban-ts-comment", + "lint:ox": "oxlint --deny-warnings --import-plugin -D correctness -D nursery -D prefer-array-some -D no-useless-promise-resolve-reject -A no-undef -A consistent-type-exports -A default -A named -A ban-ts-comment", "lint": "yarn lint:eslint && yarn lint:prettier", "lint:fix": "yarn lint:eslint:fix && yarn lint:prettier:fix", "test": "vitest --run", diff --git a/packages/backend/server/tests/workspace-usage.spec.ts b/packages/backend/server/tests/workspace-usage.spec.ts index 6cda0e2ad2..a7051b1eb0 100644 --- a/packages/backend/server/tests/workspace-usage.spec.ts +++ b/packages/backend/server/tests/workspace-usage.spec.ts @@ -65,9 +65,7 @@ test.afterEach.always(async t => { test('should get blob size limit', async t => { const { resolver } = t.context; - fakeUserService.getStorageQuotaById.returns( - Promise.resolve(100 * 1024 * 1024 * 1024) - ); + fakeUserService.getStorageQuotaById.resolves(100 * 1024 * 1024 * 1024); const res = await resolver.checkBlobSize(new FakePrisma().fakeUser, '', 100); t.not(res, false); // @ts-expect-error diff --git a/packages/frontend/core/src/bootstrap/register-plugins.ts b/packages/frontend/core/src/bootstrap/register-plugins.ts index be6cf1c998..348208f60b 100644 --- a/packages/frontend/core/src/bootstrap/register-plugins.ts +++ b/packages/frontend/core/src/bootstrap/register-plugins.ts @@ -81,7 +81,7 @@ export async function bootstrapPluginSystem( logger.debug(`registering plugin ${pluginName}`); logger.debug(`package.json: ${packageJson}`); if (!release && !runtimeConfig.enablePlugin) { - return Promise.resolve(); + return; } const baseURL = url; const entryURL = `${baseURL}/${core}`; @@ -95,7 +95,7 @@ export async function bootstrapPluginSystem( const loadedAssetName = `${pluginName}_${asset}`; // todo(himself65): add assets into shadow dom if (loadedAssets.has(loadedAssetName)) { - return Promise.resolve(); + return; } if (asset.endsWith('.css')) { loadedAssets.add(loadedAssetName); @@ -111,7 +111,7 @@ export async function bootstrapPluginSystem( } return null; } else { - return Promise.resolve(); + return; } }) ); diff --git a/packages/frontend/graphql/src/fetcher.ts b/packages/frontend/graphql/src/fetcher.ts index 4015f96e74..2c2e6f0198 100644 --- a/packages/frontend/graphql/src/fetcher.ts +++ b/packages/frontend/graphql/src/fetcher.ts @@ -255,6 +255,6 @@ export const fetchWithTraceReport = async ( requestId, ...(event ? { event } : {}), }); - return await Promise.reject(err); + throw err; } }; From 4dddbbdaee2c8efc591bab229b6037f330eaf39c Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:50 +0000 Subject: [PATCH 14/35] fix: add prefer-dom-node-remove rule (#5112) --- .eslintrc.js | 1 + .../frontend/component/src/components/block-hub/index.tsx | 2 +- .../component/src/components/block-suite-editor/index.tsx | 2 +- .../frontend/core/src/components/page-detail-editor.tsx | 6 +++--- .../core/src/components/pure/plugin-header/index.tsx | 2 +- packages/plugins/image-preview/src/component/index.tsx | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fd49734c02..6bbfc4aaa1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -209,6 +209,7 @@ const config = { 'unicorn/no-useless-fallback-in-spread': 'error', 'unicorn/prefer-dom-node-dataset': 'error', 'unicorn/prefer-dom-node-append': 'error', + 'unicorn/prefer-dom-node-remove': 'error', 'unicorn/prefer-array-some': 'error', 'unicorn/no-useless-promise-resolve-reject': 'error', 'sonarjs/no-all-duplicated-branches': 'error', diff --git a/packages/frontend/component/src/components/block-hub/index.tsx b/packages/frontend/component/src/components/block-hub/index.tsx index d189399a9e..a6f27b9937 100644 --- a/packages/frontend/component/src/components/block-hub/index.tsx +++ b/packages/frontend/component/src/components/block-hub/index.tsx @@ -10,7 +10,7 @@ export const RootBlockHub = () => { const div = ref.current; if (blockHub) { if (div.hasChildNodes()) { - div.removeChild(div.firstChild as ChildNode); + (div.firstChild as ChildNode).remove(); } div.append(blockHub); } diff --git a/packages/frontend/component/src/components/block-suite-editor/index.tsx b/packages/frontend/component/src/components/block-suite-editor/index.tsx index 3e6d433ab0..d8b40e2a48 100644 --- a/packages/frontend/component/src/components/block-suite-editor/index.tsx +++ b/packages/frontend/component/src/components/block-suite-editor/index.tsx @@ -136,7 +136,7 @@ const BlockSuiteEditorImpl = ({ } container.append(editor); return () => { - container.removeChild(editor); + editor.remove(); }; }, [editor]); diff --git a/packages/frontend/core/src/components/page-detail-editor.tsx b/packages/frontend/core/src/components/page-detail-editor.tsx index f61d623768..ca3dc916d9 100644 --- a/packages/frontend/core/src/components/page-detail-editor.tsx +++ b/packages/frontend/core/src/components/page-detail-editor.tsx @@ -177,7 +177,7 @@ const EditorWrapper = memo(function EditorWrapper({ document.body.append(div); return () => { cleanup(); - document.body.removeChild(div); + div.remove(); }; }); }); @@ -238,11 +238,11 @@ const PluginContentAdapter = memo( root.append(div); if (abortController.signal.aborted) { cleanup(); - root.removeChild(div); + div.remove(); } else { const cl = () => { cleanup(); - root.removeChild(div); + div.remove(); }; const dispose = addCleanup(pluginName, cl); abortController.signal.addEventListener('abort', () => { diff --git a/packages/frontend/core/src/components/pure/plugin-header/index.tsx b/packages/frontend/core/src/components/pure/plugin-header/index.tsx index 1080b39eaa..6156691546 100644 --- a/packages/frontend/core/src/components/pure/plugin-header/index.tsx +++ b/packages/frontend/core/src/components/pure/plugin-header/index.tsx @@ -30,7 +30,7 @@ export const PluginHeader = () => { pluginsRef.current = pluginsRef.current.filter( name => name !== pluginName ); - root.removeChild(div); + div.remove(); cleanup(); }); }); diff --git a/packages/plugins/image-preview/src/component/index.tsx b/packages/plugins/image-preview/src/component/index.tsx index 8b5e62ff1d..853ddd52a2 100644 --- a/packages/plugins/image-preview/src/component/index.tsx +++ b/packages/plugins/image-preview/src/component/index.tsx @@ -233,7 +233,7 @@ const ImagePreviewModalImpl = ( a.download = block.id ?? 'image'; document.body.append(a); a.click(); - document.body.removeChild(a); + a.remove(); } }, [props.pageId, props.workspace] From bd488262fa59e41d4ca1a0c86b8cd877decd0e11 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:54 +0000 Subject: [PATCH 15/35] fix: add prefer-date-now rule (#5113) --- .eslintrc.js | 1 + packages/backend/server/src/modules/auth/service.ts | 2 +- .../core/src/hooks/affine/use-block-suite-meta-helper.ts | 2 +- packages/frontend/electron/src/shared/utils.ts | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 6bbfc4aaa1..c65b1c31fb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -211,6 +211,7 @@ const config = { 'unicorn/prefer-dom-node-append': 'error', 'unicorn/prefer-dom-node-remove': 'error', 'unicorn/prefer-array-some': 'error', + 'unicorn/prefer-date-now': 'error', 'unicorn/no-useless-promise-resolve-reject': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', diff --git a/packages/backend/server/src/modules/auth/service.ts b/packages/backend/server/src/modules/auth/service.ts index 91652c34dc..a852374ec6 100644 --- a/packages/backend/server/src/modules/auth/service.ts +++ b/packages/backend/server/src/modules/auth/service.ts @@ -23,7 +23,7 @@ export type UserClaim = Pick< hasPassword?: boolean; }; -export const getUtcTimestamp = () => Math.floor(new Date().getTime() / 1000); +export const getUtcTimestamp = () => Math.floor(Date.now() / 1000); @Injectable() export class AuthService { diff --git a/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts b/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts index 2380b239fb..2898844a4e 100644 --- a/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts +++ b/packages/frontend/core/src/hooks/affine/use-block-suite-meta-helper.ts @@ -85,7 +85,7 @@ export function useBlockSuiteMetaHelper( setPageMeta(pageId, { trash: true, - trashDate: +new Date(), + trashDate: Date.now(), trashRelate: isRoot ? parentMeta?.id : undefined, }); setPageReadonly(pageId, true); diff --git a/packages/frontend/electron/src/shared/utils.ts b/packages/frontend/electron/src/shared/utils.ts index 80d05b78da..b3fe238e8f 100644 --- a/packages/frontend/electron/src/shared/utils.ts +++ b/packages/frontend/electron/src/shared/utils.ts @@ -1,7 +1,7 @@ import type { EventBasedChannel } from 'async-call-rpc'; export function getTime() { - return new Date().getTime(); + return Date.now(); } export const isMacOS = () => { From 19c61e051dfa4ccf72f92e2dc5e32fe6c77c0a57 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:43:58 +0000 Subject: [PATCH 16/35] fix: add no-typeof-undefined rule (#5114) --- .eslintrc.js | 1 + packages/backend/server/src/config/def.ts | 2 +- packages/backend/server/src/config/env.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c65b1c31fb..4e59b55aaf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -212,6 +212,7 @@ const config = { 'unicorn/prefer-dom-node-remove': 'error', 'unicorn/prefer-array-some': 'error', 'unicorn/prefer-date-now': 'error', + 'unicorn/no-typeof-undefined': 'error', 'unicorn/no-useless-promise-resolve-reject': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', diff --git a/packages/backend/server/src/config/def.ts b/packages/backend/server/src/config/def.ts index 895df5f08d..1b9a065d9a 100644 --- a/packages/backend/server/src/config/def.ts +++ b/packages/backend/server/src/config/def.ts @@ -50,7 +50,7 @@ function boolean(value: string) { } export function parseEnvValue(value: string | undefined, type?: EnvConfigType) { - if (typeof value === 'undefined') { + if (value === undefined) { return; } diff --git a/packages/backend/server/src/config/env.ts b/packages/backend/server/src/config/env.ts index f68dfedd5f..8d5b950fff 100644 --- a/packages/backend/server/src/config/env.ts +++ b/packages/backend/server/src/config/env.ts @@ -10,7 +10,7 @@ export function applyEnvToConfig(rawConfig: AFFiNEConfig) { ? [config, process.env[env]] : [config[0], parseEnvValue(process.env[env], config[1])]; - if (typeof value !== 'undefined') { + if (value !== undefined) { set(rawConfig, path, value); } } From 68caf5027d308b270f17ac79eba0418365cd596f Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:44:02 +0000 Subject: [PATCH 17/35] fix: add new-for-builtins rule (#5116) --- .eslintrc.js | 1 + packages/backend/server/tests/exception-logger.e2e.ts | 2 +- packages/frontend/graphql/src/utils.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4e59b55aaf..a94e1e5012 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -214,6 +214,7 @@ const config = { 'unicorn/prefer-date-now': 'error', 'unicorn/no-typeof-undefined': 'error', 'unicorn/no-useless-promise-resolve-reject': 'error', + 'unicorn/new-for-builtins': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', 'sonarjs/no-empty-collection': 'error', diff --git a/packages/backend/server/tests/exception-logger.e2e.ts b/packages/backend/server/tests/exception-logger.e2e.ts index 982225fdeb..12f00ee9eb 100644 --- a/packages/backend/server/tests/exception-logger.e2e.ts +++ b/packages/backend/server/tests/exception-logger.e2e.ts @@ -17,7 +17,7 @@ class FakePrisma { get workspace() { return { async findUnique() { - throw Error('exception from graphql'); + throw new Error('exception from graphql'); }, }; } diff --git a/packages/frontend/graphql/src/utils.ts b/packages/frontend/graphql/src/utils.ts index fb9804c504..138a427bdd 100644 --- a/packages/frontend/graphql/src/utils.ts +++ b/packages/frontend/graphql/src/utils.ts @@ -3,7 +3,7 @@ export const TRACE_ID_BYTES = 16; export const TRACE_VERSION = '00'; export const TRACE_FLAG = '01'; -const BytesBuffer = Array(32); +const BytesBuffer = new Array(32); type TraceSpan = { name: string; From cb2c659f52df85e1044602ee2d69fecfd6f27870 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:44:06 +0000 Subject: [PATCH 18/35] fix: add no-new-array rule (#5117) --- .eslintrc.js | 1 + packages/backend/server/tests/history.spec.ts | 90 ++++++++++--------- .../workspace-detail-skeleton.tsx | 24 ++--- .../workspace-list-skeleton.tsx | 8 +- packages/frontend/graphql/src/utils.ts | 2 +- tests/affine-cloud/e2e/collaboration.spec.ts | 4 +- 6 files changed, 73 insertions(+), 56 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index a94e1e5012..9f1fd2bba0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -214,6 +214,7 @@ const config = { 'unicorn/prefer-date-now': 'error', 'unicorn/no-typeof-undefined': 'error', 'unicorn/no-useless-promise-resolve-reject': 'error', + 'unicorn/no-new-array': 'error', 'unicorn/new-for-builtins': 'error', 'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-element-overwrite': 'error', diff --git a/packages/backend/server/tests/history.spec.ts b/packages/backend/server/tests/history.spec.ts index 103a7d231a..c2ed148574 100644 --- a/packages/backend/server/tests/history.spec.ts +++ b/packages/backend/server/tests/history.spec.ts @@ -183,26 +183,30 @@ test('should correctly list all history records', async t => { // insert expired data await db.snapshotHistory.createMany({ - data: new Array(10).fill(0).map((_, i) => ({ - workspaceId: snapshot.workspaceId, - id: snapshot.id, - blob: snapshot.blob, - state: snapshot.state, - timestamp: new Date(timestamp - 10 - i), - expiredAt: new Date(timestamp - 1), - })), + data: Array.from({ length: 10 }) + .fill(0) + .map((_, i) => ({ + workspaceId: snapshot.workspaceId, + id: snapshot.id, + blob: snapshot.blob, + state: snapshot.state, + timestamp: new Date(timestamp - 10 - i), + expiredAt: new Date(timestamp - 1), + })), }); // insert available data await db.snapshotHistory.createMany({ - data: new Array(10).fill(0).map((_, i) => ({ - workspaceId: snapshot.workspaceId, - id: snapshot.id, - blob: snapshot.blob, - state: snapshot.state, - timestamp: new Date(timestamp + i), - expiredAt: new Date(timestamp + 1000), - })), + data: Array.from({ length: 10 }) + .fill(0) + .map((_, i) => ({ + workspaceId: snapshot.workspaceId, + id: snapshot.id, + blob: snapshot.blob, + state: snapshot.state, + timestamp: new Date(timestamp + i), + expiredAt: new Date(timestamp + 1000), + })), }); const list = await manager.list( @@ -243,14 +247,16 @@ test('should be able to get last history record', async t => { // insert available data await db.snapshotHistory.createMany({ - data: new Array(10).fill(0).map((_, i) => ({ - workspaceId: snapshot.workspaceId, - id: snapshot.id, - blob: snapshot.blob, - state: snapshot.state, - timestamp: new Date(timestamp + i), - expiredAt: new Date(timestamp + 1000), - })), + data: Array.from({ length: 10 }) + .fill(0) + .map((_, i) => ({ + workspaceId: snapshot.workspaceId, + id: snapshot.id, + blob: snapshot.blob, + state: snapshot.state, + timestamp: new Date(timestamp + i), + expiredAt: new Date(timestamp + 1000), + })), }); const history = await manager.last(snapshot.workspaceId, snapshot.id); @@ -299,26 +305,30 @@ test('should be able to cleanup expired history', async t => { // insert expired data await db.snapshotHistory.createMany({ - data: new Array(10).fill(0).map((_, i) => ({ - workspaceId: snapshot.workspaceId, - id: snapshot.id, - blob: snapshot.blob, - state: snapshot.state, - timestamp: new Date(timestamp - 10 - i), - expiredAt: new Date(timestamp - 1), - })), + data: Array.from({ length: 10 }) + .fill(0) + .map((_, i) => ({ + workspaceId: snapshot.workspaceId, + id: snapshot.id, + blob: snapshot.blob, + state: snapshot.state, + timestamp: new Date(timestamp - 10 - i), + expiredAt: new Date(timestamp - 1), + })), }); // insert available data await db.snapshotHistory.createMany({ - data: new Array(10).fill(0).map((_, i) => ({ - workspaceId: snapshot.workspaceId, - id: snapshot.id, - blob: snapshot.blob, - state: snapshot.state, - timestamp: new Date(timestamp + i), - expiredAt: new Date(timestamp + 1000), - })), + data: Array.from({ length: 10 }) + .fill(0) + .map((_, i) => ({ + workspaceId: snapshot.workspaceId, + id: snapshot.id, + blob: snapshot.blob, + state: snapshot.state, + timestamp: new Date(timestamp + i), + expiredAt: new Date(timestamp + 1000), + })), }); let count = await db.snapshotHistory.count(); diff --git a/packages/frontend/component/src/components/setting-components/workspace-detail-skeleton.tsx b/packages/frontend/component/src/components/setting-components/workspace-detail-skeleton.tsx index 6755c200d6..dde4dfd578 100644 --- a/packages/frontend/component/src/components/setting-components/workspace-detail-skeleton.tsx +++ b/packages/frontend/component/src/components/setting-components/workspace-detail-skeleton.tsx @@ -8,17 +8,19 @@ export const WorkspaceDetailSkeleton = () => { <> } subtitle={} /> - {new Array(3).fill(0).map((_, index) => { - return ( - } key={index}> - } - desc={} - spreadCol={false} - > - - ); - })} + {Array.from({ length: 3 }) + .fill(0) + .map((_, index) => { + return ( + } key={index}> + } + desc={} + spreadCol={false} + > + + ); + })} ); }; diff --git a/packages/frontend/component/src/components/setting-components/workspace-list-skeleton.tsx b/packages/frontend/component/src/components/setting-components/workspace-list-skeleton.tsx index 170798d4a4..8783ab82b0 100644 --- a/packages/frontend/component/src/components/setting-components/workspace-list-skeleton.tsx +++ b/packages/frontend/component/src/components/setting-components/workspace-list-skeleton.tsx @@ -26,9 +26,11 @@ export const WorkspaceListItemSkeleton = () => { export const WorkspaceListSkeleton = () => { return ( <> - {new Array(5).fill(0).map((_, index) => { - return ; - })} + {Array.from({ length: 5 }) + .fill(0) + .map((_, index) => { + return ; + })} ); }; diff --git a/packages/frontend/graphql/src/utils.ts b/packages/frontend/graphql/src/utils.ts index 138a427bdd..4e2a1f8e83 100644 --- a/packages/frontend/graphql/src/utils.ts +++ b/packages/frontend/graphql/src/utils.ts @@ -3,7 +3,7 @@ export const TRACE_ID_BYTES = 16; export const TRACE_VERSION = '00'; export const TRACE_FLAG = '01'; -const BytesBuffer = new Array(32); +const BytesBuffer = Array.from({ length: 32 }); type TraceSpan = { name: string; diff --git a/tests/affine-cloud/e2e/collaboration.spec.ts b/tests/affine-cloud/e2e/collaboration.spec.ts index 2be5db6571..2af8c02d54 100644 --- a/tests/affine-cloud/e2e/collaboration.spec.ts +++ b/tests/affine-cloud/e2e/collaboration.spec.ts @@ -265,7 +265,9 @@ test.describe('collaboration members', () => { await addUserToWorkspace(workspaceId, userB.id, 1 /* READ */); }; await Promise.all( - new Array(10).fill(1).map(() => createUserAndAddToWorkspace()) + Array.from({ length: 10 }) + .fill(1) + .map(() => createUserAndAddToWorkspace()) ); await openSettingModal(page); From eadf8a085bd7ba4af7f897e7afc0aa6256075eea Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:44:10 +0000 Subject: [PATCH 19/35] fix: add prefer-blob-reading-methods rule (#5118) --- .eslintrc.js | 1 + tests/affine-cloud/e2e/collaboration.spec.ts | 16 ++++------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9f1fd2bba0..d0ac35f591 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -212,6 +212,7 @@ const config = { 'unicorn/prefer-dom-node-remove': 'error', 'unicorn/prefer-array-some': 'error', 'unicorn/prefer-date-now': 'error', + 'unicorn/prefer-blob-reading-methods': 'error', 'unicorn/no-typeof-undefined': 'error', 'unicorn/no-useless-promise-resolve-reject': 'error', 'unicorn/no-new-array': 'error', diff --git a/tests/affine-cloud/e2e/collaboration.spec.ts b/tests/affine-cloud/e2e/collaboration.spec.ts index 2af8c02d54..a592bc494c 100644 --- a/tests/affine-cloud/e2e/collaboration.spec.ts +++ b/tests/affine-cloud/e2e/collaboration.spec.ts @@ -368,18 +368,10 @@ test('can sync svg between different browsers', async ({ page, browser }) => { expect(src).not.toBeNull(); // fetch the src resource in the browser - const svg2 = await page2.evaluate(async src => { - async function blobToString(blob: Blob) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsText(blob); - }); - } - - const blob = fetch(src!).then(res => res.blob()); - return blobToString(await blob); + const svg2 = await page2.evaluate(src => { + return fetch(src!) + .then(res => res.blob()) + .then(blob => blob.text()); }, src); // turn the blob into string and check if it contains the svg From db3039fbf4ca14dad9218f7d5bd97c02bd56d9f1 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:44:14 +0000 Subject: [PATCH 20/35] fix: add require-array-sort-compare rule (#5119) --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index d0ac35f591..5b4ec4b310 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -135,6 +135,7 @@ const config = { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/require-array-sort-compare': 'error', '@typescript-eslint/no-unused-vars': [ 'error', { From acf2de813aba7d79b264988a99dc4410ccfe80f8 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:44:17 +0000 Subject: [PATCH 21/35] fix: add unified-signatures rule (#5120) --- .eslintrc.js | 1 + packages/frontend/workspace/src/affine/gql.ts | 21 ++----------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5b4ec4b310..16302e62b9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -136,6 +136,7 @@ const config = { '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/require-array-sort-compare': 'error', + '@typescript-eslint/unified-signatures': 'error', '@typescript-eslint/no-unused-vars': [ 'error', { diff --git a/packages/frontend/workspace/src/affine/gql.ts b/packages/frontend/workspace/src/affine/gql.ts index b7988042a0..36f02c4eca 100644 --- a/packages/frontend/workspace/src/affine/gql.ts +++ b/packages/frontend/workspace/src/affine/gql.ts @@ -44,18 +44,9 @@ export const fetcher = gqlFetcherFactory( * }) * ``` */ -export function useQuery( - options: QueryOptions -): SWRResponse< - QueryResponse, - GraphQLError | GraphQLError[], - { - suspense: true; - } ->; export function useQuery( options: QueryOptions, - config: Omit< + config?: Omit< SWRConfiguration< QueryResponse, GraphQLError | GraphQLError[], @@ -160,17 +151,9 @@ export function useQueryInfinite( * * trigger({ name: 'John Doe' }) */ -export function useMutation( - options: Omit, 'variables'> -): SWRMutationResponse< - QueryResponse, - GraphQLError | GraphQLError[], - K, - QueryVariables ->; export function useMutation( options: Omit, 'variables'>, - config: Omit< + config?: Omit< SWRMutationConfiguration< QueryResponse, GraphQLError | GraphQLError[], From e9ea67bd38a5369697f60e87de8aaebfed067ca2 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:44:21 +0000 Subject: [PATCH 22/35] fix: add prefer-for-of rule (#5121) --- .eslintrc.js | 1 + .../frontend/component/src/components/page-list/utils.tsx | 6 +++--- tools/workers/src/index.ts | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 16302e62b9..2bac52fb92 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -137,6 +137,7 @@ const config = { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/require-array-sort-compare': 'error', '@typescript-eslint/unified-signatures': 'error', + '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/no-unused-vars': [ 'error', { diff --git a/packages/frontend/component/src/components/page-list/utils.tsx b/packages/frontend/component/src/components/page-list/utils.tsx index 1c33f2fd1f..c0f29a9e10 100644 --- a/packages/frontend/component/src/components/page-list/utils.tsx +++ b/packages/frontend/component/src/components/page-list/utils.tsx @@ -162,10 +162,10 @@ export function shallowEqual(objA: any, objB: any) { } // Test for A's keys different from B. - for (let i = 0; i < keysA.length; i++) { + for (const key of keysA) { if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i]) || - !Object.is(objA[keysA[i]], objB[keysA[i]]) + !Object.prototype.hasOwnProperty.call(objB, key) || + !Object.is(objA[key], objB[key]) ) { return false; } diff --git a/tools/workers/src/index.ts b/tools/workers/src/index.ts index 364d631abb..d49e2c433e 100644 --- a/tools/workers/src/index.ts +++ b/tools/workers/src/index.ts @@ -14,8 +14,8 @@ function isOriginAllowed( allowedOrigin: string | RegExp | Array ): boolean { if (Array.isArray(allowedOrigin)) { - for (let i = 0; i < allowedOrigin.length; ++i) { - if (isOriginAllowed(origin, allowedOrigin[i])) { + for (const allowed of allowedOrigin) { + if (isOriginAllowed(origin, allowed)) { return true; } } From 7a7cbc45d7286b67b6c2ff05eb1c1ffd145e9134 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 04:44:25 +0000 Subject: [PATCH 23/35] fix: add prefer-readonly rule (#5122) --- .eslintrc.js | 1 + packages/backend/server/src/middleware/exception-logger.ts | 2 +- packages/backend/server/src/modules/auth/guard.ts | 4 ++-- packages/backend/server/src/modules/auth/service.ts | 6 +++--- packages/backend/server/src/modules/doc/manager.ts | 4 ++-- packages/backend/server/src/utils/doc.ts | 2 +- packages/common/debug/src/index.ts | 2 +- packages/common/env/src/ua-helper.ts | 2 +- packages/common/infra/src/type.ts | 4 ++-- .../src/components/page-list/filter/logical/matcher.ts | 6 ++++-- .../src/components/page-list/filter/logical/typesystem.ts | 4 ++-- packages/frontend/core/src/utils/user-setting.ts | 4 ++-- packages/frontend/core/src/utils/workspace-setting.ts | 2 +- packages/frontend/electron/src/main/helper-process.ts | 2 +- .../electron/src/main/updater/custom-github-provider.ts | 2 +- packages/frontend/electron/src/shared/utils.ts | 2 +- packages/frontend/graphql/src/utils.ts | 6 +++--- packages/frontend/workspace/src/blob/engine.ts | 4 ++-- .../src/providers/storage/affine/batch-sync-sender.ts | 4 ++-- packages/frontend/workspace/src/providers/sync/engine.ts | 6 +++--- packages/frontend/workspace/src/providers/sync/peer.ts | 6 +++--- 21 files changed, 39 insertions(+), 36 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2bac52fb92..5e5566f176 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -270,6 +270,7 @@ const config = { }, ], '@typescript-eslint/no-misused-promises': ['error'], + '@typescript-eslint/prefer-readonly': 'error', 'i/no-extraneous-dependencies': ['error'], 'react-hooks/exhaustive-deps': [ 'warn', diff --git a/packages/backend/server/src/middleware/exception-logger.ts b/packages/backend/server/src/middleware/exception-logger.ts index 89abbd02e0..4e4c97d3c8 100644 --- a/packages/backend/server/src/middleware/exception-logger.ts +++ b/packages/backend/server/src/middleware/exception-logger.ts @@ -14,7 +14,7 @@ const TrivialExceptions = [NotFoundException]; @Catch() export class ExceptionLogger implements ExceptionFilter { - private logger = new Logger('ExceptionLogger'); + private readonly logger = new Logger('ExceptionLogger'); catch(exception: Error, host: ArgumentsHost) { // with useGlobalFilters, the context is always HTTP diff --git a/packages/backend/server/src/modules/auth/guard.ts b/packages/backend/server/src/modules/auth/guard.ts index 6b4532354d..7e311159d2 100644 --- a/packages/backend/server/src/modules/auth/guard.ts +++ b/packages/backend/server/src/modules/auth/guard.ts @@ -53,8 +53,8 @@ class AuthGuard implements CanActivate { constructor( @Inject(NextAuthOptionsProvide) private readonly nextAuthOptions: NextAuthOptions, - private auth: AuthService, - private prisma: PrismaService, + private readonly auth: AuthService, + private readonly prisma: PrismaService, private readonly reflector: Reflector ) {} diff --git a/packages/backend/server/src/modules/auth/service.ts b/packages/backend/server/src/modules/auth/service.ts index a852374ec6..b6150a65d1 100644 --- a/packages/backend/server/src/modules/auth/service.ts +++ b/packages/backend/server/src/modules/auth/service.ts @@ -28,9 +28,9 @@ export const getUtcTimestamp = () => Math.floor(Date.now() / 1000); @Injectable() export class AuthService { constructor( - private config: Config, - private prisma: PrismaService, - private mailer: MailService + private readonly config: Config, + private readonly prisma: PrismaService, + private readonly mailer: MailService ) {} sign(user: UserClaim) { diff --git a/packages/backend/server/src/modules/doc/manager.ts b/packages/backend/server/src/modules/doc/manager.ts index a31bc1a841..3362bc5ced 100644 --- a/packages/backend/server/src/modules/doc/manager.ts +++ b/packages/backend/server/src/modules/doc/manager.ts @@ -60,9 +60,9 @@ const MAX_SEQ_NUM = 0x3fffffff; // u31 */ @Injectable() export class DocManager implements OnModuleInit, OnModuleDestroy { - private logger = new Logger(DocManager.name); + private readonly logger = new Logger(DocManager.name); private job: NodeJS.Timeout | null = null; - private seqMap = new Map(); + private readonly seqMap = new Map(); private busy = false; constructor( diff --git a/packages/backend/server/src/utils/doc.ts b/packages/backend/server/src/utils/doc.ts index 7230b05a18..52c59204c4 100644 --- a/packages/backend/server/src/utils/doc.ts +++ b/packages/backend/server/src/utils/doc.ts @@ -16,7 +16,7 @@ export class DocID { raw: string; workspace: string; variant: DocVariant; - private sub: string | null; + private readonly sub: string | null; static parse(raw: string): DocID | null { try { diff --git a/packages/common/debug/src/index.ts b/packages/common/debug/src/index.ts index d943e27fd5..b805541cfb 100644 --- a/packages/common/debug/src/index.ts +++ b/packages/common/debug/src/index.ts @@ -24,7 +24,7 @@ if (typeof window !== 'undefined') { } export class DebugLogger { - private _debug: debug.Debugger; + private readonly _debug: debug.Debugger; constructor(namespace: string) { this._debug = debug(namespace); diff --git a/packages/common/env/src/ua-helper.ts b/packages/common/env/src/ua-helper.ts index 66a685dce3..fc372b6d9a 100644 --- a/packages/common/env/src/ua-helper.ts +++ b/packages/common/env/src/ua-helper.ts @@ -1,7 +1,7 @@ import { assertExists } from '@blocksuite/global/utils'; export class UaHelper { - private uaMap; + private readonly uaMap; public isLinux = false; public isMacOs = false; public isSafari = false; diff --git a/packages/common/infra/src/type.ts b/packages/common/infra/src/type.ts index e998db425b..cd459f2eca 100644 --- a/packages/common/infra/src/type.ts +++ b/packages/common/infra/src/type.ts @@ -45,8 +45,8 @@ export abstract class HandlerManager< Handlers extends Record, > { static instance: HandlerManager>; - private _app: App; - private _namespace: Namespace; + private readonly _app: App; + private readonly _namespace: Namespace; private _handlers: Handlers; constructor() { diff --git a/packages/frontend/component/src/components/page-list/filter/logical/matcher.ts b/packages/frontend/component/src/components/page-list/filter/logical/matcher.ts index d7ffc5763c..fe3574378e 100644 --- a/packages/frontend/component/src/components/page-list/filter/logical/matcher.ts +++ b/packages/frontend/component/src/components/page-list/filter/logical/matcher.ts @@ -4,9 +4,11 @@ import { typesystem } from './typesystem'; type MatcherData = { type: Type; data: Data }; export class Matcher { - private list: MatcherData[] = []; + private readonly list: MatcherData[] = []; - constructor(private _match?: (type: Type, target: TType) => boolean) {} + constructor( + private readonly _match?: (type: Type, target: TType) => boolean + ) {} register(type: Type, data: Data) { this.list.push({ type, data }); diff --git a/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts b/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts index 5458e02e2b..28345c3727 100644 --- a/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts +++ b/packages/frontend/component/src/components/page-list/filter/logical/typesystem.ts @@ -92,8 +92,8 @@ export type ValueOfData = T extends DataDefine export class DataDefine> { constructor( - private config: DataDefineConfig, - private dataMap: Map + private readonly config: DataDefineConfig, + private readonly dataMap: Map ) {} create(data?: Data): TDataType { diff --git a/packages/frontend/core/src/utils/user-setting.ts b/packages/frontend/core/src/utils/user-setting.ts index 6493047649..3a668b6459 100644 --- a/packages/frontend/core/src/utils/user-setting.ts +++ b/packages/frontend/core/src/utils/user-setting.ts @@ -5,8 +5,8 @@ import type { Map as YMap } from 'yjs'; import { Doc as YDoc } from 'yjs'; export class UserSetting { constructor( - private workspace: Workspace, - private userId: string + private readonly workspace: Workspace, + private readonly userId: string ) {} get setting(): YDoc { diff --git a/packages/frontend/core/src/utils/workspace-setting.ts b/packages/frontend/core/src/utils/workspace-setting.ts index 18d0344588..dacdc92ce9 100644 --- a/packages/frontend/core/src/utils/workspace-setting.ts +++ b/packages/frontend/core/src/utils/workspace-setting.ts @@ -13,7 +13,7 @@ const COLLECTIONS_TRASH_KEY = 'collections_trash'; const SETTING_KEY = 'setting'; export class WorkspaceSetting { - constructor(private workspace: Workspace) {} + constructor(private readonly workspace: Workspace) {} get doc() { return this.workspace.doc; diff --git a/packages/frontend/electron/src/main/helper-process.ts b/packages/frontend/electron/src/main/helper-process.ts index ed73b096d5..23b6da1653 100644 --- a/packages/frontend/electron/src/main/helper-process.ts +++ b/packages/frontend/electron/src/main/helper-process.ts @@ -33,7 +33,7 @@ function pickAndBind( class HelperProcessManager { ready: Promise; - #process: UtilityProcess; + readonly #process: UtilityProcess; // a rpc server for the main process -> helper process rpc?: _AsyncVersionOf; diff --git a/packages/frontend/electron/src/main/updater/custom-github-provider.ts b/packages/frontend/electron/src/main/updater/custom-github-provider.ts index 9615b3c550..75a9861f6a 100644 --- a/packages/frontend/electron/src/main/updater/custom-github-provider.ts +++ b/packages/frontend/electron/src/main/updater/custom-github-provider.ts @@ -29,7 +29,7 @@ const hrefRegExp = /\/tag\/([^/]+)$/; export class CustomGitHubProvider extends BaseGitHubProvider { constructor( options: CustomPublishOptions, - private updater: AppUpdater, + private readonly updater: AppUpdater, runtimeOptions: ProviderRuntimeOptions ) { super(options as unknown as GithubOptions, 'github.com', runtimeOptions); diff --git a/packages/frontend/electron/src/shared/utils.ts b/packages/frontend/electron/src/shared/utils.ts index b3fe238e8f..b7e7438b1f 100644 --- a/packages/frontend/electron/src/shared/utils.ts +++ b/packages/frontend/electron/src/shared/utils.ts @@ -22,7 +22,7 @@ interface MessagePortLike { } export class MessageEventChannel implements EventBasedChannel { - constructor(private worker: MessagePortLike) {} + constructor(private readonly worker: MessagePortLike) {} on(listener: (data: unknown) => void) { const f = (data: unknown) => { diff --git a/packages/frontend/graphql/src/utils.ts b/packages/frontend/graphql/src/utils.ts index 4e2a1f8e83..9c537d7521 100644 --- a/packages/frontend/graphql/src/utils.ts +++ b/packages/frontend/graphql/src/utils.ts @@ -54,7 +54,7 @@ export class TraceReporter { private spansCache = new Array(); private reportIntervalId: number | undefined | NodeJS.Timeout; - private reportInterval = 60_000; + private readonly reportInterval = 60_000; private static instance: TraceReporter; @@ -175,7 +175,7 @@ export class TraceReporter { }; } - private initTraceReport = () => { + private readonly initTraceReport = () => { if (!this.reportIntervalId && TraceReporter.shouldReportTrace) { if (typeof window !== 'undefined') { this.reportIntervalId = window.setInterval( @@ -191,7 +191,7 @@ export class TraceReporter { } }; - private reportHandler = () => { + private readonly reportHandler = () => { if (this.spansCache.length <= 0) { clearInterval(this.reportIntervalId); this.reportIntervalId = undefined; diff --git a/packages/frontend/workspace/src/blob/engine.ts b/packages/frontend/workspace/src/blob/engine.ts index 16f993bdaa..0c5adb241d 100644 --- a/packages/frontend/workspace/src/blob/engine.ts +++ b/packages/frontend/workspace/src/blob/engine.ts @@ -5,8 +5,8 @@ const logger = new DebugLogger('affine:blob-engine'); export class BlobEngine { constructor( - private local: BlobStorage, - private remotes: BlobStorage[] + private readonly local: BlobStorage, + private readonly remotes: BlobStorage[] ) {} get storages() { diff --git a/packages/frontend/workspace/src/providers/storage/affine/batch-sync-sender.ts b/packages/frontend/workspace/src/providers/storage/affine/batch-sync-sender.ts index daebb9bc53..764eea09f7 100644 --- a/packages/frontend/workspace/src/providers/storage/affine/batch-sync-sender.ts +++ b/packages/frontend/workspace/src/providers/storage/affine/batch-sync-sender.ts @@ -15,12 +15,12 @@ interface SyncUpdateSender { * - retryable, allow retry when previous sync request failed but with retry flag been set to true */ export class BatchSyncSender { - private buffered: Uint8Array[] = []; + private readonly buffered: Uint8Array[] = []; private job: Promise | null = null; private started = true; constructor( - private guid: string, + private readonly guid: string, private readonly rawSender: SyncUpdateSender ) {} diff --git a/packages/frontend/workspace/src/providers/sync/engine.ts b/packages/frontend/workspace/src/providers/sync/engine.ts index 85221587de..fca3e2c74a 100644 --- a/packages/frontend/workspace/src/providers/sync/engine.ts +++ b/packages/frontend/workspace/src/providers/sync/engine.ts @@ -63,9 +63,9 @@ export class SyncEngine { private abort = new AbortController(); constructor( - private rootDoc: Doc, - private local: Storage, - private remotes: Storage[] + private readonly rootDoc: Doc, + private readonly local: Storage, + private readonly remotes: Storage[] ) { this._status = { step: SyncEngineStep.Stopped, diff --git a/packages/frontend/workspace/src/providers/sync/peer.ts b/packages/frontend/workspace/src/providers/sync/peer.ts index a0c270e801..5597bb8469 100644 --- a/packages/frontend/workspace/src/providers/sync/peer.ts +++ b/packages/frontend/workspace/src/providers/sync/peer.ts @@ -69,8 +69,8 @@ export class SyncPeer { logger = new DebugLogger('affine:sync-peer:' + this.name); constructor( - private rootDoc: Doc, - private storage: Storage + private readonly rootDoc: Doc, + private readonly storage: Storage ) { this.logger.debug('peer start'); @@ -150,7 +150,7 @@ export class SyncPeer { } } - private state: { + private readonly state: { connectedDocs: Map; pushUpdatesQueue: AsyncQueue<{ docId: string; From 89f267a3fefa49eaae5c2532d43a0fa35a6174c4 Mon Sep 17 00:00:00 2001 From: liuyi Date: Wed, 29 Nov 2023 08:05:08 +0000 Subject: [PATCH 24/35] refactor(server): simplify metrics creation and usage (#5115) --- packages/backend/server/package.json | 1 + .../server/src/graphql/logger-plugin.ts | 10 +- .../backend/server/src/metrics/metrics.ts | 175 ++++++++++++------ .../server/src/metrics/opentelemetry.ts | 47 ++++- packages/backend/server/src/metrics/prisma.ts | 132 +++++++++++++ packages/backend/server/src/metrics/utils.ts | 10 +- .../src/modules/auth/next-auth.controller.ts | 14 +- .../backend/server/src/modules/doc/history.ts | 12 +- .../backend/server/src/modules/doc/manager.ts | 6 +- .../src/modules/sync/events/events.gateway.ts | 8 +- .../src/modules/workspaces/controller.ts | 6 +- packages/backend/server/src/prisma/service.ts | 7 + yarn.lock | 3 +- 13 files changed, 341 insertions(+), 90 deletions(-) create mode 100644 packages/backend/server/src/metrics/prisma.ts diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index d893852232..301b2599fc 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -48,6 +48,7 @@ "@opentelemetry/instrumentation-ioredis": "^0.35.3", "@opentelemetry/instrumentation-nestjs-core": "^0.33.3", "@opentelemetry/instrumentation-socket.io": "^0.34.3", + "@opentelemetry/resources": "^1.18.1", "@opentelemetry/sdk-metrics": "^1.18.1", "@opentelemetry/sdk-node": "^0.45.1", "@opentelemetry/sdk-trace-node": "^1.18.1", diff --git a/packages/backend/server/src/graphql/logger-plugin.ts b/packages/backend/server/src/graphql/logger-plugin.ts index 67c399d667..9028ce722a 100644 --- a/packages/backend/server/src/graphql/logger-plugin.ts +++ b/packages/backend/server/src/graphql/logger-plugin.ts @@ -20,7 +20,7 @@ export class GQLLoggerPlugin implements ApolloServerPlugin { const res = reqContext.contextValue.req.res as Response; const operation = reqContext.request.operationName; - metrics().gqlRequest.add(1, { operation }); + metrics.gql.counter('query_counter').add(1, { operation }); const start = Date.now(); return Promise.resolve({ @@ -30,7 +30,9 @@ export class GQLLoggerPlugin implements ApolloServerPlugin { 'Server-Timing', `gql;dur=${costInMilliseconds};desc="GraphQL"` ); - metrics().gqlTimer.record(costInMilliseconds, { operation }); + metrics.gql + .histogram('query_duration') + .record(costInMilliseconds, { operation }); return Promise.resolve(); }, didEncounterErrors: () => { @@ -39,7 +41,9 @@ export class GQLLoggerPlugin implements ApolloServerPlugin { 'Server-Timing', `gql;dur=${costInMilliseconds};desc="GraphQL ${operation}"` ); - metrics().gqlTimer.record(costInMilliseconds, { operation }); + metrics.gql + .histogram('query_duration') + .record(costInMilliseconds, { operation }); return Promise.resolve(); }, }); diff --git a/packages/backend/server/src/metrics/metrics.ts b/packages/backend/server/src/metrics/metrics.ts index b91223cf97..ccbb7be606 100644 --- a/packages/backend/server/src/metrics/metrics.ts +++ b/packages/backend/server/src/metrics/metrics.ts @@ -1,76 +1,129 @@ -import opentelemetry, { Attributes, Observable } from '@opentelemetry/api'; +import { + Attributes, + Counter, + Histogram, + Meter, + MetricOptions, +} from '@opentelemetry/api'; -interface AsyncMetric { - ob: Observable; - get value(): any; - get attrs(): Attributes | undefined; -} +import { getMeter } from './opentelemetry'; -let _metrics: ReturnType | undefined = undefined; +type MetricType = 'counter' | 'gauge' | 'histogram'; +type Metric = T extends 'counter' + ? Counter + : T extends 'gauge' + ? Histogram + : T extends 'histogram' + ? Histogram + : never; -export function getMeter(name = 'business') { - return opentelemetry.metrics.getMeter(name); -} +export type ScopedMetrics = { + [T in MetricType]: (name: string, opts?: MetricOptions) => Metric; +}; +type MetricCreators = { + [T in MetricType]: ( + meter: Meter, + name: string, + opts?: MetricOptions + ) => Metric; +}; -function createBusinessMetrics() { - const meter = getMeter(); - const asyncMetrics: AsyncMetric[] = []; +export type KnownMetricScopes = + | 'socketio' + | 'gql' + | 'jwst' + | 'auth' + | 'controllers' + | 'doc'; - function createGauge(name: string) { +const metricCreators: MetricCreators = { + counter(meter: Meter, name: string, opts?: MetricOptions) { + return meter.createCounter(name, opts); + }, + gauge(meter: Meter, name: string, opts?: MetricOptions) { let value: any; let attrs: Attributes | undefined; - const ob = meter.createObservableGauge(name); - asyncMetrics.push({ - ob, - get value() { - return value; - }, - get attrs() { - return attrs; - }, + const ob = meter.createObservableGauge(name, opts); + + ob.addCallback(result => { + result.observe(value, attrs); }); - return (newValue: any, newAttrs?: Attributes) => { - value = newValue; - attrs = newAttrs; - }; + return { + record: (newValue, newAttrs) => { + value = newValue; + attrs = newAttrs; + }, + } satisfies Histogram; + }, + histogram(meter: Meter, name: string, opts?: MetricOptions) { + return meter.createHistogram(name, opts); + }, +}; + +const scopes = new Map(); + +function make(scope: string) { + const meter = getMeter(); + const metrics = new Map(); + const prefix = scope + '/'; + + function getOrCreate( + type: T, + name: string, + opts?: MetricOptions + ): Metric { + name = prefix + name; + const metric = metrics.get(name); + if (metric) { + if (type !== metric.type) { + throw new Error( + `Metric ${name} has already been registered as ${metric.type} mode, but get as ${type} again.` + ); + } + + return metric.metric; + } else { + const metric = metricCreators[type](meter, name, opts); + metrics.set(name, { type, metric }); + return metric; + } } - const metrics = { - socketIOConnectionGauge: createGauge('socket_io_connection'), - - gqlRequest: meter.createCounter('gql_request'), - gqlError: meter.createCounter('gql_error'), - gqlTimer: meter.createHistogram('gql_timer'), - - jwstCodecMerge: meter.createCounter('jwst_codec_merge'), - jwstCodecDidnotMatch: meter.createCounter('jwst_codec_didnot_match'), - jwstCodecFail: meter.createCounter('jwst_codec_fail'), - - authCounter: meter.createCounter('auth'), - authFailCounter: meter.createCounter('auth_fail'), - - docHistoryCounter: meter.createCounter('doc_history_created'), - docRecoverCounter: meter.createCounter('doc_history_recovered'), - }; - - meter.addBatchObservableCallback( - result => { - asyncMetrics.forEach(metric => { - result.observe(metric.ob, metric.value, metric.attrs); - }); + return { + counter(name, opts) { + return getOrCreate('counter', name, opts); }, - asyncMetrics.map(({ ob }) => ob) - ); - - return metrics; + gauge(name, opts) { + return getOrCreate('gauge', name, opts); + }, + histogram(name, opts) { + return getOrCreate('histogram', name, opts); + }, + } satisfies ScopedMetrics; } -export function registerBusinessMetrics() { - if (!_metrics) { - _metrics = createBusinessMetrics(); +/** + * @example + * + * ``` + * metrics.scope.counter('example_count').add(1, { + * attr1: 'example-event' + * }) + * ``` + */ +export const metrics = new Proxy>( + // @ts-expect-error proxied + {}, + { + get(_, scopeName: string) { + let scope = scopes.get(scopeName); + if (!scope) { + scope = make(scopeName); + scopes.set(scopeName, scope); + } + + return scope; + }, } - - return _metrics; -} -export const metrics = registerBusinessMetrics; +); diff --git a/packages/backend/server/src/metrics/opentelemetry.ts b/packages/backend/server/src/metrics/opentelemetry.ts index cd5fcfa358..5b084c8cfc 100644 --- a/packages/backend/server/src/metrics/opentelemetry.ts +++ b/packages/backend/server/src/metrics/opentelemetry.ts @@ -1,5 +1,6 @@ import { MetricExporter } from '@google-cloud/opentelemetry-cloud-monitoring-exporter'; import { TraceExporter } from '@google-cloud/opentelemetry-cloud-trace-exporter'; +import { metrics } from '@opentelemetry/api'; import { CompositePropagator, W3CBaggagePropagator, @@ -16,6 +17,8 @@ import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core' import { SocketIoInstrumentation } from '@opentelemetry/instrumentation-socket.io'; import { ConsoleMetricExporter, + type MeterProvider, + MetricProducer, MetricReader, PeriodicExportingMetricReader, } from '@opentelemetry/sdk-metrics'; @@ -24,10 +27,11 @@ import { BatchSpanProcessor, ConsoleSpanExporter, SpanExporter, + TraceIdRatioBasedSampler, } from '@opentelemetry/sdk-trace-node'; import { PrismaInstrumentation } from '@prisma/instrumentation'; -import { registerBusinessMetrics } from './metrics'; +import { PrismaMetricProducer } from './prisma'; abstract class OpentelemetryFactor { abstract getMetricReader(): MetricReader; @@ -44,9 +48,14 @@ abstract class OpentelemetryFactor { ]; } + getMetricsProducers(): MetricProducer[] { + return [new PrismaMetricProducer()]; + } + create() { const traceExporter = this.getSpanExporter(); return new NodeSDK({ + sampler: new TraceIdRatioBasedSampler(0.1), traceExporter, metricReader: this.getMetricReader(), spanProcessor: new BatchSpanProcessor(traceExporter), @@ -67,7 +76,10 @@ class GCloudOpentelemetryFactor extends OpentelemetryFactor { return new PeriodicExportingMetricReader({ exportIntervalMillis: 30000, exportTimeoutMillis: 10000, - exporter: new MetricExporter(), + exporter: new MetricExporter({ + prefix: 'custom.googleapis.com', + }), + metricProducers: this.getMetricsProducers(), }); } @@ -78,7 +90,9 @@ class GCloudOpentelemetryFactor extends OpentelemetryFactor { class LocalOpentelemetryFactor extends OpentelemetryFactor { override getMetricReader(): MetricReader { - return new PrometheusExporter(); + return new PrometheusExporter({ + metricProducers: this.getMetricsProducers(), + }); } override getSpanExporter(): SpanExporter { @@ -90,6 +104,7 @@ class DebugOpentelemetryFactor extends OpentelemetryFactor { override getMetricReader(): MetricReader { return new PeriodicExportingMetricReader({ exporter: new ConsoleMetricExporter(), + metricProducers: this.getMetricsProducers(), }); } @@ -111,9 +126,30 @@ function createSDK() { return factor?.create(); } +let OPENTELEMETRY_STARTED = false; + +function ensureStarted() { + if (!OPENTELEMETRY_STARTED) { + OPENTELEMETRY_STARTED = true; + start(); + } +} + +function getMeterProvider() { + ensureStarted(); + return metrics.getMeterProvider(); +} + function registerCustomMetrics() { - const host = new HostMetrics({ name: 'instance-host-metrics' }); - host.start(); + const hostMetricsMonitoring = new HostMetrics({ + name: 'instance-host-metrics', + meterProvider: getMeterProvider() as MeterProvider, + }); + hostMetricsMonitoring.start(); +} + +export function getMeter(name = 'business') { + return getMeterProvider().getMeter(name); } export function start() { @@ -122,6 +158,5 @@ export function start() { if (sdk) { sdk.start(); registerCustomMetrics(); - registerBusinessMetrics(); } } diff --git a/packages/backend/server/src/metrics/prisma.ts b/packages/backend/server/src/metrics/prisma.ts new file mode 100644 index 0000000000..6c024c98ff --- /dev/null +++ b/packages/backend/server/src/metrics/prisma.ts @@ -0,0 +1,132 @@ +import { HrTime, ValueType } from '@opentelemetry/api'; +import { hrTime } from '@opentelemetry/core'; +import { Resource } from '@opentelemetry/resources'; +import { + AggregationTemporality, + CollectionResult, + DataPointType, + InstrumentType, + MetricProducer, + ScopeMetrics, +} from '@opentelemetry/sdk-metrics'; + +import { PrismaService } from '../prisma'; + +function transformPrismaKey(key: string) { + // replace first '_' to '/' as a scope prefix + // example: prisma_client_query_duration_seconds_sum -> prisma/client_query_duration_seconds_sum + return key.replace(/_/, '/'); +} + +export class PrismaMetricProducer implements MetricProducer { + private readonly startTime: HrTime = hrTime(); + + async collect(): Promise { + const result: CollectionResult = { + resourceMetrics: { + resource: Resource.EMPTY, + scopeMetrics: [], + }, + errors: [], + }; + + if (!PrismaService.INSTANCE) { + return result; + } + + const prisma = PrismaService.INSTANCE; + + const endTime = hrTime(); + + const metrics = await prisma.$metrics.json(); + const scopeMetrics: ScopeMetrics = { + scope: { + name: '', + }, + metrics: [], + }; + for (const counter of metrics.counters) { + scopeMetrics.metrics.push({ + descriptor: { + name: transformPrismaKey(counter.key), + description: counter.description, + unit: '1', + type: InstrumentType.COUNTER, + valueType: ValueType.INT, + }, + dataPointType: DataPointType.SUM, + aggregationTemporality: AggregationTemporality.CUMULATIVE, + dataPoints: [ + { + startTime: this.startTime, + endTime: endTime, + value: counter.value, + attributes: counter.labels, + }, + ], + isMonotonic: true, + }); + } + + for (const gauge of metrics.gauges) { + scopeMetrics.metrics.push({ + descriptor: { + name: transformPrismaKey(gauge.key), + description: gauge.description, + unit: '1', + type: InstrumentType.UP_DOWN_COUNTER, + valueType: ValueType.INT, + }, + dataPointType: DataPointType.GAUGE, + aggregationTemporality: AggregationTemporality.CUMULATIVE, + dataPoints: [ + { + startTime: this.startTime, + endTime: endTime, + value: gauge.value, + attributes: gauge.labels, + }, + ], + }); + } + + for (const histogram of metrics.histograms) { + const boundaries = []; + const counts = []; + for (const [boundary, count] of histogram.value.buckets) { + boundaries.push(boundary); + counts.push(count); + } + scopeMetrics.metrics.push({ + descriptor: { + name: transformPrismaKey(histogram.key), + description: histogram.description, + unit: 'ms', + type: InstrumentType.HISTOGRAM, + valueType: ValueType.DOUBLE, + }, + dataPointType: DataPointType.HISTOGRAM, + aggregationTemporality: AggregationTemporality.CUMULATIVE, + dataPoints: [ + { + startTime: this.startTime, + endTime: endTime, + value: { + buckets: { + boundaries, + counts, + }, + count: histogram.value.count, + sum: histogram.value.sum, + }, + attributes: histogram.labels, + }, + ], + }); + } + + result.resourceMetrics.scopeMetrics.push(scopeMetrics); + + return result; + } +} diff --git a/packages/backend/server/src/metrics/utils.ts b/packages/backend/server/src/metrics/utils.ts index 1c4fa313ff..e4d16a3492 100644 --- a/packages/backend/server/src/metrics/utils.ts +++ b/packages/backend/server/src/metrics/utils.ts @@ -1,8 +1,9 @@ import { Attributes } from '@opentelemetry/api'; -import { getMeter } from './metrics'; +import { KnownMetricScopes, metrics } from './metrics'; export const CallTimer = ( + scope: KnownMetricScopes, name: string, attrs?: Attributes ): MethodDecorator => { @@ -18,9 +19,11 @@ export const CallTimer = ( } desc.value = function (...args: any[]) { - const timer = getMeter().createHistogram(name, { + const timer = metrics[scope].histogram(name, { description: `function call time costs of ${name}`, + unit: 'ms', }); + const start = Date.now(); const end = () => { @@ -48,6 +51,7 @@ export const CallTimer = ( }; export const CallCounter = ( + scope: KnownMetricScopes, name: string, attrs?: Attributes ): MethodDecorator => { @@ -63,7 +67,7 @@ export const CallCounter = ( } desc.value = function (...args: any[]) { - const count = getMeter().createCounter(name, { + const count = metrics[scope].counter(name, { description: `function call counter of ${name}`, }); diff --git a/packages/backend/server/src/modules/auth/next-auth.controller.ts b/packages/backend/server/src/modules/auth/next-auth.controller.ts index f8d8faa043..9d717f0121 100644 --- a/packages/backend/server/src/modules/auth/next-auth.controller.ts +++ b/packages/backend/server/src/modules/auth/next-auth.controller.ts @@ -89,12 +89,13 @@ export class NextAuthController { res.redirect(`/signin${query}`); return; } - metrics().authCounter.add(1); const [action, providerId] = req.url // start with request url .slice(BASE_URL.length) // make relative to baseUrl .replace(/\?.*/, '') // remove query part, use only path part .split('/') as [AuthAction, string]; // as array of strings; + metrics.auth.counter('call_counter').add(1, { action, providerId }); + const credentialsSignIn = req.method === 'POST' && providerId === 'credentials'; let userId: string | undefined; @@ -126,7 +127,9 @@ export class NextAuthController { const options = this.nextAuthOptions; if (req.method === 'POST' && action === 'session') { if (typeof req.body !== 'object' || typeof req.body.data !== 'object') { - metrics().authFailCounter.add(1, { reason: 'invalid_session_data' }); + metrics.auth + .counter('call_fails_counter') + .add(1, { reason: 'invalid_session_data' }); throw new BadRequestException(`Invalid new session data`); } const user = await this.updateSession(req, req.body.data); @@ -209,9 +212,10 @@ export class NextAuthController { if (redirect?.endsWith('api/auth/error?error=AccessDenied')) { this.logger.log(`Early access redirect headers: ${req.headers}`); - metrics().authFailCounter.add(1, { - reason: 'no_early_access_permission', - }); + metrics.auth + .counter('call_fails_counter') + .add(1, { reason: 'no_early_access_permission' }); + if ( !req.headers?.referer || checkUrlOrigin(req.headers.referer, 'https://accounts.google.com') diff --git a/packages/backend/server/src/modules/doc/history.ts b/packages/backend/server/src/modules/doc/history.ts index 2a6dc47554..7502355950 100644 --- a/packages/backend/server/src/modules/doc/history.ts +++ b/packages/backend/server/src/modules/doc/history.ts @@ -68,7 +68,11 @@ export class DocHistoryManager { // safe to ignore // only happens when duplicated history record created in multi processes }); - metrics().docHistoryCounter.add(1, {}); + metrics.doc + .counter('history_created_counter', { + description: 'How many times the snapshot history created', + }) + .add(1); this.logger.log( `History created for ${snapshot.id} in workspace ${snapshot.workspaceId}.` ); @@ -182,7 +186,11 @@ export class DocHistoryManager { // which is not the solution in CRDT. // let user revert in client and update the data in sync system // `await this.db.snapshot.update();` - metrics().docRecoverCounter.add(1, {}); + metrics.doc + .counter('history_recovered_counter', { + description: 'How many times history recovered request happened', + }) + .add(1); return history.timestamp; } diff --git a/packages/backend/server/src/modules/doc/manager.ts b/packages/backend/server/src/modules/doc/manager.ts index 3362bc5ced..326902b35f 100644 --- a/packages/backend/server/src/modules/doc/manager.ts +++ b/packages/backend/server/src/modules/doc/manager.ts @@ -125,13 +125,13 @@ export class DocManager implements OnModuleInit, OnModuleDestroy { this.config.doc.manager.experimentalMergeWithJwstCodec && updates.length < 100 /* avoid overloading */ ) { - metrics().jwstCodecMerge.add(1); + metrics.jwst.counter('codec_merge_counter').add(1); const yjsResult = Buffer.from(encodeStateAsUpdate(doc)); let log = false; try { const jwstResult = jwstMergeUpdates(updates); if (!compare(yjsResult, jwstResult)) { - metrics().jwstCodecDidnotMatch.add(1); + metrics.jwst.counter('codec_not_match').add(1); this.logger.warn( `jwst codec result doesn't match yjs codec result for: ${guid}` ); @@ -142,7 +142,7 @@ export class DocManager implements OnModuleInit, OnModuleDestroy { } } } catch (e) { - metrics().jwstCodecFail.add(1); + metrics.jwst.counter('codec_fails_counter').add(1); this.logger.warn(`jwst apply update failed for ${guid}: ${e}`); log = true; } finally { diff --git a/packages/backend/server/src/modules/sync/events/events.gateway.ts b/packages/backend/server/src/modules/sync/events/events.gateway.ts index 2921411860..f847842c1c 100644 --- a/packages/backend/server/src/modules/sync/events/events.gateway.ts +++ b/packages/backend/server/src/modules/sync/events/events.gateway.ts @@ -45,6 +45,7 @@ export const GatewayErrorWrapper = (): MethodDecorator => { try { result = originalMethod.apply(this, args); } catch (e) { + metrics.socketio.counter('unhandled_errors').add(1); return { error: new InternalError(e as Error), }; @@ -52,6 +53,7 @@ export const GatewayErrorWrapper = (): MethodDecorator => { if (result instanceof Promise) { return result.catch(e => { + metrics.socketio.counter('unhandled_errors').add(1); return { error: new InternalError(e), }; @@ -68,7 +70,7 @@ export const GatewayErrorWrapper = (): MethodDecorator => { const SubscribeMessage = (event: string) => applyDecorators( GatewayErrorWrapper(), - CallTimer('socket_io_event_duration', { event }), + CallTimer('socketio', 'event_duration', { event }), RawSubscribeMessage(event) ); @@ -104,12 +106,12 @@ export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect { handleConnection() { this.connectionCount++; - metrics().socketIOConnectionGauge(this.connectionCount); + metrics.socketio.gauge('realtime_connections').record(this.connectionCount); } handleDisconnect() { this.connectionCount--; - metrics().socketIOConnectionGauge(this.connectionCount); + metrics.socketio.gauge('realtime_connections').record(this.connectionCount); } @Auth() diff --git a/packages/backend/server/src/modules/workspaces/controller.ts b/packages/backend/server/src/modules/workspaces/controller.ts index 2d20272f94..47b64cf4aa 100644 --- a/packages/backend/server/src/modules/workspaces/controller.ts +++ b/packages/backend/server/src/modules/workspaces/controller.ts @@ -34,7 +34,7 @@ export class WorkspacesController { // // NOTE: because graphql can't represent a File, so we have to use REST API to get blob @Get('/:id/blobs/:name') - @CallTimer('doc_controller', { method: 'get_blob' }) + @CallTimer('controllers', 'workspace_get_blob') async blob( @Param('id') workspaceId: string, @Param('name') name: string, @@ -59,7 +59,7 @@ export class WorkspacesController { @Get('/:id/docs/:guid') @Auth() @Publicable() - @CallTimer('doc_controller', { method: 'get_doc' }) + @CallTimer('controllers', 'workspace_get_doc') async doc( @CurrentUser() user: UserType | undefined, @Param('id') ws: string, @@ -106,7 +106,7 @@ export class WorkspacesController { @Get('/:id/docs/:guid/histories/:timestamp') @Auth() - @CallTimer('doc_controller', { method: 'get_history' }) + @CallTimer('controllers', 'workspace_get_history') async history( @CurrentUser() user: UserType, @Param('id') ws: string, diff --git a/packages/backend/server/src/prisma/service.ts b/packages/backend/server/src/prisma/service.ts index 25ac6b92f4..76fb92b392 100644 --- a/packages/backend/server/src/prisma/service.ts +++ b/packages/backend/server/src/prisma/service.ts @@ -7,6 +7,13 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + static INSTANCE: PrismaService | null = null; + + constructor() { + super(); + PrismaService.INSTANCE = this; + } + async onModuleInit() { await this.$connect(); } diff --git a/yarn.lock b/yarn.lock index f56433d137..edfee1f4c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -734,6 +734,7 @@ __metadata: "@opentelemetry/instrumentation-ioredis": "npm:^0.35.3" "@opentelemetry/instrumentation-nestjs-core": "npm:^0.33.3" "@opentelemetry/instrumentation-socket.io": "npm:^0.34.3" + "@opentelemetry/resources": "npm:^1.18.1" "@opentelemetry/sdk-metrics": "npm:^1.18.1" "@opentelemetry/sdk-node": "npm:^0.45.1" "@opentelemetry/sdk-trace-node": "npm:^1.18.1" @@ -9017,7 +9018,7 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/resources@npm:1.18.1": +"@opentelemetry/resources@npm:1.18.1, @opentelemetry/resources@npm:^1.18.1": version: 1.18.1 resolution: "@opentelemetry/resources@npm:1.18.1" dependencies: From a2b8a073cc7630504430aa9a7b94ec77799d5bdd Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 09:51:14 +0000 Subject: [PATCH 25/35] ci: pr title lint job does not need to install all dependencies (#5124) --- .github/workflows/pr-title-lint.yml | 9 +++++--- .../commitlint/.commitlintrc.json | 0 tools/commitlint/README.md | 3 +++ tools/commitlint/package.json | 10 +++++++++ yarn.lock | 22 +++++++++++++++++++ 5 files changed, 41 insertions(+), 3 deletions(-) rename .commitlintrc.json => tools/commitlint/.commitlintrc.json (100%) create mode 100644 tools/commitlint/README.md create mode 100644 tools/commitlint/package.json diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml index 75ecb019bf..283a1048c7 100644 --- a/.github/workflows/pr-title-lint.yml +++ b/.github/workflows/pr-title-lint.yml @@ -19,7 +19,10 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup Node.js - uses: ./.github/actions/setup-node + uses: actions/setup-node@v3 with: - electron-install: false - - run: echo "${{ github.event.pull_request.title }}" | yarn dlx commitlint -g ./.commitlintrc.json + cache: 'yarn' + node-version-file: '.nvmrc' + - name: Install dependencies + run: yarn workspaces focus @affine/commitlint-config + - run: echo "${{ github.event.pull_request.title }}" | yarn workspace @affine/commitlint-config commitlint -g ./.commitlintrc.json diff --git a/.commitlintrc.json b/tools/commitlint/.commitlintrc.json similarity index 100% rename from .commitlintrc.json rename to tools/commitlint/.commitlintrc.json diff --git a/tools/commitlint/README.md b/tools/commitlint/README.md new file mode 100644 index 0000000000..34ee016ab5 --- /dev/null +++ b/tools/commitlint/README.md @@ -0,0 +1,3 @@ +# commitlint + +This package defines commitlint and it's dependencies, so that it can be used in a CI pipeline to minimize the installation time. diff --git a/tools/commitlint/package.json b/tools/commitlint/package.json new file mode 100644 index 0000000000..6a47516f4b --- /dev/null +++ b/tools/commitlint/package.json @@ -0,0 +1,10 @@ +{ + "name": "@affine/commitlint-config", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "commitlint": "^18.4.3" + } +} diff --git a/yarn.lock b/yarn.lock index edfee1f4c1..1082ad531c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -204,6 +204,16 @@ __metadata: languageName: unknown linkType: soft +"@affine/commitlint-config@workspace:tools/commitlint": + version: 0.0.0-use.local + resolution: "@affine/commitlint-config@workspace:tools/commitlint" + dependencies: + "@commitlint/cli": "npm:^18.4.3" + "@commitlint/config-conventional": "npm:^18.4.3" + commitlint: "npm:^18.4.3" + languageName: unknown + linkType: soft + "@affine/component@workspace:*, @affine/component@workspace:packages/frontend/component": version: 0.0.0-use.local resolution: "@affine/component@workspace:packages/frontend/component" @@ -17671,6 +17681,18 @@ __metadata: languageName: node linkType: hard +"commitlint@npm:^18.4.3": + version: 18.4.3 + resolution: "commitlint@npm:18.4.3" + dependencies: + "@commitlint/cli": "npm:^18.4.3" + "@commitlint/types": "npm:^18.4.3" + bin: + commitlint: cli.js + checksum: e37da4f1cd218381a7810b2e74a553bd05b01776d76dc3038b6e805ea6640308dfa0372c2a976ab5d8c34577128fe6df945e4718c27732dd0d79b517f066fee5 + languageName: node + linkType: hard + "common-path-prefix@npm:^3.0.0": version: 3.0.0 resolution: "common-path-prefix@npm:3.0.0" From feddfdc5c96196c0155669c566b3dc58861b048f Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 10:11:32 +0000 Subject: [PATCH 26/35] ci: upgrade setup-node action (#5125) --- .github/actions/setup-node/action.yml | 2 +- .github/workflows/pr-title-lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index f00e6b47ef..c9dbcdba32 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -41,7 +41,7 @@ runs: using: 'composite' steps: - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' registry-url: https://npm.pkg.github.com diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml index 283a1048c7..5cab44c6d6 100644 --- a/.github/workflows/pr-title-lint.yml +++ b/.github/workflows/pr-title-lint.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: 'yarn' node-version-file: '.nvmrc' From 2a4741e9ab6e30be7f3d5fbdfb80cc4408b4c6f1 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 10:11:34 +0000 Subject: [PATCH 27/35] chore: add lint-staged command to pre-commit file (#5126) --- .husky/pre-commit | 2 +- packages/frontend/native/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 76188b6556..2f1bef1ce9 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn lint:ox +yarn lint-staged && yarn lint:ox && cargo fmt --all && git add . diff --git a/packages/frontend/native/src/lib.rs b/packages/frontend/native/src/lib.rs index ef92a6727e..fa8de0f14a 100644 --- a/packages/frontend/native/src/lib.rs +++ b/packages/frontend/native/src/lib.rs @@ -1,2 +1,2 @@ +pub mod hashcash; pub mod sqlite; -pub mod hashcash; \ No newline at end of file From 3fc515491b9fc03b07a18d41c1b896d0c5d8693d Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 10:11:36 +0000 Subject: [PATCH 28/35] chore: remove useless circular command (#5127) --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 78cf773d3b..903d543533 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "test": "vitest --run", "test:ui": "vitest --ui", "test:coverage": "vitest run --coverage", - "circular": "madge --circular --ts-config ./tsconfig.json ./packages/frontend/core/src/pages/**/*.tsx ./packages/frontend/core/src/index.tsx ./packages/frontend/electron/src/*/index.ts", "typecheck": "tsc -b tsconfig.json --diagnostics", "postinstall": "node ./scripts/check-version.mjs && yarn i18n-codegen gen && yarn husky install" }, From 6fca1b43f0160b3a08f008b8f050d446ab0837f2 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 10:11:38 +0000 Subject: [PATCH 29/35] chore: add rust-toolchain file (#5129) --- .github/actions/build-rust/action.yml | 13 ++++++++++++- README.md | 2 +- rust-toolchain.toml | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.github/actions/build-rust/action.yml b/.github/actions/build-rust/action.yml index 3ef2b87907..f187641f85 100644 --- a/.github/actions/build-rust/action.yml +++ b/.github/actions/build-rust/action.yml @@ -14,10 +14,17 @@ inputs: runs: using: 'composite' steps: + - name: Print rustup toolchain version + shell: bash + id: rustup-version + run: | + export RUST_TOOLCHAIN_VERSION="$(grep 'channel' rust-toolchain.toml | head -1 | awk -F '"' '{print $2}')" + echo "Rust toolchain version: $RUST_TOOLCHAIN_VERSION" + echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> "$GITHUB_OUTPUT" - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: stable + toolchain: '${{ steps.rustup-version.outputs.RUST_TOOLCHAIN_VERSION }}' targets: ${{ inputs.target }} env: CARGO_INCREMENTAL: '1' @@ -49,6 +56,8 @@ runs: run: | export CC=x86_64-unknown-linux-gnu-gcc export CC_x86_64_unknown_linux_gnu=x86_64-unknown-linux-gnu-gcc + rm -rf /usr/local/rustup/downloads/* + rustup target add x86_64-unknown-linux-gnu export RUSTFLAGS="-C debuginfo=1" yarn workspace ${{ inputs.package }} nx build ${{ inputs.package }} --target ${{ inputs.target }} if [ -d "node_modules/.cache" ]; then @@ -66,6 +75,8 @@ runs: options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build -e NX_CLOUD_ACCESS_TOKEN=${{ inputs.nx_token }} run: | export RUSTFLAGS="-C debuginfo=1" + rm -rf /usr/local/rustup/downloads/* + rustup target add aarch64-unknown-linux-gnu yarn workspace ${{ inputs.package }} nx build ${{ inputs.package }} --target ${{ inputs.target }} if [ -d "node_modules/.cache" ]; then chmod -R 777 node_modules/.cache diff --git a/README.md b/README.md index 10562298e3..513a0ff604 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ See [LICENSE] for details. [jobs available]: ./docs/jobs.md [latest packages]: https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted [contributor license agreement]: https://github.com/toeverything/affine/edit/canary/.github/CLA.md -[rust-version-icon]: https://img.shields.io/badge/Rust-1.71.0-dea584 +[rust-version-icon]: https://img.shields.io/badge/Rust-1.74.0-dea584 [stars-icon]: https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars [codecov]: https://codecov.io/gh/toeverything/affine/branch/canary/graphs/badge.svg?branch=canary [node-version-icon]: https://img.shields.io/badge/node-%3E=18.16.1-success diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..e7f52bc821 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.74.0" +profile = "default" From 906d224fa9ccfcb8152b5cc6dac17ea3a2c20b8e Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 29 Nov 2023 10:11:40 +0000 Subject: [PATCH 30/35] chore(native): fix useless import (#5130) --- packages/frontend/native/src/sqlite/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/native/src/sqlite/mod.rs b/packages/frontend/native/src/sqlite/mod.rs index ed1fb1c931..7738721167 100644 --- a/packages/frontend/native/src/sqlite/mod.rs +++ b/packages/frontend/native/src/sqlite/mod.rs @@ -1,5 +1,5 @@ use chrono::NaiveDateTime; -use napi::bindgen_prelude::{Buffer, FromNapiValue, ToNapiValue, Uint8Array}; +use napi::bindgen_prelude::{Buffer, Uint8Array}; use napi_derive::napi; use sqlx::{ migrate::MigrateDatabase, From 23518cae168c688f362b17c7750d3c90aefabb3a Mon Sep 17 00:00:00 2001 From: JimmFly <447268514@qq.com> Date: Wed, 29 Nov 2023 13:31:25 +0000 Subject: [PATCH 31/35] feat(core): add manual check for updates (#4957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit work for #4523 add `appBuildType` to `runtimeConfig` add `useAppUpdater` to manage client updates ### 🤖[[deprecated]](https://githubnext.com/copilot-for-prs-sunset) Generated by Copilot at cdd012c This pull request refactors and enhances the update functionality for the frontend. It introduces a new custom hook `useAppUpdater` that simplifies the update logic and state management, and uses it in various components and commands. It also adds more options and feedback for the user to control and monitor the update process, such as manual download, auto-check, and auto-download toggles, and update status and progress indicators. It also updates the `AboutAffine` component to show the app icon, version, and build type. It also adds new translations, dependencies, types, and schemas related to the update functionality. image --- packages/common/env/src/global.ts | 6 + packages/common/infra/src/type.ts | 8 + .../app-updater-button/index.jotai.ts | 69 ------- .../app-sidebar/app-updater-button/index.tsx | 70 +++---- .../frontend/core/.webpack/runtime-config.ts | 4 + .../core/src/commands/affine-updates.tsx | 2 +- .../general-setting/about/index.tsx | 59 ++++-- .../general-setting/about/style.css.ts | 34 ++++ .../about/update-check-section.tsx | 165 ++++++++++++++++ .../src/components/root-app-sidebar/index.tsx | 29 +++ packages/frontend/core/src/pages/open-app.tsx | 4 +- .../src/main/updater/electron-updater.ts | 51 ++++- .../electron/src/main/updater/index.ts | 23 ++- packages/frontend/hooks/package.json | 1 + .../frontend/hooks/src/use-app-updater.ts | 186 ++++++++++++++++++ packages/frontend/i18n/src/resources/en.json | 11 ++ yarn.lock | 1 + 17 files changed, 581 insertions(+), 142 deletions(-) delete mode 100644 packages/frontend/component/src/components/app-sidebar/app-updater-button/index.jotai.ts create mode 100644 packages/frontend/core/src/components/affine/setting-modal/general-setting/about/update-check-section.tsx create mode 100644 packages/frontend/hooks/src/use-app-updater.ts diff --git a/packages/common/env/src/global.ts b/packages/common/env/src/global.ts index 7e2125a38e..88c86f24ad 100644 --- a/packages/common/env/src/global.ts +++ b/packages/common/env/src/global.ts @@ -39,6 +39,12 @@ export const runtimeFlagsSchema = z.object({ editorFlags: blockSuiteFeatureFlags, appVersion: z.string(), editorVersion: z.string(), + appBuildType: z.union([ + z.literal('stable'), + z.literal('beta'), + z.literal('internal'), + z.literal('canary'), + ]), }); export type BlockSuiteFeatureFlags = z.infer; diff --git a/packages/common/infra/src/type.ts b/packages/common/infra/src/type.ts index cd459f2eca..4467e27734 100644 --- a/packages/common/infra/src/type.ts +++ b/packages/common/infra/src/type.ts @@ -190,9 +190,17 @@ export interface UpdateMeta { allowAutoUpdate: boolean; } +export type UpdaterConfig = { + autoCheckUpdate: boolean; + autoDownloadUpdate: boolean; +}; + export type UpdaterHandlers = { currentVersion: () => Promise; quitAndInstall: () => Promise; + downloadUpdate: () => Promise; + getConfig: () => Promise; + setConfig: (newConfig: Partial) => Promise; checkForUpdatesAndNotify: () => Promise<{ version: string } | null>; }; diff --git a/packages/frontend/component/src/components/app-sidebar/app-updater-button/index.jotai.ts b/packages/frontend/component/src/components/app-sidebar/app-updater-button/index.jotai.ts deleted file mode 100644 index c438a2ce9d..0000000000 --- a/packages/frontend/component/src/components/app-sidebar/app-updater-button/index.jotai.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { isBrowser } from '@affine/env/constant'; -import type { UpdateMeta } from '@toeverything/infra/type'; -import { atomWithObservable, atomWithStorage } from 'jotai/utils'; -import { Observable } from 'rxjs'; - -// todo: move to utils? -function rpcToObservable< - T, - H extends () => Promise, - E extends (callback: (t: T) => void) => () => void, ->( - initialValue: T | null, - { - event, - handler, - onSubscribe, - }: { - event?: E; - handler?: H; - onSubscribe?: () => void; - } -): Observable { - return new Observable(subscriber => { - subscriber.next(initialValue); - onSubscribe?.(); - if (!isBrowser || !environment.isDesktop || !event) { - subscriber.complete(); - return; - } - handler?.() - .then(t => { - subscriber.next(t); - }) - .catch(err => { - subscriber.error(err); - }); - return event(t => { - subscriber.next(t); - }); - }); -} - -export const updateReadyAtom = atomWithObservable(() => { - return rpcToObservable(null as UpdateMeta | null, { - event: window.events?.updater.onUpdateReady, - }); -}); - -export const updateAvailableAtom = atomWithObservable(() => { - return rpcToObservable(null as UpdateMeta | null, { - event: window.events?.updater.onUpdateAvailable, - onSubscribe: () => { - window.apis?.updater.checkForUpdatesAndNotify().catch(err => { - console.error(err); - }); - }, - }); -}); - -export const downloadProgressAtom = atomWithObservable(() => { - return rpcToObservable(null as number | null, { - event: window.events?.updater.onDownloadProgress, - }); -}); - -export const changelogCheckedAtom = atomWithStorage>( - 'affine:client-changelog-checked', - {} -); diff --git a/packages/frontend/component/src/components/app-sidebar/app-updater-button/index.tsx b/packages/frontend/component/src/components/app-sidebar/app-updater-button/index.tsx index a3afe7a00d..4d7a442c81 100644 --- a/packages/frontend/component/src/components/app-sidebar/app-updater-button/index.tsx +++ b/packages/frontend/component/src/components/app-sidebar/app-updater-button/index.tsx @@ -1,18 +1,21 @@ -import { isBrowser, Unreachable } from '@affine/env/constant'; +import { Unreachable } from '@affine/env/constant'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons'; import { Tooltip } from '@toeverything/components/tooltip'; -import clsx from 'clsx'; -import { atom, useAtomValue, useSetAtom } from 'jotai'; -import { startTransition, useCallback, useState } from 'react'; - -import * as styles from './index.css'; import { changelogCheckedAtom, + currentChangelogUnreadAtom, + currentVersionAtom, downloadProgressAtom, updateAvailableAtom, updateReadyAtom, -} from './index.jotai'; + useAppUpdater, +} from '@toeverything/hooks/use-app-updater'; +import clsx from 'clsx'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { startTransition, useCallback } from 'react'; + +import * as styles from './index.css'; export interface AddPageButtonPureProps { onClickUpdate: () => void; @@ -29,26 +32,6 @@ export interface AddPageButtonPureProps { style?: React.CSSProperties; } -const currentVersionAtom = atom(async () => { - if (!isBrowser) { - return null; - } - const currentVersion = await window.apis?.updater.currentVersion(); - return currentVersion; -}); - -const currentChangelogUnreadAtom = atom(async get => { - if (!isBrowser) { - return false; - } - const mapping = get(changelogCheckedAtom); - const currentVersion = await get(currentVersionAtom); - if (currentVersion) { - return !mapping[currentVersion]; - } - return false; -}); - export function AppUpdaterButtonPure({ updateReady, onClickUpdate, @@ -198,12 +181,12 @@ export function AppUpdaterButton({ const currentChangelogUnread = useAtomValue(currentChangelogUnreadAtom); const updateReady = useAtomValue(updateReadyAtom); const updateAvailable = useAtomValue(updateAvailableAtom); - const currentVersion = useAtomValue(currentVersionAtom); const downloadProgress = useAtomValue(downloadProgressAtom); + const currentVersion = useAtomValue(currentVersionAtom); + const { quitAndInstall, appQuitting } = useAppUpdater(); const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom); - const [appQuitting, setAppQuitting] = useState(false); - const onDismissCurrentChangelog = useCallback(() => { + const dismissCurrentChangelog = useCallback(() => { if (!currentVersion) { return; } @@ -216,13 +199,10 @@ export function AppUpdaterButton({ }) ); }, [currentVersion, setChangelogCheckAtom]); - const onClickUpdate = useCallback(() => { + + const handleClickUpdate = useCallback(() => { if (updateReady) { - setAppQuitting(true); - window.apis?.updater.quitAndInstall().catch(err => { - // TODO: add error toast here - console.error(err); - }); + quitAndInstall(); } else if (updateAvailable) { if (updateAvailable.allowAutoUpdate) { // wait for download to finish @@ -234,23 +214,25 @@ export function AppUpdaterButton({ } } else if (currentChangelogUnread) { window.open(runtimeConfig.changelogUrl, '_blank'); - onDismissCurrentChangelog(); + dismissCurrentChangelog(); } else { throw new Unreachable(); } }, [ - currentChangelogUnread, - currentVersion, - onDismissCurrentChangelog, - updateAvailable, updateReady, + quitAndInstall, + updateAvailable, + currentChangelogUnread, + dismissCurrentChangelog, + currentVersion, ]); + return ( ); } - -export * from './index.jotai'; diff --git a/packages/frontend/core/.webpack/runtime-config.ts b/packages/frontend/core/.webpack/runtime-config.ts index e54ac2410d..d613fa11aa 100644 --- a/packages/frontend/core/.webpack/runtime-config.ts +++ b/packages/frontend/core/.webpack/runtime-config.ts @@ -38,18 +38,21 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { editorFlags, appVersion: packageJson.version, editorVersion: packageJson.dependencies['@blocksuite/editor'], + appBuildType: 'stable', }, get beta() { return { ...this.stable, enablePageHistory: false, serverUrlPrefix: 'https://insider.affine.pro', + appBuildType: 'beta' as const, }; }, get internal() { return { ...this.stable, serverUrlPrefix: 'https://insider.affine.pro', + appBuildType: 'internal' as const, }; }, // canary will be aggressive and enable all features @@ -82,6 +85,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { editorFlags, appVersion: packageJson.version, editorVersion: packageJson.dependencies['@blocksuite/editor'], + appBuildType: 'canary', }, }; diff --git a/packages/frontend/core/src/commands/affine-updates.tsx b/packages/frontend/core/src/commands/affine-updates.tsx index 4ccc0a4af8..da4e167916 100644 --- a/packages/frontend/core/src/commands/affine-updates.tsx +++ b/packages/frontend/core/src/commands/affine-updates.tsx @@ -1,6 +1,6 @@ -import { updateReadyAtom } from '@affine/component/app-sidebar/app-updater-button'; import type { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ResetIcon } from '@blocksuite/icons'; +import { updateReadyAtom } from '@toeverything/hooks/use-app-updater'; import { registerAffineCommand } from '@toeverything/infra/command'; import type { createStore } from 'jotai'; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/index.tsx index 60e719f685..f25edc5001 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/index.tsx @@ -4,14 +4,39 @@ import { SettingRow } from '@affine/component/setting-components'; import { SettingWrapper } from '@affine/component/setting-components'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons'; +import { useAppUpdater } from '@toeverything/hooks/use-app-updater'; +import { useCallback } from 'react'; import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper'; +import { appIconMap, appNames } from '../../../../../pages/open-app'; import { relatedLinks } from './config'; -import { communityItem, communityWrapper, link } from './style.css'; +import * as styles from './style.css'; +import { UpdateCheckSection } from './update-check-section'; export const AboutAffine = () => { const t = useAFFiNEI18N(); const { appSettings, updateSettings } = useAppSettingHelper(); + const { toggleAutoCheck, toggleAutoDownload } = useAppUpdater(); + const channel = runtimeConfig.appBuildType; + const appIcon = appIconMap[channel]; + const appName = appNames[channel]; + + const onSwitchAutoCheck = useCallback( + (checked: boolean) => { + toggleAutoCheck(checked); + updateSettings('autoCheckUpdate', checked); + }, + [toggleAutoCheck, updateSettings] + ); + + const onSwitchAutoDownload = useCallback( + (checked: boolean) => { + toggleAutoDownload(checked); + updateSettings('autoDownloadUpdate', checked); + }, + [toggleAutoDownload, updateSettings] + ); + return ( <> { /> + className={styles.appImageRow} + > + {appName} + - {runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? ( + {environment.isDesktop ? ( <> - + updateSettings('autoCheckUpdate', checked)} + onChange={onSwitchAutoCheck} /> { ]()} > updateSettings('autoCheckUpdate', checked)} + checked={appSettings.autoDownloadUpdate} + onChange={onSwitchAutoDownload} /> { { { -
+
{relatedLinks.map(({ icon, title, link }) => { return (
{ window.open(link, '_blank'); }} @@ -107,7 +132,7 @@ export const AboutAffine = () => { { { + const t = useAFFiNEI18N(); + const isCheckingForUpdates = useAtomValue(isCheckingForUpdatesAtom); + const updateAvailable = useAtomValue(updateAvailableAtom); + const updateReady = useAtomValue(updateReadyAtom); + const downloadProgress = useAtomValue(downloadProgressAtom); + + const buttonLabel = useMemo(() => { + if (updateAvailable && downloadProgress === null) { + return t['com.affine.aboutAFFiNE.checkUpdate.button.download'](); + } + if (updateReady) { + return t['com.affine.aboutAFFiNE.checkUpdate.button.restart'](); + } + if ( + checkUpdateStatus === CheckUpdateStatus.LATEST || + checkUpdateStatus === CheckUpdateStatus.ERROR + ) { + return t['com.affine.aboutAFFiNE.checkUpdate.button.retry'](); + } + return t['com.affine.aboutAFFiNE.checkUpdate.button.check'](); + }, [checkUpdateStatus, downloadProgress, t, updateAvailable, updateReady]); + + const subtitleLabel = useMemo(() => { + if (updateAvailable && downloadProgress === null) { + return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.update-available']({ + version: updateAvailable.version, + }); + } else if (isCheckingForUpdates) { + return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.checking'](); + } else if (updateAvailable && downloadProgress !== null) { + return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.downloading'](); + } else if (updateReady) { + return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.restart'](); + } else if (checkUpdateStatus === CheckUpdateStatus.ERROR) { + return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.error'](); + } else if (checkUpdateStatus === CheckUpdateStatus.LATEST) { + return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.latest'](); + } + return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.check'](); + }, [ + checkUpdateStatus, + downloadProgress, + isCheckingForUpdates, + t, + updateAvailable, + updateReady, + ]); + + const subtitle = useMemo(() => { + return ( + + {isCheckingForUpdates ? : null} + {subtitleLabel} + + ); + }, [ + checkUpdateStatus, + downloadProgress, + isCheckingForUpdates, + subtitleLabel, + updateAvailable, + updateReady, + ]); + + return { subtitle, buttonLabel }; +}; + +export const UpdateCheckSection = () => { + const t = useAFFiNEI18N(); + const { checkForUpdates, downloadUpdate, quitAndInstall } = useAppUpdater(); + const updateAvailable = useAtomValue(updateAvailableAtom); + const updateReady = useAtomValue(updateReadyAtom); + const downloadProgress = useAtomValue(downloadProgressAtom); + const [checkUpdateStatus, setCheckUpdateStatus] = useState( + CheckUpdateStatus.UNCHECK + ); + + const { buttonLabel, subtitle } = useUpdateStatusLabels(checkUpdateStatus); + + const asyncCheckForUpdates = useAsyncCallback(async () => { + let statusCheck = CheckUpdateStatus.UNCHECK; + try { + const status = await checkForUpdates(); + + if (status === null) { + statusCheck = CheckUpdateStatus.ERROR; + } else if (status === false) { + statusCheck = CheckUpdateStatus.LATEST; + } else if (typeof status === 'string') { + statusCheck = CheckUpdateStatus.UPDATE_AVAILABLE; + } + } catch (e) { + console.error(e); + statusCheck = CheckUpdateStatus.ERROR; + } finally { + setCheckUpdateStatus(statusCheck); + } + }, [checkForUpdates]); + + const handleClick = useCallback(() => { + if (updateAvailable && downloadProgress === null) { + return downloadUpdate(); + } + if (updateReady) { + return quitAndInstall(); + } + asyncCheckForUpdates(); + }, [ + asyncCheckForUpdates, + downloadProgress, + downloadUpdate, + quitAndInstall, + updateAvailable, + updateReady, + ]); + + return ( + + + + ); +}; diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx index 3b82e54e26..e08826e098 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -20,6 +20,11 @@ import type { Page } from '@blocksuite/store'; import { useDroppable } from '@dnd-kit/core'; import { Menu } from '@toeverything/components/menu'; import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; +import { + isAutoCheckUpdateAtom, + isAutoDownloadUpdateAtom, + useAppUpdater, +} from '@toeverything/hooks/use-app-updater'; import { useAtom, useAtomValue } from 'jotai'; import type { HTMLAttributes, ReactElement } from 'react'; import { forwardRef, useCallback, useEffect, useMemo } from 'react'; @@ -102,6 +107,10 @@ export const RootAppSidebar = ({ }: RootAppSidebarProps): ReactElement => { const currentWorkspaceId = currentWorkspace.id; const { appSettings } = useAppSettingHelper(); + const { toggleAutoCheck, toggleAutoDownload } = useAppUpdater(); + const { autoCheckUpdate, autoDownloadUpdate } = appSettings; + const isAutoDownload = useAtomValue(isAutoDownloadUpdateAtom); + const isAutoCheck = useAtomValue(isAutoCheckUpdateAtom); const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace; const t = useAFFiNEI18N(); const [openUserWorkspaceList, setOpenUserWorkspaceList] = useAtom( @@ -150,6 +159,26 @@ export const RootAppSidebar = ({ } }, [sidebarOpen]); + useEffect(() => { + if (!environment.isDesktop) { + return; + } + + if (isAutoCheck !== autoCheckUpdate) { + toggleAutoCheck(autoCheckUpdate); + } + if (isAutoDownload !== autoDownloadUpdate) { + toggleAutoDownload(autoDownloadUpdate); + } + }, [ + autoCheckUpdate, + autoDownloadUpdate, + isAutoCheck, + isAutoDownload, + toggleAutoCheck, + toggleAutoDownload, + ]); + const [history, setHistory] = useHistoryAtom(); const router = useMemo(() => { return { diff --git a/packages/frontend/core/src/pages/open-app.tsx b/packages/frontend/core/src/pages/open-app.tsx index 7e6df768e6..b26da450ff 100644 --- a/packages/frontend/core/src/pages/open-app.tsx +++ b/packages/frontend/core/src/pages/open-app.tsx @@ -37,14 +37,14 @@ const schemaToChanel = { 'affine-dev': 'canary', // dev does not have a dedicated app. use canary as the placeholder. } as Record; -const appIconMap = { +export const appIconMap = { stable: '/imgs/app-icon-stable.ico', canary: '/imgs/app-icon-canary.ico', beta: '/imgs/app-icon-beta.ico', internal: '/imgs/app-icon-internal.ico', } satisfies Record; -const appNames = { +export const appNames = { stable: 'AFFiNE', canary: 'AFFiNE Canary', beta: 'AFFiNE Beta', diff --git a/packages/frontend/electron/src/main/updater/electron-updater.ts b/packages/frontend/electron/src/main/updater/electron-updater.ts index 74559e1a8d..7594664107 100644 --- a/packages/frontend/electron/src/main/updater/electron-updater.ts +++ b/packages/frontend/electron/src/main/updater/electron-updater.ts @@ -18,13 +18,53 @@ export const quitAndInstall = async () => { }; let lastCheckTime = 0; -export const checkForUpdates = async (force = true) => { - // check every 30 minutes (1800 seconds) at most - if (!disabled && (force || lastCheckTime + 1000 * 1800 < Date.now())) { + +let downloading = false; + +export type UpdaterConfig = { + autoCheckUpdate: boolean; + autoDownloadUpdate: boolean; +}; + +const config: UpdaterConfig = { + autoCheckUpdate: true, + autoDownloadUpdate: true, +}; + +export const getConfig = (): UpdaterConfig => { + return { ...config }; +}; + +export const setConfig = (newConfig: Partial = {}): void => { + Object.assign(config, newConfig); +}; + +export const checkForUpdates = async (force = false) => { + if (disabled) { + return; + } + + if ( + force || + (config.autoCheckUpdate && lastCheckTime + 1000 * 1800 < Date.now()) + ) { lastCheckTime = Date.now(); return await autoUpdater.checkForUpdates(); } - return void 0; + return; +}; + +export const downloadUpdate = async () => { + if (disabled) { + return; + } + downloading = true; + autoUpdater.downloadUpdate().catch(e => { + downloading = false; + logger.error('Failed to download update', e); + }); + logger.info('Update available, downloading...'); + return; }; export const registerUpdater = async () => { @@ -60,10 +100,9 @@ export const registerUpdater = async () => { autoUpdater.on('checking-for-update', () => { logger.info('Checking for update'); }); - let downloading = false; autoUpdater.on('update-available', info => { logger.info('Update available', info); - if (allowAutoUpdate && !downloading) { + if (config.autoDownloadUpdate && allowAutoUpdate && !downloading) { downloading = true; autoUpdater?.downloadUpdate().catch(e => { downloading = false; diff --git a/packages/frontend/electron/src/main/updater/index.ts b/packages/frontend/electron/src/main/updater/index.ts index 9a7d4378bc..19d24aaaf3 100644 --- a/packages/frontend/electron/src/main/updater/index.ts +++ b/packages/frontend/electron/src/main/updater/index.ts @@ -1,7 +1,14 @@ -import { app } from 'electron'; +import { app, type IpcMainInvokeEvent } from 'electron'; import type { NamespaceHandlers } from '../type'; -import { checkForUpdates, quitAndInstall } from './electron-updater'; +import { + checkForUpdates, + downloadUpdate, + getConfig, + quitAndInstall, + setConfig, + type UpdaterConfig, +} from './electron-updater'; export const updaterHandlers = { currentVersion: async () => { @@ -10,6 +17,18 @@ export const updaterHandlers = { quitAndInstall: async () => { return quitAndInstall(); }, + downloadUpdate: async () => { + return downloadUpdate(); + }, + getConfig: async (): Promise => { + return getConfig(); + }, + setConfig: async ( + _e: IpcMainInvokeEvent, + newConfig: Partial + ): Promise => { + return setConfig(newConfig); + }, checkForUpdatesAndNotify: async () => { const res = await checkForUpdates(true); if (res) { diff --git a/packages/frontend/hooks/package.json b/packages/frontend/hooks/package.json index e409597eef..8790d9f708 100644 --- a/packages/frontend/hooks/package.json +++ b/packages/frontend/hooks/package.json @@ -12,6 +12,7 @@ "lodash.debounce": "^4.0.8", "p-queue": "^7.4.1", "react": "18.2.0", + "rxjs": "^7.8.1", "swr": "2.2.4", "uuid": "^9.0.1" }, diff --git a/packages/frontend/hooks/src/use-app-updater.ts b/packages/frontend/hooks/src/use-app-updater.ts new file mode 100644 index 0000000000..adf0c8e7f0 --- /dev/null +++ b/packages/frontend/hooks/src/use-app-updater.ts @@ -0,0 +1,186 @@ +import { isBrowser } from '@affine/env/constant'; +import type { UpdateMeta } from '@toeverything/infra/type'; +import { atom, useAtomValue, useSetAtom } from 'jotai'; +import { atomWithObservable, atomWithStorage } from 'jotai/utils'; +import { useCallback, useState } from 'react'; +import { Observable } from 'rxjs'; + +function rpcToObservable< + T, + H extends () => Promise, + E extends (callback: (t: T) => void) => () => void, +>( + initialValue: T | null, + { + event, + handler, + onSubscribe, + }: { + event?: E; + handler?: H; + onSubscribe?: () => void; + } +): Observable { + return new Observable(subscriber => { + subscriber.next(initialValue); + onSubscribe?.(); + if (!isBrowser || !environment.isDesktop || !event) { + subscriber.complete(); + return; + } + handler?.() + .then(t => { + subscriber.next(t); + }) + .catch(err => { + subscriber.error(err); + }); + return event(t => { + subscriber.next(t); + }); + }); +} + +export const updateReadyAtom = atomWithObservable(() => { + return rpcToObservable(null as UpdateMeta | null, { + event: window.events?.updater.onUpdateReady, + }); +}); + +export const updateAvailableStateAtom = atom(null); + +export const updateAvailableAtom = atomWithObservable(get => { + return rpcToObservable(get(updateAvailableStateAtom), { + event: window.events?.updater.onUpdateAvailable, + onSubscribe: () => { + window.apis?.updater.checkForUpdatesAndNotify().catch(err => { + console.error(err); + }); + }, + }); +}); + +export const downloadProgressAtom = atomWithObservable(() => { + return rpcToObservable(null as number | null, { + event: window.events?.updater.onDownloadProgress, + }); +}); + +export const changelogCheckedAtom = atomWithStorage>( + 'affine:client-changelog-checked', + {} +); + +export const currentVersionAtom = atom(async () => { + if (!isBrowser) { + return null; + } + const currentVersion = await window.apis?.updater.currentVersion(); + return currentVersion; +}); + +export const currentChangelogUnreadAtom = atom(async get => { + if (!isBrowser) { + return false; + } + const mapping = get(changelogCheckedAtom); + const currentVersion = await get(currentVersionAtom); + if (currentVersion) { + return !mapping[currentVersion]; + } + return false; +}); + +export const isCheckingForUpdatesAtom = atom(false); +export const isAutoDownloadUpdateAtom = atom(true); +export const isAutoCheckUpdateAtom = atom(true); + +export const useAppUpdater = () => { + const [appQuitting, setAppQuitting] = useState(false); + const updateReady = useAtomValue(updateReadyAtom); + const setUpdateAvailableState = useSetAtom(updateAvailableStateAtom); + const setIsCheckingForUpdates = useSetAtom(isCheckingForUpdatesAtom); + const setIsAutoCheckUpdate = useSetAtom(isAutoCheckUpdateAtom); + const setIsAutoDownloadUpdate = useSetAtom(isAutoDownloadUpdateAtom); + + const quitAndInstall = useCallback(() => { + if (updateReady) { + setAppQuitting(true); + window.apis?.updater.quitAndInstall().catch(err => { + // TODO: add error toast here + console.error(err); + }); + } + }, [updateReady]); + + const checkForUpdates = useCallback(async () => { + setIsCheckingForUpdates(true); + try { + const updateInfo = await window.apis?.updater.checkForUpdatesAndNotify(); + setIsCheckingForUpdates(false); + if (updateInfo) { + const updateMeta: UpdateMeta = { + version: updateInfo.version, + allowAutoUpdate: false, + }; + setUpdateAvailableState(updateMeta); + return updateInfo.version; + } + return false; + } catch (err) { + setIsCheckingForUpdates(false); + console.error('Error checking for updates:', err); + return null; + } + }, [setIsCheckingForUpdates, setUpdateAvailableState]); + + const downloadUpdate = useCallback(() => { + window.apis?.updater + .downloadUpdate() + .then(() => {}) + .catch(err => { + console.error('Error downloading update:', err); + }); + }, []); + + const toggleAutoDownload = useCallback( + (enable: boolean) => { + window.apis?.updater + .setConfig({ + autoDownloadUpdate: enable, + }) + .then(() => { + setIsAutoDownloadUpdate(enable); + }) + .catch(err => { + console.error('Error setting auto download:', err); + }); + }, + [setIsAutoDownloadUpdate] + ); + + const toggleAutoCheck = useCallback( + (enable: boolean) => { + window.apis?.updater + .setConfig({ + autoCheckUpdate: enable, + }) + .then(() => { + setIsAutoCheckUpdate(enable); + }) + .catch(err => { + console.error('Error setting auto check:', err); + }); + }, + [setIsAutoCheckUpdate] + ); + + return { + quitAndInstall, + appQuitting, + checkForUpdates, + downloadUpdate, + toggleAutoDownload, + toggleAutoCheck, + }; +}; diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 0af0b9fc35..bbbb19b8c1 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -346,6 +346,17 @@ "com.affine.aboutAFFiNE.changelog.title": "Discover what's new", "com.affine.aboutAFFiNE.checkUpdate.description": "New version is ready", "com.affine.aboutAFFiNE.checkUpdate.title": "Check for updates", + "com.affine.aboutAFFiNE.checkUpdate.button.check": "Check for Update", + "com.affine.aboutAFFiNE.checkUpdate.button.download": "Download Update", + "com.affine.aboutAFFiNE.checkUpdate.button.restart": "Restart to Update", + "com.affine.aboutAFFiNE.checkUpdate.button.retry": "Retry", + "com.affine.aboutAFFiNE.checkUpdate.subtitle.check": "Manually check for updates.", + "com.affine.aboutAFFiNE.checkUpdate.subtitle.checking": "Checking for updates...", + "com.affine.aboutAFFiNE.checkUpdate.subtitle.update-available": "New update available ({{version}})", + "com.affine.aboutAFFiNE.checkUpdate.subtitle.downloading": "Downloading the latest version...", + "com.affine.aboutAFFiNE.checkUpdate.subtitle.restart": "Restart tot apply update.", + "com.affine.aboutAFFiNE.checkUpdate.subtitle.latest": "You’ve got the latest version of AFFiNE.", + "com.affine.aboutAFFiNE.checkUpdate.subtitle.error": "Unable to connect to the update server.", "com.affine.aboutAFFiNE.community.title": "Communities", "com.affine.aboutAFFiNE.contact.community": "AFFiNE Community", "com.affine.aboutAFFiNE.contact.title": "Contact Us", diff --git a/yarn.lock b/yarn.lock index 1082ad531c..b5355fd19b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13065,6 +13065,7 @@ __metadata: lodash.debounce: "npm:^4.0.8" p-queue: "npm:^7.4.1" react: "npm:18.2.0" + rxjs: "npm:^7.8.1" swr: "npm:2.2.4" uuid: "npm:^9.0.1" vitest: "npm:0.34.6" From 6661e8bd9aecd9bf219d98836ce40b973f65ee8e Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Thu, 30 Nov 2023 12:23:03 +0800 Subject: [PATCH 32/35] fix(core): language display names (#5123) --- packages/frontend/i18n/src/resources/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/i18n/src/resources/index.ts b/packages/frontend/i18n/src/resources/index.ts index 331d74ea97..d3410e9060 100644 --- a/packages/frontend/i18n/src/resources/index.ts +++ b/packages/frontend/i18n/src/resources/index.ts @@ -27,7 +27,7 @@ export const LOCALES = [ id: 1000040021, name: 'Portuguese (Brazil)', tag: 'pt-BR', - originalName: 'português (Brasil)', + originalName: 'Português (Brasil)', flagEmoji: '🇧🇷', base: false, completeRate: 0.414, @@ -77,7 +77,7 @@ export const LOCALES = [ id: 1000040008, name: 'Spanish', tag: 'es', - originalName: 'español', + originalName: 'Español', flagEmoji: '🇪🇸', base: false, completeRate: 0.357, @@ -97,7 +97,7 @@ export const LOCALES = [ id: 1000040011, name: 'Russian', tag: 'ru', - originalName: 'русский', + originalName: 'Pусский', flagEmoji: '🇷🇺', base: false, completeRate: 0.445, From 3e662f358dafd40ea15d311f0ec4802095e8f545 Mon Sep 17 00:00:00 2001 From: JimmFly Date: Thu, 30 Nov 2023 12:23:57 +0800 Subject: [PATCH 33/35] fix(core): adjust ui styles (#5094) --- .../components/page-list/view/collection-list.tsx | 6 +++--- .../component/src/ui/button/styles.css.ts | 4 +++- .../src/components/affine/language-menu/index.tsx | 3 ++- .../new-workspace-setting-detail/profile.tsx | 14 ++++++++++---- .../setting-modal/setting-sidebar/index.tsx | 11 +++++++++-- .../setting-modal/setting-sidebar/style.css.ts | 5 +++++ .../share-page-modal/share-menu/index.css.ts | 3 --- .../share-page-modal/share-menu/share-page.tsx | 12 ++---------- .../block-suite-header-title/styles.css.ts | 2 ++ .../pure/workspace-mode-filter-tab/index.css.ts | 8 ++++++++ .../pure/workspace-mode-filter-tab/index.tsx | 15 +++++++-------- .../core/src/pages/workspace/collection.tsx | 6 +++++- 12 files changed, 56 insertions(+), 33 deletions(-) create mode 100644 packages/frontend/core/src/components/pure/workspace-mode-filter-tab/index.css.ts diff --git a/packages/frontend/component/src/components/page-list/view/collection-list.tsx b/packages/frontend/component/src/components/page-list/view/collection-list.tsx index 0c6f949aba..563bef0414 100644 --- a/packages/frontend/component/src/components/page-list/view/collection-list.tsx +++ b/packages/frontend/component/src/components/page-list/view/collection-list.tsx @@ -5,7 +5,7 @@ import type { } from '@affine/env/filter'; import type { PropertiesMeta } from '@affine/env/filter'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { FilteredIcon } from '@blocksuite/icons'; +import { FilterIcon } from '@blocksuite/icons'; import { Button } from '@toeverything/components/button'; import { Menu } from '@toeverything/components/menu'; import { useCallback, useState } from 'react'; @@ -75,7 +75,7 @@ export const CollectionList = ({ -
- ); -}; - -export const BlockSuiteFallback = memo(function BlockSuiteFallback() { +export const EditorLoading = memo(function EditorLoading() { return (
( - - ), - [props.onReset] - )} - > - }> - - - + }> + + ); }); diff --git a/packages/frontend/component/src/components/page-detail-skeleton/index.tsx b/packages/frontend/component/src/components/page-detail-skeleton/index.tsx index 4b323ff011..8892880852 100644 --- a/packages/frontend/component/src/components/page-detail-skeleton/index.tsx +++ b/packages/frontend/component/src/components/page-detail-skeleton/index.tsx @@ -1,4 +1,4 @@ -import { BlockSuiteFallback } from '../block-suite-editor'; +import { EditorLoading } from '../block-suite-editor'; import { pageDetailSkeletonStyle, pageDetailSkeletonTitleStyle, @@ -8,7 +8,7 @@ export const PageDetailSkeleton = () => { return (
- +
); }; diff --git a/packages/frontend/core/.webpack/config.ts b/packages/frontend/core/.webpack/config.ts index 2586b8ce8a..15e1c1e46c 100644 --- a/packages/frontend/core/.webpack/config.ts +++ b/packages/frontend/core/.webpack/config.ts @@ -351,6 +351,8 @@ export const createConfiguration: ( 'process.env.CAPTCHA_SITE_KEY': JSON.stringify( process.env.CAPTCHA_SITE_KEY ), + 'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN), + 'process.env.BUILD_TYPE': JSON.stringify(process.env.BUILD_TYPE), runtimeConfig: JSON.stringify(runtimeConfig), }), new CopyPlugin({ diff --git a/packages/frontend/core/package.json b/packages/frontend/core/package.json index bf31b42040..fd30a0a01f 100644 --- a/packages/frontend/core/package.json +++ b/packages/frontend/core/package.json @@ -45,6 +45,8 @@ "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", "@react-hookz/web": "^23.1.0", + "@sentry/integrations": "^7.83.0", + "@sentry/react": "^7.83.0", "@toeverything/components": "^0.0.46", "@toeverything/theme": "^0.7.20", "@vanilla-extract/css": "^1.13.0", diff --git a/packages/frontend/core/project.json b/packages/frontend/core/project.json index ae85c4b3a4..a3d1d51dda 100644 --- a/packages/frontend/core/project.json +++ b/packages/frontend/core/project.json @@ -41,7 +41,7 @@ "env": "SENTRY_AUTH_TOKEN" }, { - "env": "NEXT_PUBLIC_SENTRY_DSN" + "env": "SENTRY_DSN" }, { "env": "DISTRIBUTION" diff --git a/packages/frontend/core/src/bootstrap/setup.ts b/packages/frontend/core/src/bootstrap/setup.ts index 27353eb870..36af514255 100644 --- a/packages/frontend/core/src/bootstrap/setup.ts +++ b/packages/frontend/core/src/bootstrap/setup.ts @@ -6,7 +6,15 @@ import { rootWorkspacesMetadataAtom, workspaceAdaptersAtom, } from '@affine/workspace/atom'; +import * as Sentry from '@sentry/react'; import type { createStore } from 'jotai/vanilla'; +import { useEffect } from 'react'; +import { + createRoutesFromChildren, + matchRoutes, + useLocation, + useNavigationType, +} from 'react-router-dom'; import { WorkspaceAdapters } from '../adapters/workspace'; import { performanceLogger } from '../shared'; @@ -51,6 +59,33 @@ export async function setup(store: ReturnType) { performanceSetupLogger.info('setup global'); setupGlobal(); + if (window.SENTRY_RELEASE || environment.isDebug) { + // https://docs.sentry.io/platforms/javascript/guides/react/#configure + Sentry.init({ + dsn: process.env.SENTRY_DSN, + environment: process.env.BUILD_TYPE ?? 'development', + integrations: [ + new Sentry.BrowserTracing({ + routingInstrumentation: Sentry.reactRouterV6Instrumentation( + useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes + ), + }), + new Sentry.Replay(), + ], + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + tracesSampleRate: 1.0, + }); + Sentry.setTags({ + appVersion: runtimeConfig.appVersion, + editorVersion: runtimeConfig.editorVersion, + }); + } + performanceSetupLogger.info('get root workspace meta'); // do not read `rootWorkspacesMetadataAtom` before migration await store.get(rootWorkspacesMetadataAtom); diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary.tsx deleted file mode 100644 index cd576a0ec2..0000000000 --- a/packages/frontend/core/src/components/affine/affine-error-boundary.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import type { - QueryParamError, - Unreachable, - WorkspaceNotFoundError, -} from '@affine/env/constant'; -import { PageNotFoundError } from '@affine/env/constant'; -import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; -import { Button } from '@toeverything/components/button'; -import { - currentPageIdAtom, - currentWorkspaceIdAtom, - getCurrentStore, -} from '@toeverything/infra/atom'; -import { useAtomValue } from 'jotai/react'; -import { Provider } from 'jotai/react'; -import type { ErrorInfo, ReactElement, ReactNode } from 'react'; -import type React from 'react'; -import { Component, useEffect } from 'react'; -import { useLocation, useParams } from 'react-router-dom'; - -import { - RecoverableError, - type SessionFetchErrorRightAfterLoginOrSignUp, -} from '../../unexpected-application-state/errors'; -import { - errorDescription, - errorDetailStyle, - errorDivider, - errorImage, - errorLayout, - errorRetryButton, - errorTitle, -} from './affine-error-boundary.css'; -import errorBackground from './error-status.assets.svg'; - -export type AffineErrorBoundaryProps = React.PropsWithChildren & { - height?: number | string; -}; - -type AffineError = - | QueryParamError - | Unreachable - | WorkspaceNotFoundError - | PageNotFoundError - | Error - | SessionFetchErrorRightAfterLoginOrSignUp; - -interface AffineErrorBoundaryState { - error: AffineError | null; - canRetryRecoveredError: boolean; -} - -export const DumpInfo = () => { - const location = useLocation(); - const metadata = useAtomValue(rootWorkspacesMetadataAtom); - const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom); - const currentPageId = useAtomValue(currentPageIdAtom); - const path = location.pathname; - const query = useParams(); - useEffect(() => { - console.info('DumpInfo', { - path, - query, - currentWorkspaceId, - currentPageId, - metadata, - }); - }, [path, query, currentWorkspaceId, currentPageId, metadata]); - return null; -}; - -export class AffineErrorBoundary extends Component< - AffineErrorBoundaryProps, - AffineErrorBoundaryState -> { - override state: AffineErrorBoundaryState = { - error: null, - canRetryRecoveredError: true, - }; - - private readonly handleRecoverableRetry = () => { - if (this.state.error instanceof RecoverableError) { - if (this.state.error.canRetry()) { - this.state.error.retry(); - this.setState({ - error: null, - canRetryRecoveredError: this.state.error.canRetry(), - }); - } else { - document.location.reload(); - } - } - }; - - private readonly handleRefresh = () => { - this.setState({ error: null }); - }; - - static getDerivedStateFromError( - error: AffineError - ): AffineErrorBoundaryState { - return { - error, - canRetryRecoveredError: - error instanceof RecoverableError ? error.canRetry() : true, - }; - } - - override componentDidCatch(error: AffineError, errorInfo: ErrorInfo) { - console.error('Uncaught error:', error, errorInfo); - } - - override render(): ReactNode { - if (this.state.error) { - let errorDetail: ReactElement | null = null; - const error = this.state.error; - if (error instanceof PageNotFoundError) { - errorDetail = ( - <> -

Sorry.. there was an error

- <> - Page error - - Cannot find page {error.pageId} in workspace{' '} - {error.workspace.id} - - - - ); - } else if (error instanceof RecoverableError) { - const retryButtonDesc = this.state.canRetryRecoveredError - ? 'Refetch' - : 'Reload'; - errorDetail = ( - <> -

Sorry.. there was an error

- {error.message} - - If you are still experiencing this issue, please{' '} -
- contact us through the community. - - - - - ); - } else { - errorDetail = ( - <> -

Sorry.. there was an error

- - {error.message ?? error.toString()} - - - - ); - } - return ( -
-
{errorDetail}
- -
- - - -
- ); - } - - return this.props.children; - } -} diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.css.ts b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.css.ts new file mode 100644 index 0000000000..d1026be77e --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; + +export const viewport = style({ + height: '100%', + width: '100%', +}); diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.tsx new file mode 100644 index 0000000000..864d809db0 --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.tsx @@ -0,0 +1,53 @@ +import { getCurrentStore } from '@toeverything/infra/atom'; +import { Provider } from 'jotai/react'; +import type { FC } from 'react'; +import { useMemo } from 'react'; + +import * as styles from './affine-error-fallback.css'; +import { + ERROR_REFLECT_KEY, + type FallbackProps, +} from './error-basic/fallback-creator'; +import { DumpInfo } from './error-basic/info-logger'; +import { AnyErrorFallback } from './error-fallbacks/any-error-fallback'; +import { NoPageRootFallback } from './error-fallbacks/no-page-root-fallback'; +import { PageNotFoundDetail } from './error-fallbacks/page-not-found-fallback'; +import { RecoverableErrorFallback } from './error-fallbacks/recoverable-error-fallback'; + +/** + * Register all fallback components here. + * If have new one just add it to the set. + */ +const fallbacks = new Set([ + PageNotFoundDetail, + RecoverableErrorFallback, + NoPageRootFallback, +]); + +function getErrorFallbackComponent(error: any): FC { + for (const Component of fallbacks) { + const ErrorConstructor = Reflect.get(Component, ERROR_REFLECT_KEY); + if (ErrorConstructor && error instanceof ErrorConstructor) { + return Component as FC; + } + } + return AnyErrorFallback; +} + +export interface AffineErrorFallbackProps extends FallbackProps { + height?: number | string; +} + +export const AffineErrorFallback: FC = props => { + const { error, resetError, height } = props; + const Component = useMemo(() => getErrorFallbackComponent(error), [error]); + + return ( +
+ + + + +
+ ); +}; diff --git a/packages/frontend/core/src/components/affine/error-status.assets.svg b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404-status.assets.svg similarity index 100% rename from packages/frontend/core/src/components/affine/error-status.assets.svg rename to packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404-status.assets.svg diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500-status.assets.svg b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500-status.assets.svg new file mode 100644 index 0000000000..69823de351 --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500-status.assets.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary.css.ts b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.css.ts similarity index 89% rename from packages/frontend/core/src/components/affine/affine-error-boundary.css.ts rename to packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.css.ts index 5999d7ec1c..5586afb013 100644 --- a/packages/frontend/core/src/components/affine/affine-error-boundary.css.ts +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.css.ts @@ -6,6 +6,7 @@ export const errorLayout = style({ alignItems: 'center', height: '100%', width: '100%', + gap: '20px', }); export const errorDetailStyle = style({ @@ -24,15 +25,15 @@ export const errorImage = style({ height: '178px', maxWidth: '400px', flexGrow: 1, + backgroundSize: 'cover', }); export const errorDescription = style({ marginTop: '24px', }); -export const errorRetryButton = style({ +export const errorFooter = style({ marginTop: '24px', - width: '94px', }); export const errorDivider = style({ diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.tsx new file mode 100644 index 0000000000..2aa22a1f7b --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.tsx @@ -0,0 +1,106 @@ +import { Trans } from '@affine/i18n'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; +import { + type FC, + type PropsWithChildren, + type ReactNode, + useState, +} from 'react'; + +import imageUrlFor404 from '../error-assets/404-status.assets.svg'; +import imageUrlFor500 from '../error-assets/500-status.assets.svg'; +import * as styles from './error-detail.css'; + +export enum ErrorStatus { + NotFound = 404, + Unexpected = 500, +} + +export interface ErrorDetailProps extends PropsWithChildren { + status?: ErrorStatus; + direction?: 'column' | 'row'; + title: string; + description: ReactNode | Array; + buttonText?: string; + onButtonClick?: () => void | Promise; + resetError?: () => void; + withoutImage?: boolean; +} + +const imageMap = new Map([ + [ErrorStatus.NotFound, imageUrlFor404], + [ErrorStatus.Unexpected, imageUrlFor500], +]); + +/** + * TODO: Unify with NotFoundPage. + */ +export const ErrorDetail: FC = props => { + const { + status = ErrorStatus.Unexpected, + direction = 'row', + description, + onButtonClick, + resetError, + withoutImage, + } = props; + const descriptions = Array.isArray(description) ? description : [description]; + const [isBtnLoading, setBtnLoading] = useState(false); + const t = useAFFiNEI18N(); + + const onBtnClick = useAsyncCallback(async () => { + try { + setBtnLoading(true); + await onButtonClick?.(); + resetError?.(); // Only reset when retry success. + } finally { + setBtnLoading(false); + } + }, [onButtonClick, resetError]); + + return ( +
+
+

{props.title}

+ {descriptions.map((item, i) => ( +

+ {item} +

+ ))} +
+ +
+
+ {withoutImage ? null : ( +
+ )} +
+ ); +}; + +export function ContactUS() { + return ( + + If you are still experiencing this issue, please{' '} + + contact us through the community. + + + ); +} diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/fallback-creator.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/fallback-creator.tsx new file mode 100644 index 0000000000..062deac3d1 --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/fallback-creator.tsx @@ -0,0 +1,16 @@ +import type { FC } from 'react'; + +export interface FallbackProps { + error: T; + resetError: () => void; +} + +export const ERROR_REFLECT_KEY = Symbol('ERROR_REFLECT_KEY'); + +export function createErrorFallback( + ErrorConstructor: abstract new (...args: any[]) => T, + Component: FC> +): FC> { + Reflect.set(Component, ERROR_REFLECT_KEY, ErrorConstructor); + return Component; +} diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/info-logger.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/info-logger.tsx new file mode 100644 index 0000000000..ae8004d591 --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/info-logger.tsx @@ -0,0 +1,31 @@ +import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; +import { + currentPageIdAtom, + currentWorkspaceIdAtom, +} from '@toeverything/infra/atom'; +import { useAtomValue } from 'jotai/react'; +import { useEffect } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; + +export interface DumpInfoProps { + error: any; +} + +export const DumpInfo = (_props: DumpInfoProps) => { + const location = useLocation(); + const metadata = useAtomValue(rootWorkspacesMetadataAtom); + const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom); + const currentPageId = useAtomValue(currentPageIdAtom); + const path = location.pathname; + const query = useParams(); + useEffect(() => { + console.info('DumpInfo', { + path, + query, + currentWorkspaceId, + currentPageId, + metadata, + }); + }, [path, query, currentWorkspaceId, currentPageId, metadata]); + return null; +}; diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/any-error-fallback.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/any-error-fallback.tsx new file mode 100644 index 0000000000..759af60973 --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/any-error-fallback.tsx @@ -0,0 +1,26 @@ +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { type FC, useCallback } from 'react'; + +import { ErrorDetail } from '../error-basic/error-detail'; +import type { FallbackProps } from '../error-basic/fallback-creator'; + +/** + * TODO: Support reload and retry two reset actions in page error and area error. + */ +export const AnyErrorFallback: FC = props => { + const { error } = props; + const t = useAFFiNEI18N(); + + const reloadPage = useCallback(() => { + document.location.reload(); + }, []); + + return ( + + ); +}; diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/no-page-root-fallback.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/no-page-root-fallback.tsx new file mode 100644 index 0000000000..88bbe9d5e8 --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/no-page-root-fallback.tsx @@ -0,0 +1,21 @@ +import { NoPageRootError } from '@affine/component/block-suite-editor'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; + +import { ContactUS, ErrorDetail } from '../error-basic/error-detail'; +import { createErrorFallback } from '../error-basic/fallback-creator'; + +export const NoPageRootFallback = createErrorFallback( + NoPageRootError, + props => { + const { resetError } = props; + const t = useAFFiNEI18N(); + + return ( + } + resetError={resetError} + /> + ); + } +); diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/page-not-found-fallback.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/page-not-found-fallback.tsx new file mode 100644 index 0000000000..2c8ce3a70c --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/page-not-found-fallback.tsx @@ -0,0 +1,30 @@ +import { PageNotFoundError } from '@affine/env/constant'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useCallback } from 'react'; + +import { + RouteLogic, + useNavigateHelper, +} from '../../../../hooks/use-navigate-helper'; +import { ErrorDetail, ErrorStatus } from '../error-basic/error-detail'; +import { createErrorFallback } from '../error-basic/fallback-creator'; + +export const PageNotFoundDetail = createErrorFallback(PageNotFoundError, () => { + const t = useAFFiNEI18N(); + const { jumpToIndex } = useNavigateHelper(); + + const onBtnClick = useCallback( + () => jumpToIndex(RouteLogic.REPLACE), + [jumpToIndex] + ); + + return ( + + ); +}); diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/recoverable-error-fallback.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/recoverable-error-fallback.tsx new file mode 100644 index 0000000000..5c872f71d4 --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/recoverable-error-fallback.tsx @@ -0,0 +1,41 @@ +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useCallback, useMemo, useState } from 'react'; + +import { RecoverableError } from '../../../../unexpected-application-state/errors'; +import { ContactUS, ErrorDetail } from '../error-basic/error-detail'; +import { createErrorFallback } from '../error-basic/fallback-creator'; + +export const RecoverableErrorFallback = createErrorFallback( + RecoverableError, + props => { + const { error, resetError } = props; + const t = useAFFiNEI18N(); + const [count, rerender] = useState(0); + + const canRetry = error.canRetry(); + const buttonDesc = useMemo(() => { + if (canRetry) { + return t['com.affine.error.refetch'](); + } + return t['com.affine.error.reload'](); + }, [canRetry, t]); + const onRetry = useCallback(async () => { + if (canRetry) { + rerender(count + 1); + await error.retry(); + } else { + document.location.reload(); + } + }, [error, count, canRetry]); + + return ( + ]} + /> + ); + } +); diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-status.assets.svg b/packages/frontend/core/src/components/affine/affine-error-boundary/error-status.assets.svg new file mode 100644 index 0000000000..f996812afa --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-status.assets.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/index.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/index.tsx new file mode 100644 index 0000000000..9ee5e1cc5e --- /dev/null +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/index.tsx @@ -0,0 +1,34 @@ +import { ErrorBoundary } from '@sentry/react'; +import type { FC, PropsWithChildren } from 'react'; +import { useCallback } from 'react'; + +import { AffineErrorFallback } from './affine-error-fallback'; +import { type FallbackProps } from './error-basic/fallback-creator'; + +export { type FallbackProps } from './error-basic/fallback-creator'; + +export interface AffineErrorBoundaryProps extends PropsWithChildren { + height?: number | string; +} + +/** + * TODO: Unify with SWRErrorBoundary + */ +export const AffineErrorBoundary: FC = props => { + const fallbackRender = useCallback( + (fallbackProps: FallbackProps) => { + return ; + }, + [props.height] + ); + + const onError = useCallback((error: Error, componentStack: string) => { + console.error('Uncaught error:', error, componentStack); + }, []); + + return ( + + {props.children} + + ); +}; diff --git a/packages/frontend/core/src/components/affine/any-error-boundary/index.tsx b/packages/frontend/core/src/components/affine/any-error-boundary/index.tsx deleted file mode 100644 index 35a72366c4..0000000000 --- a/packages/frontend/core/src/components/affine/any-error-boundary/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { ReactElement } from 'react'; -import type { FallbackProps } from 'react-error-boundary'; - -export const AnyErrorBoundary = (props: FallbackProps): ReactElement => { - return ( -
-

Something went wrong:

-

{props.error.toString()}

-
- ); -}; diff --git a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/members.tsx b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/members.tsx index 55926dbb34..5901dce957 100644 --- a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/members.tsx +++ b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/members.tsx @@ -29,7 +29,6 @@ import { useRef, useState, } from 'react'; -import { ErrorBoundary } from 'react-error-boundary'; import { openSettingModalAtom } from '../../../atoms'; import type { CheckedUser } from '../../../hooks/affine/use-current-user'; @@ -39,7 +38,7 @@ import { useMemberCount } from '../../../hooks/affine/use-member-count'; import { type Member, useMembers } from '../../../hooks/affine/use-members'; import { useRevokeMemberPermission } from '../../../hooks/affine/use-revoke-member-permission'; import { useUserSubscription } from '../../../hooks/use-subscription'; -import { AnyErrorBoundary } from '../any-error-boundary'; +import { AffineErrorBoundary } from '../affine-error-boundary'; import * as style from './style.css'; import type { WorkspaceSettingDetailProps } from './types'; @@ -362,10 +361,10 @@ export const MembersPanel = (props: MembersPanelProps): ReactElement | null => { return ; } return ( - + - + ); }; diff --git a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx index 220fe3ef2f..8eb7150e82 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx @@ -1,7 +1,7 @@ import { Scrollable } from '@affine/component'; import { BlockSuiteEditor, - BlockSuiteFallback, + EditorLoading, } from '@affine/component/block-suite-editor'; import type { PageMode } from '@affine/core/atoms'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; @@ -147,7 +147,7 @@ const HistoryEditorPreview = ({ onModeChange={onModeChange} /> ) : ( - + )}
); @@ -410,7 +410,7 @@ export const PageHistoryModal = ({ return ( - }> + }>

- {t[UPGRADE_TIPS_KEYS[upgradeState]]()} + {error ? error.message : t[UPGRADE_TIPS_KEYS[upgradeState]]()}