Browse Source

FEATURE: Edit ToDo text and Due fields on ToDo modal view

master
parent
commit
3c5794845a
  1. 77
      pages/category.html
  2. 48
      src/db/todo.go
  3. 4
      src/db/verification.go
  4. 4
      src/server/endpoints.go

77
pages/category.html

@ -57,14 +57,28 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p><strong>Text:</strong> <span id="modalTodoText"></span></p>
<p><strong>Created:</strong> <span id="modalTodoCreated"></span></p>
<p><strong>Due:</strong> <span id="modalTodoDue"></span></p>
<p><strong>Completion time:</strong> <span id="modalTodoCompletionTime"></span></p>
<img id="modalTodoImage" src="" class="img-fluid" style="display: none;">
<div>
<strong>Text:</strong>
<span id="modalTodoTextDisplay"></span>
<input type="text" id="modalTodoTextInput" class="form-control" style="display: none;">
</div>
<div>
<strong>Created:</strong> <span id="modalTodoCreated"></span>
</div>
<div>
<strong>Due:</strong>
<span id="modalTodoDueDisplay"></span>
<input type="date" id="modalTodoDueInput" class="form-control" style="display: none;">
</div>
<div>
<strong>Completion time:</strong> <span id="modalTodoCompletionTime"></span>
</div>
<img id="modalTodoImage" class="img-fluid" style="display: none;">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="editButton" style="display: none;" onclick="toggleEditMode(true)">Edit</button>
<button type="button" class="btn btn-success" id="saveButton" style="display: none;" onclick="saveEditedTodo()">Save</button>
</div>
</div>
</div>
@ -128,7 +142,7 @@
<tbody class="text-break">
{{ range .Todos }}
{{ if not .IsDone }}
<tr onclick="openTodoModal('{{.ID}}', '{{.Text}}', '{{.TimeCreated}}', '{{.Due}}', null, '{{ printf "%s" .Image }}');" draggable="true" id="todo-{{.ID}}" ondragstart="dragStart(event);">
<tr onclick="openTodoModal('{{.ID}}', '{{.Text}}', '{{.TimeCreated}}', '{{.Due}}', null, '{{ printf "%s" .Image }}', true);" draggable="true" id="todo-{{.ID}}" ondragstart="dragStart(event);">
{{ if not .Image }}
<!-- Display transparent white pixel -->
<td><img class="todo-image" src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' width="64px" height="64px"></td>
@ -164,7 +178,7 @@
<tbody class="text-break">
{{ range .Todos }}
{{ if .IsDone }}
<tr onclick="openTodoModal('{{.ID}}', '{{.Text}}', '{{.TimeCreated}}', '{{.Due}}', '{{.CompletionTime}}', '{{ printf "%s" .Image }}');">
<tr onclick="openTodoModal('{{.ID}}', '{{.Text}}', '{{.TimeCreated}}', '{{.Due}}', '{{.CompletionTime}}', '{{ printf "%s" .Image }}', false);">
{{ if not .Image }}
<!-- Display transparent white pixel -->
<td><img src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' width="64px" height="64px"></td>
@ -262,19 +276,22 @@ async function drop(event) {
// Update todo's group ID
let result = await updateTodo(todoId, {
text: draggedTodo.getElementsByClassName("todo-text")[0].innerText,
groupId: Number(targetGroupId),
dueUnix: Number(draggedTodo.getElementsByClassName("todo-due-unix")[0].innerText),
image: Array.from(draggedTodo.getElementsByClassName("todo-image")[0].src, char => char.charCodeAt(0))
});
window.location.reload();
}
function openTodoModal(id, text, created, due, completionTime, image) {
document.getElementById('modalTodoText').innerText = text;
let viewedTodoID;
function openTodoModal(id, text, created, due, completionTime, image, editable) {
viewedTodoID = id;
document.getElementById('modalTodoTextDisplay').innerText = text;
document.getElementById('modalTodoTextInput').value = text;
document.getElementById('modalTodoCreated').innerText = created;
document.getElementById('modalTodoDue').innerText = due;
document.getElementById('modalTodoDueDisplay').innerText = due;
document.getElementById('modalTodoDueInput').value = due;
document.getElementById('modalTodoCompletionTime').innerText = completionTime;
let img = document.getElementById('modalTodoImage');
@ -285,10 +302,42 @@ function openTodoModal(id, text, created, due, completionTime, image) {
img.style.display = 'none';
}
let editButton = document.getElementById("editButton");
if (editable) {
// Show "Edit" button
editButton.style.display = "inline";
} else {
editButton.style.display = "none";
}
const todoModal = new bootstrap.Modal(document.getElementById('todoModal'));
todoModal.show();
}
async function saveEditedTodo() {
const updatedText = document.getElementById('modalTodoTextInput').value;
const updatedDue = document.getElementById('modalTodoDueInput').value;
document.getElementById('modalTodoTextDisplay').innerText = updatedText;
document.getElementById('modalTodoDueDisplay').innerText = updatedDue;
const updatedDueUnix = Date.parse(updatedDue) / 1000;
toggleEditMode(false);
await updateTodo(viewedTodoID, {"text":updatedText, "dueUnix":updatedDueUnix, "isDone":false});
window.location.reload();
}
function toggleEditMode(isEditing) {
document.getElementById('modalTodoTextDisplay').style.display = isEditing ? 'none' : 'inline';
document.getElementById('modalTodoTextInput').style.display = isEditing ? 'inline' : 'none';
document.getElementById('modalTodoDueDisplay').style.display = isEditing ? 'none' : 'inline';
document.getElementById('modalTodoDueInput').style.display = isEditing ? 'inline' : 'none';
document.getElementById('editButton').style.display = isEditing ? 'none' : 'inline';
document.getElementById('saveButton').style.display = isEditing ? 'inline' : 'none';
}
document.addEventListener('DOMContentLoaded', async function() {
document.getElementById("newTodoText").focus();
@ -331,7 +380,7 @@ document.addEventListener('DOMContentLoaded', async function() {
// Make a request
let response = await postNewTodo(
{text: newTodoText, groupId: Number(groupId), dueUnix: Number(dueTimeStamp), image: canvasImage}
{"text": newTodoText, "groupId": Number(groupId), "dueUnix": Number(dueTimeStamp), "image": canvasImage}
);
if (response.ok) {
location.reload();

48
src/db/todo.go

@ -19,7 +19,9 @@
package db
import (
"bytes"
"database/sql"
"strings"
"time"
)
@ -157,6 +159,52 @@ func (db *DB) UpdateTodo(todoID uint64, updatedTodo Todo) error {
return err
}
// Updates all changed fields which are not nil-valued in a ToDo and retains all unchanged ones
func (db *DB) UpdateTodoSoft(todoID uint64, updatedTodo Todo) error {
originalTodo, err := db.GetTodo(todoID)
if err != nil {
return err
}
args := []interface{}{}
updates := []string{}
if (updatedTodo.GroupID != originalTodo.GroupID) && updatedTodo.GroupID != 0 {
updates = append(updates, "group_id=?")
args = append(args, updatedTodo.GroupID)
}
if (updatedTodo.DueUnix != originalTodo.DueUnix) && updatedTodo.DueUnix != 0 {
updates = append(updates, "due_unix=?")
args = append(args, updatedTodo.DueUnix)
}
if (updatedTodo.Text != originalTodo.Text) && updatedTodo.Text != "" {
updates = append(updates, "text=?")
args = append(args, updatedTodo.Text)
}
if updatedTodo.IsDone != originalTodo.IsDone {
updates = append(updates, "is_done=?")
args = append(args, updatedTodo.IsDone)
}
if (updatedTodo.CompletionTimeUnix != originalTodo.CompletionTimeUnix) && updatedTodo.CompletionTimeUnix != 0 {
updates = append(updates, "completion_time_unix=?")
args = append(args, updatedTodo.CompletionTimeUnix)
}
if !bytes.Equal(updatedTodo.Image, originalTodo.Image) && updatedTodo.Image != nil {
updates = append(updates, "image=?")
args = append(args, updatedTodo.Image)
}
if len(updates) == 0 {
return nil
}
query := "UPDATE todos SET " + strings.Join(updates, ", ") + " WHERE id=?"
args = append(args, todoID)
_, err = db.Exec(query, args...)
return err
}
// Searches and retrieves TODO groups created by the user
func (db *DB) GetAllUserTodoGroups(email string) ([]*TodoGroup, error) {
var todoGroups []*TodoGroup

4
src/db/verification.go

@ -8,8 +8,8 @@ type Verification struct {
ID uint64 `json:"id"`
Email string `json:"email"`
Code string `json:"code"`
IssuedUnix uint64 `json:"issued_unix"`
LifeSeconds uint64 `json:"life_seconds"`
IssuedUnix uint64 `json:"issuedUnix"`
LifeSeconds uint64 `json:"lifeSeconds"`
}
func NewVerification(email string, code string, issuedUnix uint64, lifeSeconds uint64) *Verification {

4
src/server/endpoints.go

@ -421,8 +421,8 @@ func (s *Server) EndpointTodoUpdate(w http.ResponseWriter, req *http.Request) {
return
}
// Update. (Creation date, owner username and an ID do not change)
err = s.db.UpdateTodo(todoID, updatedTodo)
// Update
err = s.db.UpdateTodoSoft(todoID, updatedTodo)
if err != nil {
logger.Warning("[Server] Failed to update TODO: %s", err)
http.Error(w, "Failed to update", http.StatusBadRequest)

Loading…
Cancel
Save