@justai/cuts 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Posts.astro +24 -14
package/package.json
CHANGED
package/src/Posts.astro
CHANGED
|
@@ -16,17 +16,29 @@
|
|
|
16
16
|
// stay dark. Everything else is shared.
|
|
17
17
|
//
|
|
18
18
|
// THE SOCIAL LAYER is in here: a persona's feed carries replies from OTHER personas, and they must read
|
|
19
|
-
// as themselves — their own icon (or glyph), their own name
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
19
|
+
// as themselves — their own icon (or glyph), their own name, and a link to their own profile — not as the
|
|
20
|
+
// host persona. That rendering was invented on the two apexes (justai, app.zone) and lived only there;
|
|
21
|
+
// folding it UP into this cut is what lets every persona show a cross-reply correctly instead of
|
|
22
|
+
// mislabelling it as its own.
|
|
23
|
+
//
|
|
24
|
+
// EVERYONE IS NAMED ONCE — no @handle anywhere in this feed.
|
|
25
|
+
//
|
|
26
|
+
// It started with the personas, where the name and the handle are the same word by construction: app.zone
|
|
27
|
+
// beside @appzone, AMRAP beside @amrap, Tabata beside @tabata. One reads as identity; six down a thread read
|
|
28
|
+
// as stutter, and on a 480px column each one spent width saying nothing new. The self-reply had always
|
|
29
|
+
// rendered as `name · time`, so dropping the rest just made the family read the way the host already did.
|
|
30
|
+
//
|
|
31
|
+
// Humans were kept for one release on the argument that a person's display name and their account are two
|
|
32
|
+
// different facts. Overruled, and the call is better than the argument was: what a reader needs from a line
|
|
33
|
+
// in a conversation is who said it, and the name is that. The @handle was a convention borrowed from a
|
|
34
|
+
// product where handles are how you address someone — nobody is addressed here.
|
|
35
|
+
//
|
|
36
|
+
// `handle` (the prop) and `authorHandle` (the API field) both stay: they are keys, not labels. The feed
|
|
37
|
+
// query, the is-this-mine test, and the link to another persona's profile all read them. Nothing prints them.
|
|
25
38
|
interface Props {
|
|
26
39
|
handle: string; // the persona KEY — the feed query (?persona=) and the "is this reply mine?" test
|
|
27
40
|
name: string; // the persona's display name; rendered RAW, so a name may carry markup (justai's ".pro")
|
|
28
41
|
footer: string; // the localized footer tagline after "justai.pro ·"
|
|
29
|
-
displayHandle?: string; // the rendered @handle when it differs from the key (app.zone → @appzone)
|
|
30
42
|
avatarBg?: string; // post-avatar tile background (default #fff)
|
|
31
43
|
avatarBgLight?: string; // light-scheme override for that tile (a dark gradient must invert, not stay dark)
|
|
32
44
|
// Which file is THIS persona's face in the feed. Defaults to /icon.svg, which every persona serves at its
|
|
@@ -49,7 +61,7 @@ interface Props {
|
|
|
49
61
|
// the ground. The page's own media query is reliable, so the page picks the file and the file stays dumb.
|
|
50
62
|
avatarSrcDark?: string;
|
|
51
63
|
}
|
|
52
|
-
const { handle, name, footer,
|
|
64
|
+
const { handle, name, footer, avatarBg, avatarBgLight, avatarSrc, avatarBare, avatarSrcDark } = Astro.props;
|
|
53
65
|
const vars = [
|
|
54
66
|
avatarBg && `--feed-av-bg:${avatarBg}`,
|
|
55
67
|
avatarBgLight && `--feed-av-bg-light:${avatarBgLight}`,
|
|
@@ -60,7 +72,6 @@ const vars = [
|
|
|
60
72
|
class="cut posts"
|
|
61
73
|
id="posts"
|
|
62
74
|
data-persona={handle}
|
|
63
|
-
data-handle={displayHandle ?? handle}
|
|
64
75
|
data-name={name}
|
|
65
76
|
data-av={avatarSrc ?? "/icon.svg"}
|
|
66
77
|
data-av-dark={avatarSrcDark}
|
|
@@ -121,7 +132,7 @@ const vars = [
|
|
|
121
132
|
|
|
122
133
|
.ph { display: flex; align-items: center; gap: 5px; font-size: var(--fs-xs); flex-wrap: wrap; }
|
|
123
134
|
.ph b { font-weight: 600; }
|
|
124
|
-
.
|
|
135
|
+
.dot, .pt { color: var(--text-dim); font-weight: 400; }
|
|
125
136
|
/* a persona's name in the feed links to its profile (X-style); humans have no link */
|
|
126
137
|
.pn { color: inherit; text-decoration: none; }
|
|
127
138
|
.pn:hover b { color: var(--accent); }
|
|
@@ -164,7 +175,6 @@ const vars = [
|
|
|
164
175
|
// persona's OWN name and is injected RAW — it is authored in the persona's source, never user input,
|
|
165
176
|
// so it may carry markup (justai's ".pro" span). Every name that comes from the API is escaped.
|
|
166
177
|
const PERSONA = section.dataset.persona || "";
|
|
167
|
-
const HANDLE = section.dataset.handle || PERSONA;
|
|
168
178
|
const NAME = section.dataset.name || "";
|
|
169
179
|
if (!PERSONA) return;
|
|
170
180
|
// Astro scopes this component's CSS by a data-astro-cid-* attribute; runtime nodes lack it, so copy it.
|
|
@@ -236,11 +246,11 @@ const vars = [
|
|
|
236
246
|
: `<span style="font-size:16px">${esc(c.authorGlyph || "✦")}</span>`;
|
|
237
247
|
const nm = own ? NAME : esc(c.authorName); // own name is authored markup; an API name is escaped
|
|
238
248
|
const purl = own ? "#profile" : `${c.personaUrl || homeOf(c.authorHandle)}/#profile`;
|
|
239
|
-
const meta =
|
|
249
|
+
const meta = `· ${timeAgo(c.createdAt)}`; // a persona's name IS its handle — see the header note
|
|
240
250
|
return `<div class="trow${last ? " last" : ""}"><div class="avc"><span class="rav">${av}</span>${ln}</div><div class="cc"><div class="ch"><a class="pn" href="${esc(purl)}"><b>${nm}</b></a>${ck}<span class="muted">${meta}</span></div><p class="cb">${esc(c.body)}</p></div></div>`;
|
|
241
251
|
}
|
|
242
252
|
const initial = esc(String(c.authorName || "?").slice(0, 1));
|
|
243
|
-
return `<div class="trow${last ? " last" : ""}"><div class="avc"><span class="uav" style="--c:${esc(c.authorColor || "#888")}">${initial}</span>${ln}</div><div class="cc"><div class="ch"><b>${esc(c.authorName)}</b><span class="muted"
|
|
253
|
+
return `<div class="trow${last ? " last" : ""}"><div class="avc"><span class="uav" style="--c:${esc(c.authorColor || "#888")}">${initial}</span>${ln}</div><div class="cc"><div class="ch"><b>${esc(c.authorName)}</b><span class="muted">· ${timeAgo(c.createdAt)}</span></div><p class="cb">${esc(c.body)}</p></div></div>`;
|
|
244
254
|
};
|
|
245
255
|
const post = (p: P): string => {
|
|
246
256
|
const cs = p.comments || [];
|
|
@@ -250,7 +260,7 @@ const vars = [
|
|
|
250
260
|
const n = String(p.id || "").split(":").pop() || "";
|
|
251
261
|
const perma = n ? `/feed/${n}` : "";
|
|
252
262
|
const pt = perma ? `<a class="pt" href="${perma}">${timeAgo(p.createdAt)}</a>` : `<span class="pt">${timeAgo(p.createdAt)}</span>`;
|
|
253
|
-
return `<article class="post"${perma ? ` data-href="${perma}"` : ""}><div class="trow"><div class="avc"><span class="pa">${AV}</span>${cs.length ? '<span class="ln"></span>' : ""}</div><div class="cc"><div class="ph"><a class="pn" href="#profile"><b>${NAME}</b></a><span class="
|
|
263
|
+
return `<article class="post"${perma ? ` data-href="${perma}"` : ""}><div class="trow"><div class="avc"><span class="pa">${AV}</span>${cs.length ? '<span class="ln"></span>' : ""}</div><div class="cc"><div class="ph"><a class="pn" href="#profile"><b>${NAME}</b></a><span class="dot">·</span>${pt}</div><p class="pbd">${esc(p.body)}</p>${pact}</div></div>${inner}</article>`;
|
|
254
264
|
};
|
|
255
265
|
|
|
256
266
|
let done = false;
|