mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
parent
0492784b70
commit
39f82a8739
15 changed files with 301 additions and 78 deletions
|
|
@ -2,6 +2,7 @@ import { useConfigs } from 'containers/ConfigsContext'
|
|||
import { platform } from 'platforms'
|
||||
import * as React from 'react'
|
||||
import { cx } from 'utils/cx'
|
||||
import { cancelEvent } from 'utils/DOMHelper'
|
||||
import { getFileIconURL, getFolderIconURL } from 'utils/parseIconMapCSV'
|
||||
import { Icon } from '../Icon'
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ export const Node = React.memo(function Node({
|
|||
onFocus,
|
||||
}: Props) {
|
||||
const { compactFileTree: compact } = useConfigs().value
|
||||
const ref = React.useRef<HTMLDivElement>(null)
|
||||
return (
|
||||
<a
|
||||
href={node.url}
|
||||
|
|
@ -52,13 +54,24 @@ export const Node = React.memo(function Node({
|
|||
title={node.path}
|
||||
target={node.type === 'commit' ? '_blank' : undefined}
|
||||
rel="noopener noreferrer"
|
||||
{...platform.delegateFastRedirectAnchorProps?.({ node })}
|
||||
{...(node.type === 'blob' ? platform.delegateFastRedirectAnchorProps?.({ node }) : null)}
|
||||
>
|
||||
<div className={'node-item-label'}>
|
||||
<NodeItemIcon node={node} open={expanded} loading={loading} />
|
||||
{renderLabelText(node)}
|
||||
</div>
|
||||
{renderActions && <div className={'actions'}>{renderActions(node)}</div>}
|
||||
{renderActions && (
|
||||
<div
|
||||
ref={ref}
|
||||
className={'actions'}
|
||||
onClick={e => {
|
||||
// exclude elements mounted outside but still bubbles event through react to here
|
||||
if (e.target instanceof Element && ref.current?.contains(e.target)) cancelEvent(e)
|
||||
}}
|
||||
>
|
||||
{renderActions(node)}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
import { CommentIcon } from '@primer/octicons-react'
|
||||
import {
|
||||
CheckIcon,
|
||||
CommentIcon,
|
||||
CrossReferenceIcon,
|
||||
KebabHorizontalIcon,
|
||||
} from '@primer/octicons-react'
|
||||
import { ActionList, AnchoredOverlay } from '@primer/react'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { PortalContext } from 'containers/PortalContext'
|
||||
import { platform } from 'platforms'
|
||||
import * as React from 'react'
|
||||
import { useCopyToClipboard } from 'react-use'
|
||||
import { cx } from 'utils/cx'
|
||||
import { cancelEvent, onEnterKeyDown } from 'utils/DOMHelper'
|
||||
import { is } from 'utils/is'
|
||||
import { Icon } from '../../Icon'
|
||||
import { SearchMode } from '../../searchModes'
|
||||
|
|
@ -38,6 +49,146 @@ export function useRenderFileStatus() {
|
|||
)
|
||||
}
|
||||
|
||||
function renderNodeContextMenu(node: TreeNode) {
|
||||
return <NodeContextMenu node={node} />
|
||||
}
|
||||
export function useRenderMoreActions() {
|
||||
return renderNodeContextMenu
|
||||
}
|
||||
|
||||
function NodeContextMenu({ node }: { node: TreeNode }) {
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
const [copied, setCopied] = React.useState<string | null>(null)
|
||||
const [copyState, copyToClipboard] = useCopyToClipboard()
|
||||
const portalName = React.useContext(PortalContext)
|
||||
const actionElements = {
|
||||
copyPermalink:
|
||||
node.permalink &&
|
||||
(() => {
|
||||
const mark = 'permalink'
|
||||
const onTrigger = (e: React.SyntheticEvent) => {
|
||||
cancelEvent(e)
|
||||
if (node.permalink) {
|
||||
copyToClipboard(node.permalink)
|
||||
setCopied(mark)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ActionList.Item {...getTriggerProps(onTrigger)}>
|
||||
Copy permalink
|
||||
{copyState.value && copied === mark ? (
|
||||
<ActionList.TrailingVisual>
|
||||
<CheckIcon />
|
||||
</ActionList.TrailingVisual>
|
||||
) : null}
|
||||
</ActionList.Item>
|
||||
)
|
||||
})(),
|
||||
copyLink:
|
||||
node.url &&
|
||||
(() => {
|
||||
const mark = 'link'
|
||||
const onTrigger = (e: React.SyntheticEvent) => {
|
||||
cancelEvent(e)
|
||||
if (node.url) {
|
||||
copyToClipboard(node.url)
|
||||
setCopied(mark)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ActionList.Item {...getTriggerProps(onTrigger)}>
|
||||
Copy link
|
||||
{copyState.value && copied === mark ? (
|
||||
<ActionList.TrailingVisual>
|
||||
<CheckIcon />
|
||||
</ActionList.TrailingVisual>
|
||||
) : null}
|
||||
</ActionList.Item>
|
||||
)
|
||||
})(),
|
||||
copyRelativePath: (() => {
|
||||
const mark = 'path'
|
||||
const onTrigger = (e: React.SyntheticEvent) => {
|
||||
cancelEvent(e)
|
||||
setCopied(mark)
|
||||
copyToClipboard(node.path)
|
||||
}
|
||||
return (
|
||||
<ActionList.Item {...getTriggerProps(onTrigger)}>
|
||||
Copy relative path
|
||||
{copyState.value && copied === mark ? (
|
||||
<ActionList.TrailingVisual>
|
||||
<CheckIcon />
|
||||
</ActionList.TrailingVisual>
|
||||
) : null}
|
||||
</ActionList.Item>
|
||||
)
|
||||
})(),
|
||||
openRawContent: node.rawLink && (
|
||||
<ActionList.LinkItem
|
||||
onKeyDown={e =>
|
||||
onEnterKeyDown(e, () => e.target instanceof HTMLElement && e.target.click())
|
||||
}
|
||||
href={node.rawLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Open raw content
|
||||
<ActionList.TrailingVisual>
|
||||
<CrossReferenceIcon />
|
||||
</ActionList.TrailingVisual>
|
||||
</ActionList.LinkItem>
|
||||
),
|
||||
goToDirectory: node.type === 'tree' && node.url && (
|
||||
<ActionList.LinkItem
|
||||
onKeyDown={e =>
|
||||
onEnterKeyDown(e, () => e.target instanceof HTMLElement && e.target.click())
|
||||
}
|
||||
href={node.url}
|
||||
data-gitako-bypass-click
|
||||
rel="noopener noreferrer"
|
||||
{...platform.delegateFastRedirectAnchorProps?.({ node })}
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Go to directory
|
||||
</ActionList.LinkItem>
|
||||
),
|
||||
}
|
||||
|
||||
return (
|
||||
<AnchoredOverlay
|
||||
renderAnchor={anchorProps => (
|
||||
<button
|
||||
{...anchorProps}
|
||||
aria-label={`More actions`}
|
||||
className={cx('context-menu', anchorProps.className, { active: isOpen })}
|
||||
>
|
||||
<Icon IconComponent={KebabHorizontalIcon} />
|
||||
</button>
|
||||
)}
|
||||
open={isOpen}
|
||||
onOpen={() => setIsOpen(true)}
|
||||
onClose={() => setIsOpen(false)}
|
||||
overlayProps={{
|
||||
portalContainerName: portalName || undefined,
|
||||
onKeyDown: e => cancelEvent(e),
|
||||
}}
|
||||
>
|
||||
<ActionList>
|
||||
{actionElements.copyPermalink}
|
||||
{actionElements.copyLink}
|
||||
{actionElements.copyRelativePath}
|
||||
|
||||
{(actionElements.openRawContent || actionElements.goToDirectory) && <ActionList.Divider />}
|
||||
|
||||
{actionElements.openRawContent}
|
||||
{actionElements.goToDirectory}
|
||||
</ActionList>
|
||||
</AnchoredOverlay>
|
||||
)
|
||||
}
|
||||
|
||||
export function useRenderFileCommentAmounts() {
|
||||
function renderFileCommentAmounts(node: TreeNode) {
|
||||
return node.comments?.active ? (
|
||||
|
|
@ -69,11 +220,7 @@ export function useRenderFindInFolderButton(
|
|||
<button
|
||||
title={'Find in folder...'}
|
||||
className={'find-in-folder-button'}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
onSearch(node.path + '/', searchMode)
|
||||
}}
|
||||
onClick={() => onSearch(node.path + '/', searchMode)}
|
||||
>
|
||||
<Icon type="search" />
|
||||
</button>
|
||||
|
|
@ -93,11 +240,7 @@ export function useRenderGoToButton(searched: boolean, goTo: (path: string[]) =>
|
|||
<button
|
||||
title={'Reveal in file tree'}
|
||||
className={'go-to-button'}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
goTo(node.path.split('/'))
|
||||
}}
|
||||
onClick={() => goTo(node.path.split('/'))}
|
||||
>
|
||||
<Icon type="go-to" />
|
||||
</button>
|
||||
|
|
@ -107,3 +250,8 @@ export function useRenderGoToButton(searched: boolean, goTo: (path: string[]) =>
|
|||
[searched, goTo],
|
||||
)
|
||||
}
|
||||
|
||||
const getTriggerProps = (onTrigger: (e: React.SyntheticEvent) => void) => ({
|
||||
onClick: onTrigger,
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLElement>) => onEnterKeyDown(e, onTrigger),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ export function useHandleNodeClick(
|
|||
// giving recursive toggle action higher priority than default action
|
||||
if (!recursive && isOpenInNewWindowClick(event)) return
|
||||
|
||||
// check if clicked inside an element which has `data-gitako-bypass-click` set
|
||||
if (event.target instanceof HTMLElement) {
|
||||
let e = event.target
|
||||
while (e.parentElement) {
|
||||
if (e.dataset.gitakoBypassClick) return
|
||||
e = e.parentElement
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
toggleExpansion(node, { recursive })
|
||||
break
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { Label, Text } from '@primer/react'
|
||||
import { Label, registerPortalRoot, Text } from '@primer/react'
|
||||
import { useFocusOnPendingTarget } from 'components/FocusTarget'
|
||||
import { LoadingIndicator } from 'components/LoadingIndicator'
|
||||
import { SearchBar } from 'components/SearchBar'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { PortalContext } from 'containers/PortalContext'
|
||||
import { RepoContext } from 'containers/RepoContext'
|
||||
import { platform } from 'platforms'
|
||||
import * as React from 'react'
|
||||
|
|
@ -24,6 +25,7 @@ import {
|
|||
useRenderFileStatus,
|
||||
useRenderFindInFolderButton,
|
||||
useRenderGoToButton,
|
||||
useRenderMoreActions,
|
||||
} from './hooks/useNodeRenderers'
|
||||
import { useHandleNodeClick } from './hooks/useOnNodeClick'
|
||||
import { useOnSearch } from './hooks/useOnSearch'
|
||||
|
|
@ -107,6 +109,12 @@ function LoadedFileExplorer({
|
|||
overScan: 10,
|
||||
})
|
||||
|
||||
const portalName = React.useMemo(() => `${Math.random()}`, [])
|
||||
React.useEffect(() => {
|
||||
const current = scrollElementRef.current
|
||||
if (current) registerPortalRoot(current, portalName)
|
||||
}, [scrollElementRef, portalName])
|
||||
|
||||
// - init loading
|
||||
// - "top"
|
||||
// - jump to file
|
||||
|
|
@ -149,6 +157,7 @@ function LoadedFileExplorer({
|
|||
useRenderGoToButton(searched, goTo),
|
||||
useRenderFindInFolderButton(onSearch),
|
||||
useRenderFileCommentAmounts(),
|
||||
useRenderMoreActions(),
|
||||
useRenderFileStatus(),
|
||||
])
|
||||
const renderLabelText = useRenderLabelText(searchKey)
|
||||
|
|
@ -211,26 +220,30 @@ function LoadedFileExplorer({
|
|||
onScroll={onScroll}
|
||||
tabIndex={-1} // prevent getting focus via tab key on GitHub
|
||||
>
|
||||
<div style={containerStyle}>
|
||||
{visibleRows.map(({ row, style }) => {
|
||||
const node = nodes[row]
|
||||
return (
|
||||
<Node
|
||||
key={node.path}
|
||||
node={node}
|
||||
style={style}
|
||||
depth={depths.get(node) || 0}
|
||||
focused={focusedNode?.path === node.path}
|
||||
loading={loading.has(node.path)}
|
||||
expanded={expandedNodes.has(node.path)}
|
||||
onClick={handleNodeClick}
|
||||
onFocus={handleNodeFocus}
|
||||
renderLabelText={renderLabelText}
|
||||
renderActions={renderActions}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<PortalContext.Provider value={portalName}>
|
||||
<div style={containerStyle}>
|
||||
{visibleRows
|
||||
.map(({ row, style }) => ({
|
||||
node: nodes[row],
|
||||
style,
|
||||
}))
|
||||
.map(({ node, style }) => (
|
||||
<Node
|
||||
key={node.path}
|
||||
node={node}
|
||||
style={style}
|
||||
depth={depths.get(node) || 0}
|
||||
focused={focusedNode?.path === node.path}
|
||||
loading={loading.has(node.path)}
|
||||
expanded={expandedNodes.has(node.path)}
|
||||
onClick={handleNodeClick}
|
||||
onFocus={handleNodeFocus}
|
||||
renderLabelText={renderLabelText}
|
||||
renderActions={renderActions}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PortalContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { IconProps } from '@primer/octicons-react'
|
||||
import { Box, merge, SxProp, useTheme } from '@primer/react'
|
||||
import { getBaseStyles, getSizeStyles, getVariantStyles } from '@primer/react/lib/Button/styles'
|
||||
import { getBaseStyles, getSizeStyles, getVariantStyles } from '@primer/react/lib-esm/Button/styles'
|
||||
import {
|
||||
IconButtonProps as PrimerIconButtonProps,
|
||||
StyledButton,
|
||||
} from '@primer/react/lib/Button/types'
|
||||
import React from 'react'
|
||||
} from '@primer/react/lib-esm/Button/types'
|
||||
import React, { forwardRef } from 'react'
|
||||
import { is } from 'utils/is'
|
||||
|
||||
export type IconButtonProps = PrimerIconButtonProps & {
|
||||
|
|
@ -13,10 +13,13 @@ export type IconButtonProps = PrimerIconButtonProps & {
|
|||
iconColor?: string
|
||||
}
|
||||
|
||||
// Modified version of @primer/react/lib/Button/Button.tsx
|
||||
// Modified version of @primer/react/lib-esm/Button/Button.tsx
|
||||
// Added better support of colors & size
|
||||
|
||||
export function IconButton(props: IconButtonProps) {
|
||||
export const IconButton = forwardRef(function IconButton(
|
||||
props: IconButtonProps,
|
||||
ref: React.ForwardedRef<HTMLButtonElement>,
|
||||
) {
|
||||
const {
|
||||
variant = 'default',
|
||||
size = 'medium',
|
||||
|
|
@ -40,10 +43,10 @@ export function IconButton(props: IconButtonProps) {
|
|||
].filter(is.not.undefined),
|
||||
)
|
||||
return (
|
||||
<StyledButton sx={sxStyles} {...rest}>
|
||||
<StyledButton sx={sxStyles} ref={ref} {...rest}>
|
||||
<Box as="span" sx={{ display: 'inline-block' }}>
|
||||
<Icon size={iconSize} />
|
||||
</Box>
|
||||
</StyledButton>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import React from 'react'
|
||||
import React, { ForwardedRef, forwardRef } from 'react'
|
||||
import { IconButton, IconButtonProps } from './IconButton'
|
||||
|
||||
export function RoundIconButton(props: IconButtonProps) {
|
||||
export const RoundIconButton = forwardRef(function RoundIconButton(
|
||||
props: IconButtonProps,
|
||||
ref: ForwardedRef<HTMLButtonElement>,
|
||||
) {
|
||||
return (
|
||||
<IconButton
|
||||
ref={ref}
|
||||
variant="invisible"
|
||||
title={props['aria-label']}
|
||||
{...props}
|
||||
|
|
@ -13,4 +17,4 @@ export function RoundIconButton(props: IconButtonProps) {
|
|||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Box, Button, FormControl, TextInput } from '@primer/react'
|
||||
import * as React from 'react'
|
||||
import { useUpdateEffect } from 'react-use'
|
||||
import { cancelEvent } from 'utils/DOMHelper'
|
||||
import { friendlyFormatShortcut, noop } from 'utils/general'
|
||||
import { useStateIO } from 'utils/hooks/useStateIO'
|
||||
import * as keyHelper from 'utils/keyHelper'
|
||||
|
|
@ -39,8 +40,7 @@ export function KeyboardShortcutSetting({ label, value, onChange }: Props) {
|
|||
$shortcut.onChange(undefined)
|
||||
return
|
||||
default:
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
cancelEvent(e)
|
||||
}
|
||||
$shortcut.onChange(keyHelper.parseEvent(e))
|
||||
}}
|
||||
|
|
|
|||
5
src/containers/PortalContext.tsx
Normal file
5
src/containers/PortalContext.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export type PortalContextShape = string | null
|
||||
|
||||
export const PortalContext = React.createContext<PortalContextShape>(null)
|
||||
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
|
|
@ -19,6 +19,8 @@ type TreeNode = {
|
|||
path: string
|
||||
type: 'tree' | 'blob' | 'commit'
|
||||
url?: string
|
||||
permalink?: string
|
||||
rawLink?: string
|
||||
sha?: string
|
||||
accessDenied?: boolean
|
||||
comments?: {
|
||||
|
|
|
|||
|
|
@ -92,3 +92,19 @@ export function getCurrentPath(branchName = '') {
|
|||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function getItemUrl(
|
||||
userName: string,
|
||||
repoName: string,
|
||||
branchName: string,
|
||||
type = 'blob',
|
||||
path = '',
|
||||
) {
|
||||
// Modern browsers have great support for handling unsafe URL,
|
||||
// It may be possible to sanitize path with
|
||||
// `path => path.includes('#') ? path.replace(/#/g, '%23') : '...'
|
||||
return `${window.location.origin}/${userName}/${repoName}/${type}/${branchName}/${path
|
||||
.split('/')
|
||||
.map(encodeURIComponent)
|
||||
.join('/')}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,13 +55,24 @@ export async function getPullRequestTreeData(
|
|||
url.pathname = `/${userName}/${repoName}/pull/${pullId}/files`
|
||||
const commentsMap = getCommentsMap(commentData)
|
||||
const nodes: TreeNode[] = treeData.map(
|
||||
({ filename, sha, additions, deletions, changes, status }) => {
|
||||
({
|
||||
filename,
|
||||
sha,
|
||||
additions,
|
||||
deletions,
|
||||
changes,
|
||||
status,
|
||||
raw_url: rawLink,
|
||||
blob_url: permalink,
|
||||
}) => {
|
||||
url.hash = map.get(filename) || ''
|
||||
return {
|
||||
path: filename || '',
|
||||
type: 'blob',
|
||||
name: filename?.split('/').pop() || '',
|
||||
url: `${url}`,
|
||||
permalink,
|
||||
rawLink,
|
||||
sha,
|
||||
comments: commentsMap.get(filename),
|
||||
diff: {
|
||||
|
|
|
|||
|
|
@ -66,22 +66,6 @@ export function processTree(tree: TreeNode[]): TreeNode {
|
|||
return root
|
||||
}
|
||||
|
||||
function getUrlForRedirect(
|
||||
userName: string,
|
||||
repoName: string,
|
||||
branchName: string,
|
||||
type = 'blob',
|
||||
path = '',
|
||||
) {
|
||||
// Modern browsers have great support for handling unsafe URL,
|
||||
// It may be possible to sanitize path with
|
||||
// `path => path.includes('#') ? path.replace(/#/g, '%23') : '...'
|
||||
return `${window.location.origin}/${userName}/${repoName}/${type}/${branchName}/${path
|
||||
.split('/')
|
||||
.map(encodeURIComponent)
|
||||
.join('/')}`
|
||||
}
|
||||
|
||||
export function isEnterprise() {
|
||||
return (
|
||||
(window.location.host !== 'github.com' &&
|
||||
|
|
@ -212,19 +196,19 @@ export const GitHub: Platform = {
|
|||
useGitHubCodeFold(codeFolding)
|
||||
useEnterpriseStatBarStyleFix()
|
||||
},
|
||||
delegateFastRedirectAnchorProps: options => {
|
||||
if (configRef.pjaxMode === 'native' && (!options?.node || options.node.type === 'blob')) {
|
||||
const pjaxContainerSelector = 'main'
|
||||
const turboContainerId = 'repo-content-turbo-frame'
|
||||
delegateFastRedirectAnchorProps() {
|
||||
if (configRef.pjaxMode !== 'native') return
|
||||
|
||||
return {
|
||||
'data-pjax': pjaxContainerSelector,
|
||||
'data-turbo-frame':
|
||||
URLHelper.isInPullPage() || URLHelper.isInCommitPage() ? undefined : turboContainerId,
|
||||
onClick() {
|
||||
/* Overwriting default onClick */
|
||||
},
|
||||
}
|
||||
const pjaxContainerSelector = 'main'
|
||||
const turboContainerId = 'repo-content-turbo-frame'
|
||||
|
||||
return {
|
||||
'data-pjax': pjaxContainerSelector,
|
||||
'data-turbo-frame':
|
||||
URLHelper.isInPullPage() || URLHelper.isInCommitPage() ? undefined : turboContainerId,
|
||||
onClick() {
|
||||
/* Overwriting default onClick */
|
||||
},
|
||||
}
|
||||
},
|
||||
loadWithFastRedirect: (url, element) => {
|
||||
|
|
@ -274,7 +258,12 @@ async function getRepositoryTreeData(
|
|||
name: item.path?.split('/').pop() || '',
|
||||
url:
|
||||
item.url && item.type && item.path
|
||||
? getUrlForRedirect(userName, repoName, branchName, item.type, item.path)
|
||||
? URLHelper.getItemUrl(userName, repoName, branchName, item.type, item.path)
|
||||
: undefined,
|
||||
permalink: URLHelper.getItemUrl(userName, repoName, treeData.sha, item.type, item.path),
|
||||
rawLink:
|
||||
item.url && item.type === 'blob' && item.path
|
||||
? URLHelper.getItemUrl(userName, repoName, branchName, 'raw', item.path)
|
||||
: undefined,
|
||||
contents: item.type === 'tree' ? [] : undefined,
|
||||
sha: item.sha,
|
||||
|
|
|
|||
|
|
@ -735,6 +735,7 @@ html[data-with-gitako-spacing='true'] {
|
|||
}
|
||||
}
|
||||
|
||||
.context-menu,
|
||||
.go-to-button,
|
||||
.find-in-folder-button {
|
||||
@include icon-button();
|
||||
|
|
@ -749,6 +750,7 @@ html[data-with-gitako-spacing='true'] {
|
|||
}
|
||||
|
||||
&.compact {
|
||||
.context-menu,
|
||||
.go-to-button,
|
||||
.find-in-folder-button {
|
||||
border-radius: 4px;
|
||||
|
|
@ -758,6 +760,7 @@ html[data-with-gitako-spacing='true'] {
|
|||
}
|
||||
|
||||
&:not(:hover) {
|
||||
.context-menu:not(.active),
|
||||
.go-to-button,
|
||||
.find-in-folder-button {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -157,7 +157,14 @@ export function parseIntFromElement(e: HTMLElement): number {
|
|||
return parseInt((e.innerText || '').replace(/[^0-9]/g, ''))
|
||||
}
|
||||
|
||||
export function cancelEvent(e: KeyboardEvent): void {
|
||||
export function cancelEvent(e: Event | React.BaseSyntheticEvent): void {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
export function onEnterKeyDown<E extends HTMLElement>(
|
||||
e: React.KeyboardEvent<E>,
|
||||
callback: (e: React.KeyboardEvent<E>) => void,
|
||||
) {
|
||||
if (e.key === 'Enter') callback(e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export function hasUpperCase(input: string) {
|
|||
}
|
||||
|
||||
export async function renderReact(element: ReactElement) {
|
||||
return new Promise<Node>(resolve => {
|
||||
return new Promise<ChildNode>(resolve => {
|
||||
const mount = document.createElement('div')
|
||||
ReactDOM.render(element, mount, () => {
|
||||
resolve(mount.childNodes[0])
|
||||
|
|
|
|||
Loading…
Reference in a new issue