diff --git a/package.json b/package.json index 93f4c62..3f722cb 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,10 @@ "onView:gitlens.views.contributors", "onView:gitlens.views.searchAndCompare", "onView:gitlens.views.worktrees", + "onView:gitlens.views.commitDetails", "onWebviewPanel:gitlens.welcome", "onWebviewPanel:gitlens.settings", + "onWebviewPanel:gitlens.commitDetails", "onCommand:gitlens.plus.learn", "onCommand:gitlens.plus.loginOrSignUp", "onCommand:gitlens.plus.logout", @@ -99,6 +101,7 @@ "onCommand:gitlens.showStashesView", "onCommand:gitlens.showTagsView", "onCommand:gitlens.showTimelineView", + "onCommand:gitlens.showCommitDetailsPage", "onCommand:gitlens.showWorktreesView", "onCommand:gitlens.compareWith", "onCommand:gitlens.compareHeadWith", @@ -3925,6 +3928,11 @@ "category": "GitLens" }, { + "command": "gitlens.showCommitDetailsPage", + "title": "Show Commit Details View", + "category": "GitLens" + }, + { "command": "gitlens.showWorktreesView", "title": "Show Worktrees View", "category": "GitLens" @@ -6224,6 +6232,10 @@ "when": "gitlens:enabled" }, { + "command": "gitlens.showCommitDetailsPage", + "when": "gitlens:enabled" + }, + { "command": "gitlens.showWorktreesView", "when": "gitlens:enabled && !gitlens:hasVirtualFolders" }, @@ -10934,6 +10946,15 @@ ], "scm": [ { + "type": "webview", + "id": "gitlens.views.commitDetails", + "name": "Commit Details", + "when": "!gitlens:disabled", + "contextualTitle": "GitLens", + "icon": "images/views/commits.svg", + "visibility": "hidden" + }, + { "id": "gitlens.views.commits", "name": "Commits", "when": "!gitlens:disabled", @@ -11263,6 +11284,7 @@ "chroma-js": "2.4.2", "https-proxy-agent": "5.0.1", "iconv-lite": "0.6.3", + "lit": "^2.2.7", "lodash-es": "4.17.21", "md5.js": "1.3.5", "node-fetch": "2.6.7", @@ -11297,6 +11319,7 @@ "eslint-import-resolver-typescript": "3.4.0", "eslint-plugin-anti-trojan-source": "1.1.0", "eslint-plugin-import": "2.26.0", + "eslint-plugin-lit": "^1.6.1", "fork-ts-checker-webpack-plugin": "6.5.2", "glob": "8.0.3", "html-loader": "4.1.0", diff --git a/src/commands/git/search.ts b/src/commands/git/search.ts index 96702a4..3339f14 100644 --- a/src/commands/git/search.ts +++ b/src/commands/git/search.ts @@ -36,6 +36,7 @@ interface Context { interface State extends Required { repo: string | Repository; showResultsInSideBar: boolean | SearchResultsNode; + showResultsInDetails?: boolean; } export interface SearchGitCommandArgs { @@ -242,6 +243,13 @@ export class SearchGitCommand extends QuickCommand { context.commit = result; } + if (state.showResultsInDetails) { + void this.container.commitDetailsWebviewView.show({ + commit: context.commit, + }); + break; + } + const result = yield* getSteps( this.container, { diff --git a/src/commands/gitCommands.actions.ts b/src/commands/gitCommands.actions.ts index 4048d9b..ebc5046 100644 --- a/src/commands/gitCommands.actions.ts +++ b/src/commands/gitCommands.actions.ts @@ -583,6 +583,10 @@ export namespace GitActions { } } + export async function openDetails(commit: GitCommit): Promise { + void (await Container.instance.commitDetailsWebviewView.show({ commit: commit })); + } + export async function openFiles(commit: GitCommit): Promise; export async function openFiles(files: GitFile[], repoPath: string, ref: string): Promise; export async function openFiles( diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts index 64b5a33..31c3058 100644 --- a/src/commands/quickCommand.steps.ts +++ b/src/commands/quickCommand.steps.ts @@ -41,6 +41,7 @@ import { CommitOpenChangesCommandQuickPickItem, CommitOpenChangesWithDiffToolCommandQuickPickItem, CommitOpenChangesWithWorkingCommandQuickPickItem, + CommitOpenDetailsCommandQuickPickItem, CommitOpenDirectoryCompareCommandQuickPickItem, CommitOpenDirectoryCompareWithWorkingCommandQuickPickItem, CommitOpenFileCommandQuickPickItem, @@ -1783,6 +1784,7 @@ async function getShowCommitOrStashStepItems< new CommitOpenAllChangesWithWorkingCommandQuickPickItem(state.reference), new CommitOpenAllChangesWithDiffToolCommandQuickPickItem(state.reference), QuickPickSeparator.create(), + new CommitOpenDetailsCommandQuickPickItem(state.reference), new CommitOpenFilesCommandQuickPickItem(state.reference), new CommitOpenRevisionsCommandQuickPickItem(state.reference), ); diff --git a/src/commands/searchCommits.ts b/src/commands/searchCommits.ts index 907fb90..7cdfc3c 100644 --- a/src/commands/searchCommits.ts +++ b/src/commands/searchCommits.ts @@ -13,6 +13,7 @@ export interface SearchCommitsCommandArgs { prefillOnly?: boolean; + showResultsInDetails?: boolean; showResultsInSideBar?: boolean; } @@ -53,6 +54,7 @@ export class SearchCommitsCommand extends Command { ...args?.search, showResultsInSideBar: configuration.get('gitCommands.search.showResultsInSideBar') ?? args?.showResultsInSideBar, + showResultsInDetails: args?.showResultsInDetails ?? false, }, })); } diff --git a/src/constants.ts b/src/constants.ts index a9b5a0e..74fe602 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -199,6 +199,7 @@ export const enum Commands { ShowTagsView = 'gitlens.showTagsView', ShowWorktreesView = 'gitlens.showWorktreesView', RefreshTimelinePage = 'gitlens.refreshTimelinePage', + ShowCommitDetailsPage = 'gitlens.showCommitDetailsPage', ShowTimelinePage = 'gitlens.showTimelinePage', ShowTimelineView = 'gitlens.showTimelineView', ShowWelcomePage = 'gitlens.showWelcomePage', diff --git a/src/container.ts b/src/container.ts index 05b9c12..4c1213f 100644 --- a/src/container.ts +++ b/src/container.ts @@ -50,6 +50,8 @@ import { ViewCommands } from './views/viewCommands'; import { ViewFileDecorationProvider } from './views/viewDecorationProvider'; import { WorktreesView } from './views/worktreesView'; import { VslsController } from './vsls/vsls'; +import { CommitDetailsWebview } from './webviews/commitDetails/commitDetailsWebview'; +import { CommitDetailsWebviewView } from './webviews/commitDetails/commitDetailsWebviewView'; import { HomeWebviewView } from './webviews/home/homeWebviewView'; import { RebaseEditorProvider } from './webviews/rebase/rebaseEditor'; import { SettingsWebview } from './webviews/settings/settingsWebview'; @@ -175,6 +177,8 @@ export class Container { context.subscriptions.push((this._timelineWebview = new TimelineWebview(this))); context.subscriptions.push((this._welcomeWebview = new WelcomeWebview(this))); context.subscriptions.push((this._rebaseEditor = new RebaseEditorProvider(this))); + context.subscriptions.push((this._commitDetailsWebview = new CommitDetailsWebview(this))); + context.subscriptions.push((this._commitDetailsWebviewView = new CommitDetailsWebviewView(this))); context.subscriptions.push(new ViewFileDecorationProvider()); @@ -286,6 +290,24 @@ export class Container { return this._commitsView; } + private _commitDetailsWebview: CommitDetailsWebview | undefined; + get commitDetailsWebview() { + if (this._commitDetailsWebview == null) { + this._context.subscriptions.push((this._commitDetailsWebview = new CommitDetailsWebview(this))); + } + + return this._commitDetailsWebview; + } + + private _commitDetailsWebviewView: CommitDetailsWebviewView | undefined; + get commitDetailsWebviewView() { + if (this._commitDetailsWebviewView == null) { + this._context.subscriptions.push((this._commitDetailsWebviewView = new CommitDetailsWebviewView(this))); + } + + return this._commitDetailsWebviewView; + } + private readonly _context: ExtensionContext; get context() { return this._context; diff --git a/src/quickpicks/items/commits.ts b/src/quickpicks/items/commits.ts index 976bddb..a19d892 100644 --- a/src/quickpicks/items/commits.ts +++ b/src/quickpicks/items/commits.ts @@ -251,6 +251,16 @@ export class CommitOpenDirectoryCompareWithWorkingCommandQuickPickItem extends C } } +export class CommitOpenDetailsCommandQuickPickItem extends CommandQuickPickItem { + constructor(private readonly commit: GitCommit, item?: QuickPickItem) { + super(item ?? '$(files) Open in Commit Details'); + } + + override execute(_options: { preserveFocus?: boolean; preview?: boolean }): Promise { + return GitActions.Commit.openDetails(this.commit); + } +} + export class CommitOpenFilesCommandQuickPickItem extends CommandQuickPickItem { constructor(private readonly commit: GitCommit, item?: QuickPickItem) { super(item ?? '$(files) Open Files'); diff --git a/src/webviews/apps/.eslintrc.json b/src/webviews/apps/.eslintrc.json index 5a988bf..a106b89 100644 --- a/src/webviews/apps/.eslintrc.json +++ b/src/webviews/apps/.eslintrc.json @@ -1,8 +1,11 @@ { - "extends": ["../../../.eslintrc.base.json"], + "extends": ["../../../.eslintrc.base.json", "plugin:lit/recommended"], "env": { "browser": true }, + "rules": { + "import/extensions": ["error", "never", { "js": "always" }] + }, "parserOptions": { "project": "src/webviews/apps/tsconfig.json" } diff --git a/src/webviews/apps/commitDetails/commitDetails.html b/src/webviews/apps/commitDetails/commitDetails.html new file mode 100644 index 0000000..5a76c35 --- /dev/null +++ b/src/webviews/apps/commitDetails/commitDetails.html @@ -0,0 +1,94 @@ + + + + + + + +
+ + + +
+
+
+
    +
  • + +
  • +
+ +
+
+

+ +

+
+
+ + + Autolinks + +
+
+ +
+
+ +
+
+
+ + + Files changed + + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+ #{endOfBody} + + + diff --git a/src/webviews/apps/commitDetails/commitDetails.scss b/src/webviews/apps/commitDetails/commitDetails.scss new file mode 100644 index 0000000..130eb96 --- /dev/null +++ b/src/webviews/apps/commitDetails/commitDetails.scss @@ -0,0 +1,342 @@ +// @import '../shared/base'; +// @import '../shared/buttons'; +// @import '../shared/icons'; + +:root { + --gitlens-gutter-width: 20px; + --gitlens-scrollbar-gutter-width: 10px; + --gitlens-view-background-color: #fafafa; +} + +// generic resets +html { + // height: 100%; + font-size: 62.5%; + box-sizing: border-box; + font-family: var(--font-family); +} + +* { + &, + &::before, + &::after { + box-sizing: inherit; + } +} + +body { + // height: 100%; + font-family: var(--font-family); + font-size: var(--font-size); + color: var(--color-foreground); + padding: 0; +} + +ul { + list-style: none; + margin: 0; + padding: 0; +} + +::-webkit-scrollbar-corner { + background-color: transparent !important; +} + +.button { + --button-foreground: var(--vscode-button-foreground); + --button-background: var(--vscode-button-background); + --button-hover-background: var(--vscode-button-hoverBackground); + + display: inline-block; + border: none; + padding: 0.4rem; + font-size: inherit; + line-height: inherit; + text-align: center; + text-decoration: none; + user-select: none; + background: var(--button-background); + color: var(--button-foreground); + cursor: pointer; + + &:hover { + background: var(--button-hover-background); + } + + &:focus { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: 0.2rem; + } + + &--full { + width: 100%; + } +} + +// webview-specific styles +.change-list { + list-style: none; + + &__item { + // & + & { + // margin-top: 0.25rem; + // } + } + &__link { + width: 100%; + color: inherit; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + &__type {} + &__filename {} + &__path {} + &__actions { + flex: none; + } + &__action {} +} + +.pull-request, +.issue { + display: grid; + gap: 0.25rem 0.5rem; + justify-content: start; + + &__icon { + grid-column: 1; + grid-row: 1 / 3; + color: var(--vscode-gitlens-mergedPullRequestIconColor); + } + &__title { + grid-column: 2; + grid-row: 1; + margin: 0; + font-size: 1.5rem; + } + &__date { + grid-column: 2; + grid-row: 2; + margin: 0; + font-size: 1.2rem; + } +} + +.commit-author { + display: grid; + gap: 0.25rem 0.5rem; + justify-content: start; + + &__avatar { + grid-column: 1; + grid-row: 1 / 3; + } + &__name { + grid-column: 2; + grid-row: 1; + font-size: 1.5rem; + } + &__date { + grid-column: 2; + grid-row: 2; + font-size: 1.2rem; + } +} + +.commit-details { + &__commit { + padding: { // 1.5rem + left: var( --gitlens-gutter-width); + right: var( --gitlens-scrollbar-gutter-width); + } + // background-color: var(--color-background--lighten-05); + margin-bottom: 1.75rem; + + display: flex; + flex-direction: column; + gap: 1.5rem; + } + + &__top { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + gap: 0.5rem; + } + + &__message { + font-size: 1.5rem; + border: 1px solid var(--color-background--lighten-15); + padding: 0.5rem; + } + &__message-text { + flex: 1; + margin: 0; + display: block; + + @supports (-webkit-line-clamp: 6) { + display: -webkit-box; + -webkit-line-clamp: 6; + -webkit-box-orient: vertical; + overflow: hidden; + } + } + + &__commit-action { + display: inline-flex; + justify-content: center; + align-items: center; + width: 20px; + height: 20px; + border-radius: 0.25em; + color: inherit; + padding: 2px; + vertical-align: text-bottom; + text-decoration: none; + + > * { + pointer-events: none; + } + + &:hover { + color: var(--vscode-foreground); + background-color: var(--color-background--lighten-15); + } + } + + &__authors { + flex-basis: 100%; + } + &__author { + & + & { + margin-top: 0.5rem; + } + } + + &__rich { + padding: 0.5rem var( --gitlens-scrollbar-gutter-width) 1rem var( --gitlens-gutter-width); + + > :last-child { + margin-top: 0.5rem; + } + } + &__pull-request {} + &__issue { + > :not(:first-child) { + margin-top: 0.5rem; + } + } + + &__stats { + margin-left: 0.5rem; + } + + // &__files { + // border-top: 1px solid var(--color-background--lighten-075); + // padding: { + // top: 1.75rem; + // } + // } + &__file { + padding: { + left: var( --gitlens-gutter-width); + right: var( --gitlens-scrollbar-gutter-width); + top: 1px; + bottom: 1px; + } + } + &__item-skeleton { + padding: { + left: var( --gitlens-gutter-width); + right: var( --gitlens-scrollbar-gutter-width); + top: 1px; + bottom: 1px; + } + } +} + +.commit-detail-panel { + $block: &; + + max-height: 100vh; + overflow: auto; + scrollbar-gutter: stable; + + [aria-hidden="true"] { + display: none; + } + + &__none { + padding: { + left: var( --gitlens-gutter-width); + right: var( --gitlens-scrollbar-gutter-width); + } + } + + &__header { + margin: { + top: 1rem; + bottom: 1.5rem; + } + } + &__title { + font-size: 2.4rem; + + // FIXME: specificity hack + &-icon { + // color: var(--vscode-banner-iconForeground); + font-size: inherit !important; + } + } + &__nav { + border: 1px solid var(--color-button-secondary-background); + padding: 0.5rem; + margin: { + top: 1rem; + bottom: 1.5rem; + } + } + &__commit-count { + margin: { + top: 0; + bottom: 0.5rem; + } + } + &__commits {} + &__commit { + & + & { + margin-top: 0.5rem; + } + } + &__commit-button { + appearance: none; + text-decoration: none; + border: none; + color: var(--color-button-foreground); + background-color: var(--color-button-secondary-background); + padding: 0.5rem; + display: flex; + align-items: center; + width: 100%; + gap: 0.5rem; + + > :last-child { + margin-left: auto; + } + + &[aria-current="true"] { + background-color: var(--color-button-background); + } + } + &__main { + padding: { + top: 1rem; + bottom: 1rem; + } + } +} + +@import '../shared/codicons'; +@import '../shared/utils'; diff --git a/src/webviews/apps/commitDetails/commitDetails.ts b/src/webviews/apps/commitDetails/commitDetails.ts new file mode 100644 index 0000000..90f8c9e --- /dev/null +++ b/src/webviews/apps/commitDetails/commitDetails.ts @@ -0,0 +1,392 @@ +/*global*/ +import { IpcMessage, onIpc } from '../../../webviews/protocol'; +import { + CommitActionsCommandType, + CommitSummary, + DidChangeNotificationType, + FileComparePreviousCommandType, + FileCompareWorkingCommandType, + FileMoreActionsCommandType, + OpenFileCommandType, + OpenFileOnRemoteCommandType, + PickCommitCommandType, + RichContentNotificationType, + State, +} from '../../commitDetails/protocol'; +import { App } from '../shared/appBase'; +import { FileChangeItem, FileChangeItemEventDetail } from '../shared/components/commit/file-change-item'; +import { DOM } from '../shared/dom'; +import './commitDetails.scss'; +import '../shared/components/codicon'; +import '../shared/components/commit/commit-identity'; +import '../shared/components/formatted-date'; +import '../shared/components/rich/issue-pull-request'; +import '../shared/components/skeleton-loader'; +import '../shared/components/commit/commit-stats'; +// eslint-disable-next-line @typescript-eslint/no-duplicate-imports +import '../shared/components/commit/file-change-item'; +import '../shared/components/webview-pane'; + +export class CommitDetailsApp extends App { + constructor() { + super('CommitDetailsApp'); + console.log('CommitDetailsApp', this.state); + } + + override onInitialize() { + console.log('CommitDetailsApp onInitialize', this.state); + this.renderContent(); + } + + override onBind() { + const disposables = [ + DOM.on('file-change-item', 'file-open-on-remote', e => + this.onOpenFileOnRemote(e.detail), + ), + DOM.on('file-change-item', 'file-open', e => + this.onOpenFile(e.detail), + ), + DOM.on('file-change-item', 'file-compare-working', e => + this.onCompareFileWithWorking(e.detail), + ), + DOM.on('file-change-item', 'file-compare-previous', e => + this.onCompareFileWithPrevious(e.detail), + ), + DOM.on('file-change-item', 'file-more-actions', e => + this.onFileMoreActions(e.detail), + ), + DOM.on('[data-action="commit-show-actions"]', 'click', e => this.onCommitMoreActions(e)), + DOM.on('[data-action="pick-commit"]', 'click', e => this.onPickCommit(e)), + ]; + + return disposables; + } + + onPickCommit(e: MouseEvent) { + this.sendCommand(PickCommitCommandType, undefined); + } + + onOpenFileOnRemote(e: FileChangeItemEventDetail) { + this.sendCommand(OpenFileOnRemoteCommandType, e); + } + + onOpenFile(e: FileChangeItemEventDetail) { + this.sendCommand(OpenFileCommandType, e); + } + + onCompareFileWithWorking(e: FileChangeItemEventDetail) { + this.sendCommand(FileCompareWorkingCommandType, e); + } + + onCompareFileWithPrevious(e: FileChangeItemEventDetail) { + this.sendCommand(FileComparePreviousCommandType, e); + } + + onFileMoreActions(e: FileChangeItemEventDetail) { + this.sendCommand(FileMoreActionsCommandType, e); + } + + onCommitMoreActions(e: MouseEvent) { + e.preventDefault(); + if (this.state.selected === undefined) { + e.stopPropagation(); + return; + } + + this.sendCommand(CommitActionsCommandType, undefined); + } + + protected override onMessageReceived(e: MessageEvent) { + const msg = e.data as IpcMessage; + switch (msg.method) { + case RichContentNotificationType.method: + onIpc(RichContentNotificationType, msg, params => { + const newState = { ...this.state }; + if (params.formattedMessage != null) { + newState.selected.message = params.formattedMessage; + } + if (params.pullRequest != null) { + newState.pullRequest = params.pullRequest; + } + if (params.formattedMessage != null) { + newState.issues = params.issues; + } + + this.state = newState; + this.renderRichContent(); + }); + break; + case DidChangeNotificationType.method: + onIpc(DidChangeNotificationType, msg, params => { + this.state = { ...this.state, ...params.state }; + this.renderContent(); + }); + break; + } + } + + renderCommit() { + const hasSelection = this.state.selected !== undefined; + const $empty = document.getElementById('empty'); + const $main = document.getElementById('main'); + $empty?.setAttribute('aria-hidden', hasSelection ? 'true' : 'false'); + $main?.setAttribute('aria-hidden', hasSelection ? 'false' : 'true'); + + return hasSelection; + } + + renderRichContent() { + if (!this.renderCommit()) { + return; + } + + this.renderMessage(); + this.renderAutolinks(); + } + + renderContent() { + if (!this.renderCommit()) { + return; + } + + this.renderMessage(); + this.renderAuthor(); + this.renderStats(); + this.renderFiles(); + + if (this.state.includeRichContent) { + this.renderAutolinks(); + } + } + + renderChoices() { + // + const $el = document.querySelector('[data-region="choices"]'); + if ($el == null) { + return; + } + + if (this.state.commits?.length) { + const $count = $el.querySelector('[data-region="choices-count"]'); + if ($count != null) { + $count.innerHTML = `${this.state.commits.length}`; + } + + const $list = $el.querySelector('[data-region="choices-list"]'); + if ($list != null) { + $list.innerHTML = this.state.commits + .map( + (item: CommitSummary) => ` +
  • + +
  • + `, + ) + .join(''); + } + $el.setAttribute('aria-hidden', 'false'); + } else { + $el.setAttribute('aria-hidden', 'true'); + $el.innerHTML = ''; + } + } + + renderStats() { + const $el = document.querySelector('[data-region="stats"]'); + if ($el == null) { + return; + } + if (this.state.selected.stats?.changedFiles !== undefined) { + const { added, deleted, changed } = this.state.selected.stats.changedFiles; + $el.innerHTML = ` + + `; + } else { + $el.innerHTML = ''; + } + } + + renderFiles() { + const $el = document.querySelector('[data-region="files"]'); + if ($el == null) { + return; + } + + if (this.state.selected.files?.length > 0) { + $el.innerHTML = this.state.selected.files + .map( + (file: Record) => ` +
  • + +
  • + `, + ) + .join(''); + $el.setAttribute('aria-hidden', 'false'); + } else { + $el.innerHTML = ''; + } + } + + renderAuthor() { + const $el = document.querySelector('[data-region="author"]'); + if ($el == null) { + return; + } + + if (this.state.selected.author != null) { + $el.innerHTML = ` + + `; + $el.setAttribute('aria-hidden', 'false'); + } else { + $el.innerHTML = ''; + $el.setAttribute('aria-hidden', 'true'); + } + } + + renderCommitter() { + //
  • + // + //
  • + const $el = document.querySelector('[data-region="committer"]'); + if ($el == null) { + return; + } + + if (this.state.selected.committer != null) { + $el.innerHTML = ` + + `; + $el.setAttribute('aria-hidden', 'false'); + } else { + $el.innerHTML = ''; + $el.setAttribute('aria-hidden', 'true'); + } + } + + renderTitle() { + // + const $el = document.querySelector('[data-region="commit-title"]'); + if ($el != null) { + $el.innerHTML = this.state.selected.shortSha; + } + } + + renderMessage() { + const $el = document.querySelector('[data-region="message"]'); + if ($el == null) { + return; + } + + const [headline, ...lines] = this.state.selected.message.split('\n'); + if (lines.length > 1) { + $el.innerHTML = `${headline}
    ${lines.join('
    ')}`; + } else { + $el.innerHTML = `${headline}`; + } + } + + renderAutolinks() { + const $el = document.querySelector('[data-region="autolinks"]'); + if ($el == null) { + return; + } + + const $info = document.querySelector('[data-region="rich-info"]'); + if (this.state.pullRequest != null || this.state.issues?.length > 0) { + $el.setAttribute('aria-hidden', 'false'); + $info?.setAttribute('aria-hidden', 'true'); + this.renderPullRequest(); + this.renderIssues(); + } else { + $el.setAttribute('aria-hidden', 'true'); + $info?.setAttribute('aria-hidden', 'false'); + } + } + + renderPullRequest() { + const $el = document.querySelector('[data-region="pull-request"]'); + if ($el == null) { + return; + } + + if (this.state.pullRequest != null) { + $el.innerHTML = ` + + `; + $el.setAttribute('aria-hidden', 'false'); + } else { + $el.innerHTML = ''; + $el.setAttribute('aria-hidden', 'true'); + } + } + + renderIssues() { + const $el = document.querySelector('[data-region="issue"]'); + if ($el == null) { + return; + } + if (this.state.issues?.length > 0) { + $el.innerHTML = this.state.issues + .map( + (issue: Record) => ` + + `, + ) + .join(''); + $el.setAttribute('aria-hidden', 'false'); + } else { + $el.innerHTML = ''; + $el.setAttribute('aria-hidden', 'true'); + } + } +} + +new CommitDetailsApp(); diff --git a/src/webviews/apps/shared/base.scss b/src/webviews/apps/shared/base.scss index e9e302f..b59cbb0 100644 --- a/src/webviews/apps/shared/base.scss +++ b/src/webviews/apps/shared/base.scss @@ -77,6 +77,10 @@ textarea { margin: 0; } +button:not(:disabled) { + cursor: pointer; +} + input[type='checkbox'] { background: none; border: none; diff --git a/src/webviews/apps/shared/components/codicon.ts b/src/webviews/apps/shared/components/codicon.ts new file mode 100644 index 0000000..9ea83c4 --- /dev/null +++ b/src/webviews/apps/shared/components/codicon.ts @@ -0,0 +1,1505 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('code-icon') +export class CodeIcon extends LitElement { + // Escaping unicode: https://lit.dev/docs/components/styles/#using-unicode-escapes-in-styles + static override styles = css` + :host { + font: normal normal normal 16px/1 codicon; + display: inline-block; + text-decoration: none; + text-rendering: auto; + text-align: center; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + color: inherit; + vertical-align: text-bottom; + letter-spacing: normal; + } + + :host([icon='add']):before { + content: '\\ea60'; + } + :host([icon='plus']):before { + content: '\\ea60'; + } + :host([icon='gist-new']):before { + content: '\\ea60'; + } + :host([icon='repo-create']):before { + content: '\\ea60'; + } + :host([icon='lightbulb']):before { + content: '\\ea61'; + } + :host([icon='light-bulb']):before { + content: '\\ea61'; + } + :host([icon='repo']):before { + content: '\\ea62'; + } + :host([icon='repo-delete']):before { + content: '\\ea62'; + } + :host([icon='gist-fork']):before { + content: '\\ea63'; + } + :host([icon='repo-forked']):before { + content: '\\ea63'; + } + :host([icon='git-pull-request']):before { + content: '\\ea64'; + } + :host([icon='git-pull-request-abandoned']):before { + content: '\\ea64'; + } + :host([icon='record-keys']):before { + content: '\\ea65'; + } + :host([icon='keyboard']):before { + content: '\\ea65'; + } + :host([icon='tag']):before { + content: '\\ea66'; + } + :host([icon='tag-add']):before { + content: '\\ea66'; + } + :host([icon='tag-remove']):before { + content: '\\ea66'; + } + :host([icon='person']):before { + content: '\\ea67'; + } + :host([icon='person-follow']):before { + content: '\\ea67'; + } + :host([icon='person-outline']):before { + content: '\\ea67'; + } + :host([icon='person-filled']):before { + content: '\\ea67'; + } + :host([icon='git-branch']):before { + content: '\\ea68'; + } + :host([icon='git-branch-create']):before { + content: '\\ea68'; + } + :host([icon='git-branch-delete']):before { + content: '\\ea68'; + } + :host([icon='source-control']):before { + content: '\\ea68'; + } + :host([icon='mirror']):before { + content: '\\ea69'; + } + :host([icon='mirror-public']):before { + content: '\\ea69'; + } + :host([icon='star']):before { + content: '\\ea6a'; + } + :host([icon='star-add']):before { + content: '\\ea6a'; + } + :host([icon='star-delete']):before { + content: '\\ea6a'; + } + :host([icon='star-empty']):before { + content: '\\ea6a'; + } + :host([icon='comment']):before { + content: '\\ea6b'; + } + :host([icon='comment-add']):before { + content: '\\ea6b'; + } + :host([icon='alert']):before { + content: '\\ea6c'; + } + :host([icon='warning']):before { + content: '\\ea6c'; + } + :host([icon='search']):before { + content: '\\ea6d'; + } + :host([icon='search-save']):before { + content: '\\ea6d'; + } + :host([icon='log-out']):before { + content: '\\ea6e'; + } + :host([icon='sign-out']):before { + content: '\\ea6e'; + } + :host([icon='log-in']):before { + content: '\\ea6f'; + } + :host([icon='sign-in']):before { + content: '\\ea6f'; + } + :host([icon='eye']):before { + content: '\\ea70'; + } + :host([icon='eye-unwatch']):before { + content: '\\ea70'; + } + :host([icon='eye-watch']):before { + content: '\\ea70'; + } + :host([icon='circle-filled']):before { + content: '\\ea71'; + } + :host([icon='primitive-dot']):before { + content: '\\ea71'; + } + :host([icon='close-dirty']):before { + content: '\\ea71'; + } + :host([icon='debug-breakpoint']):before { + content: '\\ea71'; + } + :host([icon='debug-breakpoint-disabled']):before { + content: '\\ea71'; + } + :host([icon='debug-hint']):before { + content: '\\ea71'; + } + :host([icon='primitive-square']):before { + content: '\\ea72'; + } + :host([icon='edit']):before { + content: '\\ea73'; + } + :host([icon='pencil']):before { + content: '\\ea73'; + } + :host([icon='info']):before { + content: '\\ea74'; + } + :host([icon='issue-opened']):before { + content: '\\ea74'; + } + :host([icon='gist-private']):before { + content: '\\ea75'; + } + :host([icon='git-fork-private']):before { + content: '\\ea75'; + } + :host([icon='lock']):before { + content: '\\ea75'; + } + :host([icon='mirror-private']):before { + content: '\\ea75'; + } + :host([icon='close']):before { + content: '\\ea76'; + } + :host([icon='remove-close']):before { + content: '\\ea76'; + } + :host([icon='x']):before { + content: '\\ea76'; + } + :host([icon='repo-sync']):before { + content: '\\ea77'; + } + :host([icon='sync']):before { + content: '\\ea77'; + } + :host([icon='clone']):before { + content: '\\ea78'; + } + :host([icon='desktop-download']):before { + content: '\\ea78'; + } + :host([icon='beaker']):before { + content: '\\ea79'; + } + :host([icon='microscope']):before { + content: '\\ea79'; + } + :host([icon='vm']):before { + content: '\\ea7a'; + } + :host([icon='device-desktop']):before { + content: '\\ea7a'; + } + :host([icon='file']):before { + content: '\\ea7b'; + } + :host([icon='file-text']):before { + content: '\\ea7b'; + } + :host([icon='more']):before { + content: '\\ea7c'; + } + :host([icon='ellipsis']):before { + content: '\\ea7c'; + } + :host([icon='kebab-horizontal']):before { + content: '\\ea7c'; + } + :host([icon='mail-reply']):before { + content: '\\ea7d'; + } + :host([icon='reply']):before { + content: '\\ea7d'; + } + :host([icon='organization']):before { + content: '\\ea7e'; + } + :host([icon='organization-filled']):before { + content: '\\ea7e'; + } + :host([icon='organization-outline']):before { + content: '\\ea7e'; + } + :host([icon='new-file']):before { + content: '\\ea7f'; + } + :host([icon='file-add']):before { + content: '\\ea7f'; + } + :host([icon='new-folder']):before { + content: '\\ea80'; + } + :host([icon='file-directory-create']):before { + content: '\\ea80'; + } + :host([icon='trash']):before { + content: '\\ea81'; + } + :host([icon='trashcan']):before { + content: '\\ea81'; + } + :host([icon='history']):before { + content: '\\ea82'; + } + :host([icon='clock']):before { + content: '\\ea82'; + } + :host([icon='folder']):before { + content: '\\ea83'; + } + :host([icon='file-directory']):before { + content: '\\ea83'; + } + :host([icon='symbol-folder']):before { + content: '\\ea83'; + } + :host([icon='logo-github']):before { + content: '\\ea84'; + } + :host([icon='mark-github']):before { + content: '\\ea84'; + } + :host([icon='github']):before { + content: '\\ea84'; + } + :host([icon='terminal']):before { + content: '\\ea85'; + } + :host([icon='console']):before { + content: '\\ea85'; + } + :host([icon='repl']):before { + content: '\\ea85'; + } + :host([icon='zap']):before { + content: '\\ea86'; + } + :host([icon='symbol-event']):before { + content: '\\ea86'; + } + :host([icon='error']):before { + content: '\\ea87'; + } + :host([icon='stop']):before { + content: '\\ea87'; + } + :host([icon='variable']):before { + content: '\\ea88'; + } + :host([icon='symbol-variable']):before { + content: '\\ea88'; + } + :host([icon='array']):before { + content: '\\ea8a'; + } + :host([icon='symbol-array']):before { + content: '\\ea8a'; + } + :host([icon='symbol-module']):before { + content: '\\ea8b'; + } + :host([icon='symbol-package']):before { + content: '\\ea8b'; + } + :host([icon='symbol-namespace']):before { + content: '\\ea8b'; + } + :host([icon='symbol-object']):before { + content: '\\ea8b'; + } + :host([icon='symbol-method']):before { + content: '\\ea8c'; + } + :host([icon='symbol-function']):before { + content: '\\ea8c'; + } + :host([icon='symbol-constructor']):before { + content: '\\ea8c'; + } + :host([icon='symbol-boolean']):before { + content: '\\ea8f'; + } + :host([icon='symbol-null']):before { + content: '\\ea8f'; + } + :host([icon='symbol-numeric']):before { + content: '\\ea90'; + } + :host([icon='symbol-number']):before { + content: '\\ea90'; + } + :host([icon='symbol-structure']):before { + content: '\\ea91'; + } + :host([icon='symbol-struct']):before { + content: '\\ea91'; + } + :host([icon='symbol-parameter']):before { + content: '\\ea92'; + } + :host([icon='symbol-type-parameter']):before { + content: '\\ea92'; + } + :host([icon='symbol-key']):before { + content: '\\ea93'; + } + :host([icon='symbol-text']):before { + content: '\\ea93'; + } + :host([icon='symbol-reference']):before { + content: '\\ea94'; + } + :host([icon='go-to-file']):before { + content: '\\ea94'; + } + :host([icon='symbol-enum']):before { + content: '\\ea95'; + } + :host([icon='symbol-value']):before { + content: '\\ea95'; + } + :host([icon='symbol-ruler']):before { + content: '\\ea96'; + } + :host([icon='symbol-unit']):before { + content: '\\ea96'; + } + :host([icon='activate-breakpoints']):before { + content: '\\ea97'; + } + :host([icon='archive']):before { + content: '\\ea98'; + } + :host([icon='arrow-both']):before { + content: '\\ea99'; + } + :host([icon='arrow-down']):before { + content: '\\ea9a'; + } + :host([icon='arrow-left']):before { + content: '\\ea9b'; + } + :host([icon='arrow-right']):before { + content: '\\ea9c'; + } + :host([icon='arrow-small-down']):before { + content: '\\ea9d'; + } + :host([icon='arrow-small-left']):before { + content: '\\ea9e'; + } + :host([icon='arrow-small-right']):before { + content: '\\ea9f'; + } + :host([icon='arrow-small-up']):before { + content: '\\eaa0'; + } + :host([icon='arrow-up']):before { + content: '\\eaa1'; + } + :host([icon='bell']):before { + content: '\\eaa2'; + } + :host([icon='bold']):before { + content: '\\eaa3'; + } + :host([icon='book']):before { + content: '\\eaa4'; + } + :host([icon='bookmark']):before { + content: '\\eaa5'; + } + :host([icon='debug-breakpoint-conditional-unverified']):before { + content: '\\eaa6'; + } + :host([icon='debug-breakpoint-conditional']):before { + content: '\\eaa7'; + } + :host([icon='debug-breakpoint-conditional-disabled']):before { + content: '\\eaa7'; + } + :host([icon='debug-breakpoint-data-unverified']):before { + content: '\\eaa8'; + } + :host([icon='debug-breakpoint-data']):before { + content: '\\eaa9'; + } + :host([icon='debug-breakpoint-data-disabled']):before { + content: '\\eaa9'; + } + :host([icon='debug-breakpoint-log-unverified']):before { + content: '\\eaaa'; + } + :host([icon='debug-breakpoint-log']):before { + content: '\\eaab'; + } + :host([icon='debug-breakpoint-log-disabled']):before { + content: '\\eaab'; + } + :host([icon='briefcase']):before { + content: '\\eaac'; + } + :host([icon='broadcast']):before { + content: '\\eaad'; + } + :host([icon='browser']):before { + content: '\\eaae'; + } + :host([icon='bug']):before { + content: '\\eaaf'; + } + :host([icon='calendar']):before { + content: '\\eab0'; + } + :host([icon='case-sensitive']):before { + content: '\\eab1'; + } + :host([icon='check']):before { + content: '\\eab2'; + } + :host([icon='checklist']):before { + content: '\\eab3'; + } + :host([icon='chevron-down']):before { + content: '\\eab4'; + } + :host([icon='chevron-left']):before { + content: '\\eab5'; + } + :host([icon='chevron-right']):before { + content: '\\eab6'; + } + :host([icon='chevron-up']):before { + content: '\\eab7'; + } + :host([icon='chrome-close']):before { + content: '\\eab8'; + } + :host([icon='chrome-maximize']):before { + content: '\\eab9'; + } + :host([icon='chrome-minimize']):before { + content: '\\eaba'; + } + :host([icon='chrome-restore']):before { + content: '\\eabb'; + } + :host([icon='circle-outline']):before { + content: '\\eabc'; + } + :host([icon='debug-breakpoint-unverified']):before { + content: '\\eabc'; + } + :host([icon='circle-slash']):before { + content: '\\eabd'; + } + :host([icon='circuit-board']):before { + content: '\\eabe'; + } + :host([icon='clear-all']):before { + content: '\\eabf'; + } + :host([icon='clippy']):before { + content: '\\eac0'; + } + :host([icon='close-all']):before { + content: '\\eac1'; + } + :host([icon='cloud-download']):before { + content: '\\eac2'; + } + :host([icon='cloud-upload']):before { + content: '\\eac3'; + } + :host([icon='code']):before { + content: '\\eac4'; + } + :host([icon='collapse-all']):before { + content: '\\eac5'; + } + :host([icon='color-mode']):before { + content: '\\eac6'; + } + :host([icon='comment-discussion']):before { + content: '\\eac7'; + } + :host([icon='credit-card']):before { + content: '\\eac9'; + } + :host([icon='dash']):before { + content: '\\eacc'; + } + :host([icon='dashboard']):before { + content: '\\eacd'; + } + :host([icon='database']):before { + content: '\\eace'; + } + :host([icon='debug-continue']):before { + content: '\\eacf'; + } + :host([icon='debug-disconnect']):before { + content: '\\ead0'; + } + :host([icon='debug-pause']):before { + content: '\\ead1'; + } + :host([icon='debug-restart']):before { + content: '\\ead2'; + } + :host([icon='debug-start']):before { + content: '\\ead3'; + } + :host([icon='debug-step-into']):before { + content: '\\ead4'; + } + :host([icon='debug-step-out']):before { + content: '\\ead5'; + } + :host([icon='debug-step-over']):before { + content: '\\ead6'; + } + :host([icon='debug-stop']):before { + content: '\\ead7'; + } + :host([icon='debug']):before { + content: '\\ead8'; + } + :host([icon='device-camera-video']):before { + content: '\\ead9'; + } + :host([icon='device-camera']):before { + content: '\\eada'; + } + :host([icon='device-mobile']):before { + content: '\\eadb'; + } + :host([icon='diff-added']):before { + content: '\\eadc'; + } + :host([icon='diff-ignored']):before { + content: '\\eadd'; + } + :host([icon='diff-modified']):before { + content: '\\eade'; + } + :host([icon='diff-removed']):before { + content: '\\eadf'; + } + :host([icon='diff-renamed']):before { + content: '\\eae0'; + } + :host([icon='diff']):before { + content: '\\eae1'; + } + :host([icon='discard']):before { + content: '\\eae2'; + } + :host([icon='editor-layout']):before { + content: '\\eae3'; + } + :host([icon='empty-window']):before { + content: '\\eae4'; + } + :host([icon='exclude']):before { + content: '\\eae5'; + } + :host([icon='extensions']):before { + content: '\\eae6'; + } + :host([icon='eye-closed']):before { + content: '\\eae7'; + } + :host([icon='file-binary']):before { + content: '\\eae8'; + } + :host([icon='file-code']):before { + content: '\\eae9'; + } + :host([icon='file-media']):before { + content: '\\eaea'; + } + :host([icon='file-pdf']):before { + content: '\\eaeb'; + } + :host([icon='file-submodule']):before { + content: '\\eaec'; + } + :host([icon='file-symlink-directory']):before { + content: '\\eaed'; + } + :host([icon='file-symlink-file']):before { + content: '\\eaee'; + } + :host([icon='file-zip']):before { + content: '\\eaef'; + } + :host([icon='files']):before { + content: '\\eaf0'; + } + :host([icon='filter']):before { + content: '\\eaf1'; + } + :host([icon='flame']):before { + content: '\\eaf2'; + } + :host([icon='fold-down']):before { + content: '\\eaf3'; + } + :host([icon='fold-up']):before { + content: '\\eaf4'; + } + :host([icon='fold']):before { + content: '\\eaf5'; + } + :host([icon='folder-active']):before { + content: '\\eaf6'; + } + :host([icon='folder-opened']):before { + content: '\\eaf7'; + } + :host([icon='gear']):before { + content: '\\eaf8'; + } + :host([icon='gift']):before { + content: '\\eaf9'; + } + :host([icon='gist-secret']):before { + content: '\\eafa'; + } + :host([icon='gist']):before { + content: '\\eafb'; + } + :host([icon='git-commit']):before { + content: '\\eafc'; + } + :host([icon='git-compare']):before { + content: '\\eafd'; + } + :host([icon='compare-changes']):before { + content: '\\eafd'; + } + :host([icon='git-merge']):before { + content: '\\eafe'; + } + :host([icon='github-action']):before { + content: '\\eaff'; + } + :host([icon='github-alt']):before { + content: '\\eb00'; + } + :host([icon='globe']):before { + content: '\\eb01'; + } + :host([icon='grabber']):before { + content: '\\eb02'; + } + :host([icon='graph']):before { + content: '\\eb03'; + } + :host([icon='gripper']):before { + content: '\\eb04'; + } + :host([icon='heart']):before { + content: '\\eb05'; + } + :host([icon='home']):before { + content: '\\eb06'; + } + :host([icon='horizontal-rule']):before { + content: '\\eb07'; + } + :host([icon='hubot']):before { + content: '\\eb08'; + } + :host([icon='inbox']):before { + content: '\\eb09'; + } + :host([icon='issue-reopened']):before { + content: '\\eb0b'; + } + :host([icon='issues']):before { + content: '\\eb0c'; + } + :host([icon='italic']):before { + content: '\\eb0d'; + } + :host([icon='jersey']):before { + content: '\\eb0e'; + } + :host([icon='json']):before { + content: '\\eb0f'; + } + :host([icon='kebab-vertical']):before { + content: '\\eb10'; + } + :host([icon='key']):before { + content: '\\eb11'; + } + :host([icon='law']):before { + content: '\\eb12'; + } + :host([icon='lightbulb-autofix']):before { + content: '\\eb13'; + } + :host([icon='link-external']):before { + content: '\\eb14'; + } + :host([icon='link']):before { + content: '\\eb15'; + } + :host([icon='list-ordered']):before { + content: '\\eb16'; + } + :host([icon='list-unordered']):before { + content: '\\eb17'; + } + :host([icon='live-share']):before { + content: '\\eb18'; + } + :host([icon='loading']):before { + content: '\\eb19'; + } + :host([icon='location']):before { + content: '\\eb1a'; + } + :host([icon='mail-read']):before { + content: '\\eb1b'; + } + :host([icon='mail']):before { + content: '\\eb1c'; + } + :host([icon='markdown']):before { + content: '\\eb1d'; + } + :host([icon='megaphone']):before { + content: '\\eb1e'; + } + :host([icon='mention']):before { + content: '\\eb1f'; + } + :host([icon='milestone']):before { + content: '\\eb20'; + } + :host([icon='mortar-board']):before { + content: '\\eb21'; + } + :host([icon='move']):before { + content: '\\eb22'; + } + :host([icon='multiple-windows']):before { + content: '\\eb23'; + } + :host([icon='mute']):before { + content: '\\eb24'; + } + :host([icon='no-newline']):before { + content: '\\eb25'; + } + :host([icon='note']):before { + content: '\\eb26'; + } + :host([icon='octoface']):before { + content: '\\eb27'; + } + :host([icon='open-preview']):before { + content: '\\eb28'; + } + :host([icon='package']):before { + content: '\\eb29'; + } + :host([icon='paintcan']):before { + content: '\\eb2a'; + } + :host([icon='pin']):before { + content: '\\eb2b'; + } + :host([icon='play']):before { + content: '\\eb2c'; + } + :host([icon='run']):before { + content: '\\eb2c'; + } + :host([icon='plug']):before { + content: '\\eb2d'; + } + :host([icon='preserve-case']):before { + content: '\\eb2e'; + } + :host([icon='preview']):before { + content: '\\eb2f'; + } + :host([icon='project']):before { + content: '\\eb30'; + } + :host([icon='pulse']):before { + content: '\\eb31'; + } + :host([icon='question']):before { + content: '\\eb32'; + } + :host([icon='quote']):before { + content: '\\eb33'; + } + :host([icon='radio-tower']):before { + content: '\\eb34'; + } + :host([icon='reactions']):before { + content: '\\eb35'; + } + :host([icon='references']):before { + content: '\\eb36'; + } + :host([icon='refresh']):before { + content: '\\eb37'; + } + :host([icon='regex']):before { + content: '\\eb38'; + } + :host([icon='remote-explorer']):before { + content: '\\eb39'; + } + :host([icon='remote']):before { + content: '\\eb3a'; + } + :host([icon='remove']):before { + content: '\\eb3b'; + } + :host([icon='replace-all']):before { + content: '\\eb3c'; + } + :host([icon='replace']):before { + content: '\\eb3d'; + } + :host([icon='repo-clone']):before { + content: '\\eb3e'; + } + :host([icon='repo-force-push']):before { + content: '\\eb3f'; + } + :host([icon='repo-pull']):before { + content: '\\eb40'; + } + :host([icon='repo-push']):before { + content: '\\eb41'; + } + :host([icon='report']):before { + content: '\\eb42'; + } + :host([icon='request-changes']):before { + content: '\\eb43'; + } + :host([icon='rocket']):before { + content: '\\eb44'; + } + :host([icon='root-folder-opened']):before { + content: '\\eb45'; + } + :host([icon='root-folder']):before { + content: '\\eb46'; + } + :host([icon='rss']):before { + content: '\\eb47'; + } + :host([icon='ruby']):before { + content: '\\eb48'; + } + :host([icon='save-all']):before { + content: '\\eb49'; + } + :host([icon='save-as']):before { + content: '\\eb4a'; + } + :host([icon='save']):before { + content: '\\eb4b'; + } + :host([icon='screen-full']):before { + content: '\\eb4c'; + } + :host([icon='screen-normal']):before { + content: '\\eb4d'; + } + :host([icon='search-stop']):before { + content: '\\eb4e'; + } + :host([icon='server']):before { + content: '\\eb50'; + } + :host([icon='settings-gear']):before { + content: '\\eb51'; + } + :host([icon='settings']):before { + content: '\\eb52'; + } + :host([icon='shield']):before { + content: '\\eb53'; + } + :host([icon='smiley']):before { + content: '\\eb54'; + } + :host([icon='sort-precedence']):before { + content: '\\eb55'; + } + :host([icon='split-horizontal']):before { + content: '\\eb56'; + } + :host([icon='split-vertical']):before { + content: '\\eb57'; + } + :host([icon='squirrel']):before { + content: '\\eb58'; + } + :host([icon='star-full']):before { + content: '\\eb59'; + } + :host([icon='star-half']):before { + content: '\\eb5a'; + } + :host([icon='symbol-class']):before { + content: '\\eb5b'; + } + :host([icon='symbol-color']):before { + content: '\\eb5c'; + } + :host([icon='symbol-constant']):before { + content: '\\eb5d'; + } + :host([icon='symbol-enum-member']):before { + content: '\\eb5e'; + } + :host([icon='symbol-field']):before { + content: '\\eb5f'; + } + :host([icon='symbol-file']):before { + content: '\\eb60'; + } + :host([icon='symbol-interface']):before { + content: '\\eb61'; + } + :host([icon='symbol-keyword']):before { + content: '\\eb62'; + } + :host([icon='symbol-misc']):before { + content: '\\eb63'; + } + :host([icon='symbol-operator']):before { + content: '\\eb64'; + } + :host([icon='symbol-property']):before { + content: '\\eb65'; + } + :host([icon='wrench']):before { + content: '\\eb65'; + } + :host([icon='wrench-subaction']):before { + content: '\\eb65'; + } + :host([icon='symbol-snippet']):before { + content: '\\eb66'; + } + :host([icon='tasklist']):before { + content: '\\eb67'; + } + :host([icon='telescope']):before { + content: '\\eb68'; + } + :host([icon='text-size']):before { + content: '\\eb69'; + } + :host([icon='three-bars']):before { + content: '\\eb6a'; + } + :host([icon='thumbsdown']):before { + content: '\\eb6b'; + } + :host([icon='thumbsup']):before { + content: '\\eb6c'; + } + :host([icon='tools']):before { + content: '\\eb6d'; + } + :host([icon='triangle-down']):before { + content: '\\eb6e'; + } + :host([icon='triangle-left']):before { + content: '\\eb6f'; + } + :host([icon='triangle-right']):before { + content: '\\eb70'; + } + :host([icon='triangle-up']):before { + content: '\\eb71'; + } + :host([icon='twitter']):before { + content: '\\eb72'; + } + :host([icon='unfold']):before { + content: '\\eb73'; + } + :host([icon='unlock']):before { + content: '\\eb74'; + } + :host([icon='unmute']):before { + content: '\\eb75'; + } + :host([icon='unverified']):before { + content: '\\eb76'; + } + :host([icon='verified']):before { + content: '\\eb77'; + } + :host([icon='versions']):before { + content: '\\eb78'; + } + :host([icon='vm-active']):before { + content: '\\eb79'; + } + :host([icon='vm-outline']):before { + content: '\\eb7a'; + } + :host([icon='vm-running']):before { + content: '\\eb7b'; + } + :host([icon='watch']):before { + content: '\\eb7c'; + } + :host([icon='whitespace']):before { + content: '\\eb7d'; + } + :host([icon='whole-word']):before { + content: '\\eb7e'; + } + :host([icon='window']):before { + content: '\\eb7f'; + } + :host([icon='word-wrap']):before { + content: '\\eb80'; + } + :host([icon='zoom-in']):before { + content: '\\eb81'; + } + :host([icon='zoom-out']):before { + content: '\\eb82'; + } + :host([icon='list-filter']):before { + content: '\\eb83'; + } + :host([icon='list-flat']):before { + content: '\\eb84'; + } + :host([icon='list-selection']):before { + content: '\\eb85'; + } + :host([icon='selection']):before { + content: '\\eb85'; + } + :host([icon='list-tree']):before { + content: '\\eb86'; + } + :host([icon='debug-breakpoint-function-unverified']):before { + content: '\\eb87'; + } + :host([icon='debug-breakpoint-function']):before { + content: '\\eb88'; + } + :host([icon='debug-breakpoint-function-disabled']):before { + content: '\\eb88'; + } + :host([icon='debug-stackframe-active']):before { + content: '\\eb89'; + } + :host([icon='debug-stackframe-dot']):before { + content: '\\eb8a'; + } + :host([icon='debug-stackframe']):before { + content: '\\eb8b'; + } + :host([icon='debug-stackframe-focused']):before { + content: '\\eb8b'; + } + :host([icon='debug-breakpoint-unsupported']):before { + content: '\\eb8c'; + } + :host([icon='symbol-string']):before { + content: '\\eb8d'; + } + :host([icon='debug-reverse-continue']):before { + content: '\\eb8e'; + } + :host([icon='debug-step-back']):before { + content: '\\eb8f'; + } + :host([icon='debug-restart-frame']):before { + content: '\\eb90'; + } + :host([icon='debug-alt']):before { + content: '\\eb91'; + } + :host([icon='call-incoming']):before { + content: '\\eb92'; + } + :host([icon='call-outgoing']):before { + content: '\\eb93'; + } + :host([icon='menu']):before { + content: '\\eb94'; + } + :host([icon='expand-all']):before { + content: '\\eb95'; + } + :host([icon='feedback']):before { + content: '\\eb96'; + } + :host([icon='group-by-ref-type']):before { + content: '\\eb97'; + } + :host([icon='ungroup-by-ref-type']):before { + content: '\\eb98'; + } + :host([icon='account']):before { + content: '\\eb99'; + } + :host([icon='bell-dot']):before { + content: '\\eb9a'; + } + :host([icon='debug-console']):before { + content: '\\eb9b'; + } + :host([icon='library']):before { + content: '\\eb9c'; + } + :host([icon='output']):before { + content: '\\eb9d'; + } + :host([icon='run-all']):before { + content: '\\eb9e'; + } + :host([icon='sync-ignored']):before { + content: '\\eb9f'; + } + :host([icon='pinned']):before { + content: '\\eba0'; + } + :host([icon='github-inverted']):before { + content: '\\eba1'; + } + :host([icon='server-process']):before { + content: '\\eba2'; + } + :host([icon='server-environment']):before { + content: '\\eba3'; + } + :host([icon='pass']):before { + content: '\\eba4'; + } + :host([icon='issue-closed']):before { + content: '\\eba4'; + } + :host([icon='stop-circle']):before { + content: '\\eba5'; + } + :host([icon='play-circle']):before { + content: '\\eba6'; + } + :host([icon='record']):before { + content: '\\eba7'; + } + :host([icon='debug-alt-small']):before { + content: '\\eba8'; + } + :host([icon='vm-connect']):before { + content: '\\eba9'; + } + :host([icon='cloud']):before { + content: '\\ebaa'; + } + :host([icon='merge']):before { + content: '\\ebab'; + } + :host([icon='export']):before { + content: '\\ebac'; + } + :host([icon='graph-left']):before { + content: '\\ebad'; + } + :host([icon='magnet']):before { + content: '\\ebae'; + } + :host([icon='notebook']):before { + content: '\\ebaf'; + } + :host([icon='redo']):before { + content: '\\ebb0'; + } + :host([icon='check-all']):before { + content: '\\ebb1'; + } + :host([icon='pinned-dirty']):before { + content: '\\ebb2'; + } + :host([icon='pass-filled']):before { + content: '\\ebb3'; + } + :host([icon='circle-large-filled']):before { + content: '\\ebb4'; + } + :host([icon='circle-large-outline']):before { + content: '\\ebb5'; + } + :host([icon='combine']):before { + content: '\\ebb6'; + } + :host([icon='gather']):before { + content: '\\ebb6'; + } + :host([icon='table']):before { + content: '\\ebb7'; + } + :host([icon='variable-group']):before { + content: '\\ebb8'; + } + :host([icon='type-hierarchy']):before { + content: '\\ebb9'; + } + :host([icon='type-hierarchy-sub']):before { + content: '\\ebba'; + } + :host([icon='type-hierarchy-super']):before { + content: '\\ebbb'; + } + :host([icon='git-pull-request-create']):before { + content: '\\ebbc'; + } + :host([icon='run-above']):before { + content: '\\ebbd'; + } + :host([icon='run-below']):before { + content: '\\ebbe'; + } + :host([icon='notebook-template']):before { + content: '\\ebbf'; + } + :host([icon='debug-rerun']):before { + content: '\\ebc0'; + } + :host([icon='workspace-trusted']):before { + content: '\\ebc1'; + } + :host([icon='workspace-untrusted']):before { + content: '\\ebc2'; + } + :host([icon='workspace-unknown']):before { + content: '\\ebc3'; + } + :host([icon='terminal-cmd']):before { + content: '\\ebc4'; + } + :host([icon='terminal-debian']):before { + content: '\\ebc5'; + } + :host([icon='terminal-linux']):before { + content: '\\ebc6'; + } + :host([icon='terminal-powershell']):before { + content: '\\ebc7'; + } + :host([icon='terminal-tmux']):before { + content: '\\ebc8'; + } + :host([icon='terminal-ubuntu']):before { + content: '\\ebc9'; + } + :host([icon='terminal-bash']):before { + content: '\\ebca'; + } + :host([icon='arrow-swap']):before { + content: '\\ebcb'; + } + :host([icon='copy']):before { + content: '\\ebcc'; + } + :host([icon='person-add']):before { + content: '\\ebcd'; + } + :host([icon='filter-filled']):before { + content: '\\ebce'; + } + :host([icon='wand']):before { + content: '\\ebcf'; + } + :host([icon='debug-line-by-line']):before { + content: '\\ebd0'; + } + :host([icon='inspect']):before { + content: '\\ebd1'; + } + :host([icon='layers']):before { + content: '\\ebd2'; + } + :host([icon='layers-dot']):before { + content: '\\ebd3'; + } + :host([icon='layers-active']):before { + content: '\\ebd4'; + } + :host([icon='compass']):before { + content: '\\ebd5'; + } + :host([icon='compass-dot']):before { + content: '\\ebd6'; + } + :host([icon='compass-active']):before { + content: '\\ebd7'; + } + :host([icon='azure']):before { + content: '\\ebd8'; + } + :host([icon='issue-draft']):before { + content: '\\ebd9'; + } + :host([icon='git-pull-request-closed']):before { + content: '\\ebda'; + } + :host([icon='git-pull-request-draft']):before { + content: '\\ebdb'; + } + :host([icon='debug-all']):before { + content: '\\ebdc'; + } + :host([icon='debug-coverage']):before { + content: '\\ebdd'; + } + :host([icon='run-errors']):before { + content: '\\ebde'; + } + :host([icon='folder-library']):before { + content: '\\ebdf'; + } + :host([icon='debug-continue-small']):before { + content: '\\ebe0'; + } + :host([icon='beaker-stop']):before { + content: '\\ebe1'; + } + :host([icon='graph-line']):before { + content: '\\ebe2'; + } + :host([icon='graph-scatter']):before { + content: '\\ebe3'; + } + :host([icon='pie-chart']):before { + content: '\\ebe4'; + } + :host([icon='bracket']):before { + content: '\\eb0f'; + } + :host([icon='bracket-dot']):before { + content: '\\ebe5'; + } + :host([icon='bracket-error']):before { + content: '\\ebe6'; + } + :host([icon='lock-small']):before { + content: '\\ebe7'; + } + :host([icon='azure-devops']):before { + content: '\\ebe8'; + } + :host([icon='verified-filled']):before { + content: '\\ebe9'; + } + :host([icon='newline']):before { + content: '\\ebea'; + } + :host([icon='layout']):before { + content: '\\ebeb'; + } + :host([icon='layout-activitybar-left']):before { + content: '\\ebec'; + } + :host([icon='layout-activitybar-right']):before { + content: '\\ebed'; + } + :host([icon='layout-panel-left']):before { + content: '\\ebee'; + } + :host([icon='layout-panel-center']):before { + content: '\\ebef'; + } + :host([icon='layout-panel-justify']):before { + content: '\\ebf0'; + } + :host([icon='layout-panel-right']):before { + content: '\\ebf1'; + } + :host([icon='layout-panel']):before { + content: '\\ebf2'; + } + :host([icon='layout-sidebar-left']):before { + content: '\\ebf3'; + } + :host([icon='layout-sidebar-right']):before { + content: '\\ebf4'; + } + :host([icon='layout-statusbar']):before { + content: '\\ebf5'; + } + :host([icon='layout-menubar']):before { + content: '\\ebf6'; + } + :host([icon='layout-centered']):before { + content: '\\ebf7'; + } + :host([icon='target']):before { + content: '\\ebf8'; + } + `; + + @property() + icon = ''; + + override render() { + return html``; + } +} diff --git a/src/webviews/apps/shared/components/commit/commit-identity.ts b/src/webviews/apps/shared/components/commit/commit-identity.ts new file mode 100644 index 0000000..6622a44 --- /dev/null +++ b/src/webviews/apps/shared/components/commit/commit-identity.ts @@ -0,0 +1,67 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import '../formatted-date'; + +@customElement('commit-identity') +export class CommitIdentity extends LitElement { + static override styles = css` + :host { + display: grid; + gap: 0.25rem 0.5rem; + justify-content: start; + } + a { + color: var(--color-link-foreground); + text-decoration: none; + } + .avatar { + grid-column: 1; + grid-row: 1 / 3; + width: 36px; + } + .thumb { + width: 100%; + height: auto; + border-radius: 0.4rem; + } + .name { + grid-column: 2; + grid-row: 1; + font-size: 1.5rem; + } + .date { + grid-column: 2; + grid-row: 2; + font-size: 1.2rem; + } + `; + + @property() + name = ''; + + @property() + email = ''; + + @property() + date = ''; + + @property() + avatar = 'https://www.gravatar.com/avatar/?s=16&d=robohash'; + + @property({ type: Boolean, reflect: true }) + committer = false; + + override render() { + const largerUrl = this.avatar.replace('s=32', 's=64'); + return html` + ${this.name} + ${this.name} + ${this.committer === true ? 'committed' : 'authored'} + + `; + } +} diff --git a/src/webviews/apps/shared/components/commit/commit-stats.ts b/src/webviews/apps/shared/components/commit/commit-stats.ts new file mode 100644 index 0000000..7586e38 --- /dev/null +++ b/src/webviews/apps/shared/components/commit/commit-stats.ts @@ -0,0 +1,42 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import '../codicon'; + +@customElement('commit-stats') +export class CommitStats extends LitElement { + static override styles = css` + :host { + display: inline-flex; + flex-direction: row; + gap: 0.5rem; + } + + .added { + color: var(--vscode-gitDecoration-addedResourceForeground); + } + .modified { + color: var(--vscode-gitDecoration-modifiedResourceForeground); + } + .deleted { + color: var(--vscode-gitDecoration-deletedResourceForeground); + } + } + `; + + @property({ type: Number }) + added = 0; + + @property({ type: Number }) + modified = 0; + + @property({ type: Number }) + removed = 0; + + override render() { + return html` + ${this.added} + ${this.modified} + ${this.removed} + `; + } +} diff --git a/src/webviews/apps/shared/components/commit/file-change-item.ts b/src/webviews/apps/shared/components/commit/file-change-item.ts new file mode 100644 index 0000000..b3c625a --- /dev/null +++ b/src/webviews/apps/shared/components/commit/file-change-item.ts @@ -0,0 +1,233 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import '../codicon'; + +export interface FileChangeItemEventDetail { + path: string; + repoPath: string; +} + +// TODO: use the model version +const statusTextMap: Record = { + '.': 'Unchanged', + '!': 'Ignored', + '?': 'Untracked', + A: 'Added', + D: 'Deleted', + M: 'Modified', + R: 'Renamed', + C: 'Copied', + AA: 'Conflict', + AU: 'Conflict', + UA: 'Conflict', + DD: 'Conflict', + DU: 'Conflict', + UD: 'Conflict', + UU: 'Conflict', + T: 'Modified', + U: 'Updated but Unmerged', +}; + +// TODO: use the model version +const statusCodiconsMap: Record = { + '.': undefined, + '!': 'diff-ignored', + '?': 'diff-added', + A: 'diff-added', + D: 'diff-removed', + M: 'diff-modified', + R: 'diff-renamed', + C: 'diff-added', + AA: 'warning', + AU: 'warning', + UA: 'warning', + DD: 'warning', + DU: 'warning', + UD: 'warning', + UU: 'warning', + T: 'diff-modified', + U: 'diff-modified', +}; + +@customElement('file-change-item') +export class FileChangeItem extends LitElement { + static override styles = css` + :host { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + font-size: var(--vscode-font-size); + line-height: 2rem; + color: var(--vscode-foreground); + } + :host(:hover), + :host(:focus-within) { + background-color: var(--vscode-list-hoverBackground); + } + + :host(:focus-within) { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: -1px; + } + + * { + box-sizing: border-box; + } + + .change-list__link { + width: 100%; + color: inherit; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + text-decoration: none; + outline: none; + } + + .change-list__status { + margin-right: 0.33em; + } + + .change-list__status-icon { + width: 16px; + aspect-ratio: 1; + vertical-align: text-bottom; + } + + .change-list__path { + color: var(--color-background--lighten-50); + } + + .change-list__actions { + flex: none; + user-select: none; + display: flex; + align-items: center; + } + + :host(:not(:hover):not(:focus-within)) .change-list__actions { + display: none; + } + + .change-list__action { + display: inline-flex; + justify-content: center; + align-items: center; + width: 2rem; + height: 2rem; + border-radius: 0.25em; + color: inherit; + padding: 2px; + vertical-align: text-bottom; + text-decoration: none; + } + .change-list__action:hover { + background-color: var(--color-background--lighten-15); + } + `; + + @property() + status = ''; + + @property() + path = ''; + + @property({ attribute: 'repo-path' }) + repoPath = ''; + + @property() + icon = ''; + + private renderIcon() { + if (this.icon !== '') { + return html``; + } + + const statusIcon = (this.status !== '' && statusCodiconsMap[this.status]) ?? 'file'; + return html` `; + } + + override render() { + const statusName = this.status !== '' ? statusTextMap[this.status] : ''; + const pathIndex = this.path.lastIndexOf('/'); + const fileName = pathIndex > -1 ? this.path.substring(pathIndex + 1) : this.path; + const filePath = pathIndex > -1 ? this.path.substring(0, pathIndex) : ''; + + return html` + + ${this.renderIcon()}${fileName} + ${filePath} + + + `; + } + + private onOpenFile(e: Event) { + e.preventDefault(); + this.fireEvent('file-open'); + } + + private onOpenFileOnRemote(e: Event) { + e.preventDefault(); + this.fireEvent('file-open-on-remote'); + } + + private onCompareWorking(e: Event) { + e.preventDefault(); + this.fireEvent('file-compare-working'); + } + + private onComparePrevious(e: Event) { + e.preventDefault(); + this.fireEvent('file-compare-previous'); + } + + private onMoreActions(e: Event) { + e.preventDefault(); + this.fireEvent('file-more-actions'); + } + + private fireEvent(eventName: string) { + const event = new CustomEvent(eventName, { + detail: { + path: this.path, + repoPath: this.repoPath, + }, + bubbles: true, + composed: true, + }); + this.dispatchEvent(event); + } +} diff --git a/src/webviews/apps/shared/components/converters/date-converter.ts b/src/webviews/apps/shared/components/converters/date-converter.ts new file mode 100644 index 0000000..9895589 --- /dev/null +++ b/src/webviews/apps/shared/components/converters/date-converter.ts @@ -0,0 +1,12 @@ +import type { ComplexAttributeConverter } from 'lit'; + +export const dateConverter = (locale?: string): ComplexAttributeConverter => { + return { + toAttribute: (date: Date) => { + return date.toLocaleDateString(locale); + }, + fromAttribute: (value: string) => { + return new Date(value); + }, + }; +}; diff --git a/src/webviews/apps/shared/components/formatted-date.ts b/src/webviews/apps/shared/components/formatted-date.ts new file mode 100644 index 0000000..807877f --- /dev/null +++ b/src/webviews/apps/shared/components/formatted-date.ts @@ -0,0 +1,19 @@ +import { html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { formatDate, fromNow } from '../../../../system/date'; +import { dateConverter } from './converters/date-converter'; + +@customElement('formatted-date') +export class FormattedDate extends LitElement { + @property() + format = 'MMMM Do, YYYY h:mma'; + + @property({ converter: dateConverter(navigator.language), reflect: true }) + date = new Date(); + + override render() { + return html``; + } +} diff --git a/src/webviews/apps/shared/components/rich/issue-pull-request.ts b/src/webviews/apps/shared/components/rich/issue-pull-request.ts new file mode 100644 index 0000000..217387b --- /dev/null +++ b/src/webviews/apps/shared/components/rich/issue-pull-request.ts @@ -0,0 +1,77 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import '../formatted-date'; +import '../codicon'; + +@customElement('issue-pull-request') +export class IssuePullRequest extends LitElement { + static override styles = css` + :host { + display: grid; + gap: 0.25rem 0.5rem; + justify-content: start; + } + + a { + color: var(--color-link-foreground); + text-decoration: none; + } + + .icon { + grid-column: 1; + grid-row: 1 / 3; + color: var(--vscode-gitlens-mergedPullRequestIconColor); + width: 32px; + text-align: center; + } + + .title { + grid-column: 2; + grid-row: 1; + margin: 0; + font-size: 1.5rem; + } + + .date { + grid-column: 2; + grid-row: 2; + margin: 0; + font-size: 1.2rem; + } + `; + + @property() + url = ''; + + @property() + name = ''; + + @property() + date = ''; + + @property() + status = 'merged'; + + @property() + key = '#1999'; + + override render() { + const icon = + this.status.toLowerCase() === 'merged' + ? 'git-merge' + : this.status.toLowerCase() === 'closed' + ? 'pass' + : 'issues'; + + return html` + +

    + ${this.name} +

    +

    + ${this.key} ${this.status} + +

    + `; + } +} diff --git a/src/webviews/apps/shared/components/skeleton-loader.ts b/src/webviews/apps/shared/components/skeleton-loader.ts new file mode 100644 index 0000000..182d9e4 --- /dev/null +++ b/src/webviews/apps/shared/components/skeleton-loader.ts @@ -0,0 +1,57 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +// height: calc(1em * var(--skeleton-line-height, 1.2) * var(--skeleton-lines, 1)); +// background-color: var(--color-background--lighten-30); +@customElement('skeleton-loader') +export class SkeletonLoader extends LitElement { + static override styles = css` + :host { + --skeleton-line-height: 1.2; + --skeleton-lines: 1; + } + + .skeleton { + position: relative; + display: block; + overflow: hidden; + border-radius: 0.25em; + width: 100%; + height: calc(1em * var(--skeleton-line-height, 1.2) * var(--skeleton-lines, 1)); + background-color: var(--color-background--lighten-15); + } + + .skeleton::before { + content: ''; + position: absolute; + display: block; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-image: linear-gradient( + to right, + transparent 0%, + var(--color-background--lighten-15) 20%, + var(--color-background--lighten-30) 60%, + transparent 100% + ); + transform: translateX(-100%); + animation: skeleton-loader 2s ease-in-out infinite; + } + + @keyframes skeleton-loader { + 100% { + transform: translateX(100%); + } + } + `; + + @property({ type: Number }) + lines = 1; + + override render() { + const style = `--skeleton-lines: ${this.lines};`; + return html`
    `; + } +} diff --git a/src/webviews/apps/shared/components/webview-pane.ts b/src/webviews/apps/shared/components/webview-pane.ts new file mode 100644 index 0000000..e39f897 --- /dev/null +++ b/src/webviews/apps/shared/components/webview-pane.ts @@ -0,0 +1,97 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import './codicon'; + +@customElement('webview-pane') +export class WebviewPane extends LitElement { + static override styles = css` + :host { + display: flex; + flex-direction: column; + background-color: var(--color-view-background); + color: var(--color-view-foreground); + } + + * { + box-sizing: border-box; + } + + .header { + flex: none; + display: flex; + background-color: var(--color-view-background); + color: var(--color-view-header-foreground); + border-top: 1px solid var(--vscode-panel-border); + } + + .header:focus-within { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: -1px; + } + + .title { + appearance: none; + width: 100%; + padding: 0; + border: none; + text-align: left; + line-height: 2.2rem; + font-weight: bold; + background: transparent; + color: inherit; + cursor: pointer; + outline: none; + } + + .icon { + font-weight: normal; + margin: 0 0.2rem; + } + + .content { + overflow: auto; + /* scrollbar-gutter: stable; */ + box-shadow: #000000 0 0.6rem 0.6rem -0.6rem inset; + padding-top: 0.6rem; + } + + :host([collapsable]:not([expanded])) .content { + display: none; + } + `; + + @property({ type: Boolean, reflect: true }) + collapsable = false; + + @property({ type: Boolean, reflect: true }) + expanded = false; + + renderTitle() { + if (!this.collapsable) { + return html`
    ${this.title}
    `; + } + return html``; + } + + override render() { + return html` +
    ${this.renderTitle()}
    +
    + +
    + `; + } + + private toggleExpanded() { + this.expanded = !this.expanded; + } +} diff --git a/src/webviews/apps/shared/dom.ts b/src/webviews/apps/shared/dom.ts index 911cbef..4f7502a 100644 --- a/src/webviews/apps/shared/dom.ts +++ b/src/webviews/apps/shared/dom.ts @@ -29,10 +29,16 @@ export namespace DOM { listener: (e: DocumentEventMap[K], target: T) => void, options?: boolean | AddEventListenerOptions, ): Disposable; + export function on( + selector: string, + name: string, + listener: (e: CustomEvent, target: T) => void, + options?: boolean | AddEventListenerOptions, + ): Disposable; export function on( sourceOrSelector: string | Window | Document | Element, name: K, - listener: (e: (DocumentEventMap | WindowEventMap)[K], target: T) => void, + listener: (e: (DocumentEventMap | WindowEventMap)[K] | CustomEvent, target: T) => void, options?: boolean | AddEventListenerOptions, ): Disposable { let disposed = false; diff --git a/src/webviews/commitDetails/commitDetailsWebview.ts b/src/webviews/commitDetails/commitDetailsWebview.ts new file mode 100644 index 0000000..5616748 --- /dev/null +++ b/src/webviews/commitDetails/commitDetailsWebview.ts @@ -0,0 +1,198 @@ +import { ProgressLocation, window } from 'vscode'; +import { Commands } from '../../constants'; +import type { Container } from '../../container'; +import { GitCommit, GitRemote, IssueOrPullRequest } from '../../git/models'; +import { RichRemoteProvider } from '../../git/remotes/provider'; +import { debug } from '../../system/decorators/log'; +import { WebviewBase } from '../webviewBase'; +import type { CommitDetails, CommitSummary, ShowCommitDetailsPageCommandArgs, State } from './protocol'; + +export class CommitDetailsWebview extends WebviewBase { + private shaList: string[] = [ + '7224b547bbaa3a643e89ceb515dfb7cbad83aa26', + 'f55b2ad418a05a51c381c667e5e87d0435883cfc', + ]; + private selectedSha: string | undefined = 'f55b2ad418a05a51c381c667e5e87d0435883cfc'; + + constructor(container: Container) { + super( + container, + 'gitlens.commitDetails', + 'commitDetails.html', + 'images/gitlens-icon.png', + 'Commit Details', + Commands.ShowCommitDetailsPage, + ); + } + + private updateShaList(refs?: string[]) { + let refsList; + if (refs?.length && refs.length > 0) { + refsList = refs; + } else { + // TODO: replace with quick pick for a commit + refsList = ['7224b547bbaa3a643e89ceb515dfb7cbad83aa26', 'f55b2ad418a05a51c381c667e5e87d0435883cfc']; + } + + this.shaList = refsList; + + if (this.selectedSha && !this.shaList.includes(this.selectedSha)) { + // TODO: maybe make a quick pick for the list of commits? + this.selectedSha = this.shaList[0]; + } + } + + protected override onShowCommand(refs?: ShowCommitDetailsPageCommandArgs): void { + // TODO: get args from command + this.updateShaList(refs); + + super.onShowCommand(); + } + + private async getLinkedIssuesAndPullRequests( + message: string, + remote: GitRemote, + ): Promise { + try { + const issueSearch = await this.container.autolinks.getLinkedIssuesAndPullRequests(message, remote); + console.log('CommitDetailsWebview getLinkedIssuesAndPullRequests', issueSearch); + + if (issueSearch != null) { + const filteredIssues = Array.from(issueSearch.values()).filter( + value => value != null, + ) as IssueOrPullRequest[]; + return filteredIssues; + } + + return undefined; + } catch (e) { + console.error(e); + return undefined; + } + } + + private async getRichContent(selected: GitCommit): Promise> { + const pullRequest = selected != null ? await selected.getAssociatedPullRequest() : undefined; + console.log('CommitDetailsWebview pullRequest', pullRequest); + + const issues: Record[] = []; + let formattedMessage; + if (selected?.message !== undefined && typeof selected.message === 'string') { + const remote = await this.container.git.getBestRemoteWithRichProvider(selected.repoPath); + console.log('CommitDetailsWebview remote', remote); + + if (remote != null) { + formattedMessage = this.container.autolinks.linkify(selected.message, true, [remote]); + const issueSearch = await this.getLinkedIssuesAndPullRequests(selected.message, remote); + + console.log('CommitDetailsWebview issueSearch', issueSearch); + + if (issueSearch !== undefined) { + issues.push(...issueSearch); + } + } + } + + return { + formattedMessage: formattedMessage, + pullRequest: pullRequest, + issues: issues?.length ? issues : undefined, + }; + } + + @debug({ args: false }) + protected async getState(init = false): Promise { + const repo = this.container.git.openRepositories?.[0]; + + console.log('CommitDetailsWebview repo', repo); + if (repo === undefined) { + return { + commits: [], + }; + } + + const commitPromises = this.shaList.map(sha => repo.getCommit(sha)); + + const results = await Promise.all(commitPromises); + + console.log('CommitDetailsWebview results', results); + const commits = results.filter(commit => commit !== undefined) as GitCommit[]; + const selected = commits.find(commit => commit.sha === this.selectedSha); + console.log('CommitDetailsWebview selected', selected); + + // const pullRequest = selected != null ? await selected.getAssociatedPullRequest() : undefined; + // console.log('CommitDetailsWebview pullRequest', pullRequest); + + // const issues: Record[] = []; + // let formattedMessage; + // if (selected?.message !== undefined && typeof selected.message === 'string') { + // const remote = await this.container.git.getBestRemoteWithRichProvider(selected.repoPath); + // console.log('CommitDetailsWebview remote', remote); + + // if (remote != null) { + // formattedMessage = this.container.autolinks.linkify(selected.message, true, [remote]); + // const issueSearch = await this.getLinkedIssuesAndPullRequests(selected.message, remote); + + // console.log('CommitDetailsWebview issueSearch', issueSearch); + + // if (issueSearch !== undefined) { + // issues.push(...issueSearch); + // } + // } + // } + + const richContent = !init && selected != null ? await this.getRichContent(selected) : undefined; + + let formattedCommit; + if (selected !== undefined) { + formattedCommit = await getDetailsModel(selected, richContent?.formattedMessage); + } + + const commitChoices = await Promise.all(commits.map(async commit => summaryModel(commit))); + + return { + // TODO: keep state of the selected commit + commits: commitChoices, + selected: formattedCommit, + pullRequest: richContent?.pullRequest, + issues: richContent?.issues, + }; + } + + protected override async includeBootstrap() { + return window.withProgress({ location: ProgressLocation.Window, title: 'Loading webview...' }, () => + this.getState(true), + ); + } +} + +async function summaryModel(commit: GitCommit): Promise { + return { + sha: commit.sha, + shortSha: commit.shortSha, + summary: commit.summary, + message: commit.message, + author: commit.author, + avatar: (await commit.getAvatarUri())?.toString(true), + }; +} + +async function getDetailsModel(commit: GitCommit, formattedMessage?: string): Promise { + if (commit === undefined) { + return; + } + + const authorAvatar = await commit.author?.getAvatarUri(commit); + const committerAvatar = await commit.committer?.getAvatarUri(commit); + + return { + sha: commit.sha, + shortSha: commit.shortSha, + summary: commit.summary, + message: formattedMessage ?? commit.message, + author: { ...commit.author, avatar: authorAvatar?.toString(true) }, + committer: { ...commit.committer, avatar: committerAvatar?.toString(true) }, + files: commit.files?.map(({ repoPath, path, status }) => ({ repoPath: repoPath, path: path, status: status })), + stats: commit.stats, + }; +} diff --git a/src/webviews/commitDetails/commitDetailsWebviewView.ts b/src/webviews/commitDetails/commitDetailsWebviewView.ts new file mode 100644 index 0000000..88f0623 --- /dev/null +++ b/src/webviews/commitDetails/commitDetailsWebviewView.ts @@ -0,0 +1,347 @@ +import { Uri, window } from 'vscode'; +import type { + DiffWithPreviousCommandArgs, + DiffWithWorkingCommandArgs, + OpenFileOnRemoteCommandArgs, +} from '../../commands'; +import { Commands, CoreCommands } from '../../constants'; +import type { Container } from '../../container'; +import { GitUri } from '../../git/gitUri'; +import { GitCommit, GitFile, IssueOrPullRequest } from '../../git/models'; +import { executeCommand, executeCoreCommand } from '../../system/command'; +import { debug } from '../../system/decorators/log'; +import { IpcMessage, onIpc } from '../protocol'; +import { WebviewViewBase } from '../webviewViewBase'; +import { + CommitActionsCommandType, + CommitDetails, + CommitSummary, + FileComparePreviousCommandType, + FileCompareWorkingCommandType, + FileMoreActionsCommandType, + FileParams, + OpenFileCommandType, + OpenFileOnRemoteCommandType, + PickCommitCommandType, + RichCommitDetails, + RichContentNotificationType, + State, +} from './protocol'; + +export class CommitDetailsWebviewView extends WebviewViewBase { + private originalTitle?: string; + private commits?: GitCommit[]; + private selectedCommit?: GitCommit; + private loadedOnce = false; + + constructor(container: Container) { + super(container, 'gitlens.views.commitDetails', 'commitDetails.html', 'Commit Details'); + this.originalTitle = this.title; + } + + override async show(options?: { commit?: GitCommit; preserveFocus?: boolean | undefined }): Promise { + if (options?.commit != null) { + this.selectCommit(options.commit); + void this.refresh(); + } + + return super.show(options != null ? { preserveFocus: options.preserveFocus } : undefined); + } + + protected override onMessageReceived(e: IpcMessage) { + switch (e.method) { + case OpenFileOnRemoteCommandType.method: + onIpc(OpenFileOnRemoteCommandType, e, params => { + this.openFileOnRemote(params); + }); + break; + case OpenFileCommandType.method: + onIpc(OpenFileCommandType, e, params => { + this.openFile(params); + }); + break; + case FileCompareWorkingCommandType.method: + onIpc(FileCompareWorkingCommandType, e, params => { + this.openFileComparisonWithWorking(params); + }); + break; + case FileComparePreviousCommandType.method: + onIpc(FileComparePreviousCommandType, e, params => { + this.openFileComparisonWithPrevious(params); + }); + break; + case FileMoreActionsCommandType.method: + onIpc(FileMoreActionsCommandType, e, params => { + this.showFileActions(params); + }); + break; + case CommitActionsCommandType.method: + onIpc(CommitActionsCommandType, e, params => { + this.showCommitActions(); + }); + break; + case PickCommitCommandType.method: + onIpc(PickCommitCommandType, e, params => { + this.showCommitSearch(); + }); + break; + } + } + + private getFileFromParams(params: FileParams): GitFile | undefined { + return this.selectedCommit?.files?.find(file => file.path === params.path && file.repoPath === params.repoPath); + } + + private showCommitSearch() { + void executeCommand(Commands.SearchCommits, { + showResultsInDetails: true, + }); + } + + private showCommitActions() { + if (this.selectedCommit === undefined) { + return; + } + + void executeCommand(Commands.ShowQuickCommit, { + commit: this.selectedCommit, + }); + } + + private showFileActions(params: FileParams) { + const file = this.getFileFromParams(params); + if (this.selectedCommit === undefined || file === undefined) { + return; + } + + const uri = GitUri.fromFile(file, this.selectedCommit.repoPath, this.selectedCommit.sha); + void executeCommand(Commands.ShowQuickCommitFile, uri, { + sha: this.selectedCommit.sha, + }); + } + + private openFileComparisonWithWorking(params: FileParams) { + const file = this.getFileFromParams(params); + if (this.selectedCommit === undefined || file === undefined) { + return; + } + + const uri = GitUri.fromFile(file, this.selectedCommit.repoPath, this.selectedCommit.sha); + void executeCommand<[Uri, DiffWithWorkingCommandArgs]>(Commands.DiffWithWorking, uri, { + showOptions: { + preserveFocus: true, + preview: true, + }, + }); + } + + private openFileComparisonWithPrevious(params: FileParams) { + const file = this.getFileFromParams(params); + if (this.selectedCommit === undefined || file === undefined) { + return; + } + + const uri = GitUri.fromFile(file, this.selectedCommit.repoPath, this.selectedCommit.sha); + const line = this.selectedCommit.lines.length ? this.selectedCommit.lines[0].line - 1 : 0; + void executeCommand<[Uri, DiffWithPreviousCommandArgs]>(Commands.DiffWithPrevious, uri, { + commit: this.selectedCommit, + line: line, + showOptions: { + preserveFocus: true, + preview: true, + }, + }); + } + + private openFile(params: FileParams) { + const file = this.getFileFromParams(params); + if (this.selectedCommit === undefined || file === undefined) { + return; + } + + const uri = GitUri.fromFile(file, this.selectedCommit.repoPath, this.selectedCommit.sha); + void executeCoreCommand(CoreCommands.Open, uri, { background: false, preview: false }); + } + + private openFileOnRemote(params: FileParams) { + const file = this.getFileFromParams(params); + if (this.selectedCommit === undefined || file === undefined) { + return; + } + + const uri = GitUri.fromFile(file, this.selectedCommit.repoPath, this.selectedCommit.sha); + + void executeCommand<[Uri, OpenFileOnRemoteCommandArgs]>(Commands.OpenFileOnRemote, uri, { + sha: this.selectedCommit?.sha, + }); + } + + private copyRemoteFileUrl() {} + + private async getRichContent(selected: GitCommit): Promise { + const pullRequest = selected != null ? await selected.getAssociatedPullRequest() : undefined; + console.log('CommitDetailsWebview pullRequest', pullRequest); + + const issues: Record[] = []; + let formattedMessage; + if (selected?.message !== undefined && typeof selected.message === 'string') { + const remote = await this.container.git.getBestRemoteWithRichProvider(selected.repoPath); + console.log('CommitDetailsWebview remote', remote); + + if (remote != null) { + const issueSearch = await this.container.autolinks.getLinkedIssuesAndPullRequests( + selected.message, + remote, + ); + // TODO: add HTML formatting option to linkify + // formattedMessage = this.container.autolinks.linkify( + // escapeMarkdown(selected.message, { quoted: true }), + // true, + // [remote], + // // issueSearch, + // ); + formattedMessage = this.container.autolinks.linkify( + encodeMarkup(selected.message), + true, + [remote], + // issueSearch, + ); + + let filteredIssues; + if (issueSearch != null) { + if (pullRequest !== undefined) { + issueSearch.delete(pullRequest.id); + } + + filteredIssues = Array.from(issueSearch.values()).filter( + value => value != null, + ) as IssueOrPullRequest[]; + } + + console.log('CommitDetailsWebview filteredIssues', filteredIssues); + + if (filteredIssues !== undefined) { + issues.push(...filteredIssues); + } + } + } + + return { + formattedMessage: formattedMessage, + pullRequest: pullRequest, + issues: issues?.length ? issues : undefined, + }; + } + + private selectCommit(commit: GitCommit) { + this.commits = [commit]; + this.selectedCommit = commit; + this.title = `${this.originalTitle}${ + this.selectedCommit !== undefined ? `: ${this.selectedCommit.shortSha}` : '' + }`; + } + + @debug({ args: false }) + protected async getState(includeRichContent = true): Promise { + if (this.commits === undefined) { + return; + } + console.log('CommitDetailsWebview selected', this.selectedCommit); + + let richContent; + let formattedCommit; + if (this.selectedCommit !== undefined) { + if (includeRichContent) { + richContent = await this.getRichContent(this.selectedCommit); + } + formattedCommit = await this.getDetailsModel(this.selectedCommit, richContent?.formattedMessage); + } + + const commitChoices = await Promise.all(this.commits.map(async commit => summaryModel(commit))); + + return { + includeRichContent: includeRichContent, + commits: commitChoices, + selected: formattedCommit, + pullRequest: richContent?.pullRequest, + issues: richContent?.issues, + }; + } + + protected override async includeBootstrap() { + return window.withProgress({ location: { viewId: this.id } }, async () => { + const state = await this.getState(this.loadedOnce); + if (state === undefined) { + return {}; + } + + if (this.loadedOnce === false) { + void this.updateRichContent(); + this.loadedOnce = true; + } + + return state; + }); + } + + private async updateRichContent() { + if (this.selectedCommit === undefined) { + return; + } + + const richContent = await this.getRichContent(this.selectedCommit); + if (richContent != null) { + void this.notify(RichContentNotificationType, richContent); + } + } + + private async getDetailsModel(commit: GitCommit, formattedMessage?: string): Promise { + if (commit === undefined) { + return; + } + + const authorAvatar = await commit.author?.getAvatarUri(commit); + // const committerAvatar = await commit.committer?.getAvatarUri(commit); + + return { + sha: commit.sha, + shortSha: commit.shortSha, + summary: commit.summary, + message: formattedMessage ?? encodeMarkup(commit.message ?? ''), + author: { ...commit.author, avatar: authorAvatar?.toString(true) }, + // committer: { ...commit.committer, avatar: committerAvatar?.toString(true) }, + files: commit.files?.map(({ repoPath, path, status }) => { + const icon = GitFile.getStatusIcon(status); + return { + repoPath: repoPath, + path: path, + status: status, + icon: { + dark: this._view!.webview.asWebviewUri( + Uri.joinPath(this.container.context.extensionUri, 'images', 'dark', icon), + ).toString(), + light: this._view!.webview.asWebviewUri( + Uri.joinPath(this.container.context.extensionUri, 'images', 'light', icon), + ).toString(), + }, + }; + }), + stats: commit.stats, + }; + } +} + +async function summaryModel(commit: GitCommit): Promise { + return { + sha: commit.sha, + shortSha: commit.shortSha, + summary: commit.summary, + message: commit.message, + author: commit.author, + avatar: (await commit.getAvatarUri())?.toString(true), + }; +} + +function encodeMarkup(text: string): string { + return text.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} diff --git a/src/webviews/commitDetails/protocol.ts b/src/webviews/commitDetails/protocol.ts new file mode 100644 index 0000000..77a95cb --- /dev/null +++ b/src/webviews/commitDetails/protocol.ts @@ -0,0 +1,49 @@ +import { IpcCommandType, IpcNotificationType } from '../protocol'; + +export type CommitSummary = { + sha: string; + summary: string; + message?: string; + author: Record; + shortSha: string; + avatar?: string; +}; + +export type CommitDetails = { + committer?: Record; + files?: Record[]; + stats?: Record; +} & CommitSummary; + +export type RichCommitDetails = { + formattedMessage?: string; + pullRequest?: Record; + issues?: Record[]; +}; + +export type State = { + commits?: CommitSummary[]; +} & Record; + +export type ShowCommitDetailsPageCommandArgs = string[]; + +// COMMANDS +export interface FileParams { + path: string; + repoPath: string; +} +export const OpenFileOnRemoteCommandType = new IpcCommandType('commit/file/openOnRemote'); +export const OpenFileCommandType = new IpcCommandType('commit/file/open'); +export const FileCompareWorkingCommandType = new IpcCommandType('commit/file/compareWorking'); +export const FileComparePreviousCommandType = new IpcCommandType('commit/file/comparePrevious'); +export const FileMoreActionsCommandType = new IpcCommandType('commit/file/moreActions'); +export const CommitActionsCommandType = new IpcCommandType('commit/moreActions'); +export const PickCommitCommandType = new IpcCommandType('commit/pickCommit'); + +// NOTIFICATIONS +export interface DidChangeParams { + state: State; +} +export const DidChangeNotificationType = new IpcNotificationType('commit/didChange'); + +export const RichContentNotificationType = new IpcNotificationType('commit/richContent'); diff --git a/src/webviews/webviewViewBase.ts b/src/webviews/webviewViewBase.ts index 0f191a1..6d836a5 100644 --- a/src/webviews/webviewViewBase.ts +++ b/src/webviews/webviewViewBase.ts @@ -42,7 +42,7 @@ export abstract class WebviewViewBase implements WebviewViewProvider, Dis protected readonly disposables: Disposable[] = []; protected isReady: boolean = false; private _disposableView: Disposable | undefined; - private _view: WebviewView | undefined; + protected _view: WebviewView | undefined; constructor( protected readonly container: Container, diff --git a/tsconfig.base.json b/tsconfig.base.json index 84f660b..483f1c1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -20,7 +20,7 @@ "sourceMap": true, "strict": true, "target": "es2020", - "useDefineForClassFields": true, + "useDefineForClassFields": false, "useUnknownInCatchVariables": false }, "include": ["src/**/*"] diff --git a/webpack.config.js b/webpack.config.js index add3d48..a34c992 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -266,6 +266,7 @@ function getWebviewsConfig(mode, env) { getHtmlPlugin('settings', false, mode, env), getHtmlPlugin('timeline', true, mode, env), getHtmlPlugin('welcome', false, mode, env), + getHtmlPlugin('commitDetails', false, mode, env), getCspHtmlPlugin(mode, env), new InlineChunkHtmlPlugin(HtmlPlugin, mode === 'production' ? ['\\.css$'] : []), new CopyPlugin({ @@ -309,6 +310,7 @@ function getWebviewsConfig(mode, env) { settings: './settings/settings.ts', timeline: './plus/timeline/timeline.ts', welcome: './welcome/welcome.ts', + commitDetails: './commitDetails/commitDetails.ts', }, mode: mode, target: 'web', @@ -441,7 +443,7 @@ function getCspHtmlPlugin(mode, env) { mode !== 'production' ? ['#{cspSource}', "'nonce-#{cspNonce}'", "'unsafe-eval'"] : ['#{cspSource}', "'nonce-#{cspNonce}'"], - 'style-src': ['#{cspSource}', "'nonce-#{cspNonce}'", "'unsafe-hashes'"], + 'style-src': ['#{cspSource}', "'unsafe-hashes'", "'unsafe-inline'"], 'font-src': ['#{cspSource}'], }, { @@ -449,11 +451,11 @@ function getCspHtmlPlugin(mode, env) { hashingMethod: 'sha256', hashEnabled: { 'script-src': true, - 'style-src': true, + 'style-src': false, }, nonceEnabled: { 'script-src': true, - 'style-src': true, + 'style-src': false, }, }, ); diff --git a/yarn.lock b/yarn.lock index 3b6ad7f..9e11f50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -125,11 +125,21 @@ methods "^1.1.2" path-to-regexp "^6.1.0" -"@microsoft/fast-element@^1.10.4", "@microsoft/fast-element@^1.6.2", "@microsoft/fast-element@^1.9.0": +"@lit/reactive-element@^1.3.0": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.3.3.tgz#851de2bd28d6c3378a816a28d2505075931559c2" + integrity sha512-ukelZ49tzUqgOAEbVujl/U62JNK3wdn5kKtXVqrjKND4QvHACZOMOYaZI6/5Jd8vsg+Fq9HDwiib70FBLydOiQ== + +"@microsoft/fast-element@^1.10.4": version "1.10.4" resolved "https://registry.yarnpkg.com/@microsoft/fast-element/-/fast-element-1.10.4.tgz#c1929cdcf73d665f2278a7fd3a8857bcddd733d9" integrity sha512-SD0L3Kt++VSTqdkmGupB5tNaSLboEB7H/rh70a4eECpzCQewEzjd85jVNpgab1A8n5d3N9sPwZGIyfiUN6x4hg== +"@microsoft/fast-element@^1.6.2", "@microsoft/fast-element@^1.9.0": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@microsoft/fast-element/-/fast-element-1.10.3.tgz#0d513583207c2a33e6ec020c366d836c21423fca" + integrity sha512-ns/EEo5WSXNwRBe29O7sSA4SSqlapyHESXBT+JAcrR/3i0fLYQFMO/PdzfEMhsXmoUkZny6ewVbM4CttZa94Kg== + "@microsoft/fast-foundation@^2.38.0", "@microsoft/fast-foundation@^2.41.1": version "2.46.11" resolved "https://registry.yarnpkg.com/@microsoft/fast-foundation/-/fast-foundation-2.46.11.tgz#4761bb3b923875a50fd0ea01c45d8ce56f273477" @@ -398,6 +408,11 @@ resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.13.0.tgz#870223438f8f2cd81157b128a4c0261adbcaa946" integrity sha512-C3064MH72iEfeGCYEGCt7FCxXoAXaMPG0QPnstcxvPmbl54erpISu06d++FY37Smja64iWy5L8wOyHHBghWbJQ== +"@types/trusted-types@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" + integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== + "@types/uuid@8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" @@ -2378,6 +2393,15 @@ eslint-plugin-import@2.26.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" +eslint-plugin-lit@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-lit/-/eslint-plugin-lit-1.6.1.tgz#e1f51fe9e580d4095b58cc4bc4dc6b44409af6b0" + integrity sha512-BpPoWVhf8dQ/Sz5Pi9NlqbGoH5BcMcVyXhi2XTx2XGMAO9U2lS+GTSsqJjI5hL3OuxCicNiUEWXazAwi9cAGxQ== + dependencies: + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + requireindex "^1.2.0" + eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -3856,6 +3880,30 @@ listenercount@~1.0.1: resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== +lit-element@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.2.1.tgz#f917d22451b848768f84164d41eb5e18903986e3" + integrity sha512-2PxyE9Yq9Jyo/YBK2anycaHcqo93YvB5D+24JxloPVqryW/BOXekne+jGsm0Ke3E5E2v7CDgkmpEmCAzYfrHCQ== + dependencies: + "@lit/reactive-element" "^1.3.0" + lit-html "^2.2.0" + +lit-html@^2.2.0: + version "2.2.6" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.2.6.tgz#e70679605420a34c4f3cbd0c483b2fb1fff781df" + integrity sha512-xOKsPmq/RAKJ6dUeOxhmOYFjcjf0Q7aSdfBJgdJkOfCUnkmmJPxNrlZpRBeVe1Gg50oYWMlgm6ccAE/SpJgSdw== + dependencies: + "@types/trusted-types" "^2.0.2" + +lit@^2.2.7: + version "2.2.7" + resolved "https://registry.yarnpkg.com/lit/-/lit-2.2.7.tgz#a563e8851db1f131912f510129dcc9a42324e838" + integrity sha512-WXYujlKFwme5ZqXOZoWuRVZQAwy7scbcVT3wCbAOHefOxyscqjywWGlF2e6nnC9E64yP9l2ZQlN8wZcRlrjUMQ== + dependencies: + "@lit/reactive-element" "^1.3.0" + lit-element "^3.2.0" + lit-html "^2.2.0" + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -4584,6 +4632,13 @@ parse-semver@^1.1.1: dependencies: semver "^5.1.0" +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" @@ -4592,6 +4647,11 @@ parse5-htmlparser2-tree-adapter@^7.0.0: domhandler "^5.0.2" parse5 "^7.0.0" +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parse5@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.0.0.tgz#51f74a5257f5fcc536389e8c2d0b3802e1bfa91a" @@ -5062,6 +5122,11 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +requireindex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" + integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"