From 1e876e60eebe8d0826165e8bb1e22edc6fc55637 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Mon, 6 May 2024 17:26:20 +0200 Subject: [PATCH] Task lists WIP: * front-end --- .../plugins/tasks/muc-task-list-view.js | 5 ++ .../custom/plugins/tasks/muc-task-view.js | 72 ++++++++++++++- .../plugins/tasks/styles/muc-task-lists.scss | 2 +- .../plugins/tasks/styles/muc-tasks.scss | 31 +++++++ conversejs/custom/plugins/tasks/task-list.js | 9 +- conversejs/custom/plugins/tasks/task.js | 10 +++ .../plugins/tasks/templates/muc-task-list.js | 2 +- .../plugins/tasks/templates/muc-task.js | 88 ++++++++++++++----- conversejs/loc.keys.js | 4 +- languages/en.yml | 2 + 10 files changed, 194 insertions(+), 31 deletions(-) create mode 100644 conversejs/custom/plugins/tasks/styles/muc-tasks.scss diff --git a/conversejs/custom/plugins/tasks/muc-task-list-view.js b/conversejs/custom/plugins/tasks/muc-task-list-view.js index a43de75e..7aa12056 100644 --- a/conversejs/custom/plugins/tasks/muc-task-list-view.js +++ b/conversejs/custom/plugins/tasks/muc-task-list-view.js @@ -22,6 +22,11 @@ export default class MUCTaskListView extends CustomElement { } this.listenTo(this.model, 'change', () => this.requestUpdate()) + + // We must also listen for new tasks + // FIXME: is there a way to only refresh if the task is part of this list? + this.listenTo(this.model.collection.chatroom.tasks, 'add', () => this.requestUpdate()) + this.listenTo(this.model.collection.chatroom.tasks, 'remove', () => this.requestUpdate()) } render () { diff --git a/conversejs/custom/plugins/tasks/muc-task-view.js b/conversejs/custom/plugins/tasks/muc-task-view.js index e336366f..1b466d82 100644 --- a/conversejs/custom/plugins/tasks/muc-task-view.js +++ b/conversejs/custom/plugins/tasks/muc-task-view.js @@ -1,15 +1,20 @@ import { CustomElement } from 'shared/components/element.js' import { api } from '@converse/headless/core' import { tplMucTask } from './templates/muc-task' +import { __ } from 'i18n' + +import './styles/muc-tasks.scss' export default class MUCTaskView extends CustomElement { static get properties () { return { - model: { type: Object, attribute: true } + model: { type: Object, attribute: true }, + edit: { type: Boolean, attribute: false } } } async initialize () { + this.edit = false if (!this.model) { return } @@ -18,7 +23,70 @@ export default class MUCTaskView extends CustomElement { } render () { - return tplMucTask(this.model) + return tplMucTask(this, this.model) + } + + async saveTask (ev) { + ev?.preventDefault?.() + + const name = ev.target.name.value.trim() + + if ((name ?? '') === '') { return } + + try { + this.querySelectorAll('input[type=submit]').forEach(el => { + el.setAttribute('disabled', true) + el.classList.add('disabled') + }) + + const task = this.model + task.set('name', name) + task.set('description', ev.target.description.value.trim()) + await task.saveItem() + + this.edit = false + } catch (err) { + console.error(err) + } finally { + this.querySelectorAll('input[type=submit]').forEach(el => { + el.removeAttribute('disabled') + el.classList.remove('disabled') + }) + } + } + + async deleteTask (ev) { + ev?.preventDefault?.() + + // eslint-disable-next-line no-undef + const i18nConfirmDelete = __(LOC_task_delete_confirm) + + // FIXME: when tasks are in a modal, api.confirm replaces the modal. This is not ok. + // const result = await api.confirm(i18nConfirmDelete) + const result = confirm(i18nConfirmDelete) + if (!result) { return } + + try { + await this.model.deleteItem() + } catch (err) { + api.alert( + 'error', __('Error'), [__('Error')] + ) + } + } + + async toggleEdit () { + this.edit = !this.edit + if (this.edit) { + await this.updateComplete + const input = this.querySelector('.task-name input[name="name"]') + if (input) { + input.focus() + // Placing cursor at the end: + input.selectionStart = input.value.length + input.selectionEnd = input.selectionStart + } + } } } diff --git a/conversejs/custom/plugins/tasks/styles/muc-task-lists.scss b/conversejs/custom/plugins/tasks/styles/muc-task-lists.scss index 74f549d7..de0787c1 100644 --- a/conversejs/custom/plugins/tasks/styles/muc-task-lists.scss +++ b/conversejs/custom/plugins/tasks/styles/muc-task-lists.scss @@ -7,7 +7,7 @@ livechat-converse-muc-task-list { width: 100%; - .task-list-description { + .task-list-line { border: 1px solid var(--chatroom-head-bg-color); display: flex; flex-flow: row nowrap; diff --git a/conversejs/custom/plugins/tasks/styles/muc-tasks.scss b/conversejs/custom/plugins/tasks/styles/muc-tasks.scss new file mode 100644 index 00000000..845a556b --- /dev/null +++ b/conversejs/custom/plugins/tasks/styles/muc-tasks.scss @@ -0,0 +1,31 @@ +.conversejs { + livechat-converse-muc-task { + padding: 0; + width: 100%; + + .task-line { + border: 1px solid var(--chatroom-head-bg-color); + display: flex; + flex-flow: row nowrap; + justify-content: space-around; + padding: 0.25em; + column-gap: 0.25em; + width: 100%; + + .task-description { + flex-grow: 2; + white-space: pre-wrap; + } + + .task-action { + border: 0; + padding-left: 0.25em; + padding-right: 0.25em; + } + + form { + width: 100%; + } + } + } +} diff --git a/conversejs/custom/plugins/tasks/task-list.js b/conversejs/custom/plugins/tasks/task-list.js index 6b19829e..44ef8ffe 100644 --- a/conversejs/custom/plugins/tasks/task-list.js +++ b/conversejs/custom/plugins/tasks/task-list.js @@ -18,8 +18,8 @@ class ChatRoomTaskList extends Model { async saveItem () { console.log('Saving task list ' + this.get('id') + '...') - await this.collection.chatroom.taskManager.saveItem(this, { name }) - console.log('Task list ' + this.get('id') + ' created.') + await this.collection.chatroom.taskManager.saveItem(this) + console.log('Task list ' + this.get('id') + ' saved.') } async deleteItem () { @@ -36,7 +36,10 @@ class ChatRoomTaskList extends Model { data.list = this.get('id') if (!data.order) { - data.order = 1 + Math.max(...this.getTasks().map(t => t.get('order') ?? 0)) + data.order = 1 + Math.max( + 0, + ...(this.getTasks().map(t => t.get('order') ?? 0).filter(o => !isNaN(o))) + ) } console.log('Creating task ' + name + '...') diff --git a/conversejs/custom/plugins/tasks/task.js b/conversejs/custom/plugins/tasks/task.js index e081d3cd..bd77f3a0 100644 --- a/conversejs/custom/plugins/tasks/task.js +++ b/conversejs/custom/plugins/tasks/task.js @@ -8,6 +8,16 @@ import { Model } from '@converse/skeletor/src/model.js' */ class ChatRoomTask extends Model { idAttribute = 'id' + + async saveItem () { + console.log('Saving task ' + this.get('id') + '...') + await this.collection.chatroom.taskManager.saveItem(this) + console.log('Task ' + this.get('id') + ' saved.') + } + + async deleteItem () { + return this.collection.chatroom.taskManager.deleteItem(this) + } } export { diff --git a/conversejs/custom/plugins/tasks/templates/muc-task-list.js b/conversejs/custom/plugins/tasks/templates/muc-task-list.js index 06790dfa..86b24ba7 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task-list.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task-list.js @@ -12,7 +12,7 @@ export default function tplMucTaskList (el, tasklist) { // eslint-disable-next-line no-undef const i18nTaskListName = __(LOC_task_list_name) return html` -
+
${el.collapsed ? html` + +
` + : html` +
+
+ ${_tplTaskForm(task)} +
+ + +
+
+
` } -export function tplMucAddTaskForm (tasklistEl, _tasklist) { - const i18nOk = __('Ok') - const i18nCancel = __('Cancel') +function _tplTaskForm (task) { // eslint-disable-next-line no-undef const i18nTaskName = __(LOC_task_name) // eslint-disable-next-line no-undef const i18nTaskDesc = __(LOC_task_description) + return html`
+ + +
` +} + +export function tplMucAddTaskForm (tasklistEl, _tasklist) { + const i18nOk = __('Ok') + const i18nCancel = __('Cancel') + return html`
-
- - -
+ ${_tplTaskForm(undefined)}