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))
