Merge branch 'development' into releases/3.2.2

This commit is contained in:
Sergio Padrino 2023-04-26 13:39:27 +02:00
commit 08a0e33704
95 changed files with 1300 additions and 1884 deletions

View file

@ -37,7 +37,7 @@ jobs:
private_key: ${{ secrets.DESKTOP_RELEASES_APP_PRIVATE_KEY }}
- name: Create Release Pull Request
uses: peter-evans/create-pull-request@v4.2.4
uses: peter-evans/create-pull-request@v5.0.0
if: |
startsWith(github.ref, 'refs/heads/releases/') && !contains(github.ref, 'test')
with:

View file

@ -25,16 +25,8 @@ Download the official installer for your operating system:
- [Windows](https://central.github.com/deployments/desktop/desktop/latest/win32)
- [Windows machine-wide install](https://central.github.com/deployments/desktop/desktop/latest/win32?format=msi)
You can install this alongside your existing GitHub Desktop for Mac or GitHub
Desktop for Windows application.
Linux is not officially supported; however, you can find installers created for Linux from a fork of GitHub Desktop in the [Community Releases](https://github.com/desktop/desktop#community-releases) section.
**NOTE**: There is no current migration path to import your existing
repositories into the new application - you can drag-and-drop your repositories
from disk onto the application to get started.
### Beta Channel
Want to test out new features and get fixes before everyone else? Install the
@ -58,15 +50,10 @@ install GitHub Desktop:
Installers for various Linux distributions can be found on the
[`shiftkey/desktop`](https://github.com/shiftkey/desktop) fork.
Arch Linux users can install the latest version from the
[AUR](https://aur.archlinux.org/packages/github-desktop-bin/).
## Is GitHub Desktop right for me? What are the primary areas of focus?
[This document](https://github.com/desktop/desktop/blob/development/docs/process/what-is-desktop.md) describes the focus of GitHub Desktop and who the product is most useful for.
And to see what the team is working on currently and in the near future, check out the [GitHub Desktop roadmap](https://github.com/desktop/desktop/blob/development/docs/process/roadmap.md).
## I have a problem with GitHub Desktop
Note: The [GitHub Desktop Code of Conduct](https://github.com/desktop/desktop/blob/development/CODE_OF_CONDUCT.md) applies in all interactions relating to the GitHub Desktop project.
@ -96,14 +83,13 @@ If you're looking for something to work on, check out the [help wanted](https://
## Building Desktop
To get your development environment set up for building Desktop, see [setup.md](./docs/contributing/setup.md).
To setup your development environment for building Desktop, check out: [`setup.md`](./docs/contributing/setup.md).
## More Resources
See [desktop.github.com](https://desktop.github.com) for more product-oriented
information about GitHub Desktop.
See our [getting started documentation](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/overview/getting-started-with-github-desktop) for more information on how to set up, authenticate, and configure GitHub Desktop.
## License

View file

@ -46,7 +46,6 @@
"prop-types": "^15.7.2",
"quick-lru": "^3.0.0",
"react": "^16.8.4",
"react-color": "^2.19.3",
"react-css-transition-replace": "^3.0.3",
"react-dom": "^16.8.4",
"react-transition-group": "^4.4.1",
@ -60,7 +59,6 @@
"untildify": "^3.0.2",
"username": "^5.1.0",
"uuid": "^3.0.1",
"wicg-focus-ring": "^1.0.1",
"winston": "^3.6.0"
},
"devDependencies": {

View file

@ -31,11 +31,7 @@ import { SignInState } from './stores/sign-in-store'
import { WindowState } from './window-state'
import { Shell } from './shells'
import {
ApplicableTheme,
ApplicationTheme,
ICustomTheme,
} from '../ui/lib/application-theme'
import { ApplicableTheme, ApplicationTheme } from '../ui/lib/application-theme'
import { IAccountRepositories } from './stores/api-repositories-store'
import { ManualConflictResolution } from '../models/manual-conflict-resolution'
import { Banner } from '../models/banner'
@ -271,9 +267,6 @@ export interface IAppState {
/** The selected appearance (aka theme) preference */
readonly selectedTheme: ApplicationTheme
/** The custom theme */
readonly customTheme?: ICustomTheme
/** The currently applied appearance (aka theme) */
readonly currentTheme: ApplicableTheme

View file

@ -35,6 +35,10 @@ const editors: IDarwinExternalEditor[] = [
name: 'Neovide',
bundleIdentifiers: ['com.neovide.neovide'],
},
{
name: 'VimR',
bundleIdentifiers: ['com.qvacua.VimR'],
},
{
name: 'Visual Studio Code',
bundleIdentifiers: ['com.microsoft.VSCode'],

View file

@ -211,7 +211,7 @@ const editors: WindowsExternalEditor[] = [
publishers: ['Microsoft Corporation'],
},
{
name: 'Visual Studio Codium',
name: 'VSCodium',
registryKeys: [
// 64-bit version of VSCodium (user)
CurrentUserUninstallKey('{2E1F05D1-C245-4562-81EE-28188DB6FD17}_is1'),
@ -245,7 +245,7 @@ const editors: WindowsExternalEditor[] = [
publishers: ['VSCodium', 'Microsoft Corporation'],
},
{
name: 'Visual Studio Codium (Insiders)',
name: 'VSCodium (Insiders)',
registryKeys: [
// 64-bit version of VSCodium - Insiders (user)
CurrentUserUninstallKey('{20F79D0D-A9AC-4220-9A81-CE675FFB6B41}_is1'),

View file

@ -73,16 +73,6 @@ export function enableResetToCommit(): boolean {
return enableDevelopmentFeatures()
}
/** Should we allow high contrast theme option */
export function enableHighContrastTheme(): boolean {
return enableBetaFeatures()
}
/** Should we allow customizing a theme */
export function enableCustomizeTheme(): boolean {
return enableBetaFeatures()
}
/** Should ci check runs show logs? */
export function enableCICheckRunsLogs(): boolean {
return false

View file

@ -3,7 +3,6 @@ import * as Path from 'path'
import { Disposable } from 'event-kit'
import { Tailer } from './tailer'
import byline from 'byline'
import * as Crypto from 'crypto'
import { createReadStream } from 'fs'
import { mkdtemp } from 'fs/promises'
@ -80,23 +79,3 @@ export async function readPartialFile(
.on('end', () => resolve(Buffer.concat(chunks, total)))
})
}
export async function getFileHash(
path: string,
type: 'sha1' | 'sha256'
): Promise<string> {
return new Promise((resolve, reject) => {
const hash = Crypto.createHash(type)
hash.setEncoding('hex')
const input = createReadStream(path)
hash.on('finish', () => {
resolve(hash.read() as string)
})
input.on('error', reject)
hash.on('error', reject)
input.pipe(hash)
})
}

View file

@ -0,0 +1,68 @@
import { Branch, BranchType } from '../models/branch'
import {
Repository,
isForkedRepositoryContributingToParent,
} from '../models/repository'
import { getRemoteHEAD } from './git'
import { getDefaultBranch } from './helpers/default-branch'
import { UpstreamRemoteName } from './stores/helpers/find-upstream-remote'
/**
* Attempts to locate the default branch as determined by the HEAD symbolic link
* in the contribution target remote (origin or upstream) if such a ref exists,
* falling back to the value of the `init.defaultBranch` configuration and
* finally a const value of `main`.
*
* In determining the default branch we prioritize finding a local branch but if
* no local branch matches the default branch name nor is tracking the
* contribution target remote HEAD we'll fall back to looking for the remote
* branch itself.
*/
export async function findDefaultBranch(
repository: Repository,
branches: ReadonlyArray<Branch>,
defaultRemoteName: string | undefined
) {
const remoteName = isForkedRepositoryContributingToParent(repository)
? UpstreamRemoteName
: defaultRemoteName
const remoteHead = remoteName
? await getRemoteHEAD(repository, remoteName)
: null
const defaultBranchName = remoteHead ?? (await getDefaultBranch())
const remoteRef = remoteHead ? `${remoteName}/${remoteHead}` : undefined
let localHit: Branch | undefined = undefined
let localTrackingHit: Branch | undefined = undefined
let remoteHit: Branch | undefined = undefined
for (const branch of branches) {
if (branch.type === BranchType.Local) {
if (branch.name === defaultBranchName) {
localHit = branch
}
if (remoteRef && branch.upstream === remoteRef) {
// Give preference to local branches that target the upstream
// default branch that also match the name. In other words, if there
// are two local branches which both track the origin default branch
// we'll prefer a branch which is also named the same as the default
// branch name.
if (!localTrackingHit || branch.name === defaultBranchName) {
localTrackingHit = branch
}
}
} else if (remoteRef && branch.name === remoteRef) {
remoteHit = branch
}
}
// When determining what the default branch is we give priority to local
// branches tracking the default branch of the contribution target (think
// origin) remote, then we consider local branches that are named the same
// as the default branch, and finally we look for the remote branch
// representing the default branch of the contribution target
return localTrackingHit ?? localHit ?? remoteHit ?? null
}

View file

@ -0,0 +1,15 @@
import { createHash } from 'crypto'
import { createReadStream } from 'fs'
/**
* Calculates the hex encoded hash digest of a given file on disk.
*/
export const getFileHash = (path: string, type: 'sha1' | 'sha256') =>
new Promise<string>((resolve, reject) => {
const hash = createHash(type)
hash.on('finish', () => resolve(hash.digest('hex')))
hash.on('error', reject)
createReadStream(path).on('error', reject).pipe(hash)
})

View file

@ -13,6 +13,7 @@ import {
import { parseRawLogWithNumstat } from './log'
import { stageFiles } from './update-index'
import { Branch } from '../../models/branch'
import { createLogParser } from './git-delimiter-parser'
export const DesktopStashEntryMarker = '!!GitHub_Desktop'
@ -41,17 +42,17 @@ type StashResult = {
* as well as the total amount of stash entries.
*/
export async function getStashes(repository: Repository): Promise<StashResult> {
const delimiter = '1F'
const delimiterString = String.fromCharCode(parseInt(delimiter, 16))
const format = ['%gD', '%H', '%gs'].join(`%x${delimiter}`)
const { formatArgs, parse } = createLogParser({
name: '%gD',
stashSha: '%H',
message: '%gs',
})
const result = await git(
['log', '-g', '-z', `--pretty=${format}`, 'refs/stash'],
['log', '-g', ...formatArgs, 'refs/stash'],
repository.path,
'getStashEntries',
{
successExitCodes: new Set([0, 128]),
}
{ successExitCodes: new Set([0, 128]) }
)
// There's no refs/stashes reflog in the repository or it's not
@ -60,34 +61,20 @@ export async function getStashes(repository: Repository): Promise<StashResult> {
return { desktopEntries: [], stashEntryCount: 0 }
}
const desktopStashEntries: Array<IStashEntry> = []
const files: StashedFileChanges = {
kind: StashedChangesLoadStates.NotLoaded,
}
const desktopEntries: Array<IStashEntry> = []
const files: StashedFileChanges = { kind: StashedChangesLoadStates.NotLoaded }
const entries = result.stdout.split('\0').filter(s => s !== '')
for (const entry of entries) {
const pieces = entry.split(delimiterString)
const entries = parse(result.stdout)
if (pieces.length === 3) {
const [name, stashSha, message] = pieces
const branchName = extractBranchFromMessage(message)
for (const { name, message, stashSha } of entries) {
const branchName = extractBranchFromMessage(message)
if (branchName !== null) {
desktopStashEntries.push({
name,
branchName,
stashSha,
files,
})
}
if (branchName !== null) {
desktopEntries.push({ name, stashSha, branchName, files })
}
}
return {
desktopEntries: desktopStashEntries,
stashEntryCount: entries.length - 1,
}
return { desktopEntries, stashEntryCount: entries.length - 1 }
}
/**

View file

@ -144,10 +144,18 @@ export class PopupManager {
if (this.popupStack.length > this.popupLimit) {
// Remove the oldest
const oldest = this.popupStack[0]
const oldestError =
oldest.type === PopupType.Error ? `: ${oldest.error.message}` : null
const justAddedError =
this.currentPopup?.type === PopupType.Error
? `Just added another Error: ${this.currentPopup.error.message}.`
: null
sendNonFatalException(
'TooManyPopups',
new Error(
`Max number of ${this.popupLimit} popups reached while adding popup of type ${this.currentPopup?.type}. Removing last popup from the stack -> type ${oldest.type} `
`Max number of ${this.popupLimit} popups reached while adding popup of type ${this.currentPopup?.type}.
Removing last popup from the stack. Type ${oldest.type}${oldestError}.
${justAddedError}`
)
)
this.popupStack = this.popupStack.slice(1)

View file

@ -1,4 +1,4 @@
import { getFileHash } from '../file-system'
import { getFileHash } from '../get-file-hash'
import { TokenStore } from '../stores'
import {
getSSHSecretStoreKey,

View file

@ -73,7 +73,6 @@ import {
ApplicationTheme,
getCurrentlyAppliedTheme,
getPersistedThemeName,
ICustomTheme,
setPersistedTheme,
} from '../../ui/lib/application-theme'
import {
@ -390,7 +389,6 @@ const InitialRepositoryIndicatorTimeout = 2 * 60 * 1000
const MaxInvalidFoldersToDisplay = 3
const lastThankYouKey = 'version-and-users-of-last-thank-you'
const customThemeKey = 'custom-theme-key'
const pullRequestSuggestedNextActionKey =
'pull-request-suggested-next-action-key'
@ -498,7 +496,6 @@ export class AppStore extends TypedBaseStore<IAppState> {
private selectedBranchesTab = BranchesTab.Branches
private selectedTheme = ApplicationTheme.System
private customTheme?: ICustomTheme
private currentTheme: ApplicableTheme = ApplicationTheme.Light
private useWindowsOpenSSH: boolean = false
@ -977,7 +974,6 @@ export class AppStore extends TypedBaseStore<IAppState> {
selectedCloneRepositoryTab: this.selectedCloneRepositoryTab,
selectedBranchesTab: this.selectedBranchesTab,
selectedTheme: this.selectedTheme,
customTheme: this.customTheme,
currentTheme: this.currentTheme,
apiRepositories: this.apiRepositoriesStore.getState(),
useWindowsOpenSSH: this.useWindowsOpenSSH,
@ -2091,14 +2087,10 @@ export class AppStore extends TypedBaseStore<IAppState> {
this.showSideBySideDiff = getShowSideBySideDiff()
this.selectedTheme = getPersistedThemeName()
this.customTheme = getObject<ICustomTheme>(customThemeKey)
// Make sure the persisted theme is applied
setPersistedTheme(this.selectedTheme)
this.currentTheme =
this.selectedTheme !== ApplicationTheme.HighContrast
? await getCurrentlyAppliedTheme()
: this.selectedTheme
this.currentTheme = await getCurrentlyAppliedTheme()
themeChangeMonitor.onThemeChanged(theme => {
this.currentTheme = theme
@ -3259,6 +3251,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
return
}
// loadBranches needs the default remote to determine the default branch
await gitStore.loadRemotes()
await gitStore.loadBranches()
const section = state.selectedSection
@ -3276,7 +3270,6 @@ export class AppStore extends TypedBaseStore<IAppState> {
}
await Promise.all([
gitStore.loadRemotes(),
gitStore.updateLastFetched(),
gitStore.loadStashEntries(),
this._refreshAuthor(repository),
@ -4557,6 +4550,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
await this.performPush(repository, account)
}
await gitStore.refreshDefaultBranch()
return this.repositoryWithRefreshedGitHubRepository(repository)
}
@ -6320,20 +6315,6 @@ export class AppStore extends TypedBaseStore<IAppState> {
public _setSelectedTheme(theme: ApplicationTheme) {
setPersistedTheme(theme)
this.selectedTheme = theme
if (theme === ApplicationTheme.HighContrast) {
this.currentTheme = theme
}
this.emitUpdate()
return Promise.resolve()
}
/**
* Set the custom application-wide theme
*/
public _setCustomTheme(theme: ICustomTheme) {
setObject(customThemeKey, theme)
this.customTheme = theme
this.emitUpdate()
return Promise.resolve()
@ -7759,5 +7740,12 @@ function constrain(
min = -Infinity,
max = Infinity
): IConstrainedValue {
return { value: typeof value === 'number' ? value : value.value, min, max }
// Match CSS's behavior where min-width takes precedence over max-width
// See https://stackoverflow.com/a/16063871
const constrainedMax = max < min ? min : max
return {
value: typeof value === 'number' ? value : value.value,
min,
max: constrainedMax,
}
}

View file

@ -30,7 +30,6 @@ import {
ErrorWithMetadata,
IErrorMetadata,
} from '../error-with-metadata'
import { compare } from '../../lib/compare'
import { queueWorkHigh } from '../../lib/queue-work'
import {
@ -101,6 +100,7 @@ import { DiffSelection, ITextDiff } from '../../models/diff'
import { getDefaultBranch } from '../helpers/default-branch'
import { stat } from 'fs/promises'
import { findForkedRemotesToPrune } from './helpers/find-forked-remotes-to-prune'
import { findDefaultBranch } from '../find-default-branch'
/** The number of commits to load from history per batch. */
const CommitBatchSize = 100
@ -467,21 +467,12 @@ export class GitStore extends BaseStore {
}
}
private async refreshDefaultBranch() {
const defaultBranchName = await this.resolveDefaultBranch()
// Find the default branch among all of our branches, giving
// priority to local branches by sorting them before remotes
this._defaultBranch =
this._allBranches
.filter(
b =>
(b.name === defaultBranchName &&
b.upstreamWithoutRemote === null) ||
b.upstreamWithoutRemote === defaultBranchName
)
.sort((x, y) => compare(x.type, y.type))
.shift() || null
public async refreshDefaultBranch() {
this._defaultBranch = await findDefaultBranch(
this.repository,
this.allBranches,
this.defaultRemote?.name
)
// The upstream default branch is only relevant for forked GitHub repos when
// the fork behavior is contributing to the parent.
@ -496,7 +487,7 @@ export class GitStore extends BaseStore {
const upstreamDefaultBranch =
(await getRemoteHEAD(this.repository, UpstreamRemoteName)) ??
defaultBranchName
getDefaultBranch()
this._upstreamDefaultBranch =
this._allBranches.find(
@ -504,7 +495,7 @@ export class GitStore extends BaseStore {
b.type === BranchType.Remote &&
b.remoteName === UpstreamRemoteName &&
b.nameWithoutRemote === upstreamDefaultBranch
) || null
) ?? null
}
private addTagToPush(tagName: string) {
@ -530,28 +521,6 @@ export class GitStore extends BaseStore {
this.emitUpdate()
}
/**
* Resolve the default branch name for the current repository,
* using the available API data, remote information or branch
* name conventions.
*/
private async resolveDefaultBranch(): Promise<string> {
if (this.currentRemote !== null) {
// the Git server should use [remote]/HEAD to advertise
// it's default branch, so see if it exists and matches
// a valid branch on the remote and attempt to use that
const branchName = await getRemoteHEAD(
this.repository,
this.currentRemote.name
)
if (branchName !== null) {
return branchName
}
}
return getDefaultBranch()
}
private refreshRecentBranches(
recentBranchNames: ReadonlyArray<string> | undefined
) {

View file

@ -3,15 +3,10 @@ import {
ApplicationTheme,
getThemeName,
getCurrentlyAppliedTheme,
ICustomTheme,
} from './lib/application-theme'
import { isHexColorLight } from './lib/color-manipulation'
import { buildCustomThemeStyles } from './lib/custom-theme'
interface IAppThemeProps {
readonly theme: ApplicationTheme
readonly useCustomTheme: boolean
readonly customTheme?: ICustomTheme
}
/**
@ -40,13 +35,6 @@ export class AppTheme extends React.PureComponent<IAppThemeProps> {
}
private async ensureTheme() {
const { customTheme, useCustomTheme } = this.props
if (customTheme !== undefined && useCustomTheme) {
this.clearThemes()
this.setCustomTheme(customTheme)
return
}
let themeToDisplay = this.props.theme
if (this.props.theme === ApplicationTheme.System) {
@ -54,15 +42,10 @@ export class AppTheme extends React.PureComponent<IAppThemeProps> {
}
const newThemeClassName = `theme-${getThemeName(themeToDisplay)}`
const body = document.body
if (
!body.classList.contains(newThemeClassName) ||
(body.classList.contains('theme-high-contrast') &&
!this.props.useCustomTheme)
) {
if (!document.body.classList.contains(newThemeClassName)) {
this.clearThemes()
body.classList.add(newThemeClassName)
document.body.classList.add(newThemeClassName)
this.updateColorScheme()
}
}
@ -74,45 +57,6 @@ export class AppTheme extends React.PureComponent<IAppThemeProps> {
rootStyle.colorScheme = isDarkTheme ? 'dark' : 'light'
}
/**
* This takes a custom theme object and applies it over top either our dark or
* light theme dynamically creating a new variables style sheet.
*
* It uses the background color of the custom theme to determine if the custom
* theme should be based on the light or dark theme. This is most important
* for the diff syntax highlighting.
*
* @param customTheme
*/
private setCustomTheme(customTheme: ICustomTheme) {
const { background } = customTheme
const body = document.body
if (!body.classList.contains('theme-high-contrast')) {
// Currently our only custom theme is the high-contrast theme
// If we were to expand upon custom theming we would not
// want this so specific.
body.classList.add('theme-high-contrast')
// This is important so that code diff syntax colors are legible if the
// user customizes to a light vs dark background. Tho, the code diff does
// still use the customizable text color for some of the syntax text so
// user can still make things illegible by choosing poorly.
const themeBase = isHexColorLight(background)
? 'theme-light'
: 'theme-dark'
body.classList.add(themeBase)
}
const customThemeStyles = buildCustomThemeStyles(customTheme)
const styles = document.createElement('style')
styles.setAttribute('type', 'text/css')
styles.appendChild(document.createTextNode(customThemeStyles))
body.appendChild(styles)
this.updateColorScheme()
}
private clearThemes() {
const body = document.body

View file

@ -1394,6 +1394,12 @@ export class App extends React.Component<IAppProps, IAppState> {
this.state.currentFoldout &&
this.state.currentFoldout.type === FoldoutType.AppMenu
// As Linux still uses the classic Electron menu, we are opting out of the
// custom menu that is shown as part of the title bar below
if (__LINUX__) {
return null
}
// When we're in full-screen mode on Windows we only need to render
// the title bar when the menu bar is active. On other platforms we
// never render the title bar while in full-screen mode.
@ -1596,7 +1602,6 @@ export class App extends React.Component<IAppProps, IAppState> {
onDismissed={onPopupDismissedFn}
selectedShell={this.state.selectedShell}
selectedTheme={this.state.selectedTheme}
customTheme={this.state.customTheme}
repositoryIndicatorsEnabled={this.state.repositoryIndicatorsEnabled}
/>
)
@ -3216,13 +3221,7 @@ export class App extends React.Component<IAppProps, IAppState> {
return (
<div id="desktop-app-chrome" className={className}>
<AppTheme
theme={currentTheme}
customTheme={this.state.customTheme}
useCustomTheme={
this.state.selectedTheme === ApplicationTheme.HighContrast
}
/>
<AppTheme theme={currentTheme} />
{this.renderTitlebar()}
{this.state.showWelcomeFlow
? this.renderWelcomeFlow()

View file

@ -25,8 +25,8 @@ interface IAutocompletingTextInputProps<ElementType, AutocompleteItemType> {
*/
readonly className?: string
/** The aria-labelledby attribute for the input field. */
readonly elementAriaLabelledBy?: string
/** Element ID for the input field. */
readonly elementId?: string
/** The placeholder for the input field. */
readonly placeholder?: string
@ -38,7 +38,7 @@ interface IAutocompletingTextInputProps<ElementType, AutocompleteItemType> {
readonly disabled?: boolean
/** Indicates if input field should be required */
readonly isRequired?: boolean
readonly required?: boolean
/**
* Indicates if input field should be considered a combobox by assistive
@ -247,11 +247,6 @@ export abstract class AutocompletingTextInput<
const searchText = state.rangeText
const className = classNames('autocompletion-popup', state.provider.kind)
const shouldForceAriaLiveMessage = this.shouldForceAriaLiveMessage
this.shouldForceAriaLiveMessage = false
const suggestionsMessage =
items.length === 1 ? '1 suggestion' : `${items.length} suggestions`
return (
<div
@ -267,16 +262,11 @@ export abstract class AutocompletingTextInput<
selectedRows={[selectedRow]}
rowRenderer={this.renderItem}
scrollToRow={selectedRow}
selectOnHover={false}
focusOnHover={false}
onRowMouseDown={this.onRowMouseDown}
onRowClick={this.insertCompletionOnClick}
onSelectedRowChanged={this.onSelectedRowChanged}
invalidationProps={searchText}
/>
<AriaLiveContainer shouldForceChange={shouldForceAriaLiveMessage}>
{suggestionsMessage}
</AriaLiveContainer>
</div>
)
}
@ -390,6 +380,7 @@ export abstract class AutocompletingTextInput<
const props = {
type: 'text',
id: this.props.elementId,
role: this.props.isCombobox ? ('combobox' as const) : undefined,
placeholder: this.props.placeholder,
value: this.props.value,
@ -400,10 +391,9 @@ export abstract class AutocompletingTextInput<
onBlur: this.onBlur,
onContextMenu: this.onContextMenu,
disabled: this.props.disabled,
'aria-required': this.props.isRequired ? true : false,
required: this.props.required ? true : false,
spellCheck: this.props.spellcheck,
autoComplete: 'off',
'aria-labelledby': this.props.elementAriaLabelledBy,
'aria-expanded': autocompleteVisible,
'aria-autocomplete': 'list' as const,
'aria-haspopup': 'listbox' as const,
@ -449,16 +439,31 @@ export abstract class AutocompletingTextInput<
const tagName = this.getElementTagName()
const className = classNames(
'autocompletion-container',
'no-invalid-state',
this.props.className,
{
'text-box-component': tagName === 'input',
'text-area-component': tagName === 'textarea',
}
)
const shouldForceAriaLiveMessage = this.shouldForceAriaLiveMessage
this.shouldForceAriaLiveMessage = false
const autoCompleteItems = this.state.autocompletionState?.items ?? []
const suggestionsMessage =
autoCompleteItems.length === 1
? '1 suggestion'
: `${autoCompleteItems.length} suggestions`
return (
<div className={className}>
{this.renderAutocompletions()}
{this.renderTextInput()}
<AriaLiveContainer shouldForceChange={shouldForceAriaLiveMessage}>
{autoCompleteItems.length > 0 ? suggestionsMessage : ''}
</AriaLiveContainer>
</div>
)
}

View file

@ -7,6 +7,7 @@ import { mapStatus } from '../../lib/status'
import { WorkingDirectoryFileChange } from '../../models/status'
import { TooltipDirection } from '../lib/tooltip'
import { TooltippedContent } from '../lib/tooltipped-content'
import { AriaLiveContainer } from '../accessibility/aria-live-container'
interface IChangedFileProps {
readonly file: WorkingDirectoryFileChange
@ -52,6 +53,13 @@ export class ChangedFile extends React.Component<IChangedFileProps, {}> {
filePadding -
statusWidth
const includedText =
this.props.include === true
? 'included'
: this.props.include === undefined
? 'partially included'
: 'not included'
return (
<div className="file">
<TooltippedContent
@ -77,6 +85,10 @@ export class ChangedFile extends React.Component<IChangedFileProps, {}> {
ariaHidden={true}
/>
<AriaLiveContainer>
{path} {mapStatus(status)} {includedText}
</AriaLiveContainer>
<Octicon
symbol={iconForStatus(status)}
className={'status status-' + fileStatus.toLowerCase()}

View file

@ -49,11 +49,12 @@ import * as OcticonSymbol from '../octicons/octicons.generated'
import { IStashEntry } from '../../models/stash-entry'
import classNames from 'classnames'
import { hasWritePermission } from '../../models/github-repository'
import { hasConflictedFiles, mapStatus } from '../../lib/status'
import { hasConflictedFiles } from '../../lib/status'
import { createObservableRef } from '../lib/observable-ref'
import { Tooltip, TooltipDirection } from '../lib/tooltip'
import { TooltipDirection } from '../lib/tooltip'
import { Popup } from '../../models/popup'
import { EOL } from 'os'
import { TooltippedContent } from '../lib/tooltipped-content'
const RowHeight = 29
const StashIcon: OcticonSymbol.OcticonSymbolType = {
@ -240,7 +241,7 @@ export class ChangesList extends React.Component<
IChangesState
> {
private headerRef = createObservableRef<HTMLDivElement>()
private listRef = React.createRef<List>()
private includeAllCheckBoxRef = React.createRef<Checkbox>()
public constructor(props: IChangesListProps) {
super(props)
@ -327,13 +328,6 @@ export class ChangesList extends React.Component<
)
}
private getFileAriaLabel = (row: number): string => {
const { workingDirectory } = this.props
const { path, status } = workingDirectory.files[row]
return `${path} ${mapStatus(status)}`
}
private onDiscardAllChanges = () => {
this.props.onDiscardChangesFromFiles(
this.props.workingDirectory.files,
@ -924,7 +918,7 @@ export class ChangesList extends React.Component<
}
public focus() {
this.listRef.current?.focus()
this.includeAllCheckBoxRef.current?.focus()
}
public render() {
@ -938,7 +932,7 @@ export class ChangesList extends React.Component<
file => file.selection.getSelectionType() !== DiffSelectionType.None
).length
const totalFilesPlural = files.length === 1 ? 'file' : 'files'
const selectedChangesDescription = `${selectedChangeCount}/${files.length} changed ${totalFilesPlural} selected`
const selectedChangesDescription = `${selectedChangeCount}/${files.length} changed ${totalFilesPlural} included`
const includeAllValue = getIncludeAllValue(
workingDirectory,
@ -949,45 +943,54 @@ export class ChangesList extends React.Component<
files.length === 0 || isCommitting || rebaseConflictState !== null
return (
<div className="changes-list-container file-list">
<div
className="header"
onContextMenu={this.onContextMenu}
ref={this.headerRef}
>
<Tooltip target={this.headerRef} direction={TooltipDirection.NORTH}>
{selectedChangesDescription}
</Tooltip>
<Checkbox
label={filesDescription}
value={includeAllValue}
onChange={this.onIncludeAllChanged}
disabled={disableAllCheckbox}
<>
<div className="changes-list-container file-list">
<div
className="header"
onContextMenu={this.onContextMenu}
ref={this.headerRef}
>
<TooltippedContent
tooltip={selectedChangesDescription}
direction={TooltipDirection.NORTH}
openOnFocus={true}
>
<Checkbox
ref={this.includeAllCheckBoxRef}
label={filesDescription}
value={includeAllValue}
onChange={this.onIncludeAllChanged}
disabled={disableAllCheckbox}
ariaDescribedBy="changesDescription"
/>
</TooltippedContent>
<div className="sr-only" id="changesDescription">
{selectedChangesDescription}
</div>
</div>
<List
id="changes-list"
rowCount={files.length}
rowHeight={RowHeight}
rowRenderer={this.renderRow}
selectedRows={this.state.selectedRows}
selectionMode="multi"
onSelectionChanged={this.props.onFileSelectionChanged}
invalidationProps={{
workingDirectory: workingDirectory,
isCommitting: isCommitting,
}}
onRowClick={this.props.onRowClick}
onScroll={this.onScroll}
setScrollTop={this.props.changesListScrollTop}
onRowKeyDown={this.onRowKeyDown}
onRowContextMenu={this.onItemContextMenu}
ariaLabel={filesDescription}
/>
</div>
<List
ref={this.listRef}
id="changes-list"
rowCount={files.length}
rowHeight={RowHeight}
rowRenderer={this.renderRow}
getRowAriaLabel={this.getFileAriaLabel}
selectedRows={this.state.selectedRows}
selectionMode="multi"
onSelectionChanged={this.props.onFileSelectionChanged}
invalidationProps={{
workingDirectory: workingDirectory,
isCommitting: isCommitting,
}}
onRowClick={this.props.onRowClick}
onScroll={this.onScroll}
setScrollTop={this.props.changesListScrollTop}
onRowKeyDown={this.onRowKeyDown}
onRowContextMenu={this.onItemContextMenu}
/>
{this.renderStashedChanges()}
{this.renderCommitMessageForm()}
</div>
</>
)
}
}

View file

@ -8,6 +8,8 @@ import { Avatar } from '../lib/avatar'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { LinkButton } from '../lib/link-button'
import { ToggledtippedContent } from '../lib/toggletipped-content'
import { TooltipDirection } from '../lib/tooltip'
interface ICommitMessageAvatarState {
readonly isPopoverOpen: boolean
@ -20,13 +22,6 @@ interface ICommitMessageAvatarProps {
/** The user whose avatar should be displayed. */
readonly user?: IAvatarUser
/**
* The title of the avatar.
* Defaults to the name and email if undefined and is
* skipped completely if title is null
*/
readonly title?: string | JSX.Element | null
/** Current email address configured by the user. */
readonly email?: string
@ -68,6 +63,26 @@ export class CommitMessageAvatar extends React.Component<
}
}
private getTitle(): string | JSX.Element | undefined {
const { user } = this.props
if (user === undefined) {
return 'Unknown user'
}
const { name, email } = user
if (user.name) {
return (
<>
Committing as <strong>{name}</strong> {email}
</>
)
}
return email
}
public render() {
return (
<div className="commit-message-avatar-component">
@ -78,12 +93,18 @@ export class CommitMessageAvatar extends React.Component<
onClick={this.onAvatarClick}
>
{this.renderWarningBadge()}
<Avatar user={this.props.user} title={this.props.title} />
<Avatar user={this.props.user} title={null} />
</Button>
)}
{!this.props.warningBadgeVisible && (
<Avatar user={this.props.user} title={this.props.title} />
<ToggledtippedContent
tooltip={this.getTitle()}
direction={TooltipDirection.NORTH}
ariaLabel="Show Commit Author Details"
>
<Avatar user={this.props.user} title={null} />
</ToggledtippedContent>
)}
{this.state.isPopoverOpen && this.renderPopover()}
@ -137,6 +158,11 @@ export class CommitMessageAvatar extends React.Component<
const updateEmailTitle = __DARWIN__ ? 'Update Email' : 'Update email'
const userName =
this.props.user && this.props.user.name
? ` for ${this.props.user.name}`
: ''
return (
<Popover
caretPosition={PopoverCaretPosition.LeftBottom}
@ -150,7 +176,7 @@ export class CommitMessageAvatar extends React.Component<
<div>
The email in your global Git config (
<span className="git-email">{this.props.email}</span>) doesn't match
your GitHub{accountTypeSuffix} account.{' '}
your GitHub{accountTypeSuffix} account{userName}.{' '}
<LinkButton
ariaLabel="Learn more about commit attribution"
uri="https://docs.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user"

View file

@ -33,9 +33,9 @@ import { Popup, PopupType } from '../../models/popup'
import { RepositorySettingsTab } from '../repository-settings/repository-settings'
import { IdealSummaryLength } from '../../lib/wrap-rich-text-commit-message'
import { isEmptyOrWhitespace } from '../../lib/is-empty-or-whitespace'
import { TooltippedContent } from '../lib/tooltipped-content'
import { TooltipDirection } from '../lib/tooltip'
import { pick } from '../../lib/pick'
import { ToggledtippedContent } from '../lib/toggletipped-content'
const addAuthorIcon = {
w: 18,
@ -426,11 +426,6 @@ export class CommitMessage extends React.Component<
private renderAvatar() {
const { commitAuthor, repository } = this.props
const { gitHubRepository } = repository
const avatarTitle = commitAuthor ? (
<>
Committing as <strong>{commitAuthor.name}</strong> {commitAuthor.email}
</>
) : undefined
const avatarUser: IAvatarUser | undefined =
commitAuthor !== null
? getAvatarUserFromAuthor(commitAuthor, gitHubRepository)
@ -449,7 +444,6 @@ export class CommitMessage extends React.Component<
return (
<CommitMessageAvatar
user={avatarUser}
title={avatarTitle}
email={commitAuthor?.email}
isEnterpriseAccount={
repositoryAccount?.endpoint !== getDotComAPIEndpoint()
@ -827,7 +821,7 @@ export class CommitMessage extends React.Component<
private renderSummaryLengthHint(): JSX.Element | null {
return (
<TooltippedContent
<ToggledtippedContent
delay={0}
tooltip={
<>
@ -842,9 +836,10 @@ export class CommitMessage extends React.Component<
direction={TooltipDirection.NORTH}
className="length-hint"
tooltipClassName="length-hint-tooltip"
ariaLabel="Open Summary Length Info"
>
<Octicon symbol={OcticonSymbol.lightBulb} />
</TooltippedContent>
</ToggledtippedContent>
)
}
@ -879,7 +874,7 @@ export class CommitMessage extends React.Component<
{this.renderAvatar()}
<AutocompletingInput
isRequired={true}
required={true}
className={summaryInputClassName}
placeholder={this.props.placeholder}
value={this.state.summary}

View file

@ -376,7 +376,7 @@ export class ChangesSidebar extends React.Component<IChangesSidebarProps, {}> {
)
return (
<div className="panel">
<div className="panel" role="tabpanel" aria-labelledby="changes-tab">
<ChangesList
ref={this.changesListRef}
dispatcher={this.props.dispatcher}

View file

@ -354,8 +354,12 @@ export class CreateBranch extends React.Component<
<div>
Your new branch will be based on your currently checked out branch (
<Ref>{currentBranchName}</Ref>){this.renderForkLinkSuffix()}.{' '}
<Ref>{currentBranchName}</Ref> is the {defaultBranchLink} for your
repository.
{defaultBranch?.name === currentBranchName && (
<>
<Ref>{currentBranchName}</Ref> is the {defaultBranchLink} for your
repository.
</>
)}
</div>
)
} else {

View file

@ -132,6 +132,59 @@ interface IDialogProps {
readonly loading?: boolean
}
/**
* If role is alertdialog, ariaDescribedBy is required.
*/
interface IAlertDialogProps extends IDialogProps {
/** This is used to point to an element containing content pertinent to the
* users workflow. This should be provided for dialogs that are alerts or
* confirmations so that that the information that is interrupting the user's
* workflow is screen reader announced and acquire a response */
readonly ariaDescribedBy: string
/** By default, a dialog has role of "dialog" and requires the use of an
* "aria-label" or "aria-labelledby" to accessibily announce the title or
* purpose of the header. This is typically accomplished by providing the
* `title` prop and the dialog component will take care of adding the
* `aria-labelledby` attribute.
*
* However, if the dialog is an alert or confirmation dialog we should use the
* role of `alertdialog` AND the `ariaDescribedBy` prop should be provided
* containing the id of the element with the information required by the user
* to proceed or be made aware of to ensure it is also read by screen readers.
*
*
* https://www.w3.org/TR/wai-aria-1.1/#alertdialog
* "An alert dialog is a modal dialog that interrupts the user's workflow to
* communicate an important message and acquire a response. Examples include
* action confirmation prompts and error message confirmations. The
* alertdialog role enables assistive technologies and browsers to distinguish
* alert dialogs from other dialogs so they have the option of giving alert
* dialogs special treatment, such as playing a system alert sound."
* */
readonly role: 'alertdialog'
}
/**
* If role is undefined or dialog, ariaDescribedBy is optional.
*/
interface IDescribedByDialogProps extends IDialogProps {
/** This is used to point to an element containing content pertinent to the
* users workflow. This should be provided for dialogs that are alerts or
* confirmations so that that the information that is interrupting the user's
* workflow is screen reader announced and acquire a response */
readonly ariaDescribedBy?: string
/** By default, a dialog has role of "dialog". This is only required for a
* role of 'alertdialog' in which case `ariaDescribedBy` must also be
* provided */
readonly role?: 'dialog'
}
/** Interface union to force usage of `ariaDescribedBy` if role of `alertdialog`
* is used */
type DialogProps = IAlertDialogProps | IDescribedByDialogProps
interface IDialogState {
/**
* When a dialog is shown we wait for a few hundred milliseconds before
@ -163,7 +216,7 @@ interface IDialogState {
* underlying elements. It's not possible to use the tab key to move focus
* out of the dialog without first dismissing it.
*/
export class Dialog extends React.Component<IDialogProps, IDialogState> {
export class Dialog extends React.Component<DialogProps, IDialogState> {
public static contextType = DialogStackContext
public declare context: React.ContextType<typeof DialogStackContext>
@ -190,7 +243,7 @@ export class Dialog extends React.Component<IDialogProps, IDialogState> {
private readonly resizeObserver: ResizeObserver
private resizeDebounceId: number | null = null
public constructor(props: IDialogProps) {
public constructor(props: DialogProps) {
super(props)
this.state = { isAppearing: true }
@ -507,7 +560,7 @@ export class Dialog extends React.Component<IDialogProps, IDialogState> {
this.checkIsTopMostDialog(false)
}
public componentDidUpdate(prevProps: IDialogProps) {
public componentDidUpdate(prevProps: DialogProps) {
if (!this.props.title && this.state.titleId) {
this.updateTitleId()
}
@ -672,10 +725,12 @@ export class Dialog extends React.Component<IDialogProps, IDialogState> {
<dialog
ref={this.onDialogRef}
id={this.props.id}
role={this.props.role}
onMouseDown={this.onDialogMouseDown}
onKeyDown={this.onKeyDown}
className={className}
aria-labelledby={this.state.titleId}
aria-describedby={this.props.ariaDescribedBy}
tabIndex={-1}
>
{this.renderHeader()}

View file

@ -89,7 +89,7 @@ import {
import { TipState, IValidBranch } from '../../models/tip'
import { Banner, BannerType } from '../../models/banner'
import { ApplicationTheme, ICustomTheme } from '../lib/application-theme'
import { ApplicationTheme } from '../lib/application-theme'
import { installCLI } from '../lib/install-cli'
import {
executeMenuItem,
@ -2449,13 +2449,6 @@ export class Dispatcher {
return this.appStore._setSelectedTheme(theme)
}
/**
* Set the custom application-wide theme
*/
public setCustomTheme(theme: ICustomTheme) {
return this.appStore._setCustomTheme(theme)
}
/**
* Increments either the `repoWithIndicatorClicked` or
* the `repoWithoutIndicatorClicked` metric

View file

@ -155,7 +155,7 @@ export class CompareSidebar extends React.Component<
const placeholderText = getPlaceholderText(this.props.compareState)
return (
<div id="compare-view">
<div id="compare-view" role="tabpanel" aria-labelledby="history-tab">
<div className="compare-form">
<FancyTextBox
symbol={OcticonSymbol.gitBranch}

View file

@ -56,16 +56,6 @@ import { ApiRepositoriesStore } from '../lib/stores/api-repositories-store'
import { CommitStatusStore } from '../lib/stores/commit-status-store'
import { PullRequestCoordinator } from '../lib/stores/pull-request-coordinator'
// We're using a polyfill for the upcoming CSS4 `:focus-ring` pseudo-selector.
// This allows us to not have to override default accessibility driven focus
// styles for buttons in the case when a user clicks on a button. This also
// gives better visibility to individuals who navigate with the keyboard.
//
// See:
// https://github.com/WICG/focus-ring
// Focus Ring! -- A11ycasts #16: https://youtu.be/ilj2P5-5CjI
import 'wicg-focus-ring'
import { sendNonFatalException } from '../lib/helpers/non-fatal-exception'
import { enableUnhandledRejectionReporting } from '../lib/feature-flag'
import { AheadBehindStore } from '../lib/stores/ahead-behind-store'

View file

@ -9,20 +9,6 @@ import {
} from '../main-process-proxy'
import { ThemeSource } from './theme-source'
/** Interface for set of customizable styles */
export interface ICustomTheme {
// application background color
background: string
// application border color
border: string
// main application text color
text: string
// used to indicate a selected item or action button
activeItem: string
// text used on selected item or action button
activeText: string
}
/**
* A set of the user-selectable appearances (aka themes)
*/
@ -30,13 +16,9 @@ export enum ApplicationTheme {
Light = 'light',
Dark = 'dark',
System = 'system',
HighContrast = 'highContrast',
}
export type ApplicableTheme =
| ApplicationTheme.Light
| ApplicationTheme.Dark
| ApplicationTheme.HighContrast
export type ApplicableTheme = ApplicationTheme.Light | ApplicationTheme.Dark
/**
* Gets the friendly name of an application theme for use
@ -48,7 +30,6 @@ export function getThemeName(theme: ApplicationTheme): ThemeSource {
case ApplicationTheme.Light:
return 'light'
case ApplicationTheme.Dark:
case ApplicationTheme.HighContrast:
return 'dark'
default:
return 'system'
@ -91,8 +72,7 @@ function getApplicationThemeSetting(): ApplicationTheme {
if (
themeSetting === ApplicationTheme.Light ||
themeSetting === ApplicationTheme.Dark ||
themeSetting === ApplicationTheme.HighContrast
themeSetting === ApplicationTheme.Dark
) {
return themeSetting
}
@ -140,9 +120,11 @@ export function supportsSystemThemeChanges(): boolean {
// was released October 2nd, 2018 and the feature can just be "attained" by upgrading
// See https://github.com/desktop/desktop/issues/9015 for more
return isWindows10And1809Preview17666OrLater()
} else {
// enabling this for Linux users as an experiment to see if distributions
// work with how Chromium detects theme changes
return true
}
return false
}
function isDarkModeEnabled(): Promise<boolean> {

View file

@ -8,6 +8,7 @@ import { Button } from './button'
import { TextBox } from './text-box'
import { Errors } from './errors'
import { getDotComAPIEndpoint } from '../../lib/api'
import { HorizontalRule } from './horizontal-rule'
/** Text to let the user know their browser will send them back to GH Desktop */
export const BrowserRedirectMessage =
@ -104,6 +105,8 @@ export class AuthenticationForm extends React.Component<
<TextBox
label="Username or email address"
disabled={disabled}
required={true}
displayInvalidState={false}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true}
onValueChanged={this.onUsernameChange}
@ -113,6 +116,8 @@ export class AuthenticationForm extends React.Component<
label="Password"
type="password"
disabled={disabled}
required={true}
displayInvalidState={false}
onValueChanged={this.onPasswordChange}
/>
@ -151,16 +156,6 @@ export class AuthenticationForm extends React.Component<
)
}
private renderSignInWithBrowser() {
return (
<>
{this.renderSignInWithBrowserButton()}
{this.props.additionalButtons}
</>
)
}
/**
* Show the sign in locally form
*
@ -174,7 +169,8 @@ export class AuthenticationForm extends React.Component<
this.renderUsernamePassword()
) : (
<>
{this.renderSignInWithBrowser()}
{this.renderSignInWithBrowserButton()}
<HorizontalRule title="or" />
{this.renderUsernamePassword()}
</>
)

View file

@ -179,12 +179,13 @@ export class AuthorInput extends React.Component<
<div className="sr-only" aria-live="polite" aria-atomic="true">
{this.state.lastActionDescription}
</div>
<div id="author-input-label" className="label">
Co-Authors&nbsp;
</div>
<div className="shadow-input" ref={this.shadowInputRef} />
<label id="author-input-label" className="label" htmlFor="author-input">
Co-Authors&nbsp;
</label>
{this.renderAuthors()}
<AutocompletingInput<UserHit>
elementId="author-input"
placeholder="@username"
isCombobox={true}
alwaysAutocomplete={true}
@ -194,7 +195,6 @@ export class AuthorInput extends React.Component<
)}
ref={this.autocompletingInputRef}
onElementRef={this.onInputRef}
elementAriaLabelledBy="author-input-label"
onAutocompleteItemSelected={this.onAutocompleteItemSelected}
onValueChanged={this.onCoAuthorsValueChanged}
onKeyDown={this.onInputKeyDown}

View file

@ -23,6 +23,10 @@ interface ICheckboxProps {
/** The label for the checkbox. */
readonly label?: string | JSX.Element
/** An aria description of a checkbox - intended to provide more verbose
* information than a label that a the user might need */
readonly ariaDescribedBy?: string
}
interface ICheckboxState {
@ -61,6 +65,10 @@ export class Checkbox extends React.Component<ICheckboxProps, ICheckboxState> {
}
}
public focus() {
this.input?.focus()
}
private updateInputState() {
const input = this.input
if (input) {
@ -94,6 +102,7 @@ export class Checkbox extends React.Component<ICheckboxProps, ICheckboxState> {
onChange={this.onChange}
ref={this.onInputRef}
disabled={this.props.disabled}
aria-describedby={this.props.ariaDescribedBy}
/>
{this.renderLabel()}
</div>

View file

@ -1,93 +0,0 @@
interface IParsedRGB {
red: number
blue: number
green: number
usePound: boolean
}
/**
* Extracts the red, blue, green values from a hex string into a object.
*
* @param hex - a 6 character string that represents a hex value -- #123456 or
* 123456.
* Note: Providing a non hex string won't fail, but may not be meaninful.
* @returns - a IParsedRGB object that houses a parsed red, green, blue and whether the value was prefaced with a #.
*/
function parseHex(hex: string): IParsedRGB {
let usePound = false
if (hex[0] === '#') {
hex = hex.slice(1)
usePound = true
}
// convert rrggbb to decimal
const num = parseInt(hex, 16)
return {
red: num >> 16,
blue: (num >> 8) & 0x00ff,
green: num & 0x0000ff,
usePound,
}
}
/**
* Given a number, it caps it at the rgb extremes of 0 and 255.
* Example:
* colorSegment = -1, returns 0.
* colorSegment = 256, returns 255.
* colorSegment = 100, returns 100.
*
* @param colorSegment - number.
* @returns - a number between 0 and 255 inclusive.
*/
function capColorSegment(colorSegment: number): number {
return colorSegment > 255 ? 255 : colorSegment < 0 ? 0 : colorSegment
}
/**
* This method takes a hex value splits it into red, green, blue values and adds
* the amount number to them. Thus, providing a positive number will lighten the
* value by pushing the value closer to 255, 255, 255 aka white. Providing a
* negative number will darken the value by pushing the value closer to 0, 0, 0
* aka black.
*
* Note: Since this caps the red, green, and blue values at 255 and 0, this
* method will also have a flattening to black or white effect to the color if
* the color already has a value you near the extremes or the amount provided is
* rather high/low.
*
* @param hex - a 6 character string that represents a hex value -- #123456 or
* 123456.
* Note: Providing a non hex string won't fail, but may not be meaninful.
* @param amount - a number. Positive lightens. Negative darkens.
* @returns - a hex value. If prefaced with a '#', it will be returnes prefaced
* with a '#'.
*/
export function lightenDarkenHexColor(hex: string, amount: number) {
const parsedHex: IParsedRGB = parseHex(hex)
const red = capColorSegment(parsedHex.red + amount)
const blue = capColorSegment(parsedHex.blue + amount)
const green = capColorSegment(parsedHex.green + amount)
const backToHex = (green | (blue << 8) | (red << 16)).toString(16)
return (parsedHex.usePound ? '#' : '') + backToHex
}
/**
* Calculates a luma range 0 to 255 and returns true if > 150.
*
* @param hex - a 6 character string that represents a hex value -- #123456 or 123456
* Note: Providing a non hex string won't fail, but may not be meaninful.
* @returns Calculates a luma range 0 to 255 and returns true if luma > 150.
*/
export function isHexColorLight(hex: string): boolean {
const { red, green, blue } = parseHex(hex)
// The resulting luma value range is 0..255, where 0 is the darkest and 255 is the lightest.
const luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue // per ITU-R BT.709
return luma > 150
}

View file

@ -1,45 +0,0 @@
import { ApplicationTheme, ICustomTheme } from './application-theme'
import { lightenDarkenHexColor } from './color-manipulation'
/**
* An array to hold default custom themes.
*
* This currently only has high-contrast, but if we expanded on customizable
* themes in the future. We could just add to this array for other defaults.
*/
export const CustomThemeDefaults = {
[ApplicationTheme.HighContrast]: {
background: '#090c11',
border: '#7a828e',
text: '#f0f3f6',
activeItem: '#9ea7b3',
activeText: '#090c11',
},
}
/**
* This takes a custom theme object and builds a custom theme style sheet base on the class
* .custom-theme.
*
* Currently, our only custom theme is a high-contrast theme, thus there are
* styles that are specifically added for this purpose such as adding borders
* or backgrounds to things that didn't have borders in our non-high-contrast
* themes.
*
* @param customTheme - object with custom theme object
*/
export function buildCustomThemeStyles(customTheme: ICustomTheme): string {
const { background, text, activeItem, activeText, border } = customTheme
const secondaryActiveColor = lightenDarkenHexColor(activeItem, 20)
const secondaryBackgroundColor = lightenDarkenHexColor(background, 20)
return `body.theme-high-contrast {
--hc-background-color: ${background};
--hc-secondary-background-color: ${secondaryBackgroundColor};
--hc-border-color: ${border};
--hc-text-color: ${text};
--hc-active-item-color: ${activeItem};
--hc-secondary-active-item-color: ${secondaryActiveColor};
--hc-active-text-color: ${activeText};
}`
}

View file

@ -14,6 +14,10 @@ interface IErrorsProps {
export class Errors extends React.Component<IErrorsProps, {}> {
public render() {
const className = classNames('errors-component', this.props.className)
return <div className={className}>{this.props.children}</div>
return (
<div className={className} role="alert">
{this.props.children}
</div>
)
}
}

View file

@ -0,0 +1,10 @@
import React from 'react'
/** Horizontal rule/separator with optional title. */
export const HorizontalRule: React.FunctionComponent<{
readonly title?: string
}> = props => (
<div className="horizontal-rule">
<span className="horizontal-rule-content">{props.title}</span>
</div>
)

View file

@ -23,9 +23,6 @@ interface IListRowProps {
/** callback to fire when the DOM element is created */
readonly onRowRef?: (index: number, element: HTMLDivElement | null) => void
/** callback to fire when the row receives a mouseover event */
readonly onRowMouseOver: (index: number, e: React.MouseEvent<any>) => void
/** callback to fire when the row receives a mousedown event */
readonly onRowMouseDown: (index: number, e: React.MouseEvent<any>) => void
@ -66,14 +63,6 @@ interface IListRowProps {
/** a custom css class to apply to the row */
readonly className?: string
/**
* aria label value for screen readers
*
* Note: you may need to apply an aria-hidden attribute to any child text
* elements for this to take precedence.
*/
readonly ariaLabel?: string
}
export class ListRow extends React.Component<IListRowProps, {}> {
@ -81,10 +70,6 @@ export class ListRow extends React.Component<IListRowProps, {}> {
this.props.onRowRef?.(this.props.rowIndex, elem)
}
private onRowMouseOver = (e: React.MouseEvent<HTMLDivElement>) => {
this.props.onRowMouseOver(this.props.rowIndex, e)
}
private onRowMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
this.props.onRowMouseDown(this.props.rowIndex, e)
}
@ -131,18 +116,15 @@ export class ListRow extends React.Component<IListRowProps, {}> {
const style = { ...this.props.style, width: '100%' }
return (
// eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
<div
id={this.props.id}
role="option"
aria-setsize={this.props.rowCount}
aria-posinset={this.props.rowIndex + 1}
aria-selected={this.props.selected}
aria-label={this.props.ariaLabel}
role="option"
aria-selected={this.props.selectable ? this.props.selected : undefined}
className={className}
tabIndex={this.props.tabIndex}
ref={this.onRef}
onMouseOver={this.onRowMouseOver}
onMouseDown={this.onRowMouseDown}
onMouseUp={this.onRowMouseUp}
onClick={this.onRowClick}

View file

@ -137,8 +137,7 @@ interface IListProps {
*
* @param row - The index of the row that was just selected
* @param source - The kind of user action that provoked the change, either
* a pointer device press, hover (if selectOnHover is set) or
* a keyboard event (arrow up/down)
* a pointer device press or a keyboard event (arrow up/down)
*/
readonly onSelectedRowChanged?: (row: number, source: SelectionSource) => void
@ -152,8 +151,7 @@ interface IListProps {
* @param start - The index of the first selected row
* @param end - The index of the last selected row
* @param source - The kind of user action that provoked the change, either
* a pointer device press, hover (if selectOnHover is set) or
* a keyboard event (arrow up/down)
* a pointer device press or a keyboard event (arrow up/down)
*/
readonly onSelectedRangeChanged?: (
start: number,
@ -169,8 +167,7 @@ interface IListProps {
*
* @param rows - The indexes of the row(s) that are part of the selection
* @param source - The kind of user action that provoked the change, either
* a pointer device press, hover (if selectOnHover is set) or
* a keyboard event (arrow up/down)
* a pointer device press or a keyboard event (arrow up/down)
*/
readonly onSelectionChanged?: (
rows: ReadonlyArray<number>,
@ -241,33 +238,20 @@ interface IListProps {
/** The row that should be scrolled to when the list is rendered. */
readonly scrollToRow?: number
/** Whether or not selection should follow pointer device */
readonly selectOnHover?: boolean
/** Type of elements that can be inserted in the list via drag & drop. Optional. */
readonly insertionDragType?: DragType
/**
* Whether or not to explicitly move focus to a row if it was selected
* by hovering (has no effect if selectOnHover is not set). Defaults to
* true if not defined.
*/
readonly focusOnHover?: boolean
/**
* The number of pixels from the top of the list indicating
* where to scroll do on rendering of the list.
*/
readonly setScrollTop?: number
/**
* Optional callback for providing an aria label for screen readers for each
* row.
*
* Note: you may need to apply an aria-hidden attribute to any child text
* elements for this to take precedence.
*/
readonly getRowAriaLabel?: (row: number) => string | undefined
/** The aria-labelledby attribute for the list component. */
readonly ariaLabelledBy?: string
/** The aria-label attribute for the list component. */
readonly ariaLabel?: string
}
interface IListState {
@ -336,6 +320,11 @@ export class List extends React.Component<IListProps, IListState> {
): React.HTMLProps<HTMLDivElement> => ({
onKeyDown: this.onKeyDown,
'aria-activedescendant': activeDescendant,
'aria-multiselectable':
this.props.selectionMode === 'multi' ||
this.props.selectionMode === 'range'
? 'true'
: undefined,
})
)
@ -670,20 +659,6 @@ export class List extends React.Component<IListProps, IListState> {
this.props.onRowContextMenu?.(row, e)
}
private onRowMouseOver = (row: number, event: React.MouseEvent<any>) => {
if (this.props.selectOnHover && this.canSelectRow(row)) {
if (!this.props.selectedRows.includes(row)) {
this.props.onSelectionChanged?.([row], { kind: 'hover', event })
// By calling scrollRowToVisible we ensure that hovering over a partially
// visible item at the top or bottom of the list scrolls it into view but
// more importantly `scrollRowToVisible` automatically manages focus so
// using it here allows us to piggy-back on its focus-preserving magic
// even though we could theoretically live without scrolling
this.scrollRowToVisible(row, this.props.focusOnHover !== false)
}
}
}
/** Convenience method for invoking canSelectRow callback when it exists */
private canSelectRow = (rowIndex: number) => {
return this.props.canSelectRow ? this.props.canSelectRow(rowIndex) : true
@ -959,11 +934,6 @@ export class List extends React.Component<IListProps, IListState> {
const id = this.getRowId(rowIndex)
const ariaLabel =
this.props.getRowAriaLabel !== undefined
? this.props.getRowAriaLabel(rowIndex)
: undefined
return (
<ListRow
key={params.key}
@ -972,12 +942,10 @@ export class List extends React.Component<IListProps, IListState> {
rowCount={this.props.rowCount}
rowIndex={rowIndex}
selected={selected}
ariaLabel={ariaLabel}
onRowClick={this.onRowClick}
onRowKeyDown={this.onRowKeyDown}
onRowMouseDown={this.onRowMouseDown}
onRowMouseUp={this.onRowMouseUp}
onRowMouseOver={this.onRowMouseOver}
onRowFocus={this.onRowFocus}
onRowBlur={this.onRowBlur}
onContextMenu={this.onRowContextMenu}
@ -1009,7 +977,13 @@ export class List extends React.Component<IListProps, IListState> {
}
return (
<div ref={this.onRef} id={this.props.id} className="list">
<div
ref={this.onRef}
id={this.props.id}
className="list"
aria-labelledby={this.props.ariaLabelledBy}
aria-label={this.props.ariaLabel}
>
{content}
</div>
)
@ -1051,12 +1025,10 @@ export class List extends React.Component<IListProps, IListState> {
* @param height - The height of the Grid as given by AutoSizer
*/
private renderGrid(width: number, height: number) {
// The currently selected list item is focusable but if
// there's no focused item (and there's items to switch between)
// the list itself needs to be focusable so that you can reach
// it with keyboard navigation and select an item.
const tabIndex =
this.props.selectedRows.length < 1 && this.props.rowCount > 0 ? 0 : -1
// The currently selected list item is focusable but if there's no focused
// item the list itself needs to be focusable so that you can reach it with
// keyboard navigation and select an item.
const tabIndex = this.props.selectedRows.length < 1 ? 0 : -1
// we select the last item from the selection array for this prop
const activeDescendant =

View file

@ -19,14 +19,17 @@ interface IRadioButtonProps<T> {
readonly checked: boolean
/**
* The label of the radio button.
* The label of the radio button. If not provided, the children are used
*/
readonly label: string | JSX.Element
readonly label?: string | JSX.Element
/**
* The value of the radio button.
*/
readonly value: T
/** Optional: The tab index of the radio button */
readonly tabIndex?: number
}
interface IRadioButtonState {
@ -58,8 +61,11 @@ export class RadioButton<T extends string> extends React.Component<
value={this.props.value}
checked={this.props.checked}
onChange={this.onSelected}
tabIndex={this.props.tabIndex}
/>
<label htmlFor={this.state.inputId}>{this.props.label}</label>
<label htmlFor={this.state.inputId}>
{this.props.label ?? this.props.children}
</label>
</div>
)
}

View file

@ -0,0 +1,74 @@
import * as React from 'react'
import { RadioButton } from './radio-button'
interface IRadioGroupProps<T> {
/** The id of the element that serves as the menu's accessibility label */
readonly ariaLabelledBy?: string
/**
* The currently selected item, denoted by its key.
*/
readonly selectedKey: T
/**
* The keys of the radio buttons to display, in order of the radio buttons.
*/
readonly radioButtonKeys: ReadonlyArray<T>
/** Optional class for radio group*/
readonly className?: string
/**
* A function that's called whenever the selected item changes, either
* as a result of a click using a pointer device or as a result of the user
* hitting an up/down while the component has focus.
*
* The key argument corresponds to the key property of the selected item.
*/
readonly onSelectionChanged: (key: T) => void
/** Render radio button label contents */
readonly renderRadioButtonLabelContents: (key: T) => JSX.Element
}
/**
* A component for presenting a small number of choices to the user.
*/
export class RadioGroup<T extends string> extends React.Component<
IRadioGroupProps<T>
> {
private onSelectionChanged = (key: T) => {
this.props.onSelectionChanged(key)
}
private renderRadioButtons() {
const { radioButtonKeys, selectedKey } = this.props
return radioButtonKeys.map(key => {
const checked = selectedKey === key
return (
<RadioButton<T>
key={key}
checked={checked}
value={key}
onSelected={this.onSelectionChanged}
tabIndex={checked ? 0 : -1}
>
{this.props.renderRadioButtonLabelContents(key)}
</RadioButton>
)
})
}
public render() {
return (
<div
role="radiogroup"
aria-labelledby={this.props.ariaLabelledBy}
className={this.props.className}
>
{this.renderRadioButtons()}
</div>
)
}
}

View file

@ -2,6 +2,9 @@ import * as React from 'react'
import classNames from 'classnames'
interface IRowProps {
/** The id of the internal element */
readonly id?: string
/** The class name for the internal element. */
readonly className?: string
}
@ -14,6 +17,10 @@ interface IRowProps {
export class Row extends React.Component<IRowProps, {}> {
public render() {
const className = classNames('row-component', this.props.className)
return <div className={className}>{this.props.children}</div>
return (
<div id={this.props.id} className={className}>
{this.props.children}
</div>
)
}
}

View file

@ -25,6 +25,15 @@ export interface ITextBoxProps {
/** Whether the input field is disabled. */
readonly disabled?: boolean
/** Indicates if input field should be required */
readonly required?: boolean
/**
* Indicates whether or not the control displays an invalid state.
* Default: true
*/
readonly displayInvalidState?: boolean
/**
* Called when the user changes the value in the input field.
*
@ -241,7 +250,11 @@ export class TextBox extends React.Component<ITextBoxProps, ITextBoxState> {
const inputId = label ? this.state.inputId : undefined
return (
<div className={classNames('text-box-component', className)}>
<div
className={classNames('text-box-component', className, {
'no-invalid-state': this.props.displayInvalidState === false,
})}
>
{label && <label htmlFor={inputId}>{label}</label>}
<input
@ -262,6 +275,7 @@ export class TextBox extends React.Component<ITextBoxProps, ITextBoxState> {
spellCheck={this.props.spellcheck === true}
aria-label={this.props.ariaLabel}
aria-controls={this.props.ariaControls}
required={this.props.required}
/>
</div>
)

View file

@ -0,0 +1,125 @@
import * as React from 'react'
import { ITooltipProps, Tooltip } from './tooltip'
import { createObservableRef } from './observable-ref'
import classNames from 'classnames'
import { AriaLiveContainer } from '../accessibility/aria-live-container'
/**
* IToggledtippedContentProps is a superset of ITooltipProps but does not
* define the `target` prop as that's set programatically in render
*/
interface IToggledtippedContentProps
extends Omit<ITooltipProps<HTMLElement>, 'target'> {
/** The tooltip contents */
readonly tooltip: JSX.Element | string | undefined
/**
* An optional additional class name to set on the tooltip in order to be able
* to apply specific styles to the tooltip
*/
readonly tooltipClassName?: string
/** An optional class name to set on the wrapper element */
readonly className?: string
/** An optional aria-label property in case children is not descriptive
* - for example an icon */
readonly ariaLabel?: string
}
interface IToggledtippedContentState {
/** State of when the tooltip is visible */
readonly tooltipVisible: boolean
}
/**
* A less keyboard toggleable version of the Tooltip content component for when
* it's acceptable to add a wrapping element around the content. The content
* will be wrapped in a button as it is toggleable. supports all the options
* that the Tooltip component does without having to worry about refs.
**/
export class ToggledtippedContent extends React.Component<
IToggledtippedContentProps,
IToggledtippedContentState
> {
private buttonRef: HTMLButtonElement | null = null
private buttonRefObservable = createObservableRef<HTMLButtonElement>()
private shouldForceAriaLiveMessage = false
public constructor(props: IToggledtippedContentProps) {
super(props)
this.state = {
tooltipVisible: false,
}
this.buttonRefObservable.subscribe(this.onButtonRef)
}
private onToggle = () => {
this.shouldForceAriaLiveMessage = !this.shouldForceAriaLiveMessage
}
private onTooltipVisible = () => {
this.setState({ tooltipVisible: true })
}
private onTooltipHidden = () => {
this.setState({ tooltipVisible: false })
}
private onButtonRef = (ref: HTMLButtonElement | null) => {
if (ref === null) {
const currRef = this.buttonRef
currRef?.removeEventListener('tooltip-shown', this.onTooltipVisible)
currRef?.removeEventListener('tooltip-hidden', this.onTooltipHidden)
} else {
ref.addEventListener('tooltip-shown', this.onTooltipVisible)
ref.addEventListener('tooltip-hidden', this.onTooltipHidden)
}
this.buttonRef = ref
}
public render() {
const {
tooltip,
children,
className,
tooltipClassName,
ariaLabel,
...rest
} = this.props
const classes = classNames('toggletip', className)
return (
<button
ref={this.buttonRefObservable}
className={classes}
aria-label={ariaLabel}
aria-haspopup="dialog"
onClick={this.onToggle}
>
<>
{tooltip !== undefined && (
<Tooltip
target={this.buttonRefObservable}
className={tooltipClassName}
isToggleTip={true}
{...rest}
>
{tooltip}
</Tooltip>
)}
{children}
{this.state.tooltipVisible && (
<AriaLiveContainer
shouldForceChange={this.shouldForceAriaLiveMessage}
>
{tooltip}
</AriaLiveContainer>
)}
</>
</button>
)
}
}

View file

@ -109,6 +109,14 @@ export interface ITooltipProps<T> {
* element within in iframe.
*/
readonly tooltipOffset?: DOMRect
/**Optional parameter for toggle tip behavior */
readonly isToggleTip?: boolean
/** Open on target focus - typically only tooltips that target an element with
* ":focus-visible open on focus. This means any time the target it focused it
* opens." */
readonly openOnFocus?: boolean
}
interface ITooltipState {
@ -280,9 +288,12 @@ export class Tooltip<T extends TooltipTarget> extends React.Component<
elem.addEventListener('mousemove', this.onTargetMouseMove)
elem.addEventListener('mousedown', this.onTargetMouseDown)
elem.addEventListener('focus', this.onTargetFocus)
elem.addEventListener('focusin', this.onTargetFocusIn)
elem.addEventListener('focusout', this.onTargetBlur)
elem.addEventListener('blur', this.onTargetBlur)
elem.addEventListener('tooltip-shown', this.onTooltipShown)
elem.addEventListener('tooltip-hidden', this.onTooltipHidden)
elem.addEventListener('click', this.onTargetClick)
}
private removeTooltip(prevTarget: TooltipTarget | null) {
@ -295,7 +306,10 @@ export class Tooltip<T extends TooltipTarget> extends React.Component<
prevTarget.removeEventListener('mousemove', this.onTargetMouseMove)
prevTarget.removeEventListener('mousedown', this.onTargetMouseDown)
prevTarget.removeEventListener('focus', this.onTargetFocus)
prevTarget.removeEventListener('focusin', this.onTargetFocusIn)
prevTarget.removeEventListener('focusout', this.onTargetBlur)
prevTarget.removeEventListener('blur', this.onTargetBlur)
prevTarget.removeEventListener('click', this.onTargetClick)
}
}
@ -318,14 +332,37 @@ export class Tooltip<T extends TooltipTarget> extends React.Component<
}
private onTargetMouseDown = (event: MouseEvent) => {
this.hideTooltip()
if (!this.props.isToggleTip) {
this.hideTooltip()
}
}
private onTargetFocus = (event: FocusEvent) => {
// We only want to show the tooltip if the target was focused as a result of
// keyboard navigation, see
// https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible
if (this.state.target?.matches(':focus-visible')) {
if (
this.state.target?.matches(':focus-visible') &&
!this.props.isToggleTip
) {
this.beginShowTooltip()
}
}
private onTargetClick = (event: FocusEvent) => {
// We only want to handle click events for toggle tips
if (!this.state.show && this.props.isToggleTip) {
this.beginShowTooltip()
}
}
/**
* The focusin event fires when an element has received focus,
* after the focus event. The two events differ in that focusin bubbles, while
* focus does not.
*/
private onTargetFocusIn = (event: FocusEvent) => {
if (this.props.openOnFocus) {
this.beginShowTooltip()
}
}

View file

@ -22,6 +22,9 @@ interface ITooltippedContentProps
/** An optional class name to set on the wrapper element */
readonly className?: string
/** Open on target focus */
readonly openOnFocus?: boolean
}
/**
@ -45,6 +48,7 @@ export class TooltippedContent extends React.Component<ITooltippedContentProps>
<Tooltip
target={this.wrapperRef}
className={tooltipClassName}
openOnFocus={this.props.openOnFocus}
{...rest}
>
{tooltip}

View file

@ -3,57 +3,21 @@ import {
ApplicationTheme,
supportsSystemThemeChanges,
getCurrentlyAppliedTheme,
ICustomTheme,
} from '../lib/application-theme'
import { Row } from '../lib/row'
import { DialogContent } from '../dialog'
import {
VerticalSegmentedControl,
ISegmentedItem,
} from '../lib/vertical-segmented-control'
import { CustomThemeSelector } from './custom-theme-selector'
import { enableHighContrastTheme } from '../../lib/feature-flag'
import { RadioGroup } from '../lib/radio-group'
import { encodePathAsUrl } from '../../lib/path'
interface IAppearanceProps {
readonly selectedTheme: ApplicationTheme
readonly customTheme?: ICustomTheme
readonly onSelectedThemeChanged: (theme: ApplicationTheme) => void
readonly onCustomThemeChanged: (theme: ICustomTheme) => void
}
interface IAppearanceState {
readonly selectedTheme: ApplicationTheme | null
}
const systemTheme: ISegmentedItem<ApplicationTheme> = {
title: 'System',
description: 'Automatically switch theme to match system theme',
key: ApplicationTheme.System,
}
const themes: ReadonlyArray<ISegmentedItem<ApplicationTheme>> = [
{
title: 'Light',
description: 'The default theme of GitHub Desktop',
key: ApplicationTheme.Light,
},
{
title: 'Dark',
description: 'GitHub Desktop is for you too, creatures of the night',
key: ApplicationTheme.Dark,
},
...(enableHighContrastTheme()
? [
{
title: 'High Contrast',
description: 'Customizable High Contrast Theme',
key: ApplicationTheme.HighContrast,
},
]
: []),
...(supportsSystemThemeChanges() ? [systemTheme] : []),
]
export class Appearance extends React.Component<
IAppearanceProps,
IAppearanceState
@ -97,8 +61,41 @@ export class Appearance extends React.Component<
this.props.onSelectedThemeChanged(theme)
}
private onCustomThemeChanged = (theme: ICustomTheme) => {
this.props.onCustomThemeChanged(theme)
public renderThemeSwatch = (theme: ApplicationTheme) => {
const darkThemeImage = encodePathAsUrl(__dirname, 'static/ghd_dark.svg')
const lightThemeImage = encodePathAsUrl(__dirname, 'static/ghd_light.svg')
switch (theme) {
case ApplicationTheme.Light:
return (
<span>
<img src={lightThemeImage} alt="" />
<span className="theme-value-label">Light</span>
</span>
)
case ApplicationTheme.Dark:
return (
<span>
<img src={darkThemeImage} alt="" />
<span className="theme-value-label">Dark</span>
</span>
)
case ApplicationTheme.System:
/** Why three images? The system theme swatch uses the first image
* positioned relatively to get the label container size and uses the
* second and third positioned absolutely over first and third one
* clipped in half to render a split dark and light theme swatch. */
return (
<span>
<span className="system-theme-swatch">
<img src={lightThemeImage} alt="" />
<img src={lightThemeImage} alt="" />
<img src={darkThemeImage} alt="" />
</span>
<span className="theme-value-label">System</span>
</span>
)
}
}
public render() {
@ -112,22 +109,24 @@ export class Appearance extends React.Component<
)
}
const themes = [
ApplicationTheme.Light,
ApplicationTheme.Dark,
...(supportsSystemThemeChanges() ? [ApplicationTheme.System] : []),
]
return (
<DialogContent>
<Row>
<VerticalSegmentedControl
items={themes}
selectedKey={selectedTheme}
onSelectionChanged={this.onSelectedThemeChanged}
/>
</Row>
<Row>
<CustomThemeSelector
onCustomThemeChanged={this.onCustomThemeChanged}
selectedTheme={selectedTheme}
customTheme={this.props.customTheme}
/>
</Row>
<h2 id="theme-heading">Theme</h2>
<RadioGroup<ApplicationTheme>
ariaLabelledBy="theme-heading"
className="theme-selector"
selectedKey={selectedTheme}
radioButtonKeys={themes}
onSelectionChanged={this.onSelectedThemeChanged}
renderRadioButtonLabelContents={this.renderThemeSwatch}
/>
</DialogContent>
)
}

View file

@ -1,193 +0,0 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import * as React from 'react'
import { ApplicationTheme, ICustomTheme } from '../lib/application-theme'
import { SketchPicker } from 'react-color'
import { Button } from '../lib/button'
import { Octicon, syncClockwise } from '../octicons'
import { enableCustomizeTheme } from '../../lib/feature-flag'
import { CustomThemeDefaults } from '../lib/custom-theme'
interface ICustomThemeSelectorProps {
readonly selectedTheme: ApplicationTheme
readonly customTheme?: ICustomTheme
readonly onCustomThemeChanged: (customTheme: ICustomTheme) => void
}
interface ICustomThemeSelectorState {
readonly customTheme?: ICustomTheme
readonly selectedThemeOptionColor: keyof ICustomTheme
readonly isPopoverOpen: boolean
readonly popoverBottom: string
readonly popoverLeft: string
}
export class CustomThemeSelector extends React.Component<
ICustomThemeSelectorProps,
ICustomThemeSelectorState
> {
public constructor(props: ICustomThemeSelectorProps) {
super(props)
this.state = {
customTheme: this.getDefaultCustomTheme(),
isPopoverOpen: false,
selectedThemeOptionColor: 'background',
popoverBottom: '500px',
popoverLeft: '275px',
}
}
private isCustom = (): boolean => {
return (
this.props.selectedTheme === ApplicationTheme.HighContrast &&
enableCustomizeTheme()
)
}
private getDefaultCustomTheme = (): ICustomTheme => {
const { customTheme } = this.props
const defaultTheme =
customTheme === undefined
? CustomThemeDefaults[ApplicationTheme.HighContrast]
: customTheme
if (customTheme === undefined) {
this.props.onCustomThemeChanged(defaultTheme)
}
return defaultTheme
}
private onThemeChange = (color: { hex: string }) => {
this.closePopover()
if (this.state.customTheme === undefined) {
log.error(
'[onThemeChange] - customTheme not defined. This should not be possible.'
)
return
}
this.setState({
customTheme: {
...this.state.customTheme,
[this.state.selectedThemeOptionColor]: color.hex,
},
})
this.props.onCustomThemeChanged(this.state.customTheme)
}
private openPopover = () => {
if (this.state === null || this.state.isPopoverOpen === true) {
return
}
this.setState({ isPopoverOpen: true })
}
private closePopover = () => {
if (this.state === null || this.state.isPopoverOpen === false) {
return
}
this.setState({ isPopoverOpen: false })
}
private onSwatchClick = (selectedThemeOptionColor: keyof ICustomTheme) => {
return (event: any) => {
const popoverBottom = `${event.currentTarget.offsetTop - 300}px`
const popoverLeft = `${event.currentTarget.offsetLeft + 50}px`
this.setState({ selectedThemeOptionColor, popoverBottom, popoverLeft })
this.openPopover()
}
}
private onResetToDefaults = () => {
this.setState({
customTheme: CustomThemeDefaults[ApplicationTheme.HighContrast],
})
this.props.onCustomThemeChanged(
CustomThemeDefaults[ApplicationTheme.HighContrast]
)
}
private renderPopover() {
if (this.state === null || !this.state.isPopoverOpen) {
return
}
if (this.state.customTheme === undefined) {
log.error(
'[onThemeChange] - customTheme not defined. This should not be possible.'
)
return
}
const styles = {
bottom: this.state.popoverBottom,
left: this.state.popoverLeft,
}
return (
<div className="color-picker-container" style={styles}>
<SketchPicker
color={this.state.customTheme[this.state.selectedThemeOptionColor]}
onChangeComplete={this.onThemeChange}
></SketchPicker>
</div>
)
}
private renderThemeOptions = () => {
const customTheme =
this.state.customTheme === undefined
? this.getDefaultCustomTheme()
: this.state.customTheme
const themePropTitleMap = new Map([
['background', 'Background'],
['border', 'Border'],
['text', 'Text'],
['activeItem', 'Active'],
['activeText', 'Active Text'],
])
return Object.entries(customTheme).map(([key, value], i) => {
const keyTyped = key as keyof ICustomTheme
return (
<div key={i} className="swatch-box">
<div
className="theme-option-swatch"
onClick={this.onSwatchClick(keyTyped)}
style={{
backgroundColor: value,
}}
></div>
<div className="theme-option-title">{themePropTitleMap.get(key)}</div>
</div>
)
})
}
public render() {
if (!this.isCustom()) {
return null
}
return (
<div className="custom-theme-selector">
<div className="custom-theme-selecter-header">
<h2>Customize:</h2>
<Button
onClick={this.onResetToDefaults}
tooltip="Reset to High Contrast defaults"
>
<Octicon symbol={syncClockwise} />
</Button>
</div>
<div className="swatch-options">{this.renderThemeOptions()}</div>
{this.renderPopover()}
</div>
)
}
}

View file

@ -20,7 +20,7 @@ import {
InvalidGitAuthorNameMessage,
} from '../lib/identifier-rules'
import { Appearance } from './appearance'
import { ApplicationTheme, ICustomTheme } from '../lib/application-theme'
import { ApplicationTheme } from '../lib/application-theme'
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'
import { Integrations } from './integrations'
import {
@ -62,7 +62,6 @@ interface IPreferencesProps {
readonly selectedExternalEditor: string | null
readonly selectedShell: Shell
readonly selectedTheme: ApplicationTheme
readonly customTheme?: ICustomTheme
readonly repositoryIndicatorsEnabled: boolean
}
@ -339,9 +338,7 @@ export class Preferences extends React.Component<
View = (
<Appearance
selectedTheme={this.props.selectedTheme}
customTheme={this.props.customTheme}
onSelectedThemeChanged={this.onSelectedThemeChanged}
onCustomThemeChanged={this.onCustomThemeChanged}
/>
)
break
@ -489,10 +486,6 @@ export class Preferences extends React.Component<
this.props.dispatcher.setSelectedTheme(theme)
}
private onCustomThemeChanged = (theme: ICustomTheme) => {
this.props.dispatcher.setCustomTheme(theme)
}
private renderFooter() {
const hasDisabledError = this.state.disallowedCharactersMessage != null

View file

@ -29,6 +29,8 @@ import {
InvalidGitAuthorNameMessage,
} from '../lib/identifier-rules'
import { Account } from '../../models/account'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
interface IRepositorySettingsProps {
readonly initialSelectedTab?: RepositorySettingsTab
@ -178,11 +180,23 @@ export class RepositorySettings extends React.Component<
selectedIndex={this.state.selectedTab}
type={TabBarType.Vertical}
>
<span>Remote</span>
<span>{__DARWIN__ ? 'Ignored Files' : 'Ignored files'}</span>
<span>{__DARWIN__ ? 'Git Config' : 'Git config'}</span>
<span>
<Octicon className="icon" symbol={OcticonSymbol.server} />
Remote
</span>
<span>
<Octicon className="icon" symbol={OcticonSymbol.file} />
{__DARWIN__ ? 'Ignored Files' : 'Ignored files'}
</span>
<span>
<Octicon className="icon" symbol={OcticonSymbol.gitCommit} />
{__DARWIN__ ? 'Git Config' : 'Git config'}
</span>
{showForkSettings && (
<span>{__DARWIN__ ? 'Fork Behavior' : 'Fork behavior'}</span>
<span>
<Octicon className="icon" symbol={OcticonSymbol.repoForked} />
{__DARWIN__ ? 'Fork Behavior' : 'Fork behavior'}
</span>
)}
</TabBar>

View file

@ -31,8 +31,8 @@ import { openFile } from './lib/open-file'
import { AheadBehindStore } from '../lib/stores/ahead-behind-store'
import { dragAndDropManager } from '../lib/drag-and-drop-manager'
import { DragType } from '../models/drag-drop'
import { clamp } from '../lib/clamp'
import { PullRequestSuggestedNextAction } from '../models/pull-request'
import { clamp } from '../lib/clamp'
interface IRepositoryViewProps {
readonly repository: Repository
@ -177,12 +177,12 @@ export class RepositoryView extends React.Component<
return (
<TabBar selectedIndex={selectedTab} onTabClicked={this.onTabClicked}>
<span className="with-indicator">
<span className="with-indicator" id="changes-tab">
<span>Changes</span>
{this.renderChangesBadge()}
</span>
<div className="with-indicator">
<div className="with-indicator" id="history-tab">
<span>History</span>
</div>
</TabBar>

View file

@ -20,6 +20,7 @@ import { getWelcomeMessage } from '../../lib/2fa'
import { getDotComAPIEndpoint } from '../../lib/api'
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'
import { Button } from '../lib/button'
import { HorizontalRule } from '../lib/horizontal-rule'
interface ISignInProps {
readonly dispatcher: Dispatcher
@ -239,9 +240,7 @@ export class SignIn extends React.Component<ISignInProps, ISignInState> {
</Button>
</Row>
<div className="horizontal-rule">
<span className="horizontal-rule-content">or</span>
</div>
<HorizontalRule title="or" />
<Row>
<TextBox

View file

@ -69,46 +69,54 @@ export interface IDropdownSuggestedActionProps<T extends string> {
}
interface IDropdownSuggestedActionState<T extends string> {
readonly selectedAction: IDropdownSuggestedActionOption<T>
readonly selectedActionValue: T
}
export class DropdownSuggestedAction<T extends string> extends React.Component<
IDropdownSuggestedActionProps<T>,
IDropdownSuggestedActionState<T>
> {
private get selectedAction() {
const selectedAction = this.props.suggestedActions.find(
a => a.value === this.state.selectedActionValue
)
if (selectedAction === undefined) {
// Shouldn't happen .. but if it did we don't want to crash app, but we want to tell dev what is up
sendNonFatalException(
'NoSuggestedActionsProvided',
new Error(
'The DropdownSuggestedActions component was provided an empty array. It requires an array of at least one item.'
)
)
}
return selectedAction
}
public constructor(props: IDropdownSuggestedActionProps<T>) {
super(props)
const { selectedActionValue, suggestedActions } = props
const firstAction = suggestedActions[0]
const selectedAction =
selectedActionValue !== undefined
? suggestedActions.find(
a => a.value === this.props.selectedActionValue
) ?? firstAction
: firstAction
const firstActionValue = suggestedActions[0].value
this.state = {
selectedAction,
selectedActionValue: selectedActionValue ?? firstActionValue,
}
}
private onActionSelectionChange = (
option: IDropdownSelectButtonOption<T>
) => {
const selectedAction = this.props.suggestedActions.find(
a => a.value === option.value
)
if (selectedAction === undefined) {
return
}
this.setState({ selectedAction })
this.props.onSuggestedActionChanged(selectedAction.value)
this.setState({ selectedActionValue: option.value })
this.props.onSuggestedActionChanged(option.value)
}
private onActionSubmitted = (e: React.MouseEvent<HTMLButtonElement>) => {
const { onClick, menuItemId } = this.state.selectedAction
if (this.selectedAction === undefined) {
// Just a type check
return
}
const { onClick, menuItemId } = this.selectedAction
onClick?.(e)
if (!e.defaultPrevented && menuItemId !== undefined) {
@ -117,16 +125,9 @@ export class DropdownSuggestedAction<T extends string> extends React.Component<
}
public render() {
const { selectedAction } = this.state
if (selectedAction === undefined) {
// Shouldn't happen .. but if it did we don't want to crash app and tell dev what is up
sendNonFatalException(
'NoSuggestedActionsProvided',
new Error(
'The DropdownSuggestedActions component was provided an empty array. It requires an array of at least one item.'
)
)
return null
if (this.selectedAction === undefined) {
// Just a type check
return
}
const {
@ -136,7 +137,7 @@ export class DropdownSuggestedAction<T extends string> extends React.Component<
disabled,
value,
title,
} = selectedAction
} = this.selectedAction
const className = classNames(
'suggested-action',

View file

@ -49,6 +49,8 @@ export class WarnLocalChangesBeforeUndo extends React.Component<
disabled={this.state.isLoading}
onSubmit={this.onSubmit}
onDismissed={this.props.onDismissed}
role="alertdialog"
ariaDescribedBy="undo-warning-message"
>
{this.getWarningDialog()}
<DialogFooter>
@ -64,7 +66,7 @@ export class WarnLocalChangesBeforeUndo extends React.Component<
}
return (
<DialogContent>
<Row>
<Row id="undo-warning-message">
You have changes in progress. Undoing the commit might result in some
of these changes being lost. Do you want to continue anyway?
</Row>

View file

@ -85,8 +85,10 @@ export class Start extends React.Component<IStartProps, {}> {
.<br />
<br />
GitHub Desktop sends usage metrics to improve the product and inform
feature decisions. Read more about what metrics are sent and how we
use them <LinkButton uri={SamplesURL}>here</LinkButton>.
feature decisions.{' '}
<LinkButton uri={SamplesURL}>
Learn more about user metrics.
</LinkButton>
</div>
</div>
)

View file

@ -102,6 +102,7 @@ export class WindowControls extends React.Component<{}, IWindowControlState> {
tabIndex={-1}
className={className}
onClick={onClick}
aria-hidden="true"
>
<svg aria-hidden="true" version="1.1" width="10" height="10">
<path d={path} />

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="228" height="120" fill="none"><g clip-path="url(#a)"><path fill="#25292E" d="M0 0h228v120H0V0Z"/><path fill="#2E5B30" d="M233 21H49v12h184V21ZM233 33H49v12h184V33Z" opacity=".3"/><path fill="#681C1C" d="M233 45H49v12h184V45ZM233 57H49v12h184V57Z" opacity=".3"/><path fill="#1E2125" d="M0 0h228v21H0z"/><rect width="28" height="3" x="15" y="6" fill="#4B535D" rx="1.5"/><rect width="28" height="3" x="15" y="11" fill="#4B535D" rx="1.5"/><rect width="6" height="6" x="5" y="7" fill="#4B535D" rx="3"/><rect width="28" height="3" x="65" y="6" fill="#4B535D" rx="1.5"/><rect width="28" height="3" x="65" y="11" fill="#4B535D" rx="1.5"/><rect width="6" height="6" x="55" y="7" fill="#4B535D" rx="3"/><rect width="28" height="3" x="115" y="6" fill="#4B535D" rx="1.5"/><rect width="28" height="3" x="115" y="11" fill="#4B535D" rx="1.5"/><rect width="6" height="6" x="105" y="7" fill="#4B535D" rx="3"/><path fill="#1E2125" d="M0 88h49v32H0z"/><rect width="42" height="5" x="3" y="90" fill="#121212" rx="2"/><rect width="42" height="5" x="3" y="25" fill="#4B535D" rx="2"/><rect width="42" height="5" x="3" y="31" fill="#4B535D" rx="2"/><rect width="42" height="5" x="3" y="37" fill="#4B535D" rx="2"/><rect width="42" height="14" x="3" y="97" fill="#121212" rx="2"/><rect width="42" height="5" x="3" y="113" fill="#2B64CF" rx="2"/><path fill="#1E2125" d="M48 21h1v99h-1z"/><rect width="105" height="7" x="53" y="35" fill="#2E5B30" rx="3.5"/><rect width="55" height="7" x="53" y="47" fill="#681C1C" rx="3.5"/><rect width="76" height="7" x="53" y="59" fill="#681C1C" rx="3.5"/><rect width="55" height="6" x="54" y="24" fill="#2E5B30" rx="3"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h228v120H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="228" height="120" fill="none"><g clip-path="url(#a)"><path fill="#fff" d="M0 0h228v120H0V0Z"/><path fill="#34D058" d="M233 21H49v12h184V21ZM233 33H49v12h184V33Z" opacity=".3"/><path fill="#D73A49" d="M233 45H49v12h184V45ZM233 57H49v12h184V57Z" opacity=".3"/><path fill="#1E2125" d="M0 0h228v21H0z"/><rect width="28" height="3" x="15" y="6" fill="#4B535D" rx="1.5"/><rect width="28" height="3" x="15" y="11" fill="#4B535D" rx="1.5"/><rect width="6" height="6" x="5" y="7" fill="#4B535D" rx="3"/><rect width="28" height="3" x="65" y="6" fill="#4B535D" rx="1.5"/><rect width="28" height="3" x="65" y="11" fill="#4B535D" rx="1.5"/><rect width="6" height="6" x="55" y="7" fill="#4B535D" rx="3"/><rect width="28" height="3" x="115" y="6" fill="#4B535D" rx="1.5"/><rect width="28" height="3" x="115" y="11" fill="#4B535D" rx="1.5"/><rect width="6" height="6" x="105" y="7" fill="#4B535D" rx="3"/><path fill="#F6F8FA" d="M0 88h49v32H0z"/><rect width="41" height="4" x="3.5" y="90.5" fill="#fff" stroke="#D9D9D9" rx="1.5"/><rect width="42" height="5" x="3" y="25" fill="#D1D5DA" rx="2"/><rect width="42" height="5" x="3" y="31" fill="#D1D5DA" rx="2"/><rect width="42" height="5" x="3" y="37" fill="#D1D5DA" rx="2"/><rect width="41" height="13" x="3.5" y="97.5" fill="#fff" stroke="#D9D9D9" rx="1.5"/><rect width="42" height="5" x="3" y="113" fill="#2B64CF" rx="2"/><path fill="#D9D9D9" d="M48 21h1v99h-1z"/><rect width="105" height="7" x="53" y="35" fill="#34D058" rx="3.5"/><rect width="55" height="7" x="53" y="47" fill="#D73A49" rx="3.5"/><rect width="76" height="7" x="53" y="59" fill="#D73A49" rx="3.5"/><rect width="55" height="6" x="54" y="24" fill="#34D058" rx="3"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h228v120H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -142,23 +142,17 @@ button {
left: 0;
}
// We're using a polyfill for the upcoming CSS4 `:focus-ring` pseudo-selector.
// This allows us to not have to override default accessibility driven focus
// styles for buttons in the case when a user clicks on a button. This also
// gives better visiblity to individuals who navigate with the keyboard.
//
// See:
// https://github.com/WICG/focus-ring
// Focus Ring! -- A11ycasts #16: https://youtu.be/ilj2P5-5CjI
:focus:not(.focus-ring) {
// By default we don't want to show the focus ring on elements unless the focus
// occurred as a result of keyboard navigation.
:focus {
outline: none;
}
// http://stackoverflow.com/questions/7538771/what-is-webkit-focus-ring-color
:focus {
:focus-visible {
outline: auto;
outline-color: var(--focus-color);
}
.more-dropdown {
color: var(--text-color) !important;
margin-left: var(--spacing);

View file

@ -5,4 +5,3 @@
@import 'mixins/checkboard-background';
@import 'mixins/close-button';
@import 'mixins/list-item';
@import 'mixins/high-contrast';

View file

@ -83,7 +83,7 @@ $overlay-background-color: rgba(0, 0, 0, 0.4);
* Background color for custom scroll bars.
* The color is applied to the thumb part of the scrollbar.
*
* Note: Only applies to win32 platforms
* Note: Only applies to win32 and linux platforms
*/
--scroll-bar-thumb-background-color: rgba(0, 0, 0, 0.2);
@ -91,7 +91,7 @@ $overlay-background-color: rgba(0, 0, 0, 0.4);
* Background color for custom scroll bars in their active state.
* The color is applied to the thumb part of the scrollbar.
*
* Note: Only applies to win32 platforms
* Note: Only applies to win32 and linux platforms
*/
--scroll-bar-thumb-background-color-active: rgba(0, 0, 0, 0.5);
@ -301,8 +301,9 @@ $overlay-background-color: rgba(0, 0, 0, 0.4);
--list-item-selected-active-badge-background-color: #{$white};
--list-item-hover-background-color: #{$gray-100};
/** Win32 has custom scrol bars, see _scroll.scss */
/** Windows/Linux have custom scrollbars, see _scroll.scss */
--win32-scroll-bar-size: 10px;
--linux-scroll-bar-size: 10px;
/**
* The z-index for tooltips. Nothing should be higher, but other z-indexes can

View file

@ -2,7 +2,6 @@
@import 'variables';
@import 'themes/dark';
@import 'themes/high-contrast';
@import 'mixins';

View file

@ -1,7 +0,0 @@
// A mixin which takes a content block that should only
// be applied when we are using the high-contrast theme
@mixin high-contrast {
body.theme-high-contrast & {
@content;
}
}

View file

@ -20,9 +20,5 @@
&:not(.not-selectable):hover {
background: var(--list-item-hover-background-color);
@include high-contrast {
color: var(--hc-active-text-color);
border: var(--hc-hover-border);
}
}
}

View file

@ -41,3 +41,25 @@
@content;
}
}
// A mixin which takes a content block that should only
// be applied when the current (real or emulated) operating
// system is Linux.
//
// This helper mixin is useful in so far that it allows us
// to keep platform specific styles next to cross-platform
// styles instead of splitting them out and possibly forgetting
// about them.
@mixin linux {
body.platform-linux & {
@content;
}
}
// An exact copy of the linux mixin except that it allows for
// writing base-level rules
@mixin linux-context {
body.platform-linux {
@content;
}
}

View file

@ -49,7 +49,7 @@ body.theme-dark {
* Background color for custom scroll bars.
* The color is applied to the thumb part of the scrollbar.
*
* Note: Only applies to win32 platforms
* Note: Only applies to win32 and linux platforms
*/
--scroll-bar-thumb-background-color: rgba(255, 255, 255, 0.2);
@ -57,7 +57,7 @@ body.theme-dark {
* Background color for custom scroll bars in their active state.
* The color is applied to the thumb part of the scrollbar.
*
* Note: Only applies to win32 platforms
* Note: Only applies to win32 and linux platforms
*/
--scroll-bar-thumb-background-color-active: rgba(255, 255, 255, 0.5);

View file

@ -1,77 +0,0 @@
body.theme-high-contrast {
// A small set of colors used to flatten ux and improve contrast
--hc-background-color: #090c11;
--hc-secondary-background-color: #323946;
--hc-border-color: #7a828e;
--hc-text-color: #f0f3f6;
--hc-active-item-color: #9ea7b3;
--hc-secondary-active-item-color: #596069;
--hc-active-text-color: #090c11;
--hc-diff-add-border-color: green;
--hc-diff-delete-border-color: crimson;
// background/text
--background-color: var(--hc-background-color);
--text-color: var(--hc-text-color);
--text-secondary-color: var(--hc-text-color);
//border
--hc-hover-border: 2px solid var(--hc-border-color);
/// tab-bar
--tab-bar-active-color: var(--hc-active-item-color);
--tab-bar-hover-background-color: var(--hc-secondary-background-color);
// box
--box-selected-active-border: var(--hc-hover-border);
--box-background-color: var(--hc-background-color);
--box-alt-background-color: var(--hc-background-color);
--box-border-color: var(--hc-border-color);
--box-selected-background-color: var(--hc-secondary-active-item-color);
--box-selected-text-color: var(--hc-active-text-color);
--box-selected-active-background-color: var(--hc-active-item-color);
--box-selected-active-text-color: var(--hc-active-text-color);
--box-placeholder-color: var(--hc-text-color);
// button
--button-background: var(--hc-active-item-color);
--button-text-color: var(--hc-active-text-color);
--secondary-button-background: var(--hc-background-color);
--secondary-button-text-color: var(--hc-text-color);
--button-hover-background: var(--hc-secondary-active-item-color);
--secondary-button-hover-background: var(--hc-secondary-background-color);
// menu
--app-menu-button-hover-background-color: var(--hc-secondary-background-color);
// toolbar
--toolbar-button-focus-background-color: var(--hc-secondary-background-color);
--toolbar-button-hover-background-color: var(--hc-secondary-background-color);
--toolbar-button-active-border-color: var(--hc-border-color);
--toolbar-background-color: var(--hc-background-color);
--toolbar-button-secondary-color: var(--hc-text-color);
// list
--list-item-hover-background-color: var(--hc-secondary-active-item-color);
// diff
--hc-diff-add-border: 1px solid var(--hc-diff-add-border-color);
--hc-diff-delete-border: 1px solid var(--hc-diff-delete-border-color);
--diff-hunk-gutter-background-color: var(--hc-background-color);
--diff-text-color: var(--hc-text-color);
--diff-line-number-color: var(--hc-text-color);
--diff-gutter-background-color: var(--hc-background-color);
--diff-hunk-background-color: var(--hc-background-color);
--diff-empty-row-background-color: var(--hc-secondary-background-color);
--diff-border-color: var(--hc-border-color);
// PR review colors
--pr-timeline-line-color: #7a828e;
--pr-changes-requested-icon-color: #0a0c10;
--pr-changes-requested-icon-background-color: #ff6a69;
--pr-approved-icon-color: #0a0c10;
--pr-approved-icon-background-color: #09b43a;
--pr-commented-icon-color: #f0f3f6;
--pr-commented-icon-background-color: #272b33;
}

View file

@ -6,40 +6,11 @@
flex-direction: column;
}
.CodeMirror-hints {
list-style: none;
flex-direction: column;
padding: 0;
max-height: 100px;
// hack to position the dialog a little better than the
// default codemirror position.
transform: translate(0, -15px);
overflow-y: auto;
li {
flex-shrink: 0;
height: 29px;
padding: 0 var(--spacing);
}
// We can't replicate the nice scrollbars that we have in
// the regular autocomplete popup since that's based on List
// and this CodeMirror stuff is just an unordered list.
// The autocomplete popup is not intended to be used with
// a pointer-device anyway so not being able to mouse-wheel
// through it shouldn't be a big deal
@include win32 {
overflow-y: hidden;
}
}
.autocompletion-popup {
overflow: hidden; // To get those sweet rounded corners
}
.autocompletion-popup,
.CodeMirror-hints {
.autocompletion-popup {
display: flex;
position: absolute;
z-index: var(--popup-z-index);
@ -69,8 +40,7 @@
border-top: var(--base-border);
}
&.selected,
&.CodeMirror-hint-active {
&.selected {
--text-color: var(--box-selected-active-text-color);
--text-secondary-color: var(--box-selected-active-text-color);
@ -78,8 +48,7 @@
background-color: var(--box-selected-active-background-color);
border-top-color: var(--box-selected-active-background-color);
& + .list-item,
& + .CodeMirror-hint-active {
& + .list-item {
border-top-color: var(--box-selected-active-background-color);
}
}
@ -172,14 +141,3 @@
}
}
}
.author-input-component {
ul.CodeMirror-hints {
margin-top: var(--spacing-double);
padding: 0;
li {
margin-bottom: auto;
padding-left: var(--spacing);
}
}
}

View file

@ -19,10 +19,6 @@
border-radius: var(--button-border-radius);
&:not([aria-disabled='true']):hover {
@include high-contrast {
border-width: 2px;
}
border-color: var(--secondary-button-hover-border-color);
background-color: var(--secondary-button-hover-background);
}

View file

@ -17,10 +17,10 @@
border-radius: 50%;
margin-right: var(--spacing-half);
}
.avatar {
margin-right: 0;
}
.toggletip {
margin-right: var(--spacing-half);
}
.warning-badge {

View file

@ -406,6 +406,10 @@ dialog {
&#preferences {
width: 600px;
.dialog-content {
min-height: 340px;
}
// This is to ensure that the dialog content is accessible when zoomed in
@media (max-width: 600px) {
overflow-y: auto;

View file

@ -275,39 +275,10 @@
.CodeMirror-linebackground {
&.diff-add {
background: var(--diff-add-background-color);
@include high-contrast {
border-right: var(--hc-diff-add-border);
border-left: var(--hc-diff-add-border);
margin-top: 1px;
&.is-first {
margin-top: 1px;
border-top: var(--hc-diff-add-border);
}
&.is-last {
border-bottom: var(--hc-diff-add-border);
}
}
}
&.diff-delete {
background: var(--diff-delete-background-color);
@include high-contrast {
border-right: var(--hc-diff-delete-border);
border-left: var(--hc-diff-delete-border);
&.is-first {
margin-top: 1px;
border-top: var(--hc-diff-delete-border);
}
&.is-last {
border-bottom: var(--hc-diff-delete-border);
}
}
}
&.diff-context {

View file

@ -2,7 +2,7 @@
background: var(--form-error-background);
border: 1px solid var(--form-error-border-color);
border-radius: var(--border-radius);
color: var(--error-color);
color: var(--text-color);
font-size: var(--font-size-sm);
padding: var(--spacing-half);
}

View file

@ -19,10 +19,5 @@
.foldout {
background: var(--background-color);
color: var(--text-color);
@include high-contrast {
border: 1px solid var(--hc-border-color);
overflow: hidden;
}
}
}

View file

@ -70,10 +70,6 @@
color: var(--text-color);
background-color: var(--box-selected-active-background-color);
@include high-contrast {
border: var(--box-selected-active-border);
}
}
}

View file

@ -79,6 +79,71 @@
font-size: var(--font-size-sm);
color: var(--text-secondary-color);
}
.theme-selector {
display: flex;
flex-wrap: wrap;
margin: 0 calc(var(--spacing-half) * -1);
.radio-button-component {
display: block;
flex-shrink: 0;
padding: var(--spacing-half);
position: relative;
width: 50%;
margin: 0;
input[type='radio'] {
position: absolute;
left: calc(var(--spacing) + var(--spacing-half));
margin-top: calc(54.5%);
&:checked + label {
border-color: var(--box-selected-active-background-color);
}
}
label {
border-radius: var(--border-radius);
border: var(--base-border);
float: left;
overflow: hidden;
padding-bottom: var(--spacing-half);
width: 100%;
margin: 0;
.theme-value-label {
margin-left: calc(var(--spacing-triple));
}
img {
width: 100%;
border-bottom: var(--base-border);
margin-bottom: var(--spacing-half);
display: block;
}
.system-theme-swatch {
width: 100%;
position: relative;
display: block;
img {
position: absolute;
top: 0;
&:first-child {
position: inherit;
}
&:last-child {
clip-path: polygon(50% 0, 100% 0%, 100% 100%, 50% 100%);
}
}
}
}
}
}
}
#external-editor-error {

View file

@ -26,6 +26,7 @@
height: 100%;
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
}
}

View file

@ -59,3 +59,40 @@
}
}
}
@include linux-context {
// Linux scrollbars need styled, too
::-webkit-scrollbar {
width: var(--linux-scroll-bar-size);
height: var(--linux-scroll-bar-size);
background: transparent;
cursor: pointer;
&-thumb {
background-color: var(--scroll-bar-thumb-background-color);
border-radius: var(--linux-scroll-bar-size);
// This little hack allows us to have a slim scroll bar
// with a bigger hit area. The scroll bar width/height
// is 10px but we're cutting off 6px using clipping so
// that it appears as if it's actually only 4px.
border-color: transparent;
border-style: solid;
border-width: 3px;
background-clip: padding-box;
// ...and when it's pressed we'll up the contrast
&:active {
background-color: var(--scroll-bar-thumb-background-color-active);
}
// When someone hovers over, or presses the bar we'll expand it to 8px
&:hover,
&:active {
border-width: 1px;
background-color: var(--scroll-bar-thumb-background-color-active);
cursor: pointer;
}
}
}
}

View file

@ -190,19 +190,6 @@
color: var(--diff-delete-text-color);
background: var(--diff-delete-background-color);
@include high-contrast {
border-right: var(--hc-diff-delete-border);
border-left: var(--hc-diff-delete-border);
&.is-first {
border-top: var(--hc-diff-delete-border);
}
&.is-last {
border-bottom: var(--hc-diff-delete-border);
}
}
.line-number {
background: var(--diff-delete-gutter-background-color);
border-color: var(--diff-delete-border-color);
@ -220,19 +207,6 @@
color: var(--diff-add-text-color);
background: var(--diff-add-background-color);
@include high-contrast {
border-left: var(--hc-diff-add-border);
border-right: var(--hc-diff-add-border);
&.is-first {
border-top: var(--hc-diff-add-border);
}
&.is-last {
border-bottom: var(--hc-diff-add-border);
}
}
.line-number {
background: var(--diff-add-gutter-background-color);
border-color: var(--diff-add-border-color);
@ -273,10 +247,6 @@
&.deleted .after {
background: var(--diff-empty-row-background-color);
@include high-contrast {
border: none;
}
.line-number {
background: var(--diff-empty-row-gutter-background-color);
border-color: var(--diff-border-color);
@ -302,10 +272,6 @@
background: var(--diff-background-color);
color: var(--diff-text-color);
@include high-contrast {
border: none;
}
.line-number {
background: var(--diff-gutter-background-color);
border-color: var(--diff-border-color);

View file

@ -14,11 +14,6 @@
&-item {
// Reset styles from global buttons
border: none;
@include high-contrast {
border: 2px solid var(--hc-background-color);
}
box-shadow: none;
color: var(--text-color);
background: var(--background-color);
@ -53,21 +48,10 @@
// one.
&.selected {
box-shadow: inset 0 -3px 0px var(--tab-bar-active-color);
@include high-contrast {
box-shadow: inset 0 -3px 0px var(--tab-bar-active-color);
background-color: var(--hc-active-item-color);
color: var(--hc-active-text-color);
}
}
&:hover {
background: var(--tab-bar-hover-background-color);
@include high-contrast {
border: var(--hc-hover-border);
color: var(--text-color);
}
}
.with-indicator {
@ -92,7 +76,7 @@
line-height: 1;
}
&.focus-ring {
&:focus-visible {
outline-offset: -4px;
}
@ -177,10 +161,6 @@
&:hover {
background: var(--tab-bar-hover-background-color);
@include high-contrast {
border: var(--hc-hover-border);
}
}
}

View file

@ -10,10 +10,6 @@
margin-bottom: var(--spacing-third);
}
:invalid {
border-color: var(--error-color);
}
input {
@include textboxish;
@include textboxish-disabled;
@ -37,4 +33,8 @@
-webkit-mask-repeat: no-repeat;
}
}
&:not(.no-invalid-state) :not(:focus):invalid {
border-color: var(--error-color);
}
}

View file

@ -38,6 +38,10 @@
#sign-in-enterprise {
.sign-in-form {
margin-top: 0;
> button {
margin-right: 0;
}
}
.sign-in-footer {

View file

@ -9,66 +9,7 @@
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 0;
.stashed-changes-button {
@include ellipsis;
min-height: 29px;
display: flex;
align-items: center;
flex-grow: 1;
padding: 0 var(--spacing);
width: 100%;
// Chrome on Windows ignores the body element
// font-family and uses Arial so we redefine
// it here
font-family: var(--font-family-sans-serif);
font-size: var(--font-size);
padding: 0 var(--spacing);
color: var(--secondary-button-text-color);
background-color: var(--secondary-button-background);
border: none;
border-top: var(--base-border);
box-shadow: none;
z-index: 0;
&:disabled {
opacity: 0.6;
}
&:hover,
&:focus {
background-color: var(--secondary-button-hover-background);
background: var(--box-selected-background-color);
}
&.selected,
&.selected:hover {
color: var(--box-selected-active-text-color);
background-color: var(--box-selected-active-background-color);
.stack-icon {
fill: var(--box-selected-active-text-color);
}
}
.stack-icon {
fill: var(--color-modified);
}
.text {
flex-grow: 1;
margin: 0 var(--spacing-half);
text-align: start;
}
.octicon {
flex-shrink: 0;
}
}
min-height: 100px;
.header {
background: var(--box-alt-background-color);
@ -104,6 +45,65 @@
}
}
.stashed-changes-button {
@include ellipsis;
min-height: 29px;
display: flex;
align-items: center;
flex-grow: 1;
padding: 0 var(--spacing);
width: 100%;
// Chrome on Windows ignores the body element
// font-family and uses Arial so we redefine
// it here
font-family: var(--font-family-sans-serif);
font-size: var(--font-size);
padding: 0 var(--spacing);
color: var(--secondary-button-text-color);
background-color: var(--secondary-button-background);
border: none;
border-top: var(--base-border);
box-shadow: none;
z-index: 0;
&:disabled {
opacity: 0.6;
}
&:hover,
&:focus {
background-color: var(--secondary-button-hover-background);
background: var(--box-selected-background-color);
}
&.selected,
&.selected:hover {
color: var(--box-selected-active-text-color);
background-color: var(--box-selected-active-background-color);
.stack-icon {
fill: var(--box-selected-active-text-color);
}
}
.stack-icon {
fill: var(--color-modified);
}
.text {
flex-grow: 1;
margin: 0 var(--spacing-half);
text-align: start;
}
.octicon {
flex-shrink: 0;
}
}
.undo-enter {
max-height: 0;
overflow: hidden;

View file

@ -30,7 +30,6 @@
flex-shrink: 0;
width: var(--text-field-height);
height: var(--text-field-height);
margin-right: var(--spacing-half);
}
&.with-length-hint input {
@ -89,6 +88,10 @@
.description-field textarea {
min-height: 80px;
@media (max-height: 500px) {
min-height: 10px;
}
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}

View file

@ -89,14 +89,6 @@
}
}
}
@include high-contrast {
.list-item:hover {
.commit {
color: var(--text-color);
}
}
}
}
#commit-drag-element .commit,

View file

@ -43,7 +43,7 @@
&:focus {
background-color: var(--toolbar-button-focus-background-color);
// Prevent the focus-ring from getting clipped by UI elements
// Prevent the focus ring from getting clipped by UI elements
// outside of the toolbar
outline-offset: -4px;
@ -56,8 +56,7 @@
}
// Focused but not through keyboard navigation
&:not(.focus-ring) {
outline: none;
&:not(:focus-visible) {
background-color: var(--toolbar-button-background-color);
.progress {

View file

@ -210,3 +210,16 @@ body > .tooltip,
// need to be position: relative or position: absolute.
position: relative;
}
button.toggletip {
// override default button styles
overflow: inherit;
text-overflow: inherit;
white-space: inherit;
font-family: inherit;
font-size: inherit;
padding: inherit;
border: none;
color: inherit;
background-color: inherit;
}

View file

@ -1,51 +0,0 @@
import { ApplicationTheme } from '../../src/ui/lib/application-theme'
import {
buildCustomThemeStyles,
CustomThemeDefaults,
} from '../../src/ui/lib/custom-theme'
describe('CustomTheme', () => {
describe('buildCustomThemeStyles', () => {
it('sets the first line to body.theme-high-contrast {', () => {
const customTheme = CustomThemeDefaults[ApplicationTheme.HighContrast]
const customThemeStyles = buildCustomThemeStyles(customTheme)
expect(customThemeStyles).toStartWith('body.theme-high-contrast {\n')
})
it('sets the last line to }', () => {
const customTheme = CustomThemeDefaults[ApplicationTheme.HighContrast]
const customThemeStyles = buildCustomThemeStyles(customTheme)
expect(customThemeStyles).toEndWith('}')
})
it('prefaces all variable lines with --', () => {
const customTheme = CustomThemeDefaults[ApplicationTheme.HighContrast]
const customThemeStyles = buildCustomThemeStyles(customTheme)
const styleLines = customThemeStyles.split('\n')
// skip first and last line
for (let i = 1; i < styleLines.length - 1; i++) {
const trimmedLine = styleLines[i].trim()
if (trimmedLine === '') {
continue
}
expect(trimmedLine).toStartWith('--')
}
})
it('ends all variable lines with ;', () => {
const customTheme = CustomThemeDefaults[ApplicationTheme.HighContrast]
const customThemeStyles = buildCustomThemeStyles(customTheme)
const styleLines = customThemeStyles.split('\n')
// skip first, last line, and empty lines
for (let i = 1; i < styleLines.length - 1; i++) {
const trimmedLine = styleLines[i].trim()
if (trimmedLine === '') {
continue
}
expect(trimmedLine).toEndWith(';')
}
})
})
})

View file

@ -48,11 +48,6 @@
resolved "https://registry.yarnpkg.com/@github/stable-socket/-/stable-socket-1.1.0.tgz#b8598c02df1a935fb0c970252f0b0062f4bdaa29"
integrity sha512-kzLHnrWGvmXka7AIUnsyF+SO5dP4dxEXX2sMixXW5ngRf3i4RhvXzFNgXVy4qBKsIsg6V3fNb3NOto0z6o4XAw==
"@icons/material@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
accessibility-developer-tools@^2.11.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz#3da0cce9d6ec6373964b84f35db7cfc3df7ab514"
@ -100,11 +95,6 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
asap@~2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
async@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
@ -250,11 +240,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
core-js@^2.4.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
@ -357,11 +342,6 @@ dexie@^3.2.2:
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.2.tgz#fa6f2a3c0d6ed0766f8d97a03720056f88fe0e01"
integrity sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==
dom-classlist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dom-classlist/-/dom-classlist-1.0.1.tgz#722814dc2f509d3d7d06f2533ba9ff48eeea59b9"
integrity sha1-cigU3C9QnT19BvJTO6n/SO7qWbk=
"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.3.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
@ -377,11 +357,6 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-matches@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-matches/-/dom-matches-2.0.0.tgz#d2728b416a87533980eb089b848d253cf23a758c"
integrity sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw=
dompurify@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c"
@ -446,13 +421,6 @@ enabled@2.0.x:
resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
encoding@^0.1.11:
version "0.1.12"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
dependencies:
iconv-lite "~0.4.13"
end-of-stream@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@ -495,19 +463,6 @@ expand-template@^2.0.3:
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
fbjs@^0.8.16:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
integrity sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
fecha@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce"
@ -630,11 +585,6 @@ humanize-plus@^1.8.1:
resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
integrity sha1-pls0RZrWNnrbs3B6gqPJ+RYWcDA=
iconv-lite@~0.4.13:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
@ -680,7 +630,7 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-stream@^1.0.1, is-stream@^1.1.0:
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@ -700,14 +650,6 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
js-tokens@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@ -765,16 +707,6 @@ lie@~3.3.0:
dependencies:
immediate "~3.0.5"
lodash-es@^4.17.15:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash@^4.0.1, lodash@^4.17.15:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
logform@^2.3.2, logform@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe"
@ -786,7 +718,7 @@ logform@^2.3.2, logform@^2.4.0:
safe-stable-stringify "^2.3.1"
triple-beam "^1.3.0"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.3.1:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
integrity sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=
@ -819,11 +751,6 @@ marked@^4.0.10:
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.10.tgz#423e295385cc0c3a70fa495e0df68b007b879423"
integrity sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==
material-colors@^1.2.1:
version "1.2.6"
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
mem@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
@ -951,14 +878,6 @@ node-addon-api@^5.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501"
integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
noop-logger@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
@ -1128,38 +1047,14 @@ progress@^2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
dependencies:
asap "~2.0.3"
prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
prop-types@^15.6.0:
version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
integrity sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=
dependencies:
fbjs "^0.8.16"
loose-envify "^1.3.1"
object-assign "^4.1.1"
prop-types@^15.6.1:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
react-is "^16.13.1"
pump@^3.0.0:
version "3.0.0"
@ -1189,19 +1084,6 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-color@^2.19.3:
version "2.19.3"
resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d"
integrity sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==
dependencies:
"@icons/material" "^0.2.4"
lodash "^4.17.15"
lodash-es "^4.17.15"
material-colors "^1.2.1"
prop-types "^15.5.10"
reactcss "^1.2.0"
tinycolor2 "^1.4.1"
react-css-transition-replace@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/react-css-transition-replace/-/react-css-transition-replace-3.0.3.tgz#23d3ed17f54e41435c0485300adb75d2e6e24aad"
@ -1222,10 +1104,10 @@ react-dom@^16.8.4:
prop-types "^15.6.2"
scheduler "^0.13.4"
react-is@^16.8.1:
version "16.8.4"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
@ -1264,13 +1146,6 @@ react@^16.8.4:
prop-types "^15.6.2"
scheduler "^0.13.4"
reactcss@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==
dependencies:
lodash "^4.0.1"
readable-stream@^2.0.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
@ -1565,11 +1440,6 @@ textarea-caret@^3.0.2:
resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.0.2.tgz#f360c48699aa1abf718680a43a31a850665c2caf"
integrity sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8=
tinycolor2@^1.4.1:
version "1.4.2"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
triple-beam@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
@ -1592,11 +1462,6 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
ua-parser-js@^0.7.9:
version "0.7.33"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
untildify@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1"
@ -1651,11 +1516,6 @@ webpack-hot-middleware@^2.10.0:
querystring "^0.2.0"
strip-ansi "^3.0.0"
whatwg-fetch@>=0.10.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
integrity sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=
which-pm-runs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
@ -1668,14 +1528,6 @@ which@^1.2.9:
dependencies:
isexe "^2.0.0"
wicg-focus-ring@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wicg-focus-ring/-/wicg-focus-ring-1.0.1.tgz#3868c464ed27b5ca0891329b44c5d1144c0b2095"
integrity sha1-OGjEZO0ntcoIkTKbRMXRFEwLIJU=
dependencies:
dom-classlist "^1.0.1"
dom-matches "^2.0.0"
wide-align@^1.1.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"

View file

@ -1,6 +1,32 @@
{
"releases": {
"3.2.2": ["[Improved] Upgrade embedded Git to 2.39.3"],
"3.2.2-beta2": [
"[Improved] Improve accessibility of GitHub Enterprise login flow - #16567",
"[Improved] Screen readers announce sign in errors - #16556",
"[Improved] Include remote branches in search for default branch - #15754",
"[Improved] Upgrade embedded Git to 2.39.3",
"[Removed] Remove unshipped high contrast theme and dependencies - #16559"
],
"3.2.2-beta1": [
"[Added] Add support for VimR code editor on macOS. - #16354. Thanks @Elmar-Wiese!",
"[Fixed] NVDA reads number of suggestions when an autocompletion list shows up - #16526",
"[Fixed] The undo commit confirmation modal message is screen reader announced - #16472",
"[Fixed] Clipping and overlapping of the changes list is fixed at 200% zoom - #16425",
"[Fixed] Preview pull request button in the no local changes suggested next action isn't intermittently disabled - #16368",
"[Fixed] The commit message avatar is now a toggle tip making the commit author details keyboard accessible - #16272",
"[Fixed] The commit length hint is keyboard and screen reader accessible - #16449",
"[Fixed] The changes list header checkbox tooltip description is announced by screen readers - #16457",
"[Fixed] The changes list header checkbox tooltip is keyboard accessible - #16487",
"[Fixed] Announce a file's state of inclusion in the commit on the changes list - #16420",
"[Fixed] Display focus ring around focused control after dismissing a dialog - #16528",
"[Improved] Identify the changes list and history commit list as the changes and history tab panels for screen readers - #16463",
"[Improved] Windows title bar controls do not interrupt screen readers in browse mode - #16483",
"[Improved] Add icons for tabs in Repository settings dialog - #16432. Thanks @sweezyio!",
"[Improved] Use correct name for VSCodium editor - #16511. Thanks @GitMensch!",
"[Improved] Styling improvements on Linux to scroll bars and default layouts - #16484. Thanks @shiftkey!",
"[Improved] Make radio theme selection look like radio buttons. - #16525"
],
"3.2.1": [
"[Added] Add Zed as an external editor option - #16026. Thanks @JosephTLyons!",
"[Added] Add support for Pulsar code editor on macOS. - #16220. Thanks @mdibella-dev!",
@ -18,9 +44,7 @@
"[Improved] Keyboard shortcuts for resizing app sidebar and file lists - #16332",
"[Improved] Misattributed commit popover does not clip when app is zoomed - #16407",
"[Improved] Accessibility improvements for the co-authors input - #16335",
"[Improved] Commit completion status is announced by screen readers - #16371",
"[Improved] Windows menu scroll bars only present when menu is scrollable - #16367",
"[Improved] Committing status after submitting a commit is screen reader announced - #16340",
"[Improved] Commit completion status is announced by screen readers - #16371, #16340",
"[Improved] Improve accessibility of dialogs for screen reader users - #16350",
"[Improved] Accessibility improvements for autocompletion suggestions - #16324",
"[Improved] Learn more links are descriptive for screen readers - #16274",

View file

@ -35,7 +35,7 @@ To see which packages have newer versions available:
> yarn outdated
```
To upgrade a package to it's latest version:
To upgrade a package to its latest version:
```sh
> yarn upgrade --latest [package-name]

View file

@ -246,6 +246,7 @@ These editors are currently supported:
- [Atom](https://atom.io/)
- [MacVim](https://macvim-dev.github.io/macvim/)
- [Neovide](https://github.com/neovide/neovide)
- [VimR](https://github.com/qvacua/vimr)
- [Visual Studio Code](https://code.visualstudio.com/) - both stable and Insiders channel
- [Visual Studio Codium](https://vscodium.com/)
- [Sublime Text](https://www.sublimetext.com/)

View file

@ -52,6 +52,7 @@
"yarn": ">= 1.9"
},
"dependencies": {
"@azure/storage-blob": "^12.13.0",
"@primer/octicons": "^10.0.0",
"@types/lodash": "^4.14.178",
"@types/marked": "^4.0.1",
@ -64,7 +65,6 @@
"airbnb-browser-shims": "^3.0.0",
"ajv": "^6.12.3",
"awesome-node-loader": "^1.1.0",
"azure-storage": "^2.10.4",
"chalk": "^2.2.0",
"cross-env": "^5.0.5",
"css-loader": "^6.7.1",
@ -89,9 +89,9 @@
"klaw-sync": "^3.0.0",
"legal-eagle": "0.16.0",
"mini-css-extract-plugin": "^2.5.3",
"node-fetch": "^2.6.9",
"parallel-webpack": "^2.3.0",
"prettier": "^2.6.0",
"request": "^2.72.0",
"rimraf": "^2.5.2",
"sass": "^1.27.0",
"sass-loader": "^10.0.3",
@ -108,7 +108,7 @@
"webpack-dev-middleware": "^5.3.1",
"webpack-hot-middleware": "^2.25.1",
"webpack-merge": "^5.8.0",
"xml2js": "^0.4.16"
"xml2js": "^0.5.0"
},
"devDependencies": {
"@github/markdownlint-github": "^0.1.0",
@ -155,7 +155,7 @@
"@types/webpack-bundle-analyzer": "^4.4.1",
"@types/webpack-hot-middleware": "^2.25.6",
"@types/webpack-merge": "^5.0.0",
"@types/xml2js": "^0.4.0",
"@types/xml2js": "^0.4.11",
"electron": "22.0.3",
"electron-builder": "^23.6.0",
"electron-packager": "^15.1.0",

View file

@ -38,32 +38,6 @@ export const licenseOverrides: LicenseLookup = {
'Code is licensed under the AFL or BSD 3-Clause license as part of the Persevere project which is administered under the Dojo foundation, and all contributions require a Dojo CLA.',
},
'wicg-focus-ring@1.0.1': {
repository: 'git+https://github.com/WICG/focus-ring',
license: 'MIT',
source:
'https://github.com/WICG/focus-ring/blob/4df78f379e4c7dbaa1a5e4ff25cf1b56700b24ee/LICENSE.md',
sourceText: `This work is being provided by the copyright holders under the following license.
License
By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.
Permission to copy, modify, and distribute this work, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the work or portions thereof, including modifications:
* The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.
* Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software and Document Short Notice should be included.
* Notice of any changes or modifications, through a copyright statement on the new code or document such as "This software or document includes material copied from or derived from [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)."
Disclaimers
THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT.
The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. Title to copyright in this work will at all times remain with copyright holders.`,
},
'tslib@2.0.0': tslibLicenseOverride,
'tslib@2.3.1': tslibLicenseOverride,
}

View file

@ -3,6 +3,16 @@ import * as distInfo from './dist-info'
import * as gitInfo from '../app/git-info'
import * as packageInfo from '../app/package-info'
import * as platforms from './build-platforms'
import * as Fs from 'fs'
import { execSync } from 'child_process'
import {
BlobServiceClient,
StorageSharedKeyCredential,
} from '@azure/storage-blob'
import * as Crypto from 'crypto'
import fetch from 'node-fetch'
import { getFileHash } from '../app/src/lib/get-file-hash'
import { stat } from 'fs/promises'
if (!distInfo.isPublishable()) {
console.log('Not a publishable build. Skipping publish.')
@ -23,12 +33,6 @@ if (!currentTipSHA.toUpperCase().startsWith(releaseSHA!.toUpperCase())) {
process.exit(0)
}
import * as Fs from 'fs'
import { execSync } from 'child_process'
import * as Azure from 'azure-storage'
import * as Crypto from 'crypto'
import request from 'request'
console.log('Packaging…')
execSync('yarn package', { stdio: 'inherit' })
@ -46,7 +50,7 @@ function getSecret() {
console.log('Uploading…')
let uploadPromise = null
let uploadPromise
if (process.platform === 'darwin') {
uploadPromise = uploadOSXAssets()
} else if (process.platform === 'win32') {
@ -56,7 +60,7 @@ if (process.platform === 'darwin') {
process.exit(1)
}
uploadPromise!
uploadPromise
.then(artifacts => {
const names = artifacts.map(function (item, index) {
return item.name
@ -124,70 +128,41 @@ async function upload(assetName: string, assetPath: string) {
const blob = `releases/${packageInfo.getVersion()}-${sha}/${cleanAssetName}`
const url = `${process.env.AZURE_STORAGE_URL}/${container}/${blob}`
return new Promise<IUploadResult>((resolve, reject) => {
azureBlobService.createBlockBlobFromLocalFile(
container,
blob,
assetPath,
(error: any) => {
if (error != null) {
reject(error)
} else {
// eslint-disable-next-line no-sync
const stats = Fs.statSync(assetPath)
const hash = Crypto.createHash('sha1')
hash.setEncoding('hex')
const input = Fs.createReadStream(assetPath)
const blockBlobClient = azureBlobService.getBlockBlobClient(blob)
hash.on('finish', () => {
resolve({
name: assetName,
url,
size: stats['size'],
sha: hash.read() as string,
})
})
await blockBlobClient.uploadFile(assetPath)
input.pipe(hash)
}
}
)
})
const { size } = await stat(assetPath)
const hash = await getFileHash(assetPath, 'sha1')
return { name: assetName, url, size, sha: hash }
}
function getAzureBlobService(): Promise<Azure.BlobService> {
return new Promise<Azure.BlobService>((resolve, reject) => {
if (
process.env.AZURE_STORAGE_ACCOUNT === undefined ||
process.env.AZURE_STORAGE_ACCESS_KEY === undefined ||
process.env.AZURE_BLOB_CONTAINER === undefined
) {
reject('Invalid azure storage credentials')
return
}
async function getAzureBlobService() {
const {
AZURE_STORAGE_ACCOUNT: account,
AZURE_STORAGE_ACCESS_KEY: key,
AZURE_BLOB_CONTAINER: container,
} = process.env
const blobService = Azure.createBlobService(
process.env.AZURE_STORAGE_ACCOUNT,
process.env.AZURE_STORAGE_ACCESS_KEY
)
if (account === undefined || key === undefined || container === undefined) {
throw new Error('Invalid azure storage credentials')
}
blobService.createContainerIfNotExists(
process.env.AZURE_BLOB_CONTAINER,
{
publicAccessLevel: 'blob',
},
(error: any) => {
if (error !== null) {
console.log(error)
reject(
`Unable to ensure azure blob container - ${process.env.AZURE_BLOB_CONTAINER}. Deployment aborting...`
)
} else {
resolve(blobService)
}
}
)
})
const blobServiceClient = new BlobServiceClient(
`https://${account}.blob.core.windows.net`,
new StorageSharedKeyCredential(account, key)
)
const containerClient = blobServiceClient.getContainerClient(container)
try {
await containerClient.createIfNotExists({ access: 'blob' })
} catch (e) {
console.error(e)
throw new Error(`Failed ensuring container "${container}" aborting...`)
}
return containerClient
}
function createSignature(body: any, secret: string) {
@ -203,10 +178,10 @@ function getContext() {
)
}
function updateDeploy(
async function updateDeploy(
artifacts: ReadonlyArray<IUploadResult>,
secret: string
): Promise<void> {
) {
const { rendererSize, mainSize } = distInfo.getBundleSizes()
const body = {
context: getContext(),
@ -218,36 +193,19 @@ function updateDeploy(
mainBundleSize: mainSize,
},
}
const signature = createSignature(body, secret)
const options = {
const url = 'https://central.github.com/api/deploy_built'
const response = await fetch(url, {
method: 'POST',
url: 'https://central.github.com/api/deploy_built',
headers: {
'X-Hub-Signature': signature,
},
json: true,
body,
}
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
if (error) {
reject(error)
return
}
if (response.statusCode !== 200) {
reject(
new Error(
`Received a non-200 response (${
response.statusCode
}): ${JSON.stringify(body)}`
)
)
return
}
resolve()
})
headers: { 'X-Hub-Signature': signature },
body: JSON.stringify(body),
})
if (!response.ok) {
throw new Error(
`Unexpected response while updating deploy ${response.status} ${response.statusText}`
)
}
}

498
yarn.lock
View file

@ -7,6 +7,95 @@
resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876"
integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==
"@azure/abort-controller@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249"
integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==
dependencies:
tslib "^2.2.0"
"@azure/core-auth@^1.3.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e"
integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==
dependencies:
"@azure/abort-controller" "^1.0.0"
tslib "^2.2.0"
"@azure/core-http@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-3.0.1.tgz#2177f3abb64afa8ca101dc34f67cc789888f9f7b"
integrity sha512-A3x+um3cAPgQe42Lu7Iv/x8/fNjhL/nIoEfqFxfn30EyxK6zC13n+OUxzZBRC0IzQqssqIbt4INf5YG7lYYFtw==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-auth" "^1.3.0"
"@azure/core-tracing" "1.0.0-preview.13"
"@azure/core-util" "^1.1.1"
"@azure/logger" "^1.0.0"
"@types/node-fetch" "^2.5.0"
"@types/tunnel" "^0.0.3"
form-data "^4.0.0"
node-fetch "^2.6.7"
process "^0.11.10"
tslib "^2.2.0"
tunnel "^0.0.6"
uuid "^8.3.0"
xml2js "^0.5.0"
"@azure/core-lro@^2.2.0":
version "2.5.2"
resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.5.2.tgz#712439f12b39ade7576f55780a4e005472c67b19"
integrity sha512-tucUutPhBwCPu6v16KEFYML81npEL6gnT+iwewXvK5ZD55sr0/Vw2jfQETMiKVeARRrXHB2QQ3SpxxGi1zAUWg==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-util" "^1.2.0"
"@azure/logger" "^1.0.0"
tslib "^2.2.0"
"@azure/core-paging@^1.1.1":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.5.0.tgz#5a5b09353e636072e6a7fc38f7879e11d0afb15f"
integrity sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==
dependencies:
tslib "^2.2.0"
"@azure/core-tracing@1.0.0-preview.13":
version "1.0.0-preview.13"
resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644"
integrity sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==
dependencies:
"@opentelemetry/api" "^1.0.1"
tslib "^2.2.0"
"@azure/core-util@^1.1.1", "@azure/core-util@^1.2.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.3.0.tgz#ea736a0cb0437ac0d049d57ff627c240b41479ec"
integrity sha512-ANP0Er7R2KHHHjwmKzPF9wbd0gXvOX7yRRHeYL1eNd/OaNrMLyfZH/FQasHRVAf6rMXX+EAUpvYwLMFDHDI5Gw==
dependencies:
"@azure/abort-controller" "^1.0.0"
tslib "^2.2.0"
"@azure/logger@^1.0.0":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.4.tgz#28bc6d0e5b3c38ef29296b32d35da4e483593fa1"
integrity sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==
dependencies:
tslib "^2.2.0"
"@azure/storage-blob@^12.13.0":
version "12.13.0"
resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.13.0.tgz#9209cbb5c2cd463fb967a0f2ae144ace20879160"
integrity sha512-t3Q2lvBMJucgTjQcP5+hvEJMAsJSk0qmAnjDLie2td017IiduZbbC9BOcFfmwzR6y6cJdZOuewLCNFmEx9IrXA==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-http" "^3.0.0"
"@azure/core-lro" "^2.2.0"
"@azure/core-paging" "^1.1.1"
"@azure/core-tracing" "1.0.0-preview.13"
"@azure/logger" "^1.0.0"
events "^3.0.0"
tslib "^2.2.0"
"@babel/code-frame@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
@ -891,6 +980,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@opentelemetry/api@^1.0.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f"
integrity sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==
"@polka/url@^1.0.0-next.20":
version "1.0.0-next.21"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
@ -1285,6 +1379,14 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/node-fetch@^2.5.0":
version "2.6.3"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.3.tgz#175d977f5e24d93ad0f57602693c435c57ad7e80"
integrity sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "12.12.24"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f"
@ -1493,6 +1595,13 @@
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
"@types/tunnel@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9"
integrity sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==
dependencies:
"@types/node" "*"
"@types/ua-parser-js@^0.7.30":
version "0.7.32"
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.32.tgz#8827d451d6702307248073b5d98aa9293d02b5e5"
@ -1552,10 +1661,10 @@
tapable "^2.2.0"
webpack "^5"
"@types/xml2js@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.0.tgz#e54a89a0055d5ed69305b2610f970909bf363e45"
integrity sha512-3gw0UqFMq7PsfMDwsawD0/L48soXfzOEh0NSAWVO99IZXnhx9LD3nOldHIpGYzZBsrS9NV2vaRFvEdWe+UweXQ==
"@types/xml2js@^0.4.11":
version "0.4.11"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.11.tgz#bf46a84ecc12c41159a7bd9cf51ae84129af0e79"
integrity sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==
dependencies:
"@types/node" "*"
@ -2332,14 +2441,7 @@ asar@^3.1.0:
optionalDependencies:
"@types/glob" "^7.1.1"
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
dependencies:
safer-buffer "~2.1.0"
assert-plus@1.0.0, assert-plus@^1.0.0:
assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
@ -2401,16 +2503,6 @@ awesome-node-loader@^1.1.0:
dependencies:
loader-utils "^1.1.0"
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
axe-core@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
@ -2421,23 +2513,6 @@ axobject-query@^2.2.0:
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
azure-storage@^2.10.4:
version "2.10.4"
resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.4.tgz#c481d207eabc05f57f019b209f7faa8737435104"
integrity sha512-zlfRPl4js92JC6+79C2EUmNGYjSknRl8pOiHQF78zy+pbOFOHtlBF6BU/OxPeHQX3gaa6NdEZnVydFxhhndkEw==
dependencies:
browserify-mime "~1.2.9"
extend "^3.0.2"
json-edm-parser "0.1.2"
md5.js "1.3.4"
readable-stream "~2.0.0"
request "^2.86.0"
underscore "^1.12.1"
uuid "^3.0.0"
validator "~9.4.1"
xml2js "0.2.8"
xmlbuilder "^9.0.7"
babel-jest@^26.6.3:
version "26.6.3"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
@ -2534,13 +2609,6 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
dependencies:
tweetnacl "^0.14.3"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -2637,11 +2705,6 @@ browser-process-hrtime@^1.0.0:
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
browserify-mime@~1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f"
integrity sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8=
browserslist@^4.14.5, browserslist@^4.17.5, browserslist@^4.21.0:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
@ -2829,11 +2892,6 @@ capture-exit@^2.0.0:
dependencies:
rsvp "^4.8.4"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chalk@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@ -3047,13 +3105,6 @@ colors@1.0.3:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==
combined-stream@1.0.6, combined-stream@~1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
integrity sha1-cj599ugBrFYTETp+RFqbactjKBg=
dependencies:
delayed-stream "~1.0.0"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@ -3296,13 +3347,6 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
dependencies:
assert-plus "^1.0.0"
data-urls@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
@ -3630,14 +3674,6 @@ easy-stack@^1.0.0:
resolved "https://registry.yarnpkg.com/easy-stack/-/easy-stack-1.0.0.tgz#12c91b3085a37f0baa336e9486eac4bf94e3e788"
integrity sha1-EskbMIWjfwuqM26UhurEv5Tj54g=
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -4349,7 +4385,7 @@ event-pubsub@4.3.0:
resolved "https://registry.yarnpkg.com/event-pubsub/-/event-pubsub-4.3.0.tgz#f68d816bc29f1ec02c539dc58c8dd40ce72cb36e"
integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==
events@^3.2.0:
events@^3.0.0, events@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@ -4480,11 +4516,6 @@ extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@^3.0.2, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extglob@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
@ -4510,7 +4541,7 @@ extract-zip@^2.0.0, extract-zip@^2.0.1:
optionalDependencies:
"@types/yauzl" "^2.9.1"
extsprintf@1.3.0, extsprintf@^1.2.0:
extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
@ -4684,11 +4715,6 @@ for-in@^1.0.2:
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
@ -4707,15 +4733,6 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=
dependencies:
asynckit "^0.4.0"
combined-stream "1.0.6"
mime-types "^2.1.12"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -4927,13 +4944,6 @@ get-value@^2.0.3, get-value@^2.0.6:
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
dependencies:
assert-plus "^1.0.0"
glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@ -5115,19 +5125,6 @@ gzip-size@^6.0.0:
dependencies:
duplexer "^0.1.2"
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.3:
version "5.1.5"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
dependencies:
ajv "^6.12.3"
har-schema "^2.0.0"
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@ -5239,14 +5236,6 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hash-base@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=
dependencies:
inherits "^2.0.1"
safe-buffer "^5.0.1"
he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@ -5349,15 +5338,6 @@ http-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
dependencies:
assert-plus "^1.0.0"
jsprim "^1.2.2"
sshpk "^1.7.0"
http2-wrapper@^1.0.0-beta.5.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
@ -5460,7 +5440,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -5803,7 +5783,7 @@ is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.2"
is-typedarray@^1.0.0, is-typedarray@~1.0.0:
is-typedarray@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
@ -5864,11 +5844,6 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
istanbul-lib-coverage@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
@ -6429,11 +6404,6 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
jsdoc-type-pratt-parser@~2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.2.tgz#a85e407ac502b444dc23333aa4d6d0dc83f76187"
@ -6487,13 +6457,6 @@ json-buffer@3.0.1:
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-edm-parser@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4"
integrity sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ=
dependencies:
jsonparse "~1.2.0"
json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@ -6514,11 +6477,6 @@ json-schema-traverse@^1.0.0:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@ -6531,7 +6489,7 @@ json-stable-stringify@^1.0.1:
dependencies:
jsonify "~0.0.0"
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
json-stringify-safe@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
@ -6586,21 +6544,6 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
jsonparse@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd"
integrity sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
dependencies:
assert-plus "1.0.0"
extsprintf "1.3.0"
json-schema "0.2.3"
verror "1.10.0"
"jsx-ast-utils@^2.4.1 || ^3.0.0":
version "3.2.1"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b"
@ -7013,14 +6956,6 @@ matchmedia-polyfill@^0.3.1:
resolved "https://registry.yarnpkg.com/matchmedia-polyfill/-/matchmedia-polyfill-0.3.1.tgz#83e754b3b4a297179fcdb71c3aefa0980bff7b18"
integrity sha512-3zLYw2mf078kXzJv/At7FzWC4O5BAtiy+D4sRevC/lewLlhMtVHDS35eWhlP+fQzI/UUiBVu+ez0rsJJzW88iQ==
md5.js@1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d"
integrity sha1-6b296UogpawYsENA/Fdk1bCdkB0=
dependencies:
hash-base "^3.0.0"
inherits "^2.0.1"
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@ -7098,7 +7033,7 @@ mime-db@1.52.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@ -7291,6 +7226,13 @@ node-addon-api@^1.6.3:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
node-fetch@^2.6.7, node-fetch@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
dependencies:
whatwg-url "^5.0.0"
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@ -7403,11 +7345,6 @@ nwsapi@^2.2.0:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -7987,16 +7924,16 @@ pretty-format@^26.0.0, pretty-format@^26.6.2:
ansi-styles "^4.0.0"
react-is "^17.0.1"
process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
progress@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
@ -8056,7 +7993,7 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.28, psl@^1.1.33:
psl@^1.1.33:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
@ -8084,11 +8021,6 @@ qs@6.9.7:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
qs@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
querystring@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
@ -8235,18 +8167,6 @@ readable-stream@^3.0.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
integrity sha1-j5A0HmilPMySh4jaz80Rs265t44=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "~1.0.0"
process-nextick-args "~1.0.6"
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
@ -8377,32 +8297,6 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
request@^2.72.0, request@^2.86.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
caseless "~0.12.0"
combined-stream "~1.0.6"
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
oauth-sign "~0.9.0"
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@ -8573,7 +8467,7 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
safe-buffer@5.2.1, safe-buffer@^5.1.0, 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==
@ -8590,7 +8484,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -8635,11 +8529,6 @@ sass@^1.27.0:
dependencies:
chokidar ">=2.0.0 <4.0.0"
sax@0.5.x:
version "0.5.8"
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=
sax@>=0.6.0, sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@ -9054,21 +8943,6 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
bcrypt-pbkdf "^1.0.0"
dashdash "^1.12.0"
ecc-jsbn "~0.1.1"
getpass "^0.1.1"
jsbn "~0.1.0"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
stack-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
@ -9218,11 +9092,6 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@ -9578,14 +9447,6 @@ tough-cookie@^4.0.0:
punycode "^2.1.1"
universalify "^0.1.2"
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
dependencies:
psl "^1.1.28"
punycode "^2.1.1"
tr46@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@ -9600,6 +9461,11 @@ tr46@^2.1.0:
dependencies:
punycode "^2.1.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
trim-repeated@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21"
@ -9690,6 +9556,11 @@ tslib@^2.0.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tslib@^2.2.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@ -9697,23 +9568,11 @@ tsutils@^3.21.0:
dependencies:
tslib "^1.8.1"
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
dependencies:
safe-buffer "^5.0.1"
tunnel@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@ -9813,11 +9672,6 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
underscore@^1.12.1:
version "1.13.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1"
integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==
underscore@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
@ -9927,16 +9781,6 @@ uuid@3.2.1:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==
uuid@^3.0.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
uuid@^8.3.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
@ -9964,25 +9808,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
validator@~9.4.1:
version "9.4.1"
resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663"
integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
dependencies:
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"
verror@^1.10.0:
version "1.10.1"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb"
@ -10052,6 +9882,11 @@ watchpack@^2.4.0:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webidl-conversions@^4.0.1, webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@ -10172,6 +10007,14 @@ whatwg-url@6.4.0:
tr46 "^1.0.0"
webidl-conversions "^4.0.1"
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
version "8.7.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"
@ -10284,30 +10127,23 @@ xml-name-validator@^3.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
xml2js@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2"
integrity sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I=
dependencies:
sax "0.5.x"
xml2js@^0.4.16:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
xml2js@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7"
integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==
dependencies:
sax ">=0.6.0"
xmlbuilder "~9.0.1"
xmlbuilder "~11.0.0"
xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1:
version "15.1.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==
xmlbuilder@^9.0.7, xmlbuilder@~9.0.1:
version "9.0.7"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xmlchars@^2.2.0:
version "2.2.0"