Merge branch 'gitblame' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#15)

This commit is contained in:
Tan Nhu 2023-04-03 08:03:54 +00:00 committed by Harness
commit c2975da1e6
20 changed files with 1029 additions and 110 deletions

View File

@ -44,6 +44,9 @@
"@harness/uicore": "3.106.3", "@harness/uicore": "3.106.3",
"@harness/use-modal": "1.1.0", "@harness/use-modal": "1.1.0",
"@popperjs/core": "^2.4.2", "@popperjs/core": "^2.4.2",
"@uiw/codemirror-extensions-color": "^4.19.9",
"@uiw/codemirror-extensions-hyper-link": "^4.19.9",
"@uiw/codemirror-themes-all": "^4.19.9",
"@uiw/react-markdown-editor": "^5.10.1", "@uiw/react-markdown-editor": "^5.10.1",
"anser": "2.0.1", "anser": "2.0.1",
"classnames": "^2.2.6", "classnames": "^2.2.6",

59
web/src/App.module.scss Normal file
View File

@ -0,0 +1,59 @@
/*
* NOTE: Styles in this file are loaded in both standalone and embedded
* versions. Be careful! Don't introduce global states that could be conflict
* with Harness Platform and other modules.
*/
.main {
--code-editor-font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
monospace;
--code-editor-font-size: 13px;
--code-editor-border-color: var(--grey-200);
:global {
.Resizer {
background-color: var(--grey-300);
opacity: 0.2;
z-index: 1;
box-sizing: border-box;
background-clip: padding-box;
}
.Resizer:hover {
transition: all 2s ease;
}
.Resizer.horizontal {
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
}
.Resizer.horizontal:hover {
border-top: 5px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.vertical {
width: 11px;
margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
}
.Resizer.vertical:hover {
border-left: 5px solid rgba(0, 0, 0, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}
}
}

6
web/src/App.module.scss.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable */
// this is an auto-generated file
declare const styles: {
readonly main: string
}
export default styles

View File

@ -1,59 +0,0 @@
/*
* NOTE: Styles in this file are loaded in both standalone and embedded
* versions. Be careful! Don't introduce global states that could be conflict
* with Harness Platform and other modules.
*/
.Resizer {
background: var(--grey-300);
opacity: 0.2;
z-index: 1;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding-box;
}
.Resizer:hover {
-webkit-transition: all 2s ease;
transition: all 2s ease;
}
.Resizer.horizontal {
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
}
.Resizer.horizontal:hover {
border-top: 5px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.vertical {
width: 11px;
margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
}
.Resizer.vertical:hover {
border-left: 5px solid rgba(0, 0, 0, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}
.wmde-markdown {
font-size: 14px !important;
}

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react' import React, { useEffect, useState, useCallback, useMemo } from 'react'
import { RestfulProvider } from 'restful-react' import { RestfulProvider } from 'restful-react'
import { TooltipContextProvider } from '@harness/uicore' import { Container, TooltipContextProvider } from '@harness/uicore'
import { ModalProvider } from '@harness/use-modal' import { ModalProvider } from '@harness/use-modal'
import { FocusStyleManager } from '@blueprintjs/core' import { FocusStyleManager } from '@blueprintjs/core'
import { tooltipDictionary } from '@harness/ng-tooltip' import { tooltipDictionary } from '@harness/ng-tooltip'
@ -17,7 +17,7 @@ import type { LanguageRecord } from './framework/strings/languageLoader'
import { StringsContextProvider } from './framework/strings/StringsContextProvider' import { StringsContextProvider } from './framework/strings/StringsContextProvider'
import 'highlight.js/styles/github.css' import 'highlight.js/styles/github.css'
import 'diff2html/bundles/css/diff2html.min.css' import 'diff2html/bundles/css/diff2html.min.css'
import './App.scss' import css from './App.module.scss'
FocusStyleManager.onlyShowFocusOnTabs() FocusStyleManager.onlyShowFocusOnTabs()
@ -43,40 +43,42 @@ const App: React.FC<AppProps> = React.memo(function App({
languageLoader(lang).then(setStrings) languageLoader(lang).then(setStrings)
}, [lang, setStrings]) }, [lang, setStrings])
// Workaround to disable editor dark mode (https://github.com/uiwjs/react-markdown-editor#support-dark-modenight-mode) // TODO: Workaround to disable editor dark mode (https://github.com/uiwjs/react-markdown-editor#support-dark-modenight-mode)
document.documentElement.setAttribute('data-color-mode', 'light') document.documentElement.setAttribute('data-color-mode', 'light')
return strings ? ( return strings ? (
<StringsContextProvider initialStrings={strings}> <Container className={css.main}>
<AppErrorBoundary> <StringsContextProvider initialStrings={strings}>
<RestfulProvider <AppErrorBoundary>
base={standalone ? '/' : getConfig('code')} <RestfulProvider
requestOptions={getRequestOptions} base={standalone ? '/' : getConfig('code')}
queryParams={queryParams} requestOptions={getRequestOptions}
queryParamStringifyOptions={{ skipNulls: true }} queryParams={queryParams}
onResponse={response => { queryParamStringifyOptions={{ skipNulls: true }}
if (!response.ok && response.status === 401) { onResponse={response => {
on401() if (!response.ok && response.status === 401) {
} on401()
}}> }
<AppContextProvider
value={{
standalone,
space,
routes,
lang,
on401,
hooks,
currentUser: defaultCurrentUser,
currentUserProfileURL
}}> }}>
<TooltipContextProvider initialTooltipDictionary={tooltipDictionary}> <AppContextProvider
<ModalProvider>{children ? children : <RouteDestinations />}</ModalProvider> value={{
</TooltipContextProvider> standalone,
</AppContextProvider> space,
</RestfulProvider> routes,
</AppErrorBoundary> lang,
</StringsContextProvider> on401,
hooks,
currentUser: defaultCurrentUser,
currentUserProfileURL
}}>
<TooltipContextProvider initialTooltipDictionary={tooltipDictionary}>
<ModalProvider>{children ? children : <RouteDestinations />}</ModalProvider>
</TooltipContextProvider>
</AppContextProvider>
</RestfulProvider>
</AppErrorBoundary>
</StringsContextProvider>
</Container>
) : null ) : null
}) })

View File

@ -1,3 +1,5 @@
@import 'src/utils/utils';
.main { .main {
--color-border: var(--grey-200); --color-border: var(--grey-200);
--box-radius: 5px; --box-radius: 5px;
@ -102,8 +104,7 @@
.cm-editor .cm-line { .cm-editor .cm-line {
&, &,
* { * {
font-family: var(--font-family); @include mono-font;
font-size: 13px;
} }
} }

View File

@ -17,6 +17,7 @@ export interface StringsMap {
and: string and: string
approve: string approve: string
ascending: string ascending: string
blameCommitLine: string
botAlerts: string botAlerts: string
branch: string branch: string
branchCreated: string branchCreated: string

View File

@ -349,3 +349,4 @@ resetZoom: Reset Zoom
zoomIn: Zoom In zoomIn: Zoom In
zoomOut: Zoom Out zoomOut: Zoom Out
checks: Checks checks: Checks
blameCommitLine: '{author} committed {timestamp}'

View File

@ -63,8 +63,8 @@
} }
.mergedBox { .mergedBox {
padding-top: var(--spacing-small) !important; padding-top: var(--spacing-xsmall) !important;
padding-bottom: var(--spacing-small) !important; padding-bottom: var(--spacing-xsmall) !important;
} }
.mergeContainer { .mergeContainer {

View File

@ -60,7 +60,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
} }
//TODO add actions when you click the options menu button and also api integration //TODO add actions when you click the options menu button and also api integration
return ( return (
<Container width={`30%`} padding={{ left: 'xxlarge', right: 'xxlarge' }}> <Container width={`30%`}>
<Container padding={{ left: 'xxlarge' }}> <Container padding={{ left: 'xxlarge' }}>
<Layout.Vertical> <Layout.Vertical>
<Layout.Horizontal> <Layout.Horizontal>

View File

@ -1,6 +1,16 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { Button, ButtonVariation, Color, Container, FlexExpander, Heading, Layout, Utils } from '@harness/uicore' import {
import { Render } from 'react-jsx-match' Button,
ButtonVariation,
Color,
Container,
FlexExpander,
Heading,
Layout,
useToggle,
Utils
} from '@harness/uicore'
import { Else, Match, Render, Truthy } from 'react-jsx-match'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import { SourceCodeViewer } from 'components/SourceCodeViewer/SourceCodeViewer' import { SourceCodeViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
import type { OpenapiContentInfo, RepoFileContent } from 'services/code' import type { OpenapiContentInfo, RepoFileContent } from 'services/code'
@ -16,9 +26,11 @@ import {
import { filenameToLanguage } from 'utils/Utils' import { filenameToLanguage } from 'utils/Utils'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import { LatestCommitForFile } from 'components/LatestCommit/LatestCommit' import { LatestCommitForFile } from 'components/LatestCommit/LatestCommit'
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
import { CommitModalButton } from 'components/CommitModalButton/CommitModalButton' import { CommitModalButton } from 'components/CommitModalButton/CommitModalButton'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { Readme } from '../FolderContent/Readme' import { Readme } from '../FolderContent/Readme'
import { GitBlame } from './GitBlame'
import css from './FileContent.module.scss' import css from './FileContent.module.scss'
export function FileContent({ export function FileContent({
@ -30,6 +42,7 @@ export function FileContent({
const { routes } = useAppContext() const { routes } = useAppContext()
const { getString } = useStrings() const { getString } = useStrings()
const history = useHistory() const history = useHistory()
const [showGitBlame, toggleGitBlame] = useToggle(false)
const content = useMemo( const content = useMemo(
() => decodeGitContent((resourceContent?.content as RepoFileContent)?.data), () => decodeGitContent((resourceContent?.content as RepoFileContent)?.data),
[resourceContent?.content] [resourceContent?.content]
@ -98,23 +111,38 @@ export function FileContent({
} }
}} }}
/> />
<PipeSeparator />
<Container padding={{ left: 'small', right: 'xsmall' }}>
<Button
variation={ButtonVariation.SECONDARY}
text={showGitBlame ? 'View File' : 'Blame'}
onClick={toggleGitBlame}
/>
</Container>
</Layout.Horizontal> </Layout.Horizontal>
</Layout.Horizontal> </Layout.Horizontal>
<Render when={(resourceContent?.content as RepoFileContent)?.data}> <Render when={(resourceContent?.content as RepoFileContent)?.data}>
<Container className={css.content}> <Container className={css.content}>
<Render when={!markdownInfo}> <Match expr={showGitBlame}>
<SourceCodeViewer language={filenameToLanguage(resourceContent?.name)} source={content} /> <Truthy>
</Render> <GitBlame repoMetadata={repoMetadata} resourcePath={resourcePath} />
<Render when={markdownInfo}> </Truthy>
<Readme <Else>
metadata={repoMetadata} <Render when={!markdownInfo}>
readmeInfo={markdownInfo as OpenapiContentInfo} <SourceCodeViewer language={filenameToLanguage(resourceContent?.name)} source={content} />
contentOnly </Render>
maxWidth="calc(100vw - 346px)" <Render when={markdownInfo}>
gitRef={gitRef} <Readme
/> metadata={repoMetadata}
</Render> readmeInfo={markdownInfo as OpenapiContentInfo}
contentOnly
maxWidth="calc(100vw - 346px)"
gitRef={gitRef}
/>
</Render>
</Else>
</Match>
</Container> </Container>
</Render> </Render>
</Container> </Container>

View File

@ -0,0 +1,83 @@
@import 'src/utils/utils';
.main {
flex-grow: 1;
:global {
.cm-editor {
.cm-content {
padding: 0;
}
.cm-line {
&,
* {
@include mono-font;
}
}
}
}
.lineNo {
min-width: 70px !important;
text-align: right;
padding-right: 10px;
height: 100%;
color: var(--grey-400);
}
}
.gitBlame {
padding: 0 var(--spacing-xlarge) 0 var(--spacing-small) !important;
:global {
.cm-gutter {
background-color: var(--grey-50);
}
}
.layout {
overflow: hidden;
border: 1px solid var(--code-editor-border-color);
border-radius: 4px;
.blameColumn {
width: 400px;
background: var(--grey-50);
flex-shrink: 0;
position: relative;
border-right: 1px solid var(--code-editor-border-color);
.blameBox {
position: absolute;
width: 100%;
&::before {
position: absolute;
z-index: 2;
content: '';
width: 100vw;
height: 1px;
background-color: var(--code-editor-border-color);
}
&:first-of-type::before {
display: none;
}
.blameBoxLayout {
padding: var(--spacing-medium);
align-items: center;
}
}
}
}
}
.blameCommitPortalClass {
:global {
.bp3-popover-content {
padding: var(--spacing-large);
max-width: 600px;
}
}
}

View File

@ -0,0 +1,13 @@
/* eslint-disable */
// this is an auto-generated file
declare const styles: {
readonly main: string
readonly lineNo: string
readonly gitBlame: string
readonly layout: string
readonly blameColumn: string
readonly blameBox: string
readonly blameBoxLayout: string
readonly blameCommitPortalClass: string
}
export default styles

View File

@ -0,0 +1,340 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { throttle } from 'lodash-es'
import { Avatar, Container, FontVariation, Layout, StringSubstitute, Text } from '@harness/uicore'
import { LanguageDescription } from '@codemirror/language'
import { indentWithTab } from '@codemirror/commands'
import { ViewPlugin, ViewUpdate } from '@codemirror/view'
import { languages } from '@codemirror/language-data'
import { EditorView, gutter, GutterMarker, keymap, WidgetType } from '@codemirror/view'
import { Compartment, EditorState } from '@codemirror/state'
import ReactTimeago from 'react-timeago'
import { color } from '@uiw/codemirror-extensions-color'
import { hyperLink } from '@uiw/codemirror-extensions-hyper-link'
import { githubLight as theme } from '@uiw/codemirror-themes-all'
import { useGet } from 'restful-react'
import { Render } from 'react-jsx-match'
import type { GitBlameEntry, GitBlameResponse } from 'utils/types'
import type { GitInfoProps } from 'utils/GitUtils'
import { useStrings } from 'framework/strings'
import { getErrorMessage } from 'utils/Utils'
import { lineWidget, LineWidgetPosition, LineWidgetSpec } from './lineWidget'
import css from './GitBlame.module.scss'
interface BlameBlock {
fromLineNumber: number
toLineNumber: number
topPosition: number
heights: Record<number, number>
commitInfo: GitBlameEntry['Commit']
lines: GitBlameEntry['Lines']
numberOfLines: number
}
type BlameBlockRecord = Record<number, BlameBlock>
const BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION = -1
export const GitBlame: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourcePath'>> = ({
repoMetadata,
resourcePath
}) => {
const { getString } = useStrings()
const [blameBlocks, setBlameBlocks] = useState<BlameBlockRecord>({})
const { data, error, loading } = useGet<GitBlameResponse>({
path: `/api/v1/repos/${repoMetadata?.path}/+/blame/${resourcePath}`,
lazy: !repoMetadata || !resourcePath
})
useEffect(() => {
if (data) {
let fromLineNumber = 1
data.forEach(({ Commit, Lines }) => {
const toLineNumber = fromLineNumber + Lines.length - 1
blameBlocks[fromLineNumber] = {
fromLineNumber,
toLineNumber,
topPosition: BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION, // Not yet calculated
heights: {}, // Not yet calculated
commitInfo: Commit,
lines: Lines,
numberOfLines: Lines.length
}
fromLineNumber = toLineNumber + 1
})
setBlameBlocks({ ...blameBlocks })
}
}, [data]) // eslint-disable-line react-hooks/exhaustive-deps
const findBlockForLineNumber = useCallback(
lineNumber => {
let startLine = lineNumber
while (!blameBlocks[startLine] && startLine > 0) {
startLine--
}
return blameBlocks[startLine]
},
[blameBlocks]
)
// eslint-disable-next-line react-hooks/exhaustive-deps
const onViewUpdate = useCallback(
throttle(({ view, geometryChanged }: ViewUpdate) => {
if (geometryChanged) {
view.viewportLineBlocks.forEach(lineBlock => {
const { from, top, height } = lineBlock
const lineNumber = view.state.doc.lineAt(from).number
const blameBlockAtLineNumber = findBlockForLineNumber(lineNumber)
if (!blameBlockAtLineNumber) {
// eslint-disable-next-line no-console
console.error('Bad math! Cannot find a block at line', lineNumber)
} else {
if (blameBlockAtLineNumber.topPosition === BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION) {
blameBlockAtLineNumber.topPosition = top
}
// CodeMirror reports top position of a block incorrectly sometimes, so we need to normalize it
// using the previous block.
if (lineNumber > 1) {
const previousBlock = findBlockForLineNumber(lineNumber - 1)
if (previousBlock.fromLineNumber !== blameBlockAtLineNumber.fromLineNumber) {
const normalizedTop =
previousBlock.topPosition + Object.values(previousBlock.heights).reduce((a, b) => a + b, 0)
blameBlockAtLineNumber.topPosition = normalizedTop
}
}
blameBlockAtLineNumber.heights[lineNumber] = height
}
})
setBlameBlocks({ ...blameBlocks })
}
}, 50),
[blameBlocks]
)
// TODO: Normalize loading and error rendering when implementing new Design layout
// that have Blame in a separate tab.
if (loading) {
return <Container padding="xlarge">{getString('loading')}</Container>
}
if (error) {
return <Container padding="xlarge">{getErrorMessage(error)}</Container>
}
return (
<Container className={css.gitBlame}>
<Layout.Horizontal className={css.layout}>
<Container className={css.blameColumn}>
{Object.values(blameBlocks)
.filter(({ topPosition }) => topPosition !== BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION)
.map(({ fromLineNumber, topPosition: top, heights, commitInfo }) => {
const height = Object.values(heights).reduce((a, b) => a + b, 0)
return (
<Container className={css.blameBox} key={fromLineNumber} height={height} style={{ top }}>
<Layout.Horizontal spacing="small" className={css.blameBoxLayout}>
<Container>
<Avatar name={commitInfo.Author.Identity.Name} size="normal" hoverCard={false} />
</Container>
<Container style={{ flexGrow: 1 }}>
<Layout.Vertical spacing="xsmall">
<Text
font={{ variation: FontVariation.BODY }}
lineClamp={2}
tooltipProps={{
portalClassName: css.blameCommitPortalClass
}}>
{commitInfo.Title}
</Text>
<Text font={{ variation: FontVariation.BODY }} lineClamp={1}>
<StringSubstitute
str={getString('blameCommitLine')}
vars={{
author: <strong>{commitInfo.Author.Identity.Name}</strong>,
timestamp: <ReactTimeago date={commitInfo.Author.When} />
}}
/>
</Text>
</Layout.Vertical>
</Container>
</Layout.Horizontal>
</Container>
)
})}
</Container>
<Render when={Object.values(blameBlocks).length}>
<GitBlameSourceViewer
source={data?.map(({ Lines }) => Lines.join('\n')).join('\n') || ''}
filename={resourcePath}
onViewUpdate={onViewUpdate}
blameBlocks={blameBlocks}
/>
</Render>
</Layout.Horizontal>
</Container>
)
}
class CustomLineNumber extends GutterMarker {
lineNumber: number
constructor(lineNumber: number) {
super()
this.lineNumber = lineNumber
}
toDOM() {
const element = document.createElement('div')
element.textContent = this.lineNumber.toString()
element.classList.add(css.lineNo)
return element
}
}
interface GitBlameSourceViewerProps {
filename: string
source: string
onViewUpdate?: (update: ViewUpdate) => void
blameBlocks: BlameBlockRecord
}
interface EditorLinePaddingWidgetSpec extends LineWidgetSpec {
blockLines: number
}
function GitBlameSourceViewer({ source, filename, onViewUpdate, blameBlocks }: GitBlameSourceViewerProps) {
const [view, setView] = useState<EditorView>()
const ref = useRef<HTMLDivElement>()
const languageConfig = useMemo(() => new Compartment(), [])
const lineWidgetSpec = useMemo(() => {
const spec: EditorLinePaddingWidgetSpec[] = []
Object.values(blameBlocks).forEach(block => {
const blockLines = block.numberOfLines
spec.push({
lineNumber: block.fromLineNumber,
position: LineWidgetPosition.TOP,
blockLines
})
spec.push({
lineNumber: block.toLineNumber,
position: LineWidgetPosition.BOTTOM,
blockLines
})
})
return spec
}, [blameBlocks])
useEffect(() => {
const customLineNumberGutter = gutter({
lineMarker(_view, line) {
const lineNumber: number = _view.state.doc.lineAt(line.from).number
return new CustomLineNumber(lineNumber)
}
})
const editorView = new EditorView({
doc: source,
extensions: [
customLineNumberGutter,
ViewPlugin.fromClass(
class {
update(update: ViewUpdate) {
onViewUpdate?.(update)
}
}
),
color,
hyperLink, // works pretty well in a markdown file
theme,
EditorView.lineWrapping,
keymap.of([indentWithTab]),
EditorState.readOnly.of(true),
EditorView.editable.of(false),
lineWidget({
spec: lineWidgetSpec,
widgetFor: spec => new EditorLinePaddingWidget(spec)
}),
/**
languageConfig is a compartment that defaults to an empty array (no language support)
at first, when a language is detected, languageConfig is used to reconfigure dynamically.
@see https://codemirror.net/examples/config/
*/
languageConfig.of([])
],
parent: ref.current
})
setView(editorView)
return () => {
editorView.destroy()
}
}, []) // eslint-disable-line
useEffect(() => {
if (view && filename) {
languageDescriptionFrom(filename)
?.load()
.then(languageSupport => {
view.dispatch({ effects: languageConfig.reconfigure(languageSupport) })
})
}
}, [filename, view, languageConfig])
return <Container ref={ref} className={css.main} />
}
function languageDescriptionFrom(filename: string) {
return LanguageDescription.matchFilename(languages, filename)
}
class EditorLinePaddingWidget extends WidgetType {
constructor(readonly spec: EditorLinePaddingWidgetSpec) {
super()
}
toDOM() {
const { blockLines, position, lineNumber } = this.spec
let height = 8
if (position === LineWidgetPosition.BOTTOM && blockLines <= 4) {
height += (5 - blockLines) * 15
}
const div = document.createElement('div')
div.setAttribute('aria-hidden', 'true')
div.setAttribute('data-line-number', String(lineNumber))
div.setAttribute('data-position', position)
div.style.height = `${height}px`
return div
}
eq() {
return false
}
ignoreEvent() {
return false
}
}

View File

@ -0,0 +1,59 @@
import { StateField, Range, Text } from '@codemirror/state'
import { EditorView, Decoration, WidgetType } from '@codemirror/view'
export enum LineWidgetPosition {
TOP = 'top',
BOTTOM = 'bottom'
}
export interface LineWidgetSpec {
lineNumber: number
position: LineWidgetPosition
}
export type LineWidgetGeneration<T extends LineWidgetSpec> = (args: T) => WidgetType
function buildLineDecorations<T extends LineWidgetSpec = LineWidgetSpec>(
doc: Text,
widgetFor: LineWidgetGeneration<T>,
spec: T[]
) {
const decorations: Range<Decoration>[] = []
spec.forEach(_spec => {
const { lineNumber, position } = _spec
const lines = doc.lines
const lineInfo = doc.line(lineNumber)
if (lineNumber <= lines) {
const decoration = Decoration.widget({
widget: widgetFor(_spec),
block: true,
side: position === LineWidgetPosition.TOP ? -1 : 1
})
decorations.push(decoration.range(position === LineWidgetPosition.TOP ? lineInfo.from : lineInfo.to))
}
})
return Decoration.set(decorations)
}
interface LineWidgetParams<T extends LineWidgetSpec = LineWidgetSpec> {
spec: T[]
widgetFor: LineWidgetGeneration<T>
}
export function lineWidget<T extends LineWidgetSpec = LineWidgetSpec>({ spec, widgetFor }: LineWidgetParams<T>) {
return StateField.define({
create: state => {
return buildLineDecorations(state.doc, widgetFor, spec)
},
update(decorations, transation) {
return transation.docChanged ? buildLineDecorations(transation.newDoc, widgetFor, spec) : decorations
},
provide: f => EditorView.decorations.from(f)
})
}

View File

@ -62,6 +62,10 @@ export interface FormDataOpenapiRegisterRequest {
username?: string username?: string
} }
export interface GitrpcBlamePart {
[key: string]: any
}
export type GitrpcFileAction = 'CREATE' | 'UPDATE' | 'DELETE' | 'MOVE' export type GitrpcFileAction = 'CREATE' | 'UPDATE' | 'DELETE' | 'MOVE'
export interface OpenapiAdminUsersCreateRequest { export interface OpenapiAdminUsersCreateRequest {
@ -832,6 +836,52 @@ export const useUpdateRepository = ({ repo_ref, ...props }: UseUpdateRepositoryP
{ base: getConfig('code'), pathParams: { repo_ref }, ...props } { base: getConfig('code'), pathParams: { repo_ref }, ...props }
) )
export interface GetBlameQueryParams {
/**
* The git reference (branch / tag / commitID) that will be used to retrieve the data. If no value is provided the default branch of the repository is used.
*/
git_ref?: string
/**
* Line number from which the file data is considered
*/
line_from?: number
/**
* Line number to which the file data is considered
*/
line_to?: number
}
export interface GetBlamePathParams {
repo_ref: string
path: string
}
export type GetBlameProps = Omit<
GetProps<GitrpcBlamePart[], UsererrorError, GetBlameQueryParams, GetBlamePathParams>,
'path'
> &
GetBlamePathParams
export const GetBlame = ({ repo_ref, path, ...props }: GetBlameProps) => (
<Get<GitrpcBlamePart[], UsererrorError, GetBlameQueryParams, GetBlamePathParams>
path={`/repos/${repo_ref}/blame/${path}`}
base={getConfig('code')}
{...props}
/>
)
export type UseGetBlameProps = Omit<
UseGetProps<GitrpcBlamePart[], UsererrorError, GetBlameQueryParams, GetBlamePathParams>,
'path'
> &
GetBlamePathParams
export const useGetBlame = ({ repo_ref, path, ...props }: UseGetBlameProps) =>
useGet<GitrpcBlamePart[], UsererrorError, GetBlameQueryParams, GetBlamePathParams>(
(paramsInPath: GetBlamePathParams) => `/repos/${paramsInPath.repo_ref}/blame/${paramsInPath.path}`,
{ base: getConfig('code'), pathParams: { repo_ref, path }, ...props }
)
export interface ListBranchesQueryParams { export interface ListBranchesQueryParams {
/** /**
* Indicates whether optional commit information should be included in the response. * Indicates whether optional commit information should be included in the response.

View File

@ -481,6 +481,78 @@ paths:
description: Internal Server Error description: Internal Server Error
tags: tags:
- repository - repository
/repos/{repo_ref}/blame/{path}:
get:
operationId: getBlame
parameters:
- description: The git reference (branch / tag / commitID) that will be used
to retrieve the data. If no value is provided the default branch of the
repository is used.
in: query
name: git_ref
required: false
schema:
default: '{Repository Default Branch}'
type: string
- description: Line number from which the file data is considered
in: query
name: line_from
required: false
schema:
default: 0
type: integer
- description: Line number to which the file data is considered
in: query
name: line_to
required: false
schema:
default: 0
type: integer
- in: path
name: repo_ref
required: true
schema:
type: string
- in: path
name: path
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/GitrpcBlamePart'
type: array
description: OK
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/UsererrorError'
description: Unauthorized
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/UsererrorError'
description: Forbidden
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/UsererrorError'
description: Not Found
'500':
content:
application/json:
schema:
$ref: '#/components/schemas/UsererrorError'
description: Internal Server Error
tags:
- repository
/repos/{repo_ref}/branches: /repos/{repo_ref}/branches:
get: get:
operationId: listBranches operationId: listBranches
@ -3643,6 +3715,8 @@ components:
username: username:
type: string type: string
type: object type: object
GitrpcBlamePart:
type: object
GitrpcFileAction: GitrpcFileAction:
enum: enum:
- CREATE - CREATE

View File

@ -9,3 +9,28 @@ export interface DiffFileEntry extends DiffFile {
fileActivities?: TypesPullReqActivity[] fileActivities?: TypesPullReqActivity[]
activities?: TypesPullReqActivity[] activities?: TypesPullReqActivity[]
} }
export interface GitBlameEntry {
Commit: {
SHA: string
Title: string
Message: string
Author: {
Identity: {
Name: string
Email: string
}
When: string
}
Committer: {
Identity: {
Name: string
Email: string
}
When: string
}
}
Lines: string[]
}
export type GitBlameResponse = GitBlameEntry[]

4
web/src/utils/utils.scss Normal file
View File

@ -0,0 +1,4 @@
@mixin mono-font {
font-family: var(--code-editor-font-family) !important;
font-size: var(--code-editor-font-size) !important;
}

View File

@ -1949,11 +1949,225 @@
"@codemirror/state" "^6.0.0" "@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0" "@codemirror/view" "^6.0.0"
"@uiw/codemirror-extensions-color@^4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-color/-/codemirror-extensions-color-4.19.9.tgz#2391a7c86d51aab195931f003d2df6386ed0877b"
integrity sha512-MHDELaVSplGbPBvNvBnkv/bsEvZbgG+DETZCDU/I7up2j2Bo8Z2cYIzlwjsN8Ap2Zq5aBdT9fN3NTKNdjRxueg==
dependencies:
colors-named "^1.0.0"
colors-named-hex "^1.0.0"
hsl-matcher "^1.2.3"
"@uiw/codemirror-extensions-events@^4.12.3": "@uiw/codemirror-extensions-events@^4.12.3":
version "4.19.4" version "4.19.4"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-events/-/codemirror-extensions-events-4.19.4.tgz#44b59a267752aaab62e94b06f07cd59e6c93f05e" resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-events/-/codemirror-extensions-events-4.19.4.tgz#44b59a267752aaab62e94b06f07cd59e6c93f05e"
integrity sha512-m2aAnR4K+yN8RX/yTKDw2mheNOq9xdVPYiawynroSkKF/6coFQcRa4j6DDDGkL4p0NxXG5vgzYRcCaQGVkQILA== integrity sha512-m2aAnR4K+yN8RX/yTKDw2mheNOq9xdVPYiawynroSkKF/6coFQcRa4j6DDDGkL4p0NxXG5vgzYRcCaQGVkQILA==
"@uiw/codemirror-extensions-hyper-link@^4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-hyper-link/-/codemirror-extensions-hyper-link-4.19.9.tgz#4e0d66e8ff277af47293602fe3355eeaf53a66b6"
integrity sha512-epxMNrCoTXcgGeiZSGw3sqJ+rHP8YuiMCHvb4gZbBHj5vQ4M8GzErqPZBv0eDXZ7jB9wthv8GFBEEw/N1WYrww==
"@uiw/codemirror-theme-abcdef@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-abcdef/-/codemirror-theme-abcdef-4.19.9.tgz#10eac4542997d1064c83061a1e3668bb0f7bd646"
integrity sha512-F2ChAT9+ryD3hhzoHmVUmINxJKl6n+A/I2LTH4Gf9i0VFLvGRpBUGRmPqct3csIK34eG/LyPikoQremVb+p5sg==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-androidstudio@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-androidstudio/-/codemirror-theme-androidstudio-4.19.9.tgz#2c38488867b6820186428f3955c263e705a90e9d"
integrity sha512-Kaf425J4q7+dy0HhDzGlXNEm4AYE+PofWpPWyek45xlKRn0fFfEqdtfwQR+7qBwF4+CazOBawT5Jx6S2uDnr6A==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-atomone@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-atomone/-/codemirror-theme-atomone-4.19.9.tgz#3e2405542aafca92b3a072f3a7e56b52bf327ac9"
integrity sha512-eNDEkB+GDeQ3gAH3R/WXiqFkgcNR8EC/WviZC5Nj+b/2V7AeRinjrWundgST/wYCxdqvu+s3mZ7QN1kpgqCUrQ==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-aura@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-aura/-/codemirror-theme-aura-4.19.9.tgz#f8df2e3216a1a58b0ddaece9421acbd6c47e37a0"
integrity sha512-ISU7xmN1pfjDG1pxpxii7trcmEyuEUCWT1kjAPqBCCCqkofLWPyK+ly83Xl1Xnga+zJ2eNDgh2qXUp58MC1EhQ==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-bbedit@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-bbedit/-/codemirror-theme-bbedit-4.19.9.tgz#7a22f0e0fc96b73a1550b3f10a1e5d5f2e39257e"
integrity sha512-fAXX8umrS8RNUimTL0QVtytX0nujpUSxqmr7X89tJddp89nNfFPIHOzQxLRwNOudCJF4/kjN15cyOseBy2dy8g==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-bespin@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-bespin/-/codemirror-theme-bespin-4.19.9.tgz#e850a8eb331d9b00219f1ccde7368d911c254e69"
integrity sha512-HsReyL2sgW+VdiGNgVV7rxpK0a4jC28lYCjJfLWt1tZSP/XlazTY1o77Yug3L/jYVIgC+FZjaAGyc4NAYQZv8Q==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-darcula@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-darcula/-/codemirror-theme-darcula-4.19.9.tgz#010f04b1a42dab0c8a13aa9cb25a4318274ce88e"
integrity sha512-4s17IOvoOvOdlmbQZ1syO5BGv0D4POYUIZzOFeMyInpcjtYvBgKgng2KSFDdIFJMj/h/nWuusQCGivNUpRc4Iw==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-dracula@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-dracula/-/codemirror-theme-dracula-4.19.9.tgz#681e892d17ff1ca5cb9a2fbe7d0a50ebd9513a62"
integrity sha512-ZOU4WnxrRxFnH1UFK7pQS72OcSL7nY1c3nufVhwSmkQh2ZY8lEsyjAPvQxWS+4oNKFBg2VB2SuNiCO/hPxmmcA==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-duotone@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-duotone/-/codemirror-theme-duotone-4.19.9.tgz#85c88801fa6a97b0b0ba9c17f21b1bc373a77a6e"
integrity sha512-UbC1jZRYZaWFbw5UDUfU57Emvtc3ZXpI6t0Uw7GiqZH0WKfJA1Y7mZtrZNaJxV9ua/78SHL4CflidVajwZcdmQ==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-eclipse@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-eclipse/-/codemirror-theme-eclipse-4.19.9.tgz#7a3dbcbd94b82de41d1c2880402a323452a62329"
integrity sha512-9h9aNd8TnIUxGgzJx5sraEvCEfX5RV/xwigTGoI4gVyYQxhTKKcQn1l2mOvweKYFM0Lw7tmT9+gSv0rA7x/I+Q==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-github@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.19.9.tgz#eecc75527e56e1278680335f1163eb2a92a11e5d"
integrity sha512-TxAJu7eFrIWUvekJ/jbev8fGx4fAYRH3FCxS21rm8B1OzF/aRcJM28zlC1rgKxMV5uG4KjWHOdvT3rNIVs1Meg==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-gruvbox-dark@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-gruvbox-dark/-/codemirror-theme-gruvbox-dark-4.19.9.tgz#7c0fec29c462a73d0ab703f764a3fc07c3b6f776"
integrity sha512-bMGxo3fqwa0RxYnLX+gsK3E3MWzfdz3rVFu/8SvO2BEZugbknRa5eRiodQkVf8r63L5vpcjeZtVgIUsa6It81g==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-material@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-material/-/codemirror-theme-material-4.19.9.tgz#fc140b5085841100daa42fef4d1fc8ac92b2628f"
integrity sha512-0KzOgywKEs+p3/V38u8VW8eu2m8vAkbCVcxvupnTnv0MG5pf8JaS/gVWCQqRv7lSDtwlfVB9UdGmDR2NHEZNew==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-noctis-lilac@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-noctis-lilac/-/codemirror-theme-noctis-lilac-4.19.9.tgz#ba1ed1bfefc4424c250ae0e1887ff67d949e9c70"
integrity sha512-qDIOenJpSizfC+ZNdUnxZbjA4LIvdIMaw+EVFk+d7J5L74ak4HObK4oDVFN8nEUgdPf1FJ0G3yLk8wwVr35yAg==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-nord@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-nord/-/codemirror-theme-nord-4.19.9.tgz#a20d4e16288bf246a68eaa03bcce139daa82c5d3"
integrity sha512-RQLOTYVzauO2BAvE9/21ZurtCxnVO3vbfF7Y1CExEr4vCJr7+TA/KQ/eWJwhk45C+mFbZJ8/VgBev0iqyQ8XVQ==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-okaidia@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-okaidia/-/codemirror-theme-okaidia-4.19.9.tgz#2459dd3b6a4fc63967a8579f2f4aa7bc73adef5e"
integrity sha512-8NWKVbxQrBZc8jA5urwuzkdtotmCo60vvtRzjuq1jyVO+Rh2hyjE2oPbE+7H8qdZRsU+JqGZgEe9Y4Vm/3hlSA==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-solarized@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-solarized/-/codemirror-theme-solarized-4.19.9.tgz#c3703489e04bf2334bdad6c8972e6641b3c573a6"
integrity sha512-Z7uAxxDK0ZICnQmjoMvAqTAhdALG5v8CNmpNsvZZ5qr3bgtPw7N97fvCXXCqzjINOYZIETNVmps/+YwUd0lyyg==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-sublime@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-sublime/-/codemirror-theme-sublime-4.19.9.tgz#a8504dfceff5b833dca27c3de774a71b7284f959"
integrity sha512-u6BtA0jt1bsIK2tBAHr7YF4ZbkW7sEkoHAGgNqjnkYUsSyyvtKliv3AERRKM5WYFJ5npw1mxytpYQ61piUns5Q==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-tokyo-night-day@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-tokyo-night-day/-/codemirror-theme-tokyo-night-day-4.19.9.tgz#4134bd93bc6d44ce3ac70331a55e423b6bd38649"
integrity sha512-6I/+M31SeNHAh1sAQvyUFKH0NbWI7GKQ1ng8Hm9DQJ/t654tUtYtdZlAF4Mn+mwdRLJjkYhok5fxVBgs40NXTA==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-tokyo-night-storm@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-tokyo-night-storm/-/codemirror-theme-tokyo-night-storm-4.19.9.tgz#c0ef96ca87c6c00adc809980c2b2cfade0b575c3"
integrity sha512-iBim76h4VgnwIbGWssJnMrfTlPCjXh/BqgMm/mc3GmSu2z+SXcAEzW7NC4Iui85obpMVRo7G/8AvkBF5EVUFmg==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-tokyo-night@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-tokyo-night/-/codemirror-theme-tokyo-night-4.19.9.tgz#529b0af8cb06aeae4a43caad6425eb9d9404aba2"
integrity sha512-AWxY2dIJSwXGSu/kgnb5/kDyjX6fYgJrt6e8FKszGf7V/ctgE/eA1lJhDAXqbErC9tx25XDkS+oE7dyoOtZ9Ag==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-vscode@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.19.9.tgz#c426054e328ffc9515323acc92672f24100255e3"
integrity sha512-cpj40KO+O3mqIe8GmkWGlfo+ltA/72v+kWVZA4QoOK/DfuNGL4mAXZWFjxSvbSI6BmJZISKpKL23mIOpRmy1Zw==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-theme-xcode@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-theme-xcode/-/codemirror-theme-xcode-4.19.9.tgz#866ff78ea7c2987a06821f135955d57aa2d3f250"
integrity sha512-3oF2nrC9ef2Um/9yLSRDM9YTco58kePhiTtyaOt+Ly6SzEnGLfughRk1Aam4JLMqwXYRIhu0cLiqK0OIJZDLkw==
dependencies:
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-themes-all@^4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-themes-all/-/codemirror-themes-all-4.19.9.tgz#ffb4327c3cc97362aedd7c77754c21e352370499"
integrity sha512-/Id11RqhYEz40W3skdcQvgJsZgO6mJc27cwjL2I+EC4jtSSo9MrJruYlN+wRDt/+jASMSWbw/Z4oJsgueAm3dA==
dependencies:
"@uiw/codemirror-theme-abcdef" "4.19.9"
"@uiw/codemirror-theme-androidstudio" "4.19.9"
"@uiw/codemirror-theme-atomone" "4.19.9"
"@uiw/codemirror-theme-aura" "4.19.9"
"@uiw/codemirror-theme-bbedit" "4.19.9"
"@uiw/codemirror-theme-bespin" "4.19.9"
"@uiw/codemirror-theme-darcula" "4.19.9"
"@uiw/codemirror-theme-dracula" "4.19.9"
"@uiw/codemirror-theme-duotone" "4.19.9"
"@uiw/codemirror-theme-eclipse" "4.19.9"
"@uiw/codemirror-theme-github" "4.19.9"
"@uiw/codemirror-theme-gruvbox-dark" "4.19.9"
"@uiw/codemirror-theme-material" "4.19.9"
"@uiw/codemirror-theme-noctis-lilac" "4.19.9"
"@uiw/codemirror-theme-nord" "4.19.9"
"@uiw/codemirror-theme-okaidia" "4.19.9"
"@uiw/codemirror-theme-solarized" "4.19.9"
"@uiw/codemirror-theme-sublime" "4.19.9"
"@uiw/codemirror-theme-tokyo-night" "4.19.9"
"@uiw/codemirror-theme-tokyo-night-day" "4.19.9"
"@uiw/codemirror-theme-tokyo-night-storm" "4.19.9"
"@uiw/codemirror-theme-vscode" "4.19.9"
"@uiw/codemirror-theme-xcode" "4.19.9"
"@uiw/codemirror-themes" "4.19.9"
"@uiw/codemirror-themes@4.19.9":
version "4.19.9"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-themes/-/codemirror-themes-4.19.9.tgz#988876213a2e350244ac2a0d479ebb792afbe94d"
integrity sha512-PH3hl1w42z7GXe/zoD9gSadOGBWyKPl7vHm/8V1PUuHXT21+neyfRc7v0xPwb05pGP6ExfbmPi78y4+g6cHopg==
dependencies:
"@codemirror/language" "^6.0.0"
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
"@uiw/codemirror-themes@^4.12.3": "@uiw/codemirror-themes@^4.12.3":
version "4.19.4" version "4.19.4"
resolved "https://registry.yarnpkg.com/@uiw/codemirror-themes/-/codemirror-themes-4.19.4.tgz#78a443d98ec8261134349163efd01e939e4a3f22" resolved "https://registry.yarnpkg.com/@uiw/codemirror-themes/-/codemirror-themes-4.19.4.tgz#78a443d98ec8261134349163efd01e939e4a3f22"
@ -3685,6 +3899,16 @@ colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
colors-named-hex@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/colors-named-hex/-/colors-named-hex-1.0.1.tgz#efbecaae99ac0793c7e6d34341845c5d54b0f460"
integrity sha512-2uoNWhxAPRn3GVpbuaQ2p5LAMhsjd9r/ZqQNJTUcEr1XQCkMFWh9zPLJLjg1l51xYApdDCMIEjkDNpoAXK2vMw==
colors-named@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/colors-named/-/colors-named-1.0.1.tgz#addea53c6fcf2661b7fae07553f8a396b615adb9"
integrity sha512-MGhZwJ6SPSdrg5qsr3YB3sD4zB2bfm3bPj9iIdkjxruUYNQakiBBWL+kFM3SXnyfkZClj3Bq7vofBKbgeJRJ/Q==
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@ -6318,6 +6542,11 @@ hpack.js@^2.1.6:
readable-stream "^2.0.1" readable-stream "^2.0.1"
wbuf "^1.1.0" wbuf "^1.1.0"
hsl-matcher@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/hsl-matcher/-/hsl-matcher-1.2.3.tgz#a398ecef6ef592df9f636082f0ba0584ecc68825"
integrity sha512-irW1socbRjPPAtX/jdl2AWntFB2cCv5W0BGoeZW4uFURCWzupsyAqnw0wPB1qh5wKjdrEYVRV++IuLko+P31OA==
html-encoding-sniffer@^2.0.1: html-encoding-sniffer@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"