@justai/cuts 0.33.0 → 0.35.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 +2 -2
- package/src/FilePicker.astro +10 -1
- package/src/Toolbar.astro +15 -85
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justai/cuts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "A persona's named parts \u2014 the page shell that holds them, and the cuts themselves.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@justai/core": "^0.
|
|
34
|
+
"@justai/core": "^0.7.0",
|
|
35
35
|
"@justai/ui": "^0.3.0",
|
|
36
36
|
"@justai/atrium": "^0.1.9"
|
|
37
37
|
}
|
package/src/FilePicker.astro
CHANGED
|
@@ -93,6 +93,7 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
93
93
|
// two kernels at once would otherwise have both arrive calling themselves "files", and the second
|
|
94
94
|
// would silently take the first one's place.
|
|
95
95
|
import atrium from "@justai/atrium";
|
|
96
|
+
import { PRESENTATION_PARAM, PRESENTATION_EMBEDDED } from "@justai/core";
|
|
96
97
|
|
|
97
98
|
interface Picked {
|
|
98
99
|
path: string;
|
|
@@ -147,7 +148,15 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
147
148
|
|
|
148
149
|
async function pick(req: PickRequest = {}): Promise<Picked | null> {
|
|
149
150
|
const bus = await router();
|
|
150
|
-
if (!frame.getAttribute("src"))
|
|
151
|
+
if (!frame.getAttribute("src")) {
|
|
152
|
+
// DECLARE THE PRESENTATION. A page opened by a person and a page opened by another persona
|
|
153
|
+
// are two different situations, and the same controls do not belong in both — standalone
|
|
154
|
+
// there is nothing to cancel. The frame is the slower truth (a handshake takes a beat), so
|
|
155
|
+
// the declaration rides in the URL and is available in the first paint. It is presentation
|
|
156
|
+
// only: nothing that must not be forged is decided by it.
|
|
157
|
+
const src = root.dataset.src!;
|
|
158
|
+
frame.setAttribute("src", src + (src.includes("?") ? "&" : "?") + PRESENTATION_PARAM + "=" + PRESENTATION_EMBEDDED);
|
|
159
|
+
}
|
|
151
160
|
// ONE HEADING, and it is the sheet's. A per-call title retitles the sheet rather than being
|
|
152
161
|
// forwarded into the frame: the kernel renders a heading of its own for callers that have no
|
|
153
162
|
// sheet around them, and sending it one here printed "Upload logo" twice, one under the other.
|
package/src/Toolbar.astro
CHANGED
|
@@ -119,89 +119,19 @@ const { sectionsLabel, profileLabel, kernelLabel, postsLabel, home = true } = As
|
|
|
119
119
|
</style>
|
|
120
120
|
|
|
121
121
|
<script>
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// Land at the top of .below (toolbar + kernel) — skip the Profile, no flash.
|
|
139
|
-
const land = () => {
|
|
140
|
-
// presentation mode (a ?data= link with present:true) owns the viewport — no landing scroll
|
|
141
|
-
if (document.body.dataset.present) return;
|
|
142
|
-
// respect a section hash on load/refresh: #posts / #profile land there; otherwise (#kernel or
|
|
143
|
-
// none) land on the Kernel. behavior:instant overrides the CSS scroll-behavior:smooth.
|
|
144
|
-
const h = location.hash;
|
|
145
|
-
if (h === "#posts" && posts) posts.scrollIntoView({ behavior: "instant", block: "start" });
|
|
146
|
-
else if (h === "#profile" && profile) profile.scrollIntoView({ behavior: "instant", block: "start" });
|
|
147
|
-
else window.scrollTo({ top: below.getBoundingClientRect().top + window.scrollY, behavior: "instant" });
|
|
148
|
-
};
|
|
149
|
-
land();
|
|
150
|
-
window.addEventListener("load", land);
|
|
151
|
-
const reveal = () => document.body.classList.add("ready");
|
|
152
|
-
requestAnimationFrame(reveal);
|
|
153
|
-
setTimeout(reveal, 500);
|
|
154
|
-
|
|
155
|
-
// Toolbar hairline appears once content scrolls under it; the nav highlights whichever
|
|
156
|
-
// cut is currently at the top of the active region (the toolbar line) — Profile (the
|
|
157
|
-
// first cut when you scroll up), App or Posts.
|
|
158
|
-
const cuts = [[profile, navProfile], [kernel, navKernel], [posts, navPosts]].filter(([el, nav]) => el && nav) as [HTMLElement, HTMLElement][];
|
|
159
|
-
// scroll-reactive title strip: the cut at the toolbar line names itself. Profile stays collapsed
|
|
160
|
-
// (its big header is the title); Kernel → app name, Posts → @handle.
|
|
161
|
-
const tbTitle = document.getElementById("tbTitle");
|
|
162
|
-
const tbTitleText = document.getElementById("tbTitleText");
|
|
163
|
-
const pname = (document.querySelector(".pname")?.textContent || "").trim();
|
|
164
|
-
const handle = (document.querySelector(".handle")?.textContent || "").trim();
|
|
165
|
-
const titleFor = (c: [HTMLElement, HTMLElement]) => (c[0] === kernel ? pname : c[0] === posts ? handle : "");
|
|
166
|
-
let lastTitleCut: [HTMLElement, HTMLElement] | null = null, titleInit = true;
|
|
167
|
-
const onScroll = () => {
|
|
168
|
-
if (toolbar) toolbar.classList.toggle("stuck", window.scrollY >= below.offsetTop - 1);
|
|
169
|
-
const line = 84; // ≥ #kernel/#posts scroll-margin-top, so anchor clicks land at/above the line and are detected
|
|
170
|
-
let current = cuts[0];
|
|
171
|
-
for (const c of cuts) if (c[0].getBoundingClientRect().top <= line) current = c;
|
|
172
|
-
for (const c of cuts) c[1].classList.toggle("active", c === current);
|
|
173
|
-
if (current !== lastTitleCut) {
|
|
174
|
-
lastTitleCut = current;
|
|
175
|
-
// scroll-linked URL: reflect the current cut in the hash (replaceState = no jump, no history spam)
|
|
176
|
-
const cid = current[0].id;
|
|
177
|
-
if (cid && "#" + cid !== location.hash) setHash(cid);
|
|
178
|
-
const t = titleFor(current);
|
|
179
|
-
if (titleInit) {
|
|
180
|
-
// first paint: snap the strip into place — no "settling into the room" animation on load/refresh
|
|
181
|
-
titleInit = false;
|
|
182
|
-
if (tbTitle) { tbTitle.style.transition = "none"; tbTitle.classList.toggle("show", !!t); }
|
|
183
|
-
if (t && tbTitleText) tbTitleText.textContent = t;
|
|
184
|
-
if (tbTitle) { void tbTitle.offsetHeight; tbTitle.style.transition = ""; }
|
|
185
|
-
} else {
|
|
186
|
-
if (tbTitle) tbTitle.classList.toggle("show", !!t);
|
|
187
|
-
if (t && tbTitleText && tbTitleText.textContent !== t) {
|
|
188
|
-
tbTitleText.classList.add("fade");
|
|
189
|
-
setTimeout(() => { tbTitleText.textContent = t; tbTitleText.classList.remove("fade"); }, 150);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
window.addEventListener("scroll", onScroll, { passive: true });
|
|
195
|
-
onScroll();
|
|
196
|
-
// nav clicks: scroll programmatically so the sticky toolbar always pins flush. The native anchor
|
|
197
|
-
// + scroll-margin left a gap above the bar when jumping to the Kernel from the Profile (the title
|
|
198
|
-
// strip is still collapsed there, so the bar is shorter than the margin). Kernel → land at .below
|
|
199
|
-
// top (bar pins at 0); Profile/Posts → scrollIntoView.
|
|
200
|
-
cuts.forEach(([el, nav]) => nav.addEventListener("click", (e) => {
|
|
201
|
-
e.preventDefault();
|
|
202
|
-
if (el === kernel) window.scrollTo({ top: below.getBoundingClientRect().top + window.scrollY, behavior: "smooth" });
|
|
203
|
-
else el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
204
|
-
setHash(el.id);
|
|
205
|
-
}));
|
|
206
|
-
})();
|
|
122
|
+
// queries: where does the toolbar's scroll behaviour live · land on the kernel · scroll spy ·
|
|
123
|
+
// title strip · why is the scroll code not in this file any more
|
|
124
|
+
//
|
|
125
|
+
// The BEHAVIOUR moved to @justai/core (`personaScroll`); what stays here is the markup and the CSS.
|
|
126
|
+
//
|
|
127
|
+
// It moved because this cut can only serve pages that adopt its markup, and three surfaces cannot:
|
|
128
|
+
// `/me` and `/files` carry extra navigation of their own and the feed is a different build. All
|
|
129
|
+
// three hand-wrote the same eighty lines, and by the third copy they had drifted — `/files` opened
|
|
130
|
+
// on the profile with an empty title strip, and its hash writer clobbered the query string where
|
|
131
|
+
// this one preserved it. One implementation, four consumers.
|
|
132
|
+
//
|
|
133
|
+
// The defaults ARE this cut's contract — the three cut ids, `nav<Cut>`, `#tbTitle`/`#tbTitleText`,
|
|
134
|
+
// `.toolbar`, `#below` — so a persona using the shell passes nothing at all.
|
|
135
|
+
import { personaScroll } from "@justai/core";
|
|
136
|
+
personaScroll();
|
|
207
137
|
</script>
|