提交 d415a740 编写于 作者: P Peng Lyu

webview infra

上级 062084c0
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.title {
display: flex;
align-items: flex-start;
margin-top: 10px;
}
h3 {
margin: 0;
}
body hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #555;
margin: 0 !important;
padding: 0;
}
body .review-comment {
padding: 20px;
display: flex;
}
body .review-comment .avatar-container {
margin-top: 4px !important;
}
body img.avatar {
height: 28px;
width: 28px;
max-width: 28px;
max-height: 28px;
display: inline-block;
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 3px;
border-style: none;
margin-right: 5px;
}
body .review-comment .review-comment-contents {
margin-left: 20px;
}
body {
margin: auto;
width: 100%;
max-width: 1200px;
}
.prIcon {
display: flex;
border-radius: 10px;
margin-right: 5px;
margin-top: 18px;
}
.status {
display: inline-block;
height: 28px;
box-sizing: border-box;
line-height: 20px;
border-radius: 3px;
color: white;
padding: 4px 8px;
margin-right: 5px;
}
.details {
display: flex;
flex-direction: column;
}
\ No newline at end of file
此差异已折叠。
......@@ -3,16 +3,41 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare var acquireVsCodeApi: any;
const vscode = acquireVsCodeApi();
import { renderTimelineEvent, getStatus } from './pullRequestOverviewRenderer';
window.addEventListener('message', event => {
// declare var acquireVsCodeApi: any;
// const vscode = acquireVsCodeApi();
function handleMessage(event: any) {
const message = event.data; // The json data that the extension sent
switch (message.command) {
case 'refactor':
console.log('hahah');
case 'initialize':
document.getElementById('pullrequest')!.innerHTML = message.pullrequest.events.map(renderTimelineEvent).join('');
setTitleHTML(message.pullrequest);
break;
default:
break;
}
});
\ No newline at end of file
}
window.addEventListener('message', handleMessage);
function setTitleHTML(pr: any) {
document.getElementById('title')!.innerHTML = `
<div class="prIcon"><svg width="64" height="64" class="octicon octicon-git-compare" viewBox="0 0 14 16" version="1.1" aria-hidden="true"><path fill="#FFFFFF" fill-rule="evenodd" d="M5 12H4c-.27-.02-.48-.11-.69-.31-.21-.2-.3-.42-.31-.69V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V11c.03.78.34 1.47.94 2.06.6.59 1.28.91 2.06.94h1v2l3-3-3-3v2zM2 1.8c.66 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2C1.35 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2zm11 9.48V5c-.03-.78-.34-1.47-.94-2.06-.6-.59-1.28-.91-2.06-.94H9V0L6 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 12 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg></div>
<div class="details">
<div>
<h2>${pr.title} (<a href=${pr.html_url}>#${pr.number}</a>) </h2>
</div>
<div>
<div class="status">${getStatus(pr)}</div>
<img class="avatar" src="${pr.author.avatar_url}">
<strong class="author"><a href="${pr.author.html_url}">${pr.author.login}</a></strong>
</div>
<div class="comment-body">
${pr.body}
</div>
</div>
`;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export enum EventType {
Committed,
Mentioned,
Subscribed,
Commented,
Reviewed,
Other
}
export interface Author {
name: string;
email: string;
date: Date;
}
export interface Committer {
name: string;
email: string;
date: Date;
}
export interface Tree {
sha: string;
url: string;
}
export interface Parent {
sha: string;
url: string;
html_url: string;
}
export interface Verification {
verified: boolean;
reason: string;
signature?: any;
payload?: any;
}
export interface User {
login: string;
id: number;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface Html {
href: string;
}
export interface PullRequest {
href: string;
}
export interface Links {
html: Html;
pull_request: PullRequest;
}
export interface MentionEvent {
id: number;
url: string;
actor: User;
event: EventType;
commit_id: string;
commit_url: string;
created_at: Date;
}
export interface SubscribeEvent {
id: number;
url: string;
actor: User;
event: EventType;
commit_id: string;
commit_url: string;
created_at: Date;
}
export interface CommentEvent {
url: string;
html_url: string;
author: Author;
user: User;
created_at: Date;
updated_at: Date;
id: number;
event: EventType;
actor: User;
author_association: string;
body: string;
}
export interface ReviewEvent {
id: number;
user: User;
body: string;
commit_id: string;
submitted_at: Date;
state: string;
html_url: string;
pull_request_url: string;
author_association: string;
_links: Links;
event: EventType;
}
export interface CommitEvent {
sha: string;
url: string;
html_url: string;
author: Author;
committer: Committer;
tree: Tree;
message: string;
parents: Parent[];
verification: Verification;
event: EventType;
}
export type TimelineEvent = CommitEvent | ReviewEvent | SubscribeEvent | CommentEvent | MentionEvent;
export function renderComment(user: any, body: string): string {
return `<hr><div class="comment-container">
<div class="review-comment" tabindex="0" role="treeitem">
<div class="avatar-container">
<img class="avatar" src="${user.avatar_url}">
</div>
<div class="review-comment-contents">
<div>
<strong class="author"><a href="${user.html_url}">${user.login}</a></strong>
</div>
<div class="comment-body">
${body}
</div>
</div>
</div>
</div>`;
}
export function renderCommit(timelineEvent: CommitEvent): string {
return `<hr><div class="comment-container">
<div class="review-comment" tabindex="0" role="treeitem">
<div class="review-comment-contents">
<div>
<strong>${timelineEvent.author.name} commit: <a href="${timelineEvent.html_url}">${timelineEvent.message} (${timelineEvent.sha})</a></strong>
</div>
</div>
</div>
</div>`;
}
export function renderReview(timelineEvent: ReviewEvent): string {
return `<hr><div class="comment-container">
<div class="review-comment" tabindex="0" role="treeitem">
<div class="review-comment-contents">
<div>
<strong><a href="${timelineEvent.html_url}">${timelineEvent.user.login} left a review.</a></strong><span></span>
</div>
</div>
</div>
</div>`;
}
export function renderTimelineEvent(timelineEvent: TimelineEvent): string {
switch (timelineEvent.event) {
case EventType.Committed:
return renderCommit((<CommitEvent>timelineEvent));
case EventType.Commented:
return renderComment((<CommentEvent>timelineEvent).user, (<CommentEvent>timelineEvent).body);
case EventType.Reviewed:
return renderReview((<ReviewEvent>timelineEvent));
}
return '';
}
// export function getStatusBGCoor(pr: any) {
// if (pr.isMerged) {
// return '#6f42c1';
// } else if (pr.isOpen) {
// return '#2cbe4e';
// } else {
// return '#cb2431';
// }
// }
export function getStatus(pr: any) {
if (pr.isMerged) {
return 'Merged';
} else if (pr.isOpen) {
return 'Open';
} else {
return 'Closed';
}
}
\ No newline at end of file
......@@ -8,5 +8,8 @@
"strict": true,
"noImplicitAny": true,
"noUnusedLocals": true
}
},
"include": [
"./**/*"
]
}
\ No newline at end of file
......@@ -8,10 +8,7 @@ import * as vscode from 'vscode';
import { PullRequestModel } from './common/models/pullRequestModel';
import { FileChangeTreeItem } from './common/treeItems';
import { ReviewManager } from './review/reviewManager';
import { TimelineEvent, EventType, CommentEvent, ReviewEvent, CommitEvent } from './common/models/timelineEvent';
const MarkdownIt = require('markdown-it');
let panel: vscode.WebviewPanel;
import { PullRequestOverviewPanel } from './common/pullRequestOverview';
export function registerCommands(context: vscode.ExtensionContext) {
// initialize resources
......@@ -40,231 +37,6 @@ export function registerCommands(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('pr.openDescription', async (pr: PullRequestModel) => {
// Create and show a new webview
console.log(pr);
console.log(pr.prItem);
if (panel) {
panel.title = `Pull Request #${pr.prNumber}`;
} else {
panel = vscode.window.createWebviewPanel(
'pullRequestDescription', // Identifies the type of the webview. Used internally
`Pull Request #${pr.prNumber}`, // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in.
{} // Webview options. More on these later.
);
}
panel.webview.html = getHtmlForWebview(); //await getWebviewContent(pr);
PullRequestOverviewPanel.createOrShow(context.extensionPath, pr);
}));
let md = new MarkdownIt();
function getHtmlForWebview() {
const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'media', 'index.js'));
const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' });
const nonce = new Date().getTime() + '' + new Date().getMilliseconds();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cat Coding</title>
</head>
<body>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
async function getWebviewContent(pr: PullRequestModel) {
const timelineEvents = await pr.getTimelineEvents();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pull Request</title>
<style>
.title {
display: flex;
align-items: flex-start;
margin-top: 10px;
}
h3 {
margin: 0;
}
body hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #555;
margin: 0 !important;
padding: 0;
}
body .review-comment {
padding: 20px;
display: flex;
}
body .review-comment .avatar-container {
margin-top: 4px !important;
}
body img.avatar {
height: 28px;
width: 28px;
max-width: 28px;
max-height: 28px;
display: inline-block;
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 3px;
border-style: none;
margin-right: 5px;
}
body .review-comment .review-comment-contents {
margin-left: 20px;
}
body {
margin: auto;
width: 100%;
max-width: 1200px;
}
.prIcon {
display: flex;
border-radius: 10px;
margin-right: 5px;
margin-top: 18px;
}
.status {
display: inline-block;
height: 28px;
box-sizing: border-box;
line-height: 20px;
background-color: ${getStatusBGCoor(pr)};
border-radius: 3px;
color: white;
padding: 4px 8px;
margin-right: 5px;
}
.details {
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div class="title">
<div class="prIcon"><svg width="64" height="64" class="octicon octicon-git-compare" viewBox="0 0 14 16" version="1.1" aria-hidden="true"><path fill="#FFFFFF" fill-rule="evenodd" d="M5 12H4c-.27-.02-.48-.11-.69-.31-.21-.2-.3-.42-.31-.69V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V11c.03.78.34 1.47.94 2.06.6.59 1.28.91 2.06.94h1v2l3-3-3-3v2zM2 1.8c.66 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2C1.35 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2zm11 9.48V5c-.03-.78-.34-1.47-.94-2.06-.6-.59-1.28-.91-2.06-.94H9V0L6 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 12 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg></div>
<div class="details">
<div>
<h2>${pr.title} (<a href=${pr.html_url}>#${pr.prNumber}</a>) </h2>
</div>
<div>
<div class="status">${getStatus(pr)}</div>
<img class="avatar" src="${pr.prItem.user.avatar_url}">
<strong class="author"><a href="${pr.prItem.user.html_url}">${pr.prItem.user.login}</a></strong>
</div>
<div class="comment-body">
${md.render(pr.prItem.body)}
</div>
</div>
</div>
<div class="discussion">
${timelineEvents.map(renderTimelineEvent).join('')}
</div>
</body>
</html>`;
}
function getStatusBGCoor(pr: PullRequestModel) {
if (pr.isMerged) {
return '#6f42c1';
} else if (pr.isOpen) {
return '#2cbe4e';
} else {
return '#cb2431';
}
}
function getStatus(pr: PullRequestModel) {
if (pr.isMerged) {
return 'Merged';
} else if (pr.isOpen) {
return 'Open';
} else {
return 'Closed';
}
}
function renderComment(user: any, body: string): string {
return `<hr><div class="comment-container">
<div class="review-comment" tabindex="0" role="treeitem">
<div class="avatar-container">
<img class="avatar" src="${user.avatar_url}">
</div>
<div class="review-comment-contents">
<div>
<strong class="author"><a href="${user.html_url}">${user.login}</a></strong>
</div>
<div class="comment-body">
${md.render(body)}
</div>
</div>
</div>
</div>`;
}
function renderCommit(timelineEvent: CommitEvent): string {
return `<hr><div class="comment-container">
<div class="review-comment" tabindex="0" role="treeitem">
<div class="review-comment-contents">
<div>
<strong>${timelineEvent.author.name} commit: <a href="${timelineEvent.html_url}">${timelineEvent.message} (${timelineEvent.sha})</a></strong>
</div>
</div>
</div>
</div>`;
}
function renderReview(timelineEvent: ReviewEvent): string {
return `<hr><div class="comment-container">
<div class="review-comment" tabindex="0" role="treeitem">
<div class="review-comment-contents">
<div>
<strong><a href="${timelineEvent.html_url}">${timelineEvent.user.login} left a review.</a></strong><span></span>
</div>
</div>
</div>
</div>`;
}
function renderTimelineEvent(timelineEvent: TimelineEvent): string {
switch (timelineEvent.event) {
case EventType.Committed:
return renderCommit((<CommitEvent>timelineEvent));
case EventType.Commented:
return renderComment((<CommentEvent>timelineEvent).user, (<CommentEvent>timelineEvent).body);
case EventType.Reviewed:
return renderReview((<ReviewEvent>timelineEvent));
}
return '';
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as path from 'path';
import { PullRequestModel } from './models/pullRequestModel';
const MarkdownIt = require('markdown-it');
export class PullRequestOverviewPanel {
/**
* Track the currently panel. Only allow a single panel to exist at a time.
*/
public static currentPanel: PullRequestOverviewPanel | undefined;
private static readonly viewType = 'PullRequestOverview';
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string;
private _disposables: vscode.Disposable[] = [];
private _pullRequest: PullRequestModel;
private _md = MarkdownIt();
public static createOrShow(extensionPath: string, pullRequestModel: PullRequestModel) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it.
// Otherwise, create a new panel.
if (PullRequestOverviewPanel.currentPanel) {
PullRequestOverviewPanel.currentPanel._panel.reveal(column);
} else {
PullRequestOverviewPanel.currentPanel = new PullRequestOverviewPanel(extensionPath, column || vscode.ViewColumn.One);
}
PullRequestOverviewPanel.currentPanel.update(pullRequestModel);
PullRequestOverviewPanel.currentPanel._panel.webview.postMessage({
command: 'refactor'
});
}
private constructor(extensionPath: string, column: vscode.ViewColumn) {
this._extensionPath = extensionPath;
// Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(PullRequestOverviewPanel.viewType, 'Pull Request', column, {
// Enable javascript in the webview
enableScripts: true,
// And restric the webview to only loading content from our extension's `media` directory.
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, 'media'))
]
});
// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programatically
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// Handle messages from the webview
this._panel.webview.onDidReceiveMessage(message => {
console.log(message);
this._onDidReceiveMessage(message);
}, null, this._disposables);
}
public async update(pullRequestModel: PullRequestModel) {
this._pullRequest = pullRequestModel;
this._panel.webview.html = this.getHtmlForWebview();
const timelineEvents = await pullRequestModel.getTimelineEvents();
this._panel.webview.postMessage({
command: 'initialize',
pullrequest: {
number: pullRequestModel.prNumber,
title: pullRequestModel.title,
body: pullRequestModel.prItem.body,
author: pullRequestModel.author,
state: pullRequestModel.state,
events: timelineEvents
}
});
}
private _onDidReceiveMessage(message) {
switch (message.command) {
case 'alert':
vscode.window.showErrorMessage(message.text);
return;
}
}
public dispose() {
PullRequestOverviewPanel.currentPanel = undefined;
// Clean up our resources
this._panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private getHtmlForWebview() {
const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'media', 'index.js'));
const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' });
const stylePathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'media', 'index.css'));
const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' });
const baseStyles = `<link rel="stylesheet" type="text/css" href="${styleUri}">`;
const nonce = getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
${baseStyles}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cat Coding</title>
</head>
<body>
<script nonce="${nonce}" src="${scriptUri}"></script>
<div id="title" class="title"></div>
<div id="pullrequest" class="discussion"></div>
</body>
</html>`;
}
}
function getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
\ No newline at end of file
......@@ -11,6 +11,9 @@
"rootDir": "./src",
"jsx": "react"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
".vscode-test"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册