Extract task form into a shared macro for create and edit dialogs

Amolith created

Change summary

src/cmd/webui/task/mod.rs   |  9 ++++
src/cmd/webui/task/views.rs |  4 ++
templates/base.html         | 67 +----------------------------------
templates/macros.html       | 74 +++++++++++++++++++++++++++++++++++++++
templates/task.html         | 55 +---------------------------
5 files changed, 91 insertions(+), 118 deletions(-)

Detailed changes

src/cmd/webui/task/mod.rs 🔗

@@ -94,6 +94,13 @@ pub(in crate::cmd::webui) async fn task_handler(
                 .collect(),
         };
 
+        let edit_heading = format!("Edit {}", task_view.short_id);
+        let edit_form_action = format!(
+            "/projects/{}/tasks/{}",
+            store.project_name(),
+            task_view.full_id
+        );
+
         Ok(TaskTemplate {
             all_projects,
             active_project: Some(name),
@@ -102,6 +109,8 @@ pub(in crate::cmd::webui) async fn task_handler(
             blockers_open,
             blockers_resolved,
             subtasks,
+            edit_heading,
+            edit_form_action,
         })
     })
     .await;

src/cmd/webui/task/views.rs 🔗

@@ -46,4 +46,8 @@ pub(super) struct TaskTemplate {
     pub(super) blockers_open: Vec<BlockerRef>,
     pub(super) blockers_resolved: Vec<BlockerRef>,
     pub(super) subtasks: Vec<TaskRow>,
+    /// Pre-built heading for the edit dialog, e.g. "Edit td-XXXXXXX".
+    pub(super) edit_heading: String,
+    /// Pre-built form action URL for the edit dialog.
+    pub(super) edit_form_action: String,
 }

templates/base.html 🔗

@@ -1,3 +1,4 @@
+{% import "macros.html" as macros %}
 <!DOCTYPE html>
 <html lang="en">
 <head>
@@ -54,71 +55,7 @@
     </button>
   </div>
 
-  <!-- New task dialog -->
-  <dialog id="dlg-new-task" closedby="any">
-    <form method="post" id="form-new-task">
-      <header>
-        <h3>New task</h3>
-      </header>
-      <div class="vstack">
-        <div data-field>
-          <label for="nt-project">Project</label>
-          <select id="nt-project" aria-label="Project" required>
-            <option value=""{% if active_project.is_none() %} selected{% endif %}>Select project…</option>
-            {% for p in all_projects %}
-            <option value="{{ p }}"{% if active_project.as_deref() == Some(p.as_str()) %} selected{% endif %}>{{ p }}</option>
-            {% endfor %}
-          </select>
-        </div>
-        <label data-field>
-          Title
-          <input type="text" id="nt-title" name="title" required>
-        </label>
-        <label data-field>
-          Description
-          <textarea id="nt-desc" name="description" rows="3"></textarea>
-        </label>
-        <div class="hstack gap-2">
-          <div data-field>
-            <label for="nt-type">Type</label>
-            <select id="nt-type" name="task_type">
-              <option value="task" selected>Task</option>
-              <option value="bug">Bug</option>
-              <option value="feature">Feature</option>
-            </select>
-          </div>
-          <div data-field>
-            <label for="nt-priority">Priority</label>
-            <select id="nt-priority" name="priority">
-              <option value="high">High</option>
-              <option value="medium" selected>Medium</option>
-              <option value="low">Low</option>
-            </select>
-          </div>
-          <div data-field>
-            <label for="nt-effort">Effort</label>
-            <select id="nt-effort" name="effort">
-              <option value="low">Low</option>
-              <option value="medium" selected>Medium</option>
-              <option value="high">High</option>
-            </select>
-          </div>
-        </div>
-        <label data-field>
-          Labels <small class="text-light">(comma-separated)</small>
-          <input type="text" name="labels" placeholder="frontend, urgent">
-        </label>
-        <label data-field>
-          Parent task ID <small class="text-light">(optional)</small>
-          <input type="text" name="parent" placeholder="td-XXXXXXX">
-        </label>
-      </div>
-      <footer>
-        <button type="button" commandfor="dlg-new-task" command="close" class="outline">Cancel</button>
-        <button type="submit">Create</button>
-      </footer>
-    </form>
-  </dialog>
+  {% call macros::task_form_dialog("dlg-new-task", "New task", "", "Create", "create", all_projects, active_project, "nt", "", "", "task", "medium", "medium", "", "") %}{% endcall %}
 
   <!-- New project dialog -->
   <dialog id="dlg-new-project" closedby="any">

templates/macros.html 🔗

@@ -132,3 +132,77 @@
   {% endif %}
   {% endif %}
 {% endmacro %}
+
+{% macro task_form_dialog(dialog_id, heading, form_action, submit_label, mode, all_projects, active_project, prefix, title, description, task_type, priority, effort, parent, labels) %}
+<dialog id="{{ dialog_id }}" closedby="any">
+  <form method="post" action="{{ form_action }}" {% if mode == "create" %}id="form-new-task"{% endif %}>
+    <header>
+      <h3>{{ heading }}</h3>
+    </header>
+    <div class="vstack">
+      {% if mode == "create" %}
+      <div data-field>
+        <label for="{{ prefix }}-project">Project</label>
+        <select id="{{ prefix }}-project" aria-label="Project" required>
+          <option value=""{% if active_project.is_none() %} selected{% endif %}>Select project…</option>
+          {% for p in all_projects %}
+          <option value="{{ p }}"{% if active_project.as_deref() == Some(p.as_str()) %} selected{% endif %}>{{ p }}</option>
+          {% endfor %}
+        </select>
+      </div>
+      {% endif %}
+      <label data-field>
+        Title
+        <input type="text" id="{{ prefix }}-title" name="title" value="{{ title }}" required>
+      </label>
+      <label data-field>
+        Description
+        <textarea id="{{ prefix }}-desc" name="description" rows="{% if mode == "edit" %}6{% else %}3{% endif %}">{{ description }}</textarea>
+      </label>
+      <div class="hstack gap-2">
+        <div data-field>
+          <label for="{{ prefix }}-type">Type</label>
+          <select id="{{ prefix }}-type" name="task_type">
+            <option value="task"{% if task_type == "task" %} selected{% endif %}>Task</option>
+            <option value="bug"{% if task_type == "bug" %} selected{% endif %}>Bug</option>
+            <option value="feature"{% if task_type == "feature" %} selected{% endif %}>Feature</option>
+          </select>
+        </div>
+        <div data-field>
+          <label for="{{ prefix }}-priority">Priority</label>
+          <select id="{{ prefix }}-priority" name="priority">
+            <option value="high"{% if priority == "high" %} selected{% endif %}>High</option>
+            <option value="medium"{% if priority == "medium" %} selected{% endif %}>Medium</option>
+            <option value="low"{% if priority == "low" %} selected{% endif %}>Low</option>
+          </select>
+        </div>
+        <div data-field>
+          <label for="{{ prefix }}-effort">Effort</label>
+          <select id="{{ prefix }}-effort" name="effort">
+            <option value="low"{% if effort == "low" %} selected{% endif %}>Low</option>
+            <option value="medium"{% if effort == "medium" %} selected{% endif %}>Medium</option>
+            <option value="high"{% if effort == "high" %} selected{% endif %}>High</option>
+          </select>
+        </div>
+      </div>
+      {% if mode == "create" %}
+      <label data-field>
+        Labels <small class="text-light">(comma-separated)</small>
+        <input type="text" id="{{ prefix }}-labels" name="labels" value="{{ labels }}" placeholder="frontend, urgent">
+      </label>
+      {% endif %}
+      <label data-field>
+        Parent task ID <small class="text-light">{% if mode == "edit" %}(leave empty to clear){% else %}(optional){% endif %}</small>
+        <input type="text" id="{{ prefix }}-parent" name="parent" value="{{ parent }}" placeholder="td-XXXXXXX" aria-label="Parent task ID">
+      </label>
+      {% if mode == "edit" %}
+      <input type="hidden" name="_parent_present" value="1">
+      {% endif %}
+    </div>
+    <footer>
+      <button type="button" commandfor="{{ dialog_id }}" command="close" class="outline">Cancel</button>
+      <button type="submit">{{ submit_label }}</button>
+    </footer>
+  </form>
+</dialog>
+{% endmacro %}

templates/task.html 🔗

@@ -1,4 +1,5 @@
 {% extends "base.html" %}
+{% import "macros.html" as macros %}
 
 {% block title %}{{ task.title }} — td{% endblock %}
 
@@ -188,57 +189,5 @@
 </article>
 {% endif %}
 
-<!-- Edit task dialog -->
-<dialog id="dlg-edit-task" closedby="any">
-  <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}">
-    <header>
-      <h3>Edit {{ task.short_id }}</h3>
-    </header>
-    <div class="vstack">
-      <label data-field>
-        Title
-        <input type="text" name="title" value="{{ task.title }}" required>
-      </label>
-      <label data-field>
-        Description
-        <textarea name="description" rows="6">{{ task.description_raw }}</textarea>
-      </label>
-      <div class="hstack gap-2">
-        <div data-field>
-          <label for="edit-type">Type</label>
-          <select id="edit-type" name="task_type">
-            <option value="task"{% if task.task_type == "task" %} selected{% endif %}>Task</option>
-            <option value="bug"{% if task.task_type == "bug" %} selected{% endif %}>Bug</option>
-            <option value="feature"{% if task.task_type == "feature" %} selected{% endif %}>Feature</option>
-          </select>
-        </div>
-        <div data-field>
-          <label for="edit-priority">Priority</label>
-          <select id="edit-priority" name="priority">
-            <option value="high"{% if task.priority == "high" %} selected{% endif %}>High</option>
-            <option value="medium"{% if task.priority == "medium" %} selected{% endif %}>Medium</option>
-            <option value="low"{% if task.priority == "low" %} selected{% endif %}>Low</option>
-          </select>
-        </div>
-        <div data-field>
-          <label for="edit-effort">Effort</label>
-          <select id="edit-effort" name="effort">
-            <option value="low"{% if task.effort == "low" %} selected{% endif %}>Low</option>
-            <option value="medium"{% if task.effort == "medium" %} selected{% endif %}>Medium</option>
-            <option value="high"{% if task.effort == "high" %} selected{% endif %}>High</option>
-          </select>
-        </div>
-      </div>
-      <label data-field>
-        Parent task ID <small class="text-light">(leave empty to clear)</small>
-        <input type="text" name="parent" value="{{ task.parent_id }}" placeholder="td-XXXXXXX" aria-label="Parent task ID">
-      </label>
-      <input type="hidden" name="_parent_present" value="1">
-    </div>
-    <footer>
-      <button type="button" commandfor="dlg-edit-task" command="close" class="outline">Cancel</button>
-      <button type="submit">Save</button>
-    </footer>
-  </form>
-</dialog>
+{% call macros::task_form_dialog("dlg-edit-task", edit_heading, edit_form_action, "Save", "edit", all_projects, active_project, "edit", task.title, task.description_raw, task.task_type, task.priority, task.effort, task.parent_id, "") %}{% endcall %}
 {% endblock %}