modify checkbox text in notes
This commit is contained in:
+6
-1
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user