├── .env.example ├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── dependabot.yml │ ├── release-assets.yml │ ├── tests.yml │ └── version.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .sentryclirc ├── LICENSE ├── Makefile ├── README.md ├── Safari ├── .gitignore └── Gitako │ ├── Gitako Extension │ ├── Gitako_Extension.entitlements │ ├── Info.plist │ └── SafariWebExtensionHandler.swift │ ├── Gitako.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── Gitako │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Gitako-1024.png │ │ │ ├── Gitako-128.png │ │ │ ├── Gitako-16.png │ │ │ ├── Gitako-256.png │ │ │ ├── Gitako-257.png │ │ │ ├── Gitako-32.png │ │ │ ├── Gitako-33.png │ │ │ ├── Gitako-512.png │ │ │ ├── Gitako-513.png │ │ │ └── Gitako-64.png │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Gitako.entitlements │ ├── Info.plist │ └── ViewController.swift │ ├── GitakoTests │ ├── GitakoTests.swift │ └── Info.plist │ └── GitakoUITests │ ├── GitakoUITests.swift │ └── Info.plist ├── __tests__ ├── .eslintrc.json ├── babel.config.js ├── cases │ ├── baseline.ts │ ├── empty-project.ts │ ├── expand-to-target.ts │ ├── homepage.not-render.ts │ ├── pjax.commits-page.ts │ ├── pjax.files-page.ts │ ├── pjax.general.ts │ ├── pjax.internal.ts │ ├── project-page.gitako.ts │ └── pull-request-page.gitako.ts ├── global.d.ts ├── jest.config.js ├── jest.puppeteer.config.js ├── puppeteer.d.ts ├── selectors.ts ├── setup.ts ├── testURL.ts ├── tsconfig.json └── utils.ts ├── assets ├── Chrome.svg ├── Edge.svg └── Firefox.svg ├── babel.config.js ├── contributing.md ├── jest-puppeteer.config.js ├── jest.config.js ├── package.json ├── patches ├── @primer+behaviors+1.1.3.patch ├── pjax-api+3.44.0.patch ├── spica+0.0.781.patch └── styled-components+5.3.5.patch ├── scripts ├── get-version.js ├── post-version.sh └── vscode-icons │ ├── check-emit-dir.js │ ├── generate-file-icon-index.js │ ├── generate-folder-icon-index.js │ ├── generate-icon-index.js │ ├── language-id-ext.json │ └── resolve-languages-map.js ├── server ├── .eslintrc.json ├── .gitignore ├── api │ ├── gitee.ts │ ├── github.ts │ ├── index.ts │ ├── redirect.ts │ └── utils.ts ├── package.json ├── tsconfig.json ├── vercel.json └── yarn.lock ├── src ├── .eslintrc.json ├── analytics.ts ├── assets │ └── icons │ │ ├── Gitako-128.png │ │ ├── Gitako-256.png │ │ ├── Gitako-64.png │ │ ├── Gitako.png │ │ ├── csv.d.ts │ │ ├── file-icons-index.csv │ │ ├── folder-icons-index.csv │ │ └── png.d.ts ├── background.ts ├── common.d.ts ├── components │ ├── AccessDeniedDescription.tsx │ ├── Clippy.tsx │ ├── FileExplorer │ │ ├── DiffStatGraph.tsx │ │ ├── DiffStatText.tsx │ │ ├── Node.tsx │ │ ├── hooks │ │ │ ├── useExpandTo.tsx │ │ │ ├── useFocusNode.tsx │ │ │ ├── useGetCurrentPath.tsx │ │ │ ├── useGoTo.tsx │ │ │ ├── useHandleKeyDown.tsx │ │ │ ├── useNodeRenderers.tsx │ │ │ ├── useOnNodeClick.tsx │ │ │ ├── useOnSearch.tsx │ │ │ ├── useRenderLabelText.tsx │ │ │ ├── useToggleExpansion.tsx │ │ │ ├── useVisibleNodesGenerator.tsx │ │ │ └── useVisibleNodesGeneratorMethods.tsx │ │ ├── index.tsx │ │ ├── useHandleNodeFocus.tsx │ │ ├── useLatestValueRef.tsx │ │ ├── useVirtualScroll.tsx │ │ └── useVisibleNodes.tsx │ ├── FocusTarget.tsx │ ├── Footer.tsx │ ├── Gitako.tsx │ ├── Highlight.test.tsx │ ├── Highlight.tsx │ ├── HighlightOnIndexes.test.tsx │ ├── HighlightOnIndexes.tsx │ ├── Icon.tsx │ ├── IconButton.tsx │ ├── Inputs │ │ ├── Checkbox.tsx │ │ └── SelectInput.tsx │ ├── LoadingIndicator.tsx │ ├── MetaBar.tsx │ ├── Portal.tsx │ ├── ResizeHandler.tsx │ ├── RoundIconButton.tsx │ ├── SearchBar.tsx │ ├── SideBar.tsx │ ├── SideBarResizeHandler.tsx │ ├── SidebarContext.tsx │ ├── Size.tsx │ ├── ToggleShowButton.tsx │ ├── searchModes │ │ ├── fuzzyMode.test.ts │ │ ├── fuzzyMode.tsx │ │ ├── index.tsx │ │ └── regexMode.tsx │ └── settings │ │ ├── AccessTokenSettings.tsx │ │ ├── FileTreeSettings.tsx │ │ ├── KeyboardShortcutSetting.tsx │ │ ├── SettingsBar.tsx │ │ ├── SettingsSection.tsx │ │ ├── SidebarSettings.tsx │ │ └── SimpleConfigField │ │ ├── Checkbox.tsx │ │ ├── FieldLabel.tsx │ │ ├── SelectInput.tsx │ │ └── index.tsx ├── containers │ ├── ConfigsContext.tsx │ ├── ErrorBoundary.tsx │ ├── ErrorContext.tsx │ ├── Inspector.tsx │ ├── OAuthWrapper.tsx │ ├── PortalContext.tsx │ ├── ReloadContext.tsx │ ├── RepoContext.tsx │ ├── SideBarState.tsx │ └── Theme.tsx ├── content.scss ├── content.tsx ├── env.ts ├── firefox-shim.js ├── global.d.ts ├── manifest.json ├── platforms │ ├── GitHub │ │ ├── API.ts │ │ ├── CopyFileButton.tsx │ │ ├── DOMHelper.ts │ │ ├── Request.d.ts │ │ ├── URLHelper.ts │ │ ├── embeddedDataStructures.ts │ │ ├── getCommitTreeData.ts │ │ ├── getPullRequestTreeData.ts │ │ ├── hooks │ │ │ ├── useEnterpriseStatBarStyleFix.ts │ │ │ ├── useGitHubAttachCopySnippetButton.ts │ │ │ └── useGitHubCodeFold.tsx │ │ ├── index.ts │ │ └── utils.ts │ ├── Gitea │ │ ├── API.ts │ │ ├── DOMHelper.ts │ │ ├── Request.d.ts │ │ ├── URLHelper.ts │ │ └── index.ts │ ├── Gitee │ │ ├── API.ts │ │ ├── DOMHelper.ts │ │ ├── Request.d.ts │ │ ├── URLHelper.ts │ │ └── index.ts │ ├── dummyPlatformForTypeSafety.ts │ ├── index.ts │ └── platform.d.ts ├── react-override.d.ts ├── styles │ ├── clippy.scss │ ├── code-folding.scss │ ├── gitee.scss │ ├── github.scss │ ├── index.scss │ ├── keyframes.scss │ ├── layout.scss │ ├── primer-like.scss │ └── themes.scss └── utils │ ├── $.ts │ ├── DOMHelper.ts │ ├── EventHub.ts │ ├── URLHelper.ts │ ├── VisibleNodesGenerator │ ├── BaseLayer.ts │ ├── CompressLayer.ts │ ├── FlattenLayer.ts │ ├── ShakeLayer.ts │ ├── VisibleNodesGenerator.test.ts │ ├── index.ts │ ├── prepare.ts │ ├── searchResults │ │ └── json.json │ └── treeData.json │ ├── assert.ts │ ├── config │ ├── helper.ts │ └── migrations │ │ ├── 1.0.1.ts │ │ ├── 1.3.4.ts │ │ ├── 2.6.0.ts │ │ ├── 3.0.0.ts │ │ ├── 3.13.1.ts │ │ ├── 3.5.0.ts │ │ ├── clearRaiseErrorCache.ts │ │ └── index.ts │ ├── createAnchorClickHandler.ts │ ├── cx.ts │ ├── features.ts │ ├── general.test.ts │ ├── general.ts │ ├── getSafeWidth.test.ts │ ├── getSafeWidth.ts │ ├── gitSubmodule.ts │ ├── hooks │ ├── useAbortableEffect.ts │ ├── useCSSVariable.ts │ ├── useConditionalHook.ts │ ├── useEffectOnSerializableUpdates.ts │ ├── useElementSize.ts │ ├── useFastRedirect.ts │ ├── useHandleNetworkError.ts │ ├── useLoadedContext.ts │ ├── useOnLocationChange.ts │ ├── useOnShortcutPressed.tsx │ ├── useProgressBar.ts │ ├── useResizeHandler.ts │ ├── useStateIO.ts │ └── useUpdateReason.ts │ ├── is.ts │ ├── keyHelper.ts │ ├── networkService.ts │ ├── parseIconMapCSV.ts │ ├── storageHelper.ts │ ├── treeParser.ts │ └── waitForNextEvent.ts ├── tsconfig.json ├── webpack.config.ts └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_OAUTH_CLIENT_ID=GITHUB_OAUTH_CLIENT_ID 2 | GITHUB_OAUTH_CLIENT_SECRET=GITHUB_OAUTH_CLIENT_SECRET 3 | 4 | SENTRY_PUBLIC_KEY=SENTRY_PUBLIC_KEY 5 | SENTRY_PROJECT_ID=SENTRY_PROJECT_ID 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "es2022": true 5 | }, 6 | "overrides": [ 7 | { 8 | "files": ["webpack.config.ts", "*.tsx?"], 9 | "excludedFiles": ["*.d.ts"], 10 | "plugins": ["@typescript-eslint"], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": "latest", 14 | "sourceType": "module" 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: enixcoda 4 | patreon: # enixcoda 5 | open_collective: # enixcoda 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Generate build for reuse 2 | on: 3 | workflow_call: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Get yarn cache directory path 13 | id: yarn-cache-dir-path 14 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 15 | 16 | - uses: actions/cache@v3 17 | id: yarn-cache 18 | with: 19 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 20 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 21 | restore-keys: | 22 | ${{ runner.os }}-yarn- 23 | 24 | - name: Install Dependencies 25 | env: 26 | PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true 27 | run: | 28 | yarn --ignore-platform --ignore-engines --frozen-lockfile --prefer-offline 29 | 30 | - name: Retrieve vscode icons 31 | uses: actions/checkout@v3 32 | with: 33 | repository: 'vscode-icons/vscode-icons' 34 | path: 'vscode-icons' 35 | 36 | - name: Build 37 | run: | 38 | NODE_OPTIONS=--openssl-legacy-provider make build 39 | 40 | - name: Archive production artifacts 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: dist 44 | path: dist 45 | -------------------------------------------------------------------------------- /.github/workflows/release-assets.yml: -------------------------------------------------------------------------------- 1 | name: Release assets when push tags 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.* # roughly matching version numbers 7 | - release-* 8 | 9 | jobs: 10 | build: 11 | uses: ./.github/workflows/build.yml 12 | 13 | version: 14 | uses: ./.github/workflows/version.yml 15 | 16 | release: 17 | needs: 18 | - build 19 | - version 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - name: Download a built dist artifact 27 | uses: actions/download-artifact@v4 28 | with: 29 | name: dist 30 | path: dist 31 | 32 | - name: Create Release 33 | id: create_release 34 | uses: actions/create-release@latest 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | with: 38 | tag_name: ${{ needs.version.outputs.VERSION }} 39 | release_name: ${{ needs.version.outputs.VERSION }} 40 | draft: false 41 | prerelease: false 42 | 43 | - name: Generate release zip 44 | run: | 45 | make compress 46 | 47 | - name: Upload Release Asset 48 | id: upload-release-asset 49 | uses: actions/upload-release-asset@v1 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | upload_url: ${{ steps.create_release.outputs.upload_url }} 54 | asset_path: ./dist/Gitako.zip 55 | asset_name: Gitako.zip 56 | asset_content_type: application/zip 57 | -------------------------------------------------------------------------------- /.github/workflows/version.yml: -------------------------------------------------------------------------------- 1 | name: Get VERSION for referencing 2 | on: 3 | workflow_call: 4 | outputs: 5 | VERSION: 6 | description: "The VERSION string" 7 | value: ${{ jobs.version.outputs.VERSION }} 8 | 9 | jobs: 10 | version: 11 | runs-on: ubuntu-latest 12 | 13 | outputs: 14 | VERSION: ${{ steps.get_ref.outputs.VERSION }} 15 | 16 | steps: 17 | - name: Get the ref 18 | id: get_ref 19 | run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .env 3 | node_modules 4 | tmp 5 | dist 6 | dist-firefox 7 | yarn-error.log 8 | /vscode-icons 9 | firefox-profile 10 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn lint-staged --quiet 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *-profile/ 2 | dist/ 3 | vscode-icons/ 4 | Safari 5 | -------------------------------------------------------------------------------- /.sentryclirc: -------------------------------------------------------------------------------- 1 | [defaults] 2 | org = enix 3 | project = gitako 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 EnixCoda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RAW_VERSION?=$(shell node scripts/get-version.js) 2 | FULL_VERSION=v$(RAW_VERSION) 3 | 4 | pull-icons: 5 | git clone https://github.com/vscode-icons/vscode-icons.git vscode-icons --depth=1 6 | 7 | update-icons: 8 | cd vscode-icons && git pull 9 | node scripts/vscode-icons/resolve-languages-map 10 | node scripts/vscode-icons/generate-icon-index 11 | 12 | build: 13 | yarn build 14 | 15 | build-all: 16 | yarn build:all 17 | 18 | test: 19 | yarn test 20 | 21 | upload-for-analytics: 22 | # make sure sentry can retrieve current commit on remote, push both branch and tag 23 | git push 24 | git push --tags 25 | yarn sentry-cli releases new "$(FULL_VERSION)" 26 | yarn sentry-cli releases set-commits "$(FULL_VERSION)" --auto 27 | yarn sentry-cli releases files "$(FULL_VERSION)" upload-sourcemaps dist --no-rewrite 28 | yarn sentry-cli releases finalize "$(FULL_VERSION)" 29 | 30 | compress: 31 | cd dist && zip -r Gitako-$(FULL_VERSION).zip * -x *.map -x *.DS_Store -x *.zip 32 | 33 | compress-firefox: 34 | cd dist-firefox && zip -r Gitako-$(FULL_VERSION)-firefox.zip * -x *.map -x *.DS_Store -x *.zip 35 | 36 | compress-source: 37 | git archive -o dist/Gitako-$(FULL_VERSION)-source.zip HEAD 38 | zip dist/Gitako-$(FULL_VERSION)-source.zip .env 39 | zip -r dist/Gitako-$(FULL_VERSION)-source.zip vscode-icons/icons 40 | 41 | release: 42 | $(MAKE) build-all 43 | $(MAKE) test 44 | $(MAKE) upload-for-analytics 45 | $(MAKE) compress 46 | $(MAKE) compress-firefox 47 | $(MAKE) compress-source 48 | 49 | release-dry-run: 50 | $(MAKE) build-all 51 | $(MAKE) test 52 | $(MAKE) compress 53 | $(MAKE) compress-firefox 54 | $(MAKE) compress-source 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gitako 2 | 3 |
4 |
5 |
17 | Current access token is either invalid or not granted with permissions to access this 18 | project. 19 |
20 | {platform === GitHub && ( 21 |22 | You can grant or request access{' '} 23 | 26 | here 27 | {' '} 28 | if you setup Gitako with OAuth. Or try clear and set token again. 29 |
30 | )} 31 | > 32 | ) : ( 33 |34 | Gitako needs access token to read this project. Please setup access token in the settings 35 | panel below. 36 |
37 | )} 38 |47 | {JSON.stringify($.value, null, 2)} 48 |49 |
{ 5 | (props: PropsWithChildren
, context?: any): ReactElement | undefined
7 | contextTypes?: ValidationMap | undefined
9 | displayName?: string | undefined
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/clippy.scss:
--------------------------------------------------------------------------------
1 | @mixin clippy {
2 | .clippy-wrapper {
3 | position: relative;
4 | width: 0;
5 | height: 0;
6 | top: 8px;
7 | left: calc(100% - 40px);
8 | z-index: 1;
9 |
10 | .clippy {
11 | width: 32px;
12 | height: 32px;
13 | border: 1px solid var(--color-border-default);
14 | border-radius: 4px;
15 |
16 | @include interactive-background;
17 | .icon {
18 | width: 100%;
19 | height: 100%;
20 | display: block;
21 | background-image: url('~@primer/octicons-react/build/svg/copy-16.svg?inline');
22 | background-position: center;
23 | background-repeat: no-repeat;
24 | &.success {
25 | background-image: url('~@primer/octicons-react/build/svg/check-16.svg?inline');
26 | }
27 | &.fail {
28 | background-image: url('~@primer/octicons-react/build/svg/x-16.svg?inline');
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
35 | // TODO: use react to render the button content and set color with CSS variables
36 | @media (prefers-color-scheme: dark) {
37 | :root[data-color-mode='auto'] .markdown-body .clippy .icon {
38 | filter: invert(0.7); // hack to make it looks like a normal color :P
39 | }
40 | }
41 | :root[data-color-mode='dark'] {
42 | .markdown-body .clippy .icon {
43 | filter: invert(0.7); // hack to make it looks like a normal color :P
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/styles/code-folding.scss:
--------------------------------------------------------------------------------
1 | @mixin code-folding {
2 | .blob-wrapper table .blob-num {
3 | position: relative; // for positioning
4 | min-width: 60px;
5 | padding-right: 20px;
6 | }
7 |
8 | // hide code fold handler if not enabled
9 | .gitako-code-fold-handler {
10 | display: none;
11 | }
12 |
13 | .gitako-code-fold-attached:not(.gitako-code-fold-attached-disabled) {
14 | tr {
15 | .gitako-code-fold-handler {
16 | @include hide-for-print();
17 |
18 | display: initial;
19 | position: absolute;
20 | top: 0px;
21 | right: 0px;
22 | width: 20px;
23 | height: 20px;
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 |
28 | &::before {
29 | width: 16px;
30 | height: 20px;
31 | transition: 0.25s ease;
32 | @include pseudo-primer-icon('chevron-down-16');
33 | }
34 |
35 | @include interactive-background-on-before(
36 | var(--color-fg-subtle),
37 | var(--color-fg-default),
38 | var(--color-fg-muted)
39 | );
40 | }
41 |
42 | &.gitako-code-fold-active {
43 | background-color: var(--color-neutral-muted);
44 |
45 | .gitako-code-fold-handler {
46 | &::before {
47 | transform: rotate(-90deg);
48 | }
49 |
50 | @include interactive-background-on-before(
51 | var(--color-fg-muted),
52 | var(--color-fg-default),
53 | var(--color-fg-subtle)
54 | );
55 | }
56 |
57 | .blob-code::after {
58 | color: var(--color-fg-muted);
59 | content: '⋯';
60 | margin: 0.1em 0.2em 0px;
61 | }
62 | }
63 |
64 | // hide folded sections, except for print
65 | &.gitako-code-fold-hidden {
66 | @media screen {
67 | display: none;
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/styles/gitee.scss:
--------------------------------------------------------------------------------
1 | $gitee-header-z-index: 1002;
2 |
3 | @mixin gitee($name) {
4 | [data-gitako-platform='Gitee'] {
5 | &[data-#{$name}-ready='true'] {
6 | // reset styles
7 | .#{$name}-side-bar {
8 | input[type='text'],
9 | input[type='password'],
10 | .ui-autocomplete-input,
11 | textarea,
12 | .uneditable-input {
13 | padding: initial;
14 | border: none;
15 | }
16 | }
17 |
18 | padding-top: 0;
19 |
20 | .#{$name}-side-bar {
21 | h1,
22 | h2,
23 | h3,
24 | h4,
25 | h5,
26 | h6 {
27 | margin: 0;
28 | }
29 | }
30 | }
31 |
32 | &[data-with-gitako-spacing='left'] {
33 | body {
34 | width: auto; // shrink width
35 | .site-content {
36 | min-width: 1040px;
37 | }
38 | }
39 |
40 | &[data-with-gitako-spacing='right'] {
41 | body {
42 | min-width: calc(1040px + var(--gitako-width));
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/styles/github.scss:
--------------------------------------------------------------------------------
1 | @mixin github($github-content-width) {
2 | @media (min-width: $github-content-width) {
3 | [data-gitako-platform='GitHub'] {
4 | body {
5 | min-width: $github-content-width;
6 | }
7 |
8 | &[data-with-gitako-spacing='left'],
9 | &[data-with-gitako-spacing='right'] {
10 | #files-bucket,
11 | .files-bucket {
12 | & > .position-relative.px-4 {
13 | width: unset !important;
14 | left: unset !important;
15 | right: unset !important;
16 | margin-left: unset !important;
17 | margin-right: unset !important;
18 | }
19 | }
20 | }
21 |
22 | &[data-with-gitako-spacing='right'] {
23 | body {
24 | min-width: auto; // it will break layout when too narrow, but at least it makes everything visible
25 |
26 | .container-xl {
27 | margin-left: max(
28 | 0,
29 | calc((100vw - 1280px - var(--gitako-width)) / 2)
30 | ); // override margin-left: auto;
31 | margin-right: max(
32 | 0,
33 | calc((100vw - 1280px - var(--gitako-width)) / 2)
34 | ); // override margin-right: auto;
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/keyframes.scss:
--------------------------------------------------------------------------------
1 | @keyframes rotate {
2 | from {
3 | transform: rotateZ(0);
4 | }
5 | to {
6 | transform: rotateZ(360deg);
7 | }
8 | }
9 |
10 | @keyframes pulse-rotate {
11 | 0% {
12 | transform: rotateZ(0);
13 | }
14 | 20% {
15 | transform: rotateZ(190deg);
16 | }
17 | 30% {
18 | transform: rotateZ(175deg);
19 | }
20 | 50% {
21 | transform: rotateZ(180deg);
22 | }
23 | 70% {
24 | transform: rotateZ(370deg);
25 | }
26 | 80% {
27 | transform: rotateZ(355deg);
28 | }
29 | 100% {
30 | transform: rotateZ(360deg);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/styles/layout.scss:
--------------------------------------------------------------------------------
1 | @mixin flex($justify: center, $align: center, $inline: true) {
2 | @if $inline {
3 | display: inline-flex;
4 | } @else {
5 | display: flex;
6 | }
7 |
8 | @if $justify {
9 | justify-content: $justify;
10 | }
11 | @if $align {
12 | align-items: $align;
13 | }
14 | }
15 |
16 | @mixin flex-center {
17 | @include flex();
18 | }
19 |
--------------------------------------------------------------------------------
/src/styles/primer-like.scss:
--------------------------------------------------------------------------------
1 | // primer-like styles
2 | @import './themes.scss';
3 |
4 | @mixin interactive-frame() {
5 | @include interactive-border;
6 | @include interactive-background;
7 | }
8 |
9 | @mixin interactive-border(
10 | $default: var(--color-btn-border),
11 | $hover: var(--color-btn-hover-border),
12 | $active: var(--color-btn-active-border),
13 | $focus: $hover
14 | ) {
15 | border: 1px solid $default;
16 | &:hover {
17 | border: 1px solid $hover;
18 | }
19 | &:focus {
20 | border: 1px solid $focus;
21 | }
22 | &:active {
23 | border: 1px solid $active;
24 | }
25 | }
26 |
27 | @mixin interactive-background(
28 | $default: var(--color-btn-bg),
29 | $hover: var(--color-btn-hover-bg),
30 | $active: var(--color-btn-active-bg),
31 | $focus: $hover
32 | ) {
33 | background-color: $default;
34 | &:hover {
35 | background-color: $hover;
36 | }
37 | &:focus {
38 | background-color: $focus;
39 | }
40 | &:active {
41 | background-color: $active;
42 | }
43 | }
44 |
45 | @mixin interactive-background-on-before($default, $hover, $active, $focus: $hover) {
46 | &::before {
47 | background-color: $default;
48 | }
49 | &:hover {
50 | &::before {
51 | background-color: $hover;
52 | }
53 | }
54 | &:active {
55 | &::before {
56 | background-color: $active;
57 | }
58 | }
59 | &:hover {
60 | &::before {
61 | background-color: $hover;
62 | }
63 | }
64 | }
65 |
66 | @mixin pseudo-primer-icon($icon-name) {
67 | content: '';
68 | display: block;
69 | cursor: pointer;
70 | user-select: none;
71 | -webkit-mask-image: url('~@primer/octicons-react/build/svg/' + $icon-name + '.svg?inline');
72 | mask-image: url('~@primer/octicons-react/build/svg/' + $icon-name + '.svg?inline');
73 | -webkit-mask-size: contain;
74 | mask-size: contain;
75 | -webkit-mask-position: center;
76 | mask-position: center;
77 | }
78 |
79 | @mixin icon-button(
80 | $default: transparent,
81 | $hover: var(--color-btn-hover-bg),
82 | $active: var(--color-btn-focus-bg),
83 | $focus: $hover
84 | ) {
85 | @include flex-center();
86 | cursor: pointer;
87 | padding: 0;
88 | border: none;
89 |
90 | @include interactive-background($default, $hover, $active, $focus);
91 | }
92 |
--------------------------------------------------------------------------------
/src/utils/$.ts:
--------------------------------------------------------------------------------
1 | export function $ >(props: P) {
5 | const lastPropsRef = useRef (props)
6 | useEffect(() => {
7 | if (IN_PRODUCTION_MODE) return
8 | const output: ([string, keyof P, P[keyof P]] | [string, keyof P, P[keyof P], P[keyof P]])[] = []
9 | for (const key of Object.keys(props)) {
10 | if (key === 'children') continue
11 | const $key = key as keyof P
12 | if (!(key in lastPropsRef.current)) output.push([`[Added]`, $key, props[$key]])
13 | if (lastPropsRef.current[$key] !== props[$key])
14 | output.push([`[Updated]`, $key, lastPropsRef.current[$key], props[$key]])
15 | }
16 |
17 | for (const key of Object.keys(lastPropsRef.current)) {
18 | if (key === 'children') continue
19 | const $key = key as keyof P
20 | if (!(key in props)) output.push([`[Removed]`, $key, props[$key]])
21 | }
22 |
23 | if (output.length) {
24 | console.group(`[Update Reasons]`)
25 | for (const record of output) {
26 | console.log(...record.map(r => (typeof r === 'function' ? '[fn]' : r)))
27 | }
28 | console.groupEnd()
29 | }
30 |
31 | lastPropsRef.current = props
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/keyHelper.ts:
--------------------------------------------------------------------------------
1 | const keyCodeArray = [
2 | ...'1234567890abcdefghijklmnopqrstuvwxyz'.split(''),
3 | /* the key that's located left to the 1 key in Mac
4 | keybords in multiple Engligh laybouts (Backquote) */
5 | ...'`§'.split(''),
6 | ..."[]\\;',./".split(''),
7 | 'alt',
8 | 'shift',
9 | 'ctrl',
10 | 'meta',
11 | ]
12 | const validKeyCodes = new Set(keyCodeArray)
13 |
14 | function isValidKey(key: string) {
15 | return validKeyCodes.has(key)
16 | }
17 |
18 | /**
19 | * parse a string representation of key combination
20 | *
21 | * @param {string} keysString
22 | * @returns {string}
23 | */
24 | function parse(keysString: string) {
25 | return (
26 | keysString
27 | .split('+')
28 | /* when trying to set a combination includes '+',
29 | input should be 'shift + =' instead of 'shift + +',
30 | thus a valid key string won't contain '++' */
31 | .map(_ => _.trim().toLowerCase())
32 | .filter(isValidKey)
33 | .sort((a, b) => keyCodeArray.indexOf(b) - keyCodeArray.indexOf(a))
34 | .join('+')
35 | )
36 | }
37 |
38 | function parseKeyCode(code: string) {
39 | return code.toLowerCase().replace(/^control$/, 'ctrl')
40 | }
41 |
42 | export function parseEvent(e: KeyboardEvent | React.KeyboardEvent) {
43 | const { altKey: alt, shiftKey: shift, metaKey: meta, ctrlKey: ctrl } = e
44 | try {
45 | const code = parseKeyCode(e.key)
46 | const keys = { meta, ctrl, shift, alt, [code]: true }
47 | const combination = parse(
48 | Object.entries(keys)
49 | .filter(([, pressed]) => pressed)
50 | .map(([key]) => key)
51 | .join('+'),
52 | )
53 | return combination
54 | } catch (err) {
55 | const serializedKeyData = JSON.stringify({
56 | keyCode: e.keyCode,
57 | key: e.key,
58 | charCode: e.charCode,
59 | })
60 | throw new Error(`Error parse keyboard event: ${serializedKeyData}`)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/utils/networkService.ts:
--------------------------------------------------------------------------------
1 | export const gitakoServiceHost = 'gitako.enix.one'
2 |
--------------------------------------------------------------------------------
/src/utils/parseIconMapCSV.ts:
--------------------------------------------------------------------------------
1 | import rawFileIconIndex from 'assets/icons/file-icons-index.csv'
2 | import rawFolderIconIndex from 'assets/icons/folder-icons-index.csv'
3 |
4 | const rowSeparator = '\n'
5 | const colSeparator = ','
6 | const arraySeparator = ':'
7 |
8 | function parseFileIconMapCSV() {
9 | const filenameIndex = new Map(initialState: S | (() => S)): {
4 | value: S
5 | onChange: React.Dispatch