diff --git a/docs/styles.md b/docs/styles.md index 394cf59..27d0f44 100644 --- a/docs/styles.md +++ b/docs/styles.md @@ -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` diff --git a/ext-src/KanbnTaskPanel.ts b/ext-src/KanbnTaskPanel.ts index 88355c8..2c62927 100644 --- a/ext-src/KanbnTaskPanel.ts +++ b/ext-src/KanbnTaskPanel.ts @@ -3,7 +3,10 @@ import * as vscode 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; } @@ -162,7 +176,10 @@ 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(); @@ -173,7 +190,11 @@ export default class KanbnTaskPanel { // 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(); @@ -258,6 +279,7 @@ export default class KanbnTaskPanel { index, task, tasks, + customFields: index.options.customFields ?? [], columnName: this._columnName, dateFormat: this._kanbn.getDateFormat(index), panelUuid: this._panelUuid, diff --git a/src/App.tsx b/src/App.tsx index a033a1f..c573c53 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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} diff --git a/src/TaskEditor.tsx b/src/TaskEditor.tsx index 675a998..8e502a4 100644 --- a/src/TaskEditor.tsx +++ b/src/TaskEditor.tsx @@ -53,11 +53,12 @@ const Markdown = 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, 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 }); } @@ -224,484 +237,521 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui handleChange, isSubmitting }) => ( - +

{editing ? 'Update task' : 'Create new task'} {dirty && *} - {editing && - { - [ - 'created' in task!.metadata ? `Created ${formatDate(task!.metadata.created, dateFormat)}` : null, - 'updated' in task!.metadata ? `Updated ${formatDate(task!.metadata.updated, dateFormat)}` : null - ].filter(i => i).join(', ') - } - }

- -
-
-
- -
{taskData.id}
- + {editing && } + {editing && } + +
+ {editing && + { + [ + 'created' in task!.metadata ? `Created ${formatDate(task!.metadata.created, dateFormat)}` : null, + 'updated' in task!.metadata ? `Updated ${formatDate(task!.metadata.updated, dateFormat)}` : null + ].filter(i => i).join(', ') + } + } +
+
+
+
-
- - + /> + +
{taskData.id}
+ +
+
+ +
-
-

Sub-tasks

- - {({ insert, remove, push }) => ( -
- {values.subTasks.length > 0 && values.subTasks.map((subTask, index) => ( -
-
- - -
-
- - -
-
- -
-
- ))} -
- -
-
- )} -
-
-
-

Relations

- - {({ insert, remove, push }) => ( -
- {values.relations.length > 0 && values.relations.map((relation, index) => ( -
-
- - -
-
- - {Object.keys(tasks).map(t => )} - - -
-
- -
-
- ))} -
- -
-
- )} -
-
-
-

Comments

- - {({ insert, remove, push }) => ( -
- {values.comments.length > 0 && values.comments.map((comment, index) => ( -
-
-
- { - editingComment === index - ? - - - - :
- - {comment.author || 'Anonymous'} -
- } -
-
- {formatDate(comment.date, dateFormat)} -
-
- - -
-
-
-
- { - editingComment === index - ? - - - - : - } -
-
-
- ))} -
- -
-
- )} -
-
+ + { + editingDescription + ? + : + } +
-
-
- {editing && } - {editing && } - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - - {({ insert, remove, push }) => ( -
- {( - 'tags' in values.metadata && - values.metadata.tags!.length > 0 - ) && values.metadata.tags!.map((tag, index) => ( -
-
- -
- +
+

Sub-tasks

+ + {({ insert, remove, push }) => ( +
+ {values.subTasks.length > 0 && values.subTasks.map((subTask, index) => ( +
+
+ + +
+
+ + +
+
+ +
+
+ ))} +
+ +
+
+ )} +
+
+
+

Relations

+ + {({ insert, remove, push }) => ( +
+ {values.relations.length > 0 && values.relations.map((relation, index) => ( +
+
+ + +
+
+ + {Object.keys(tasks).map(t => )} + + +
+
+ +
+
+ ))} +
+ +
+
+ )} +
+
+
+

Comments

+ + {({ insert, remove, push }) => ( +
+ {values.comments.length > 0 && values.comments.map((comment, index) => ( +
+
+
+ { + editingComment === index + ? + + + + :
+ + {comment.author || 'Anonymous'} +
+ } +
+
+ {formatDate(comment.date, dateFormat)}
+ +
+
+
+
+ { + editingComment === index + ? + + + + : + }
- ))} -
-
+ ))} +
+
- )} - -
+
+ )} +
- - +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ { + customFields.map(customField => ( +
+ + +
+ )) + } +
+ + + {({ insert, remove, push }) => ( +
+ {( + 'tags' in values.metadata && + values.metadata.tags!.length > 0 + ) && values.metadata.tags!.map((tag, index) => ( +
+
+ +
+ +
+
+ +
+
+ ))} +
+ +
+
+ )} +
+
+
+
+ )}
diff --git a/src/TaskItem.tsx b/src/TaskItem.tsx index 96932a0..e285383 100644 --- a/src/TaskItem.tsx +++ b/src/TaskItem.tsx @@ -102,7 +102,9 @@ const TaskItem = ({ task, columnName, customFields, position, dateFormat, vscode <> - {task.metadata[customField.name]} + {customField.type === 'date' + ? formatDate(task.metadata[customField.name], dateFormat) + : task.metadata[customField.name]} ) diff --git a/src/index.css b/src/index.css index 32ecf54..112f3b6 100644 --- a/src/index.css +++ b/src/index.css @@ -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 -----------------------------------------------------------------------------*/