simple note creation and editing

This commit is contained in:
Thomas Lindner 2023-10-29 21:55:05 +01:00
parent f947999cd1
commit 6e0023497c
9 changed files with 180 additions and 18 deletions

View file

@ -21,6 +21,7 @@ packages = find:
python_requires = >=3.10
install_requires =
jinja2
python-multipart
starlette
uvicorn[standard]

View file

@ -1,3 +1,5 @@
from uuid import UUID, uuid4
from jinja2 import PackageLoader
from starlette.applications import Starlette
from starlette.requests import Request
@ -12,16 +14,134 @@ templates = Jinja2Templates(
directory="/nonexistent", # just a dummy, FileSystemLoader is not used
loader=PackageLoader("wikinotes"),
)
notes: dict[UUID, list[UUID]] = {}
paragraphs: dict[UUID, str] = {}
async def home(request: Request) -> Response:
return templates.TemplateResponse("home.html", context={"request": request})
only_body = request.headers.get("hx-request", "false") == "true"
return templates.TemplateResponse(
"home.html",
headers={
"HX-Push-Url": str(request.url_for("home")),
},
context={
"request": request,
"only_body": only_body,
},
)
async def create_note(request: Request) -> Response:
note_id = uuid4()
notes[note_id] = []
return templates.TemplateResponse(
"note.html",
headers={
"HX-Push-Url": str(request.url_for("note", note_id=note_id)),
},
context={
"request": request,
"only_body": True,
"note_id": note_id,
"paragraphs": [],
},
)
async def note(request: Request) -> Response:
note_id = request.path_params["note_id"]
only_body = request.headers.get("hx-request", "false") == "true"
return templates.TemplateResponse(
"note.html",
headers={
"HX-Push-Url": str(request.url_for("note", note_id=note_id)),
},
context={
"request": request,
"only_body": only_body,
"note_id": note_id,
"paragraphs": [(id, paragraphs[id]) for id in notes[note_id]],
},
)
async def create_paragraph(request: Request) -> Response:
note_id = request.path_params["note_id"]
paragraph_id = uuid4()
async with request.form() as form:
paragraphs[paragraph_id] = str(form["content"])
notes[note_id] = [paragraph_id]
return templates.TemplateResponse(
"paragraph.html",
context={
"request": request,
"note_id": note_id,
"paragraph_id": paragraph_id,
"content": paragraphs[paragraph_id],
},
)
async def paragraph(request: Request) -> Response:
note_id = request.path_params["note_id"]
paragraph_id = request.path_params["paragraph_id"]
if request.method == "PUT":
async with request.form() as form:
paragraphs[paragraph_id] = str(form["content"])
return templates.TemplateResponse(
"paragraph.html",
context={
"request": request,
"note_id": note_id,
"paragraph_id": paragraph_id,
"content": paragraphs[paragraph_id],
},
)
async def edit_paragraph(request: Request) -> Response:
note_id = request.path_params["note_id"]
paragraph_id = request.path_params["paragraph_id"]
return templates.TemplateResponse(
"edit.html",
context={
"request": request,
"note_id": note_id,
"paragraph_id": paragraph_id,
"content": paragraphs[paragraph_id],
},
)
app = Starlette(
debug=True,
routes=[
Route("/", home, name="home"),
Route(
"/note",
create_note,
name="create_note",
methods=["POST"],
),
Route("/note/{note_id:uuid}", note),
Route(
"/note/{note_id:uuid}/paragraph",
create_paragraph,
name="create_paragraph",
methods=["POST"],
),
Route(
"/note/{note_id:uuid}/paragraph/{paragraph_id:uuid}",
paragraph,
name="paragraph",
methods=["GET", "PUT"],
),
Route(
"/note/{note_id:uuid}/paragraph/{paragraph_id:uuid}/edit",
edit_paragraph,
name="edit_paragraph",
),
Mount("/static", app=StaticFiles(packages=["wikinotes"]), name="static"),
],
)

View file

@ -4,3 +4,11 @@ body {
nav {
--bs-emphasis-color-rgb: var(--bs-white-rgb);
}
main > p.editable {
min-height: 1em; /* ensure paragraph is always clickable */
border-radius: var(--bs-border-radius);
}
main > p.editable:hover {
background-color: var(--bs-primary-bg-subtle);
box-shadow: 0 0 0 calc(var(--bs-gutter-x) * .5) var(--bs-primary-bg-subtle);
}

View file

@ -7,22 +7,7 @@
<link href="{{ url_for("static", path="style.css") }}" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-sm fixed-top bg-primary bg-gradient">
<div class="container">
<a class="navbar-brand" href="{{ url_for("home") }}">WikiNotes</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#toolbar" aria-controls="toolbar"
aria-expanded="false" aria-label="toggle toolbar">
<span class="navbar-toggler-icon"></span>
</button>
<div id="toolbar" class="collapse navbar-collapse">
{% block nav %}{% endblock %}
</div>
</div>
</nav>
<main class="container">
{% block main %}{% endblock %}
</main>
{% block body %}{% endblock %}
<script src="{{ url_for("static", path="bootstrap.bundle.min.js") }}"></script>
<script src="{{ url_for("static", path="htmx.min.js") }}"></script>
</body>

View file

@ -0,0 +1,21 @@
{% if not only_body %}
{% extends "base.html" %}
{% endif %}
{% block body %}
<nav class="navbar navbar-expand-sm fixed-top bg-primary bg-gradient">
<div class="container">
<a class="navbar-brand" href="{{ url_for("home") }}" hx-get="{{ url_for("home") }}" hx-target="body">WikiNotes</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#toolbar" aria-controls="toolbar"
aria-expanded="false" aria-label="toggle toolbar">
<span class="navbar-toggler-icon"></span>
</button>
<div id="toolbar" class="collapse navbar-collapse">
{% block nav %}{% endblock %}
</div>
</div>
</nav>
<main class="container">
{% block main %}{% endblock %}
</main>
{% endblock %}

View file

@ -0,0 +1,5 @@
<form hx-put="{{ url_for("paragraph", note_id=note_id, paragraph_id=paragraph_id) }}" hx-target="this" hx-swap="outerHTML">
<textarea class="form-control mb-3" name="content" rows="10" autofocus>{{ content }}</textarea>
<button class="btn btn-primary">Submit</button>
<button class="btn" hx-get="{{ url_for("paragraph", note_id=note_id, paragraph_id=paragraph_id) }}">Cancel</button>
</form>

View file

@ -1,4 +1,9 @@
{% extends "base.html" %}
{% extends "body.html" %}
{% block nav %}
<div class="navbar-nav">
<a class="nav-link" href="#" hx-post="{{ url_for("create_note") }}" hx-target="body">New Note</a>
</div>
{% endblock %}
{% block main %}
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce fermentum arcu sit amet

View file

@ -0,0 +1,16 @@
{% extends "body.html" %}
{% block nav %}
<div class="navbar-nav">
<a class="nav-link" href="#" hx-post="{{ url_for("create_note") }}" hx-target="body">New Note</a>
</div>
{% endblock %}
{% block main %}
{% for paragraph_id, content in paragraphs %}
{% include "paragraph.html" %}
{% else %}
<form hx-post="{{ url_for("create_paragraph", note_id=note_id) }}" hx-target="this" hx-swap="outerHTML">
<textarea class="form-control mb-3" name="content" rows="10" autofocus></textarea>
<button class="btn btn-primary">Submit</button>
</form>
{% endfor%}
{% endblock %}

View file

@ -0,0 +1 @@
<p class="editable" title="Click to edit" hx-get="{{ url_for("edit_paragraph", note_id=note_id, paragraph_id=paragraph_id) }}" hx-target="this" hx-swap="outerHTML">{{ content }}</p>