mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
Merge branch 'develop'
This commit is contained in:
commit
9d004278ed
25 changed files with 1812 additions and 2502 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gitako",
|
||||
"version": "0.5.8",
|
||||
"version": "0.5.13",
|
||||
"description": "The missing part of GitHub.",
|
||||
"repository": "https://github.com/EnixCoda/Gitako",
|
||||
"author": "EnixCoda",
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
"less": "^3.9.0",
|
||||
"less-loader": "^4.0.5",
|
||||
"style-loader": "^0.23.1",
|
||||
"typescript": "3.3",
|
||||
"typescript": "^3.6.3",
|
||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.29.6",
|
||||
|
|
|
|||
|
|
@ -26,19 +26,19 @@
|
|||
],
|
||||
"types": "index.d.ts",
|
||||
"devDependencies": {
|
||||
"browserify": "^15.0.0",
|
||||
"browserify": "^16.5.0",
|
||||
"jscs": "^3.0.7",
|
||||
"jsdom": "^11.5.1",
|
||||
"jsdom": "^15.2.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"jshint": "^2.5.6",
|
||||
"npmpub": "^3.1.0",
|
||||
"nyc": "^11.4.1",
|
||||
"opn-cli": "^3.1.0",
|
||||
"serve": "^11.0.0",
|
||||
"jshint": "^2.10.2",
|
||||
"npmpub": "^5.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"opn-cli": "^5.0.0",
|
||||
"serve": "^11.2.0",
|
||||
"tap-nyc": "^1.0.3",
|
||||
"tap-spec": "^4.1.1",
|
||||
"tape": "^4.8.0",
|
||||
"uglify-js": "^3.3.8"
|
||||
"tap-spec": "^5.0.0",
|
||||
"tape": "^4.11.0",
|
||||
"uglify-js": "^3.6.4"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "jscs . && jshint . --exclude-path .gitignore",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,13 +2,16 @@
|
|||
rm -rf dist
|
||||
yarn build
|
||||
|
||||
GITAKO_VERSION=v$(node scripts/get-version.js)
|
||||
echo "Got version $GITAKO_VERSION"
|
||||
GIT_SHA=$(git rev-parse HEAD)
|
||||
VERSION=v$(node scripts/get-version.js)
|
||||
echo "Got version $VERSION"
|
||||
|
||||
# sentry
|
||||
yarn sentry-cli releases new "$GITAKO_VERSION"
|
||||
yarn sentry-cli releases files "$GITAKO_VERSION" upload-sourcemaps dist --no-rewrite
|
||||
yarn sentry-cli releases finalize "$GITAKO_VERSION"
|
||||
yarn sentry-cli releases new "$VERSION"
|
||||
git push # make sure sentry can retrieve current commit on remote
|
||||
yarn sentry-cli releases set-commits $VERSION --commit "EnixCoda/Gitako@$GIT_SHA"
|
||||
yarn sentry-cli releases files "$VERSION" upload-sourcemaps dist --no-rewrite
|
||||
yarn sentry-cli releases finalize "$VERSION"
|
||||
|
||||
cd dist
|
||||
rm -f ./gitako.zip
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as Sentry from '@sentry/browser'
|
||||
import { Middleware } from 'driver/connect.js'
|
||||
import { IN_PRODUCTION_MODE } from 'env'
|
||||
import * as Sentry from '@sentry/browser'
|
||||
import { version } from '../package.json'
|
||||
|
||||
const PUBLIC_KEY = 'd22ec5c9cc874539a51c78388c12e3b0'
|
||||
|
|
@ -29,19 +29,24 @@ export const withErrorLog: Middleware = function withErrorLog(method, args) {
|
|||
]
|
||||
}
|
||||
|
||||
function reportError(error: Error, extra?: any) {
|
||||
function reportError(
|
||||
error: Error,
|
||||
extra?: {
|
||||
[key: string]: any
|
||||
},
|
||||
) {
|
||||
if (!IN_PRODUCTION_MODE) {
|
||||
console.error(error)
|
||||
console.error('Extra:\n', extra)
|
||||
return
|
||||
}
|
||||
|
||||
Sentry.captureException(error)
|
||||
if (extra) {
|
||||
Sentry.withScope(scope => {
|
||||
Sentry.withScope(scope => {
|
||||
if (extra) {
|
||||
Object.keys(extra).forEach(key => {
|
||||
scope.setExtra(key, extra[key])
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Sentry.captureException(error)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import * as React from 'react'
|
||||
import { FixedSizeList as List, ListChildComponentProps } from 'react-window'
|
||||
import LoadingIndicator from 'components/LoadingIndicator'
|
||||
import Node from 'components/Node'
|
||||
import SearchBar from 'components/SearchBar'
|
||||
import connect from 'driver/connect'
|
||||
import { FileExplorer as FileExplorerCore } from 'driver/core'
|
||||
import SearchBar from 'components/SearchBar'
|
||||
import Node from 'components/Node'
|
||||
import LoadingIndicator from 'components/LoadingIndicator'
|
||||
import cx from 'utils/cx'
|
||||
import { ConnectorState } from 'driver/core/FileExplorer'
|
||||
import { TreeData, MetaData } from 'utils/GitHubHelper'
|
||||
import { VisibleNodes, TreeNode } from 'utils/VisibleNodesGenerator'
|
||||
import * as React from 'react'
|
||||
import { FixedSizeList as List, ListChildComponentProps } from 'react-window'
|
||||
import cx from 'utils/cx'
|
||||
import { MetaData, TreeData } from 'utils/GitHubHelper'
|
||||
import { usePrevious } from 'utils/hooks'
|
||||
import { TreeNode, VisibleNodes } from 'utils/VisibleNodesGenerator'
|
||||
import Icon from './Icon'
|
||||
import SizeObserver from './SizeObserver'
|
||||
import { usePrevious } from 'utils/hooks'
|
||||
|
||||
export type Props = {
|
||||
treeData?: TreeData
|
||||
|
|
@ -26,7 +26,6 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
|
|||
static defaultProps: Partial<Props & ConnectorState> = {
|
||||
freeze: false,
|
||||
searchKey: '',
|
||||
searched: false,
|
||||
visibleNodes: null,
|
||||
}
|
||||
|
||||
|
|
@ -126,16 +125,16 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
|
|||
})
|
||||
|
||||
private renderActions: Node['props']['renderActions'] = node => {
|
||||
const { searchKey, searched, goTo } = this.props
|
||||
const { searchKey, goTo } = this.props
|
||||
return (
|
||||
searchKey &&
|
||||
searched && (
|
||||
<div className={'go-to-wrapper'}>
|
||||
<button className={'go-to-button'} onClick={this.revealNode(goTo, node)}>
|
||||
<Icon type="go-to" />
|
||||
Reveal in file tree
|
||||
</button>
|
||||
</div>
|
||||
searchKey && (
|
||||
<button
|
||||
title={'Reveal in file tree'}
|
||||
className={'go-to-button'}
|
||||
onClick={this.revealNode(goTo, node)}
|
||||
>
|
||||
<Icon type="go-to" />
|
||||
</button>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -157,7 +156,7 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
|
|||
visibleNodes,
|
||||
freeze,
|
||||
handleKeyDown,
|
||||
handleSearchKeyChange,
|
||||
search,
|
||||
toggleShowSettings,
|
||||
onFocusSearchBar,
|
||||
searchKey,
|
||||
|
|
@ -174,11 +173,7 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
|
|||
) : (
|
||||
visibleNodes && (
|
||||
<React.Fragment>
|
||||
<SearchBar
|
||||
searchKey={searchKey}
|
||||
onSearchKeyChange={handleSearchKeyChange}
|
||||
onFocus={onFocusSearchBar}
|
||||
/>
|
||||
<SearchBar searchKey={searchKey} onSearch={search} onFocus={onFocusSearchBar} />
|
||||
{this.renderFiles(visibleNodes)}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from 'react'
|
||||
import Icon from 'components/Icon'
|
||||
import * as React from 'react'
|
||||
import cx from 'utils/cx'
|
||||
import { OperatingSystems, os } from 'utils/general'
|
||||
import { TreeNode } from 'utils/VisibleNodesGenerator'
|
||||
import { os, OperatingSystems } from 'utils/general'
|
||||
|
||||
function getIconType(node: TreeNode) {
|
||||
switch (node.type) {
|
||||
|
|
@ -52,7 +52,7 @@ export default class Node extends React.PureComponent<Props> {
|
|||
className={cx('node-item', { expanded })}
|
||||
style={{ paddingLeft: `${10 + 20 * depth}px` }}
|
||||
>
|
||||
<div>
|
||||
<div className={'node-item-label'}>
|
||||
<Icon type={getIconType(node)} />
|
||||
<span className={'node-item-name'}>{name}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,36 @@
|
|||
import * as React from 'react'
|
||||
import cx from 'utils/cx'
|
||||
|
||||
type Props = {
|
||||
onSearchKeyChange: React.FormEventHandler
|
||||
onSearch: (searchKey: string) => void
|
||||
onFocus: React.FocusEventHandler
|
||||
searchKey: string
|
||||
}
|
||||
|
||||
export default function SearchBar({ onSearchKeyChange, onFocus, searchKey }: Props) {
|
||||
export default function SearchBar({ onSearch, onFocus, searchKey }: Props) {
|
||||
return (
|
||||
<div className={'search-input-wrapper'}>
|
||||
<input
|
||||
onFocus={onFocus}
|
||||
tabIndex={0}
|
||||
className="form-control search-input"
|
||||
className={cx('form-control search-input', {
|
||||
error: !isValidRegexpSource(searchKey),
|
||||
})}
|
||||
aria-label="search files"
|
||||
placeholder="Search files (RegEx)"
|
||||
placeholder="Search files (use RegExp)"
|
||||
type="text"
|
||||
onChange={onSearchKeyChange}
|
||||
onChange={({ target: { value } }) => onSearch(value)}
|
||||
value={searchKey}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function isValidRegexpSource(source: string) {
|
||||
try {
|
||||
new RegExp(source)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import Icon from 'components/Icon'
|
||||
import * as React from 'react'
|
||||
import configHelper, { configKeys } from 'utils/configHelper'
|
||||
import configHelper, { Config, configKeys } from 'utils/configHelper'
|
||||
import { friendlyFormatShortcut, JSONRequest, parseURLSearch } from 'utils/general'
|
||||
import keyHelper from 'utils/keyHelper'
|
||||
import { version } from '../../package.json'
|
||||
|
||||
const WIKI_HOME_LINK = 'https://github.com/EnixCoda/Gitako/wiki'
|
||||
const wikiLinks = {
|
||||
compressSingletonFolder: 'https://github.com/EnixCoda/Gitako/wiki/Compress-Singleton-Folder',
|
||||
changeLog: 'https://github.com/EnixCoda/Gitako/wiki/Change-Log',
|
||||
copyFileButton: 'https://github.com/EnixCoda/Gitako/wiki/Copy-file-and-snippet',
|
||||
copySnippet: 'https://github.com/EnixCoda/Gitako/wiki/Copy-file-and-snippet',
|
||||
createAccessToken:
|
||||
'https://github.com/EnixCoda/Gitako/wiki/How-to-create-access-token-for-Gitako%3F',
|
||||
compressSingletonFolder: `${WIKI_HOME_LINK}/Compress-Singleton-Folder`,
|
||||
changeLog: `${WIKI_HOME_LINK}/Change-Log`,
|
||||
copyFileButton: `${WIKI_HOME_LINK}/Copy-file-and-snippet`,
|
||||
copySnippet: `${WIKI_HOME_LINK}/Copy-file-and-snippet`,
|
||||
createAccessToken: `${WIKI_HOME_LINK}/How-to-create-access-token-for-Gitako%3F`,
|
||||
}
|
||||
|
||||
const oauth = {
|
||||
|
|
@ -26,15 +26,16 @@ type Props = {
|
|||
activated: boolean
|
||||
onAccessTokenChange: (accessToken: string) => void
|
||||
onShortcutChange: (shortcut: string) => void
|
||||
compressSingletonFolder: boolean
|
||||
copyFileButton: boolean
|
||||
copySnippetButton: boolean
|
||||
setCopyFile: (copyFileButton: Props['copyFileButton']) => void
|
||||
setCopySnippet: (copySnippetButton: Props['copySnippetButton']) => void
|
||||
setCompressSingleton: (compressSingletonFolder: Props['compressSingletonFolder']) => void
|
||||
setIntelligentToggle: (intelligentToggle: Props['intelligentToggle']) => void
|
||||
toggleShowSettings: () => void
|
||||
toggleShowSideBarShortcut?: string
|
||||
}
|
||||
} & Pick<
|
||||
Config,
|
||||
'compressSingletonFolder' | 'copyFileButton' | 'copySnippetButton' | 'intelligentToggle'
|
||||
>
|
||||
|
||||
type State = {
|
||||
accessToken?: string
|
||||
|
|
@ -47,7 +48,8 @@ type State = {
|
|||
label: string
|
||||
onChange: (e: React.FormEvent<HTMLInputElement>) => Promise<void> | void
|
||||
getValue: () => boolean
|
||||
wikiLink: string
|
||||
wikiLink?: string
|
||||
description?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
|
|
@ -71,14 +73,14 @@ export default class SettingsBar extends React.PureComponent<Props, State> {
|
|||
},
|
||||
{
|
||||
key: 'copy-file',
|
||||
label: 'Copy File',
|
||||
label: 'Copy File Shortcut',
|
||||
onChange: this.createOnToggleChecked(configKeys.copyFileButton, this.props.setCopyFile),
|
||||
getValue: () => this.props.copyFileButton,
|
||||
wikiLink: wikiLinks.copyFileButton,
|
||||
},
|
||||
{
|
||||
key: 'copy-snippet',
|
||||
label: 'Copy Snippet',
|
||||
label: 'Copy Snippet Shortcut',
|
||||
onChange: this.createOnToggleChecked(
|
||||
configKeys.copySnippetButton,
|
||||
this.props.setCopySnippet,
|
||||
|
|
@ -86,6 +88,18 @@ export default class SettingsBar extends React.PureComponent<Props, State> {
|
|||
getValue: () => this.props.copySnippetButton,
|
||||
wikiLink: wikiLinks.copySnippet,
|
||||
},
|
||||
{
|
||||
key: 'intelligent-toggle',
|
||||
label: 'Intelligent Toggle',
|
||||
onChange: async (e: React.FormEvent<HTMLInputElement>) => {
|
||||
const { checked } = e.currentTarget
|
||||
const intelligentToggle = checked ? null : true
|
||||
await configHelper.setOne(configKeys.intelligentToggle, intelligentToggle)
|
||||
this.props.setIntelligentToggle(intelligentToggle)
|
||||
},
|
||||
getValue: () => this.props.intelligentToggle === null,
|
||||
description: `Gitako will open/close automatically according to page content when this is enabled.`,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -300,9 +314,17 @@ export default class SettingsBar extends React.PureComponent<Props, State> {
|
|||
checked={option.getValue()}
|
||||
/>
|
||||
{option.label}
|
||||
<a href={option.wikiLink} target={'_blank'}>
|
||||
(?)
|
||||
</a>
|
||||
{option.wikiLink ? (
|
||||
<a href={option.wikiLink} target={'_blank'}>
|
||||
(?)
|
||||
</a>
|
||||
) : (
|
||||
option.description && (
|
||||
<span className={'description'} title={option.description}>
|
||||
(?)
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</label>
|
||||
<br />
|
||||
</React.Fragment>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import * as React from 'react'
|
||||
import { SideBar as SideBarCore } from 'driver/core'
|
||||
import connect from 'driver/connect'
|
||||
import FileExplorer from 'components/FileExplorer'
|
||||
import ToggleShowButton from 'components/ToggleShowButton'
|
||||
import MetaBar from 'components/MetaBar'
|
||||
import SettingsBar from 'components/SettingsBar'
|
||||
import Portal from 'components/Portal'
|
||||
import Resizable from 'components/Resizable'
|
||||
import cx from 'utils/cx'
|
||||
import SettingsBar from 'components/SettingsBar'
|
||||
import ToggleShowButton from 'components/ToggleShowButton'
|
||||
import connect from 'driver/connect'
|
||||
import { SideBar as SideBarCore } from 'driver/core'
|
||||
import { ConnectorState } from 'driver/core/SideBar'
|
||||
import * as React from 'react'
|
||||
import cx from 'utils/cx'
|
||||
|
||||
export type Props = {}
|
||||
|
||||
|
|
@ -100,6 +100,7 @@ class Gitako extends React.PureComponent<Props & ConnectorState> {
|
|||
compressSingletonFolder,
|
||||
copyFileButton,
|
||||
copySnippetButton,
|
||||
intelligentToggle,
|
||||
toggleShowSideBarShortcut,
|
||||
logoContainerElement,
|
||||
toggleShowSideBar,
|
||||
|
|
@ -109,6 +110,7 @@ class Gitako extends React.PureComponent<Props & ConnectorState> {
|
|||
setCompressSingleton,
|
||||
setCopyFile,
|
||||
setCopySnippet,
|
||||
setIntelligentToggle,
|
||||
} = this.props
|
||||
return (
|
||||
<div className={'gitako-side-bar'}>
|
||||
|
|
@ -132,9 +134,11 @@ class Gitako extends React.PureComponent<Props & ConnectorState> {
|
|||
compressSingletonFolder={compressSingletonFolder}
|
||||
copyFileButton={copyFileButton}
|
||||
copySnippetButton={copySnippetButton}
|
||||
intelligentToggle={intelligentToggle}
|
||||
setCompressSingleton={setCompressSingleton}
|
||||
setCopyFile={setCopyFile}
|
||||
setCopySnippet={setCopySnippet}
|
||||
setIntelligentToggle={setIntelligentToggle}
|
||||
/>
|
||||
</div>
|
||||
</Resizable>
|
||||
|
|
|
|||
|
|
@ -306,6 +306,10 @@
|
|||
input[type='text'].form-control {
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
|
||||
&.error {
|
||||
border-color: #d73a49;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,30 +356,32 @@
|
|||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.node-item-name {
|
||||
padding-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.node-item-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.node-item-name {
|
||||
padding-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.node-item:hover .node-item-name {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.go-to-wrapper {
|
||||
max-width: 16px;
|
||||
overflow: hidden;
|
||||
transition: max-width 0.5s linear;
|
||||
&:hover {
|
||||
max-width: 160px;
|
||||
}
|
||||
.go-to-button {
|
||||
white-space: nowrap;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
.go-to-button {
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
&:hover {
|
||||
.go-to-button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -409,6 +415,10 @@
|
|||
}
|
||||
&-section {
|
||||
padding-bottom: 16px;
|
||||
|
||||
.description:hover {
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
|
||||
/* inputs for access token & shortcut were too wide */
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import * as React from 'react'
|
||||
import { raiseError } from 'analytics'
|
||||
|
||||
export type Method<Args = any[]> = (
|
||||
...args: Args extends any[] ? Args : any[]
|
||||
) => void | Promise<void>
|
||||
export type Method<Args extends any[] = any[]> = (...args: Args) => void | Promise<void>
|
||||
export type Middleware = <M extends Method, MM extends Method>(
|
||||
method: M,
|
||||
args: Parameters<M>,
|
||||
|
|
@ -35,9 +32,9 @@ function run<M extends Method>([method, args]: [M, Parameters<M>]) {
|
|||
|
||||
export type DispatchState<Props, State> = React.Component<Props, State>['setState']
|
||||
export type GetState<State> = () => State
|
||||
export type TriggerOtherMethod<Props, State> = <Args, MC extends MethodCreator<Props, State, Args>>(
|
||||
methodCreator: MC,
|
||||
...args: Parameters<ReturnType<MC>>
|
||||
export type TriggerOtherMethod<Props, State> = <Args extends any[]>(
|
||||
methodCreator: MethodCreator<Props, State, Args>,
|
||||
...args: Parameters<ReturnType<MethodCreator<Props, State, Args>>>
|
||||
) => void
|
||||
|
||||
export type Dispatch<Props, State> = {
|
||||
|
|
@ -46,7 +43,7 @@ export type Dispatch<Props, State> = {
|
|||
call: TriggerOtherMethod<Props, State>
|
||||
}
|
||||
|
||||
export type MethodCreator<Props, State, Args = []> = (
|
||||
export type MethodCreator<Props, State, Args extends any[] = []> = (
|
||||
dispatch: Dispatch<Props, State>,
|
||||
) => Method<Args>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import { raiseError } from 'analytics'
|
||||
import { Props } from 'components/FileExplorer'
|
||||
import { GetCreatedMethod, MethodCreator } from 'driver/connect'
|
||||
import * as ini from 'ini'
|
||||
import { Base64 } from 'js-base64'
|
||||
import DOMHelper from 'utils/DOMHelper'
|
||||
import { findNode, searchKeyToRegexps } from 'utils/general'
|
||||
import GitHubHelper, { BlobData } from 'utils/GitHubHelper'
|
||||
import treeParser from 'utils/treeParser'
|
||||
import URLHelper from 'utils/URLHelper'
|
||||
import VisibleNodesGenerator, { TreeNode, VisibleNodes } from 'utils/VisibleNodesGenerator'
|
||||
import GitHubHelper, { BlobData } from 'utils/GitHubHelper'
|
||||
import { findNode } from 'utils/general'
|
||||
import { MethodCreator, GetCreatedMethod } from 'driver/connect'
|
||||
import { Props } from 'components/FileExplorer'
|
||||
import { raiseError } from 'analytics'
|
||||
|
||||
export type ConnectorState = {
|
||||
stateText: string
|
||||
visibleNodes: VisibleNodes | null
|
||||
searchKey: string
|
||||
searched: boolean
|
||||
searched: boolean // derived state from searchKey, = !!searchKey
|
||||
|
||||
init: GetCreatedMethod<typeof init>
|
||||
execAfterRender: GetCreatedMethod<typeof execAfterRender>
|
||||
handleKeyDown: GetCreatedMethod<typeof handleKeyDown>
|
||||
handleSearchKeyChange: GetCreatedMethod<typeof handleSearchKeyChange>
|
||||
search: GetCreatedMethod<typeof search>
|
||||
onNodeClick: GetCreatedMethod<typeof onNodeClick>
|
||||
onFocusSearchBar: GetCreatedMethod<typeof onFocusSearchBar>
|
||||
setUpTree: GetCreatedMethod<typeof setUpTree>
|
||||
|
|
@ -48,7 +48,7 @@ type Task = () => void
|
|||
const tasksAfterRender: (Task)[] = []
|
||||
let visibleNodesGenerator: VisibleNodesGenerator
|
||||
|
||||
type BoundMethodCreator<Args = []> = MethodCreator<Props, ConnectorState, Args>
|
||||
type BoundMethodCreator<Args extends any[] = []> = MethodCreator<Props, ConnectorState, Args>
|
||||
|
||||
const init: BoundMethodCreator = dispatch => () =>
|
||||
dispatch.call(setStateText, 'Fetching File List...')
|
||||
|
|
@ -118,7 +118,7 @@ function handleParsed(root: TreeNode, parsed: Parsed) {
|
|||
node.accessDenied = true
|
||||
}
|
||||
} else {
|
||||
raiseError(Error(`Sub-module node ${path} not found`))
|
||||
raiseError(new Error(`Sub-module node not found`), { path })
|
||||
}
|
||||
} else {
|
||||
handleParsed(root, value as Parsed)
|
||||
|
|
@ -150,7 +150,7 @@ const setUpTree: BoundMethodCreator<
|
|||
compress: compressSingletonFolder,
|
||||
})
|
||||
|
||||
await visibleNodesGenerator.init()
|
||||
visibleNodesGenerator.init()
|
||||
|
||||
tasksAfterRender.push(DOMHelper.focusSearchInput)
|
||||
dispatch.call(setStateText, '')
|
||||
|
|
@ -278,28 +278,15 @@ const handleKeyDown: BoundMethodCreator<[React.KeyboardEvent]> = dispatch => eve
|
|||
|
||||
const onFocusSearchBar: BoundMethodCreator = dispatch => () => dispatch.call(focusNode, null, false)
|
||||
|
||||
const handleSearchKeyChange: BoundMethodCreator<
|
||||
[React.FormEvent<HTMLInputElement>]
|
||||
> = dispatch => async event => {
|
||||
const searchKey = event.currentTarget.value
|
||||
await dispatch.call(search, searchKey)
|
||||
}
|
||||
|
||||
const search: BoundMethodCreator<[string]> = dispatch => {
|
||||
let i = 0
|
||||
return async searchKey => {
|
||||
dispatch.set({ searchKey })
|
||||
const j = (i += 1)
|
||||
await visibleNodesGenerator.search(searchKey)
|
||||
if (i === j) {
|
||||
dispatch.set(({ searched }) => ({ searched: !(searched && searchKey === '') }))
|
||||
dispatch.call(updateVisibleNodes)
|
||||
}
|
||||
}
|
||||
const search: BoundMethodCreator<[string]> = dispatch => searchKey => {
|
||||
dispatch.set({ searchKey, searched: searchKey !== '' })
|
||||
const regexps = searchKeyToRegexps(searchKey)
|
||||
visibleNodesGenerator.search(regexps)
|
||||
dispatch.call(updateVisibleNodes)
|
||||
}
|
||||
|
||||
const goTo: BoundMethodCreator<[string[]]> = dispatch => async currentPath => {
|
||||
await visibleNodesGenerator.search('')
|
||||
visibleNodesGenerator.search([])
|
||||
tasksAfterRender.push(() => {
|
||||
const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
|
||||
if (nodeExpandedTo) {
|
||||
|
|
@ -360,7 +347,6 @@ export default {
|
|||
handleKeyDown,
|
||||
onFocusSearchBar,
|
||||
search,
|
||||
handleSearchKeyChange,
|
||||
setExpand,
|
||||
goTo,
|
||||
toggleNodeExpansion,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
import { Props } from 'components/SideBar'
|
||||
import { GetCreatedMethod, MethodCreator } from 'driver/connect'
|
||||
import configHelper, { Config, configKeys } from 'utils/configHelper'
|
||||
import DOMHelper from 'utils/DOMHelper'
|
||||
import GitHubHelper, {
|
||||
NOT_FOUND,
|
||||
BAD_CREDENTIALS,
|
||||
API_RATE_LIMIT,
|
||||
BAD_CREDENTIALS,
|
||||
BLOCKED_PROJECT,
|
||||
EMPTY_PROJECT,
|
||||
MetaData,
|
||||
NOT_FOUND,
|
||||
TreeData,
|
||||
BLOCKED_PROJECT,
|
||||
} from 'utils/GitHubHelper'
|
||||
import configHelper from 'utils/configHelper'
|
||||
import URLHelper from 'utils/URLHelper'
|
||||
import keyHelper from 'utils/keyHelper'
|
||||
import { MethodCreator, GetCreatedMethod } from 'driver/connect'
|
||||
import { Props } from 'components/SideBar'
|
||||
import URLHelper from 'utils/URLHelper'
|
||||
|
||||
export type ConnectorState = {
|
||||
// initial width of side bar
|
||||
baseSize: number
|
||||
// error message
|
||||
error?: string
|
||||
// whether Gitako side bar should be shown
|
||||
|
|
@ -25,22 +23,14 @@ export type ConnectorState = {
|
|||
showSettings: boolean
|
||||
// whether failed loading the repo due to it is private
|
||||
errorDueToAuth: boolean
|
||||
// access token for GitHub
|
||||
accessToken?: string
|
||||
// the shortcut string for toggle sidebar
|
||||
toggleShowSideBarShortcut?: string
|
||||
// meta data for the repository
|
||||
metaData?: MetaData
|
||||
// file tree data
|
||||
treeData?: TreeData
|
||||
// few settings
|
||||
compressSingletonFolder: boolean
|
||||
copyFileButton: boolean
|
||||
copySnippetButton: boolean
|
||||
logoContainerElement: Element | null
|
||||
disabled: boolean
|
||||
initializingPromise: Promise<void> | null
|
||||
|
||||
} & {
|
||||
init: GetCreatedMethod<typeof init>
|
||||
onPJAXEnd: GetCreatedMethod<typeof onPJAXEnd>
|
||||
onKeyDown: GetCreatedMethod<typeof onKeyDown>
|
||||
|
|
@ -52,9 +42,17 @@ export type ConnectorState = {
|
|||
setCopyFile: GetCreatedMethod<typeof setCopyFile>
|
||||
setCopySnippet: GetCreatedMethod<typeof setCopySnippet>
|
||||
setCompressSingleton: GetCreatedMethod<typeof setCompressSingleton>
|
||||
}
|
||||
setIntelligentToggle: GetCreatedMethod<typeof setIntelligentToggle>
|
||||
} & {
|
||||
baseSize: number
|
||||
toggleShowSideBarShortcut?: string
|
||||
accessToken?: string
|
||||
} & Pick<
|
||||
Config,
|
||||
'compressSingletonFolder' | 'copyFileButton' | 'copySnippetButton' | 'intelligentToggle'
|
||||
>
|
||||
|
||||
type BoundMethodCreator<Args = []> = MethodCreator<Props, ConnectorState, Args>
|
||||
type BoundMethodCreator<Args extends any[] = []> = MethodCreator<Props, ConnectorState, Args>
|
||||
|
||||
const init: BoundMethodCreator = dispatch => async () => {
|
||||
const { initializingPromise } = dispatch.get()
|
||||
|
|
@ -91,6 +89,7 @@ const init: BoundMethodCreator = dispatch => async () => {
|
|||
compressSingletonFolder,
|
||||
copyFileButton,
|
||||
copySnippetButton,
|
||||
intelligentToggle,
|
||||
} = await configHelper.getAll()
|
||||
DOMHelper.decorateGitHubPageContent({ copyFileButton, copySnippetButton })
|
||||
dispatch.set({
|
||||
|
|
@ -100,6 +99,7 @@ const init: BoundMethodCreator = dispatch => async () => {
|
|||
compressSingletonFolder,
|
||||
copyFileButton,
|
||||
copySnippetButton,
|
||||
intelligentToggle,
|
||||
})
|
||||
|
||||
if (!metaData.branchName || !metaData.userName) return
|
||||
|
|
@ -148,7 +148,8 @@ const init: BoundMethodCreator = dispatch => async () => {
|
|||
.catch(err => dispatch.call(handleError, err))
|
||||
Object.assign(metaData, { api: metaDataFromAPI })
|
||||
dispatch.call(setMetaData, metaData)
|
||||
const shouldShow = URLHelper.isInCodePage(metaData)
|
||||
const shouldShow =
|
||||
intelligentToggle === null ? URLHelper.isInCodePage(metaData) : intelligentToggle
|
||||
dispatch.call(setShouldShow, shouldShow)
|
||||
DOMHelper.markGitakoReadyState()
|
||||
} catch (err) {
|
||||
|
|
@ -179,12 +180,15 @@ const handleError: BoundMethodCreator<[Error]> = dispatch => async err => {
|
|||
}
|
||||
|
||||
const onPJAXEnd: BoundMethodCreator = dispatch => () => {
|
||||
const { metaData, copyFileButton, copySnippetButton } = dispatch.get()
|
||||
const { metaData, copyFileButton, copySnippetButton, intelligentToggle } = dispatch.get()
|
||||
DOMHelper.unmountTopProgressBar()
|
||||
DOMHelper.decorateGitHubPageContent({ copyFileButton, copySnippetButton })
|
||||
const mergedMetaData = { ...metaData, ...URLHelper.parse() }
|
||||
dispatch.call(setShouldShow, URLHelper.isInCodePage(mergedMetaData))
|
||||
dispatch.call(setMetaData, mergedMetaData)
|
||||
|
||||
if (intelligentToggle === null) {
|
||||
dispatch.call(setShouldShow, URLHelper.isInCodePage(mergedMetaData))
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyDown: BoundMethodCreator<[KeyboardEvent]> = dispatch => e => {
|
||||
|
|
@ -197,8 +201,15 @@ const onKeyDown: BoundMethodCreator<[KeyboardEvent]> = dispatch => e => {
|
|||
}
|
||||
}
|
||||
|
||||
const toggleShowSideBar: BoundMethodCreator = dispatch => () =>
|
||||
dispatch.call(setShouldShow, !dispatch.get().shouldShow)
|
||||
const toggleShowSideBar: BoundMethodCreator = dispatch => () => {
|
||||
const { intelligentToggle } = dispatch.get()
|
||||
const shouldShow = !dispatch.get().shouldShow
|
||||
dispatch.call(setShouldShow, shouldShow)
|
||||
|
||||
if (intelligentToggle !== null) {
|
||||
dispatch.call(setIntelligentToggle, shouldShow)
|
||||
}
|
||||
}
|
||||
|
||||
const setShouldShow: BoundMethodCreator<
|
||||
[ConnectorState['shouldShow']]
|
||||
|
|
@ -250,9 +261,16 @@ const setCopySnippet: BoundMethodCreator<
|
|||
[ConnectorState['copySnippetButton']]
|
||||
> = dispatch => copySnippetButton => dispatch.set({ copySnippetButton })
|
||||
|
||||
const setIntelligentToggle: BoundMethodCreator<
|
||||
[ConnectorState['intelligentToggle']]
|
||||
> = dispatch => intelligentToggle => {
|
||||
configHelper.setOne(configKeys.intelligentToggle, intelligentToggle)
|
||||
dispatch.set({ intelligentToggle })
|
||||
}
|
||||
|
||||
const useListeners: BoundMethodCreator<[boolean]> = dispatch => {
|
||||
const $onPJAXEnd = dispatch.call.bind(dispatch, onPJAXEnd)
|
||||
const $onKeyDown = dispatch.call.bind(dispatch, onKeyDown)
|
||||
const $onPJAXEnd = () => dispatch.call(onPJAXEnd)
|
||||
const $onKeyDown = (e: KeyboardEvent) => dispatch.call(onKeyDown, e)
|
||||
return on => {
|
||||
const { disabled } = dispatch.get()
|
||||
if (on && !disabled) {
|
||||
|
|
@ -279,6 +297,7 @@ export default {
|
|||
setCompressSingleton,
|
||||
setCopyFile,
|
||||
setCopySnippet,
|
||||
setIntelligentToggle,
|
||||
setError,
|
||||
handleError,
|
||||
useListeners,
|
||||
|
|
|
|||
5
src/global.d.ts
vendored
5
src/global.d.ts
vendored
|
|
@ -4,8 +4,3 @@ declare module '*.svg?svgr' {
|
|||
const component: SvgrComponent
|
||||
export default component
|
||||
}
|
||||
|
||||
type Omit<T, K extends keyof T> = Pick<
|
||||
T,
|
||||
({ [P in keyof T]: P } & { [P in K]: never } & { [x: string]: never })[keyof T]
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Gitako - Github file tree",
|
||||
"version": "0.5.8",
|
||||
"version": "0.5.13",
|
||||
"description": "The missing part of GitHub.",
|
||||
"author": "EnixCoda",
|
||||
"icons": {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
* this helper helps manipulating DOM
|
||||
*/
|
||||
|
||||
import * as PJAX from 'pjax'
|
||||
import * as NProgress from 'nprogress'
|
||||
import { raiseError } from 'analytics'
|
||||
import * as NProgress from 'nprogress'
|
||||
import * as PJAX from 'pjax'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ function $<EE extends Element, E extends (element: EE) => any, O extends () => a
|
|||
}
|
||||
|
||||
function isInCodePage() {
|
||||
const branchListSelector = '.branch-select-menu'
|
||||
const branchListSelector = '#branch-select-menu.branch-select-menu'
|
||||
return Boolean($(branchListSelector))
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ function getCurrentBranch() {
|
|||
}
|
||||
const defaultTitle = 'Switch branches or tags'
|
||||
const title = branchButtonElement.title.trim()
|
||||
if (title !== defaultTitle) return title
|
||||
if (title !== defaultTitle && !title.includes(' ')) return title
|
||||
}
|
||||
|
||||
const findFileButtonSelector =
|
||||
|
|
@ -83,7 +83,7 @@ function getCurrentBranch() {
|
|||
const result = urlFromFindFileButton.match(commitPathRegex)
|
||||
if (result) {
|
||||
const [_, userName, repoName, branchName] = result
|
||||
return branchName
|
||||
if (!branchName.includes(' ')) return branchName
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function isEmptyProject(content: any /* examined any */) {
|
|||
}
|
||||
|
||||
function isBlockedProject(content: any /* examined any */) {
|
||||
return content && content['message'] === "Repository access blocked"
|
||||
return content && content['message'] === 'Repository access blocked'
|
||||
}
|
||||
|
||||
type Options = {
|
||||
|
|
@ -45,7 +45,7 @@ async function request(url: string, { accessToken }: Options = {}) {
|
|||
if (isEmptyProject(content)) throw new Error(EMPTY_PROJECT)
|
||||
if (isBlockedProject(content)) throw new Error(BLOCKED_PROJECT)
|
||||
// Unknown type of error, report it!
|
||||
raiseError(new Error(`Got ${res.statusText} when requesting ${url}`))
|
||||
raiseError(new Error(res.statusText))
|
||||
throw new Error(content && content.message)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import storageHelper from 'utils/storageHelper'
|
||||
import { pick } from 'utils/general'
|
||||
|
||||
type Config = {
|
||||
export type Config = {
|
||||
sideBarWidth: number
|
||||
shortcut: string | undefined
|
||||
access_token: string | undefined
|
||||
compressSingletonFolder: boolean
|
||||
copyFileButton: boolean
|
||||
copySnippetButton: boolean
|
||||
intelligentToggle: boolean | null // `null` stands for intelligent, boolean for sidebar open status
|
||||
}
|
||||
|
||||
export enum configKeys {
|
||||
|
|
@ -17,6 +17,7 @@ export enum configKeys {
|
|||
compressSingletonFolder = 'compressSingletonFolder',
|
||||
copyFileButton = 'copyFileButton',
|
||||
copySnippetButton = 'copySnippetButton',
|
||||
intelligentToggle = 'intelligentToggle',
|
||||
}
|
||||
|
||||
const defaultConfigs: Config = {
|
||||
|
|
@ -26,6 +27,7 @@ const defaultConfigs: Config = {
|
|||
compressSingletonFolder: true,
|
||||
copyFileButton: true,
|
||||
copySnippetButton: true,
|
||||
intelligentToggle: null,
|
||||
}
|
||||
|
||||
const configKeyArray = Object.values(configKeys)
|
||||
|
|
@ -34,11 +36,7 @@ function applyDefaultConfigs(configs: Config) {
|
|||
return configKeyArray.reduce(
|
||||
(applied, configKey) => {
|
||||
const key = configKey as keyof Config
|
||||
if (!(key in configs)) {
|
||||
applied[key] = defaultConfigs[key]
|
||||
} else {
|
||||
applied[key] = configs[key]
|
||||
}
|
||||
Object.assign(applied, { [key]: key in configs ? configs[key] : defaultConfigs[key] })
|
||||
return applied
|
||||
},
|
||||
{} as Config,
|
||||
|
|
@ -49,16 +47,16 @@ async function getAll(): Promise<Config> {
|
|||
return applyDefaultConfigs(await storageHelper.get(configKeyArray))
|
||||
}
|
||||
|
||||
async function getOne(key: keyof Config) {
|
||||
async function getOne(key: configKeys) {
|
||||
return (await getAll())[key]
|
||||
}
|
||||
|
||||
async function set(partialConfig: Partial<Config>) {
|
||||
return await storageHelper.set(pick(partialConfig, configKeyArray))
|
||||
async function setAll(partialConfig: Partial<Config>) {
|
||||
return await storageHelper.set(partialConfig)
|
||||
}
|
||||
|
||||
async function setOne(key: configKeys, value: any) {
|
||||
return await set({
|
||||
return await setAll({
|
||||
[key]: value,
|
||||
})
|
||||
}
|
||||
|
|
@ -66,6 +64,6 @@ async function setOne(key: configKeys, value: any) {
|
|||
export default {
|
||||
getAll,
|
||||
getOne,
|
||||
set,
|
||||
setAll,
|
||||
setOne,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ export default function cx(...classNames: any[]): string {
|
|||
case 'string':
|
||||
return className
|
||||
case 'object':
|
||||
return cx(
|
||||
...Object.entries(className).map(([key, value]) => (Boolean(value) ? key : null)),
|
||||
)
|
||||
return cx(...Object.entries(className).map(([key, value]) => (value ? key : null)))
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,3 +128,14 @@ export async function JSONRequest(url: string, data: any, method = 'post') {
|
|||
body: JSON.stringify(data),
|
||||
})).json()
|
||||
}
|
||||
|
||||
export function searchKeyToRegexps(searchKey: string) {
|
||||
if (!searchKey) return []
|
||||
|
||||
try {
|
||||
// case-sensitive when searchKey contains uppercase char
|
||||
return [new RegExp(searchKey, /[A-Z]/i.test(searchKey) ? '' : 'i')]
|
||||
} catch (err) {
|
||||
return [/$^/] // matching nothing if failed transforming regexp
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,15 +38,24 @@ function parseKeyCode(code: string) {
|
|||
|
||||
function parseEvent(e: KeyboardEvent | React.KeyboardEvent) {
|
||||
const { altKey: alt, shiftKey: shift, metaKey: meta, ctrlKey: ctrl } = e
|
||||
const code = parseKeyCode(e.key)
|
||||
const keys = { meta, ctrl, shift, alt, [code]: true }
|
||||
const combination = parse(
|
||||
Object.entries(keys)
|
||||
.filter(([key, pressed]) => pressed)
|
||||
.map(([key, pressed]) => key)
|
||||
.join('+')
|
||||
)
|
||||
return combination
|
||||
try {
|
||||
const code = parseKeyCode(e.key)
|
||||
const keys = { meta, ctrl, shift, alt, [code]: true }
|
||||
const combination = parse(
|
||||
Object.entries(keys)
|
||||
.filter(([key, pressed]) => pressed)
|
||||
.map(([key, pressed]) => key)
|
||||
.join('+'),
|
||||
)
|
||||
return combination
|
||||
} catch (err) {
|
||||
const serializedKeyData = JSON.stringify({
|
||||
keyCode: e.keyCode,
|
||||
key: e.key,
|
||||
charCode: e.charCode,
|
||||
})
|
||||
throw new Error(`Error parse keyboard event: ${serializedKeyData}`)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
const localStorage = chrome.storage.local
|
||||
|
||||
function get(mapping: string | string[] | object): Promise<any> {
|
||||
function get(mapping: string[] | null): Promise<any> {
|
||||
return new Promise(resolve => localStorage.get(mapping, resolve))
|
||||
}
|
||||
|
||||
function set(value: any): Promise<void> {
|
||||
// it's ok
|
||||
return new Promise(resolve => localStorage.set(value, resolve))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,41 +31,15 @@ export type TreeNode = {
|
|||
accessDenied?: boolean
|
||||
}
|
||||
|
||||
function getFilterFunc(keyRegex: RegExp) {
|
||||
return function filterFunc({ name }: TreeNode) {
|
||||
return keyRegex.test(name)
|
||||
}
|
||||
}
|
||||
|
||||
function filterDuplications<T>(arr: T[]) {
|
||||
return Array.from(new Set(arr))
|
||||
}
|
||||
|
||||
function search(treeNodes: TreeNode[], searchKey: string): TreeNode[] {
|
||||
if (!searchKey) return treeNodes
|
||||
/**
|
||||
* if searchKey is 'abcd'
|
||||
* then keyRegex will be /abcd/i and /a.*?b.*?c.*?d/i
|
||||
*/
|
||||
const regexpGenerators: ((raw: string) => RegExp)[] = [
|
||||
raw => new RegExp(raw, 'i'),
|
||||
raw => new RegExp(raw.split('').join('.*?'), 'i'),
|
||||
]
|
||||
|
||||
const keyRegexes: RegExp[] = []
|
||||
for (const generator of regexpGenerators) {
|
||||
try {
|
||||
const regExp = generator(searchKey)
|
||||
if (keyRegexes.find(keyRegex => keyRegex.source === regExp.source)) continue
|
||||
// prevent duplicated regExp
|
||||
keyRegexes.push(regExp)
|
||||
} catch (err) {
|
||||
// ignore invalid regexp
|
||||
}
|
||||
}
|
||||
function search(treeNodes: TreeNode[], regexps: RegExp[]): TreeNode[] {
|
||||
if (!regexps.length) return treeNodes
|
||||
|
||||
const searchResults = ([] as TreeNode[]).concat(
|
||||
...keyRegexes.map(keyRegex => treeNodes.filter(getFilterFunc(keyRegex))),
|
||||
...regexps.map(keyRegex => treeNodes.filter(({ name }) => keyRegex.test(name))),
|
||||
)
|
||||
return filterDuplications(searchResults)
|
||||
}
|
||||
|
|
@ -121,10 +95,10 @@ class L2 {
|
|||
this.searchedNodes = null
|
||||
}
|
||||
|
||||
search = async (searchKey: string) => {
|
||||
this.compressed = !searchKey
|
||||
this.searchedNodes = searchKey
|
||||
? search(this.l1.nodes, searchKey)
|
||||
search = (regexps: RegExp[]) => {
|
||||
this.compressed = !regexps.length
|
||||
this.searchedNodes = regexps.length
|
||||
? search(this.l1.nodes, regexps)
|
||||
: this.getRoot().contents || []
|
||||
}
|
||||
|
||||
|
|
@ -246,8 +220,8 @@ export default class VisibleNodesGenerator {
|
|||
this.l3 = new L3(this.l1, this.l2)
|
||||
this.l4 = new L4(this.l1, this.l2, this.l3)
|
||||
|
||||
this.search = async (...args) => {
|
||||
const r = await this.l2.search(...args)
|
||||
this.search = (...args) => {
|
||||
const r = this.l2.search(...args)
|
||||
this.l3.generateVisibleNodes()
|
||||
this.l4.focusNode(null)
|
||||
return r
|
||||
|
|
@ -258,8 +232,8 @@ export default class VisibleNodesGenerator {
|
|||
this.focusNode = (...args) => this.l4.focusNode(...args)
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.l2.search('')
|
||||
init() {
|
||||
this.l2.search([])
|
||||
this.l3.generateVisibleNodes()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue