mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
Merge branch 'feature/primer' into develop
This commit is contained in:
commit
0fbb9bd217
25 changed files with 1483 additions and 484 deletions
3
.babelrc
3
.babelrc
|
|
@ -3,8 +3,9 @@
|
|||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"chrome": "67"
|
||||
"esmodules": true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -11,14 +11,18 @@
|
|||
"start": "VERSION=dev-v$(node scripts/get-version.js) webpack --watch",
|
||||
"debug-firefox": "web-ext run -s dist",
|
||||
"analyse-bundle": "ANALYSE= NODE_ENV=production webpack",
|
||||
"postinstall": "rm -rf node_modules/@types/react-native",
|
||||
"build": "VERSION=v$(node scripts/get-version.js) NODE_ENV=production webpack",
|
||||
"roll": "make release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@primer/components": "^16.0.0",
|
||||
"@primer/css": "^14.2.0",
|
||||
"@primer/octicons": "^9.2.0",
|
||||
"@primer/octicons-react": "^9.2.0",
|
||||
"@sentry/browser": "^5.12.1",
|
||||
"@types/firefox-webext-browser": "^70.0.1",
|
||||
"@types/history": "^4.7.5",
|
||||
"@types/ini": "^1.3.30",
|
||||
"@types/js-base64": "^2.3.1",
|
||||
"@types/node": "^11.10.4",
|
||||
|
|
@ -26,13 +30,15 @@
|
|||
"@types/react": "^16.8.24",
|
||||
"@types/react-dom": "^16.8.5",
|
||||
"@types/react-window": "^1.8.1",
|
||||
"@types/styled-components": "^5.0.1",
|
||||
"ini": "^1.3.5",
|
||||
"js-base64": "^2.5.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-use": "^13.8.0",
|
||||
"react-window": "^1.8.5",
|
||||
"styled-components": "^5.0.1",
|
||||
"webextension-polyfill": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -53,10 +59,10 @@
|
|||
"fork-ts-checker-webpack-plugin": "^0.5.2",
|
||||
"fsevents": "^1.2.4",
|
||||
"json-loader": "^0.5.7",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.0.5",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
"sass": "^1.26.2",
|
||||
"sass-loader": "^8.0.2",
|
||||
"typescript": "^3.7.2",
|
||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||
"url-loader": "^1.1.2",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Text } from '@primer/components'
|
||||
import { LoadingIndicator } from 'components/LoadingIndicator'
|
||||
import { Node } from 'components/Node'
|
||||
import { SearchBar } from 'components/SearchBar'
|
||||
|
|
@ -6,11 +7,10 @@ import { connect } from 'driver/connect'
|
|||
import { FileExplorerCore } from 'driver/core'
|
||||
import { ConnectorState, Props } from 'driver/core/FileExplorer'
|
||||
import * as React from 'react'
|
||||
import useEvent from 'react-use/esm/useEvent'
|
||||
import { useEvent, usePrevious } from 'react-use'
|
||||
import { FixedSizeList as List, ListChildComponentProps, ListProps } from 'react-window'
|
||||
import { cx } from 'utils/cx'
|
||||
import { useOnLocationChange } from 'utils/hooks/useOnLocationChange'
|
||||
import { usePrevious } from 'utils/hooks/usePrevious'
|
||||
import { getCurrentPath } from 'utils/URLHelper'
|
||||
import { TreeNode, VisibleNodes } from 'utils/VisibleNodesGenerator'
|
||||
import { Icon } from './Icon'
|
||||
|
|
@ -69,7 +69,11 @@ const RawFileExplorer: React.FC<Props & ConnectorState> = function RawFileExplor
|
|||
({ nodes, focusedNode }: VisibleNodes) => {
|
||||
const inSearch = searchKey !== ''
|
||||
if (inSearch && nodes.length === 0) {
|
||||
return <label className={'no-results'}>No results found.</label>
|
||||
return (
|
||||
<Text marginTop={6} textAlign="center" color="text.gray">
|
||||
No results found.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<SizeObserver className={'files'}>
|
||||
|
|
@ -210,7 +214,7 @@ function ListView({
|
|||
}}
|
||||
itemData={{ nodes }}
|
||||
itemCount={nodes.length}
|
||||
itemSize={35}
|
||||
itemSize={36}
|
||||
height={height}
|
||||
width={width}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Breadcrumb } from '@primer/components'
|
||||
import * as React from 'react'
|
||||
import { MetaData } from 'utils/GitHubHelper'
|
||||
|
||||
|
|
@ -10,15 +11,13 @@ export function MetaBar({ metaData }: Props) {
|
|||
const repoUrl = metaData?.api?.html_url
|
||||
return (
|
||||
<div className={'meta-bar'}>
|
||||
<a className={'username'} href={userUrl}>
|
||||
{metaData.userName}
|
||||
</a>
|
||||
/
|
||||
<a className={'repo-name'} href={repoUrl}>
|
||||
{metaData.repoName}
|
||||
</a>
|
||||
/
|
||||
{metaData.branchName}
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href={userUrl}>{metaData.userName}</Breadcrumb.Item>
|
||||
<Breadcrumb.Item className={'repo-name'} href={repoUrl}>
|
||||
{metaData.repoName}
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item selected>{metaData.branchName}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Text } from '@primer/components'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { cx } from 'utils/cx'
|
||||
|
|
@ -45,24 +46,19 @@ export function Node({ node, depth, expanded, focused, renderActions, style, onC
|
|||
|
||||
const { name, path } = node
|
||||
return (
|
||||
<div
|
||||
className={cx(`node-item-row`, { focused, disabled: node.accessDenied })}
|
||||
style={style}
|
||||
<a
|
||||
href={node.url}
|
||||
onClick={onClickNode}
|
||||
className={cx(`node-item`, { focused, disabled: node.accessDenied, expanded })}
|
||||
style={{ ...style, paddingLeft: `${10 + 20 * depth}px` }}
|
||||
title={path}
|
||||
>
|
||||
<a href={node.url} onClick={onClickNode}>
|
||||
<div
|
||||
className={cx('node-item', { expanded })}
|
||||
style={{ paddingLeft: `${10 + 20 * depth}px` }}
|
||||
>
|
||||
<div className={'node-item-label'}>
|
||||
<NodeItemIcon node={node} open={expanded} />
|
||||
<span className={'node-item-name'}>{name}</span>
|
||||
</div>
|
||||
{renderActions && <div>{renderActions(node)}</div>}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className={'node-item-label'}>
|
||||
<NodeItemIcon node={node} open={expanded} />
|
||||
<Text fontSize={14}>{name}</Text>
|
||||
</div>
|
||||
{renderActions && <div>{renderActions(node)}</div>}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { HorizontalResizeHandler } from 'components/ResizeHandler'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { useWindowSize } from 'react-use'
|
||||
import { cx } from 'utils/cx'
|
||||
import { bodySpacingClassName } from 'utils/DOMHelper'
|
||||
import * as features from 'utils/features'
|
||||
import { useMediaStyleSheet } from 'utils/hooks/useMediaStyleSheet'
|
||||
import { useWindowSize } from 'utils/hooks/useWindowSize'
|
||||
|
||||
export type Size = number
|
||||
type Props = {
|
||||
|
|
@ -24,13 +24,11 @@ export function Resizable({ baseSize, className, children }: React.PropsWithChil
|
|||
setSize(baseSize)
|
||||
}, [baseSize])
|
||||
|
||||
useWindowSize(
|
||||
width => {
|
||||
if (size > width - MINIMAL_CONTENT_VIEWPORT_WIDTH)
|
||||
setSize(width - MINIMAL_CONTENT_VIEWPORT_WIDTH)
|
||||
},
|
||||
[size],
|
||||
)
|
||||
const { width } = useWindowSize()
|
||||
React.useEffect(() => {
|
||||
if (size > width - MINIMAL_CONTENT_VIEWPORT_WIDTH)
|
||||
setSize(width - MINIMAL_CONTENT_VIEWPORT_WIDTH)
|
||||
}, [width, size])
|
||||
|
||||
React.useEffect(() => {
|
||||
document.documentElement.style.setProperty('--gitako-width', size + 'px')
|
||||
|
|
@ -49,17 +47,13 @@ export function Resizable({ baseSize, className, children }: React.PropsWithChil
|
|||
size,
|
||||
)
|
||||
|
||||
const onResize = React.useCallback((size: number) => {
|
||||
if (size < window.innerWidth - MINIMAL_CONTENT_VIEWPORT_WIDTH) setSize(size)
|
||||
}, [])
|
||||
return (
|
||||
<div className={cx('gitako-position-wrapper', className)}>
|
||||
<div className={'gitako-position-content'}>{children}</div>
|
||||
{features.resize && (
|
||||
<HorizontalResizeHandler
|
||||
onResize={size => {
|
||||
if (size < window.innerWidth - MINIMAL_CONTENT_VIEWPORT_WIDTH) setSize(size)
|
||||
}}
|
||||
size={size}
|
||||
/>
|
||||
)}
|
||||
{features.resize && <HorizontalResizeHandler onResize={onResize} size={size} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { TextInput } from '@primer/components'
|
||||
import { Search } from '@primer/octicons-react'
|
||||
import * as React from 'react'
|
||||
import { cx } from 'utils/cx'
|
||||
|
||||
|
|
@ -10,10 +12,12 @@ type Props = {
|
|||
export function SearchBar({ onSearch, onFocus, searchKey }: Props) {
|
||||
return (
|
||||
<div className={'search-input-wrapper'}>
|
||||
<input
|
||||
<TextInput
|
||||
backgroundColor="white"
|
||||
icon={Search as any}
|
||||
onFocus={onFocus}
|
||||
tabIndex={0}
|
||||
className={cx('form-control', 'search-input', {
|
||||
className={cx('search-input', {
|
||||
error: !isValidRegexpSource(searchKey),
|
||||
})}
|
||||
aria-label="search files"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Link } from '@primer/components'
|
||||
import { Icon } from 'components/Icon'
|
||||
import { VERSION } from 'env'
|
||||
import * as React from 'react'
|
||||
|
|
@ -5,6 +6,7 @@ import { Config } from 'utils/configHelper'
|
|||
import { useStates } from 'utils/hooks/useStates'
|
||||
import { AccessTokenSettings } from './settings/AccessTokenSettings'
|
||||
import { FileTreeSettings } from './settings/FileTreeSettings'
|
||||
import { SettingsSection } from './settings/SettingsSection'
|
||||
import { SidebarSettings } from './settings/SidebarSettings'
|
||||
import { SimpleToggleField } from './SimpleToggleField'
|
||||
|
||||
|
|
@ -52,14 +54,13 @@ function SettingsBarContent() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<h3 className={'gitako-settings-bar-title'}>Settings</h3>
|
||||
<h2 className={'gitako-settings-bar-title'}>Settings</h2>
|
||||
<div className={'gitako-settings-bar-content'}>
|
||||
<div className={'shadow-shelter'} />
|
||||
<AccessTokenSettings />
|
||||
<SidebarSettings />
|
||||
<FileTreeSettings />
|
||||
<div className={'gitako-settings-bar-content-section others'}>
|
||||
<h4>More</h4>
|
||||
<SettingsSection title={'More'}>
|
||||
{moreFields.map(field => (
|
||||
<React.Fragment key={field.key}>
|
||||
<SimpleToggleField field={field} />
|
||||
|
|
@ -67,13 +68,12 @@ function SettingsBarContent() {
|
|||
))}
|
||||
|
||||
{reloadHint && <div className={'hint'}>{reloadHint}</div>}
|
||||
</div>
|
||||
<div className={'gitako-settings-bar-content-section issue'}>
|
||||
<h4>Contact</h4>
|
||||
</SettingsSection>
|
||||
<SettingsSection title={'Contact'}>
|
||||
<a href="https://github.com/EnixCoda/Gitako/issues" target="_blank">
|
||||
Report bug / Request feature.
|
||||
</a>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
@ -85,14 +85,15 @@ export function SettingsBar(props: Props) {
|
|||
<div className={'gitako-settings-bar'}>
|
||||
{activated && <SettingsBarContent />}
|
||||
<div className={'header-row'}>
|
||||
<a
|
||||
className={'version'}
|
||||
<Link
|
||||
color="gray.4"
|
||||
fontSize={14}
|
||||
href={wikiLinks.changeLog}
|
||||
target={'_blank'}
|
||||
title={'Check out new features!'}
|
||||
>
|
||||
{VERSION}
|
||||
</a>
|
||||
</Link>
|
||||
{activated ? (
|
||||
<Icon
|
||||
type={'chevron-down'}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,10 @@ import { SideBarCore } from 'driver/core'
|
|||
import { ConnectorState, Props } from 'driver/core/SideBar'
|
||||
import { oauth } from 'env'
|
||||
import * as React from 'react'
|
||||
import useEvent from 'react-use/esm/useEvent'
|
||||
import { useEvent, useUpdateEffect } from 'react-use'
|
||||
import { cx } from 'utils/cx'
|
||||
import * as DOMHelper from 'utils/DOMHelper'
|
||||
import { JSONRequest, parseURLSearch } from 'utils/general'
|
||||
import { useDidUpdate } from 'utils/hooks/useDidUpdate'
|
||||
import * as keyHelper from 'utils/keyHelper'
|
||||
import * as URLHelper from 'utils/URLHelper'
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ const RawGitako: React.FC<Props & ConnectorState> = function RawGitako(props) {
|
|||
useEvent('pjax:complete', attachCopySnippetButton, window)
|
||||
|
||||
// init again when setting new accessToken
|
||||
useDidUpdate(() => {
|
||||
useUpdateEffect(() => {
|
||||
props.init()
|
||||
}, [accessToken || '']) // fallback for preventing duplicated requests
|
||||
|
||||
|
|
@ -162,11 +161,11 @@ function renderAccessDeniedError(hasToken: boolean) {
|
|||
) : (
|
||||
<p>
|
||||
Gitako needs access token to read this project due to{' '}
|
||||
<a target="_blank" href="https://developer.github.com/v3/#rate-limiting">
|
||||
<a href="https://developer.github.com/v3/#rate-limiting" target="_blank">
|
||||
GitHub rate limiting
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a target="_blank" href="https://developer.github.com/v3/#authentication">
|
||||
<a href="https://developer.github.com/v3/#authentication" target="_blank">
|
||||
auth needs
|
||||
</a>
|
||||
. Please setup access token in the settings panel below.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { Field } from './settings/Field'
|
||||
import { SimpleField } from './SettingsBar'
|
||||
|
||||
type Props = {
|
||||
|
|
@ -12,9 +13,24 @@ export function SimpleToggleField({ field, onChange }: Props) {
|
|||
const configContext = useConfigs()
|
||||
const value = configContext.val[field.key]
|
||||
return (
|
||||
<div className={'form-checkbox'}>
|
||||
<Field
|
||||
id={field.key}
|
||||
title={
|
||||
<>
|
||||
{field.label}{' '}
|
||||
{field.wikiLink ? (
|
||||
<a href={field.wikiLink} target={'_blank'}>
|
||||
(?)
|
||||
</a>
|
||||
) : (
|
||||
field.description && <p className={'note'}>{field.description}</p>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
className={'field-checkbox'}
|
||||
checkbox
|
||||
>
|
||||
<input
|
||||
className={'form-control'}
|
||||
id={field.key}
|
||||
name={field.key}
|
||||
type={'checkbox'}
|
||||
|
|
@ -25,16 +41,6 @@ export function SimpleToggleField({ field, onChange }: Props) {
|
|||
}}
|
||||
checked={overwrite ? overwrite.value(value) : Boolean(value)}
|
||||
/>
|
||||
<label htmlFor={field.key}>
|
||||
{field.label}{' '}
|
||||
{field.wikiLink ? (
|
||||
<a href={field.wikiLink} target={'_blank'}>
|
||||
(?)
|
||||
</a>
|
||||
) : (
|
||||
field.description && <p className={'note'}>{field.description}</p>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export function SizeObserver({ type = 'div', children, ...rest }: Props) {
|
|||
)
|
||||
}, [])
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if (features.resize) {
|
||||
const observer = new window.ResizeObserver(entries => {
|
||||
const entry = entries[0]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { Button, TextInput } from '@primer/components'
|
||||
import { wikiLinks } from 'components/SettingsBar'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { oauth } from 'env'
|
||||
import * as React from 'react'
|
||||
import { useStates } from 'utils/hooks/useStates'
|
||||
import { SettingsSection } from './SettingsSection'
|
||||
|
||||
const ACCESS_TOKEN_REGEXP = /^[0-9a-f]{40}$/
|
||||
|
||||
|
|
@ -13,6 +15,7 @@ export function AccessTokenSettings(props: React.PropsWithChildren<Props>) {
|
|||
const hasAccessToken = Boolean(configContext.val.access_token)
|
||||
const useAccessToken = useStates('')
|
||||
const useAccessTokenHint = useStates<React.ReactNode>('')
|
||||
const focusInput = useStates(false)
|
||||
|
||||
const { val: accessTokenHint } = useAccessTokenHint
|
||||
const { val: accessToken } = useAccessToken
|
||||
|
|
@ -26,7 +29,7 @@ export function AccessTokenSettings(props: React.PropsWithChildren<Props>) {
|
|||
({ currentTarget: { value } }: React.FormEvent<HTMLInputElement>) => {
|
||||
useAccessToken.set(value)
|
||||
useAccessTokenHint.set(
|
||||
ACCESS_TOKEN_REGEXP.test(value) ? '' : 'This token is in unknown format.',
|
||||
ACCESS_TOKEN_REGEXP.test(value) ? '' : 'Gitako does not recognize the token.',
|
||||
)
|
||||
},
|
||||
[],
|
||||
|
|
@ -57,47 +60,51 @@ export function AccessTokenSettings(props: React.PropsWithChildren<Props>) {
|
|||
)
|
||||
|
||||
return (
|
||||
<div className={'gitako-settings-bar-content-section access-token'}>
|
||||
<h4>
|
||||
Access Token{' '}
|
||||
<a href={wikiLinks.createAccessToken} target="_blank">
|
||||
(?)
|
||||
</a>
|
||||
</h4>
|
||||
{!hasAccessToken && (
|
||||
<a
|
||||
className={'link-button'}
|
||||
onClick={() => {
|
||||
// use js here to make sure redirect_uri is latest url
|
||||
const url = `https://github.com/login/oauth/authorize?client_id=${
|
||||
oauth.clientId
|
||||
}&scope=repo&redirect_uri=${encodeURIComponent(window.location.href)}`
|
||||
window.location.href = url
|
||||
}}
|
||||
>
|
||||
Create with OAuth (recommended)
|
||||
</a>
|
||||
<SettingsSection
|
||||
title={
|
||||
<>
|
||||
Access Token{' '}
|
||||
<a href={wikiLinks.createAccessToken} target="_blank">
|
||||
(?)
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{hasAccessToken ? (
|
||||
<Button onClick={() => configContext.set({ access_token: '' })}>Clear</Button>
|
||||
) : (
|
||||
<div>
|
||||
<a
|
||||
className={'link-button'}
|
||||
onClick={() => {
|
||||
// use js here to make sure redirect_uri is latest url
|
||||
const url = `https://github.com/login/oauth/authorize?client_id=${
|
||||
oauth.clientId
|
||||
}&scope=repo&redirect_uri=${encodeURIComponent(window.location.href)}`
|
||||
window.location.href = url
|
||||
}}
|
||||
>
|
||||
Create with OAuth (recommended)
|
||||
</a>
|
||||
<div className={'access-token-input-control'}>
|
||||
<TextInput
|
||||
backgroundColor="#fff"
|
||||
marginRight={1}
|
||||
className={'access-token-input'}
|
||||
value={accessToken}
|
||||
placeholder="Or input here manually"
|
||||
onFocus={() => focusInput.set(true)}
|
||||
onBlur={() => focusInput.set(false)}
|
||||
onChange={onInputAccessToken}
|
||||
onKeyPress={onPressAccessToken}
|
||||
/>
|
||||
<Button onClick={() => saveToken()} disabled={!accessToken}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={'access-token-input-control'}>
|
||||
<input
|
||||
className={'access-token-input form-control'}
|
||||
disabled={hasAccessToken}
|
||||
placeholder={hasAccessToken ? 'Your token is saved' : 'Or input here manually'}
|
||||
value={accessToken}
|
||||
onChange={onInputAccessToken}
|
||||
onKeyPress={onPressAccessToken}
|
||||
/>
|
||||
{hasAccessToken && !accessToken ? (
|
||||
<button className={'btn'} onClick={() => configContext.set({ access_token: '' })}>
|
||||
Clear
|
||||
</button>
|
||||
) : (
|
||||
<button className={'btn'} onClick={() => saveToken()} disabled={!accessToken}>
|
||||
Save
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{accessTokenHint && <span className={'hint'}>{accessTokenHint}</span>}
|
||||
</div>
|
||||
{accessTokenHint && !focusInput.val && <span className={'hint'}>{accessTokenHint}</span>}
|
||||
</SettingsSection>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
33
src/components/settings/Field.tsx
Normal file
33
src/components/settings/Field.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import * as React from 'react'
|
||||
import { cx } from 'utils/cx'
|
||||
|
||||
type Props = {
|
||||
id?: string
|
||||
className?: string
|
||||
checkbox?: boolean
|
||||
title: React.ReactNode
|
||||
}
|
||||
|
||||
export function Field({
|
||||
className,
|
||||
title,
|
||||
id,
|
||||
checkbox,
|
||||
children,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div className={cx('field', className)}>
|
||||
{checkbox ? (
|
||||
<>
|
||||
{children}
|
||||
<label htmlFor={id}>{title}</label>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<label htmlFor={id}>{title}</label>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ import { SimpleToggleField } from 'components/SimpleToggleField'
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { Config } from 'utils/configHelper'
|
||||
import { Field } from './Field'
|
||||
import { SettingsSection } from './SettingsSection'
|
||||
|
||||
const options: {
|
||||
key: Config['icons']
|
||||
|
|
@ -31,12 +33,8 @@ type Props = {}
|
|||
export function FileTreeSettings(props: React.PropsWithChildren<Props>) {
|
||||
const configContext = useConfigs()
|
||||
return (
|
||||
<div className={'gitako-settings-bar-content-section'}>
|
||||
<h4>File Tree</h4>
|
||||
<label className="form-label" htmlFor="file-tree-icons">
|
||||
Icons
|
||||
</label>
|
||||
<div>
|
||||
<SettingsSection title={'File Tree'}>
|
||||
<Field title="Icons" id="file-tree-icons">
|
||||
<select
|
||||
id="file-tree-icons"
|
||||
onChange={e => {
|
||||
|
|
@ -44,8 +42,6 @@ export function FileTreeSettings(props: React.PropsWithChildren<Props>) {
|
|||
icons: e.target.value as Config['icons'],
|
||||
})
|
||||
}}
|
||||
className={'file-tree-input form-control'}
|
||||
placeholder={'focus here and press the shortcut keys'}
|
||||
value={configContext.val.icons}
|
||||
>
|
||||
{options.map(option => (
|
||||
|
|
@ -54,7 +50,7 @@ export function FileTreeSettings(props: React.PropsWithChildren<Props>) {
|
|||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</Field>
|
||||
<SimpleToggleField
|
||||
field={{
|
||||
key: 'compressSingletonFolder',
|
||||
|
|
@ -62,6 +58,6 @@ export function FileTreeSettings(props: React.PropsWithChildren<Props>) {
|
|||
wikiLink: wikiLinks.compressSingletonFolder,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
13
src/components/settings/SettingsSection.tsx
Normal file
13
src/components/settings/SettingsSection.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import * as React from 'react'
|
||||
type Props = {
|
||||
title?: React.ReactNode
|
||||
}
|
||||
|
||||
export function SettingsSection({ title, children }: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div className={'settings-section'}>
|
||||
{title && <h3>{title}</h3>}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
import { Button, TextInput } from '@primer/components'
|
||||
import { SimpleToggleField } from 'components/SimpleToggleField'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { friendlyFormatShortcut } from 'utils/general'
|
||||
import { useStates } from 'utils/hooks/useStates'
|
||||
import * as keyHelper from 'utils/keyHelper'
|
||||
import { Field } from './Field'
|
||||
import { SettingsSection } from './SettingsSection'
|
||||
|
||||
type Props = {}
|
||||
|
||||
|
|
@ -11,44 +14,56 @@ export function SidebarSettings(props: React.PropsWithChildren<Props>) {
|
|||
const configContext = useConfigs()
|
||||
const useToggleShowSideBarShortcut = useStates(configContext.val.shortcut)
|
||||
const { val: toggleShowSideBarShortcut } = useToggleShowSideBarShortcut
|
||||
const focused = useStates(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
useToggleShowSideBarShortcut.set(configContext.val.shortcut)
|
||||
}, [configContext.val.shortcut])
|
||||
|
||||
return (
|
||||
<div className={'gitako-settings-bar-content-section toggle-shortcut'}>
|
||||
<h4>Toggle Sidebar</h4>
|
||||
<label className="form-label" htmlFor="toggle-sidebar-shortcut">
|
||||
Keyboard Shortcut
|
||||
</label>
|
||||
<div className={'toggle-shortcut-input-control'}>
|
||||
<input
|
||||
id="toggle-sidebar-shortcut"
|
||||
className={'toggle-shortcut-input form-control'}
|
||||
placeholder={'focus here and press the shortcut keys'}
|
||||
value={friendlyFormatShortcut(toggleShowSideBarShortcut)}
|
||||
onKeyDown={React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
// Clear shortcut with backspace
|
||||
const shortcut = e.key === 'Backspace' ? '' : keyHelper.parseEvent(e)
|
||||
useToggleShowSideBarShortcut.set(shortcut)
|
||||
}, [])}
|
||||
readOnly
|
||||
/>
|
||||
<button
|
||||
className={'btn'}
|
||||
disabled={toggleShowSideBarShortcut === configContext.val.shortcut}
|
||||
onClick={() => {
|
||||
const { val: toggleShowSideBarShortcut } = useToggleShowSideBarShortcut
|
||||
if (typeof toggleShowSideBarShortcut !== 'string') return
|
||||
configContext.set({ shortcut: toggleShowSideBarShortcut })
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<SettingsSection title={'Toggle Sidebar'}>
|
||||
<Field id="toggle-sidebar-shortcut" title="Keyboard Shortcut">
|
||||
<div className={'toggle-shortcut-input-control'}>
|
||||
<TextInput
|
||||
id="toggle-sidebar-shortcut"
|
||||
backgroundColor="#fff"
|
||||
marginRight={1}
|
||||
className={'toggle-shortcut-input'}
|
||||
onFocus={() => focused.set(true)}
|
||||
onBlur={() => focused.set(false)}
|
||||
placeholder={focused.val ? 'Press key combination' : 'Click here to set'}
|
||||
value={friendlyFormatShortcut(toggleShowSideBarShortcut)}
|
||||
onKeyDown={React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
// Clear shortcut with backspace
|
||||
const shortcut = e.key === 'Backspace' ? '' : keyHelper.parseEvent(e)
|
||||
useToggleShowSideBarShortcut.set(shortcut)
|
||||
}, [])}
|
||||
readOnly
|
||||
/>
|
||||
{configContext.val.shortcut === toggleShowSideBarShortcut ? (
|
||||
<Button
|
||||
disabled={!configContext.val.shortcut}
|
||||
onClick={() => {
|
||||
configContext.set({ shortcut: '' })
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
const { val: toggleShowSideBarShortcut } = useToggleShowSideBarShortcut
|
||||
if (typeof toggleShowSideBarShortcut !== 'string') return
|
||||
configContext.set({ shortcut: toggleShowSideBarShortcut })
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
<SimpleToggleField
|
||||
field={{
|
||||
key: 'intelligentToggle',
|
||||
|
|
@ -59,6 +74,6 @@ export function SidebarSettings(props: React.PropsWithChildren<Props>) {
|
|||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,31 @@
|
|||
@import '~nprogress/nprogress.css';
|
||||
|
||||
@name: gitako;
|
||||
@import '~@primer/css/base/index.scss';
|
||||
|
||||
@min-screen-width: 1280px;
|
||||
@github-content-width: 1020px;
|
||||
@side-bar-base-width: (@min-screen-width - @github-content-width);
|
||||
@min-width-with-gitako: (@min-screen-width + @side-bar-base-width);
|
||||
@import '~@primer/css/support/variables/colors.scss';
|
||||
@import '~@primer/css/support/variables/color-system.scss';
|
||||
@import '~@primer/css/support/variables/typography.scss';
|
||||
@import '~@primer/css/support/variables/misc.scss';
|
||||
@import '~@primer/css/support/variables/layout.scss';
|
||||
|
||||
@github-header-z-index: 32;
|
||||
@github-pull-request-float-header-z-index: 110;
|
||||
@minimal-z-index: max(@github-header-z-index, @github-pull-request-float-header-z-index) + 1;
|
||||
$name: gitako;
|
||||
|
||||
$min-screen-width: 1280px;
|
||||
$github-content-width: 1020px;
|
||||
$side-bar-base-width: $min-screen-width - $github-content-width;
|
||||
$min-width-with-gitako: $min-screen-width + $side-bar-base-width;
|
||||
|
||||
$github-header-z-index: 32;
|
||||
$github-pull-request-float-header-z-index: 110;
|
||||
$minimal-z-index: max($github-header-z-index, $github-pull-request-float-header-z-index) + 1;
|
||||
|
||||
:root {
|
||||
--gitako-width: @side-bar-base-width;
|
||||
--gitako-width: #{$side-bar-base-width};
|
||||
}
|
||||
|
||||
.@{name}-ready {
|
||||
.#{$name}-ready {
|
||||
.js-header-wrapper {
|
||||
background: #24292e;
|
||||
background: $gray-dark;
|
||||
}
|
||||
.Header {
|
||||
max-width: 1012px;
|
||||
|
|
@ -25,9 +33,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (min-width: @github-content-width) {
|
||||
@media (min-width: $github-content-width) {
|
||||
body {
|
||||
min-width: @github-content-width;
|
||||
min-width: $github-content-width;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,20 +45,20 @@
|
|||
width: 0;
|
||||
height: 0;
|
||||
top: 8px;
|
||||
left: ~'calc(100% - 40px)';
|
||||
left: calc(100% - 40px);
|
||||
z-index: 1;
|
||||
|
||||
.clippy {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid $border-gray-dark;
|
||||
border-radius: 4px;
|
||||
background: #f9f9f9;
|
||||
background: $bg-gray-light;
|
||||
&:hover {
|
||||
background: #fff;
|
||||
background: $bg-white;
|
||||
}
|
||||
&:active {
|
||||
background: #f0f0f0;
|
||||
background: $bg-gray-light;
|
||||
}
|
||||
.icon {
|
||||
width: 100%;
|
||||
|
|
@ -74,7 +82,7 @@
|
|||
left: 0; /* reposition progress bar of GitHub */
|
||||
}
|
||||
|
||||
.hidden {
|
||||
%hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -82,12 +90,12 @@
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.@{name}-toggle-show-button-wrapper {
|
||||
@animation-duration: 0.5s;
|
||||
.#{$name}-toggle-show-button-wrapper {
|
||||
$animation-duration: 0.5s;
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
z-index: @minimal-z-index;
|
||||
z-index: $minimal-z-index;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
|
@ -96,53 +104,53 @@
|
|||
will-change: transform;
|
||||
border: 1px solid transparent;
|
||||
|
||||
.transform-expanded {
|
||||
@mixin transform-expanded() {
|
||||
transform: translate(calc(var(--gitako-width) - 30px));
|
||||
}
|
||||
.transform-collapsed {
|
||||
@mixin transform-collapsed() {
|
||||
transform: translate(5px);
|
||||
}
|
||||
|
||||
.transform-expanded;
|
||||
animation: toggle-show-button-wrapper-expand @animation-duration;
|
||||
@include transform-expanded();
|
||||
animation: toggle-show-button-wrapper-expand $animation-duration;
|
||||
|
||||
&.collapsed {
|
||||
border-color: #999999;
|
||||
border-color: $border-gray;
|
||||
border-radius: 3px;
|
||||
background: #f5f5f5;
|
||||
background: $bg-gray;
|
||||
|
||||
.transform-collapsed;
|
||||
animation: toggle-show-button-wrapper-collapse @animation-duration;
|
||||
@include transform-collapsed();
|
||||
animation: toggle-show-button-wrapper-collapse $animation-duration;
|
||||
|
||||
.action-icon {
|
||||
color: #0366d6d0;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toggle-show-button-wrapper-expand {
|
||||
from {
|
||||
.transform-collapsed;
|
||||
@include transform-collapsed();
|
||||
}
|
||||
to {
|
||||
.transform-expanded;
|
||||
@include transform-expanded();
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toggle-show-button-wrapper-collapse {
|
||||
from {
|
||||
.transform-expanded;
|
||||
@include transform-expanded();
|
||||
}
|
||||
to {
|
||||
.transform-collapsed;
|
||||
@include transform-collapsed();
|
||||
}
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
color: #666666;
|
||||
color: $gray;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
transition: all @animation-duration ease;
|
||||
transition: all $animation-duration ease;
|
||||
.octicon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -151,7 +159,7 @@
|
|||
|
||||
&.error {
|
||||
.action-icon {
|
||||
color: #d73a49;
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,36 +176,36 @@
|
|||
}
|
||||
}
|
||||
|
||||
.@{name}-side-bar {
|
||||
.@{name}-position-wrapper {
|
||||
.#{$name}-side-bar {
|
||||
.#{$name}-position-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
z-index: @minimal-z-index;
|
||||
z-index: $minimal-z-index;
|
||||
display: flex;
|
||||
|
||||
&.hidden {
|
||||
.hidden;
|
||||
@extend %hidden;
|
||||
}
|
||||
|
||||
.gitako-position-content {
|
||||
width: var(--gitako-width);
|
||||
}
|
||||
}
|
||||
.@{name}-resize-handler {
|
||||
.#{$name}-resize-handler {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: ew-resize;
|
||||
user-select: none;
|
||||
width: 0;
|
||||
background: #e1e4e8;
|
||||
border-left: 1px solid #e1e4e8;
|
||||
background: $bg-gray;
|
||||
border-right: 1px solid $border-gray;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:active {
|
||||
width: 16px;
|
||||
background: #fafbfc;
|
||||
border-right: 1px solid #e1e4e8;
|
||||
background: $bg-gray-light;
|
||||
}
|
||||
|
||||
.octicon {
|
||||
|
|
@ -207,27 +215,31 @@
|
|||
font-size: 0;
|
||||
}
|
||||
}
|
||||
.@{name}-side-bar-body {
|
||||
.#{$name}-side-bar-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fafbfc;
|
||||
border-right: 1px solid #e1e4e8;
|
||||
border-left: 1px solid #e1e4e8;
|
||||
background: $bg-gray-light;
|
||||
border-right: 1px solid $border-gray-light;
|
||||
border-left: 1px solid $border-gray;
|
||||
overflow: hidden;
|
||||
|
||||
&.hidden {
|
||||
.hidden;
|
||||
@extend %hidden;
|
||||
}
|
||||
|
||||
.octicon {
|
||||
transition: transform 0.3s ease;
|
||||
color: rgba(3, 47, 98, 0.55);
|
||||
color: rgba($blue-800, 0.55);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.octicon-color {
|
||||
color: rgba($blue-800, 0.55);
|
||||
}
|
||||
|
||||
.octicon-wrapper {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
|
|
@ -235,11 +247,7 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.octicon-color {
|
||||
color: rgba(3, 47, 98, 0.55);
|
||||
}
|
||||
|
||||
.@{name}-side-bar-content {
|
||||
.#{$name}-side-bar-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
|
@ -247,20 +255,11 @@
|
|||
min-height: 0; // make content shrinkable
|
||||
|
||||
.meta-bar {
|
||||
position: relative;
|
||||
position: relative; // prevent overlap by outline of other elements
|
||||
padding: 10px;
|
||||
padding-right: 30px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
color: #586069;
|
||||
background-color: #f1f8ff;
|
||||
border-bottom: 1px solid #c8e1ff;
|
||||
|
||||
a {
|
||||
// fix a weird bug:
|
||||
// when gitako failed loading repo, cursor hovering <a> in meta bar will be 'text'
|
||||
cursor: pointer;
|
||||
}
|
||||
padding-right: 30px; // space for toggle button
|
||||
background: $bg-blue-light;
|
||||
border-bottom: 1px solid $border-gray;
|
||||
|
||||
.repo-name {
|
||||
font-weight: bolder;
|
||||
|
|
@ -314,123 +313,111 @@
|
|||
|
||||
/* search input */
|
||||
.search-input-wrapper {
|
||||
input.form-control {
|
||||
.search-input {
|
||||
width: 100%;
|
||||
|
||||
&.error {
|
||||
border-color: #d73a49;
|
||||
border-color: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: 0px 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.files {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.node-item-row {
|
||||
background: #fff;
|
||||
.node-item {
|
||||
background: $bg-white;
|
||||
&:hover {
|
||||
background: #f6f8fa;
|
||||
background: $bg-gray-light;
|
||||
}
|
||||
&.focused {
|
||||
background: #f0f0f6;
|
||||
&.focused,
|
||||
&:active {
|
||||
background: $bg-gray;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.node-item {
|
||||
pointer-events: none;
|
||||
color: #999999;
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
color: $blue;
|
||||
line-height: 20px;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
border-top: 1px solid $border-gray-light;
|
||||
white-space: nowrap;
|
||||
|
||||
@mixin icon-size() {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.octicon-wrapper:first-child {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
&-type-icon {
|
||||
margin-right: 4px;
|
||||
@include icon-size();
|
||||
|
||||
& + .octicon-wrapper,
|
||||
& + .node-item-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.node-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
color: #0366d6;
|
||||
line-height: 20px;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
border-top: 1px solid #eaecef;
|
||||
transition: all 0.5s ease;
|
||||
&-icon {
|
||||
@include icon-size();
|
||||
object-fit: contain;
|
||||
vertical-align: text-bottom;
|
||||
box-sizing: content-box;
|
||||
|
||||
&.dim {
|
||||
filter: sepia(1) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
// folder icon rotate when expand
|
||||
&.expanded .octicon.ChevronRight {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.node-item-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.go-to-button {
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
|
||||
.icon-size() {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.octicon-wrapper:first-child {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
&-type-icon {
|
||||
margin-right: 4px;
|
||||
.icon-size();
|
||||
|
||||
& + .octicon-wrapper,
|
||||
& + .node-item-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
.icon-size();
|
||||
object-fit: contain;
|
||||
vertical-align: text-bottom;
|
||||
box-sizing: content-box;
|
||||
|
||||
&.dim {
|
||||
filter: sepia(1) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
// folder icon rotate when expand
|
||||
&.expanded .octicon.ChevronRight {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.node-item-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover .node-item-name {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
&:hover {
|
||||
.go-to-button {
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
&:hover {
|
||||
.go-to-button {
|
||||
display: inline-block;
|
||||
}
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{name}-settings-bar {
|
||||
.#{$name}-settings-bar {
|
||||
z-index: 2;
|
||||
background: #fafbfc;
|
||||
background: $bg-gray-light;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
&-title {
|
||||
border-top: 1px solid #eaecef;
|
||||
padding: 6px 10px;
|
||||
border-top: 1px solid $border-gray-light;
|
||||
padding: 10px;
|
||||
box-shadow: 0 1px 8px 0 rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.14),
|
||||
0 3px 3px -2px rgba(0, 0, 0, 0.12);
|
||||
z-index: 1;
|
||||
|
|
@ -447,58 +434,60 @@
|
|||
height: 10px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #fafbfc;
|
||||
background: $bg-gray-light;
|
||||
z-index: 1;
|
||||
}
|
||||
&-section {
|
||||
.settings-section {
|
||||
&:not(:last-of-type) {
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.description:hover {
|
||||
cursor: help;
|
||||
.field {
|
||||
margin-bottom: 4px;
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
input {
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
select {
|
||||
width: 100%;
|
||||
background: $bg-white;
|
||||
}
|
||||
&.field-checkbox {
|
||||
padding-left: 20px;
|
||||
vertical-align: middle;
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
float: left;
|
||||
margin: 5px 0 0 -20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* inputs for access token & shortcut were too wide */
|
||||
input.form-control {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.form-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
select {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: 2px;
|
||||
.link-button {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.access-token {
|
||||
border-bottom: none; // prevent overwrite by github style
|
||||
.link-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
.hint {
|
||||
color: #6a737d;
|
||||
}
|
||||
.hint {
|
||||
color: $gray;
|
||||
}
|
||||
.access-token-input-control {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
.access-token-input {
|
||||
flex: 1;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
.toggle-shortcut {
|
||||
.hint {
|
||||
color: #6a737d;
|
||||
}
|
||||
}
|
||||
.toggle-shortcut-input-control {
|
||||
|
|
@ -507,22 +496,14 @@
|
|||
flex: 1;
|
||||
}
|
||||
}
|
||||
.singleton {
|
||||
.hint {
|
||||
color: #6a737d;
|
||||
}
|
||||
}
|
||||
.header-row {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border-top: 1px solid #eaecef;
|
||||
border-top: 1px solid $border-gray-light;
|
||||
|
||||
.version {
|
||||
color: #999999;
|
||||
}
|
||||
.show-settings-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
|
@ -534,10 +515,6 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
input.form-control:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { Gitako } from 'components/Gitako'
|
|||
import { addMiddleware } from 'driver/connect'
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import './content.less'
|
||||
import './content.scss'
|
||||
|
||||
addMiddleware(withErrorLog)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export function useDidUpdate(effect: React.EffectCallback, deps?: React.DependencyList) {
|
||||
const firstTime = React.useRef(true)
|
||||
React.useEffect(() => {
|
||||
if (firstTime.current) {
|
||||
firstTime.current = false
|
||||
return
|
||||
}
|
||||
return effect()
|
||||
}, deps)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import useLocation from 'react-use/esm/useLocation'
|
||||
import { useLocation } from 'react-use'
|
||||
|
||||
export function useOnLocationChange(
|
||||
callback: React.EffectCallback,
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export function usePrevious<T>(newValue: T) {
|
||||
const previousRef = React.useRef(newValue)
|
||||
React.useEffect(() => {
|
||||
previousRef.current = newValue
|
||||
})
|
||||
return previousRef.current
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export function useWindowSize(
|
||||
callback: (width: number) => void,
|
||||
deps?: ReadonlyArray<any> | undefined,
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
const resizeListener: (this: Window, ev: UIEvent) => void = () => callback(window.innerWidth)
|
||||
window.addEventListener('resize', resizeListener)
|
||||
return () => window.removeEventListener('resize', resizeListener)
|
||||
}, deps)
|
||||
}
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "Node",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["dom", "es2017.object", "es2016"],
|
||||
"baseUrl": ".",
|
||||
"experimentalDecorators": true,
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
"*": ["src/*", "packages/*"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,10 +81,12 @@ module.exports = {
|
|||
cacheDirectory: true,
|
||||
},
|
||||
include: [srcPath, packagesPath],
|
||||
exclude: /node_modules/,
|
||||
sideEffects: false,
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
loader: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
|
||||
test: /\.scss$/,
|
||||
loader: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
|
||||
include: [srcPath],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue