Compare commits

...

10 Commits

Author SHA1 Message Date
2ffd43908e # 0.12.2 2023-05-30 11:59:24 -07:00
5cb3b39764 # 0.12.1 2023-05-25 21:35:18 -06:00
c0930f4780 # 0.12.0
* First release with the following changes
2023-05-25 13:09:54 -06:00
Gordon
f85619450f Fix typo in docs 2021-06-08 20:44:02 +01:00
Gordon
5fbfb0f740 0.11.0 2021-06-08 18:05:30 +01:00
Gordon
abe249c20a Update docs 2021-06-08 15:23:59 +01:00
Gordon
c1f12c3ec0 Update changelog 2021-06-08 14:02:01 +01:00
Gordon
58cf1eb003 Filter by custom fields 2021-06-08 13:59:54 +01:00
Gordon
0a7918732e Update changelog 2021-06-08 01:53:09 +01:00
Gordon
026fbf95cd Custom field editing 2021-06-08 01:51:57 +01:00
34 changed files with 33173 additions and 696 deletions

28
.kanbn/board.css Normal file
View File

@ -0,0 +1,28 @@
.kanbn-column-wip .kanbn-column-task-list {
border-color: #6929c4;
}
.kanbn-column-next .kanbn-column-task-list {
border-color: #1192e8;
}
.kanbn-column-wait .kanbn-column-task-list {
border-color: #005d5d;
}
.kanbn-column-later .kanbn-column-task-list {
border-color: #9f1853;
}
.kanbn-column-maybe .kanbn-column-task-list {
border-color: #fa4d56;
}
.kanbn-column-friday .kanbn-column-task-list {
border-color: #570408;
}
.kanbn-column-done .kanbn-column-task-list {
border-color: #198038;
}
.kanbn-task-data-workload {
display: none;
}
.kanbn-task-data-relation {
display: block;
color: #198038;
}

30
.kanbn/index.md Normal file
View File

@ -0,0 +1,30 @@
---
startedColumns:
- 'In Progress'
completedColumns:
- Done
---
# VSCode-Kanbn
## Backlog
- [review-save-options](tasks/review-save-options.md)
- [can-this-run-in-the-browser](tasks/can-this-run-in-the-browser.md)
- [how-do-i-use-local-javascript-project-reference](tasks/how-do-i-use-local-javascript-project-reference.md)
## Todo
- [shrink-app-size](tasks/shrink-app-size.md)
## In Progress
## Done
- [add-link-to-raw-in-panel-view](tasks/add-link-to-raw-in-panel-view.md)
- [add-raw-button-in-create-new-task](tasks/add-raw-button-in-create-new-task.md)
- [make-tags-linkable](tasks/make-tags-linkable.md)
- [show-relations-on-the-item-card](tasks/show-relations-on-the-item-card.md)
- [understand-how-relations-work](tasks/understand-how-relations-work.md)
- [review-adding-more-on-item-card](tasks/review-adding-more-on-item-card.md)
- [remove-relations-in-form](tasks/remove-relations-in-form.md)

View File

@ -0,0 +1,14 @@
---
created: 2023-05-25T04:00:46.616Z
updated: 2023-05-25T04:50:52.235Z
assigned: ""
progress: 0
tags: []
started: 2023-05-25T04:41:03.902Z
completed: 2023-05-25T04:50:44.587Z
---
# Add link to raw in panel view
- Double click event
- Extra button

View File

@ -0,0 +1,11 @@
---
created: 2023-05-25T04:00:30.417Z
updated: 2023-05-25T04:50:46.136Z
assigned: ""
progress: 0
tags: []
started: 2023-05-22T00:00:00.000Z
completed: 2023-05-25T04:50:46.136Z
---
# Add raw button in Create new task

View File

@ -0,0 +1,9 @@
---
created: 2023-05-25T04:04:06.760Z
updated: 2023-05-25T04:04:06.753Z
assigned: ""
progress: 0
tags: []
---
# Can this run in the browser?

View File

@ -0,0 +1,19 @@
---
created: 2023-05-25T18:54:00.309Z
updated: 2023-05-25T18:55:32.671Z
assigned: ""
progress: 0
tags: []
started: 2023-05-25T18:55:32.671Z
---
# Fix relations bug
I started to look at th kanbn project all in javascript but I don't know how to reference it
## Comments
- date: 2023-05-25T18:54:44.808Z
```bash
node bin\kanbn add -n Mike
```

View File

@ -0,0 +1,9 @@
---
created: 2023-05-26T03:47:27.942Z
updated: 2023-05-26T03:47:27.933Z
assigned: ""
progress: 0
tags: []
---
# How do I use local javascript project reference

View File

@ -0,0 +1,9 @@
---
created: 2023-05-25T04:05:13.848Z
updated: 2023-05-25T04:05:33.759Z
assigned: ""
progress: 0
column: Backlog
---
# Make tags linkable

View File

@ -0,0 +1,18 @@
---
created: 2023-05-30T18:38:37.231Z
updated: 2023-05-30T18:54:59.604Z
assigned: ""
progress: 1
tags: []
started: 2023-05-28T00:00:00.000Z
completed: 2023-05-30T00:00:00.000Z
---
# Remove relations in form
## Comments
- date: 2023-05-30T18:42:55.772Z
Added count to button title
Used false
see false && values.relations.length > 0 && values.relations.map((relation, index) =>

View File

@ -0,0 +1,14 @@
---
created: 2023-05-25T04:01:15.185Z
updated: 2023-05-26T03:27:06.579Z
assigned: ""
progress: 0
tags:
- Maybe
completed: 2023-05-26T03:26:02.606Z
---
# Review adding more on item card
- Maybe not needed because that is what tags are meant for
- Buy maybe so because I don't like tags in the metadata

View File

@ -0,0 +1,9 @@
---
created: 2023-05-25T04:01:05.154Z
updated: 2023-05-25T04:01:05.146Z
assigned: ""
progress: 0
tags: []
---
# Review save options

View File

@ -0,0 +1,15 @@
---
created: 2023-05-25T04:59:37.306Z
updated: 2023-05-25T18:52:55.747Z
assigned: ""
progress: 0
tags: []
started: 2023-05-24T00:00:00.000Z
completed: 2023-05-25T18:52:55.747Z
---
# Show relations on the item card
## Sub-tasks
- [ ] make clickable

View File

@ -0,0 +1,9 @@
---
created: 2023-05-25T04:03:54.530Z
updated: 2023-05-25T18:55:36.452Z
assigned: ""
progress: 0
tags: []
---
# Shrink app size

View File

@ -0,0 +1,25 @@
---
created: 2023-05-25T18:54:00.309Z
updated: 2023-05-26T03:26:57.786Z
assigned: ""
progress: 0
tags: []
started: 2023-05-25T00:00:00.000Z
completed: 2023-05-26T03:25:48.979Z
---
# Understand how relations work
I started to look at th kanbn project all in javascript but I don't know how to reference it
## Relations
- [a b](b.md)
- [c d](d.md)
## Comments
- date: 2023-05-25T18:54:44.808Z
```bash
node bin\kanbn add -n Mike
```

View File

@ -8,5 +8,6 @@
},
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
"typescript.tsc.autoDetect": "off",
"todo-tree.tree.scanMode": "workspace only" // Turn off tsc task auto detection since we have the necessary task as npm scripts
"todo-tree.tree.scanMode": "workspace only",
"cSpell.enabled": false // Turn off tsc task auto detection since we have the necessary task as npm scripts
}

View File

@ -1,10 +1,35 @@
# 0.12.3
* See board ...
# 0.12.2
* See below
- [remove-relations-in-form](tasks/remove-relations-in-form.md)
# 0.12.1
* See below
- [understand-how-relations-work](tasks/understand-how-relations-work.md)
- [review-adding-more-on-item-card](tasks/review-adding-more-on-item-card.md)
# 0.12.0
* First release with the following changes
- [add-link-to-raw-in-panel-view](.kanbn/tasks/add-link-to-raw-in-panel-view.md)
- [add-raw-button-in-create-new-task](.kanbn/tasks/add-raw-button-in-create-new-task.md)
- [make-tags-linkable](.kanbn/tasks/make-tags-linkable.md)
- [show-relations-on-the-item-card](.kanbn/tasks/show-relations-on-the-item-card.md)
# 0.11.0
* Added KaTeX support to task markdown (description and comments) using `$$...$$` for blocks and `$...$` for inline
* Added syntax highlighting to code blocks in task markdown (description and comments)
* Added column sorting
* KaTeX support in task markdown (description and comments) using `$$...$$` for blocks and `$...$` for inline
* Syntax highlighting for code blocks in task markdown (description and comments)
* Columns can now be sorted, with the ability to optionally save sort settings per column
* Added relations and custom fields to task cards
* Added task card customisation using `board.css`
* Task cards can be customised using `board.css`
* Custom fields can be modified using task editor
* Board can be filtered by custom field values
# 0.10.0

View File

@ -1,7 +1,5 @@
# Kanbn Extension for Visual Studio Code
![Marketplace version](https://vsmarketplacebadge.apphb.com/version-short/gordonlarrigan.vscode-kanbn.svg?color=lightblue) ![Marketplace installs](https://vsmarketplacebadge.apphb.com/installs-short/gordonlarrigan.vscode-kanbn.svg?color=lightblue) ![Marketplace rating](https://vsmarketplacebadge.apphb.com/rating-short/gordonlarrigan.vscode-kanbn.svg?color=lightblue) [![MIT License](https://img.shields.io/github/license/basementuniverse/vscode-kanbn?color=orange)](https://opensource.org/licenses/MIT)
This extension adds a [Kanbn](https://www.npmjs.com/package/@basementuniverse/kanbn)-powered kanban board to Visual Studio Code.
![Kanbn](docs/preview.gif "Kanbn")
@ -32,20 +30,6 @@ Click on a task's title to open the task editor in a new tab. From here, you can
You can also modify the index or task files directly, or by using Kanbn CLI commands, and the Kanbn board should update automatically to reflect these changes.
## Filtering the Kanbn board
At the top-right of the Kanbn board there is a filter input. To filter visible tasks, enter a filter string and click the filter button (or press Enter).
### Filter string syntax
Text entered into the filter string input will be tested against each task's `id` and `name` fields. To filter on other fields, try the following:
- `overdue` will filter all tasks that have a due date in the past
- `description:search-string` will filter for tasks that contain `search-string` in their description or sub-tasks
- `assigned:search-string` will filter for tasks that contain `search-string` in their assigned user
- `tag:search-string` will filter for tasks that contain `search-string` in one of their tags
- `relation:search-string` will filter for tasks that contain `search-string` in one of their relations (either the relation type or related task id)
## Commands
The following commands are available:
@ -66,6 +50,40 @@ The following configuration settings are available:
- `kanbn.showSprintButton` when set to `true`, a 'Start sprint` button will will appear above the Kanbn board. This button will show the current sprint name if a sprint is currently active, and can be used to start a new sprint.
- `kanbn.showBurndownButton` when set to `true`, a 'Show burndown chart` button will appear above the Kanbn board.
## Filtering the Kanbn board
At the top-right of the Kanbn board there is a filter input. To filter visible tasks, enter a filter string and click the filter button (or press Enter).
### Filter string syntax
Text entered into the filter string input will be tested against each task's `id` and `name` fields. To filter on other fields, try the following:
- `overdue` will filter all tasks that have a due date in the past
- `description:search-string` will filter for tasks that contain `search-string` in their description or sub-tasks
- `assigned:search-string` will filter for tasks that contain `search-string` in their assigned user
- `tag:search-string` will filter for tasks that contain `search-string` in one of their tags
- `relation:search-string` will filter for tasks that contain `search-string` in one of their relations (either the relation type or related task id)
- `subtask:search-string` will filter for tasks that contain `search-string` in one of their sub-tasks
- `comment:search-string` will filter for tasks that contain `search-string` in one of their comments (either the comment author or text)
- `{custom field name}:search-string` will filter for tasks that have a custom field in their metadata that contains `search-string` in its value
- `{boolean custom field name}` will filter for tasks that have a boolean custom field in their metadata set to true
#### Examples
For these examples, assume we have a string custom field 'MyCustomField' and a boolean custom field 'MyCustomFlag' defined in the project options, i.e. `index.md` will contain:
```yaml
customFields:
- name: MyCustomField
type: string
- name: MyCustomFlag
type: boolean
```
(See https://github.com/basementuniverse/kanbn/blob/master/docs/index-structure.md for more information on custom fields)
- `assigned:testperson tag:large mycustomflag` will show tasks that are assigned to `testperson` and have a tag `Large` (search terms are case-insensitive) and have `MyCustomFlag` set to true
- `mycustomfield:test123 some title` will show tasks that have both `some` and `title` in their name/id and have a `MyCustomField` field that contains `test123`
## Styling the Kanbn board
This extension has been tested using various themes (light, dark and high-contrast), so it should always look somewhat presentable. However, if you'd like to set your own styles you can do so by creating a CSS file called `board.css` in the Kanbn directory. [Check here](docs/styles.md) for more information.

View File

@ -102,7 +102,7 @@ Here's an example of a task card style using some of the above features:
- `kanbn-task-tag`
- `kanbn-task-tag-{Tag name in param-case}`
- `kanbn-task-data-custom-field`
- `kanbn-task-data-{Custom field name in param-case}
- `kanbn-task-data-{Custom field name in param-case}`
- `kanbn-task-data-assigned`
- `kanbn-task-data-created`
- `kanbn-task-data-updated`
@ -129,6 +129,9 @@ Here's an example of a task card style using some of the above features:
- `kanbn-task-editor-field-label`
- `kanbn-task-editor-field-label-description`
- `kanbn-task-editor-field-input`
- `kanbn-task-editor-custom-field`
- `kanbn-task-editor-custom-field-{Custom field name in param-case}`
- `kanbn-task-editor-custom-checkbox`
- `kanbn-task-editor-id`
- `kanbn-task-editor-description-preview`
- `kanbn-task-editor-button-edit-description`
@ -145,6 +148,7 @@ Here's an example of a task card style using some of the above features:
- `kanbn-task-editor-button`
- `kanbn-task-editor-button-delete`
- `kanbn-task-editor-buttons`
- `kanbn-task-editor-main-buttons`
- `kanbn-task-editor-button-add`
- `kanbn-task-editor-button-edit`
- `kanbn-task-editor-field-relations`

View File

@ -1,5 +1,5 @@
import * as path from "path";
import * as vscode from "vscode";
import { join } from "path";
import { WebviewPanel, Disposable, window, ViewColumn, workspace, Uri, commands } from "vscode";
import getNonce from "./getNonce";
import KanbnTaskPanel from "./KanbnTaskPanel";
import KanbnBurndownPanel from "./KanbnBurndownPanel";
@ -24,12 +24,12 @@ export default class KanbnBoardPanel {
private static readonly viewType = "react";
private readonly _panel: vscode.WebviewPanel;
private readonly _panel: WebviewPanel;
private readonly _extensionPath: string;
private readonly _workspacePath: string;
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
private readonly _kanbnFolderName: string;
private _disposables: vscode.Disposable[] = [];
private _disposables: Disposable[] = [];
public static createOrShow(
extensionPath: string,
@ -37,7 +37,7 @@ export default class KanbnBoardPanel {
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
kanbnFolderName: string
) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it, otherwise create a new panel
if (KanbnBoardPanel.currentPanel) {
@ -46,7 +46,7 @@ export default class KanbnBoardPanel {
KanbnBoardPanel.currentPanel = new KanbnBoardPanel(
extensionPath,
workspacePath,
column || vscode.ViewColumn.One,
column || ViewColumn.One,
kanbn,
kanbnFolderName
);
@ -59,7 +59,7 @@ export default class KanbnBoardPanel {
try {
index = await KanbnBoardPanel.currentPanel._kanbn.getIndex();
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
let tasks: any[];
@ -68,7 +68,7 @@ export default class KanbnBoardPanel {
KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task)
);
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
KanbnBoardPanel.currentPanel._panel.webview.postMessage({
@ -81,8 +81,8 @@ export default class KanbnBoardPanel {
columnSorting: index.options.columnSorting ?? {},
customFields: index.options.customFields ?? [],
dateFormat: KanbnBoardPanel.currentPanel._kanbn.getDateFormat(index),
showBurndownButton: vscode.workspace.getConfiguration("kanbn").get("showBurndownButton"),
showSprintButton: vscode.workspace.getConfiguration("kanbn").get("showSprintButton"),
showBurndownButton: workspace.getConfiguration("kanbn").get("showBurndownButton"),
showSprintButton: workspace.getConfiguration("kanbn").get("showSprintButton"),
});
}
}
@ -90,7 +90,7 @@ export default class KanbnBoardPanel {
private constructor(
extensionPath: string,
workspacePath: string,
column: vscode.ViewColumn,
column: ViewColumn,
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
kanbnFolderName: string
) {
@ -100,7 +100,7 @@ export default class KanbnBoardPanel {
this._kanbnFolderName = kanbnFolderName;
// Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(KanbnBoardPanel.viewType, "Kanbn Board", column, {
this._panel = window.createWebviewPanel(KanbnBoardPanel.viewType, "Kanbn Board", column, {
// Enable javascript in the webview
enableScripts: true,
@ -109,14 +109,14 @@ export default class KanbnBoardPanel {
// Restrict the webview to only loading content from allowed paths
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, "build")),
vscode.Uri.file(path.join(this._workspacePath, this._kanbnFolderName)),
vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
Uri.file(join(this._extensionPath, "build")),
Uri.file(join(this._workspacePath, this._kanbnFolderName)),
Uri.file(join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
],
});
(this._panel as any).iconPath = {
light: vscode.Uri.file(path.join(this._extensionPath, "resources", "project_light.svg")),
dark: vscode.Uri.file(path.join(this._extensionPath, "resources", "project_dark.svg")),
light: Uri.file(join(this._extensionPath, "resources", "project_light.svg")),
dark: Uri.file(join(this._extensionPath, "resources", "project_dark.svg")),
};
// Set the webview's title to the kanbn project name
@ -138,7 +138,7 @@ export default class KanbnBoardPanel {
// Display error message
case "error":
vscode.window.showErrorMessage(message.text);
window.showErrorMessage(message.text);
return;
// Open a task in the editor
@ -153,12 +153,18 @@ export default class KanbnBoardPanel {
);
return;
// Go to raw task in the editor
case "kanbn.goToRaw":
let uri = Uri.file(join(this._workspacePath, ".kanbn", "tasks", message.taskId + ".md"));
await commands.executeCommand('vscode.open', uri);
return;
// Move a task
case "kanbn.move":
try {
await kanbn.moveTask(message.task, message.columnName, message.position);
} catch (e) {
vscode.window.showErrorMessage(e.message);
window.showErrorMessage(e.message);
}
return;
@ -185,7 +191,7 @@ export default class KanbnBoardPanel {
);
}
// Prompt for a task property to sort by
const sortBy: string = await vscode.window.showQuickPick(
const sortBy = await window.showQuickPick(
[
'None',
...Object.keys(sortByFields),
@ -204,7 +210,7 @@ export default class KanbnBoardPanel {
}
// Prompt for sort direction and save settings
const sortDirection = await vscode.window.showQuickPick(
const sortDirection = await window.showQuickPick(
[
'Ascending',
'Descending',
@ -215,7 +221,7 @@ export default class KanbnBoardPanel {
}
);
if (sortDirection !== undefined) {
const saveSort = await vscode.window.showQuickPick(
const saveSort = await window.showQuickPick(
[
"Yes",
"No",
@ -256,7 +262,7 @@ export default class KanbnBoardPanel {
// Start a new sprint
case "kanbn.sprint":
// Prompt for a sprint name
const newSprintName = await vscode.window.showInputBox({
const newSprintName = await window.showInputBox({
placeHolder: "The sprint name.",
});
@ -265,7 +271,7 @@ export default class KanbnBoardPanel {
try {
await kanbn.sprint(newSprintName, "", new Date());
} catch (e) {
vscode.window.showErrorMessage(e.message);
window.showErrorMessage(e.message);
}
}
KanbnBurndownPanel.update();
@ -291,20 +297,20 @@ export default class KanbnBoardPanel {
}
private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const manifest = require(join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest["main.js"];
const mainStyle = manifest["main.css"];
const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
const scriptUri = Uri.file(join(this._extensionPath, "build", mainScript)).with({
scheme: "vscode-resource",
});
const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
const styleUri = Uri.file(join(this._extensionPath, "build", mainStyle)).with({
scheme: "vscode-resource",
});
const customStyleUri = vscode.Uri.file(
path.join(this._workspacePath, this._kanbnFolderName, "board.css")
const customStyleUri = Uri.file(
join(this._workspacePath, this._kanbnFolderName, "board.css")
).with({ scheme: "vscode-resource" });
const codiconsUri = vscode.Uri.file(
path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
const codiconsUri = Uri.file(
join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
).with({ scheme: "vscode-resource" });
// Use a nonce to whitelist which scripts can be run
@ -321,7 +327,7 @@ export default class KanbnBoardPanel {
<link rel="stylesheet" type="text/css" href="${customStyleUri}">
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${vscode.Uri.file(path.join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
<base href="${Uri.file(join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,5 +1,5 @@
import * as path from "path";
import * as vscode from "vscode";
import { join } from "path";
import { WebviewPanel, Disposable, window, ViewColumn, Uri } from "vscode";
import getNonce from "./getNonce";
export default class KanbnBurndownPanel {
@ -7,7 +7,7 @@ export default class KanbnBurndownPanel {
private static readonly viewType = "react";
private readonly _panel: vscode.WebviewPanel;
private readonly _panel: WebviewPanel;
private readonly _extensionPath: string;
private readonly _workspacePath: string;
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
@ -16,7 +16,7 @@ export default class KanbnBurndownPanel {
private sprint: string = '';
private startDate: string = '';
private endDate: string = '';
private _disposables: vscode.Disposable[] = [];
private _disposables: Disposable[] = [];
public static async createOrShow(
extensionPath: string,
@ -24,7 +24,7 @@ export default class KanbnBurndownPanel {
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
kanbnFolderName: string
) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it, otherwise create a new panel
if (KanbnBurndownPanel.currentPanel) {
@ -33,7 +33,7 @@ export default class KanbnBurndownPanel {
KanbnBurndownPanel.currentPanel = new KanbnBurndownPanel(
extensionPath,
workspacePath,
column || vscode.ViewColumn.One,
column || ViewColumn.One,
kanbn,
kanbnFolderName
);
@ -46,7 +46,7 @@ export default class KanbnBurndownPanel {
try {
index = await KanbnBurndownPanel.currentPanel._kanbn.getIndex();
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
KanbnBurndownPanel.currentPanel._panel.webview.postMessage({
@ -78,7 +78,7 @@ export default class KanbnBurndownPanel {
private constructor(
extensionPath: string,
workspacePath: string,
column: vscode.ViewColumn,
column: ViewColumn,
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
kanbnFolderName: string
) {
@ -88,7 +88,7 @@ export default class KanbnBurndownPanel {
this._kanbnFolderName = kanbnFolderName;
// Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
this._panel = window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
// Enable javascript in the webview
enableScripts: true,
@ -97,14 +97,14 @@ export default class KanbnBurndownPanel {
// Restrict the webview to only loading content from allowed paths
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, "build")),
vscode.Uri.file(path.join(this._workspacePath, this._kanbnFolderName)),
vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
Uri.file(join(this._extensionPath, "build")),
Uri.file(join(this._workspacePath, this._kanbnFolderName)),
Uri.file(join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
],
});
(this._panel as any).iconPath = {
light: vscode.Uri.file(path.join(this._extensionPath, "resources", "burndown_light.svg")),
dark: vscode.Uri.file(path.join(this._extensionPath, "resources", "burndown_dark.svg")),
light: Uri.file(join(this._extensionPath, "resources", "burndown_light.svg")),
dark: Uri.file(join(this._extensionPath, "resources", "burndown_dark.svg")),
};
// Set the webview's title to the kanbn project name
@ -126,7 +126,7 @@ export default class KanbnBurndownPanel {
// Display error message
case "error":
vscode.window.showErrorMessage(message.text);
window.showErrorMessage(message.text);
return;
// Refresh the kanbn chart
@ -158,20 +158,20 @@ export default class KanbnBurndownPanel {
}
private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const manifest = require(join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest["main.js"];
const mainStyle = manifest["main.css"];
const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
const scriptUri = Uri.file(join(this._extensionPath, "build", mainScript)).with({
scheme: "vscode-resource",
});
const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
const styleUri = Uri.file(join(this._extensionPath, "build", mainStyle)).with({
scheme: "vscode-resource",
});
const customStyleUri = vscode.Uri.file(
path.join(this._workspacePath, this._kanbnFolderName, "board.css")
const customStyleUri = Uri.file(
join(this._workspacePath, this._kanbnFolderName, "board.css")
).with({ scheme: "vscode-resource" });
const codiconsUri = vscode.Uri.file(
path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
const codiconsUri = Uri.file(
join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
).with({ scheme: "vscode-resource" });
// Use a nonce to whitelist which scripts can be run
@ -188,7 +188,7 @@ export default class KanbnBurndownPanel {
<link rel="stylesheet" type="text/css" href="${customStyleUri}">
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${vscode.Uri.file(path.join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
<base href="${Uri.file(join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,15 +1,15 @@
import { status } from '@basementuniverse/kanbn/src/main';
import * as vscode from 'vscode';
import { StatusBarItem, ExtensionContext, window, StatusBarAlignment, workspace } from 'vscode';
export default class KanbnStatusBarItem {
private readonly _statusBarItem: vscode.StatusBarItem;
private readonly _statusBarItem: StatusBarItem;
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main');
constructor(
context: vscode.ExtensionContext,
context: ExtensionContext,
kanbn: typeof import('@basementuniverse/kanbn/src/main')
) {
this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
this._statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 0);
context.subscriptions.push(this._statusBarItem);
this._kanbn = kanbn;
}
@ -28,7 +28,7 @@ export default class KanbnStatusBarItem {
const text = [
`$(project) ${status.tasks}`
];
let tooltip = [];
let tooltip: any = [];
if (status.tasks > 0) {
tooltip = [
`${status.tasks} task${status.tasks === 1 ? '' : 's'}`
@ -52,7 +52,7 @@ export default class KanbnStatusBarItem {
this._statusBarItem.text = '$(project)';
this._statusBarItem.tooltip = 'Initialise Kanbn';
this._statusBarItem.command = 'kanbn.init';
if (vscode.workspace.getConfiguration('kanbn').get('showUninitialisedStatusBarItem')) {
if (workspace.getConfiguration('kanbn').get('showUninitialisedStatusBarItem')) {
this._statusBarItem.show();
} else {
this._statusBarItem.hide();

View File

@ -1,9 +1,12 @@
import * as path from "path";
import * as vscode from "vscode";
import { join } from "path";
import { WebviewPanel, Disposable, window, ViewColumn, Uri, workspace, commands } from "vscode";
import getNonce from "./getNonce";
import { v4 as uuidv4 } from "uuid";
function transformTaskData(taskData: any) {
function transformTaskData(
taskData: any,
customFields: { name: string, type: 'boolean' | 'date' | 'number' | 'string' }[]
) {
const result = {
id: taskData.id,
name: taskData.name,
@ -50,6 +53,17 @@ function transformTaskData(taskData: any) {
result.metadata["completed"] = new Date(Date.parse(taskData.metadata.completed));
}
// Add custom fields
for (let customField of customFields) {
if (customField.name in taskData.metadata && taskData.metadata[customField.name] !== null) {
if (customField.type === 'date') {
result.metadata[customField.name] = new Date(Date.parse(taskData.metadata[customField.name]));
} else {
result.metadata[customField.name] = taskData.metadata[customField.name];
}
}
}
return result;
}
@ -57,7 +71,7 @@ export default class KanbnTaskPanel {
private static readonly viewType = "react";
private static panels: Record<string, KanbnTaskPanel> = {};
private readonly _panel: vscode.WebviewPanel;
private readonly _panel: WebviewPanel;
private readonly _extensionPath: string;
private readonly _workspacePath: string;
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
@ -65,7 +79,7 @@ export default class KanbnTaskPanel {
private readonly _panelUuid: string;
private _taskId: string | null;
private _columnName: string | null;
private _disposables: vscode.Disposable[] = [];
private _disposables: Disposable[] = [];
public static async show(
extensionPath: string,
@ -75,14 +89,14 @@ export default class KanbnTaskPanel {
taskId: string | null,
columnName: string | null
) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined;
// Create a new panel
const panelUuid = uuidv4();
const taskPanel = new KanbnTaskPanel(
extensionPath,
workspacePath,
column || vscode.ViewColumn.One,
column || ViewColumn.One,
kanbn,
kanbnFolderName,
taskId,
@ -96,7 +110,7 @@ export default class KanbnTaskPanel {
private constructor(
extensionPath: string,
workspacePath: string,
column: vscode.ViewColumn,
column: ViewColumn,
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
kanbnFolderName: string,
taskId: string | null,
@ -112,7 +126,7 @@ export default class KanbnTaskPanel {
this._panelUuid = panelUuid;
// Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(KanbnTaskPanel.viewType, "New task", column, {
this._panel = window.createWebviewPanel(KanbnTaskPanel.viewType, "New task", column, {
// Enable javascript in the webview
enableScripts: true,
@ -121,14 +135,14 @@ export default class KanbnTaskPanel {
// Restrict the webview to only loading content from allowed paths
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, "build")),
vscode.Uri.file(path.join(this._workspacePath, this._kanbnFolderName)),
vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
Uri.file(join(this._extensionPath, "build")),
Uri.file(join(this._workspacePath, this._kanbnFolderName)),
Uri.file(join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
],
});
(this._panel as any).iconPath = {
light: vscode.Uri.file(path.join(this._extensionPath, "resources", "task_light.svg")),
dark: vscode.Uri.file(path.join(this._extensionPath, "resources", "task_dark.svg")),
light: Uri.file(join(this._extensionPath, "resources", "task_light.svg")),
dark: Uri.file(join(this._extensionPath, "resources", "task_dark.svg")),
};
// Set the webview's title to the kanbn task name
@ -152,7 +166,7 @@ export default class KanbnTaskPanel {
// Display error message
case "error":
vscode.window.showErrorMessage(message.text);
window.showErrorMessage(message.text);
return;
// Update the task webview panel title
@ -162,49 +176,64 @@ export default class KanbnTaskPanel {
// Create a task
case "kanbn.create":
await this._kanbn.createTask(transformTaskData(message.taskData), message.taskData.column);
await this._kanbn.createTask(
transformTaskData(message.taskData, message.customFields),
message.taskData.column
);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Created task '${message.taskData.name}'.`);
if (workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
window.showInformationMessage(`Created task '${message.taskData.name}'.`);
}
return;
// Update a task
case "kanbn.update":
await this._kanbn.updateTask(message.taskId, transformTaskData(message.taskData), message.taskData.column);
await this._kanbn.updateTask(
message.taskId,
transformTaskData(message.taskData, message.customFields),
message.taskData.column
);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Updated task '${message.taskData.name}'.`);
if (workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
window.showInformationMessage(`Updated task '${message.taskData.name}'.`);
}
return;
// Delete a task and close the webview panel
case "kanbn.delete":
vscode.window
window
.showInformationMessage(`Delete task '${message.taskData.name}'?`, "Yes", "No")
.then(async (value) => {
if (value === "Yes") {
await this._kanbn.deleteTask(message.taskId, true);
KanbnTaskPanel.panels[message.panelUuid].dispose();
delete KanbnTaskPanel.panels[message.panelUuid];
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Deleted task '${message.taskData.name}'.`);
if (workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
window.showInformationMessage(`Deleted task '${message.taskData.name}'.`);
}
}
});
return;
// Go to raw task and close the webview panel
case "kanbn.goToRaw":
KanbnTaskPanel.panels[message.panelUuid].dispose();
delete KanbnTaskPanel.panels[message.panelUuid];
let uri = Uri.file(join(this._workspacePath, ".kanbn", "tasks", message.taskId + ".md"));
await commands.executeCommand('vscode.open', uri);
return;
// Archive a task and close the webview panel
case 'kanbn.archive':
await this._kanbn.archiveTask(message.taskId);
KanbnTaskPanel.panels[message.panelUuid].dispose();
delete KanbnTaskPanel.panels[message.panelUuid];
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Archived task '${message.taskData.name}'.`);
if (workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
window.showInformationMessage(`Archived task '${message.taskData.name}'.`);
}
return;
}
@ -229,7 +258,7 @@ export default class KanbnTaskPanel {
try {
index = await this._kanbn.getIndex();
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
let tasks: any[];
@ -239,7 +268,7 @@ export default class KanbnTaskPanel {
...this._kanbn.hydrateTask(index, task),
}));
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
let task = null;
@ -258,6 +287,7 @@ export default class KanbnTaskPanel {
index,
task,
tasks,
customFields: index.options.customFields ?? [],
columnName: this._columnName,
dateFormat: this._kanbn.getDateFormat(index),
panelUuid: this._panelUuid,
@ -265,20 +295,20 @@ export default class KanbnTaskPanel {
}
private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const manifest = require(join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest["main.js"];
const mainStyle = manifest["main.css"];
const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
const scriptUri = Uri.file(join(this._extensionPath, "build", mainScript)).with({
scheme: "vscode-resource",
});
const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
const styleUri = Uri.file(join(this._extensionPath, "build", mainStyle)).with({
scheme: "vscode-resource",
});
const customStyleUri = vscode.Uri.file(
path.join(this._workspacePath, this._kanbnFolderName, "board.css")
const customStyleUri = Uri.file(
join(this._workspacePath, this._kanbnFolderName, "board.css")
).with({ scheme: "vscode-resource" });
const codiconsUri = vscode.Uri.file(
path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
const codiconsUri = Uri.file(
join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
).with({ scheme: "vscode-resource" });
// Use a nonce to whitelist which scripts can be run
@ -295,7 +325,7 @@ export default class KanbnTaskPanel {
<link rel="stylesheet" type="text/css" href="${customStyleUri}">
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${vscode.Uri.file(path.join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
<base href="${Uri.file(join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,4 +1,4 @@
import * as vscode from "vscode";
import { ExtensionContext, commands, workspace, window, RelativePattern } from "vscode";
import KanbnStatusBarItem from "./KanbnStatusBarItem";
import KanbnBoardPanel from "./KanbnBoardPanel";
import KanbnBurndownPanel from "./KanbnBurndownPanel";
@ -6,19 +6,19 @@ import KanbnTaskPanel from "./KanbnTaskPanel";
let kanbnStatusBarItem: KanbnStatusBarItem;
export async function activate(context: vscode.ExtensionContext) {
export async function activate(context: ExtensionContext) {
// Register a command to initialise Kanbn in the current workspace. This command will be invoked when the status
// bar item is clicked in a workspace where Kanbn isn't already initialised.
context.subscriptions.push(
vscode.commands.registerCommand("kanbn.init", async () => {
commands.registerCommand("kanbn.init", async () => {
// If no workspace folder is opened, we can't initialise kanbn
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before initialising Kanbn.");
if (workspace.workspaceFolders === undefined) {
window.showErrorMessage("You need to open a workspace before initialising Kanbn.");
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
process.chdir(workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is already initialised, get the project name
@ -28,7 +28,7 @@ export async function activate(context: vscode.ExtensionContext) {
}
// Prompt for a new project name
const newProjectName = await vscode.window.showInputBox({
const newProjectName = await window.showInputBox({
value: projectName,
placeHolder: "The project name.",
validateInput: (text) => {
@ -41,7 +41,7 @@ export async function activate(context: vscode.ExtensionContext) {
await kanbn.initialise({
name: newProjectName,
});
vscode.window.showInformationMessage(`Initialised Kanbn project '${newProjectName}'.`);
window.showInformationMessage(`Initialised Kanbn project '${newProjectName}'.`);
KanbnBoardPanel.update();
}
kanbnStatusBarItem.update();
@ -51,28 +51,28 @@ export async function activate(context: vscode.ExtensionContext) {
// Register a command to open the kanbn board. This command will be invoked when the status bar item is clicked
// in a workspace where kanbn has already been initialised.
context.subscriptions.push(
vscode.commands.registerCommand("kanbn.board", async () => {
commands.registerCommand("kanbn.board", async () => {
// If no workspace folder is opened, we can't open the kanbn board
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before viewing the Kanbn board.");
if (workspace.workspaceFolders === undefined) {
window.showErrorMessage("You need to open a workspace before viewing the Kanbn board.");
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
process.chdir(workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is initialised, view the kanbn board
if (await kanbn.initialised()) {
KanbnBoardPanel.createOrShow(
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
workspace.workspaceFolders[0].uri.fsPath,
kanbn,
await kanbn.getFolderName()
);
KanbnBoardPanel.update();
} else {
vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the Kanbn board.");
window.showErrorMessage("You need to initialise Kanbn before viewing the Kanbn board.");
}
kanbnStatusBarItem.update();
})
@ -80,57 +80,57 @@ export async function activate(context: vscode.ExtensionContext) {
// Register a command to add a new kanbn task.
context.subscriptions.push(
vscode.commands.registerCommand("kanbn.addTask", async () => {
commands.registerCommand("kanbn.addTask", async () => {
// If no workspace folder is opened, we can't add a new task
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before adding a new task.");
if (workspace.workspaceFolders === undefined) {
window.showErrorMessage("You need to open a workspace before adding a new task.");
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
process.chdir(workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is initialised, open the task webview
if (await kanbn.initialised()) {
KanbnTaskPanel.show(
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
workspace.workspaceFolders[0].uri.fsPath,
kanbn,
await kanbn.getFolderName(),
null,
null
);
} else {
vscode.window.showErrorMessage("You need to initialise Kanbn before adding a new task.");
window.showErrorMessage("You need to initialise Kanbn before adding a new task.");
}
})
);
// Register a command to open a burndown chart.
context.subscriptions.push(
vscode.commands.registerCommand("kanbn.burndown", async () => {
commands.registerCommand("kanbn.burndown", async () => {
// If no workspace folder is opened, we can't open the burndown chart
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before viewing the burndown chart.");
if (workspace.workspaceFolders === undefined) {
window.showErrorMessage("You need to open a workspace before viewing the burndown chart.");
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
process.chdir(workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is initialised, view the burndown chart
if (await kanbn.initialised()) {
KanbnBurndownPanel.createOrShow(
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
workspace.workspaceFolders[0].uri.fsPath,
kanbn,
await kanbn.getFolderName()
);
KanbnBurndownPanel.update();
} else {
vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the burndown chart.");
window.showErrorMessage("You need to initialise Kanbn before viewing the burndown chart.");
}
kanbnStatusBarItem.update();
})
@ -138,15 +138,15 @@ export async function activate(context: vscode.ExtensionContext) {
// Register a command to archive tasks.
context.subscriptions.push(
vscode.commands.registerCommand("kanbn.archiveTasks", async () => {
commands.registerCommand("kanbn.archiveTasks", async () => {
// If no workspace folder is opened, we can't archive tasks
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before sending tasks to the archive.");
if (workspace.workspaceFolders === undefined) {
window.showErrorMessage("You need to open a workspace before sending tasks to the archive.");
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
process.chdir(workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// Get a list of tracked tasks
@ -155,12 +155,12 @@ export async function activate(context: vscode.ExtensionContext) {
tasks = [...(await kanbn.findTrackedTasks())];
} catch (e) {}
if (tasks.length === 0) {
vscode.window.showInformationMessage("There are no tasks to archive.");
window.showInformationMessage("There are no tasks to archive.");
return;
}
// Prompt for a selection of tasks to archive
const archiveTaskIds = await vscode.window.showQuickPick(
const archiveTaskIds = await window.showQuickPick(
tasks,
{
placeHolder: 'Select tasks to archive...',
@ -173,8 +173,8 @@ export async function activate(context: vscode.ExtensionContext) {
}
KanbnBoardPanel.update();
kanbnStatusBarItem.update();
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(
if (workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
window.showInformationMessage(
`Archived ${archiveTaskIds.length} task${archiveTaskIds.length === 1 ? '' : 's'}.`
);
}
@ -184,15 +184,15 @@ export async function activate(context: vscode.ExtensionContext) {
// Register a command to restore a task from the archive.
context.subscriptions.push(
vscode.commands.registerCommand("kanbn.restoreTasks", async () => {
commands.registerCommand("kanbn.restoreTasks", async () => {
// If no workspace folder is opened, we can't restore tasks from the archive
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before restoring tasks from the archive.");
if (workspace.workspaceFolders === undefined) {
window.showErrorMessage("You need to open a workspace before restoring tasks from the archive.");
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
process.chdir(workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// Get a list of archived tasks
@ -201,12 +201,12 @@ export async function activate(context: vscode.ExtensionContext) {
archivedTasks = await kanbn.listArchivedTasks();
} catch (e) {}
if (archivedTasks.length === 0) {
vscode.window.showInformationMessage("There are no archived tasks to restore.");
window.showInformationMessage("There are no archived tasks to restore.");
return;
}
// Prompt for a selection of tasks to restore
const restoreTaskIds = await vscode.window.showQuickPick(
const restoreTaskIds = await window.showQuickPick(
archivedTasks,
{
placeHolder: 'Select tasks to restore...',
@ -219,7 +219,7 @@ export async function activate(context: vscode.ExtensionContext) {
const index = await kanbn.getIndex();
// Prompt for a column to restore the tasks into
const restoreColumn = await vscode.window.showQuickPick(
const restoreColumn = await window.showQuickPick(
[
'None (use original)',
...Object.keys(index.columns)
@ -234,8 +234,8 @@ export async function activate(context: vscode.ExtensionContext) {
}
KanbnBoardPanel.update();
kanbnStatusBarItem.update();
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(
if (workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
window.showInformationMessage(
`Restored ${restoreTaskIds.length} task${restoreTaskIds.length === 1 ? '' : 's'}.`
);
}
@ -245,9 +245,9 @@ export async function activate(context: vscode.ExtensionContext) {
);
// If a workspace folder is open, add a status bar item and start watching for file changes
if (vscode.workspace.workspaceFolders !== undefined) {
if (workspace.workspaceFolders !== undefined) {
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
process.chdir(workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// Create status bar item
@ -256,10 +256,10 @@ export async function activate(context: vscode.ExtensionContext) {
KanbnBoardPanel.update();
// Initialise file watcher
const uri = vscode.workspace.workspaceFolders[0].uri.fsPath;
const uri = workspace.workspaceFolders[0].uri.fsPath;
const kanbnFolderName = await kanbn.getFolderName();
const fileWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(uri, `${kanbnFolderName}/**.*`)
const fileWatcher = workspace.createFileSystemWatcher(
new RelativePattern(uri, `${kanbnFolderName}/**/*.md`)
);
fileWatcher.onDidChange(() => {
kanbnStatusBarItem.update();
@ -269,7 +269,7 @@ export async function activate(context: vscode.ExtensionContext) {
}
// Handle configuration changes
vscode.workspace.onDidChangeConfiguration((e) => {
workspace.onDidChangeConfiguration((e) => {
kanbnStatusBarItem.update();
KanbnBoardPanel.update();
});

32098
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,13 +7,13 @@
"color": "#72abdd",
"theme": "dark"
},
"version": "0.10.0",
"version": "0.12.3",
"engines": {
"vscode": "^1.23.0"
},
"author": "Gordon Larrigan",
"license": "MIT",
"publisher": "gordonlarrigan",
"publisher": "phares",
"keywords": [
"kanbn",
"kanban",
@ -118,12 +118,15 @@
"vscode-codicons": "^0.0.15"
},
"scripts": {
"vscode:prepublish": "./scripts/build-non-split.js && tsc -p tsconfig.extension.json",
"vscode:prepublish": "node ./scripts/build-non-split.js && tsc -p tsconfig.extension.json",
"postinstall": "node ./node_modules/vscode/bin/install",
"start": "react-scripts start",
"build": "./scripts/build-non-split.js && tsc -p tsconfig.extension.json",
"react:build": "node ./scripts/build-non-split.js",
"tsc:build": "tsc -p tsconfig.extension.json",
"build": "node ./scripts/build-non-split.js && tsc -p tsconfig.extension.json",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"vscode:package": "vsce package"
},
"devDependencies": {
"@types/dateformat": "^3.0.1",

View File

@ -66,6 +66,7 @@ function App() {
setTasks(tasks);
setColumnName(event.data.columnName);
setColumnNames(Object.keys(event.data.index.columns));
setCustomFields(event.data.customFields);
setPanelUuid(event.data.panelUuid);
break;
@ -118,6 +119,7 @@ function App() {
tasks={tasks}
columnName={columnName}
columnNames={columnNames}
customFields={customFields}
dateFormat={dateFormat}
panelUuid={panelUuid}
vscode={vscode}

View File

@ -70,12 +70,23 @@ const filterProperties = [
'description',
'assigned',
'tag',
'relation'
'relation',
'subtask',
'comment',
];
// Filter tasks according to the filter string
const filterTask = (task: KanbnTask, taskFilter: string) => {
const filterTask = (
task: KanbnTask,
taskFilter: string,
customFields: { name: string, type: 'boolean' | 'date' | 'number' | 'string' }[]
) => {
let result = true;
const customFieldMap = Object.fromEntries(customFields.map(customField => [
customField.name.toLowerCase(),
customField,
]));
const customFieldNames = Object.keys(customFieldMap);
taskFilter.split(' ').forEach(f => {
const parts = f.split(':').map(p => p.toLowerCase());
@ -90,6 +101,17 @@ const filterTask = (task: KanbnTask, taskFilter: string) => {
return;
}
// Filter boolean custom fields
if (customFieldNames.includes(parts[0]) && customFieldMap[parts[0]].type === 'boolean') {
if (
!(customFieldMap[parts[0]].name in task.metadata) ||
!task.metadata[customFieldMap[parts[0]].name]
) {
result = false;
}
return;
}
// Filter task id or name
if (
!task.id.toLowerCase().includes(parts[0]) &&
@ -101,9 +123,14 @@ const filterTask = (task: KanbnTask, taskFilter: string) => {
}
// If this filter section contains a property name and value, check the value against the property
if (parts.length === 2 && filterProperties.indexOf(parts[0]) !== -1) {
if (
parts.length === 2 && (
filterProperties.includes(parts[0]) ||
customFieldNames.includes(parts[0])
)
) {
// Filter by property value
// Fetch the value to filter by
let propertyValue = '';
switch (parts[0]) {
case 'description':
@ -121,8 +148,24 @@ const filterTask = (task: KanbnTask, taskFilter: string) => {
case 'relation':
propertyValue = task.relations.map(relation => `${relation.type} ${relation.task}`).join(' ');
break;
default: break;
case 'subtask':
propertyValue = task.subTasks.map(subTask => `${subTask.text}`).join(' ');
break;
case 'comment':
propertyValue = task.comments.map(comment => `${comment.author} ${comment.text}`).join(' ');
break;
default:
if (
customFieldNames.includes(parts[0]) &&
customFieldMap[parts[0]].type !== 'boolean' &&
customFieldMap[parts[0]].name in task.metadata
) {
propertyValue = `${task.metadata[customFieldMap[parts[0]].name]}`;
}
break;
}
// Check the search term against the value
if (!propertyValue.toLowerCase().includes(parts[1])) {
result = false;
}
@ -326,7 +369,9 @@ const Board = ({
snapshot.isDraggingOver ? 'drag-over' : null
].filter(i => i).join(' ')}
>
{column.filter(task => filterTask(task, taskFilter)).map((task, position) => <TaskItem
{column
.filter(task => filterTask(task, taskFilter, customFields))
.map((task, position) => <TaskItem
task={task}
columnName={columnName}
customFields={customFields}

View File

@ -53,11 +53,12 @@ const Markdown = props => (<ReactMarkdown {...{
...props,
}} />);
const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUuid, vscode }: {
const TaskEditor = ({ task, tasks, columnName, columnNames, customFields, dateFormat, panelUuid, vscode }: {
task: KanbnTask | null,
tasks: Record<string, KanbnTask>,
columnName: string,
columnNames: string[],
customFields: { name: string, type: 'boolean' | 'date' | 'number' | 'string' }[],
dateFormat: string,
panelUuid: string,
vscode: VSCodeApi
@ -76,7 +77,17 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
due: (task && 'due' in task.metadata) ? formatDate(task.metadata.due!, 'yyyy-mm-dd') : '',
completed: (task && 'completed' in task.metadata) ? formatDate(task.metadata.completed!, 'yyyy-mm-dd') : '',
assigned: (task && 'assigned' in task.metadata) ? task.metadata.assigned : (gitUsername() || ''),
tags: (task && 'tags' in task.metadata) ? (task.metadata.tags || []) : []
tags: (task && 'tags' in task.metadata) ? (task.metadata.tags || []) : [],
...Object.fromEntries(
customFields.map(customField => [
customField.name,
(task && customField.name in task.metadata)
? (customField.type === 'date'
? formatDate(task.metadata[customField.name], 'yyyy-mm-dd')
: task.metadata[customField.name]
) : null,
]),
)
},
relations: task ? task.relations : [],
subTasks: task ? task.subTasks : [],
@ -112,12 +123,14 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
command: 'kanbn.update',
taskId: task!.id,
taskData: values,
customFields,
panelUuid
});
} else {
vscode.postMessage({
command: 'kanbn.create',
taskData: values,
customFields,
panelUuid
});
}
@ -126,6 +139,16 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
setSubmitting(false);
};
// Called when the go to raw task button is clicked
const handleGoToRawTask = values => {
vscode.postMessage({
command: 'kanbn.goToRaw',
taskId: task!.id,
taskData: values,
panelUuid
});
};
// Called when the delete task button is clicked
const handleRemoveTask = values => {
vscode.postMessage({
@ -224,10 +247,51 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
handleChange,
isSubmitting
}) => (
<React.Fragment>
<Form>
<h1 className="kanbn-task-editor-title">
{editing ? 'Update task' : 'Create new task'}
{dirty && <span className="kanbn-task-editor-dirty">*</span>}
</h1>
<div className="kanbn-task-editor-buttons kanbn-task-editor-main-buttons">
{editing && <button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-go-to"
title="Go to raw task"
onClick={() => {
handleGoToRawTask(values);
}}
>
<i className="codicon codicon-file-code"></i>Go to raw
</button>}
{editing && <button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
title="Delete task"
onClick={() => {
handleRemoveTask(values);
}}
>
<i className="codicon codicon-trash"></i>Delete
</button>}
{editing && <button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-archive"
title="Archive task"
onClick={() => {
handleArchiveTask(values);
}}
>
<i className="codicon codicon-archive"></i>Archive
</button>}
<button
type="submit"
className="kanbn-task-editor-button kanbn-task-editor-button-submit"
title="Save task"
disabled={isSubmitting}
>
<i className="codicon codicon-save"></i>Save
</button>
</div>
{editing && <span className="kanbn-task-editor-dates">
{
[
@ -236,8 +300,6 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
].filter(i => i).join(', ')
}
</span>}
</h1>
<Form>
<div className="kanbn-task-editor-form">
<div className="kanbn-task-editor-column-left">
<div className="kanbn-task-editor-field kanbn-task-editor-field-name">
@ -359,7 +421,7 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
<FieldArray name="relations">
{({ insert, remove, push }) => (
<div>
{values.relations.length > 0 && values.relations.map((relation, index) => (
{false && values.relations.length > 0 && values.relations.map((relation, index) => (
<div className="kanbn-task-editor-row kanbn-task-editor-row-relation" key={index}>
<div className="kanbn-task-editor-column kanbn-task-editor-field-relation-type">
<Field
@ -403,7 +465,7 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
<button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-add"
title="Add relation"
title={`Add relation [${values.relations.length} relation(s)]`}
onClick={() => push({ type: '', task: '' })}
>
<i className="codicon codicon-link"></i>Add relation
@ -511,36 +573,6 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
</div>
</div>
<div className="kanbn-task-editor-column-right">
<div className="kanbn-task-editor-buttons">
{editing && <button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
title="Delete task"
onClick={() => {
handleRemoveTask(values);
}}
>
<i className="codicon codicon-trash"></i>Delete
</button>}
{editing && <button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-archive"
title="Archive task"
onClick={() => {
handleArchiveTask(values);
}}
>
<i className="codicon codicon-archive"></i>Archive
</button>}
<button
type="submit"
className="kanbn-task-editor-button kanbn-task-editor-button-submit"
title="Save task"
disabled={isSubmitting}
>
<i className="codicon codicon-save"></i>Save
</button>
</div>
<div className="kanbn-task-editor-field kanbn-task-editor-field-column">
<label className="kanbn-task-editor-field-label">
<p>Column</p>
@ -642,6 +674,45 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
name="progress"
/>
</div>
{
customFields.map(customField => (
<div className={[
'kanbn-task-editor-field kanbn-task-editor-custom-field',
`kanbn-task-editor-custom-field-${paramCase(customField.name)}`
].join(' ')}>
<label className="kanbn-task-editor-field-label">
{customField.type === 'boolean'
? (
<>
<Field
className="kanbn-task-editor-field-input kanbn-task-editor-custom-checkbox"
type="checkbox"
name={`metadata.${customField.name}`}
/><p>{customField.name}</p>
</>
) : (
<>
<p>{customField.name}</p>
<Field
className="kanbn-task-editor-field-input"
type={{
date: 'date',
number: 'number',
string: 'text',
}[customField.type]}
name={`metadata.${customField.name}`}
/>
</>
)}
</label>
<ErrorMessage
className="kanbn-task-editor-field-errors"
component="div"
name={`metadata.${customField.name}`}
/>
</div>
))
}
<div className="kanbn-task-editor-field kanbn-task-editor-field-tags">
<label className="kanbn-task-editor-field-label">
<p>Tags</p>
@ -701,7 +772,6 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui
</div>
</div>
</Form>
</React.Fragment>
)}
</Formik>
</div>

View File

@ -71,7 +71,14 @@ const TaskItem = ({ task, columnName, customFields, position, dateFormat, vscode
<div className="kanbn-task-data kanbn-task-data-tags">
{task.metadata.tags!.map(tag => {
return (
<span className={[
<span onClick={() => {
vscode.postMessage({
command: 'kanbn.goToRaw',
taskId: task.id,
columnName: task.column
});
}}
className={[
'kanbn-task-tag',
`kanbn-task-tag-${paramCase(tag)}`
].join(' ')}>
@ -102,7 +109,9 @@ const TaskItem = ({ task, columnName, customFields, position, dateFormat, vscode
<>
<i className="codicon codicon-json"></i>
<span title={customField.name}>
{task.metadata[customField.name]}
{customField.type === 'date'
? formatDate(task.metadata[customField.name], dateFormat)
: task.metadata[customField.name]}
</span>
</>
)
@ -166,13 +175,27 @@ const TaskItem = ({ task, columnName, customFields, position, dateFormat, vscode
{
task.workload !== undefined &&
<div className="kanbn-task-data kanbn-task-data-workload">
<i className="codicon codicon-run"></i>{task.workload}
<i className="codicon codicon-tools"
onClick={() => {
vscode.postMessage({
command: 'kanbn.goToRaw',
taskId: task.id,
columnName: task.column
});
}}></i>{task.workload}
</div>
}
{
task.relations.length > 0 &&
task.relations.map(relation => (
<div className={[
<div onClick={() => {
vscode.postMessage({
command: 'kanbn.goToRaw',
taskId: task.id,
columnName: task.column
});
}}
className={[
'kanbn-task-data kanbn-task-data-relation',
relation.type ? `kanbn-task-data-relation-${relation.type}` : null,
].join(' ')}>

View File

@ -351,10 +351,9 @@ Task editor styles
-----------------------------------------------------------------------------*/
.kanbn-task-editor-title {
display: inline-block;
font-size: 1.5em;
margin-top: 0;
padding-bottom: 0.5em;
border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
}
.kanbn-task-editor-dirty {
@ -368,10 +367,12 @@ Task editor styles
font-weight: normal;
opacity: 0.8;
float: right;
margin: 2px;
}
.kanbn-task-editor-form {
display: flex;
border-top: 1px solid var(--vscode-activityBar-inactiveForeground);
}
.kanbn-task-editor-field .kanbn-task-editor-title,
@ -439,6 +440,12 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
text-align: right;
}
.kanbn-task-editor-main-buttons {
float: right;
position: relative;
top: -0.5em;
}
.kanbn-task-editor-button {
outline: none;
border: 1px transparent solid;
@ -602,6 +609,14 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
float: right;
}
.kanbn-task-editor-custom-checkbox {
float: left;
width: auto;
position: relative;
top: -.2em;
margin: .5em 1em .5em 0;
}
/*-----------------------------------------------------------------------------
Burndown chart styles
-----------------------------------------------------------------------------*/

View File

@ -1,9 +1,9 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { render } from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
render(
<App />,
document.getElementById('root') as HTMLElement
);

BIN
vscode-kanbn-0.12.0.vsix Normal file

Binary file not shown.

BIN
vscode-kanbn-0.12.1.vsix Normal file

Binary file not shown.

BIN
vscode-kanbn-0.12.2.vsix Normal file

Binary file not shown.