@hanzo/rooms 0.1.46 → 0.1.48
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/dist/App.js +47 -13
- package/dist/Column.js +11 -1
- package/dist/dev.d.ts +9 -0
- package/dist/dev.js +28 -1
- package/dist/lib/destination.d.ts +15 -0
- package/dist/lib/destination.js +32 -3
- package/package.json +1 -1
package/dist/App.js
CHANGED
|
@@ -19,21 +19,27 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
19
19
|
// where nothing fills it: drawn there it was an empty pane to the right of the
|
|
20
20
|
// builder. So Dev's pane is cut on its own and the summary is Chat's alone.
|
|
21
21
|
//
|
|
22
|
+
// EVERY MOVE PUTS THE DRAWER AWAY, the one to where the reader already is
|
|
23
|
+
// included. Pressed on the page it names, a row moves nothing, and a drawer
|
|
24
|
+
// that closed only on a new address stayed open over the page it had asked for.
|
|
25
|
+
//
|
|
26
|
+
// THE PLANS' OWN ADDRESS LEAVES for the pay site in its place (dev.tsx `leave`),
|
|
27
|
+
// so the builder's own plans are never drawn.
|
|
28
|
+
//
|
|
22
29
|
// THE WINDOW IS A FRAME (`data-slot="frame"`): the column, the pane and the
|
|
23
30
|
// column beside it are three panes floating on a lit floor (ground.ts), one
|
|
24
31
|
// gutter round them and between them, `--pane-gap`, each sized by the edge
|
|
25
32
|
// beside it.
|
|
26
|
-
import { useEffect, useState } from 'react';
|
|
33
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
27
34
|
import { Menu } from 'lucide-react';
|
|
28
35
|
import { AiProvider } from '@hanzo/ai/react';
|
|
29
36
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
30
37
|
import { Builder, HostProvider, route } from '@hanzo/build';
|
|
31
38
|
import { SidebarIconButton } from '@hanzo/ui/chat';
|
|
32
|
-
import { scrim } from '@hanzo/ui/glass';
|
|
33
39
|
import { useAi } from './lib/ai.js';
|
|
34
40
|
import { Chat } from './Chat.js';
|
|
35
41
|
import { Column } from './Column.js';
|
|
36
|
-
import { useDevHost } from './dev.js';
|
|
42
|
+
import { isPlans, leave, useDevHost } from './dev.js';
|
|
37
43
|
import { useRooms } from './host.js';
|
|
38
44
|
import { wordmark } from './ModeMark.js';
|
|
39
45
|
import { empty, openThread, useOpen } from './open.js';
|
|
@@ -42,10 +48,34 @@ import { floor, pane } from './ground.js';
|
|
|
42
48
|
import { app, go, place, talk } from './lib/host.js';
|
|
43
49
|
/** `/` is Chat on a new conversation. */
|
|
44
50
|
const HOME = { mode: 'chat', thread: null };
|
|
51
|
+
/**
|
|
52
|
+
* The dim under the phone's drawer: design's scrim at two fifths, so the page
|
|
53
|
+
* still reads through the drawer's glass the way it reads through the browser's
|
|
54
|
+
* own bar, rather than a black wall the glass has nothing to show of. gui
|
|
55
|
+
* carries a `color-mix()` to the page unchanged; its types only name custom
|
|
56
|
+
* properties.
|
|
57
|
+
*/
|
|
58
|
+
const DIM = 'color-mix(in srgb, var(--surface-scrim) 40%, transparent)';
|
|
59
|
+
/** The phone's drawer stands the gutter off each edge, or the device's inset where that is wider. */
|
|
60
|
+
const INSET = {
|
|
61
|
+
top: 'max(var(--pane-gap), var(--safe-top))',
|
|
62
|
+
bottom: 'max(var(--pane-gap), var(--safe-bottom))',
|
|
63
|
+
left: 'max(var(--pane-gap), var(--safe-left))',
|
|
64
|
+
};
|
|
45
65
|
/** Where each mode was last, so the switch goes back to it. */
|
|
46
66
|
const last = { chat: '', dev: '' };
|
|
47
67
|
export function App() {
|
|
48
|
-
const
|
|
68
|
+
const [drawer, setDrawer] = useState(false);
|
|
69
|
+
const shut = useCallback(() => setDrawer(false), []);
|
|
70
|
+
const dev = useDevHost();
|
|
71
|
+
// The builder's moves put the drawer away first.
|
|
72
|
+
const host = useMemo(() => ({
|
|
73
|
+
...dev,
|
|
74
|
+
go: (to, how) => {
|
|
75
|
+
shut();
|
|
76
|
+
dev.go(to, how);
|
|
77
|
+
},
|
|
78
|
+
}), [dev, shut]);
|
|
49
79
|
const { client } = useAi();
|
|
50
80
|
const here = useRooms().path ?? '/';
|
|
51
81
|
const at = place(here) ?? HOME;
|
|
@@ -54,7 +84,11 @@ export function App() {
|
|
|
54
84
|
const run = at.mode === 'dev' && route(at.at).kind === 'run';
|
|
55
85
|
const asked = at.mode === 'chat' ? at.thread : null;
|
|
56
86
|
const { thread } = useOpen();
|
|
57
|
-
const
|
|
87
|
+
const plans = at.mode === 'dev' && isPlans(at.at);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (plans)
|
|
90
|
+
leave('app');
|
|
91
|
+
}, [plans]);
|
|
58
92
|
// An address names Chat's conversation, and one just begun writes its id back.
|
|
59
93
|
useEffect(() => {
|
|
60
94
|
if (mode !== 'chat')
|
|
@@ -76,12 +110,12 @@ export function App() {
|
|
|
76
110
|
last[mode] = `${window.location.pathname}${window.location.search}`;
|
|
77
111
|
setDrawer(false);
|
|
78
112
|
}, [mode, here]);
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
/** A move of the app's own: the drawer away, then the address. */
|
|
114
|
+
const move = useCallback((address) => {
|
|
115
|
+
shut();
|
|
116
|
+
go(address);
|
|
117
|
+
}, [shut]);
|
|
118
|
+
const toChat = () => move(last.chat || talk());
|
|
119
|
+
const toDev = () => move(last.dev || app(''));
|
|
120
|
+
return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...floor, flex: 1, minW: 0, p: "var(--pane-gap)", gap: "var(--pane-gap)", height: "100dvh", overflow: "hidden", "$max-md": { p: 0, gap: 0 }, children: [_jsx(Column, { host: host, mode: mode, thread: thread, onChat: toChat, onDev: toDev, go: move }), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, gap: "var(--pane-gap)", "$max-md": { gap: 0 }, children: [_jsxs(XStack, { "data-slot": "app-bar", height: 44, px: "$1", gap: "$1", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(SidebarIconButton, { label: "Open sidebar", onPress: () => setDrawer(true), width: 44, height: 44, rounded: 999, children: _jsx(Menu, { size: 18 }) }), _jsx(Text, { fontSize: "$4", fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) })] }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] }), drawer ? (_jsxs(_Fragment, { children: [_jsx(YStack, { render: "button", "aria-label": "Close sidebar", onPress: () => setDrawer(false), bg: DIM, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-overlay)", $md: { display: 'none' } }), _jsx(YStack, { "data-slot": "drawer", position: "absolute", t: INSET.top, b: INSET.bottom, l: INSET.left, width: "min(320px, calc(100% - 2 * var(--pane-gap) - 48px))", z: "var(--z-modal)", $md: { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, onChat: toChat, onDev: toDev, go: move, drawer: true }) })] })) : null] }) }) }));
|
|
87
121
|
}
|
package/dist/Column.js
CHANGED
|
@@ -38,6 +38,16 @@ const GUTTER_END = 'calc(-1 * var(--pane-gap))';
|
|
|
38
38
|
/** The sidebar fills the column. Its `width` is typed in px alone, though its frame takes any gui size — the type is
|
|
39
39
|
* @hanzo/ui's to widen. */
|
|
40
40
|
const FILL = { width: '100%' };
|
|
41
|
+
/**
|
|
42
|
+
* The phone's drawer floats over the page under the browser's own glass bar, so
|
|
43
|
+
* it is cut like that bar: the composer's corner, the strong hairline, the lit
|
|
44
|
+
* top edge and the floating drop. Its fill and blur stay the pane's.
|
|
45
|
+
*/
|
|
46
|
+
const FLOAT = {
|
|
47
|
+
borderRadius: 'var(--radius-composer)',
|
|
48
|
+
borderColor: 'var(--border-strong)',
|
|
49
|
+
boxShadow: 'var(--edge-highlight), var(--shadow-floating)',
|
|
50
|
+
};
|
|
41
51
|
/** The column's width, px: its default, and the two a drag holds it between. */
|
|
42
52
|
const WIDTH = 272;
|
|
43
53
|
const FLOOR = 220;
|
|
@@ -89,7 +99,7 @@ export function Column({ host, mode, thread, onChat, onDev, go, drawer = false,
|
|
|
89
99
|
groups.map((g) => (_jsx(YStack, { role: "group", "aria-label": g.label, items: "center", gap: 2, pt: "$1", children: g.links.map((link) => (_jsxs(Fragment, { children: [_jsx(SidebarIconButton, { label: link.label, onPress: () => link.onPress?.(), width: 36, height: 32, rounded: 999, "aria-current": link.active ? 'page' : undefined, children: link.icon }), link.id === NEW ? _jsx(Mark, {}) : null] }, link.id))) }, g.id)))), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, onOrg: org })] }) }));
|
|
90
100
|
return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
|
|
91
101
|
// inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
|
|
92
|
-
style: drawer ? undefined : span.style, position: "relative", shrink: 0, height: "100%", width: drawer ? '100%' : 'var(--span)', ...(drawer ? {} : { '$max-md': { display: 'none' } }), children: [_jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "false", role: "navigation", "aria-label": brand(), ...pane(), ...FILL, px: "$2.5", pt: "$3", pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "mode-switch", items: "center", gap: "$2", px: "$1.5", pb: "$3", minW: 0, children: [_jsx(HanzoMark, { size: 18 }), _jsx(ModeMark, { mode: mode, onChat: onChat, onDev: onDev })] }), _jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", px: "$3", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
|
|
102
|
+
style: drawer ? undefined : span.style, position: "relative", shrink: 0, height: "100%", width: drawer ? '100%' : 'var(--span)', ...(drawer ? {} : { '$max-md': { display: 'none' } }), children: [_jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "false", role: "navigation", "aria-label": brand(), ...pane(), ...(drawer ? FLOAT : null), ...FILL, px: "$2.5", pt: "$3", pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "mode-switch", items: "center", gap: "$2", px: "$1.5", pb: "$3", minW: 0, children: [_jsx(HanzoMark, { size: 18 }), _jsx(ModeMark, { mode: mode, onChat: onChat, onDev: onDev })] }), _jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", px: "$3", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
|
|
93
103
|
if (e.key === 'Escape')
|
|
94
104
|
setWords('');
|
|
95
105
|
}, placeholder: mode === 'chat' ? 'Filter chats' : 'Filter runs', "aria-label": "Filter recents" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$2" })] }), mode === 'chat' ? (_jsxs(YStack, { role: "list", mt: "$2.5", gap: 2, shrink: 0, children: [_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, icon: _jsx(Plus, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: "New chat" }) }), _jsx(Row, {})] })) : (_jsx(YStack, { "data-slot": "app-places", mt: "$1.5", shrink: 0, children: groups.map((g) => (_jsxs(YStack, { pt: "$1.5", children: [_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$1", color: "$soft", fontWeight: "600", textTransform: "uppercase", letterSpacing: 0.6, children: g.label }), _jsx(YStack, { role: "list", "aria-labelledby": `${uid}-${g.id}`, gap: 1, children: g.links.map((link) => (_jsxs(Fragment, { children: [_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: link.label }) }), link.id === NEW ? _jsx(Row, {}) : null] }, link.id))) })] }, g.id))) })), _jsx(XStack, { height: 1, bg: "$borderColor", mx: "$1.5", mt: "$3", shrink: 0 }), _jsx(Recents, { host: host, mode: mode, thread: thread, words: words, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), _jsxs(XStack, { "data-slot": "app-foot", items: "center", gap: "$1", pt: "$2", shrink: 0, children: [_jsx(YStack, { flex: 1, minW: 0, children: _jsx(Me, { host: host, onOrg: org }) }), drawer ? null : (_jsx(SidebarIconButton, { label: "Collapse sidebar", onPress: () => setCollapsed(true), width: 32, height: 32, rounded: 999, children: _jsx(PanelLeft, { size: 16 }) }))] })] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: () => setCollapsed(true), label: "Resize sidebar",
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { type Host } from '@hanzo/build';
|
|
2
|
+
/** Whether a builder address names the plans. */
|
|
3
|
+
export declare const isPlans: (to: string) => boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Leaves for the plans on the pay site (`/pay/cart`), for the organization in
|
|
6
|
+
* use. `from` is the page to come back to once paid: this one, or Dev when the
|
|
7
|
+
* address itself named the plans, which is replaced rather than returned to so
|
|
8
|
+
* Back and the pay site's return both land in the app.
|
|
9
|
+
*/
|
|
10
|
+
export declare function leave(from: 'here' | 'app'): void;
|
|
2
11
|
/** The host for a signed-in reader. */
|
|
3
12
|
export declare function useDevHost(): Host;
|
package/dist/dev.js
CHANGED
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
// rooms' shell draws its own Settings pane, and the app has none, so Chat's
|
|
20
20
|
// "See usage" opens the builder's Usage — the page the account menu's Usage
|
|
21
21
|
// opens — rather than nothing.
|
|
22
|
+
//
|
|
23
|
+
// THE PLANS ARE THE PAY SITE'S. Every way the app reaches the plans — the
|
|
24
|
+
// account menu's View all plans and its plan's Upgrade, Billing's Adjust plan,
|
|
25
|
+
// an automation's See plans — names the builder's plans address, and `go`
|
|
26
|
+
// leaves for the pay site's plans there (`leave`), for the organization in use,
|
|
27
|
+
// coming back once paid. A typed or reloaded plans address leaves the same way
|
|
28
|
+
// (App.tsx). A page load, at once, not a move inside the app: the pay site is
|
|
29
|
+
// where a plan is bought.
|
|
22
30
|
import { useEffect, useMemo } from 'react';
|
|
23
31
|
import { useLook, useRooms } from './host.js';
|
|
24
32
|
import { useIam } from '@hanzo/iam/react';
|
|
@@ -29,6 +37,7 @@ import { enter } from './lib/destination.js';
|
|
|
29
37
|
import { api } from './lib/api.js';
|
|
30
38
|
import { app, go, place } from './lib/host.js';
|
|
31
39
|
import { link } from './lib/tags.js';
|
|
40
|
+
import { payUrl } from './lib/pay.js';
|
|
32
41
|
import { showSettings, useOpen } from './open.js';
|
|
33
42
|
/** Where the builder links out: every one of them an address of this site. */
|
|
34
43
|
const LINKS = {
|
|
@@ -37,6 +46,24 @@ const LINKS = {
|
|
|
37
46
|
settings: app('-/settings'),
|
|
38
47
|
home: '/',
|
|
39
48
|
};
|
|
49
|
+
/** The builder's address for the plans. */
|
|
50
|
+
const PLANS = path({ kind: 'screen', screen: 'plans' });
|
|
51
|
+
/** Whether a builder address names the plans. */
|
|
52
|
+
export const isPlans = (to) => path(route(to)) === PLANS;
|
|
53
|
+
/**
|
|
54
|
+
* Leaves for the plans on the pay site (`/pay/cart`), for the organization in
|
|
55
|
+
* use. `from` is the page to come back to once paid: this one, or Dev when the
|
|
56
|
+
* address itself named the plans, which is replaced rather than returned to so
|
|
57
|
+
* Back and the pay site's return both land in the app.
|
|
58
|
+
*/
|
|
59
|
+
export function leave(from) {
|
|
60
|
+
const back = from === 'here' ? window.location.href : `${window.location.origin}${app('')}`;
|
|
61
|
+
const to = payUrl('/cart', { org: org(), returnUrl: back });
|
|
62
|
+
if (from === 'here')
|
|
63
|
+
window.location.assign(to);
|
|
64
|
+
else
|
|
65
|
+
window.location.replace(to);
|
|
66
|
+
}
|
|
40
67
|
/** The person's theme, when they have chosen one the builder can name. */
|
|
41
68
|
const theme = (t) => (t === 'light' || t === 'dark' || t === 'system' ? t : undefined);
|
|
42
69
|
/** The builder address a pathname names: Dev's place, or New for a path that is not Dev's. */
|
|
@@ -88,7 +115,7 @@ export function useDevHost() {
|
|
|
88
115
|
person: { name, email, avatar },
|
|
89
116
|
admin: administers(bearer(), scoped),
|
|
90
117
|
path: here,
|
|
91
|
-
go: (to, how) => go(app(to), how?.replace),
|
|
118
|
+
go: (to, how) => (isPlans(to) ? leave('here') : go(app(to), how?.replace)),
|
|
92
119
|
links: LINKS,
|
|
93
120
|
// An address of the app moves the app, another of this site moves in the
|
|
94
121
|
// router, and another origin is left in this tab. platform.hanzo.ai is
|
|
@@ -54,6 +54,21 @@ export declare function door(): string;
|
|
|
54
54
|
* the page is `door()`'s; a control that says "Sign in" names LOGIN.
|
|
55
55
|
*/
|
|
56
56
|
export declare function enter(to?: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* The campaign a visit arrived with: the UTM parameters and the ad platforms'
|
|
59
|
+
* click ids. They ride to the sign-in page as its own parameters, so the page a
|
|
60
|
+
* campaign's visitor signs up on is a landing with that campaign, not one with
|
|
61
|
+
* it folded inside `next=`.
|
|
62
|
+
*/
|
|
63
|
+
export declare const CAMPAIGN: readonly ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content", "gclid", "gbraid", "wbraid", "fbclid", "li_fat_id", "twclid", "ttclid"];
|
|
64
|
+
/**
|
|
65
|
+
* The sign-in address for a reader on `here`: `to`, with `here` as its `next=`
|
|
66
|
+
* when that is a page worth coming back to, and the campaign `here` carries as
|
|
67
|
+
* `to`'s own parameters. The page itself is written in the address too, so the
|
|
68
|
+
* place (a room, a prompt in `?q=`) survives what sessionStorage does not: a new
|
|
69
|
+
* tab, a pasted link.
|
|
70
|
+
*/
|
|
71
|
+
export declare function entry(to: string, here: string): string;
|
|
57
72
|
/**
|
|
58
73
|
* The `?next=` a sign-in page was opened with, when it is an address on this
|
|
59
74
|
* site (`safe`): where the sign-in goes on to. Anything else is undefined.
|
package/dist/lib/destination.js
CHANGED
|
@@ -119,10 +119,39 @@ export function enter(to = door()) {
|
|
|
119
119
|
if (handoff(here))
|
|
120
120
|
return;
|
|
121
121
|
rememberDestination();
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
window.location.assign(entry(to, here));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The campaign a visit arrived with: the UTM parameters and the ad platforms'
|
|
126
|
+
* click ids. They ride to the sign-in page as its own parameters, so the page a
|
|
127
|
+
* campaign's visitor signs up on is a landing with that campaign, not one with
|
|
128
|
+
* it folded inside `next=`.
|
|
129
|
+
*/
|
|
130
|
+
export const CAMPAIGN = [
|
|
131
|
+
"utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content",
|
|
132
|
+
"gclid", "gbraid", "wbraid", "fbclid", "li_fat_id", "twclid", "ttclid",
|
|
133
|
+
];
|
|
134
|
+
/**
|
|
135
|
+
* The sign-in address for a reader on `here`: `to`, with `here` as its `next=`
|
|
136
|
+
* when that is a page worth coming back to, and the campaign `here` carries as
|
|
137
|
+
* `to`'s own parameters. The page itself is written in the address too, so the
|
|
138
|
+
* place (a room, a prompt in `?q=`) survives what sessionStorage does not: a new
|
|
139
|
+
* tab, a pasted link.
|
|
140
|
+
*/
|
|
141
|
+
export function entry(to, here) {
|
|
142
|
+
const [path, query = ""] = to.split("?");
|
|
143
|
+
const out = new URLSearchParams(query);
|
|
124
144
|
const back = safe(here, "");
|
|
125
|
-
|
|
145
|
+
if (back && back !== "/")
|
|
146
|
+
out.set("next", back);
|
|
147
|
+
const from = new URLSearchParams(here.split("#")[0].split("?")[1] ?? "");
|
|
148
|
+
for (const k of CAMPAIGN) {
|
|
149
|
+
const v = from.get(k)?.trim();
|
|
150
|
+
if (v && !out.has(k))
|
|
151
|
+
out.set(k, v);
|
|
152
|
+
}
|
|
153
|
+
const q = out.toString();
|
|
154
|
+
return q ? `${path}?${q}` : path;
|
|
126
155
|
}
|
|
127
156
|
/**
|
|
128
157
|
* The `?next=` a sign-in page was opened with, when it is an address on this
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|