tangled
alpha
login
or
join now
zzstoatzz.io
/
status
0
fork
atom
slack status without the slack
status.zzstoatzz.io/
quickslice
0
fork
atom
overview
issues
pulls
pipelines
fix: clippy warning (avoid unnecessary to_string)
zzstoatzz.io
6 months ago
eab1944a
5ba48af9
+226
-1
4 changed files
expand all
collapse all
unified
split
src
api
status.rs
templates.rs
templates
feed.html
status.html
+8
src/api/status.rs
···
130
130
vec![]
131
131
});
132
132
133
133
+
let is_admin_flag = is_admin(did.as_str());
133
134
let html = StatusTemplate {
134
135
title: "your status",
135
136
handle,
136
137
current_status,
137
138
history,
138
139
is_owner: true, // They're viewing their own status
140
140
+
is_admin: is_admin_flag,
139
141
}
140
142
.render()
141
143
.expect("template should be valid");
···
192
194
current_status,
193
195
history,
194
196
is_owner: false, // Visitor viewing owner's status
197
197
+
is_admin: false,
195
198
}
196
199
.render()
197
200
.expect("template should be valid");
···
275
278
vec![]
276
279
});
277
280
281
281
+
let is_admin_flag = match session.get::<String>("did").unwrap_or(None) {
282
282
+
Some(d) => is_admin(&d),
283
283
+
None => false,
284
284
+
};
278
285
let html = StatusTemplate {
279
286
title: &format!("@{} status", handle),
280
287
handle: handle.clone(),
281
288
current_status,
282
289
history,
283
290
is_owner,
291
291
+
is_admin: is_admin_flag,
284
292
}
285
293
.render()
286
294
.expect("template should be valid");
+1
src/templates.rs
···
36
36
pub current_status: Option<StatusFromDb>,
37
37
pub history: Vec<StatusFromDb>,
38
38
pub is_owner: bool,
39
39
+
pub is_admin: bool,
39
40
}
40
41
41
42
#[derive(Template)]
+109
-1
templates/feed.html
···
45
45
</button>
46
46
</div>
47
47
</header>
48
48
+
49
49
+
{% if is_admin %}
50
50
+
<!-- Admin Upload (fixed top-left) -->
51
51
+
<div class="admin-panel" id="admin-panel">
52
52
+
<button class="admin-toggle" id="admin-toggle" title="admin tools" aria-label="admin tools">⚙️</button>
53
53
+
<div class="admin-content" id="admin-content" style="display:none;">
54
54
+
<div class="admin-section">
55
55
+
<div class="admin-title">upload emoji</div>
56
56
+
<form id="emoji-upload-form">
57
57
+
<input type="text" id="emoji-name" placeholder="name (optional)" maxlength="40" />
58
58
+
<input type="file" id="emoji-file" accept="image/png,image/gif" required />
59
59
+
<button type="submit">upload</button>
60
60
+
</form>
61
61
+
<div class="admin-msg" id="admin-msg" aria-live="polite"></div>
62
62
+
</div>
63
63
+
</div>
64
64
+
</div>
65
65
+
{% endif %}
48
66
49
67
<!-- Simple Settings (logged in users only) -->
50
68
{% if let Some(p) = &profile %}
···
738
756
font-size: 1.5rem;
739
757
}
740
758
}
759
759
+
760
760
+
/* Admin panel (top-left) */
761
761
+
.admin-panel {
762
762
+
position: fixed;
763
763
+
top: 12px;
764
764
+
left: 12px;
765
765
+
z-index: 9999;
766
766
+
}
767
767
+
.admin-toggle {
768
768
+
background: var(--bg-tertiary);
769
769
+
border: 1px solid var(--border-color);
770
770
+
border-radius: 10px;
771
771
+
padding: 6px 8px;
772
772
+
cursor: pointer;
773
773
+
color: var(--text-secondary);
774
774
+
}
775
775
+
.admin-content {
776
776
+
margin-top: 8px;
777
777
+
background: var(--bg-tertiary);
778
778
+
border: 1px solid var(--border-color);
779
779
+
border-radius: 12px;
780
780
+
padding: 10px;
781
781
+
width: 240px;
782
782
+
box-shadow: var(--shadow-md);
783
783
+
}
784
784
+
.admin-title {
785
785
+
font-size: 12px;
786
786
+
color: var(--text-secondary);
787
787
+
margin-bottom: 6px;
788
788
+
}
789
789
+
.admin-content input[type="text"],
790
790
+
.admin-content input[type="file"] {
791
791
+
width: 100%;
792
792
+
margin-bottom: 8px;
793
793
+
}
794
794
+
.admin-content button[type="submit"] {
795
795
+
width: 100%;
796
796
+
background: var(--accent);
797
797
+
color: #fff;
798
798
+
border: none;
799
799
+
border-radius: 8px;
800
800
+
padding: 6px 8px;
801
801
+
cursor: pointer;
802
802
+
}
803
803
+
.admin-msg { font-size: 12px; color: var(--text-secondary); margin-top: 6px; }
741
804
</style>
742
805
743
806
<script>
···
1206
1269
});
1207
1270
});
1208
1271
});
1209
1209
-
</script>
1272
1272
+
</script>
1273
1273
+
<script>
1274
1274
+
// Admin upload toggles and submit
1275
1275
+
document.addEventListener('DOMContentLoaded', function () {
1276
1276
+
const toggle = document.getElementById('admin-toggle');
1277
1277
+
const content = document.getElementById('admin-content');
1278
1278
+
const form = document.getElementById('emoji-upload-form');
1279
1279
+
const file = document.getElementById('emoji-file');
1280
1280
+
const name = document.getElementById('emoji-name');
1281
1281
+
const msg = document.getElementById('admin-msg');
1282
1282
+
if (!toggle || !content || !form) return;
1283
1283
+
1284
1284
+
toggle.addEventListener('click', () => {
1285
1285
+
content.style.display = content.style.display === 'none' ? 'block' : 'none';
1286
1286
+
});
1287
1287
+
1288
1288
+
form.addEventListener('submit', async (e) => {
1289
1289
+
e.preventDefault();
1290
1290
+
msg.textContent = '';
1291
1291
+
if (!file.files || file.files.length === 0) {
1292
1292
+
msg.textContent = 'choose a PNG or GIF';
1293
1293
+
return;
1294
1294
+
}
1295
1295
+
const f = file.files[0];
1296
1296
+
if (!['image/png','image/gif'].includes(f.type)) {
1297
1297
+
msg.textContent = 'only PNG or GIF';
1298
1298
+
return;
1299
1299
+
}
1300
1300
+
const fd = new FormData();
1301
1301
+
fd.append('file', f);
1302
1302
+
if (name.value.trim().length) fd.append('name', name.value.trim());
1303
1303
+
try {
1304
1304
+
const res = await fetch('/admin/upload-emoji', { method: 'POST', body: fd });
1305
1305
+
const json = await res.json();
1306
1306
+
if (!res.ok || !json.success) {
1307
1307
+
msg.textContent = json.error || 'upload failed';
1308
1308
+
return;
1309
1309
+
}
1310
1310
+
msg.textContent = `uploaded: ${json.filename}`;
1311
1311
+
// Optionally refresh emoji list used by picker
1312
1312
+
} catch (err) {
1313
1313
+
msg.textContent = 'network error';
1314
1314
+
}
1315
1315
+
});
1316
1316
+
});
1317
1317
+
</script>
1210
1318
{%endblock content%}
+108
templates/status.html
···
46
46
{% endif %}
47
47
</div>
48
48
</header>
49
49
+
50
50
+
{% if is_admin %}
51
51
+
<!-- Admin Upload (fixed top-left) -->
52
52
+
<div class="admin-panel" id="admin-panel">
53
53
+
<button class="admin-toggle" id="admin-toggle" title="admin tools" aria-label="admin tools">⚙️</button>
54
54
+
<div class="admin-content" id="admin-content" style="display:none;">
55
55
+
<div class="admin-section">
56
56
+
<div class="admin-title">upload emoji</div>
57
57
+
<form id="emoji-upload-form">
58
58
+
<input type="text" id="emoji-name" placeholder="name (optional)" maxlength="40" />
59
59
+
<input type="file" id="emoji-file" accept="image/png,image/gif" required />
60
60
+
<button type="submit">upload</button>
61
61
+
</form>
62
62
+
<div class="admin-msg" id="admin-msg" aria-live="polite"></div>
63
63
+
</div>
64
64
+
</div>
65
65
+
</div>
66
66
+
{% endif %}
49
67
50
68
<!-- Simple Settings (owner only) -->
51
69
{% if is_owner %}
···
350
368
align-items: center;
351
369
margin-bottom: 2rem;
352
370
}
371
371
+
372
372
+
/* Admin panel (top-left) */
373
373
+
.admin-panel {
374
374
+
position: fixed;
375
375
+
top: 12px;
376
376
+
left: 12px;
377
377
+
z-index: 9999;
378
378
+
}
379
379
+
.admin-toggle {
380
380
+
background: var(--bg-tertiary);
381
381
+
border: 1px solid var(--border-color);
382
382
+
border-radius: 10px;
383
383
+
padding: 6px 8px;
384
384
+
cursor: pointer;
385
385
+
color: var(--text-secondary);
386
386
+
}
387
387
+
.admin-content {
388
388
+
margin-top: 8px;
389
389
+
background: var(--bg-tertiary);
390
390
+
border: 1px solid var(--border-color);
391
391
+
border-radius: 12px;
392
392
+
padding: 10px;
393
393
+
width: 240px;
394
394
+
box-shadow: var(--shadow-md);
395
395
+
}
396
396
+
.admin-title {
397
397
+
font-size: 12px;
398
398
+
color: var(--text-secondary);
399
399
+
margin-bottom: 6px;
400
400
+
}
401
401
+
.admin-content input[type="text"],
402
402
+
.admin-content input[type="file"] {
403
403
+
width: 100%;
404
404
+
margin-bottom: 8px;
405
405
+
}
406
406
+
.admin-content button[type="submit"] {
407
407
+
width: 100%;
408
408
+
background: var(--accent);
409
409
+
color: #fff;
410
410
+
border: none;
411
411
+
border-radius: 8px;
412
412
+
padding: 6px 8px;
413
413
+
cursor: pointer;
414
414
+
}
415
415
+
.admin-msg { font-size: 12px; color: var(--text-secondary); margin-top: 6px; }
353
416
354
417
.header-actions {
355
418
display: flex;
···
1963
2026
});
1964
2027
});
1965
2028
});
2029
2029
+
</script>
2030
2030
+
<script>
2031
2031
+
// Admin upload toggles and submit
2032
2032
+
document.addEventListener('DOMContentLoaded', function () {
2033
2033
+
const toggle = document.getElementById('admin-toggle');
2034
2034
+
const content = document.getElementById('admin-content');
2035
2035
+
const form = document.getElementById('emoji-upload-form');
2036
2036
+
const file = document.getElementById('emoji-file');
2037
2037
+
const name = document.getElementById('emoji-name');
2038
2038
+
const msg = document.getElementById('admin-msg');
2039
2039
+
if (!toggle || !content || !form) return;
2040
2040
+
2041
2041
+
toggle.addEventListener('click', () => {
2042
2042
+
content.style.display = content.style.display === 'none' ? 'block' : 'none';
2043
2043
+
});
2044
2044
+
2045
2045
+
form.addEventListener('submit', async (e) => {
2046
2046
+
e.preventDefault();
2047
2047
+
msg.textContent = '';
2048
2048
+
if (!file.files || file.files.length === 0) {
2049
2049
+
msg.textContent = 'choose a PNG or GIF';
2050
2050
+
return;
2051
2051
+
}
2052
2052
+
const f = file.files[0];
2053
2053
+
if (!['image/png','image/gif'].includes(f.type)) {
2054
2054
+
msg.textContent = 'only PNG or GIF';
2055
2055
+
return;
2056
2056
+
}
2057
2057
+
const fd = new FormData();
2058
2058
+
fd.append('file', f);
2059
2059
+
if (name.value.trim().length) fd.append('name', name.value.trim());
2060
2060
+
try {
2061
2061
+
const res = await fetch('/admin/upload-emoji', { method: 'POST', body: fd });
2062
2062
+
const json = await res.json();
2063
2063
+
if (!res.ok || !json.success) {
2064
2064
+
msg.textContent = json.error || 'upload failed';
2065
2065
+
return;
2066
2066
+
}
2067
2067
+
msg.textContent = `uploaded: ${json.filename}`;
2068
2068
+
// Optionally refresh emoji list used by picker
2069
2069
+
} catch (err) {
2070
2070
+
msg.textContent = 'network error';
2071
2071
+
}
2072
2072
+
});
2073
2073
+
});
1966
2074
</script>
1967
2075
{%endblock content%}