From 2c93bc64eb40def6fef55b75866398cf6ea95777 Mon Sep 17 00:00:00 2001 From: EnixCoda Date: Tue, 17 May 2022 23:06:05 +0800 Subject: [PATCH] chore: resolve eslint issues --- src/analytics.ts | 14 ++---- src/components/SideBarBodyWrapper.tsx | 9 ++-- src/containers/ConfigsContext.tsx | 4 +- src/containers/OAuthWrapper.tsx | 4 +- src/containers/RepoContext.tsx | 13 +++-- src/platforms/GitHub/API.ts | 26 +++++----- src/platforms/GitHub/CopyFileButton.tsx | 7 ++- src/platforms/GitHub/DOMHelper.ts | 2 +- src/platforms/GitHub/URLHelper.ts | 2 +- src/platforms/GitHub/getCommitTreeData.ts | 2 +- .../hooks/useGitHubAttachCopyFileButton.ts | 5 +- .../hooks/useGitHubAttachCopySnippetButton.ts | 5 +- .../GitHub/hooks/useGitHubCodeFold.tsx | 5 +- src/platforms/GitHub/utils.ts | 2 +- src/platforms/Gitea/API.ts | 30 ++++++------ src/platforms/Gitea/URLHelper.ts | 4 +- src/platforms/Gitea/index.ts | 2 +- src/platforms/Gitee/API.ts | 9 ++-- src/platforms/Gitee/URLHelper.ts | 2 +- src/platforms/Gitee/index.ts | 5 +- src/platforms/dummyPlatformForTypeSafety.ts | 2 +- src/platforms/index.ts | 8 +-- src/platforms/platform.d.ts | 6 +-- src/utils/DOMHelper.ts | 2 +- src/utils/EventHub.ts | 4 +- src/utils/VisibleNodesGenerator/prepare.ts | 2 +- src/utils/config/helper.ts | 5 +- src/utils/config/migrations/1.0.1.ts | 4 +- src/utils/config/migrations/1.3.4.ts | 4 +- src/utils/config/migrations/2.6.0.ts | 9 ++-- src/utils/config/migrations/3.0.0.ts | 2 +- src/utils/config/migrations/3.5.0.ts | 4 +- src/utils/config/migrations/index.ts | 4 +- src/utils/general.ts | 49 ++++++------------- src/utils/gitSubmodule.ts | 2 +- src/utils/hooks/useAsyncMemo.ts | 16 ------ src/utils/hooks/useCatchNetworkError.ts | 2 +- .../hooks/useEffectOnSerializableUpdates.ts | 3 +- src/utils/hooks/useMediaStyleSheet.ts | 27 ---------- src/utils/hooks/usePJAX.ts | 2 +- src/utils/hooks/useResizeHandler.ts | 26 +++++----- src/utils/hooks/useUpdateReason.ts | 8 +-- src/utils/keyHelper.ts | 4 +- src/utils/parseIconMapCSV.ts | 2 +- src/utils/storageHelper.ts | 16 ++---- 45 files changed, 150 insertions(+), 215 deletions(-) delete mode 100644 src/utils/hooks/useAsyncMemo.ts delete mode 100644 src/utils/hooks/useMediaStyleSheet.ts diff --git a/src/analytics.ts b/src/analytics.ts index 2d9a6cb..341a71e 100644 --- a/src/analytics.ts +++ b/src/analytics.ts @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/browser' import { Middleware } from 'driver/connect.js' import { IN_PRODUCTION_MODE, VERSION } from 'env' import { platform } from 'platforms' +import { forOf } from 'utils/general' const PUBLIC_KEY = 'd22ec5c9cc874539a51c78388c12e3b0' const PROJECT_ID = '1406497' @@ -69,12 +70,7 @@ export const withErrorLog: Middleware = function withErrorLog(method, args) { ] } -export function raiseError( - error: Error, - extra?: { - [key: string]: any - }, -) { +export function raiseError(error: Error, extra?: unknown) { if (!IN_PRODUCTION_MODE || platform.isEnterprise()) { // ignore errors from enterprise to get less noise on Sentry console.error(error) @@ -83,10 +79,8 @@ export function raiseError( } Sentry.withScope(scope => { - if (extra) { - Object.keys(extra).forEach(key => { - scope.setExtra(key, extra[key]) - }) + if (typeof extra === 'object' && extra) { + forOf(extra, (key, value) => scope.setExtra(key, value)) } Sentry.captureException(error) }) diff --git a/src/components/SideBarBodyWrapper.tsx b/src/components/SideBarBodyWrapper.tsx index 78e502b..92bb720 100644 --- a/src/components/SideBarBodyWrapper.tsx +++ b/src/components/SideBarBodyWrapper.tsx @@ -7,6 +7,7 @@ import { cx } from 'utils/cx' import { setCSSVariable } from 'utils/DOMHelper' import * as features from 'utils/features' import { detectBrowser } from 'utils/general' +import { ResizeState } from 'utils/hooks/useResizeHandler' import { useConditionalHook } from '../utils/hooks/useConditionalHook' type Size = number @@ -107,6 +108,10 @@ export function SideBarBodyWrapper({ ) const dummySize: [number, number] = React.useMemo(() => [size, size], [size]) + const onResizeStateChange = React.useCallback((state: ResizeState) => { + blockLeaveRef.current = state === 'resizing' + }, []) + return (
{ - blockLeaveRef.current = state === 'resizing' - }} + onResizeStateChange={onResizeStateChange} size={dummySize} /> )} diff --git a/src/containers/ConfigsContext.tsx b/src/containers/ConfigsContext.tsx index 4710a83..53cb7cc 100644 --- a/src/containers/ConfigsContext.tsx +++ b/src/containers/ConfigsContext.tsx @@ -29,9 +29,9 @@ export function ConfigsContextWrapper(props: React.PropsWithChildren) { ) } -export const useConfigs = useNonNullContext(ConfigsContext) +export const useConfigs = createUseNonNullContext(ConfigsContext) -function useNonNullContext(theContext: React.Context): () => T { +function createUseNonNullContext(theContext: React.Context): () => T { return () => { const context = React.useContext(theContext) if (context === null) throw new Error(`Empty context`) diff --git a/src/containers/OAuthWrapper.tsx b/src/containers/OAuthWrapper.tsx index c338c64..9fdc257 100644 --- a/src/containers/OAuthWrapper.tsx +++ b/src/containers/OAuthWrapper.tsx @@ -18,7 +18,7 @@ export function OAuthWrapper({ children }: React.PropsWithChildren<{}>) { if (needGetAccessTokenRef.current) { $state.onChange(running ? 'getting-access-token' : 'after-getting-access-token') } - }, [running]) + }, [running]) // eslint-disable-line react-hooks/exhaustive-deps // block children rendering on the first render if setting token if (running && $state.value !== 'getting-access-token') return null @@ -38,7 +38,7 @@ function useGetAccessToken() { } $block.onChange(false) }) - }, []) + }, []) // eslint-disable-line react-hooks/exhaustive-deps return $block.value } diff --git a/src/containers/RepoContext.tsx b/src/containers/RepoContext.tsx index 3e7004a..6d64fdf 100644 --- a/src/containers/RepoContext.tsx +++ b/src/containers/RepoContext.tsx @@ -38,10 +38,13 @@ function usePartialMetaData(): PartialMetaData | null { // sync along URL and DOM const $partialMetaData = useStateIO(isGettingAccessToken ? null : resolvePartialMetaData) const $committedPartialMetaData = useStateIO($partialMetaData.value) - const setPartialMetaData = () => $partialMetaData.onChange(resolvePartialMetaData()) + const setPartialMetaData = React.useCallback( + () => $partialMetaData.onChange(resolvePartialMetaData()), + [], // eslint-disable-line react-hooks/exhaustive-deps + ) React.useEffect(() => { if (!isGettingAccessToken) setPartialMetaData() - }, [isGettingAccessToken]) + }, [isGettingAccessToken, setPartialMetaData]) useOnPJAXDone(setPartialMetaData) useEffectOnSerializableUpdates( $partialMetaData.value, @@ -52,7 +55,7 @@ function usePartialMetaData(): PartialMetaData | null { if (!$partialMetaData.value && !isGettingAccessToken) { $state.onChange('disabled') } - }, [$partialMetaData.value]) + }, [$partialMetaData.value]) // eslint-disable-line react-hooks/exhaustive-deps return $committedPartialMetaData.value } @@ -76,7 +79,7 @@ function useDefaultBranch(partialMetaData: PartialMetaData | null) { const defaultBranch = await platform.getDefaultBranchName(partialMetaData, accessToken) $defaultBranch.onChange(defaultBranch) }) - }, [partialMetaData, accessToken]) + }, [partialMetaData, accessToken]) // eslint-disable-line react-hooks/exhaustive-deps return $defaultBranch.value } @@ -102,6 +105,6 @@ function useMetaData( } else { $metaData.onChange(null) } - }, [partialMetaData, defaultBranchName, theBranch]) + }, [partialMetaData, defaultBranchName, theBranch]) // eslint-disable-line react-hooks/exhaustive-deps return $metaData.value } diff --git a/src/platforms/GitHub/API.ts b/src/platforms/GitHub/API.ts index c1b4d98..29f97cf 100644 --- a/src/platforms/GitHub/API.ts +++ b/src/platforms/GitHub/API.ts @@ -1,17 +1,21 @@ import { errors } from 'platforms' import { isEnterprise } from '.' +import { is } from '../../utils/is' import { continuousLoadPages, getDOM, resolveHeaderLink } from './utils' -function isAPIRateLimitExceeded(content: any /* safe any */) { - return content?.['documentation_url'] === 'https://developer.github.com/v3/#rate-limiting' +function isAPIRateLimitExceeded(content: JSONValue) { + return ( + is.JSON.object(content) && + content?.['documentation_url'] === 'https://developer.github.com/v3/#rate-limiting' + ) } -function isEmptyProject(content: any /* safe any */) { - return content?.['message'] === 'Git Repository is empty.' +function isEmptyProject(content: JSONValue) { + return is.JSON.object(content) && content?.['message'] === 'Git Repository is empty.' } -function isBlockedProject(content: any /* safe any */) { - return content?.['message'] === 'Repository access blocked' +function isBlockedProject(content: JSONValue) { + return is.JSON.object(content) && content?.['message'] === 'Repository access blocked' } export const responseBodyResolvers = { @@ -151,11 +155,10 @@ export async function getPullPageDocuments( ) } -export async function getCommitPageDocuments( - userName: string, +export async function getCommitPageDocuments(): Promise { + /* userName: string, repoName: string, - commitId: string, -): Promise { + commitId: string, */ // arguments are not used because info are collected from DOM directly return continuousLoadPages(document) } @@ -191,7 +194,7 @@ export async function requestCommitTreeData( userName: string, repoName: string, sha: string, - page: number = 1, + page = 1, accessToken?: string, ): Promise { const search = new URLSearchParams({ @@ -205,6 +208,7 @@ export async function requestCommitTreeData( export async function getPaginatedData(sendRequest: (page: number) => Promise) { const responses: Response[] = [] let page = 1 + // eslint-disable-next-line no-constant-condition while (true) { const response = await sendRequest(page) responses.push(response) diff --git a/src/platforms/GitHub/CopyFileButton.tsx b/src/platforms/GitHub/CopyFileButton.tsx index 47d26c0..7a3bec2 100644 --- a/src/platforms/GitHub/CopyFileButton.tsx +++ b/src/platforms/GitHub/CopyFileButton.tsx @@ -3,8 +3,6 @@ import { cx } from 'utils/cx' import { copyElementContent } from 'utils/DOMHelper' import { getCodeElement } from './DOMHelper' -type Props = {} - const className = 'gitako-copy-file-button' export const copyFileButtonClassName = className @@ -13,7 +11,8 @@ const contents = { error: 'Copy failed!', normal: 'Copy file', } -export function CopyFileButton(props: React.PropsWithChildren) { + +export function CopyFileButton() { const [content, setContent] = React.useState(contents.normal) React.useEffect(() => { if (content !== contents.normal) { @@ -31,7 +30,7 @@ export function CopyFileButton(props: React.PropsWithChildren) { // onClick on won't work when rendered with `renderReact` const element = elementRef.current if (element) { - function copyCode() { + const copyCode = () => { const codeElement = getCodeElement() if (codeElement) { setContent(copyElementContent(codeElement, true) ? contents.success : contents.error) diff --git a/src/platforms/GitHub/DOMHelper.ts b/src/platforms/GitHub/DOMHelper.ts index e61e012..dc42fce 100644 --- a/src/platforms/GitHub/DOMHelper.ts +++ b/src/platforms/GitHub/DOMHelper.ts @@ -73,7 +73,7 @@ export function getCurrentBranch(passive = false) { const commitPathRegex = /^(.*?)\/(.*?)\/find\/(.*?)$/ const result = urlFromFindFileButton.match(commitPathRegex) if (result) { - const [_, userName, repoName, branchName] = result + const [_, userName, repoName, branchName] = result // eslint-disable-line @typescript-eslint/no-unused-vars if (!branchName.includes(' ')) return branchName } } diff --git a/src/platforms/GitHub/URLHelper.ts b/src/platforms/GitHub/URLHelper.ts index a98cc6f..9945ecf 100644 --- a/src/platforms/GitHub/URLHelper.ts +++ b/src/platforms/GitHub/URLHelper.ts @@ -4,7 +4,7 @@ export function parse(): Partial files) .flat() - const documents = await API.getCommitPageDocuments(userName, repoName, commitSHA) + const documents = await API.getCommitPageDocuments(/* userName, repoName, commitSHA */) const getItemURL = (path: string) => { for (const doc of documents) { diff --git a/src/platforms/GitHub/hooks/useGitHubAttachCopyFileButton.ts b/src/platforms/GitHub/hooks/useGitHubAttachCopyFileButton.ts index aa1e409..d911923 100644 --- a/src/platforms/GitHub/hooks/useGitHubAttachCopyFileButton.ts +++ b/src/platforms/GitHub/hooks/useGitHubAttachCopyFileButton.ts @@ -7,11 +7,10 @@ import { GitHub } from '../index' export function useGitHubAttachCopyFileButton(copyFileButton: boolean) { const attachCopyFileButton = React.useCallback( function attachCopyFileButton() { - if (platform !== GitHub) return - if (copyFileButton) return DOMHelper.attachCopyFileBtn() || undefined // for the sake of react effect + if (platform === GitHub && copyFileButton) DOMHelper.attachCopyFileBtn() }, [copyFileButton], ) - React.useEffect(attachCopyFileButton, [copyFileButton]) + React.useEffect(attachCopyFileButton, [attachCopyFileButton]) useOnPJAXDone(attachCopyFileButton) } diff --git a/src/platforms/GitHub/hooks/useGitHubAttachCopySnippetButton.ts b/src/platforms/GitHub/hooks/useGitHubAttachCopySnippetButton.ts index 9a92f76..6d0926b 100644 --- a/src/platforms/GitHub/hooks/useGitHubAttachCopySnippetButton.ts +++ b/src/platforms/GitHub/hooks/useGitHubAttachCopySnippetButton.ts @@ -7,11 +7,10 @@ import { GitHub } from '../index' export function useGitHubAttachCopySnippetButton(copySnippetButton: boolean) { const attachCopySnippetButton = React.useCallback( function attachCopySnippetButton() { - if (platform !== GitHub) return - if (copySnippetButton) return DOMHelper.attachCopySnippet() || undefined // for the sake of react effect + if (platform === GitHub && copySnippetButton) DOMHelper.attachCopySnippet() }, [copySnippetButton], ) - React.useEffect(attachCopySnippetButton, [copySnippetButton]) + React.useEffect(attachCopySnippetButton, [attachCopySnippetButton]) useOnPJAXDone(attachCopySnippetButton) } diff --git a/src/platforms/GitHub/hooks/useGitHubCodeFold.tsx b/src/platforms/GitHub/hooks/useGitHubCodeFold.tsx index 83c49db..6bb0641 100644 --- a/src/platforms/GitHub/hooks/useGitHubCodeFold.tsx +++ b/src/platforms/GitHub/hooks/useGitHubCodeFold.tsx @@ -51,10 +51,11 @@ function init() { type Level = number // measured by leading whitespace amount const stack: [Level, LineNumber][] = [] - function trySeal(lineNumber: number, level: number) { + const trySeal = (lineNumber: number, level: number) => { let ignoredTheHighestLevelItem = false while (stack.length) { - const top = stack.pop()! // safe + const top = stack.pop() + if (top === undefined) throw new Error() const [$LineNumber, $level] = top if ($level < level) { diff --git a/src/platforms/GitHub/utils.ts b/src/platforms/GitHub/utils.ts index bac1be7..f6702ce 100644 --- a/src/platforms/GitHub/utils.ts +++ b/src/platforms/GitHub/utils.ts @@ -89,7 +89,7 @@ export async function continuousLoadPages(doc: Document, onReceivePage?: (doc: D */ const fragmentSelector = 'include-fragment[data-targets="diff-file-filter.progressiveLoaders"]' const documents: Document[] = [doc] - while (true) { + while (true) { // eslint-disable-line no-constant-condition const fragment = doc.querySelector(fragmentSelector) as HTMLElement if (!fragment) break const src = fragment.getAttribute('src') diff --git a/src/platforms/Gitea/API.ts b/src/platforms/Gitea/API.ts index d86a1ca..cca5304 100644 --- a/src/platforms/Gitea/API.ts +++ b/src/platforms/Gitea/API.ts @@ -1,12 +1,13 @@ import { raiseError } from 'analytics' import { errors } from 'platforms' +import { is } from 'utils/is' -function isEmptyProject(content: any /* safe any */) { - return content?.['message'] === 'Git Repository is empty.' +function isEmptyProject(content: JSONValue) { + return is.JSON.object(content) && content?.['message'] === 'Git Repository is empty.' } -function isBlockedProject(content: any /* safe any */) { - return content?.['message'] === 'Repository access blocked' +function isBlockedProject(content: JSONValue) { + return is.JSON.object(content) && content?.['message'] === 'Repository access blocked' } async function request( @@ -77,8 +78,7 @@ export async function getTreeData( ): Promise { const search = new URLSearchParams() if (recursive) search.set('recursive', '1') - const url = - `${API_ENDPOINT}/repos/${userName}/${repoName}/git/trees/${branchName}?` + search + const url = `${API_ENDPOINT}/repos/${userName}/${repoName}/git/trees/${branchName}?` + search return await request(url, { accessToken }) } @@ -94,14 +94,14 @@ export async function getBlobData( export async function OAuth(code: string): Promise { const endpoint = `https://gitako.enix.one/oauth/gitea?` - const res = await fetch(endpoint + new URLSearchParams({ code }).toString(), { - method: 'post', - }) + const res = await fetch(endpoint + new URLSearchParams({ code }).toString(), { + method: 'post', + }) - if (res.ok) { - const body = await res.json() - const accessToken = body?.accessToken - if (typeof accessToken === 'string') return accessToken - } - return null + if (res.ok) { + const body = await res.json() + const accessToken = body?.accessToken + if (typeof accessToken === 'string') return accessToken + } + return null } diff --git a/src/platforms/Gitea/URLHelper.ts b/src/platforms/Gitea/URLHelper.ts index a68b81f..e5c100f 100644 --- a/src/platforms/Gitea/URLHelper.ts +++ b/src/platforms/Gitea/URLHelper.ts @@ -2,7 +2,7 @@ import { raiseError } from 'analytics' export function parse(): Partial & { path: string[] } { const { pathname } = window.location - let [ + const [ , // ignore content before the first '/' userName, @@ -67,4 +67,4 @@ export function getCurrentPath(branchName = '') { return path.map(decodeURIComponent) } return [] -} \ No newline at end of file +} diff --git a/src/platforms/Gitea/index.ts b/src/platforms/Gitea/index.ts index ab58b6a..e02078c 100644 --- a/src/platforms/Gitea/index.ts +++ b/src/platforms/Gitea/index.ts @@ -61,7 +61,7 @@ function getUrlForRedirect( userName: string, repoName: string, branchName: string, - type = 'blob', + type = 'blob', // eslint-disable-line @typescript-eslint/no-unused-vars path = '', ) { // Modern browsers have great support for handling unsafe URL, diff --git a/src/platforms/Gitee/API.ts b/src/platforms/Gitee/API.ts index a9921e7..1948501 100644 --- a/src/platforms/Gitee/API.ts +++ b/src/platforms/Gitee/API.ts @@ -1,12 +1,13 @@ import { raiseError } from 'analytics' import { errors } from 'platforms' +import { is } from 'utils/is' -function isEmptyProject(content: any /* safe any */) { - return content?.['message'] === 'Git Repository is empty.' +function isEmptyProject(content: JSONValue) { + return is.JSON.object(content) && content?.['message'] === 'Git Repository is empty.' } -function isBlockedProject(content: any /* safe any */) { - return content?.['message'] === 'Repository access blocked' +function isBlockedProject(content: JSONValue) { + return is.JSON.object(content) && content?.['message'] === 'Repository access blocked' } async function request( diff --git a/src/platforms/Gitee/URLHelper.ts b/src/platforms/Gitee/URLHelper.ts index 0a539da..e5c100f 100644 --- a/src/platforms/Gitee/URLHelper.ts +++ b/src/platforms/Gitee/URLHelper.ts @@ -2,7 +2,7 @@ import { raiseError } from 'analytics' export function parse(): Partial & { path: string[] } { const { pathname } = window.location - let [ + const [ , // ignore content before the first '/' userName, diff --git a/src/platforms/Gitee/index.ts b/src/platforms/Gitee/index.ts index 5769520..9b201c8 100644 --- a/src/platforms/Gitee/index.ts +++ b/src/platforms/Gitee/index.ts @@ -184,11 +184,10 @@ export const Gitee: Platform = { export function useGiteeAttachCopySnippetButton(copySnippetButton: boolean) { const attachCopySnippetButton = React.useCallback( function attachCopySnippetButton() { - if (platform !== Gitee) return - if (copySnippetButton) return DOMHelper.attachCopySnippet() || undefined // for the sake of react effect + if (platform === Gitee && copySnippetButton) DOMHelper.attachCopySnippet() }, [copySnippetButton], ) - React.useEffect(attachCopySnippetButton, [copySnippetButton]) + React.useEffect(attachCopySnippetButton, [attachCopySnippetButton]) useOnPJAXDone(attachCopySnippetButton) } diff --git a/src/platforms/dummyPlatformForTypeSafety.ts b/src/platforms/dummyPlatformForTypeSafety.ts index 9216714..c41982a 100644 --- a/src/platforms/dummyPlatformForTypeSafety.ts +++ b/src/platforms/dummyPlatformForTypeSafety.ts @@ -16,6 +16,6 @@ export const dummyPlatformForTypeSafety: Platform = { getOAuthLink: dummyPlatformMethod, } -function dummyPlatformMethod(): any { +function dummyPlatformMethod(): any { // eslint-disable-line @typescript-eslint/no-explicit-any throw new Error(`Do not call dummy platform methods`) } diff --git a/src/platforms/index.ts b/src/platforms/index.ts index b5ac56f..a29a355 100644 --- a/src/platforms/index.ts +++ b/src/platforms/index.ts @@ -1,3 +1,4 @@ +import { forOf } from 'utils/general' import { dummyPlatformForTypeSafety } from './dummyPlatformForTypeSafety' import { Gitea } from './Gitea' import { Gitee } from './Gitee' @@ -17,10 +18,9 @@ function resolvePlatform() { } function getPlatformName() { - const keys = Object.keys(platforms) as (keyof typeof platforms)[] - for (const key of keys) { - if (platform === platforms[key]) return key - } + return forOf(platforms, (name, $platform) => { + if (platform === $platform) return name + }) } export const platform = resolvePlatform() diff --git a/src/platforms/platform.d.ts b/src/platforms/platform.d.ts index e555d7a..f449f53 100644 --- a/src/platforms/platform.d.ts +++ b/src/platforms/platform.d.ts @@ -23,9 +23,9 @@ type Platform = { getCurrentPath(branchName: string): string[] | null setOAuth(code: string): Promise getOAuthLink(): string - delegatePJAXProps?(options?: { - node?: TreeNode - }): void | (React.DOMAttributes & Record) // support data-* attributes + delegatePJAXProps?(options?: { node?: TreeNode }): + | (React.DOMAttributes & Record) // support data-* attributes + | void loadWithPJAX?(url: string, element: HTMLElement): void usePlatformHooks?(): void } diff --git a/src/utils/DOMHelper.ts b/src/utils/DOMHelper.ts index fa56f3a..c27734c 100644 --- a/src/utils/DOMHelper.ts +++ b/src/utils/DOMHelper.ts @@ -47,7 +47,7 @@ export function $( existCallback: undefined | null, otherwise: () => T2, ): HTMLElement | null | T2 -export function $(selector: string, existCallback?: any, otherwise?: any) { +export function $(selector: string, existCallback?: any, otherwise?: any) { // eslint-disable-line @typescript-eslint/no-explicit-any const element = document.querySelector(selector) if (element) { return existCallback ? existCallback(element) : element diff --git a/src/utils/EventHub.ts b/src/utils/EventHub.ts index 1fc02c5..381374b 100644 --- a/src/utils/EventHub.ts +++ b/src/utils/EventHub.ts @@ -18,8 +18,8 @@ export class EventSubscription = VoidFN { ports: { [key in keyof Shape]: EventSubscription diff --git a/src/utils/VisibleNodesGenerator/prepare.ts b/src/utils/VisibleNodesGenerator/prepare.ts index bbce9da..cce967b 100644 --- a/src/utils/VisibleNodesGenerator/prepare.ts +++ b/src/utils/VisibleNodesGenerator/prepare.ts @@ -35,7 +35,7 @@ function recursiveMarkDiff( const [path] = node markDiff(path, state) } else { - const [name, children] = node + const [/* name */, children] = node for (const child of children) { recursiveMarkDiff(child, state, markDiff) } diff --git a/src/utils/config/helper.ts b/src/utils/config/helper.ts index 14bb299..ac69b04 100644 --- a/src/utils/config/helper.ts +++ b/src/utils/config/helper.ts @@ -73,10 +73,7 @@ function applyDefaultConfigs(configs: Partial) { export type VersionedConfig = Record & { configVersion: string } -const prepareConfig = new Promise(async resolve => { - await migrateConfig() - resolve() -}) +const prepareConfig = new Promise((resolve, reject) => migrateConfig().then(resolve, reject)) async function get(): Promise { await prepareConfig diff --git a/src/utils/config/migrations/1.0.1.ts b/src/utils/config/migrations/1.0.1.ts index 45d2a1b..c7904f5 100644 --- a/src/utils/config/migrations/1.0.1.ts +++ b/src/utils/config/migrations/1.0.1.ts @@ -1,3 +1,4 @@ +import { is } from 'utils/is' import { storageHelper } from 'utils/storageHelper' import { Migration } from '.' import { Storage } from '../../storageHelper' @@ -5,7 +6,7 @@ import { Storage } from '../../storageHelper' export const migration: Migration = { version: '1.0.1', async migrate(version) { - const config: any | void = await storageHelper.get([ + const config: JSONObject | void = await storageHelper.get([ 'configVersion', 'sideBarWidth', 'shortcut', @@ -20,6 +21,7 @@ export const migration: Migration = { config && (!('configVersion' in config) || config.configVersion === null || + !is.string(config.configVersion) || config.configVersion < version) ) { await storageHelper.set({ platform_GitHub: config, configVersion: version }) diff --git a/src/utils/config/migrations/1.3.4.ts b/src/utils/config/migrations/1.3.4.ts index 9fd8cd9..bf02f4f 100644 --- a/src/utils/config/migrations/1.3.4.ts +++ b/src/utils/config/migrations/1.3.4.ts @@ -1,3 +1,4 @@ +import { is } from 'utils/is' import { storageHelper } from 'utils/storageHelper' import { Migration } from '.' import { Storage } from '../../storageHelper' @@ -6,7 +7,7 @@ import { Config, VersionedConfig } from '../helper' export const migration: Migration = { version: '1.3.4', async migrate(version) { - const config: any | void = await storageHelper.get & Storage>([ + const config: JSONObject | void = await storageHelper.get & Storage>([ 'configVersion', 'platform_undefined', 'platform_GitHub', @@ -15,6 +16,7 @@ export const migration: Migration = { if ( config && 'configVersion' in config && + is.string(config.configVersion) && config.configVersion < version && (config.platform_GitHub || config.platform_undefined) && !config['platform_github.com'] diff --git a/src/utils/config/migrations/2.6.0.ts b/src/utils/config/migrations/2.6.0.ts index d5a522f..2579bc3 100644 --- a/src/utils/config/migrations/2.6.0.ts +++ b/src/utils/config/migrations/2.6.0.ts @@ -13,12 +13,9 @@ export const migration: Migration = { await onConfigOutdated(version, async configs => { for (const key of Object.keys(configs)) { - if ( - typeof configs[key] === 'object' && - configs[key] !== null && - 'access_token' in configs[key] - ) { - const configBeforeMigrate: ConfigBeforeMigrate = configs[key] + const target = configs[key] + if (typeof target === 'object' && target !== null && 'access_token' in target) { + const configBeforeMigrate: ConfigBeforeMigrate = target const { access_token: accessToken, ...rest } = configBeforeMigrate const configAfterMigrate: ConfigAfterMigrate = { ...rest, diff --git a/src/utils/config/migrations/3.0.0.ts b/src/utils/config/migrations/3.0.0.ts index 4db2fba..2c4cbbd 100644 --- a/src/utils/config/migrations/3.0.0.ts +++ b/src/utils/config/migrations/3.0.0.ts @@ -16,7 +16,7 @@ export const migration: Migration = { const key = 'platform_github.com' const config = configs[key] if (typeof config === 'object' && config !== null && 'copySnippetButton' in config) { - const configBeforeMigrate: ConfigBeforeMigrate = config + const configBeforeMigrate = config as ConfigBeforeMigrate const { copySnippetButton, ...rest } = configBeforeMigrate if (copySnippetButton) { const configAfterMigrate: ConfigAfterMigrate = { diff --git a/src/utils/config/migrations/3.5.0.ts b/src/utils/config/migrations/3.5.0.ts index 1c25b3c..eb7415c 100644 --- a/src/utils/config/migrations/3.5.0.ts +++ b/src/utils/config/migrations/3.5.0.ts @@ -9,14 +9,14 @@ export const migration: Migration = { copyFileButton: boolean } type ConfigAfterMigrate = { - copyFileButton: boolean + copyFileButton: false } await onConfigOutdated(version, async configs => { const key = 'platform_github.com' const config = configs[key] if (typeof config === 'object' && config !== null && 'copyFileButton' in config) { - const configBeforeMigrate: ConfigBeforeMigrate = config + const configBeforeMigrate = config as ConfigBeforeMigrate const { copyFileButton, ...rest } = configBeforeMigrate if (copyFileButton) { const configAfterMigrate: ConfigAfterMigrate = { diff --git a/src/utils/config/migrations/index.ts b/src/utils/config/migrations/index.ts index 276c197..a864f86 100644 --- a/src/utils/config/migrations/index.ts +++ b/src/utils/config/migrations/index.ts @@ -19,14 +19,14 @@ export async function migrateConfig() { } } -export async function onConfigOutdated( +export async function onConfigOutdated( configVersion: string, runIfOutdated: (config: T) => Async, ) { const config = await storageHelper.get() if (config && config.configVersion < configVersion) { - const { configVersion: $configVersion, ...restConfig } = config + const { configVersion: $configVersion, ...restConfig } = config // eslint-disable-line @typescript-eslint/no-unused-vars await runIfOutdated(restConfig as T) await storageHelper.set({ configVersion }) } diff --git a/src/utils/general.ts b/src/utils/general.ts index 982ed86..e06852f 100644 --- a/src/utils/general.ts +++ b/src/utils/general.ts @@ -60,21 +60,6 @@ export function friendlyFormatShortcut(shortcut?: string) { } } -/** - * if item's name matches path, return the depth of the item - * else return 0 - */ -function measureDistance(item: TreeNode, path: TreeNode['name'][]): number { - const pathString = path.join('/') - if (item.name.startsWith(pathString + '/')) { - // If accessing a leading item of compressed node, path will be shorter than item.name - return path.length - } else if (pathString === item.name || pathString.startsWith(item.name + '/')) { - return item.name.split('/').length - } - return 0 -} - export async function traverse( range: T[] = [], conditionAndEffect: (node: T) => Async, @@ -121,7 +106,11 @@ export function parseURLSearch(search = window.location.search) { return new URLSearchParams(search) } -export async function JSONRequest(url: string, data: any, extra: RequestInit = { method: 'post' }) { +export async function JSONRequest( + url: string, + data: D, + extra: RequestInit = { method: 'post' }, +) { return ( await fetch(url, { mode: 'cors', @@ -171,12 +160,12 @@ export function isValidRegexpSource(source: string) { return Boolean(safeRegexp(source)) } -export function withEffect any>( +export function withEffect any>( // eslint-disable-line @typescript-eslint/no-explicit-any method: Method, effect: (payload: ReturnType) => void, ): (...args: Parameters) => ReturnType { return (...args) => { - const returnValue = method.apply(null, args) + const returnValue = method(...args) Promise.resolve(returnValue).then(effect) return returnValue } @@ -186,22 +175,6 @@ export function run(fn: () => T) { return fn() } -export function createPromiseQueue() { - let promise: Promise - return { - async enter() { - let leave: () => void - const current = new Promise(resolve => (leave = () => resolve())) - - const lastPromise = promise - promise = current! - if (lastPromise) await lastPromise - - return leave! - }, - } -} - export function isOpenInNewWindowClick(event: React.MouseEvent) { return ( (os === OperatingSystems.macOS && (event.metaKey || event.shiftKey)) || @@ -228,3 +201,11 @@ export function formatHash(hash?: string) { export function isNotFalsy(value: T | undefined | null): value is T { return value !== undefined && value !== null } + +export function forOf(target: T, callback: (key: K, value: T[K]) => R) { + for (const key of Object.keys(target)) { + const $key = key as keyof typeof target + const r = callback($key, target[$key]) + if (r !== undefined) return r + } +} diff --git a/src/utils/gitSubmodule.ts b/src/utils/gitSubmodule.ts index c6ef1aa..35a82f7 100644 --- a/src/utils/gitSubmodule.ts +++ b/src/utils/gitSubmodule.ts @@ -10,7 +10,7 @@ const subModuleURLRegex = { function transformModuleGitURL(node: TreeNode, URL: string) { const matched = URL.match(subModuleURLRegex.git) if (!matched) return - const [_, userName, repoName] = matched + const [, userName, repoName] = matched return appendCommitPath(`https://${window.location.host}/${userName}/${repoName}`, node) } diff --git a/src/utils/hooks/useAsyncMemo.ts b/src/utils/hooks/useAsyncMemo.ts deleted file mode 100644 index 62e2f53..0000000 --- a/src/utils/hooks/useAsyncMemo.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react' -import { useStateIO } from './useStateIO' - -export function useAsyncMemo( - factory: (dependencies: D) => T | Promise, - deps: D, - initialValue: T, -): T { - const firstTime = React.useRef(true) - const state = useStateIO(() => initialValue) - React.useEffect(() => { - if (firstTime.current) firstTime.current = false - Promise.resolve(factory(deps)).then(consumed => state.onChange(() => consumed)) - }, deps) - return state.value -} diff --git a/src/utils/hooks/useCatchNetworkError.ts b/src/utils/hooks/useCatchNetworkError.ts index dbbb180..6885c3b 100644 --- a/src/utils/hooks/useCatchNetworkError.ts +++ b/src/utils/hooks/useCatchNetworkError.ts @@ -43,6 +43,6 @@ export function useCatchNetworkError() { } } }, - [accessToken /* , stateContext.value, errorContext.value */], + [accessToken /* , stateContext.value, errorContext.value */], // eslint-disable-line react-hooks/exhaustive-deps ) } diff --git a/src/utils/hooks/useEffectOnSerializableUpdates.ts b/src/utils/hooks/useEffectOnSerializableUpdates.ts index ecbcd4c..0ba2070 100644 --- a/src/utils/hooks/useEffectOnSerializableUpdates.ts +++ b/src/utils/hooks/useEffectOnSerializableUpdates.ts @@ -5,5 +5,6 @@ export function useEffectOnSerializableUpdates( serialize: (value: T) => string, onChange: (value: T) => void, ) { - React.useEffect(() => onChange(value), [onChange, serialize(value)]) + const serialized = React.useMemo(() => serialize(value), [value, serialize]) + React.useEffect(() => onChange(value), [onChange, serialized]) // eslint-disable-line react-hooks/exhaustive-deps } diff --git a/src/utils/hooks/useMediaStyleSheet.ts b/src/utils/hooks/useMediaStyleSheet.ts deleted file mode 100644 index 40d3c76..0000000 --- a/src/utils/hooks/useMediaStyleSheet.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from 'react' -import { createStyleSheet, setStyleSheetMedia } from '../general' - -export function useMediaStyleSheet( - content: string, - getMediaQuery: (width: number) => string[], - size: number, -) { - const style = React.useRef() - // this order may prevent first effect actually occur at first time - React.useEffect(() => { - setSheetMedia() - }, [size]) - React.useEffect(() => { - style.current = createStyleSheet(content) - setSheetMedia() - }, []) - function setSheetMedia() { - if (style.current) - setStyleSheetMedia( - style.current, - getMediaQuery(size) - .map(query => `(${query})`) - .join(' and '), - ) - } -} diff --git a/src/utils/hooks/usePJAX.ts b/src/utils/hooks/usePJAX.ts index 396dcaf..9234e56 100644 --- a/src/utils/hooks/usePJAX.ts +++ b/src/utils/hooks/usePJAX.ts @@ -22,7 +22,7 @@ const config: Config = { }, link: 'a:not(a)', // this helps fixing the go-back-in-history issue form: 'form:not(form)', // prevent blocking form submissions - fallback(target, reason) { + fallback(/* target, reason */) { // prevent unexpected reload }, } diff --git a/src/utils/hooks/useResizeHandler.ts b/src/utils/hooks/useResizeHandler.ts index 2911a30..9c937d4 100644 --- a/src/utils/hooks/useResizeHandler.ts +++ b/src/utils/hooks/useResizeHandler.ts @@ -31,13 +31,14 @@ export function useResizeHandler( if (!pointerDown.current) return const [x0, y0] = initialSizeRef.current // Allow minor movement, this happened unintentionally for few times when I use track pad - pointerMoved.current = pointerMoved.current || (clientX - x0) ** 2 + (clientY - y0) ** 2 > distanceTolerance ** 2 + pointerMoved.current = + pointerMoved.current || (clientX - x0) ** 2 + (clientY - y0) ** 2 > distanceTolerance ** 2 const [x1, y1] = baseSize.current onResize([x1 + clientX - x0, y1 + clientY - y0]) } window.addEventListener('pointermove', onPointerMove) return () => window.removeEventListener('pointermove', onPointerMove) - }, [onResize]) + }, [onResize, distanceTolerance]) React.useEffect(() => { const onPointerUp = (e: PointerEvent) => { @@ -53,16 +54,19 @@ export function useResizeHandler( } window.addEventListener('pointerup', onPointerUp) return () => window.removeEventListener('pointerup', onPointerUp) - }, []) + }, [onClick, onResizeStateChange]) - const onPointerDown = React.useCallback((e: React.PointerEvent) => { - e.preventDefault() // Prevent unexpected selection when dragging in Safari - const { clientX, clientY } = e - pointerDown.current = true - initialSizeRef.current = [clientX, clientY] - baseSize.current = latestPropSize.current - onResizeStateChange?.('resizing') - }, []) + const onPointerDown = React.useCallback( + (e: React.PointerEvent) => { + e.preventDefault() // Prevent unexpected selection when dragging in Safari + const { clientX, clientY } = e + pointerDown.current = true + initialSizeRef.current = [clientX, clientY] + baseSize.current = latestPropSize.current + onResizeStateChange?.('resizing') + }, + [onResizeStateChange], + ) return { onPointerDown } } diff --git a/src/utils/hooks/useUpdateReason.ts b/src/utils/hooks/useUpdateReason.ts index 327bf74..5b3abf2 100644 --- a/src/utils/hooks/useUpdateReason.ts +++ b/src/utils/hooks/useUpdateReason.ts @@ -5,19 +5,19 @@ export function useUpdateReason

(props: P) { const lastPropsRef = React.useRef

(props) React.useEffect(() => { if (IN_PRODUCTION_MODE) return - let output: unknown[][] = [] + const output: ([string, keyof P, P[keyof P]] | [string, keyof P, P[keyof P], P[keyof P]])[] = [] for (const key of Object.keys(props)) { if (key === 'children') continue const $key = key as keyof P - if (!(key in lastPropsRef.current)) output.push([`[Added]`, key, props[$key]]) + if (!(key in lastPropsRef.current)) output.push([`[Added]`, $key, props[$key]]) if (lastPropsRef.current[$key] !== props[$key]) - output.push([`[Updated]`, key, lastPropsRef.current[$key], props[$key]]) + output.push([`[Updated]`, $key, lastPropsRef.current[$key], props[$key]]) } for (const key of Object.keys(lastPropsRef.current)) { if (key === 'children') continue const $key = key as keyof P - if (!(key in props)) output.push([`[Removed]`, key, props[$key]]) + if (!(key in props)) output.push([`[Removed]`, $key, props[$key]]) } if (output.length) { diff --git a/src/utils/keyHelper.ts b/src/utils/keyHelper.ts index 407d513..91f3f84 100644 --- a/src/utils/keyHelper.ts +++ b/src/utils/keyHelper.ts @@ -46,8 +46,8 @@ export function parseEvent(e: KeyboardEvent | React.KeyboardEvent) { const keys = { meta, ctrl, shift, alt, [code]: true } const combination = parse( Object.entries(keys) - .filter(([key, pressed]) => pressed) - .map(([key, pressed]) => key) + .filter(([, pressed]) => pressed) + .map(([key]) => key) .join('+'), ) return combination diff --git a/src/utils/parseIconMapCSV.ts b/src/utils/parseIconMapCSV.ts index a820307..979442c 100644 --- a/src/utils/parseIconMapCSV.ts +++ b/src/utils/parseIconMapCSV.ts @@ -71,7 +71,7 @@ export function getFileIconURL(node: TreeNode) { // 1. swap time with space // 2. prevent app crash on when extension context invalidates const extensionURL = browser.runtime.getURL('').replace(/\/$/, '') -export function getIconURL(type: 'folder' | 'file', name: string = 'default', open?: boolean) { +export function getIconURL(type: 'folder' | 'file', name = 'default', open?: boolean) { const filename = (name === 'default' ? 'default_' + type : type + '_type_' + name) + (open ? '_opened' : '') + diff --git a/src/utils/storageHelper.ts b/src/utils/storageHelper.ts index e3dc40a..e393f9f 100644 --- a/src/utils/storageHelper.ts +++ b/src/utils/storageHelper.ts @@ -9,20 +9,12 @@ export type Storage = { // ['platform_github.com']?: Config } -async function get< - T extends { - [key: string]: any - } ->(mapping: string | string[] | null = null): Promise { - try { - return (await localStorage.get(mapping || undefined)) as T - } catch (err) {} +async function get(mapping: string | string[] | null = null) { + return (await localStorage.get(mapping || undefined)) as T | undefined } -function set(value: any): Promise | void { - try { - return localStorage.set(value) - } catch (err) {} +function set(value: T): Promise | void { + return localStorage.set(value) } export const storageHelper = { get, set }