modify checkbox text in notes

This commit is contained in:
2026-11-01 20:04:33 +01:00
parent 7675acd81e
commit e55af30644
7 changed files with 77 additions and 8 deletions
+6 -1
View File
@@ -167,7 +167,8 @@ login.
Logged-in users manage their own private short notes at `/notes`. Each note has
a title, a text body and an optional checklist. Unchecked items are shown first;
when an item is checked it is saved as done, moves to the bottom of the checklist
and is displayed with a line through it.
and is displayed with a line through it. Checklist item text can be edited at any
time.
Notes are stored in `main_app.db` in the `note` and `note_item` tables. Apply
the users database migration once (the production launcher does this
@@ -184,6 +185,10 @@ The main app (`:8000`) listens on all interfaces; the sensors API (`:8001`)
listens on `127.0.0.1` by default and is open to every local user without
authentication.
Do not run the main app on the sensors port (`8001`) while `SENSORS_API_URL` is
unset: it defaults to `http://localhost:8001`, so the main app would proxy API
requests to itself. Both launchers refuse to start in that configuration.
### Development
```bash
+14
View File
@@ -174,6 +174,20 @@ def create_app():
user_db.session.commit()
return _note_redirect(note.id)
@app.route(
"/notes/<int:note_id>/items/<int:item_id>", methods=["POST"]
)
@login_required
def notes_update_item(note_id, item_id):
note = _note_or_404(note_id)
item = NoteItem.query.filter_by(id=item_id, note_id=note.id).first_or_404()
text = request.form.get("text", "").strip()
if text:
item.text = text[:255]
note.touch()
user_db.session.commit()
return _note_redirect(note.id)
@app.route(
"/notes/<int:note_id>/items/<int:item_id>/toggle", methods=["POST"]
)
+10
View File
@@ -38,6 +38,16 @@ esac
PORT="${PORT:-$DEFAULT_PORT}"
HOST="${HOST:-$DEFAULT_HOST}"
# Prevent the main app from proxying to itself. Without an explicit
# SENSORS_API_URL the client targets localhost:8001, so main must not listen
# there as well.
if [ "$APP" = "main" ] && [ -z "${SENSORS_API_URL:-}" ] && [ "$PORT" = "8001" ]; then
echo "Refusing to start main on port 8001: SENSORS_API_URL would default to" >&2
echo "http://localhost:8001 and the app would call itself recursively." >&2
echo "Use another port (for example 8000) or set SENSORS_API_URL explicitly." >&2
exit 2
fi
if [ "$APP" = "main" ]; then
SECRET_FILE=".secret_key"
if [ -z "${SECRET_KEY:-}" ]; then
+10
View File
@@ -38,6 +38,16 @@ esac
PORT="${PORT:-$DEFAULT_PORT}"
HOST="${HOST:-$DEFAULT_HOST}"
# Prevent the main app from proxying to itself. Without an explicit
# SENSORS_API_URL the client targets localhost:8001, so main must not listen
# there as well.
if [ "$APP" = "main" ] && [ -z "${SENSORS_API_URL:-}" ] && [ "$PORT" = "8001" ]; then
echo "Refusing to start main on port 8001: SENSORS_API_URL would default to" >&2
echo "http://localhost:8001 and the app would call itself recursively." >&2
echo "Use another port (for example 8000) or set SENSORS_API_URL explicitly." >&2
exit 2
fi
if [ -x ".venv/bin/python" ]; then
PYTHON=".venv/bin/python"
else
+19 -3
View File
@@ -343,7 +343,7 @@ textarea {
padding: 0.15em 0;
}
.check-form {
flex: 1;
flex: 0 0 auto;
flex-direction: row;
align-items: center;
gap: 0.6em;
@@ -354,8 +354,24 @@ textarea {
margin: 0;
accent-color: var(--mc-yellow);
}
.item-text { flex: 1; overflow-wrap: anywhere; }
.checklist li.done .item-text {
.item-edit-form {
flex: 1;
flex-direction: row;
align-items: center;
gap: 0.4em;
margin: 0;
}
.item-edit-form input { flex: 1; min-width: 0; }
.item-edit-form button {
background: var(--mc-dim);
color: var(--mc-black);
box-shadow: none;
padding: 0.1em 0.4em;
font-size: 0.85em;
letter-spacing: 0;
}
.item-edit-form button:hover { background: var(--mc-cyan); }
.checklist li.done .item-edit-form input {
text-decoration: line-through;
color: var(--mc-dim);
}
+7 -1
View File
@@ -61,9 +61,15 @@
{% if item.is_done %}checked{% endif %}
onchange="this.form.submit()"
aria-label="Mark '{{ item.text }}' as done">
<span class="item-text">{{ item.text }}</span>
<noscript><button type="submit">[ Update ]</button></noscript>
</form>
<form method="post"
action="{{ url_for('notes_update_item', note_id=note.id, item_id=item.id) }}"
class="item-edit-form">
<input type="text" name="text" value="{{ item.text }}"
maxlength="255" required aria-label="Checklist item text">
<button type="submit">[ Save ]</button>
</form>
<form method="post"
action="{{ url_for('notes_delete_item', note_id=note.id, item_id=item.id) }}"
class="item-delete-form">
+11 -3
View File
@@ -235,11 +235,18 @@ class MainAppTest(unittest.TestCase):
)
with self.main_app.app_context():
first, second = (
first, _second = (
NoteItem.query.filter_by(note_id=note_id).order_by(NoteItem.id).all()
)
first_id = first.id
self.assertEqual(
self.client.post(
f"/notes/{note_id}/items/{first_id}",
data={"text": "renamed first item"},
).status_code,
302,
)
self.assertEqual(
self.client.post(
f"/notes/{note_id}/items/{first_id}/toggle",
@@ -253,7 +260,8 @@ class MainAppTest(unittest.TestCase):
self.assertEqual(page.status_code, 200)
self.assertIn('class="done"', html)
self.assertIn('value="Test title"', html)
self.assertLess(html.index("second item"), html.index("first item"))
self.assertIn('value="renamed first item"', html)
self.assertLess(html.index("second item"), html.index("renamed first item"))
with self.main_app.app_context():
user_db.session.expire_all()
@@ -264,7 +272,7 @@ class MainAppTest(unittest.TestCase):
self.assertFalse(items[1].is_done)
self.assertEqual(
[item.text for item in items[0].note.ordered_items],
["second item", "first item"],
["second item", "renamed first item"],
)