1. package.json
{ "name": "vscode-extension-for-worktile", "repository": "https://github.com/atinc/wt-rd-vscode-extension", "displayName": "Worktile", "description": "View and operate Worktile resources in Visual Studio Code.", "version": "0.1.2", "icon": "out/static/images/worktile_logo.jpg", "engines": { "vscode": "^1.43.0" }, "categories": [ "Other" ], // 能在商店看到有那些特性 以及一些快捷键 "activationEvents": [ "onCommand:worktile.me", "onCommand:worktile.search-work-item", "onCommand:worktile.open-work-item", "onCommand:worktile.open-work-item-tree", "onView:worktile-work-items" ], "main": "./out/extension.js", "contributes": { // 在vs里面任何一个行为都是一个事件 可以随意定义 前提: 1没有人知道谁会触发 2当触发了之后会做什么 也没有人知道,写完之后可以将事件暴露给用户 "commands": [ { "command": "worktile.search-work-item", // 搜索事件 "title": "Search Worktile Work Item" }, { "command": "worktile.open-work-item", "title": "Open Worktile Work Item" }, // 打开树事件 { "command": "worktile.open-work-item-tree", "title": "Open Worktile Work Item Tree" }, { "command": "worktile.me", "title": "Me", "icon": { "dark": "out/static/images/user_dark.svg", "light": "out/static/images/user_light.svg" } }, // 刷新所有树事件 { "command": "worktile.refresh-work-item-tree", "title": "Refresh Whole Tree", "icon": { "dark": "out/static/images/refresh_dark.svg", "light": "out/static/images/refresh_light.svg" } }, { "command": "worktile.refresh-work-item-tree-node", "title": "Refresh Node", "icon": { "dark": "out/static/images/refresh_dark.svg", "light": "out/static/images/refresh_light.svg" } } ], // 热键绑定 "keybindings": [ { "command": "worktile.search-work-item", "key": "ctrl+shift+w", "mac": "cmd+shift+w" } ], // 左侧的树 "viewsContainers": { "activitybar": [ { "id": "worktile-explorer", "title": "Worktile", "icon": "out/static/images/worktile_dark.png" } ] }, // 一块区域就叫一个view,例如左侧的树 目前只有一个,如果后面在增加在新增一个就好 "views": { "worktile-explorer": [ { "id": "worktile-work-items", "name": "Dashboard" } ] }, "menus": { // 左侧最上面的图标的配置 "view/title": [ { "command": "worktile.me", "when": "view == worktile-work-items", "group": "navigation" }, { "command": "worktile.refresh-work-item-tree", "when": "view == worktile-work-items", "group": "navigation" } ], // 树的图标 -- 可以自定义具体什么时候触发事件 事件要在上面先定义 "view/item/context": [ { "command": "worktile.refresh-work-item-tree-node", "when": "view == worktile-work-items && viewItem != do-not-show", "group": "inline" } ] } }, "scripts": { "build:sky": "cd sky && npm run build", "vscode:prepublish": "npm run compile", "sync-static": "cp -r ./src/static ./out/static", "compile": "rm -rf ./out && npm run build:sky && tsc -p ./ && npm run sync-static", "lint": "eslint src --ext ts", "watch": "tsc -watch -p ./", "pretest": "npm run compile && npm run lint", "test": "node ./out/test/runTest.js" }, "devDependencies": { "@types/glob": "^7.1.3", "@types/mocha": "^7.0.2", "@types/node": "^13.13.14", "@types/vscode": "^1.43.0", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", "eslint": "^6.8.0", "glob": "^7.1.6", "mocha": "^7.2.0", "typescript": "^3.9.6", "vscode-test": "^1.4.0" }, "dependencies": { "@types/lodash": "^4.14.157", "@types/request": "^2.48.5", "crypto": "^1.0.1", "lodash": "^4.17.19", "moment": "^2.27.0", "query-string": "^6.13.1", "reflect-metadata": "^0.1.13", "request": "^2.88.2" }, "publisher": "sunjingyun" }
2./src/views/serch-bar.ts
import * as vscode from "vscode"; import * as _ from "lodash"; import { DEFAULT_SEARCH_TEXT, DEFAULT_SEARCH_PLACEHOLDER, DEFAULT_SEARCH_TEXT_FORMAT, SEARCH_WORK_ITEM_CMD } from "../core/constant"; import { WorkItemWebview } from "./work-item-webview"; import { BaseView } from "./base"; import { injectable, inject } from "../modules/container"; @injectable() export class WorkItemSearchBar extends BaseView { @inject() private profileWebview!: WorkItemWebview; constructor(context: vscode.ExtensionContext) { super(context); context.subscriptions.push( // trigger 事实上会执行onHandler方法 vscode.commands.registerCommand(SEARCH_WORK_ITEM_CMD, () => this.trigger()) ); } protected async onHandler(identifier: string): Promise<void> { const result = await vscode.window.showInputBox({ value: DEFAULT_SEARCH_TEXT, // 显示的默认的文案 placeHolder: DEFAULT_SEARCH_PLACEHOLDER, validateInput: (input) => { // 正则是否能匹配上 if (input.length > 0 && input.match(DEFAULT_SEARCH_TEXT_FORMAT)) { return ""; } else { return "Bad identifier format"; } } }); if (result && result.length > 0) { await this.profileWebview.trigger(result); } } protected onError(error: Error): void { vscode.window.showErrorMessage('An exception occurred when opening the search box, please try again'); } }
3./src/views/tree-provider.ts
context.subscriptions.push( // 注册了一个事件 vscode.window.registerTreeDataProvider(WORK_ITEMS_VIEW, this) );
4./src/views/work-item-webview.ts
import * as vscode from "vscode"; import * as _ from "lodash"; import * as moment from "moment"; import { OPEN_WORK_ITEM_VIEW_TYPE, OPEN_WORK_ITEM_CMD, PRODUCT_NAME } from "../core/constant"; import { injectable } from "../modules/container"; import { WorkItemBaseView } from "./base.work-item"; @injectable() export class WorkItemWebview extends WorkItemBaseView { constructor(context: vscode.ExtensionContext) { super(context); context.subscriptions.push( vscode.commands.registerCommand(OPEN_WORK_ITEM_CMD, (identifier: string) => this.trigger(identifier)) ); } private async createWebview(): Promise<vscode.WebviewPanel> { return await vscode.window.createWebviewPanel( OPEN_WORK_ITEM_VIEW_TYPE, PRODUCT_NAME, vscode.ViewColumn.One, { retainContextWhenHidden: true, // 是否允许所有生命周期都存在 enableScripts: true, localResourceRoots: [this.getFileURI(this.context)] } ); } protected async onHandler(identifier: string): Promise<void> { if (_.isNil(identifier) || _.isEmpty(identifier)) { vscode.window.showInformationMessage('Please specify an valid identifier'); return; } const identity = await this.authService.getIdentity(); const workItem = await this.agileService.getWorkItemByIdentifier(identifier); if (_.isNil(workItem) || _.isNil(identity)) { vscode.window.showErrorMessage('The work item does not exist or you do not have permission to view the work item'); return; } const panel = await this.createWebview(); panel.title = `#${identifier} - ${PRODUCT_NAME}`; panel.iconPath = { dark: this.getFileURI(this.context, "images/worktile_dark.png"), light: this.getFileURI(this.context, "images/worktile_light.png") }; // 定义监听 可以收到用户的操作,可以发送给webview panel.webview.onDidReceiveMessage( async message => { switch (message.command) { case 'init': panel.webview.postMessage({ command: 'init', data: { workItem, identity } }); break; case 'setTitle': { const newTitle = message.text as string; const newWorkItem = await this.agileService.setWorkItemProperties(workItem.id, workItem.type, { title: newTitle }); if (newWorkItem && newWorkItem.title === newTitle) { vscode.window.showInformationMessage('标题已更新为:' + newTitle); } panel.webview.postMessage({ command: 'refresh', data: { workItem: newWorkItem } }); } break; case 'loadStates': { const stateId = message.text; // 调用api来请求数据 const states = await this.agileService.getNextStates(workItem.project.type, workItem.type, stateId); panel.webview.postMessage({ command: 'loadStates', data: { states: states } }); } break; } }, undefined, this.context.subscriptions ); panel.webview.html = this.getHTMLTemplate(this.context, panel, "output/index.html"); } protected onError(error: Error): void { vscode.window.showErrorMessage('An exception occurred when opening the work-item, please try again'); }; }
5. 如果在vscode里引入css、js一些静态资源的时候,不能使用绝对路径和相对路径,只能使用baseurl来设置
6. this._onDidChangeTreeData.fire 指手动触发刷新,会销毁旧的,在创建新的
vscode.commands.registerCommand(REFRESH_WHOLE_TREE_CMD, async (node: WorktileTreeNode) => this._onDidChangeTreeData.fire(node))