mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
Merge branch 'develop' into manifest-v3
This commit is contained in:
commit
dba3c15e72
56 changed files with 746 additions and 511 deletions
|
|
@ -1,2 +0,0 @@
|
|||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
PUPPETEER_EXEC_PATH=/opt/homebrew/bin/chromium
|
||||
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
|
|
@ -11,10 +11,9 @@ jobs:
|
|||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache deps
|
||||
uses: actions/cache@v1
|
||||
- uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -26,7 +25,7 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
||||
run: |
|
||||
yarn
|
||||
yarn --ignore-platform --ignore-engines --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Retrieve vscode icons
|
||||
uses: actions/checkout@v3
|
||||
|
|
@ -36,7 +35,7 @@ jobs:
|
|||
|
||||
- name: Build
|
||||
run: |
|
||||
make build
|
||||
NODE_OPTIONS=--openssl-legacy-provider make build
|
||||
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
|
|
|
|||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
|
|
@ -13,7 +13,7 @@ on:
|
|||
- 'server/**'
|
||||
# Runs everyday to detect GitHub update in time
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: '30 16 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
||||
run: |
|
||||
yarn
|
||||
yarn --ignore-platform --ignore-engines --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: E2E Test
|
||||
uses: mujo-code/puppeteer-headful@master
|
||||
|
|
@ -89,7 +89,7 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
||||
run: |
|
||||
yarn
|
||||
yarn --ignore-platform --ignore-engines --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Unit Test
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ Gitako is **FREE**. If you like it, please
|
|||
|
||||
[Feature discussions](https://github.com/EnixCoda/Gitako/discussions) and [bug reports](https://github.com/EnixCoda/Gitako/issues/) are also welcome!
|
||||
|
||||
Check out [contributing.md](./contributing.md) if you want to contribute to Gitako directly.
|
||||
|
||||
### About
|
||||
|
||||
#### Source of the name and logo?
|
||||
|
|
|
|||
|
|
@ -579,7 +579,7 @@
|
|||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 3.10.0;
|
||||
MARKETING_VERSION = 3.12.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = enixcoda.Gitako.Extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -604,7 +604,7 @@
|
|||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 3.10.0;
|
||||
MARKETING_VERSION = 3.12.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = enixcoda.Gitako.Extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -631,7 +631,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 3.10.0;
|
||||
MARKETING_VERSION = 3.12.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = enixcoda.Gitako;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Developer Sign for Distribution";
|
||||
|
|
@ -657,7 +657,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 3.10.0;
|
||||
MARKETING_VERSION = 3.12.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = enixcoda.Gitako;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Developer Sign for Distribution";
|
||||
|
|
|
|||
15
__tests__/cases/non-parallel/empty-project.ts
Normal file
15
__tests__/cases/non-parallel/empty-project.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import { getTextContent, sleep } from '../../utils'
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() => page.goto(testURL`https://github.com/GitakoExtension/test-empty`))
|
||||
|
||||
it('should render error message', async () => {
|
||||
await sleep(5000)
|
||||
|
||||
expect(await getTextContent(selectors.gitako.errorMessage)).toBe(
|
||||
'This project seems to be empty.',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import { expectToFind, expectToNotFind, sleep, waitForRedirect } from '../../utils'
|
||||
|
||||
jest.retryTimes(3)
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() => page.goto('https://github.com/EnixCoda/Gitako/commits/develop'))
|
||||
beforeAll(() => page.goto(testURL`https://github.com/EnixCoda/Gitako/commits/develop`))
|
||||
|
||||
it('should not break go back in history', async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const commitLinks = await page.$$(
|
||||
`main .TimelineItem-body ol li > div:nth-child(1) a[href*="/commit/"]`,
|
||||
)
|
||||
const commitLinks = await page.$$(selectors.github.commitLinks)
|
||||
if (commitLinks.length < 2) throw new Error(`No enough commits`)
|
||||
commitLinks[i].click()
|
||||
await waitForRedirect()
|
||||
await expectToFind('div.commit')
|
||||
await expectToFind(selectors.github.commitSummary)
|
||||
await sleep(1000)
|
||||
|
||||
page.goBack()
|
||||
await sleep(1000)
|
||||
// The selector for file content
|
||||
await expectToNotFind('div.commit')
|
||||
await expectToNotFind(selectors.github.commitSummary)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import { expectToFind, expectToNotFind, sleep, waitForRedirect } from '../../utils'
|
||||
|
||||
jest.retryTimes(3)
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() => page.goto('https://github.com/EnixCoda/Gitako/tree/develop/src'))
|
||||
beforeAll(() => page.goto(testURL`https://github.com/EnixCoda/Gitako/tree/develop/src`))
|
||||
|
||||
it('should not break go back in history', async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const commitLinks = await page.$$(
|
||||
`.js-details-container div[role="row"] div[role="rowheader"] a[title*="."]`,
|
||||
)
|
||||
if (commitLinks.length < 2) throw new Error(`No enough files`)
|
||||
const fileItems = await page.$$(selectors.github.fileListItemFileLinks)
|
||||
if (fileItems.length < 2) throw new Error(`No enough files`)
|
||||
|
||||
await waitForRedirect(async () => {
|
||||
await commitLinks[i].click()
|
||||
await fileItems[i].click()
|
||||
})
|
||||
await expectToFind('table.js-file-line-container')
|
||||
await expectToFind(selectors.github.fileContent)
|
||||
await sleep(1000)
|
||||
|
||||
page.goBack()
|
||||
await sleep(1000)
|
||||
// The selector for file content
|
||||
await expectToNotFind('table.js-file-line-container')
|
||||
|
||||
await expectToNotFind(selectors.github.fileContent)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import {
|
||||
collapseFloatModeSidebar,
|
||||
expandFloatModeSidebar,
|
||||
getTextContent,
|
||||
patientClick,
|
||||
selectFileTreeItem,
|
||||
sleep,
|
||||
waitForRedirect,
|
||||
} from '../../utils'
|
||||
|
|
@ -10,20 +12,20 @@ import {
|
|||
jest.retryTimes(3)
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() => page.goto('https://github.com/EnixCoda/Gitako/tree/develop/src'))
|
||||
beforeAll(() => page.goto(testURL`https://github.com/EnixCoda/Gitako/tree/develop/src`))
|
||||
|
||||
it('should work with PJAX', async () => {
|
||||
await sleep(3000)
|
||||
|
||||
await expandFloatModeSidebar()
|
||||
await patientClick(selectFileTreeItem('src/analytics.ts'))
|
||||
await patientClick(selectors.gitako.fileItemOf('src/analytics.ts'))
|
||||
await waitForRedirect()
|
||||
await collapseFloatModeSidebar()
|
||||
|
||||
await page.click('a[data-selected-links^="repo_issues "]')
|
||||
await page.click(selectors.github.navBarItemIssues)
|
||||
await waitForRedirect()
|
||||
|
||||
await page.click('a[data-selected-links^="repo_pulls "]')
|
||||
await page.click(selectors.github.navBarItemPulls)
|
||||
await waitForRedirect()
|
||||
|
||||
page.goBack()
|
||||
|
|
@ -32,10 +34,6 @@ describe(`in Gitako project page`, () => {
|
|||
page.goBack()
|
||||
await sleep(1000)
|
||||
|
||||
expect(
|
||||
await page.evaluate(
|
||||
() => document.querySelector('.final-path')?.textContent === 'analytics.ts',
|
||||
),
|
||||
).toBe(true)
|
||||
expect(await getTextContent(selectors.github.breadcrumbFileName)).toBe('/analytics.ts')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import {
|
||||
expandFloatModeSidebar,
|
||||
expectToFind,
|
||||
expectToNotFind,
|
||||
patientClick,
|
||||
selectFileTreeItem,
|
||||
sleep,
|
||||
waitForRedirect,
|
||||
} from '../../utils'
|
||||
|
|
@ -11,17 +12,16 @@ import {
|
|||
jest.retryTimes(3)
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() => page.goto('https://github.com/EnixCoda/Gitako/tree/test/multiple-changes'))
|
||||
beforeAll(() => page.goto(testURL`https://github.com/EnixCoda/Gitako/tree/test/multiple-changes`))
|
||||
|
||||
it('should work with PJAX', async () => {
|
||||
await sleep(3000)
|
||||
|
||||
await expandFloatModeSidebar()
|
||||
await patientClick(selectFileTreeItem('.babelrc'))
|
||||
await patientClick(selectors.gitako.fileItemOf('.babelrc'))
|
||||
await waitForRedirect()
|
||||
|
||||
// The selector for file content
|
||||
await expectToFind('table.js-file-line-container')
|
||||
await expectToFind(selectors.github.fileContent)
|
||||
|
||||
await waitForRedirect(async () => {
|
||||
await sleep(1000) // This prevents failing in some cases due to some mystery scheduling issue of puppeteer or jest
|
||||
|
|
@ -29,6 +29,6 @@ describe(`in Gitako project page`, () => {
|
|||
})
|
||||
|
||||
// The selector for file content
|
||||
await expectToNotFind('table.js-file-line-container')
|
||||
await expectToNotFind(selectors.github.fileContent)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,39 +1,37 @@
|
|||
import {
|
||||
expandFloatModeSidebar,
|
||||
expectToFind,
|
||||
expectToNotFind,
|
||||
scroll,
|
||||
selectFileTreeItem,
|
||||
} from '../../utils'
|
||||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import { expandFloatModeSidebar, expectToFind, expectToNotFind, scroll } from '../../utils'
|
||||
|
||||
jest.retryTimes(3)
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() =>
|
||||
page.goto('https://github.com/EnixCoda/Gitako/tree/test/200-changed-files-200-lines-each'),
|
||||
page.goto(
|
||||
testURL`https://github.com/EnixCoda/Gitako/tree/test/200-changed-files-200-lines-each`,
|
||||
),
|
||||
)
|
||||
|
||||
it('should render Gitako', async () => {
|
||||
await expectToFind('.gitako-side-bar .gitako-side-bar-body-wrapper')
|
||||
await expectToFind(selectors.gitako.bodyWrapper)
|
||||
})
|
||||
|
||||
it('should render file list', async () => {
|
||||
await expectToFind('.gitako-side-bar .files .node-item')
|
||||
await expectToFind(selectors.gitako.fileItem)
|
||||
})
|
||||
|
||||
it('should render while scroll', async () => {
|
||||
await expandFloatModeSidebar()
|
||||
|
||||
const filesEle = await page.waitForSelector('.gitako-side-bar .files')
|
||||
const filesEle = await page.waitForSelector(selectors.gitako.files)
|
||||
// node of tsconfig.json should NOT be rendered before scroll down
|
||||
await expectToNotFind(selectFileTreeItem('tsconfig.json'))
|
||||
await expectToNotFind(selectors.gitako.fileItemOf('tsconfig.json'))
|
||||
const box = await filesEle?.boundingBox()
|
||||
if (box) {
|
||||
await page.mouse.move(box.x + 40, box.y + 40)
|
||||
await scroll({ totalDistance: 10000, stepDistance: 100 })
|
||||
|
||||
// node of tsconfig.json should be rendered now
|
||||
await expectToFind(selectFileTreeItem('tsconfig.json'))
|
||||
await expectToFind(selectors.gitako.fileItemOf('tsconfig.json'))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
* Confirm basic behaviors of puppeteer assertions
|
||||
*/
|
||||
|
||||
import { testURL } from '../../testURL'
|
||||
import { expectToFind, expectToNotFind } from '../../utils'
|
||||
|
||||
describe(`in random page`, () => {
|
||||
beforeAll(() => page.goto('https://google.com'))
|
||||
beforeAll(() => page.goto(testURL`https://google.com`))
|
||||
|
||||
it('wait for hidden non-exist element should resolve null', async () => {
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import { expectToFind, selectFileTreeItem, sleep, waitForRedirect } from '../../utils'
|
||||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import { expectToFind, sleep, waitForRedirect } from '../../utils'
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() => page.goto('https://github.com/EnixCoda/Gitako/tree/develop/src'))
|
||||
beforeAll(() => page.goto(testURL`https://github.com/EnixCoda/Gitako/tree/develop/src`))
|
||||
|
||||
it('expand to target on load and after redirect', async () => {
|
||||
await sleep(3000)
|
||||
|
||||
// Expect Gitako sidebar to have expanded src to see contents
|
||||
await expectToFind(selectFileTreeItem('src/components'))
|
||||
await expectToFind(selectors.gitako.fileItemOf('src/components'))
|
||||
|
||||
await page.click(
|
||||
`.js-details-container div[role="row"] div[role="rowheader"] [title="components"]`,
|
||||
)
|
||||
await page.click(selectors.github.fileListItemLinkOf('components'))
|
||||
await waitForRedirect()
|
||||
|
||||
// Expect Gitako sidebar to have expanded components and see contents
|
||||
await expectToFind(selectFileTreeItem('src/components/Gitako.tsx'))
|
||||
await expectToFind(selectors.gitako.fileItemOf('src/components/Gitako.tsx'))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { testURL } from '../../testURL'
|
||||
import { expectToNotFind } from '../../utils'
|
||||
|
||||
describe(`in GitHub homepage`, () => {
|
||||
beforeAll(() => page.goto('https://github.com'))
|
||||
beforeAll(() => page.goto(testURL`https://github.com`))
|
||||
|
||||
it('should not render Gitako', async () => {
|
||||
await expectToNotFind('.gitako-side-bar .gitako-side-bar-body-wrapper')
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { selectors } from '../../selectors'
|
||||
import { testURL } from '../../testURL'
|
||||
import { expectToFind } from '../../utils'
|
||||
|
||||
describe(`in Gitako project page`, () => {
|
||||
beforeAll(() => page.goto('https://github.com/EnixCoda/Gitako/pull/71'))
|
||||
beforeAll(() => page.goto(testURL`https://github.com/EnixCoda/Gitako/pull/71`))
|
||||
|
||||
it('should render Gitako', async () => {
|
||||
await expectToFind('.gitako-side-bar .gitako-side-bar-body-wrapper')
|
||||
await expectToFind(selectors.gitako.bodyWrapper)
|
||||
})
|
||||
|
||||
it('should render file list', async () => {
|
||||
await expectToFind('.gitako-side-bar .files .node-item')
|
||||
await expectToFind(selectors.gitako.fileItem)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
const baseConfig = require('./jest.config')
|
||||
|
||||
/**
|
||||
* @type {import('@jest/types').Config.InitialOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
maxWorkers: 1,
|
||||
testMatch: [...baseConfig.testMatch, '**/__tests__/cases/non-parallel/*.ts?(x)'],
|
||||
setupFilesAfterEnv: ['<rootDir>/setup.ts'],
|
||||
}
|
||||
|
|
|
|||
21
__tests__/selectors.ts
Normal file
21
__tests__/selectors.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export const selectors = {
|
||||
github: {
|
||||
breadcrumbFileName: `[data-testid="breadcrumbs-filename"]`,
|
||||
fileContent: 'textarea[aria-label="file content"]',
|
||||
commitLinks: `li[data-testid="commit-row-item"] [data-testid="listview-item-title-container"] a[href*="/commit/"]`,
|
||||
// assume title contains `.` is file item
|
||||
fileListItemFileLinks: `table[aria-labelledby="folders-and-files"] tr.react-directory-row td.react-directory-row-name-cell-large-screen .react-directory-filename-column .react-directory-truncate a[aria-label$="(File)"]`,
|
||||
fileListItemLinkOf: (name: string) =>
|
||||
`table[aria-labelledby="folders-and-files"] tr.react-directory-row td.react-directory-row-name-cell-large-screen .react-directory-filename-column .react-directory-truncate a[title="${name}"]`,
|
||||
commitSummary: 'div.commit',
|
||||
navBarItemIssues: 'a[data-selected-links^="repo_issues "]',
|
||||
navBarItemPulls: 'a[data-selected-links^="repo_pulls "]',
|
||||
},
|
||||
gitako: {
|
||||
fileItem: '.gitako-side-bar .files .node-item',
|
||||
fileItemOf: (path: string) => `.gitako-side-bar .files .node-item[title="${path}"]`,
|
||||
errorMessage: '#gitako-logo-mount-point .error-message',
|
||||
files: '.gitako-side-bar .files',
|
||||
bodyWrapper: '.gitako-side-bar .gitako-side-bar-body-wrapper',
|
||||
},
|
||||
}
|
||||
1
__tests__/setup.ts
Normal file
1
__tests__/setup.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
jest.retryTimes(3)
|
||||
11
__tests__/testURL.ts
Normal file
11
__tests__/testURL.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// string template function, take input URL string and add a search param
|
||||
// example: url`http://g.com` => `http://g.com?k1="json-value"`
|
||||
export function testURL(strings: TemplateStringsArray, ...values: unknown[]) {
|
||||
const raw = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '')
|
||||
const GITAKO_ACCESS_TOKEN = process.env.GITAKO_ACCESS_TOKEN
|
||||
if (!GITAKO_ACCESS_TOKEN) return raw
|
||||
|
||||
const url = new URL(raw, 'http://localhost')
|
||||
url.searchParams.set('gitako-config-accessToken', JSON.stringify(GITAKO_ACCESS_TOKEN))
|
||||
return url.href
|
||||
}
|
||||
|
|
@ -87,11 +87,11 @@ export async function waitForRedirect(action?: () => void | Promise<void>) {
|
|||
fired = true
|
||||
return action()
|
||||
})
|
||||
return Promise.race([waitForLegacyPJAXRedirect($action), waitForTurboRedirect($action)])
|
||||
}
|
||||
|
||||
export function selectFileTreeItem(path: string): string {
|
||||
return `.gitako-side-bar .files a[title="${path}"]`
|
||||
return Promise.race([
|
||||
waitForLegacyPJAXRedirect($action),
|
||||
waitForTurboRedirect($action),
|
||||
sleep(3 * 1000),
|
||||
])
|
||||
}
|
||||
|
||||
export async function patientClick(selector: string) {
|
||||
|
|
@ -119,3 +119,7 @@ export async function collapseFloatModeSidebar() {
|
|||
})
|
||||
await sleep(500)
|
||||
}
|
||||
|
||||
export function getTextContent(query: string) {
|
||||
return page.evaluate(query => document.querySelector(query)?.textContent, query)
|
||||
}
|
||||
|
|
|
|||
44
contributing.md
Normal file
44
contributing.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Contributing
|
||||
|
||||
Thank you if you are trying to contribute!
|
||||
|
||||
Note: if you were using Windows, you may need to find alternatives for `make` commands. Or use WSL. I've not tested development on Windows and do not guarantee if it would work.
|
||||
|
||||
## Set up development env
|
||||
|
||||
1. Clone the repo
|
||||
1. Run `make pull-icons` to install dependencies
|
||||
1. Run `yarn` to install dependencies
|
||||
1. Run `yarn dev` to start the development server, you'll see a `dist` folder appear in the root of this project
|
||||
1. Open the extensions page in Chrome, enable developer mode, and load the extension from the `dist` folder
|
||||
1. Navigate to repository of your choice and you should see the extension appear
|
||||
|
||||
When you modify source code, you need to do either of below to apply your changes:
|
||||
|
||||
- (recommended) use [the Extension Reloader extension](https://chrome.google.com/webstore/detail/fimgfedafeadlieiabdeeaodndnlbhid). It could reload all extensions then refresh the page (you need to enable it in its settings).
|
||||
- manually reload the extension in the `chrome://extensions` and then refresh your repository page
|
||||
|
||||
## Develop with more browsers
|
||||
|
||||
Gitako supports more browsers, in order to develop for them, please do the followings.
|
||||
|
||||
### Edge:
|
||||
|
||||
- Similar to above steps for Chrome
|
||||
|
||||
### Firefox:
|
||||
|
||||
- run `yarn dev-firefox`
|
||||
- a new instance of Firefox will open with Gitako automatically installed
|
||||
- navigate to a GitHub repo and you should see the extension appear
|
||||
- when you modify source, refresh the tab
|
||||
|
||||
### Safari (macOS only):
|
||||
|
||||
- run `yarn dev-safari`
|
||||
- Open `Safari/Gitako/Gitako.xcodeproj` in Xcode
|
||||
- Click the "Run" button
|
||||
- Enable developer mode in Safari's preferences
|
||||
- Enable Gitako in Safari's preferences
|
||||
- Open a Safari tab and visit a GitHub repo, then activate Gitako via Gitako icon next to the address bar
|
||||
- when you modify source, click the "Run" button in Xcode and refresh the tab
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
const path = require('path')
|
||||
if (process.arch === 'arm64' && process.platform === 'darwin') {
|
||||
require('dotenv').config({
|
||||
path: '.env.arm.mac',
|
||||
})
|
||||
require('dotenv').config()
|
||||
}
|
||||
|
||||
const CRX_PATH = path.resolve(__dirname, 'dist')
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gitako",
|
||||
"version": "3.10.0",
|
||||
"version": "3.12.0",
|
||||
"description": "File tree for GitHub, and more than that.",
|
||||
"repository": "https://github.com/EnixCoda/Gitako",
|
||||
"author": "EnixCoda",
|
||||
|
|
@ -9,13 +9,13 @@
|
|||
"homepage": "https://github.com/EnixCoda/Gitako",
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"dev": "VERSION=dev-v$(node scripts/get-version.js) webpack --watch",
|
||||
"dev": "NODE_OPTIONS=--openssl-legacy-provider VERSION=dev-v$(node scripts/get-version.js) webpack --watch",
|
||||
"dev-safari": "TARGET=safari yarn run dev",
|
||||
"debug-firefox": "web-ext run --source-dir=dist --firefox-profile=firefox-profile --profile-create-if-missing --keep-profile-changes --start-url https://github.com/EnixCoda/Gitako",
|
||||
"analyze-bundle": "ANALYZE= NODE_ENV=production webpack",
|
||||
"dev-firefox": "web-ext run --source-dir=dist --firefox-profile=firefox-profile --profile-create-if-missing --keep-profile-changes --start-url https://github.com/EnixCoda/Gitako",
|
||||
"analyze-bundle": "NODE_OPTIONS=--openssl-legacy-provider ANALYZE= NODE_ENV=production webpack",
|
||||
"postinstall": "node scripts/fix-deps",
|
||||
"postversion": "sh scripts/post-version.sh",
|
||||
"build": "VERSION=v$(node scripts/get-version.js) NODE_ENV=production webpack",
|
||||
"build": "NODE_OPTIONS=--openssl-legacy-provider VERSION=v$(node scripts/get-version.js) NODE_ENV=production webpack",
|
||||
"roll": "make release",
|
||||
"test": "yarn run test:parallel && yarn run test:non-parallel",
|
||||
"test:unit": "NODE_ENV=test jest --config jest.config.js",
|
||||
|
|
@ -46,6 +46,7 @@
|
|||
"react-use": "^17.3.2",
|
||||
"react-window": "^1.8.7",
|
||||
"styled-components": "^5.3.5",
|
||||
"superstruct": "^1.0.3",
|
||||
"webext-domain-permission-toggle": "^3.0.0",
|
||||
"webext-dynamic-content-scripts": "^9.0.0",
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
|
|
@ -84,7 +85,7 @@
|
|||
"json-loader": "^0.5.7",
|
||||
"lint-staged": "^13.0.3",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.3",
|
||||
"puppeteer": "^10.1.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
"sass": "^1.26.2",
|
||||
|
|
@ -114,6 +115,7 @@
|
|||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"fsevents": "^2.3.3",
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.3"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ export function useRenderGoToButton(searched: boolean, goTo: (path: string[]) =>
|
|||
? function renderGoToButton(node: TreeNode): React.ReactNode {
|
||||
return (
|
||||
<button
|
||||
title={'Reveal in file tree'}
|
||||
title={'Reveal in file tree (⏎)'}
|
||||
className={'go-to-button'}
|
||||
onClick={() => goTo(node.path.split('/'))}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { SearchBar } from 'components/SearchBar'
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { PortalContext } from 'containers/PortalContext'
|
||||
import { RepoContext } from 'containers/RepoContext'
|
||||
import { platform } from 'platforms'
|
||||
import { useInspector } from 'containers/Inspector'
|
||||
import * as React from 'react'
|
||||
import { usePrevious, useUpdateEffect } from 'react-use'
|
||||
import { cx } from 'utils/cx'
|
||||
|
|
@ -171,13 +171,17 @@ function LoadedFileExplorer({
|
|||
const renderLabelText = useRenderLabelText(searchKey)
|
||||
|
||||
const goToCurrentItem = React.useCallback(() => {
|
||||
const targetPath = platform.getCurrentPath(metaData.branchName)
|
||||
const targetPath = getCurrentPath()
|
||||
if (targetPath) expandTo(targetPath)
|
||||
}, [metaData.branchName, expandTo])
|
||||
}, [getCurrentPath, expandTo])
|
||||
|
||||
useOnLocationChange(goToCurrentItem)
|
||||
useAfterRedirect(goToCurrentItem)
|
||||
|
||||
const [currentPath, setCurrentPath] = React.useState(() => getCurrentPath())
|
||||
useAfterRedirect(React.useCallback(() => setCurrentPath(getCurrentPath()), [getCurrentPath]))
|
||||
useInspector('CurrentPath', currentPath)
|
||||
|
||||
const ref = React.useRef<HTMLDivElement | null>(null)
|
||||
useFocusOnPendingTarget(
|
||||
'files',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { GearIcon } from '@primer/octicons-react'
|
||||
import { GearIcon, SyncIcon } from '@primer/octicons-react'
|
||||
import { Link } from '@primer/react'
|
||||
import { ReloadContext } from 'containers/ReloadContext'
|
||||
import { VERSION } from 'env'
|
||||
import * as React from 'react'
|
||||
import { RoundIconButton } from './RoundIconButton'
|
||||
|
|
@ -11,23 +12,42 @@ type Props = {
|
|||
|
||||
export function Footer(props: Props) {
|
||||
const { toggleShowSettings } = props
|
||||
const reload = React.useContext(ReloadContext)
|
||||
return (
|
||||
<div className={'gitako-footer'}>
|
||||
<Link
|
||||
className={'version'}
|
||||
href={wikiLinks.changeLog}
|
||||
title={'Check out new features!'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{VERSION}
|
||||
</Link>
|
||||
<RoundIconButton
|
||||
aria-label={'settings'}
|
||||
icon={GearIcon}
|
||||
iconColor="fg.muted"
|
||||
onClick={toggleShowSettings}
|
||||
/>
|
||||
<div className="gitako-footer-section">
|
||||
<Link
|
||||
className={'version'}
|
||||
href={wikiLinks.changeLog}
|
||||
title={'Gitako changelog'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{VERSION}
|
||||
</Link>
|
||||
<Link
|
||||
title="About to say good bye."
|
||||
href={wikiLinks.bye}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
👋
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<RoundIconButton
|
||||
aria-label={'Reload'}
|
||||
icon={SyncIcon}
|
||||
iconColor="fg.muted"
|
||||
onClick={() => reload()}
|
||||
/>
|
||||
<RoundIconButton
|
||||
aria-label={'Settings'}
|
||||
icon={GearIcon}
|
||||
iconColor="fg.muted"
|
||||
onClick={toggleShowSettings}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { SideBar } from 'components/SideBar'
|
||||
import { ConfigsContextWrapper } from 'containers/ConfigsContext'
|
||||
import { InspectorContextWrapper } from 'containers/Inspector'
|
||||
import { ReloadContextWrapper } from 'containers/ReloadContext'
|
||||
import { InspectorContextWrapper } from 'containers/StateInspector'
|
||||
import * as React from 'react'
|
||||
import { StyleSheetManager } from 'styled-components'
|
||||
import { insertMountPoint } from 'utils/DOMHelper'
|
||||
|
|
@ -12,25 +12,26 @@ import { RepoContextWrapper } from '../containers/RepoContext'
|
|||
import { StateBarStateContextWrapper } from '../containers/SideBarState'
|
||||
|
||||
export function Gitako() {
|
||||
const mountPoint = React.useMemo(() => insertMountPoint(), [])
|
||||
return (
|
||||
<InspectorContextWrapper>
|
||||
<StyleSheetManager target={insertMountPoint()}>
|
||||
<ReloadContextWrapper>
|
||||
<ErrorBoundary>
|
||||
<ConfigsContextWrapper>
|
||||
<StateBarStateContextWrapper>
|
||||
<StateBarErrorContextWrapper>
|
||||
<StyleSheetManager target={mountPoint}>
|
||||
<ReloadContextWrapper>
|
||||
<ErrorBoundary>
|
||||
<ConfigsContextWrapper>
|
||||
<InspectorContextWrapper>
|
||||
<StateBarErrorContextWrapper>
|
||||
<StateBarStateContextWrapper>
|
||||
<OAuthWrapper>
|
||||
<RepoContextWrapper>
|
||||
<SideBar />
|
||||
</RepoContextWrapper>
|
||||
</OAuthWrapper>
|
||||
</StateBarErrorContextWrapper>
|
||||
</StateBarStateContextWrapper>
|
||||
</ConfigsContextWrapper>
|
||||
</ErrorBoundary>
|
||||
</ReloadContextWrapper>
|
||||
</StyleSheetManager>
|
||||
</InspectorContextWrapper>
|
||||
</StateBarStateContextWrapper>
|
||||
</StateBarErrorContextWrapper>
|
||||
</InspectorContextWrapper>
|
||||
</ConfigsContextWrapper>
|
||||
</ErrorBoundary>
|
||||
</ReloadContextWrapper>
|
||||
</StyleSheetManager>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export function SearchBar({ onSearch, onFocus, value }: Props) {
|
|||
block
|
||||
sx={{ borderRadius: 0 }}
|
||||
className={'search-input'}
|
||||
aria-label="search files"
|
||||
aria-label="Search files"
|
||||
placeholder={formatWithShortcut(`Search files`, focusSearchInputShortcut)}
|
||||
onChange={({ target: { value } }) => onSearch(value, searchMode)}
|
||||
value={value}
|
||||
|
|
|
|||
|
|
@ -65,24 +65,12 @@ export function SideBar() {
|
|||
|
||||
return (
|
||||
<Theme>
|
||||
<ToggleShowButtonWrapper
|
||||
shouldExpand={shouldExpand}
|
||||
setShouldExpand={setShouldExpand}
|
||||
toggleShowSideBar={toggleShowSideBar}
|
||||
/>
|
||||
<SidebarContext.Provider value={sidebarContextValue}>
|
||||
<IIFC>
|
||||
{() => {
|
||||
const logoContainerElement = useLogoContainerElement()
|
||||
return (
|
||||
<Portal into={logoContainerElement}>
|
||||
<ToggleShowButton
|
||||
error={error}
|
||||
className={cx({
|
||||
hidden: shouldExpand,
|
||||
})}
|
||||
onHover={sidebarToggleMode === 'float' ? () => setShouldExpand(true) : undefined}
|
||||
onClick={toggleShowSideBar}
|
||||
/>
|
||||
</Portal>
|
||||
)
|
||||
}}
|
||||
</IIFC>
|
||||
<div className={'gitako-side-bar'}>
|
||||
<div
|
||||
className={cx('gitako-side-bar-body-wrapper', `toggle-mode-${sidebarToggleMode}`, {
|
||||
|
|
@ -174,6 +162,30 @@ export function SideBar() {
|
|||
)
|
||||
}
|
||||
|
||||
function ToggleShowButtonWrapper({
|
||||
shouldExpand,
|
||||
setShouldExpand,
|
||||
toggleShowSideBar,
|
||||
}: {
|
||||
shouldExpand: boolean
|
||||
setShouldExpand: React.Dispatch<React.SetStateAction<boolean>>
|
||||
toggleShowSideBar: () => void
|
||||
}) {
|
||||
const logoContainerElement = useLogoContainerElement()
|
||||
const { sidebarToggleMode } = useConfigs().value
|
||||
return (
|
||||
<Portal into={logoContainerElement}>
|
||||
<ToggleShowButton
|
||||
className={cx({
|
||||
hidden: shouldExpand,
|
||||
})}
|
||||
onHover={sidebarToggleMode === 'float' ? () => setShouldExpand(true) : undefined}
|
||||
onClick={toggleShowSideBar}
|
||||
/>
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function useFocusSidebarOnExpand(shouldExpand: boolean) {
|
||||
React.useEffect(() => {
|
||||
// prevent keeping focus within Gitako
|
||||
|
|
@ -278,18 +290,21 @@ function useCollapseOnNoPermissionWhenTokenHasBeenSet(
|
|||
|
||||
function useShouldExpand() {
|
||||
const getDerivedExpansion = useGetDerivedExpansion()
|
||||
const error = useLoadedContext(SideBarErrorContext).value
|
||||
const [shouldExpand, setShouldExpand] = React.useState(getDerivedExpansion)
|
||||
const toggleShowSideBar = React.useCallback(
|
||||
() => setShouldExpand(show => !show),
|
||||
[setShouldExpand],
|
||||
)
|
||||
|
||||
useSaveExpandStateOnToggle(shouldExpand)
|
||||
useUpdateBodyIndentOnStateUpdate(shouldExpand)
|
||||
const $shouldExpand = error ? false : shouldExpand
|
||||
|
||||
useSaveExpandStateOnToggle($shouldExpand)
|
||||
useUpdateBodyIndentOnStateUpdate($shouldExpand)
|
||||
useUpdateBodyIndentAfterRedirect(setShouldExpand)
|
||||
useCollapseOnNoPermissionWhenTokenHasBeenSet(setShouldExpand)
|
||||
|
||||
return [shouldExpand, setShouldExpand, toggleShowSideBar] as const
|
||||
return [$shouldExpand, setShouldExpand, toggleShowSideBar] as const
|
||||
}
|
||||
|
||||
function useShowSidebarKeyboard(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { SyncIcon } from '@primer/octicons-react'
|
||||
import iconURL from 'assets/icons/Gitako.png'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { SideBarErrorContext } from 'containers/ErrorContext'
|
||||
import { ReloadContext } from 'containers/ReloadContext'
|
||||
import * as React from 'react'
|
||||
import { useDebounce, useWindowSize } from 'react-use'
|
||||
import { cx } from 'utils/cx'
|
||||
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
|
||||
import { useResizeHandler } from 'utils/hooks/useResizeHandler'
|
||||
import { RoundIconButton } from './RoundIconButton'
|
||||
|
||||
type Props = {
|
||||
error?: string | null
|
||||
className?: React.HTMLAttributes<HTMLButtonElement>['className']
|
||||
onHover?: React.HTMLAttributes<HTMLButtonElement>['onMouseEnter']
|
||||
onClick?: (e: PointerEvent) => void
|
||||
|
|
@ -21,8 +22,10 @@ function getSafeDistance(y: number, height: number) {
|
|||
return Math.max(0, Math.min(y, height - buttonHeight))
|
||||
}
|
||||
|
||||
export function ToggleShowButton({ error, className, onClick, onHover }: Props) {
|
||||
export function ToggleShowButton({ className, onClick, onHover }: Props) {
|
||||
const reload = React.useContext(ReloadContext)
|
||||
const error = useLoadedContext(SideBarErrorContext).value
|
||||
|
||||
const ref = React.useRef<HTMLDivElement>(null)
|
||||
const config = useConfigs()
|
||||
const [distance, setDistance] = React.useState(config.value.toggleButtonVerticalDistance)
|
||||
|
|
|
|||
|
|
@ -20,15 +20,13 @@ export const wikiLinks = {
|
|||
compressSingletonFolder: `${WIKI_HOME_LINK}/Compress-Singleton-Folder`,
|
||||
changeLog: `${WIKI_HOME_LINK}/Change-Log`,
|
||||
codeFolding: `${WIKI_HOME_LINK}/Code-folding`,
|
||||
copyFileButton: `${WIKI_HOME_LINK}/Copy-file-and-snippet`,
|
||||
copySnippet: `${WIKI_HOME_LINK}/Copy-file-and-snippet`,
|
||||
createAccessToken: `${WIKI_HOME_LINK}/Access-token-for-Gitako`,
|
||||
pjaxMode: `${WIKI_HOME_LINK}/Pjax-Mode`,
|
||||
bye: `${WIKI_HOME_LINK}/About-to-say-good-bye`,
|
||||
}
|
||||
|
||||
const moreFields: SimpleConfigField<
|
||||
'copyFileButton' | 'copySnippetButton' | 'codeFolding' | 'pjaxMode'
|
||||
>[] =
|
||||
const moreFields: SimpleConfigField<'copySnippetButton' | 'codeFolding' | 'pjaxMode'>[] =
|
||||
platform === GitHub
|
||||
? [
|
||||
{
|
||||
|
|
@ -47,12 +45,6 @@ const moreFields: SimpleConfigField<
|
|||
onChange: checked => (checked ? 'native' : 'pjax-api'),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'copyFileButton',
|
||||
label: 'Copy file button',
|
||||
wikiLink: wikiLinks.copyFileButton,
|
||||
tooltip: `Read more in Gitako's Wiki`,
|
||||
},
|
||||
{
|
||||
key: 'copySnippetButton',
|
||||
label: 'Copy snippet button',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { PropsWithChildren } from 'common'
|
||||
import { useInspector } from 'containers/StateInspector'
|
||||
import { useInspector } from 'containers/Inspector'
|
||||
import * as React from 'react'
|
||||
import { useStateIO } from 'utils/hooks/useStateIO'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { PropsWithChildren, ReactIO } from 'common'
|
||||
import { IN_PRODUCTION_MODE } from 'env'
|
||||
import * as React from 'react'
|
||||
import { noop } from 'utils/general'
|
||||
import { useStateIO } from 'utils/hooks/useStateIO'
|
||||
import { useConfigs } from './ConfigsContext'
|
||||
import { Config } from 'utils/config/helper'
|
||||
|
||||
export type InspectorContextShape = ReactIO<JSONObject>
|
||||
|
||||
|
|
@ -11,7 +14,10 @@ export const InspectorContextWrapper = IN_PRODUCTION_MODE
|
|||
? React.Fragment
|
||||
: function InspectorContextWrapper({ children }: PropsWithChildren) {
|
||||
const $ = useStateIO<JSONObject>({})
|
||||
const [show, setShow] = React.useState(true)
|
||||
const configs = useConfigs()
|
||||
const { __showInspector: show } = configs.value
|
||||
const setShow = (__showInspector: Config['__showInspector']) =>
|
||||
configs.onChange({ __showInspector })
|
||||
|
||||
return (
|
||||
<InspectorContext.Provider value={$}>
|
||||
|
|
@ -57,9 +63,11 @@ export const InspectorContextWrapper = IN_PRODUCTION_MODE
|
|||
)
|
||||
}
|
||||
|
||||
export function useInspector(key: string, value: JSONValue) {
|
||||
const $ = React.useContext(InspectorContext)
|
||||
React.useEffect(() => {
|
||||
$?.onChange(prev => ({ ...prev, [key]: value }))
|
||||
}, [key, value]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}
|
||||
export const useInspector = IN_PRODUCTION_MODE
|
||||
? noop
|
||||
: function useInspector(key: string, value: JSONValue) {
|
||||
const $ = React.useContext(InspectorContext)
|
||||
React.useEffect(() => {
|
||||
$?.onChange(prev => ({ ...prev, [key]: value }))
|
||||
}, [key, value]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import { useHandleNetworkError } from 'utils/hooks/useHandleNetworkError'
|
|||
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
|
||||
import { useStateIO } from 'utils/hooks/useStateIO'
|
||||
import { SideBarStateContext } from './SideBarState'
|
||||
import { useInspector } from './StateInspector'
|
||||
import { useInspector } from './Inspector'
|
||||
|
||||
export const RepoContext = React.createContext<MetaData | null>(null)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { PropsWithChildren } from 'common'
|
||||
import * as React from 'react'
|
||||
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
|
||||
import { useStateIO } from 'utils/hooks/useStateIO'
|
||||
import { useInspector } from './StateInspector'
|
||||
import { SideBarErrorContext } from './ErrorContext'
|
||||
import { useInspector } from './Inspector'
|
||||
|
||||
export type SideBarState =
|
||||
| 'disabled'
|
||||
|
|
@ -13,7 +15,8 @@ export type SideBarState =
|
|||
| 'tree-rendering'
|
||||
| 'tree-rendered'
|
||||
| 'idle'
|
||||
| 'error-due-to-auth'
|
||||
| 'error' // when error occurs, sidebar should never expand
|
||||
| 'error-due-to-auth' // this is a special error, user can expand sidebar and set token to fix the error
|
||||
|
||||
export type SideBarStateContextShape = IO<SideBarState>
|
||||
|
||||
|
|
@ -22,10 +25,17 @@ export const SideBarStateContext = React.createContext<SideBarStateContextShape
|
|||
export function StateBarStateContextWrapper({ children }: PropsWithChildren) {
|
||||
const $state = useStateIO<SideBarState>('disabled')
|
||||
useInspector('SideBarStateContext', $state.value)
|
||||
|
||||
return (
|
||||
<SideBarStateContext.Provider value={$state}>
|
||||
{$state.value !== null && children}
|
||||
</SideBarStateContext.Provider>
|
||||
const error = useLoadedContext(SideBarErrorContext).value
|
||||
const $$state: IO<SideBarState> = React.useMemo(
|
||||
() =>
|
||||
error && $state.value !== 'error'
|
||||
? {
|
||||
...$state,
|
||||
value: 'error',
|
||||
}
|
||||
: $state,
|
||||
[$state, error],
|
||||
)
|
||||
|
||||
return <SideBarStateContext.Provider value={$$state}>{children}</SideBarStateContext.Provider>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { Gitako } from 'components/Gitako'
|
||||
import * as React from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { insertSideBarMountPoint } from 'utils/DOMHelper'
|
||||
import { insertMountPoint, insertSideBarMountPoint } from 'utils/DOMHelper'
|
||||
import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
|
||||
import './content.scss'
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
|
|
@ -12,7 +13,18 @@ if (document.readyState === 'loading') {
|
|||
|
||||
async function init() {
|
||||
await injectStyles(browser.runtime.getURL('content.css'))
|
||||
createRoot(insertSideBarMountPoint()).render(<Gitako />)
|
||||
const mountPoint = insertSideBarMountPoint()
|
||||
const MountPointWatcher = () => {
|
||||
useAfterRedirect(React.useCallback(() => insertMountPoint(() => mountPoint), []))
|
||||
return null
|
||||
}
|
||||
|
||||
createRoot(mountPoint).render(
|
||||
<>
|
||||
<MountPointWatcher />
|
||||
<Gitako />
|
||||
</>,
|
||||
)
|
||||
}
|
||||
|
||||
// injects a copy of stylesheets so that other extensions(e.g. dark reader) could read
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { errors } from 'platforms'
|
|||
import { isEnterprise } from '.'
|
||||
import { is } from '../../utils/is'
|
||||
import { gitakoServiceHost } from '../../utils/networkService'
|
||||
import { continuousLoadPages, getDOM, resolveHeaderLink } from './utils'
|
||||
import { continuousLoadFragmentedPages, getDOM, resolveHeaderLink } from './utils'
|
||||
|
||||
function isAPIRateLimitExceeded(content: JSONValue) {
|
||||
return (
|
||||
|
|
@ -147,7 +147,7 @@ export async function getPullPageDocuments(
|
|||
document?: Document,
|
||||
): Promise<Document[]> {
|
||||
// Response of this API contains view of few files but is not complete.
|
||||
return continuousLoadPages(
|
||||
return continuousLoadFragmentedPages(
|
||||
document ||
|
||||
(await getDOM(`${window.location.origin}/${userName}/${repoName}/pull/${pullId}/files`)),
|
||||
)
|
||||
|
|
@ -158,7 +158,7 @@ export async function getCommitPageDocuments(): Promise<Document[]> {
|
|||
repoName: string,
|
||||
commitId: string, */
|
||||
// arguments are not used because info are collected from DOM directly
|
||||
return continuousLoadPages(document)
|
||||
return continuousLoadFragmentedPages(document)
|
||||
}
|
||||
|
||||
export async function getBlobData(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,103 @@
|
|||
import { raiseError } from 'analytics'
|
||||
import { Clippy, ClippyClassName } from 'components/Clippy'
|
||||
import * as React from 'react'
|
||||
import * as s from 'superstruct'
|
||||
import { $ } from 'utils/$'
|
||||
import { formatClass, parseIntFromElement } from 'utils/DOMHelper'
|
||||
import { renderReact, run } from 'utils/general'
|
||||
import { CopyFileButton, copyFileButtonClassName } from './CopyFileButton'
|
||||
import { renderReact } from 'utils/general'
|
||||
import { embeddedDataStruct } from './embeddedDataStructures'
|
||||
|
||||
const selectors = {
|
||||
normal: {
|
||||
reactApp: `react-app[app-name="react-code-view"] [data-target="react-app.reactRoot"]`,
|
||||
codeTab: '#code-tab',
|
||||
branchSwitcher: [`summary[title="Switch branches or tags"]`, `#branch-select-menu`].join(),
|
||||
fileNavigation: `.file-navigation`,
|
||||
breadcrumbs: `[data-testid="breadcrumbs"]`,
|
||||
breadcrumbsFilename: `[data-testid="breadcrumbs-filename"]`,
|
||||
},
|
||||
globalNavigation: {
|
||||
navbar: {
|
||||
repositoryOwner: [
|
||||
'.AppHeader-context-item[data-hovercard-type="user"]',
|
||||
'.AppHeader-context-item[data-hovercard-type="organization"]',
|
||||
].join(),
|
||||
// its meant to be the element visually next to the `repositoryOwner` element
|
||||
repositoryName:
|
||||
'nav[role="navigation"] ul[role="list"] li:nth-child(2) .AppHeader-context-item',
|
||||
},
|
||||
branchSelector: 'button[id^="branch-picker-"]',
|
||||
pathContext: '[data-testid="breadcrumbs"]',
|
||||
pathContextFileName: '[data-testid="breadcrumbs-filename"]',
|
||||
pathContextScreenReaderHeading: '[data-testid="screen-reader-heading"]',
|
||||
embeddedData: {
|
||||
app: 'script[type="application/json"][data-target="react-app.embeddedData"]',
|
||||
reposOverview:
|
||||
'[partial-name="repos-overview"] script[type="application/json"][data-target="react-partial.embeddedData"]',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const getDOMJSON = (selector: string) =>
|
||||
$(selector, e => {
|
||||
try {
|
||||
return JSON.parse(e.textContent || '')
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
function getMetaFromPayload(payload: s.Infer<typeof embeddedDataStruct.repoPayload>) {
|
||||
const { repo, refInfo } = payload
|
||||
const { defaultBranch, name: repoName, ownerLogin: userName } = repo
|
||||
const { name: branchName } = refInfo
|
||||
|
||||
return {
|
||||
defaultBranch,
|
||||
metaData: {
|
||||
userName,
|
||||
repoName,
|
||||
branchName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// in code page, there is a JSON script tag in DOM with meta data
|
||||
function resolveEmbeddedAppData() {
|
||||
const data = getDOMJSON(selectors.globalNavigation.embeddedData.app)
|
||||
if (s.is(data, embeddedDataStruct.app)) return getMetaFromPayload(data.payload)
|
||||
}
|
||||
|
||||
function resolveEmbeddedReposOverviewData() {
|
||||
const data = getDOMJSON(selectors.globalNavigation.embeddedData.reposOverview)
|
||||
if (s.is(data, embeddedDataStruct.reposOverview))
|
||||
return getMetaFromPayload(data.props.initialPayload)
|
||||
}
|
||||
|
||||
export function resolveEmbeddedData(): {
|
||||
defaultBranch: string
|
||||
metaData: MetaData
|
||||
} | void {
|
||||
return resolveEmbeddedAppData() || resolveEmbeddedReposOverviewData()
|
||||
}
|
||||
|
||||
export function resolveMeta(): Partial<MetaData> {
|
||||
const dataFromJSON = resolveEmbeddedData()
|
||||
if (dataFromJSON) return dataFromJSON.metaData
|
||||
|
||||
const metaData = {
|
||||
userName: $('[itemprop="author"] > a[rel="author"]', e => e.textContent?.trim()) || undefined,
|
||||
repoName: $('[itemprop="name"] > a[href]', e => e.textContent?.trim()) || undefined,
|
||||
userName:
|
||||
$(
|
||||
'[itemprop="author"] > a[rel="author"]',
|
||||
e => e.textContent?.trim(),
|
||||
() => $(selectors.globalNavigation.navbar.repositoryOwner, e => e.textContent?.trim()),
|
||||
) || undefined,
|
||||
repoName:
|
||||
$(
|
||||
'[itemprop="name"] > a[href]',
|
||||
e => e.textContent?.trim(),
|
||||
() => $(selectors.globalNavigation.navbar.repositoryName, e => e.textContent?.trim()),
|
||||
) || undefined,
|
||||
branchName: getCurrentBranch(true),
|
||||
}
|
||||
if (!metaData.userName || !metaData.repoName) {
|
||||
|
|
@ -19,17 +107,26 @@ export function resolveMeta(): Partial<MetaData> {
|
|||
}
|
||||
|
||||
export function isInRepoPage() {
|
||||
const repoHeadSelector = '.repohead' // legacy
|
||||
const repoHeadSelector = '.repohead'
|
||||
const authorNameSelector = '.author[itemprop="author"]'
|
||||
return Boolean(
|
||||
document.querySelector(repoHeadSelector) || document.querySelector(authorNameSelector),
|
||||
document.querySelector(
|
||||
[
|
||||
repoHeadSelector,
|
||||
authorNameSelector,
|
||||
selectors.globalNavigation.navbar.repositoryOwner,
|
||||
].join(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export function isInCodePage() {
|
||||
const branchListSelector = ['#branch-select-menu', '.branch-select-menu'].join()
|
||||
const branchListSelector = [
|
||||
selectors.normal.breadcrumbsFilename,
|
||||
selectors.normal.branchSwitcher,
|
||||
].join()
|
||||
// The element may still exist in DOM for PR pages, but not visible
|
||||
return Boolean($(branchListSelector, e => e.offsetWidth > 0 && e.offsetHeight > 0))
|
||||
return Boolean($(branchListSelector))
|
||||
}
|
||||
|
||||
export function isInPullFilesPage() {
|
||||
|
|
@ -50,18 +147,21 @@ export function getCurrentBranch(passive = false) {
|
|||
const selectedBranchButtonSelector = [
|
||||
'main #branch-select-menu summary',
|
||||
'main .branch-select-menu summary',
|
||||
selectors.globalNavigation.branchSelector,
|
||||
].join()
|
||||
const branchButtonElement = $(selectedBranchButtonSelector)
|
||||
if (branchButtonElement) {
|
||||
const branchNameSpanElement = branchButtonElement.querySelector('span')
|
||||
const branchNameSpanElement = branchButtonElement.querySelector(
|
||||
['.ref-selector-button-text-container', 'span'].join(),
|
||||
)
|
||||
if (branchNameSpanElement) {
|
||||
const partialBranchNameFromInnerText = branchNameSpanElement.textContent || ''
|
||||
const partialBranchNameFromInnerText = branchNameSpanElement.textContent?.trim() || ''
|
||||
if (partialBranchNameFromInnerText && !partialBranchNameFromInnerText.includes('…'))
|
||||
return partialBranchNameFromInnerText
|
||||
}
|
||||
const defaultTitle = 'Switch branches or tags'
|
||||
const title = branchButtonElement.title.trim()
|
||||
if (title !== defaultTitle && !title.includes(' ')) return title
|
||||
if (title && title !== defaultTitle && !title.includes(' ')) return title
|
||||
}
|
||||
|
||||
const findFileButtonSelector = 'main .file-navigation a[data-hotkey="t"]'
|
||||
|
|
@ -78,6 +178,17 @@ export function getCurrentBranch(passive = false) {
|
|||
}
|
||||
}
|
||||
|
||||
const branchNameFromCodeTab = $(selectors.normal.codeTab, e => {
|
||||
if (e instanceof HTMLAnchorElement) {
|
||||
const chunks = e.href.split('/')
|
||||
const indexOfTree = chunks.indexOf('tree')
|
||||
if (indexOfTree === -1) return
|
||||
const branchName = chunks.slice(indexOfTree + 1).join('/')
|
||||
return branchName
|
||||
}
|
||||
})
|
||||
if (branchNameFromCodeTab) return branchNameFromCodeTab
|
||||
|
||||
if (!passive) raiseError(new Error('cannot get current branch'))
|
||||
}
|
||||
|
||||
|
|
@ -104,33 +215,17 @@ const PAGE_TYPES = {
|
|||
* TODO: distinguish type 'preview'
|
||||
*/
|
||||
function getCurrentPageType() {
|
||||
const blobPathSelector = '#blob-path' // path next to branch switcher
|
||||
const blobWrapperSelector = 'main .blob-wrapper table'
|
||||
const readmeSelector = 'main .readme'
|
||||
const searchResultSelector = '.codesearch-results'
|
||||
const searchResultSelector = '.search-sub-header'
|
||||
const blobPathSelector = '[aria-label="file content"]'
|
||||
const readmeSelector = 'main #readme'
|
||||
return (
|
||||
$(searchResultSelector, () => PAGE_TYPES.SEARCH) ||
|
||||
$(blobWrapperSelector, () => $(blobPathSelector, () => PAGE_TYPES.RAW_TEXT)) ||
|
||||
$(blobPathSelector, () => PAGE_TYPES.RAW_TEXT) ||
|
||||
$(readmeSelector, () => PAGE_TYPES.RENDERED) ||
|
||||
PAGE_TYPES.OTHERS
|
||||
)
|
||||
}
|
||||
|
||||
const REPO_TYPE_PRIVATE = 'private' as const
|
||||
const REPO_TYPE_PUBLIC = 'public' as const
|
||||
export function getRepoPageType() {
|
||||
const headerSelector = `main .pagehead.repohead h1`
|
||||
return $(headerSelector, header => {
|
||||
const repoPageTypes = [REPO_TYPE_PRIVATE, REPO_TYPE_PUBLIC]
|
||||
for (const repoPageType of repoPageTypes) {
|
||||
if (header.classList.contains(repoPageType)) {
|
||||
return repoPageType
|
||||
}
|
||||
}
|
||||
raiseError(new Error('cannot get repo page type'))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* get text content of raw text content
|
||||
*/
|
||||
|
|
@ -145,49 +240,6 @@ export function getCodeElement() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add copy file content buttons to button groups
|
||||
* click these buttons will copy file content to clipboard
|
||||
*/
|
||||
export function attachCopyFileBtn() {
|
||||
const removeButtons = () => {
|
||||
const buttons = document.querySelectorAll(formatClass(copyFileButtonClassName))
|
||||
buttons.forEach(button => {
|
||||
button.parentElement?.removeChild(button)
|
||||
})
|
||||
}
|
||||
|
||||
if (getCurrentPageType() === PAGE_TYPES.RAW_TEXT) {
|
||||
let buttonGroup: HTMLElement | null = null
|
||||
|
||||
if (!buttonGroup) {
|
||||
const rawUrlButtonSelector = '#raw-url'
|
||||
const $buttonGroup = document.querySelector(rawUrlButtonSelector)?.parentElement
|
||||
if ($buttonGroup) buttonGroup = $buttonGroup
|
||||
}
|
||||
|
||||
if (!buttonGroup) {
|
||||
const buttonGroupSelector = 'main .Box-header .BtnGroup'
|
||||
const buttonGroups = document.querySelectorAll(buttonGroupSelector)
|
||||
const $buttonGroup = buttonGroups[buttonGroups.length - 1]
|
||||
if ($buttonGroup) buttonGroup = $buttonGroup as HTMLElement
|
||||
}
|
||||
|
||||
run(async () => {
|
||||
if (!buttonGroup) raiseError(new Error(`No button groups found`))
|
||||
else if (!buttonGroup.lastElementChild) return
|
||||
else {
|
||||
removeButtons() // prevent duplicated buttons
|
||||
const button = await renderReact(React.createElement(CopyFileButton))
|
||||
if (button instanceof HTMLElement) buttonGroup.appendChild(button)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// return callback so that disabling after redirecting from file page to non-page works properly
|
||||
return removeButtons
|
||||
}
|
||||
|
||||
export function attachCopySnippet() {
|
||||
const readmeSelector = 'main div#readme'
|
||||
return $(readmeSelector, () => {
|
||||
|
|
@ -257,17 +309,35 @@ export function getPath() {
|
|||
const pathElement =
|
||||
document.querySelector(blobPathElementSelector) ||
|
||||
document.querySelector(folderPathElementSelector)?.nextElementSibling
|
||||
if (!pathElement?.querySelector('.js-repo-root')) {
|
||||
return []
|
||||
if (pathElement?.querySelector('.js-repo-root')) {
|
||||
const path = (pathElement.textContent || '')
|
||||
.replace(/\n/g, '')
|
||||
.replace(/\/\s+Jump to.*/m, '')
|
||||
.trim()
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.slice(1) // the first is the repo's name
|
||||
return path
|
||||
}
|
||||
const path = ((pathElement as HTMLDivElement).textContent || '')
|
||||
.replace(/\n/g, '')
|
||||
.replace(/\/\s+Jump to.*/m, '')
|
||||
.trim()
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.slice(1) // the first is the repo's name
|
||||
return path
|
||||
|
||||
const pathContextElement = document.querySelector(
|
||||
selectors.globalNavigation.pathContext,
|
||||
)?.parentElement
|
||||
let path = pathContextElement?.textContent?.trim()
|
||||
if (path) {
|
||||
// [Breadcrumbs]:repoName/:path
|
||||
const screenReader = pathContextElement?.querySelector(
|
||||
selectors.globalNavigation.pathContextScreenReaderHeading,
|
||||
)
|
||||
if (screenReader) path = path.replace(screenReader.textContent || '', '')
|
||||
return path.split('/').slice(1)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export function isNativeFileTreeShown() {
|
||||
return Boolean($('#repos-file-tree'))
|
||||
}
|
||||
|
||||
export function isNativePRFileTreeShown() {
|
||||
|
|
|
|||
81
src/platforms/GitHub/embeddedDataStructures.ts
Normal file
81
src/platforms/GitHub/embeddedDataStructures.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import * as s from 'superstruct'
|
||||
|
||||
const repo = s.object({
|
||||
id: s.number(),
|
||||
defaultBranch: s.string(),
|
||||
name: s.string(),
|
||||
ownerLogin: s.string(),
|
||||
currentUserCanPush: s.boolean(),
|
||||
isFork: s.boolean(),
|
||||
isEmpty: s.boolean(),
|
||||
createdAt: s.string(),
|
||||
ownerAvatar: s.string(),
|
||||
public: s.boolean(),
|
||||
private: s.boolean(),
|
||||
isOrgOwned: s.boolean(),
|
||||
})
|
||||
|
||||
const user = s.object({
|
||||
id: s.number(),
|
||||
login: s.string(),
|
||||
userEmail: s.string(),
|
||||
})
|
||||
|
||||
const rel = s.object({
|
||||
name: s.string(),
|
||||
listCacheKey: s.string(),
|
||||
canEdit: s.boolean(),
|
||||
refType: s.string(),
|
||||
currentOid: s.string(),
|
||||
})
|
||||
|
||||
const treeItem = s.object({
|
||||
name: s.string(),
|
||||
path: s.string(),
|
||||
contentType: s.string(),
|
||||
})
|
||||
|
||||
const tree = s.object({
|
||||
items: s.array(treeItem),
|
||||
templateDirectorySuggestionUrl: s.nullable(s.never()),
|
||||
readme: s.nullable(s.never()),
|
||||
totalCount: s.number(),
|
||||
showBranchInfobar: s.boolean(),
|
||||
})
|
||||
|
||||
const repoPayload = s.object({
|
||||
allShortcutsEnabled: s.boolean(),
|
||||
path: s.string(),
|
||||
repo: repo,
|
||||
currentUser: user,
|
||||
refInfo: rel,
|
||||
tree: tree,
|
||||
fileTree: s.nullable(s.never()),
|
||||
fileTreeProcessingTime: s.nullable(s.never()),
|
||||
foldersToFetch: s.array(s.unknown()),
|
||||
treeExpanded: s.boolean(),
|
||||
symbolsExpanded: s.boolean(),
|
||||
isOverview: s.boolean(),
|
||||
overview: s.unknown(),
|
||||
})
|
||||
|
||||
const reposOverview = s.object({
|
||||
props: s.object({
|
||||
initialPayload: repoPayload,
|
||||
appPayload: s.unknown(),
|
||||
}),
|
||||
})
|
||||
const app = s.object({
|
||||
payload: repoPayload,
|
||||
})
|
||||
|
||||
export const embeddedDataStruct = {
|
||||
repo,
|
||||
user,
|
||||
rel,
|
||||
treeItem,
|
||||
tree,
|
||||
repoPayload,
|
||||
reposOverview,
|
||||
app,
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { platform } from 'platforms'
|
||||
import * as React from 'react'
|
||||
import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
|
||||
import * as DOMHelper from '../DOMHelper'
|
||||
import { GitHub } from '../index'
|
||||
|
||||
export function useGitHubAttachCopyFileButton(copyFileButton: boolean) {
|
||||
const attachCopyFileButton = React.useCallback(
|
||||
function attachCopyFileButton() {
|
||||
if (platform === GitHub && copyFileButton) DOMHelper.attachCopyFileBtn()
|
||||
},
|
||||
[copyFileButton],
|
||||
)
|
||||
React.useEffect(attachCopyFileButton, [attachCopyFileButton])
|
||||
useAfterRedirect(attachCopyFileButton)
|
||||
}
|
||||
|
|
@ -7,13 +7,12 @@ import { resolveGitModules } from 'utils/gitSubmodule'
|
|||
import { sortFoldersToFront } from 'utils/treeParser'
|
||||
import * as API from './API'
|
||||
import * as DOMHelper from './DOMHelper'
|
||||
import * as URLHelper from './URLHelper'
|
||||
import { getCommitTreeData } from './getCommitTreeData'
|
||||
import { getPullRequestTreeData } from './getPullRequestTreeData'
|
||||
import { useEnterpriseStatBarStyleFix } from './hooks/useEnterpriseStatBarStyleFix'
|
||||
import { useGitHubAttachCopyFileButton } from './hooks/useGitHubAttachCopyFileButton'
|
||||
import { useGitHubAttachCopySnippetButton } from './hooks/useGitHubAttachCopySnippetButton'
|
||||
import { useGitHubCodeFold } from './hooks/useGitHubCodeFold'
|
||||
import * as URLHelper from './URLHelper'
|
||||
|
||||
export function processTree(tree: TreeNode[]): TreeNode {
|
||||
// nodes are created from items and put onto tree
|
||||
|
|
@ -67,17 +66,27 @@ export function processTree(tree: TreeNode[]): TreeNode {
|
|||
}
|
||||
|
||||
export function isEnterprise() {
|
||||
if (window.location.host === 'github.com') return false
|
||||
|
||||
return (
|
||||
(window.location.host !== 'github.com' &&
|
||||
/**
|
||||
* <a class="Header-link " href="https://host.com/" data-hotkey="g d" aria-label="Homepage Enterprise">
|
||||
* <span>Enterprise</span>
|
||||
* </a>
|
||||
*/
|
||||
$(
|
||||
/**
|
||||
* <a class="AppHeader-logo" href="https://host.com/" data-hotkey="g d" aria-label="Homepage Enterprise">
|
||||
* <svg></svg>
|
||||
* </a>
|
||||
*/
|
||||
$('a.AppHeader-logo[aria-label="Homepage Enterprise"]') !== null ||
|
||||
/**
|
||||
* <a class="Header-link " href="https://host.com/" data-hotkey="g d" aria-label="Homepage Enterprise">
|
||||
* <span>Enterprise</span>
|
||||
* </a>
|
||||
*/
|
||||
$(
|
||||
[
|
||||
'a.Header-link[aria-label="Homepage Enterprise"]',
|
||||
e => e.textContent?.trim() === 'Enterprise',
|
||||
)) ||
|
||||
'a.Header-link[aria-label="Homepage"]', // legacy support
|
||||
].join(),
|
||||
e => e.textContent?.trim() === 'Enterprise',
|
||||
) ||
|
||||
false
|
||||
)
|
||||
}
|
||||
|
|
@ -107,7 +116,7 @@ export const GitHub: Platform = {
|
|||
}
|
||||
|
||||
const { type } = metaFromURL
|
||||
let branchName
|
||||
let branchName = metaFromDOM.branchName
|
||||
if (URLHelper.isInPullPage()) {
|
||||
branchName = DOMHelper.getIssueTitle()
|
||||
} else if (URLHelper.isInCommitPage()) {
|
||||
|
|
@ -128,6 +137,9 @@ export const GitHub: Platform = {
|
|||
return metaData
|
||||
},
|
||||
async getDefaultBranchName({ userName, repoName }, accessToken) {
|
||||
const dataFromJSON = DOMHelper.resolveEmbeddedData()
|
||||
if (dataFromJSON?.defaultBranch) return dataFromJSON.defaultBranch
|
||||
|
||||
return (await API.getRepoMeta(userName, repoName, accessToken)).default_branch
|
||||
},
|
||||
resolveUrlFromMetaData({ userName, repoName, branchName }) {
|
||||
|
|
@ -157,7 +169,7 @@ export const GitHub: Platform = {
|
|||
},
|
||||
shouldExpandSideBar() {
|
||||
return Boolean(
|
||||
DOMHelper.isInCodePage() ||
|
||||
(DOMHelper.isInCodePage() && !DOMHelper.isNativePRFileTreeShown()) ||
|
||||
URLHelper.isInCommitPage() ||
|
||||
(URLHelper.isInPullPage() && !DOMHelper.isNativePRFileTreeShown()),
|
||||
)
|
||||
|
|
@ -190,8 +202,7 @@ export const GitHub: Platform = {
|
|||
return `https://github.com/login/oauth/authorize?${params}`
|
||||
},
|
||||
usePlatformHooks() {
|
||||
const { copyFileButton, copySnippetButton, codeFolding } = useConfigs().value
|
||||
useGitHubAttachCopyFileButton(copyFileButton)
|
||||
const { copySnippetButton, codeFolding } = useConfigs().value
|
||||
useGitHubAttachCopySnippetButton(copySnippetButton)
|
||||
useGitHubCodeFold(codeFolding)
|
||||
useEnterpriseStatBarStyleFix()
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export async function getDOM(url: string) {
|
|||
return new DOMParser().parseFromString(await (await fetch(url)).text(), 'text/html')
|
||||
}
|
||||
|
||||
export async function continuousLoadPages(doc: Document, onReceivePage?: (doc: Document) => void) {
|
||||
export async function continuousLoadFragmentedPages(doc: Document) {
|
||||
/**
|
||||
* <include-fragment
|
||||
* src="..."
|
||||
|
|
@ -91,17 +91,21 @@ export async function continuousLoadPages(doc: Document, onReceivePage?: (doc: D
|
|||
'include-fragment[data-targets="diff-file-filter.progressiveLoaders"]',
|
||||
'.js-diff-progressive-container include-fragment[src]', // legacy support
|
||||
]
|
||||
|
||||
const documents: Document[] = [doc]
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const fragment = doc.querySelector(fragmentSelectors.join()) as HTMLElement
|
||||
if (!fragment) break
|
||||
const src = fragment.getAttribute('src')
|
||||
if (!src) break
|
||||
// Using `src` directly below would fail in Firefox if the src is an absolute path
|
||||
doc = await getDOM(new URL(src, window.location.origin).href)
|
||||
documents.push(doc)
|
||||
onReceivePage?.(doc)
|
||||
|
||||
const selector = fragmentSelectors.find(selector => doc.querySelector(selector))
|
||||
if (selector) {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const fragment = doc.querySelector(selector)
|
||||
if (!(fragment instanceof HTMLElement)) break
|
||||
const src = fragment.getAttribute('src')
|
||||
if (!src) break
|
||||
// Using `src` without origin below would fail in Firefox if the src is an absolute path
|
||||
doc = await getDOM(new URL(src, window.location.origin).href)
|
||||
documents.push(doc)
|
||||
}
|
||||
}
|
||||
return documents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ export const Gitea: Platform = {
|
|||
},
|
||||
async getTreeData(metaData, path, recursive, accessToken) {
|
||||
const { userName, repoName, branchName } = metaData
|
||||
const treeData = await API.getTreeData(userName, repoName, branchName, recursive)
|
||||
const treeData = await API.getTreeData(userName, repoName, branchName, recursive, accessToken)
|
||||
|
||||
const root = processTree(
|
||||
treeData.tree.map(item => ({
|
||||
|
|
|
|||
|
|
@ -26,17 +26,6 @@ export function getCurrentBranch() {
|
|||
raiseError(new Error('cannot get current branch'))
|
||||
}
|
||||
|
||||
const REPO_TYPE_PRIVATE = 'private' as const
|
||||
const REPO_TYPE_PUBLIC = 'public' as const
|
||||
export function getRepoPageType() {
|
||||
const headerSelector = `.git-project-title .icon-lock`
|
||||
return $(
|
||||
headerSelector,
|
||||
() => REPO_TYPE_PRIVATE,
|
||||
() => REPO_TYPE_PUBLIC,
|
||||
)
|
||||
}
|
||||
|
||||
export function attachCopySnippet() {
|
||||
const readmeSelector = '.file_content.markdown-body'
|
||||
return $(readmeSelector, () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { GITEE_OAUTH } from 'env'
|
||||
import { Base64 } from 'js-base64'
|
||||
import { platform } from 'platforms'
|
||||
import { errors, platform } from 'platforms'
|
||||
import * as React from 'react'
|
||||
import { resolveGitModules } from 'utils/gitSubmodule'
|
||||
import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
|
||||
|
|
@ -180,6 +180,10 @@ export const Gitee: Platform = {
|
|||
usePlatformHooks() {
|
||||
useProgressBar()
|
||||
},
|
||||
mapErrorMessage: (error: Error) =>
|
||||
({
|
||||
['Only signed in user is allowed to call APIs.']: errors.BAD_CREDENTIALS,
|
||||
}[error.message]),
|
||||
}
|
||||
|
||||
export function useGiteeAttachCopySnippetButton(copySnippetButton: boolean) {
|
||||
|
|
|
|||
1
src/platforms/platform.d.ts
vendored
1
src/platforms/platform.d.ts
vendored
|
|
@ -29,4 +29,5 @@ type Platform = {
|
|||
| void
|
||||
loadWithFastRedirect?(url: string, element: HTMLElement): boolean | void
|
||||
usePlatformHooks?(): void
|
||||
mapErrorMessage?: (error: Error) => string | void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ $minimal-z-index: max(
|
|||
}
|
||||
|
||||
html[data-with-gitako-spacing='true'] {
|
||||
body {
|
||||
body,
|
||||
.AppHeader deferred-side-panel div[data-modal-dialog-overlay] // side panel opened via clicking github top-left icon in global navigation mode
|
||||
{
|
||||
@media screen {
|
||||
margin-left: var(--gitako-width);
|
||||
}
|
||||
|
|
@ -585,6 +587,11 @@ html[data-with-gitako-spacing='true'] {
|
|||
padding: 2px 6px 2px 10px;
|
||||
border-top: 1px solid var(--color-border-muted);
|
||||
|
||||
&-section {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.version {
|
||||
color: var(--color-fg-muted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
export function $(selector: string): HTMLElement | null
|
||||
export function $<T1>(selector: string, existCallback: (element: HTMLElement) => T1): T1 | null
|
||||
export function $<T1, T2>(
|
||||
export function $<E extends HTMLElement>(selector: string): E | null
|
||||
export function $<R1>(selector: string, existCallback: (element: HTMLElement) => R1): R1 | null
|
||||
export function $<R1, R2>(
|
||||
selector: string,
|
||||
existCallback: (element: HTMLElement) => T1,
|
||||
otherwise: () => T2,
|
||||
): T1 | T2
|
||||
export function $<T2>(
|
||||
existCallback: (element: HTMLElement) => R1,
|
||||
otherwise: () => R2,
|
||||
): R1 | R2
|
||||
export function $<E extends HTMLElement, R2>(
|
||||
selector: string,
|
||||
existCallback: undefined | null,
|
||||
otherwise: () => T2,
|
||||
): HTMLElement | T2
|
||||
otherwise: () => R2,
|
||||
): E | R2
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function $(selector: string, existCallback?: any, otherwise?: any) {
|
||||
const element = document.querySelector(selector)
|
||||
|
|
|
|||
|
|
@ -103,34 +103,37 @@ export function setBodyIndent(shouldShowGitako: boolean) {
|
|||
* </html>
|
||||
*/
|
||||
|
||||
const mountPointContainer = document.documentElement
|
||||
export function insertMountPoint() {
|
||||
return $(formatID(rootElementID), undefined, () => {
|
||||
const mountPointContainer = document.body
|
||||
export function insertMountPoint(
|
||||
create = () => {
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('id', rootElementID)
|
||||
mountPointContainer.appendChild(element)
|
||||
return element
|
||||
})
|
||||
},
|
||||
) {
|
||||
return $(formatID(rootElementID), undefined, () => mountPointContainer.appendChild(create()))
|
||||
}
|
||||
|
||||
export function insertSideBarMountPoint() {
|
||||
const sidebarMountPointID = 'gitako-sidebar-mount-point'
|
||||
return $(formatID(sidebarMountPointID), undefined, () => {
|
||||
const sideBarElement = document.createElement('div')
|
||||
sideBarElement.setAttribute('id', sidebarMountPointID)
|
||||
insertMountPoint().appendChild(sideBarElement)
|
||||
return sideBarElement
|
||||
})
|
||||
const id = 'gitako-sidebar-mount-point'
|
||||
const create = () => {
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('id', id)
|
||||
return element
|
||||
}
|
||||
return $<HTMLDivElement, HTMLDivElement>(formatID(id), undefined, () =>
|
||||
insertMountPoint().appendChild(create()),
|
||||
)
|
||||
}
|
||||
|
||||
export function insertLogoMountPoint() {
|
||||
const logoMountPointID = 'gitako-logo-mount-point'
|
||||
return $(formatID(logoMountPointID), undefined, () => {
|
||||
const logoMountElement = document.createElement('div')
|
||||
logoMountElement.setAttribute('id', logoMountPointID)
|
||||
insertMountPoint().appendChild(logoMountElement)
|
||||
return logoMountElement
|
||||
})
|
||||
const id = 'gitako-logo-mount-point'
|
||||
const create = () => {
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('id', id)
|
||||
return element
|
||||
}
|
||||
return $(formatID(id), undefined, () => insertMountPoint().appendChild(create()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export type Config = {
|
|||
focusSearchInputShortcut: string | undefined // shortcut for focusing search input
|
||||
accessToken: string | undefined
|
||||
compressSingletonFolder: boolean
|
||||
copyFileButton: boolean
|
||||
copySnippetButton: boolean
|
||||
intelligentToggle: boolean | null // `null` stands for intelligent, boolean for sidebar open state
|
||||
icons: 'rich' | 'dim' | 'native'
|
||||
|
|
@ -23,6 +22,7 @@ export type Config = {
|
|||
restoreExpandedFolders: boolean
|
||||
pjaxMode: 'native' | 'pjax-api'
|
||||
showDiffInText: boolean
|
||||
__showInspector?: boolean
|
||||
}
|
||||
|
||||
export type ConfigKeys = keyof Config
|
||||
|
|
@ -33,7 +33,6 @@ enum configKeys {
|
|||
focusSearchInputShortcut = 'focusSearchInputShortcut',
|
||||
accessToken = 'accessToken',
|
||||
compressSingletonFolder = 'compressSingletonFolder',
|
||||
copyFileButton = 'copyFileButton',
|
||||
copySnippetButton = 'copySnippetButton',
|
||||
intelligentToggle = 'intelligentToggle',
|
||||
icons = 'icons',
|
||||
|
|
@ -47,6 +46,7 @@ enum configKeys {
|
|||
restoreExpandedFolders = 'restoreExpandedFolders',
|
||||
pjaxMode = 'pjaxMode',
|
||||
showDiffInText = 'showDiffInText',
|
||||
__showInspector = '__showInspector',
|
||||
}
|
||||
|
||||
// NOT use platform name to distinguish GHE from github.com
|
||||
|
|
@ -59,7 +59,6 @@ export const getDefaultConfigs: () => Config = () => ({
|
|||
focusSearchInputShortcut: undefined,
|
||||
accessToken: '',
|
||||
compressSingletonFolder: true,
|
||||
copyFileButton: !isInGitHub, // disable on github.com
|
||||
copySnippetButton: !isInGitHub, // disable on github.com
|
||||
intelligentToggle: null,
|
||||
icons: 'rich',
|
||||
|
|
@ -77,7 +76,7 @@ export const getDefaultConfigs: () => Config = () => ({
|
|||
|
||||
const configKeyArray = Object.values(configKeys)
|
||||
|
||||
function applyDefaultConfigs(configs: Partial<Config>) {
|
||||
function applyDefaultConfigs(configs: Partial<Config> = {}) {
|
||||
const defaultConfigs = getDefaultConfigs()
|
||||
return configKeyArray.reduce((applied, key) => {
|
||||
Object.assign(applied, { [key]: key in configs ? configs[key] : defaultConfigs[key] })
|
||||
|
|
@ -95,15 +94,45 @@ const updateConfigRef = async (config: Partial<Config>) => {
|
|||
const configMigration = migrateConfig()
|
||||
configMigration.then(async () => updateConfigRef(await get()))
|
||||
|
||||
let loadedConfigFromURL = false
|
||||
|
||||
function getConfigFromURL() {
|
||||
const config: Partial<Config> = {}
|
||||
// config params pattern:
|
||||
// ?gitako-config-<key>=<json-value>
|
||||
new URLSearchParams(window.location.search).forEach((value, key) => {
|
||||
if (key.match(/^gitako-config-/)) {
|
||||
const configKey = key.replace(/^gitako-config-/, '')
|
||||
if (configKey in configKeys) {
|
||||
try {
|
||||
config[configKeys[configKey as configKeys]] = JSON.parse(value)
|
||||
loadedConfigFromURL = true
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse config "${configKey}" from URL: ${value}`, {
|
||||
cause: error,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.warn(`Unknown config "${configKey}" from URL: ${value}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
async function get(): Promise<Config> {
|
||||
await configMigration
|
||||
const config = await storageHelper.get<Record<string, Config>>([platformStorageKey])
|
||||
return applyDefaultConfigs(config?.[platformStorageKey] || {})
|
||||
const savedConfig = (await storageHelper.get<Record<string, Config>>([platformStorageKey]))?.[
|
||||
platformStorageKey
|
||||
]
|
||||
const configFromURL = getConfigFromURL()
|
||||
return applyDefaultConfigs({ ...savedConfig, ...configFromURL })
|
||||
}
|
||||
|
||||
async function set(config: Config) {
|
||||
updateConfigRef(config)
|
||||
return await storageHelper.set({ [platformStorageKey]: config })
|
||||
if (!loadedConfigFromURL) await storageHelper.set({ [platformStorageKey]: config })
|
||||
}
|
||||
|
||||
export const configHelper = { get, set }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { getSafeWidth, MINIMAL_CONTENT_VIEWPORT_WIDTH, MINIMAL_WIDTH } from './getSafeWidth'
|
||||
|
||||
jest.retryTimes(3) // Math.random may result in failure due to floating point precision
|
||||
|
||||
it(`should shrink when window is being resized smaller`, () => {
|
||||
const randomGrow = 100 * Math.random()
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { platform } from 'platforms'
|
||||
import * as React from 'react'
|
||||
import { useEvent } from 'react-use'
|
||||
import { useEvent, useInterval } from 'react-use'
|
||||
|
||||
const config: import('pjax-api').Config = {
|
||||
areas: [
|
||||
|
|
@ -54,8 +54,17 @@ export const loadWithFastRedirect = (url: string, element: HTMLElement) => {
|
|||
}
|
||||
|
||||
export function useAfterRedirect(callback: () => void) {
|
||||
useEvent('pjax:end', callback, document) // legacy support
|
||||
useEvent('turbo:render', callback, document) // prevent page content shift after first redirect to new page via turbo when sidebar is pinned
|
||||
const latestHref = React.useRef(location.href)
|
||||
const raceCallback = React.useCallback(() => {
|
||||
const { href } = location
|
||||
if (latestHref.current !== href) {
|
||||
latestHref.current = href
|
||||
callback()
|
||||
}
|
||||
}, [callback])
|
||||
useInterval(raceCallback, 500)
|
||||
useEvent('pjax:end', raceCallback, document) // legacy support
|
||||
useEvent('turbo:render', raceCallback, document) // prevent page content shift after first redirect to new page via turbo when sidebar is pinned
|
||||
}
|
||||
|
||||
export function useRedirectedEvents(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { errors, platformName } from 'platforms'
|
||||
import { errors, platform, platformName } from 'platforms'
|
||||
import { useCallback } from 'react'
|
||||
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
|
||||
import { SideBarErrorContext } from '../../containers/ErrorContext'
|
||||
|
|
@ -12,33 +12,34 @@ export function useHandleNetworkError() {
|
|||
|
||||
return useCallback(
|
||||
function handleNetworkError(err: Error) {
|
||||
if (err.message === errors.EMPTY_PROJECT) {
|
||||
const message = platform.mapErrorMessage?.(err) || err.message
|
||||
if (message === errors.EMPTY_PROJECT) {
|
||||
changeErrorContext('This project seems to be empty.')
|
||||
return
|
||||
}
|
||||
|
||||
if (err.message === errors.BLOCKED_PROJECT) {
|
||||
if (message === errors.BLOCKED_PROJECT) {
|
||||
changeErrorContext('Access to the project is blocked.')
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
err.message === errors.NOT_FOUND ||
|
||||
err.message === errors.BAD_CREDENTIALS ||
|
||||
err.message === errors.API_RATE_LIMIT
|
||||
message === errors.NOT_FOUND ||
|
||||
message === errors.BAD_CREDENTIALS ||
|
||||
message === errors.API_RATE_LIMIT
|
||||
) {
|
||||
changeStateContext('error-due-to-auth')
|
||||
return
|
||||
}
|
||||
|
||||
if (err.message === errors.CONNECTION_BLOCKED) {
|
||||
if (message === errors.CONNECTION_BLOCKED) {
|
||||
if (accessToken) changeErrorContext(`Cannot connect to ${platformName}.`)
|
||||
else changeStateContext('error-due-to-auth')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (err.message === errors.SERVER_FAULT) {
|
||||
if (message === errors.SERVER_FAULT) {
|
||||
changeErrorContext(`${platformName} server went down.`)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"target": "ESNext",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"lib": ["dom", "es2017.object", "es2016", "ES2019.Array", "ES2020.String"],
|
||||
"lib": ["dom", "es2017.object", "es2016", "ES2019.Array", "ES2020.String", "ES2022.Error"],
|
||||
"baseUrl": "src",
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
|
|
|||
|
|
@ -117,11 +117,11 @@ module.exports = {
|
|||
sideEffects: false,
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
test: /\.m?js$/,
|
||||
loader: 'babel-loader',
|
||||
// Transpile as least files under node_modules
|
||||
include:
|
||||
/node_modules\/(webext-content-scripts|webext-detect-page|webext-dynamic-content-scripts)\/.*\.js$/,
|
||||
/node_modules\/(webext-content-scripts|webext-detect-page|webext-dynamic-content-scripts|superstruct)\/.*\.m?js$/,
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
},
|
||||
|
|
|
|||
174
yarn.lock
174
yarn.lock
|
|
@ -2812,11 +2812,6 @@ abab@^2.0.6:
|
|||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
|
||||
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||
|
||||
abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
||||
|
|
@ -4039,7 +4034,7 @@ chokidar@^3.4.0, chokidar@^3.4.2:
|
|||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
chownr@^1.1.1, chownr@^1.1.4:
|
||||
chownr@^1.1.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
|
|
@ -4722,13 +4717,6 @@ debug@4.3.1:
|
|||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^3.2.6:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.3.2, debug@^4.3.4, debug@~4.3.1:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
|
|
@ -4877,11 +4865,6 @@ detect-file@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
|
||||
integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
|
||||
|
||||
detect-libc@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
||||
|
||||
detect-newline@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||
|
|
@ -6092,13 +6075,6 @@ fs-extra@~9.0.1:
|
|||
jsonfile "^6.0.1"
|
||||
universalify "^1.0.0"
|
||||
|
||||
fs-minipass@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
|
||||
integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
|
||||
dependencies:
|
||||
minipass "^2.6.0"
|
||||
|
||||
fs-monkey@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
|
||||
|
|
@ -6124,23 +6100,10 @@ fs.realpath@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
fsevents@^1.2.7:
|
||||
version "1.2.9"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
|
||||
integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
|
||||
dependencies:
|
||||
nan "^2.12.1"
|
||||
node-pre-gyp "^0.12.0"
|
||||
|
||||
fsevents@^2.3.2, fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
fsevents@~2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805"
|
||||
integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==
|
||||
fsevents@^1.2.7, fsevents@^2.3.2, fsevents@^2.3.3, fsevents@~2.1.2, fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
|
|
@ -6786,7 +6749,7 @@ hyphenate-style-name@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48"
|
||||
integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.4:
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
|
|
@ -6827,13 +6790,6 @@ iferr@^0.1.5:
|
|||
resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
|
||||
integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
|
||||
|
||||
ignore-walk@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.2.tgz#99d83a246c196ea5c93ef9315ad7b0819c35069b"
|
||||
integrity sha512-EXyErtpHbn75ZTsOADsfx6J/FPo6/5cjev46PXrcTpd8z3BoRkXgYu9/JVqrI7tusjmwCZutGeRJeU0Wo1e4Cw==
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
ignore@^3.3.5:
|
||||
version "3.3.10"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
|
||||
|
|
@ -8697,21 +8653,6 @@ minimist@^1.2.6:
|
|||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
minipass@^2.6.0, minipass@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
|
||||
integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
|
||||
dependencies:
|
||||
safe-buffer "^5.1.2"
|
||||
yallist "^3.0.0"
|
||||
|
||||
minizlib@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
|
||||
integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
|
||||
dependencies:
|
||||
minipass "^2.9.0"
|
||||
|
||||
mississippi@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
|
||||
|
|
@ -8816,7 +8757,7 @@ mz@2.7.0:
|
|||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nan@^2.12.1, nan@^2.14.0:
|
||||
nan@^2.14.0:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
|
||||
|
|
@ -8867,15 +8808,6 @@ ncp@~2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
||||
integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
|
||||
|
||||
needle@^2.2.1:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
|
||||
integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
|
||||
dependencies:
|
||||
debug "^3.2.6"
|
||||
iconv-lite "^0.4.4"
|
||||
sax "^1.2.4"
|
||||
|
||||
negotiator@0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
|
|
@ -8947,22 +8879,6 @@ node-notifier@10.0.1:
|
|||
uuid "^8.3.2"
|
||||
which "^2.0.2"
|
||||
|
||||
node-pre-gyp@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
|
||||
integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==
|
||||
dependencies:
|
||||
detect-libc "^1.0.2"
|
||||
mkdirp "^0.5.1"
|
||||
needle "^2.2.1"
|
||||
nopt "^4.0.1"
|
||||
npm-packlist "^1.1.6"
|
||||
npmlog "^4.0.2"
|
||||
rc "^1.2.7"
|
||||
rimraf "^2.6.1"
|
||||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-releases@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.3.tgz#225ee7488e4a5e636da8da52854844f9d716ca96"
|
||||
|
|
@ -8973,14 +8889,6 @@ node-releases@^2.0.6:
|
|||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
|
||||
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
|
||||
|
||||
nopt@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
|
||||
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
|
||||
dependencies:
|
||||
abbrev "1"
|
||||
osenv "^0.1.4"
|
||||
|
||||
normalize-path@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
|
||||
|
|
@ -9008,19 +8916,6 @@ normalize-url@^6.0.1:
|
|||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||
|
||||
npm-bundled@^1.0.1:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
|
||||
integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
|
||||
|
||||
npm-packlist@^1.1.6:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44"
|
||||
integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==
|
||||
dependencies:
|
||||
ignore-walk "^3.0.1"
|
||||
npm-bundled "^1.0.1"
|
||||
|
||||
npm-run-path@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
|
||||
|
|
@ -9042,7 +8937,7 @@ npm-run-path@^5.1.0:
|
|||
dependencies:
|
||||
path-key "^4.0.0"
|
||||
|
||||
npmlog@^4.0.2, npmlog@^4.1.2:
|
||||
npmlog@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
|
||||
|
|
@ -9260,7 +9155,7 @@ os-browserify@^0.3.0:
|
|||
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
|
||||
integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
|
||||
|
||||
os-homedir@^1.0.0, os-homedir@^1.0.1:
|
||||
os-homedir@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
|
||||
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
|
||||
|
|
@ -9288,19 +9183,6 @@ os-shim@^0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
|
||||
integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=
|
||||
|
||||
os-tmpdir@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
||||
|
||||
osenv@^0.1.4:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
|
||||
integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
|
||||
dependencies:
|
||||
os-homedir "^1.0.0"
|
||||
os-tmpdir "^1.0.0"
|
||||
|
||||
p-cancelable@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050"
|
||||
|
|
@ -9726,10 +9608,10 @@ prepend-http@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||
|
||||
prettier@^2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
|
||||
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
|
||||
prettier@^2.8.3:
|
||||
version "2.8.3"
|
||||
resolved "https://registry.npmmirror.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632"
|
||||
integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==
|
||||
|
||||
pretty-format@^27.0.2, pretty-format@^27.5.1:
|
||||
version "27.5.1"
|
||||
|
|
@ -10014,7 +9896,7 @@ raw-loader@^4.0.0:
|
|||
loader-utils "^1.2.3"
|
||||
schema-utils "^2.5.0"
|
||||
|
||||
rc@1.2.8, rc@^1.2.7:
|
||||
rc@1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
||||
|
|
@ -10494,7 +10376,7 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
|
|||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3:
|
||||
rimraf@^2.5.4, rimraf@^2.6.3:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||
|
|
@ -10556,7 +10438,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
|||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
|
@ -10601,7 +10483,7 @@ sass@^1.26.2:
|
|||
dependencies:
|
||||
chokidar ">=2.0.0 <4.0.0"
|
||||
|
||||
sax@>=0.6.0, sax@^1.2.4:
|
||||
sax@>=0.6.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
|
@ -10687,7 +10569,7 @@ semver@7.3.7, semver@^7.3.5, semver@^7.3.7:
|
|||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
|
||||
semver@^5.5.0, semver@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
|
@ -11443,6 +11325,11 @@ stylis@^4.0.6:
|
|||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.1.tgz#e46c6a9bbf7c58db1e65bb730be157311ae1fe12"
|
||||
integrity sha512-lVrM/bNdhVX2OgBFNa2YJ9Lxj7kPzylieHd3TNjuGE0Re9JB7joL5VUKOVH1kdNNJTgGPpT8hmwIAPLaSyEVFQ==
|
||||
|
||||
superstruct@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmmirror.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046"
|
||||
integrity sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==
|
||||
|
||||
supports-color@6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
|
|
@ -11512,19 +11399,6 @@ tar-stream@^2.0.0:
|
|||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@^4:
|
||||
version "4.4.19"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3"
|
||||
integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==
|
||||
dependencies:
|
||||
chownr "^1.1.4"
|
||||
fs-minipass "^1.2.7"
|
||||
minipass "^2.9.0"
|
||||
minizlib "^1.3.3"
|
||||
mkdirp "^0.5.5"
|
||||
safe-buffer "^5.2.1"
|
||||
yallist "^3.1.1"
|
||||
|
||||
terser-webpack-plugin@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4"
|
||||
|
|
@ -12597,7 +12471,7 @@ y18n@^5.0.5:
|
|||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||
|
||||
yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1:
|
||||
yallist@^3.0.2:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
|
|
|
|||
Loading…
Reference in a new issue