diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf70b8..f342eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # 0.11.0 -* Added KaTeX support to task markdown (description and comments) +* 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 +* Added relations and custom fields to task cards +* Added task card customisation using `board.css` # 0.10.0 diff --git a/docs/styles.md b/docs/styles.md index 28bbffd..394cf59 100644 --- a/docs/styles.md +++ b/docs/styles.md @@ -8,6 +8,62 @@ Visual Studio Code will add `vscode-light`, `vscode-dark` and `vscode-high-contr Various Codicon icons have been used in this extension. Check [here](https://code.visualstudio.com/api/references/icons-in-labels) for a listing of available icons. +## Column and tag configuration + +Each column is given a CSS class containing the column name in param-case. You can use this to apply custom styles to specific columns. + +For example, say we have a column called 'In Progress'. We can apply a custom border colour like so: +```css +.kanbn-column-in-progress .kanbn-column-task-list { + border-color: #ff0; +} +``` + +Similarly, each tag is given a CSS class containing the tag name in param-case. + +For example, say we have tags called 'QA required' and 'Breaking Change'. We can apply custom styles like so: +```css +.kanbn-task-tag-qa-required { + background-color: #418; +} + +.kanbn-task-tag-breaking-change { + background-color: #900; + color: #eee; + font-size: 1.5em; +} +``` + +## Task card configuration + +The task cards that appear on the Kanbn board can be configured by showing or hiding different elements, or by applying alternative styles to them. + +The following elements are hidden by default (but can be re-enabled by adding a custom board style): +- Updated date +- Started date +- Due date +- Completed date +- Number of comments +- Relations + +Task fields are generally contained within a `.kanbn-task-data` element, most of which have `display: inline-block` by default. Additionally, the outer-container of a task card is given CSS classes containing the task's column name, completed status and overdue status. + +Here's an example of a task card style using some of the above features: +```css +.kanbn-task-data-relation { + display: block; /* this will display task relations, one per line */ +} + +.kanbn-task-data-relation-blocks { + color: #900; + font-weight: bold; /* this will highlight task relations of type 'blocks' */ +} + +.kanbn-task-completed .kanbn-task-data-completed { + display: inline-block; /* this will show the completed date only for tasks that have been completed */ +} +``` + ## CSS classes ### Kanbn board @@ -24,7 +80,7 @@ Various Codicon icons have been used in this extension. Check [here](https://cod - `kanbn-header-description` - `kanbn-board` - `kanbn-column` -- `kanbn-column-{Column name in snake-case}` +- `kanbn-column-{Column name in param-case}` - `kanbn-column-name` - `kanbn-column-count` - `kanbn-column-button` @@ -35,19 +91,30 @@ Various Codicon icons have been used in this extension. Check [here](https://cod - `kanbn-column-task-list` - `kanbn-column-task-list.drag-over` - `kanbn-task` -- `kanbn-task-row` -- `kanbn-task-name` -- `kanbn-task-data` -- `kanbn-task-tags` -- `kanbn-task-tag` -- `kanbn-task-tag-{Tag name in snake-case}` -- `kanbn-task-assigned` -- `kanbn-task-date` -- `kanbn-task-workload` -- `kanbn-task-progress` +- `kanbn-task-{Column name in param-case}` - `kanbn-task-overdue` -- `kanbn-task-comments` -- `kanbn-task-sub-tasks` +- `kanbn-task-completed` +- `drag` +- `kanbn-task-data` +- `kanbn-task-data-label` +- `kanbn-task-data-name` +- `kanbn-task-data-tags` +- `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-assigned` +- `kanbn-task-data-created` +- `kanbn-task-data-updated` +- `kanbn-task-data-started` +- `kanbn-task-data-due` +- `kanbn-task-data-completed` +- `kanbn-task-data-comments` +- `kanbn-task-data-sub-tasks` +- `kanbn-task-data-relation` +- `kanbn-task-data-relation-{Relation type in param-case}` +- `kanbn-task-data-workload` +- `kanbn-task-progress` ### Task editor @@ -107,7 +174,7 @@ Various Codicon icons have been used in this extension. Check [here](https://cod - `kanbn-task-editor-row-tag` - `kanbn-task-editor-field-tag` - `kanbn-task-editor-tag-highlight` -- `kanbn-task-tag-{Tag name in snake-case}` +- `kanbn-task-tag-{Tag name in param-case}` ### Burndown chart diff --git a/ext-src/KanbnBoardPanel.ts b/ext-src/KanbnBoardPanel.ts index 6ecc88e..381d3af 100644 --- a/ext-src/KanbnBoardPanel.ts +++ b/ext-src/KanbnBoardPanel.ts @@ -79,6 +79,7 @@ export default class KanbnBoardPanel { startedColumns: index.options.startedColumns ?? [], completedColumns: index.options.completedColumns ?? [], 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"), diff --git a/package-lock.json b/package-lock.json index 2f017b6..4013a19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1352,9 +1352,9 @@ } }, "@basementuniverse/kanbn": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@basementuniverse/kanbn/-/kanbn-0.9.0.tgz", - "integrity": "sha512-LYJlJ6/KLkxqW/ZpPkLsAYTZ6ki5wmP9ZIgLPNW28+KVXhFz78/+3amoynMeOFUOBiKymCJYlKtvly6y0CvdTg==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@basementuniverse/kanbn/-/kanbn-0.10.0.tgz", + "integrity": "sha512-inNRbX1ZuiUiub0PbEDJAZGI9m92+AwGWnoByEZEHAq0L8rD1Z8DZVzeyXQDhrNdt8gpkjINR8+l8NJ8Tmqc0Q==", "requires": { "asciichart": "^1.5.25", "auto-load": "^3.0.4", @@ -4168,9 +4168,9 @@ "dev": true }, "chrono-node": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.2.6.tgz", - "integrity": "sha512-ahgxpY4ihg3frV5t7pZYrS0Iap5MErTQ7whVNBxbiLjplc2HhGwj3zgr0dEnJos/FAuZVjrHoky8J9YiNc5ZKQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.2.7.tgz", + "integrity": "sha512-QQxlQUwebgNJDtUJpRQlhKoJJgSfvNTe6Zk1LgyVHVnbCj6xKygmiviJonS/n5A4xd9qjEl1u+ke2vzD77ty/g==", "requires": { "dayjs": "^1.10.0" } @@ -10245,9 +10245,9 @@ } }, "humanize-duration": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.26.0.tgz", - "integrity": "sha512-SddekX3p5ApvPY6bbAYppGKe874jP6iFZXYtrQToDV4R0j2UpTYPqwTFM2QpXpuw9DhS/eXTUnKYTF9TbXAJ6A==" + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz", + "integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==" }, "iconv-lite": { "version": "0.4.24", @@ -13798,9 +13798,9 @@ "dev": true }, "nextgen-events": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/nextgen-events/-/nextgen-events-1.4.0.tgz", - "integrity": "sha512-bwSpShfuLg/lcRFokZvtUcrn5kumy6uRI6Jc+QWzH+bW6ZRdog7LfwAwhqeTzVfsprZgDGrctr6rO5aJhcdAfg==" + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nextgen-events/-/nextgen-events-1.5.2.tgz", + "integrity": "sha512-0ZEIRQywH5Oxt2IYYufRltQg/KjXhKM7f7MHve+ZIRaKnIR1PPYEXAl2WBmej5Sf0Qh2GgE/21sMRZVuOyxLzw==" }, "nice-try": { "version": "1.0.5", diff --git a/package.json b/package.json index 3d28eb4..47088f8 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ } }, "dependencies": { - "@basementuniverse/kanbn": "^0.9.0", + "@basementuniverse/kanbn": "^0.10.0", "dateformat": "^4.5.1", "formik": "^2.2.6", "git-user-name": "^2.0.0", diff --git a/src/App.tsx b/src/App.tsx index 89119e9..a033a1f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ function App() { const [startedColumns, setStartedColumns] = useState([]); const [completedColumns, setCompletedColumns] = useState([]); const [columnSorting, setColumnSorting] = useState({}); + const [customFields, setCustomFields] = useState([]); const [dateFormat, setDateFormat] = useState(''); const [task, setTask] = useState({}); const [tasks, setTasks] = useState({}); @@ -48,6 +49,7 @@ function App() { setStartedColumns(event.data.startedColumns); setCompletedColumns(event.data.completedColumns); setColumnSorting(event.data.columnSorting); + setCustomFields(event.data.customFields); setShowBurndownButton(event.data.showBurndownButton); setShowSprintButton(event.data.showSprintButton); @@ -101,6 +103,7 @@ function App() { startedColumns={startedColumns} completedColumns={completedColumns} columnSorting={columnSorting} + customFields={customFields} dateFormat={dateFormat} showBurndownButton={showBurndownButton} showSprintButton={showSprintButton} diff --git a/src/Board.tsx b/src/Board.tsx index 270204c..639d79a 100644 --- a/src/Board.tsx +++ b/src/Board.tsx @@ -140,6 +140,7 @@ const Board = ({ startedColumns, completedColumns, columnSorting, + customFields, dateFormat, showBurndownButton, showSprintButton, @@ -153,6 +154,7 @@ const Board = ({ startedColumns: string[], completedColumns: string[], columnSorting: { [columnName: string]: { field: string, order: 'ascending' | 'descending' }[] }, + customFields: { name: string, type: 'boolean' | 'date' | 'number' | 'string' }[], dateFormat: string, showBurndownButton: boolean, showSprintButton: boolean, @@ -326,6 +328,8 @@ const Board = ({ > {column.filter(task => filterTask(task, taskFilter)).map((task, position) => i).join(' ')} style={{ @@ -45,10 +50,9 @@ const TaskItem = ({ task, position, dateFormat, vscode }: { ...provided.draggableProps.style }} > -
+
-
- { - 'tags' in task.metadata && - task.metadata.tags!.length > 0 && -
- {task.metadata.tags!.map(tag => { - return ( - - {tag} - - ); - })} + { + 'tags' in task.metadata && + task.metadata.tags!.length > 0 && +
+ {task.metadata.tags!.map(tag => { + return ( + + {tag} + + ); + })} +
+ } + { + customFields.map(customField => { + if (customField.name in task.metadata) { + return ( +
+ { + customField.type === 'boolean' + ? ( + <> + + {customField.name} + + ) : ( + <> + + + {task.metadata[customField.name]} + + + ) + } +
+ ); + } + return (<>); + }) + } + { + 'assigned' in task.metadata && + !!task.metadata.assigned && +
+ {task.metadata.assigned} +
+ } + { + createdDate && +
+ {createdDate} +
+ } + { + updatedDate && +
+ {updatedDate} +
+ } + { + startedDate && +
+ {startedDate} +
+ } + { + dueDate && +
+ {dueDate} +
+ } + { + completedDate && +
+ {completedDate} +
+ } + { + task.comments.length > 0 && +
+ {task.comments.length} +
+ } + { + task.subTasks.length > 0 && +
+ + {task.subTasks.filter(subTask => subTask.completed).length} / {task.subTasks.length} +
+ } + { + task.workload !== undefined && +
+ {task.workload} +
+ } + { + task.relations.length > 0 && + task.relations.map(relation => ( +
+ + + {relation.type} + {relation.task}
- } -
-
- { - 'assigned' in task.metadata && - !!task.metadata.assigned && -
- {task.metadata.assigned} -
- } - { - (createdDate || updatedDate) && -
i).join('\n')}> - i).join(' ')} - >{updatedDate || createdDate} -
- } - { - task.comments.length > 0 && -
- {task.comments.length} -
- } - { - task.subTasks.length > 0 && -
- - {task.subTasks.filter(subTask => subTask.completed).length} / {task.subTasks.length} -
- } - { - task.workload !== undefined && -
- {task.workload} -
- } - { - task.workload !== undefined && - task.progress !== undefined && -
- } -
+ )) + } + { + task.workload !== undefined && + task.progress !== undefined && +
+ }
); }} diff --git a/src/index.css b/src/index.css index e63ae9b..32ecf54 100644 --- a/src/index.css +++ b/src/index.css @@ -220,7 +220,33 @@ body.vscode-dark .kanbn-task { border-color: var(--vscode-activityBar-foreground); } -.kanbn-task-name { +.kanbn-task-data { + display: inline-block; + margin: 4px 8px 4px 0; + min-width: 30%; + opacity: 0.7; + font-size: 0.9em; + font-weight: normal; + font-style: italic; +} + +.kanbn-task-data-label { + opacity: 0.4; +} + +.kanbn-task-data-name { + display: block; +} + +.kanbn-task-data-tags { + display: block; +} + +.kanbn-task-data-relation { + display: block; +} + +.kanbn-task-data-name button { margin: 0; padding: 0; border: none; @@ -232,22 +258,13 @@ body.vscode-dark .kanbn-task { text-align: left; } -.kanbn-task-name:focus, .kanbn-task-name:hover { +.kanbn-task-data-name button:focus, +.kanbn-task-data-name button:hover { border: none; outline: none; text-decoration: underline; } -.kanbn-task-data { - display: inline-block; - margin: 4px 8px 4px 0; - min-width: 30%; - opacity: 0.7; - font-size: 0.9em; - font-weight: normal; - font-style: italic; -} - .kanbn-task div .codicon { position: relative; top: 1px; @@ -291,7 +308,7 @@ body.vscode-dark .kanbn-task { background-color: #f42; } -.kanbn-task-overdue { +.kanbn-task-overdue .kanbn-task-data-due { color: #f22 !important; } @@ -305,6 +322,30 @@ body.vscode-dark .kanbn-task { transition: width .5s ease-in-out; } +.kanbn-task-data-updated { + display: none; +} + +.kanbn-task-data-started { + display: none; +} + +.kanbn-task-data-due { + display: none; +} + +.kanbn-task-data-completed { + display: none; +} + +.kanbn-task-data-comments { + display: none; +} + +.kanbn-task-data-relation { + display: none; +} + /*----------------------------------------------------------------------------- Task editor styles -----------------------------------------------------------------------------*/