simple note creation and editing
This commit is contained in:
parent
f947999cd1
commit
6e0023497c
|
@ -21,6 +21,7 @@ packages = find:
|
|||
python_requires = >=3.10
|
||||
install_requires =
|
||||
jinja2
|
||||
python-multipart
|
||||
starlette
|
||||
uvicorn[standard]
|
||||
|
||||
|
|
|
@ -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"),
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
21
src/wikinotes/templates/body.html
Normal file
21
src/wikinotes/templates/body.html
Normal 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 %}
|
5
src/wikinotes/templates/edit.html
Normal file
5
src/wikinotes/templates/edit.html
Normal 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>
|
|
@ -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
|
||||
|
|
16
src/wikinotes/templates/note.html
Normal file
16
src/wikinotes/templates/note.html
Normal 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 %}
|
1
src/wikinotes/templates/paragraph.html
Normal file
1
src/wikinotes/templates/paragraph.html
Normal 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>
|
Loading…
Reference in a new issue