diff --git a/src/analytics.ts b/src/analytics.ts index 90cab9b..9cde53b 100644 --- a/src/analytics.ts +++ b/src/analytics.ts @@ -10,9 +10,9 @@ export function raiseError(error: Error) { export const withErrorLog: Middleware = function withErrorLog(method, args) { return [ - async function() { + async function(...args: any[]) { try { - await method.apply(this, arguments) + await method.apply(null, args) } catch (error) { raiseError(error) } @@ -21,9 +21,13 @@ export const withErrorLog: Middleware = function withErrorLog(method, args) { ] } -function encodeParams(params: object) { +function encodeParams< + T extends { + [key: string]: any + } +>(params: T) { return Object.keys(params) - .map((key: keyof object) => `${key}=${encodeURIComponent(JSON.stringify(params[key]))}`) + .map((key: keyof T) => `${key}=${encodeURIComponent(JSON.stringify(params[key]))}`) .join('&') } diff --git a/src/components/FileExplorer.tsx b/src/components/FileExplorer.tsx index 9aead51..674f999 100644 --- a/src/components/FileExplorer.tsx +++ b/src/components/FileExplorer.tsx @@ -14,16 +14,13 @@ export type Props = { metaData: MetaData freeze: boolean compressSingletonFolder: boolean - accessToken: string + accessToken: string | undefined toggleShowSettings: React.MouseEventHandler } class FileExplorer extends React.Component { static defaultProps: Partial = { - treeData: null, - metaData: null, freeze: false, - visibleNodes: null, } componentWillMount() { @@ -60,7 +57,7 @@ class FileExplorer extends React.Component { { className={cx(`file-explorer`, { freeze })} tabIndex={-1} onKeyDown={handleKeyDown} - onClick={freeze ? toggleShowSettings : null} + onClick={freeze ? toggleShowSettings : undefined} > {stateText ? ( diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index a595042..2598d0a 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -80,7 +80,8 @@ class Gitako extends React.PureComponent { {metaData && } {errorDueToAuth ? this.renderAccessDeniedError() - : metaData && ( + : metaData && + treeData && ( = { call: TriggerOtherMethod } -export type MethodCreator = (dispatch: Dispatch) => Method +export type MethodCreator = ( + dispatch: Dispatch, +) => Method type Sources = { [key: string]: MethodCreator diff --git a/src/driver/core/FileExplorer.ts b/src/driver/core/FileExplorer.ts index 13de683..175d7e0 100644 --- a/src/driver/core/FileExplorer.ts +++ b/src/driver/core/FileExplorer.ts @@ -28,7 +28,12 @@ function getVisibleParentNode(nodes: TreeNode[], focusedNode: TreeNode, depths: const focusedNodeIndex = nodes.indexOf(focusedNode) const focusedNodeDepth = depths.get(focusedNode) let indexOfParentNode = focusedNodeIndex - 1 - while (indexOfParentNode !== -1 && depths.get(nodes[indexOfParentNode]) >= focusedNodeDepth) { + let depth: number | undefined + while (indexOfParentNode !== -1) { + depth = depths.get(nodes[indexOfParentNode]) + if (depth === undefined || focusedNodeDepth === undefined || !(depth >= focusedNodeDepth)) { + break + } --indexOfParentNode } const parentNode = nodes[indexOfParentNode] @@ -44,13 +49,13 @@ const init: MethodCreator = dispatch => () => function resolveGitModules(root: TreeNode, blobData: BlobData) { if (blobData) { - if (blobData.encoding === 'base64') { + if (blobData.encoding === 'base64' && blobData.content && Array.isArray(root.contents)) { const content = atob(blobData.content) const parsed = ini.parse(content) Object.values(parsed).map((value: { url: string; path: string }) => { const { url, path } = value // for now, handle modules at root only - const node = root.contents.find(node => node.path === path) + const node = Array.isArray(root.contents) && root.contents.find(node => node.path === path) if (node) { node.url = url } @@ -66,6 +71,7 @@ const setUpTree: MethodCreator = dispatch => () => const { root, gitModules } = treeParser.parse(treeData, metaData) if (gitModules) { + if (metaData.userName && metaData.repoName && gitModules.sha) { const blobData = await GitHubHelper.getBlobData({ userName: metaData.userName, repoName: metaData.repoName, @@ -160,14 +166,16 @@ const handleKeyDown: MethodCreator< if (focusedNode.type === 'tree') { if (expandedNodes.has(focusedNode)) { const nextNode = nodes[focusedNodeIndex + 1] - if (depths.get(nextNode) > depths.get(focusedNode)) { + const d1 = depths.get(nextNode) + const d2 = depths.get(focusedNode) + if (d1 !== undefined && d2 !== undefined && d1 > d2) { dispatch.call(focusNode, nextNode, false) } } else { dispatch.call(setExpand, focusedNode, true) } } else if (focusedNode.type === 'blob') { - DOMHelper.loadWithPJAX(focusedNode.url) + if (focusedNode.url) DOMHelper.loadWithPJAX(focusedNode.url) } else if (focusedNode.type === 'commit') { window.open(focusedNode.url) } @@ -177,7 +185,7 @@ const handleKeyDown: MethodCreator< if (focusedNode.type === 'tree') { dispatch.call(setExpand, focusedNode, true) } else if (focusedNode.type === 'blob') { - DOMHelper.loadWithPJAX(focusedNode.url) + if (focusedNode.url) DOMHelper.loadWithPJAX(focusedNode.url) } else if (focusedNode.type === 'commit') { window.open(focusedNode.url) } @@ -231,13 +239,14 @@ const delayExpandThreshold = 400 function shouldDelayExpand(node: TreeNode) { return ( visibleNodesGenerator.visibleNodes.expandedNodes.has(node) && + Array.isArray(node.contents) && node.contents.length > delayExpandThreshold ) } const setExpand: MethodCreator = dispatch => ( node, - expand = false + expand = false, ) => { visibleNodesGenerator.setExpand(node, expand) const applyChanges = () => dispatch.call(focusNode, node, false) @@ -251,7 +260,7 @@ const setExpand: MethodCreator = dis const toggleNodeExpansion: MethodCreator = dispatch => ( node, - skipScrollToNode + skipScrollToNode, ) => { visibleNodesGenerator.toggleExpand(node) const applyChanges = () => { @@ -285,7 +294,7 @@ const onNodeClick: MethodCreator = dispatch = dispatch.call(toggleNodeExpansion, node, true) } else if (node.type === 'blob') { dispatch.call(focusNode, node, true) - DOMHelper.loadWithPJAX(node.url) + if (node.url) DOMHelper.loadWithPJAX(node.url) } else if (node.type === 'commit') { window.open(node.url, '_blank') } diff --git a/src/driver/core/SideBar.ts b/src/driver/core/SideBar.ts index b9bc1dc..ec127a8 100644 --- a/src/driver/core/SideBar.ts +++ b/src/driver/core/SideBar.ts @@ -180,7 +180,7 @@ const setShouldShow: MethodCreator< ConnectorState, [ConnectorState['shouldShow']] > = dispatch => shouldShow => { - dispatch.set({ shouldShow }, shouldShow ? DOMHelper.focusFileExplorer : null) + dispatch.set({ shouldShow }, shouldShow ? DOMHelper.focusFileExplorer : undefined) DOMHelper.setBodyIndent(shouldShow) } diff --git a/src/utils/DOMHelper.ts b/src/utils/DOMHelper.ts index f1936ec..c839a0e 100644 --- a/src/utils/DOMHelper.ts +++ b/src/utils/DOMHelper.ts @@ -28,7 +28,7 @@ function setBodyIndent(shouldShowGitako: boolean) { } } -function $ any, O extends () => any>( +function $ any, O extends () => any>( selector: string, existCallback?: E, otherwise?: O, @@ -41,7 +41,7 @@ function $ any, O extends () => any>( : ReturnType | ReturnType { const element = document.querySelector(selector) if (element) { - return existCallback ? existCallback(element) : element + return existCallback ? existCallback(element as EE) : element } return otherwise ? otherwise() : null } @@ -60,16 +60,15 @@ function getBranches() { function getCurrentBranch() { const selectedBranchButtonSelector = '.repository-content > .file-navigation > .branch-select-menu > button' - const branchNameFromButtonElement = $( - selectedBranchButtonSelector, - (element: HTMLButtonElement) => element.title.trim(), + const branchNameFromButtonElement = $(selectedBranchButtonSelector, element => + (element as HTMLButtonElement).title.trim(), ) if (branchNameFromButtonElement) return branchNameFromButtonElement const selectedBranchSelector = '.select-menu.branch-select-menu .select-menu-modal .select-menu-list .select-menu-item.selected svg.select-menu-item-icon + span' const branchNameFromSelectElement = $(selectedBranchSelector, element => - element.textContent.trim(), + element.textContent ? element.textContent.trim() : '', ) if (branchNameFromSelectElement) return branchNameFromSelectElement } @@ -306,7 +305,7 @@ function attachCopySnippet() { * * */ - target.parentNode.insertBefore(clippy, target) + if (target.parentNode) target.parentNode.insertBefore(clippy, target) } } }), @@ -318,13 +317,18 @@ function attachCopySnippet() { */ function focusFileExplorer() { const sideBarContentSelector = '.gitako-side-bar .file-explorer' - $(sideBarContentSelector, (sideBarElement: HTMLElement) => sideBarElement.focus()) + $(sideBarContentSelector, sideBarElement => { + if (sideBarElement instanceof HTMLElement) sideBarElement.focus() + }) } function focusSearchInput() { const searchInputSelector = '.search-input' - $(searchInputSelector, (searchInputElement: HTMLElement) => { - if (document.activeElement !== searchInputElement) { + $(searchInputSelector, searchInputElement => { + if ( + document.activeElement !== searchInputElement && + searchInputElement instanceof HTMLElement + ) { searchInputElement.focus() } }) diff --git a/src/utils/GitHubHelper.ts b/src/utils/GitHubHelper.ts index 641ba17..ce1010e 100644 --- a/src/utils/GitHubHelper.ts +++ b/src/utils/GitHubHelper.ts @@ -21,7 +21,7 @@ type Options = { } async function request(url: string, { accessToken }: Options = {}) { - const headers = {} as { + const headers = {} as HeadersInit & { Authorization?: string } if (accessToken) { @@ -76,7 +76,7 @@ async function getTreeData({ userName, repoName, branchName, accessToken }: Meta export type ItemData = { userName: string repoName: string - accessToken: string + accessToken?: string } export type BlobData = { @@ -98,7 +98,7 @@ async function getBlobData({ function getUrlForRedirect( { userName, repoName, branchName }: MetaData, type = 'blob', - path?: string + path?: string, ) { return `https://github.com/${userName}/${repoName}/${type}/${branchName}/${path}` } diff --git a/src/utils/configHelper.ts b/src/utils/configHelper.ts index 4df8ee9..d506dc7 100644 --- a/src/utils/configHelper.ts +++ b/src/utils/configHelper.ts @@ -2,8 +2,8 @@ import storageHelper from 'utils/storageHelper' import { pick } from 'utils/general' type Config = { - shortcut: string | null - access_token: string | null + shortcut: string | undefined + access_token: string | undefined compressSingletonFolder: boolean copyFileButton: boolean copySnippetButton: boolean @@ -18,8 +18,8 @@ export enum configKeys { } const defaultConfigs = { - shortcut: null, - access_token: null, + shortcut: undefined, + access_token: undefined, compressSingletonFolder: true, copyFileButton: true, copySnippetButton: true,