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