+11
-2
src/main.py
+11
-2
src/main.py
···
3
3
4
4
from aiohttp.client import ClientSession
5
5
from flask import Flask, g, session, redirect, render_template, request, url_for
6
-
from flask_htmx import HTMX
6
+
from flask_htmx import HTMX, make_response as htmx_reponse
7
7
from typing import Any
8
8
9
9
from .atproto import (
···
167
167
kv.set(user.did, json.dumps(record))
168
168
169
169
if htmx:
170
-
return render_template("_editor_profile.html", profile=record)
170
+
return htmx_reponse(
171
+
render_template("_editor_profile.html", profile=record),
172
+
reswap="outerHTML",
173
+
)
171
174
172
175
return redirect(url_for("page_editor"), 303)
173
176
···
212
215
if success:
213
216
kv = KV(app, app.logger, "links_from_did")
214
217
kv.set(user.did, json.dumps(record))
218
+
219
+
if htmx:
220
+
return htmx_reponse(
221
+
render_template("_editor_links.html", links=record["links"]),
222
+
reswap="outerHTML",
223
+
)
215
224
216
225
return redirect(url_for("page_editor"), 303)
217
226
+28
-9
src/static/style.css
+28
-9
src/static/style.css
···
5
5
--color-background-secondary: #eee;
6
6
--color-border: #bbb;
7
7
--color-border-secondary: #ddd;
8
+
--color-success: #080;
8
9
}
9
10
10
11
@media (prefers-color-scheme: dark) {
···
12
13
--color-background-secondary: #333;
13
14
--color-border: #555;
14
15
--color-border-secondary: #333;
16
+
--color-success: #af2;
15
17
}
16
18
}
17
19
···
69
71
padding: 0.25em;
70
72
}
71
73
72
-
input[type="submit"]:last-child {
74
+
button.submit:last-of-type,
75
+
input[type="submit"]:last-of-type {
73
76
margin-top: 1em;
74
77
}
75
78
76
-
label:has(input) {
79
+
editor-label {
77
80
display: block;
78
-
margin-top: 0.25em;
79
81
}
80
82
81
-
label:has(input):has(span) input {
82
-
display: block;
83
+
editor-label + editor-label {
84
+
margin-top: 0.25em;
83
85
}
84
86
85
-
label:has(input):has(span) span {
87
+
editor-label .label {
86
88
font-size: 14px;
87
89
font-weight: 700;
88
90
text-transform: uppercase;
···
166
168
}
167
169
168
170
.htmx-indicator {
169
-
background: yellowgreen;
170
-
height: 2px;
171
+
--height: 2px;
172
+
height: var(--height);
173
+
margin: 2px 0;
174
+
opacity: 0;
175
+
visibility: none;
171
176
}
172
177
173
-
.htmx-request .htmx-indicator {
178
+
.htmx-request .progress {
179
+
animation: progress-bar 0.5s linear;
180
+
animation-fill-mode: both;
181
+
background: var(--color-success);
182
+
border-radius: calc(var(--height) / 2);
174
183
display: block;
184
+
height: var(--height);
185
+
}
186
+
187
+
@keyframes progress-bar {
188
+
0% {
189
+
width: 25%;
190
+
}
191
+
100% {
192
+
width: 100%;
193
+
}
175
194
}
176
195
177
196
form[hx-post] {
+81
src/templates/_editor_links.html
+81
src/templates/_editor_links.html
···
1
+
<div id="editor-links-container" x-data="{ links: {{ links }}, linksChanged: false }">
2
+
<div>
3
+
<h2 style="display: inline-block;">links</h2>
4
+
<template x-if="linksChanged">
5
+
<span class="alert">You have unsaved changes!</span>
6
+
</template>
7
+
</div>
8
+
9
+
<form
10
+
method="post"
11
+
action="/editor/links"
12
+
hx-post="/editor/links"
13
+
hx-target="#editor-links-container"
14
+
@change="linksChanged = true"
15
+
>
16
+
<input type="submit" value="save links" />
17
+
{% include "_htmx_indicator.html" %}
18
+
19
+
<div x-sort x-sort:config="{ handle: '[x-sort\\:handle]' }">
20
+
<template x-for="(link, index) in links">
21
+
<link-editor-item x-data="{ editing: !link.href }" x-sort:item="link.href">
22
+
<link-editor-header>
23
+
<div class="static link-item" :style="'color: ' + link.backgroundColor">
24
+
<span class="link-item-title" x-text="link.title"></span>
25
+
<span class="link-item-detail" x-show="link.subtitle" x-text="link.subtitle"></span>
26
+
</div>
27
+
<link-editor-drag-handle x-sort:handle>⠿</link-editor-drag-handle>
28
+
</link-editor-header>
29
+
<div x-show="!editing">
30
+
<link-editor-buttons>
31
+
<button type="button" @click="editing = true">edit</button>
32
+
<button type="button" @click="if (confirm('delete ' + link.title + '?')) links.splice(index, 1)">delete</button>
33
+
</link-editor-buttons>
34
+
</div>
35
+
<div x-show="editing">
36
+
<editor-label>
37
+
<label>
38
+
<span class="label">URL</span>
39
+
<input type="text" name="link-href" x-model="link.href" required />
40
+
</label>
41
+
</editor-label>
42
+
<editor-label>
43
+
<label>
44
+
<span class="label">Title</span>
45
+
<input type="text" name="link-title" x-model="link.title" required />
46
+
</label>
47
+
</editor-label>
48
+
<editor-label>
49
+
<label>
50
+
<span class="label">Subtitle</span>
51
+
<input type="text" name="link-subtitle" x-model="link.subtitle" />
52
+
</label>
53
+
</editor-label>
54
+
<editor-label>
55
+
<label>
56
+
<span class="label">Background color</span>
57
+
<input type="color" name="link-background-color" x-model="link.backgroundColor" required />
58
+
</label>
59
+
</editor-label>
60
+
<button type="button" class="submit" @click="editing = false">close</button>
61
+
</div>
62
+
</link-editor-item>
63
+
</template>
64
+
</div>
65
+
66
+
<template x-if="links.length === 0">
67
+
<p>
68
+
Nothing here! Add your first link, then the second, then...
69
+
You can always delete and sort them.
70
+
</p>
71
+
</template>
72
+
73
+
<button type="button" @click="links.push({ backgroundColor: '#A1D87E' })" style="display: block; margin-top: 1em;">
74
+
add link
75
+
</button>
76
+
77
+
<input type="submit" value="save links" />
78
+
{% include "_htmx_indicator.html" %}
79
+
</form>
80
+
<!-- #editor-links-container -->
81
+
</div>
+16
-14
src/templates/_editor_profile.html
+16
-14
src/templates/_editor_profile.html
···
1
-
<form action="/editor/profile" method="post" hx-post="/editor/profile" hx-swap="outerHTML">
2
-
<label>
3
-
<span>Display name</span>
4
-
<input type="text" name="displayName" value="{{ profile.displayName }}" required />
5
-
</label>
6
-
<label>
7
-
<span>Description</span>
8
-
<input type="text" name="description" value="{{ profile.description }}" />
9
-
</label>
1
+
<form method="post" action="/editor/profile" hx-post="/editor/profile">
2
+
<editor-label>
3
+
<label>
4
+
<span class="label">Display name</span>
5
+
<input type="text" name="displayName" value="{{ profile.displayName }}" required />
6
+
</label>
7
+
</editor-label>
8
+
<editor-label>
9
+
<label>
10
+
<span class="label">Description</span>
11
+
<input type="text" name="description" value="{{ profile.description }}" />
12
+
</label>
13
+
</editor-label>
10
14
{% if profile_from_bluesky %}
11
15
<p>
12
16
<span class="faded caption">Profile was fetched from Bluesky. On save it will use an independent, ligo.at only copy.</span>
13
17
</p>
14
18
{% endif %}
15
-
<label>
16
-
<input type="submit" value="save profile">
17
-
</label>
18
-
<div class="htmx-indicator"></div>
19
+
<input type="submit" value="save profile">
20
+
{% include "_htmx_indicator.html" %}
21
+
<!-- /editor/profile -->
19
22
</form>
20
-
<!-- /editor/profile -->
+3
src/templates/_htmx_indicator.html
+3
src/templates/_htmx_indicator.html
+2
-64
src/templates/editor.html
+2
-64
src/templates/editor.html
···
15
15
<script defer src="{{ url_for('static', filename='alpine.3.15.0.min.js') }}"></script>
16
16
</head>
17
17
<body>
18
-
<div class="wrapper editor" x-data="{ links: {{ links }}, linksChanged: false }">
18
+
<div class="wrapper editor">
19
19
<header>
20
20
<h1>ligo.at</h1>
21
21
<span class="tagline">edit your profile & links</span>
···
36
36
<h2>profile</h2>
37
37
{% include "_editor_profile.html" %}
38
38
39
-
<div>
40
-
<h2 style="display: inline-block;">links</h2>
41
-
<template x-if="linksChanged">
42
-
<span class="alert">You have unsaved changes!</span>
43
-
</template>
44
-
</div>
45
-
46
39
<noscript>
47
40
JavaScript is needed for a better experience configuring the links.
48
41
</noscript>
49
42
50
-
<form action="/editor/links" method="post" @change="linksChanged = true">
51
-
<input type="submit" value="save links" />
52
-
53
-
<div x-sort x-sort:config="{ handle: '[x-sort\\:handle]' }">
54
-
<template x-for="(link, index) in links">
55
-
<link-editor-item x-data="{ editing: !link.href }" x-sort:item="link.href">
56
-
<link-editor-header>
57
-
<div class="static link-item" :style="'color: ' + link.backgroundColor">
58
-
<span class="link-item-title" x-text="link.title"></span>
59
-
<span class="link-item-detail" x-show="link.subtitle" x-text="link.subtitle"></span>
60
-
</div>
61
-
<link-editor-drag-handle x-sort:handle>⠿</link-editor-drag-handle>
62
-
</link-editor-header>
63
-
<div x-show="!editing">
64
-
<link-editor-buttons>
65
-
<button type="button" @click="editing = true">edit</button>
66
-
<button type="button" @click="if (confirm('delete ' + link.title + '?')) links.splice(index, 1)">delete</button>
67
-
</link-editor-buttons>
68
-
</div>
69
-
<div x-show="editing">
70
-
<label>
71
-
<span>URL</span>
72
-
<input type="text" name="link-href" x-model="link.href" required />
73
-
</label>
74
-
<label>
75
-
<span>Title</span>
76
-
<input type="text" name="link-title" x-model="link.title" required />
77
-
</label>
78
-
<label>
79
-
<span>Subtitle</span>
80
-
<input type="text" name="link-subtitle" x-model="link.subtitle" />
81
-
</label>
82
-
<label>
83
-
<span>Background color</span>
84
-
<input type="color" name="link-background-color" x-model="link.backgroundColor" required />
85
-
</label>
86
-
<button type="button" @click="editing = false" style="margin-top: 1em;">close</button>
87
-
</div>
88
-
</link-editor-item>
89
-
</template>
90
-
</div>
91
-
92
-
<template x-if="links.length === 0">
93
-
<p>
94
-
Nothing here! Add your first link, then the second, then...
95
-
You can always delete and sort them.
96
-
</p>
97
-
</template>
98
-
99
-
<button type="button" @click="links.push({ backgroundColor: '#A1D87E' })" style="display: block; margin-top: 1em;">
100
-
add link
101
-
</button>
102
-
103
-
<input type="submit" value="save links" />
104
-
</form>
105
-
<!-- /editor/links -->
43
+
{% include "_editor_links.html" %}
106
44
107
45
<footer>
108
46
<p>