@autono/open-pages 0.6.0 → 0.8.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/dist/{build-6hM9Ga2d.js → build-CCoxDxL-.js} +11 -10
- package/dist/cli/bin.d.ts +1 -1
- package/dist/cli/bin.js +427 -30
- package/dist/{config-CBbwLHOo.js → config-D_U_aCQz.js} +484 -460
- package/dist/{config-C-sasGou.d.ts → config-p-TWNXbv.d.ts} +4 -4
- package/dist/{design-C13iz9_4.js → design-Cpqx7SJs.js} +1 -2
- package/dist/{dev-BO0cHiYy.js → dev-CTrB263u.js} +40 -20
- package/dist/{export-DbVwSmKW.js → export-enKL9R7x.js} +7 -10
- package/dist/index.d.ts +6 -8
- package/dist/index.js +2 -3
- package/dist/locale/index.d.ts +8 -14
- package/dist/locale/index.js +67 -32
- package/dist/{open-pages-plugin-C0poXLsx.js → open-pages-plugin-C8cFhMB9.js} +23 -38
- package/dist/preview-Dmzyd1cf.js +24 -0
- package/dist/{sync-ui-CDydLpY4.js → sync-ui-BzRiAGCK.js} +3 -5
- package/dist/{types-BzxkFHhd.d.ts → types-DDdUFUIS.d.ts} +19 -6
- package/dist/{update-D1U8TVwd.js → update-CWaCKlnl.js} +4 -7
- package/dist/{update-package-D9zws9nc.js → update-package-GaNsVdr8.js} +44 -22
- package/dist/{version-B6AiiGvT.js → version-DZrXLoAD.js} +19 -3
- package/dist/vite/index.d.ts +3 -7
- package/dist/vite/index.js +2 -7
- package/package.json +22 -21
- package/skills/apply-comments/SKILL.md +1 -0
- package/skills/create-page/SKILL.md +5 -1
- package/skills/create-theme/SKILL.md +2 -0
- package/skills/page-authoring/SKILL.md +3 -0
- package/skills/web-design-guidelines/SKILL.md +41 -0
- package/src/app/app.tsx +19 -14
- package/src/app/assets/autono-social.png +0 -0
- package/src/app/assets/autono.svg +7 -0
- package/src/app/components/asset-view.tsx +510 -26
- package/src/app/components/command/command-menu.tsx +5 -1
- package/src/app/components/command/command.tsx +4 -2
- package/src/app/components/command/home-command-menu.tsx +6 -6
- package/src/app/components/icon-tooltip.tsx +24 -15
- package/src/app/components/sidebar/folder-item.tsx +46 -19
- package/src/app/components/sidebar/icon-picker.tsx +1 -1
- package/src/app/components/sidebar/sidebar-footer.tsx +18 -7
- package/src/app/components/sidebar/sidebar.tsx +91 -64
- package/src/app/components/theme-toggle.tsx +4 -5
- package/src/app/components/themes/theme-detail.tsx +6 -3
- package/src/app/components/themes/themes-gallery.tsx +12 -7
- package/src/app/index.html +33 -2
- package/src/app/lib/assets.ts +33 -0
- package/src/app/lib/last-home-location.ts +18 -0
- package/src/app/main.tsx +10 -1
- package/src/app/routes/home-shell.tsx +82 -61
- package/src/app/routes/home.tsx +42 -39
- package/src/app/routes/page.tsx +30 -10
- package/src/app/routes/themes.tsx +6 -8
- package/src/app/styles.css +40 -4
- package/src/app/virtual.d.ts +1 -0
- package/src/locale/en.ts +17 -6
- package/src/locale/ja.ts +18 -6
- package/src/locale/types.ts +17 -4
- package/src/locale/zh-cn.ts +19 -8
- package/src/locale/zh-tw.ts +17 -6
- package/src/shared/update-package.ts +193 -0
- package/workspace/deps.json +1 -1
- package/dist/export-BsK6OCpc.js +0 -4
- package/dist/preview-sOKhzyWB.js +0 -21
- package/dist/sync-Be_hxSOF.js +0 -139
- package/dist/sync-lKEXyPOh.js +0 -3
- package/dist/version-CeffgdfZ.js +0 -3
- package/src/app/components/language-toggle.tsx +0 -46
- package/src/app/favicon.ico +0 -0
package/src/app/index.html
CHANGED
|
@@ -2,9 +2,40 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" href="./
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="./assets/autono.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>
|
|
7
|
+
<title>Autono</title>
|
|
8
|
+
<meta name="description" content="Autono — the web page framework built for agents." />
|
|
9
|
+
<meta property="og:title" content="Autono" />
|
|
10
|
+
<meta property="og:site_name" content="Autono" />
|
|
11
|
+
<meta property="og:description" content="The web page framework built for agents." />
|
|
12
|
+
<meta property="og:type" content="website" />
|
|
13
|
+
<meta property="og:image" content="./assets/autono-social.png" />
|
|
14
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
15
|
+
<meta name="twitter:title" content="Autono" />
|
|
16
|
+
<meta name="twitter:description" content="The web page framework built for agents." />
|
|
17
|
+
<meta name="twitter:image" content="./assets/autono-social.png" />
|
|
18
|
+
<style>
|
|
19
|
+
html {
|
|
20
|
+
background-color: oklch(0.99 0 0);
|
|
21
|
+
}
|
|
22
|
+
html.dark {
|
|
23
|
+
background-color: oklch(0.145 0 0);
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
26
|
+
<script>
|
|
27
|
+
// Apply the stored theme before first paint; next-themes only runs
|
|
28
|
+
// after the bundle loads, which flashes white for dark-mode users.
|
|
29
|
+
let stored = null;
|
|
30
|
+
try {
|
|
31
|
+
stored = localStorage.getItem('theme');
|
|
32
|
+
} catch (_) {}
|
|
33
|
+
const dark =
|
|
34
|
+
stored === 'dark' ||
|
|
35
|
+
(stored !== 'light' && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
36
|
+
document.documentElement.classList.toggle('dark', dark);
|
|
37
|
+
document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
|
|
38
|
+
</script>
|
|
8
39
|
</head>
|
|
9
40
|
<body>
|
|
10
41
|
<div id="root"></div>
|
package/src/app/lib/assets.ts
CHANGED
|
@@ -156,6 +156,39 @@ export async function fetchSvgAsFile(routeUrl: string, filename: string): Promis
|
|
|
156
156
|
return new File([blob], filename, { type: 'image/svg+xml' });
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
export type GoogleFontItem = {
|
|
160
|
+
family: string;
|
|
161
|
+
category: string;
|
|
162
|
+
// Weight keys from the Google Fonts catalog, e.g. ['400', '400i', '700'].
|
|
163
|
+
variants: string[];
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export async function searchGoogleFonts(
|
|
167
|
+
query: string,
|
|
168
|
+
signal?: AbortSignal,
|
|
169
|
+
): Promise<GoogleFontItem[]> {
|
|
170
|
+
const q = query.trim();
|
|
171
|
+
const params = new URLSearchParams();
|
|
172
|
+
if (q) params.set('q', q);
|
|
173
|
+
else params.set('limit', '30');
|
|
174
|
+
const res = await fetch(`/__gfonts/search?${params.toString()}`, { signal });
|
|
175
|
+
if (!res.ok) throw new Error(`gfonts ${res.status}`);
|
|
176
|
+
return (await res.json()) as GoogleFontItem[];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function fetchFontAsFile(
|
|
180
|
+
family: string,
|
|
181
|
+
variant: string,
|
|
182
|
+
filenameBase: string,
|
|
183
|
+
): Promise<File> {
|
|
184
|
+
const params = new URLSearchParams({ family, variant });
|
|
185
|
+
const res = await fetch(`/__gfonts/download?${params.toString()}`);
|
|
186
|
+
if (!res.ok) throw new Error(`gfonts download ${res.status}`);
|
|
187
|
+
const ext = res.headers.get('x-font-ext') || 'ttf';
|
|
188
|
+
const blob = await res.blob();
|
|
189
|
+
return new File([blob], `${filenameBase}.${ext}`, { type: blob.type || 'font/ttf' });
|
|
190
|
+
}
|
|
191
|
+
|
|
159
192
|
export type UseAssetsResult = {
|
|
160
193
|
assets: AssetEntry[];
|
|
161
194
|
loading: boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const LAST_HOME_LOCATION_KEY = 'open-pages:last-home-location';
|
|
2
|
+
|
|
3
|
+
export function rememberHomeLocation(pathname: string, search: string): void {
|
|
4
|
+
try {
|
|
5
|
+
sessionStorage.setItem(LAST_HOME_LOCATION_KEY, pathname + search);
|
|
6
|
+
} catch {
|
|
7
|
+
// sessionStorage can be unavailable (private browsing, quota); falling
|
|
8
|
+
// back to the default home route is fine.
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function readLastHomeLocation(): string {
|
|
13
|
+
try {
|
|
14
|
+
return sessionStorage.getItem(LAST_HOME_LOCATION_KEY) ?? '/';
|
|
15
|
+
} catch {
|
|
16
|
+
return '/';
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/app/main.tsx
CHANGED
|
@@ -7,7 +7,16 @@ import './styles.css';
|
|
|
7
7
|
// biome-ignore lint/style/noNonNullAssertion: #root is guaranteed by index.html
|
|
8
8
|
createRoot(document.getElementById('root')!).render(
|
|
9
9
|
<StrictMode>
|
|
10
|
-
<
|
|
10
|
+
{/* next-themes injects an anti-flash <script> meant for SSR; in this
|
|
11
|
+
client-only app it never runs, and React 19 warns about executable
|
|
12
|
+
script tags, so mark it as a data block. */}
|
|
13
|
+
<ThemeProvider
|
|
14
|
+
attribute="class"
|
|
15
|
+
defaultTheme="system"
|
|
16
|
+
enableSystem
|
|
17
|
+
disableTransitionOnChange
|
|
18
|
+
scriptProps={{ type: 'application/json' }}
|
|
19
|
+
>
|
|
11
20
|
<App />
|
|
12
21
|
</ThemeProvider>
|
|
13
22
|
</StrictMode>,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Menu } from 'lucide-react';
|
|
2
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { Outlet, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
|
-
import
|
|
5
|
+
import logo from '~/assets/autono.svg';
|
|
6
6
|
import { ThemeToggle } from '~/components/theme-toggle';
|
|
7
7
|
import {
|
|
8
8
|
DropdownMenu,
|
|
@@ -12,11 +12,12 @@ import {
|
|
|
12
12
|
} from '~/components/ui/dropdown-menu';
|
|
13
13
|
import { useAssets } from '~/lib/assets';
|
|
14
14
|
import { useFolders } from '~/lib/folders';
|
|
15
|
+
import { rememberHomeLocation } from '~/lib/last-home-location';
|
|
15
16
|
import { format, useLocale } from '~/lib/use-locale';
|
|
16
17
|
import { cn } from '~/lib/utils';
|
|
17
18
|
import { CommandMenuTrigger } from '../components/command/command-menu';
|
|
18
19
|
import { HomeCommandMenu } from '../components/command/home-command-menu';
|
|
19
|
-
import {
|
|
20
|
+
import { SystemViewIcon } from '../components/sidebar/folder-item';
|
|
20
21
|
import { ALL_DOCS_ID, ASSETS_ID, Sidebar, THEMES_ID } from '../components/sidebar/sidebar';
|
|
21
22
|
import { pageIds } from '../lib/pages';
|
|
22
23
|
import type { FoldersManifest } from '../lib/sdk';
|
|
@@ -64,6 +65,10 @@ export function HomeShell() {
|
|
|
64
65
|
|
|
65
66
|
const selectedId = pathToSelectedId(location.pathname, searchParams);
|
|
66
67
|
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
rememberHomeLocation(location.pathname, location.search);
|
|
70
|
+
}, [location.pathname, location.search]);
|
|
71
|
+
|
|
67
72
|
const [commandOpen, setCommandOpen] = useState(false);
|
|
68
73
|
const openCommandMenu = useCallback(() => setCommandOpen(true), []);
|
|
69
74
|
|
|
@@ -138,7 +143,7 @@ export function HomeShell() {
|
|
|
138
143
|
};
|
|
139
144
|
|
|
140
145
|
return (
|
|
141
|
-
<div className="flex h-dvh overflow-hidden bg-
|
|
146
|
+
<div className="flex h-dvh overflow-hidden bg-sidebar text-foreground">
|
|
142
147
|
<div className="hidden md:block">
|
|
143
148
|
<Sidebar
|
|
144
149
|
folders={manifest.folders}
|
|
@@ -174,69 +179,85 @@ export function HomeShell() {
|
|
|
174
179
|
/>
|
|
175
180
|
</div>
|
|
176
181
|
|
|
177
|
-
<div className="relative flex min-w-0 flex-1 flex-col
|
|
178
|
-
<div className="flex
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<button
|
|
188
|
-
type="button"
|
|
189
|
-
aria-label={t.home.menu}
|
|
190
|
-
className="flex size-8 items-center justify-center rounded-[6px] text-muted-foreground hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground"
|
|
191
|
-
>
|
|
192
|
-
<Menu className="size-4" />
|
|
193
|
-
</button>
|
|
194
|
-
}
|
|
182
|
+
<div className="relative flex min-w-0 flex-1 flex-col md:py-2 md:pr-2">
|
|
183
|
+
<div className="relative flex min-w-0 flex-1 flex-col overflow-y-auto bg-background md:rounded-[10px] md:shadow-edge md:ring-1 md:ring-foreground/[0.06]">
|
|
184
|
+
<div className="flex items-center justify-between border-b border-hairline bg-sidebar px-4 py-3 md:hidden">
|
|
185
|
+
<div className="flex min-w-0 items-center gap-2.5">
|
|
186
|
+
<img
|
|
187
|
+
src={logo}
|
|
188
|
+
alt=""
|
|
189
|
+
aria-hidden
|
|
190
|
+
draggable={false}
|
|
191
|
+
className="size-6 shrink-0 select-none rounded-[6px] ring-1 ring-foreground/10"
|
|
195
192
|
/>
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
</DropdownMenuItem>
|
|
217
|
-
{import.meta.env.DEV && (
|
|
193
|
+
<h1 className="truncate font-heading text-lg font-bold tracking-tight">
|
|
194
|
+
{t.home.appTitle}
|
|
195
|
+
</h1>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="-mr-1.5 flex items-center gap-0.5">
|
|
198
|
+
<CommandMenuTrigger onClick={openCommandMenu} />
|
|
199
|
+
<ThemeToggle />
|
|
200
|
+
<DropdownMenu>
|
|
201
|
+
<DropdownMenuTrigger
|
|
202
|
+
render={
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
aria-label={t.home.menu}
|
|
206
|
+
className="flex size-8 items-center justify-center rounded-[6px] text-muted-foreground outline-none transition-[background-color,color,scale] duration-100 hover:bg-muted hover:text-foreground active:scale-95 focus-visible:ring-2 focus-visible:ring-ring/30 aria-expanded:bg-muted aria-expanded:text-foreground"
|
|
207
|
+
>
|
|
208
|
+
<Menu className="size-4" />
|
|
209
|
+
</button>
|
|
210
|
+
}
|
|
211
|
+
/>
|
|
212
|
+
<DropdownMenuContent align="end" className="min-w-[200px]">
|
|
218
213
|
<DropdownMenuItem
|
|
219
|
-
onClick={() => selectFolder(
|
|
220
|
-
className={cn(
|
|
214
|
+
onClick={() => selectFolder(ALL_DOCS_ID)}
|
|
215
|
+
className={cn(
|
|
216
|
+
selectedId !== THEMES_ID &&
|
|
217
|
+
selectedId !== ASSETS_ID &&
|
|
218
|
+
'bg-muted text-foreground',
|
|
219
|
+
)}
|
|
221
220
|
>
|
|
222
|
-
<
|
|
223
|
-
<span className="flex-1 truncate">{t.home.
|
|
224
|
-
<span className="folio">{
|
|
221
|
+
<SystemViewIcon kind="all" className="text-muted-foreground" />
|
|
222
|
+
<span className="flex-1 truncate">{t.home.pages}</span>
|
|
223
|
+
<span className="folio">{pageIds.length.toString().padStart(2, '0')}</span>
|
|
225
224
|
</DropdownMenuItem>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
<DropdownMenuItem
|
|
226
|
+
onClick={() => selectFolder(THEMES_ID)}
|
|
227
|
+
className={cn(selectedId === THEMES_ID && 'bg-muted text-foreground')}
|
|
228
|
+
>
|
|
229
|
+
<SystemViewIcon kind="themes" className="text-muted-foreground" />
|
|
230
|
+
<span className="flex-1 truncate">{t.home.themes}</span>
|
|
231
|
+
<span className="folio">
|
|
232
|
+
{themeRegistry.length.toString().padStart(2, '0')}
|
|
233
|
+
</span>
|
|
234
|
+
</DropdownMenuItem>
|
|
235
|
+
{import.meta.env.DEV && (
|
|
236
|
+
<DropdownMenuItem
|
|
237
|
+
onClick={() => selectFolder(ASSETS_ID)}
|
|
238
|
+
className={cn(selectedId === ASSETS_ID && 'bg-muted text-foreground')}
|
|
239
|
+
>
|
|
240
|
+
<SystemViewIcon kind="assets" className="text-muted-foreground" />
|
|
241
|
+
<span className="flex-1 truncate">{t.home.assets}</span>
|
|
242
|
+
<span className="folio">
|
|
243
|
+
{globalAssets.length.toString().padStart(2, '0')}
|
|
244
|
+
</span>
|
|
245
|
+
</DropdownMenuItem>
|
|
246
|
+
)}
|
|
247
|
+
</DropdownMenuContent>
|
|
248
|
+
</DropdownMenu>
|
|
249
|
+
</div>
|
|
229
250
|
</div>
|
|
230
|
-
</div>
|
|
231
251
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
252
|
+
<div
|
|
253
|
+
className={cn(
|
|
254
|
+
isAssetsRoute
|
|
255
|
+
? 'flex min-h-0 flex-1 flex-col'
|
|
256
|
+
: 'mx-auto w-full max-w-[1180px] px-5 py-8 md:px-10 md:py-12',
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
<Outlet context={ctx} />
|
|
260
|
+
</div>
|
|
240
261
|
</div>
|
|
241
262
|
</div>
|
|
242
263
|
|
package/src/app/routes/home.tsx
CHANGED
|
@@ -32,11 +32,11 @@ import {
|
|
|
32
32
|
} from '~/components/ui/dropdown-menu';
|
|
33
33
|
import { format, useLocale } from '~/lib/use-locale';
|
|
34
34
|
import { cn } from '~/lib/utils';
|
|
35
|
-
import { DOC_DND_MIME, FolderIconChip } from '../components/sidebar/folder-item';
|
|
35
|
+
import { DOC_DND_MIME, FolderIconChip, SystemViewIcon } from '../components/sidebar/folder-item';
|
|
36
36
|
import { ALL_DOCS_ID, DRAFT_ID } from '../components/sidebar/sidebar';
|
|
37
37
|
import { PageThumb } from '../lib/page-thumb';
|
|
38
38
|
import { loadPage, pageCreatedAt, pageIds, pageKinds } from '../lib/pages';
|
|
39
|
-
import type { Folder,
|
|
39
|
+
import type { Folder, PageModule } from '../lib/sdk';
|
|
40
40
|
import type { HomeOutletContext } from './home-shell';
|
|
41
41
|
|
|
42
42
|
type SortKey = 'created-desc' | 'created-asc' | 'title-asc' | 'title-desc';
|
|
@@ -92,10 +92,6 @@ export function Home() {
|
|
|
92
92
|
const visibleDocs = isAll ? pageIds : isDraft ? draftDocs : (docsByFolder[selectedId] ?? []);
|
|
93
93
|
|
|
94
94
|
const title = selectedFolder?.name ?? (isAll ? t.home.pages : t.home.draft);
|
|
95
|
-
const headerIcon = selectedFolder?.icon ?? {
|
|
96
|
-
type: 'emoji' as const,
|
|
97
|
-
value: isAll ? '🎞️' : '📝',
|
|
98
|
-
};
|
|
99
95
|
|
|
100
96
|
const [query, setQuery] = useState('');
|
|
101
97
|
const [sortKey, setSortKey] = useSortPref();
|
|
@@ -131,10 +127,14 @@ export function Home() {
|
|
|
131
127
|
|
|
132
128
|
return (
|
|
133
129
|
<>
|
|
134
|
-
<header className="mb-
|
|
135
|
-
<div className="flex flex-wrap items-center gap-
|
|
136
|
-
|
|
137
|
-
|
|
130
|
+
<header className="mb-6 md:mb-8">
|
|
131
|
+
<div className="flex flex-wrap items-center gap-2.5">
|
|
132
|
+
{selectedFolder ? (
|
|
133
|
+
<FolderIconChip icon={selectedFolder.icon} className="size-5 text-[16px]" />
|
|
134
|
+
) : (
|
|
135
|
+
<SystemViewIcon kind={isAll ? 'all' : 'draft'} className="text-muted-foreground" />
|
|
136
|
+
)}
|
|
137
|
+
<h1 className="font-heading text-[19px] font-semibold leading-none tracking-[-0.015em] md:text-[21px]">
|
|
138
138
|
{title}
|
|
139
139
|
</h1>
|
|
140
140
|
<DropdownMenu>
|
|
@@ -143,7 +143,7 @@ export function Home() {
|
|
|
143
143
|
<button
|
|
144
144
|
type="button"
|
|
145
145
|
aria-label={t.home.folders}
|
|
146
|
-
className="flex size-7 items-center justify-center rounded-[6px] border border-border bg-card text-muted-foreground hover:text-foreground aria-expanded:border-foreground/40 aria-expanded:text-foreground md:hidden"
|
|
146
|
+
className="flex size-7 items-center justify-center rounded-[6px] border border-border bg-card text-muted-foreground outline-none transition-[background-color,color,scale] duration-100 hover:bg-muted hover:text-foreground active:scale-95 focus-visible:ring-2 focus-visible:ring-ring/30 aria-expanded:border-foreground/40 aria-expanded:text-foreground md:hidden"
|
|
147
147
|
>
|
|
148
148
|
<ChevronDown className="size-4" />
|
|
149
149
|
</button>
|
|
@@ -154,7 +154,7 @@ export function Home() {
|
|
|
154
154
|
onClick={() => selectFolder(ALL_DOCS_ID)}
|
|
155
155
|
className={cn(isAll && 'bg-muted text-foreground')}
|
|
156
156
|
>
|
|
157
|
-
<
|
|
157
|
+
<SystemViewIcon kind="all" className="text-muted-foreground" />
|
|
158
158
|
<span className="flex-1 truncate">{t.home.pages}</span>
|
|
159
159
|
<span className="folio">{pageIds.length.toString().padStart(2, '0')}</span>
|
|
160
160
|
</DropdownMenuItem>
|
|
@@ -162,7 +162,7 @@ export function Home() {
|
|
|
162
162
|
onClick={() => selectFolder(DRAFT_ID)}
|
|
163
163
|
className={cn(isDraft && 'bg-muted text-foreground')}
|
|
164
164
|
>
|
|
165
|
-
<
|
|
165
|
+
<SystemViewIcon kind="draft" className="text-muted-foreground" />
|
|
166
166
|
<span className="flex-1 truncate">{t.home.draft}</span>
|
|
167
167
|
<span className="folio">{draftDocs.length.toString().padStart(2, '0')}</span>
|
|
168
168
|
</DropdownMenuItem>
|
|
@@ -182,7 +182,7 @@ export function Home() {
|
|
|
182
182
|
</DropdownMenuContent>
|
|
183
183
|
</DropdownMenu>
|
|
184
184
|
{!loading && (
|
|
185
|
-
<span className="folio ml-
|
|
185
|
+
<span className="folio ml-0.5">
|
|
186
186
|
{(isSearching ? filteredDocs.length : visibleDocs.length).toString().padStart(2, '0')}
|
|
187
187
|
{isSearching && (
|
|
188
188
|
<span className="opacity-40">
|
|
@@ -206,8 +206,12 @@ export function Home() {
|
|
|
206
206
|
<NoResultsState query={query} onClear={() => setQuery('')} />
|
|
207
207
|
) : (
|
|
208
208
|
<ul className="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-x-6 gap-y-9 md:grid-cols-[repeat(auto-fill,minmax(300px,1fr))]">
|
|
209
|
-
{sortedDocs.map((id) => (
|
|
210
|
-
<li
|
|
209
|
+
{sortedDocs.map((id, i) => (
|
|
210
|
+
<li
|
|
211
|
+
key={id}
|
|
212
|
+
className="rise-in"
|
|
213
|
+
style={{ animationDelay: `${Math.min(i, 11) * 30}ms` }}
|
|
214
|
+
>
|
|
211
215
|
<DocCard
|
|
212
216
|
id={id}
|
|
213
217
|
folders={manifest.folders}
|
|
@@ -259,7 +263,7 @@ function SearchInput({ value, onChange }: { value: string; onChange: (value: str
|
|
|
259
263
|
type="button"
|
|
260
264
|
onClick={() => onChange('')}
|
|
261
265
|
aria-label={t.home.clearSearch}
|
|
262
|
-
className="absolute right-1.5 top-1/2 flex size-5 -translate-y-1/2 items-center justify-center rounded-[4px] text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
266
|
+
className="absolute right-1.5 top-1/2 flex size-5 -translate-y-1/2 items-center justify-center rounded-[4px] text-muted-foreground outline-none transition-[background-color,color,scale] duration-100 hover:bg-muted hover:text-foreground active:scale-90 focus-visible:ring-2 focus-visible:ring-ring/30"
|
|
263
267
|
>
|
|
264
268
|
<X className="size-3" />
|
|
265
269
|
</button>
|
|
@@ -289,7 +293,7 @@ function SortControl({ value, onChange }: { value: SortKey; onChange: (next: Sor
|
|
|
289
293
|
<button
|
|
290
294
|
type="button"
|
|
291
295
|
aria-label={`${t.home.sortLabel}: ${labels[value]}`}
|
|
292
|
-
className="flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-[6px] border border-border bg-background pl-2 pr-1.5 text-[12.5px] font-medium text-foreground outline-none hover:bg-muted focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30"
|
|
296
|
+
className="flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-[6px] border border-border bg-background pl-2 pr-1.5 text-[12.5px] font-medium text-foreground outline-none transition-[background-color,translate] duration-100 hover:bg-muted active:translate-y-px focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30"
|
|
293
297
|
>
|
|
294
298
|
<FieldIcon k={value} className="size-3.5 text-muted-foreground" />
|
|
295
299
|
<span>{labels[value]}</span>
|
|
@@ -336,12 +340,10 @@ function HomeLoading() {
|
|
|
336
340
|
function NoResultsState({ query, onClear }: { query: string; onClear: () => void }) {
|
|
337
341
|
const t = useLocale();
|
|
338
342
|
return (
|
|
339
|
-
<div className="rounded-[
|
|
343
|
+
<div className="rounded-[8px] border border-dashed border-border px-8 py-20">
|
|
340
344
|
<div className="mx-auto flex max-w-md flex-col items-center text-center">
|
|
341
|
-
<
|
|
342
|
-
|
|
343
|
-
</div>
|
|
344
|
-
<p className="mt-4 font-heading text-[15px] font-semibold tracking-tight">
|
|
345
|
+
<Search className="size-5 text-muted-foreground/60" aria-hidden />
|
|
346
|
+
<p className="mt-4 font-heading text-[14px] font-semibold tracking-tight">
|
|
345
347
|
{t.home.noMatches}
|
|
346
348
|
</p>
|
|
347
349
|
<p className="mt-1.5 text-[13px] leading-relaxed text-muted-foreground">
|
|
@@ -364,14 +366,12 @@ function EmptyState({ isDraft, folderName }: { isDraft: boolean; folderName?: st
|
|
|
364
366
|
folderName ?? t.home.folderEmptyTitle,
|
|
365
367
|
);
|
|
366
368
|
return (
|
|
367
|
-
<div className="rounded-[
|
|
369
|
+
<div className="rounded-[8px] border border-dashed border-border px-8 py-20">
|
|
368
370
|
<div className="mx-auto flex max-w-md flex-col items-center text-center">
|
|
369
|
-
<
|
|
370
|
-
<FolderPlus className="size-5" />
|
|
371
|
-
</div>
|
|
371
|
+
<FolderPlus className="size-5 text-muted-foreground/60" aria-hidden />
|
|
372
372
|
{isDraft ? (
|
|
373
373
|
<>
|
|
374
|
-
<p className="mt-4 font-heading text-[
|
|
374
|
+
<p className="mt-4 font-heading text-[14px] font-semibold tracking-tight">
|
|
375
375
|
{t.home.noDocsYet}
|
|
376
376
|
</p>
|
|
377
377
|
<p className="mt-1.5 text-[13px] leading-relaxed text-muted-foreground">
|
|
@@ -384,7 +384,7 @@ function EmptyState({ isDraft, folderName }: { isDraft: boolean; folderName?: st
|
|
|
384
384
|
</>
|
|
385
385
|
) : (
|
|
386
386
|
<>
|
|
387
|
-
<p className="mt-4 font-heading text-[
|
|
387
|
+
<p className="mt-4 font-heading text-[14px] font-semibold tracking-tight">
|
|
388
388
|
{folderEmptyTitle}
|
|
389
389
|
</p>
|
|
390
390
|
<p className="mt-1.5 text-[13px] leading-relaxed text-muted-foreground">
|
|
@@ -502,9 +502,12 @@ function DocCard({
|
|
|
502
502
|
onDragEnd={() => setDragging(false)}
|
|
503
503
|
className={cn('group relative motion-safe:transition-opacity', dragging && 'opacity-40')}
|
|
504
504
|
>
|
|
505
|
-
<Link
|
|
505
|
+
<Link
|
|
506
|
+
to={`/p/${id}`}
|
|
507
|
+
className="block rounded-[6px] outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
508
|
+
>
|
|
506
509
|
{/* Page thumb — tight border, grey baseboard, no shadcn rounded-xl */}
|
|
507
|
-
<div className="relative aspect-video overflow-hidden rounded-[6px] border border-hairline bg-card shadow-edge ring-1 ring-foreground/[0.04] group-hover:shadow-floating group-hover:ring-foreground/20 motion-safe:transition-[box-shadow,--tw-ring-color] motion-safe:duration-200">
|
|
510
|
+
<div className="relative aspect-video overflow-hidden rounded-[6px] border border-hairline bg-card shadow-edge ring-1 ring-foreground/[0.04] group-hover:shadow-floating group-hover:ring-foreground/20 motion-safe:transition-[box-shadow,--tw-ring-color,scale] motion-safe:duration-200 group-active:scale-[0.99]">
|
|
508
511
|
<div className="h-full w-full motion-safe:transition-transform motion-safe:duration-300 motion-safe:group-hover:scale-[1.03]">
|
|
509
512
|
<PageThumb source={{ pageId: id, kind: pageKinds[id] }} title={displayTitle} />
|
|
510
513
|
</div>
|
|
@@ -528,7 +531,7 @@ function DocCard({
|
|
|
528
531
|
</div>
|
|
529
532
|
|
|
530
533
|
{import.meta.env.DEV && (
|
|
531
|
-
<div className="absolute right-2 top-2">
|
|
534
|
+
<div className="absolute right-2 top-2 z-20">
|
|
532
535
|
<DropdownMenu>
|
|
533
536
|
<DropdownMenuTrigger
|
|
534
537
|
render={
|
|
@@ -538,7 +541,7 @@ function DocCard({
|
|
|
538
541
|
e.stopPropagation();
|
|
539
542
|
e.preventDefault();
|
|
540
543
|
}}
|
|
541
|
-
className="flex size-7 items-center justify-center rounded-[5px] bg-card/90 text-foreground shadow-edge ring-1 ring-border opacity-0 backdrop-blur hover:bg-card group-hover:opacity-100 aria-expanded:opacity-100 motion-safe:transition-opacity"
|
|
544
|
+
className="flex size-7 items-center justify-center rounded-[5px] bg-card/90 text-foreground shadow-edge ring-1 ring-border opacity-0 outline-none backdrop-blur hover:bg-card group-hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-ring/40 aria-expanded:opacity-100 motion-safe:transition-opacity motion-safe:duration-150"
|
|
542
545
|
aria-label={tCard.home.docActions}
|
|
543
546
|
>
|
|
544
547
|
<MoreHorizontal className="size-3.5" />
|
|
@@ -731,7 +734,7 @@ function MoveDialog({
|
|
|
731
734
|
</DialogHeader>
|
|
732
735
|
<div className="max-h-[320px] overflow-y-auto rounded-[6px] border border-border bg-background">
|
|
733
736
|
<FolderOption
|
|
734
|
-
|
|
737
|
+
chip={<SystemViewIcon kind="draft" className="text-muted-foreground" />}
|
|
735
738
|
label={t.home.draft}
|
|
736
739
|
active={selected === null}
|
|
737
740
|
onClick={() => setSelected(null)}
|
|
@@ -739,7 +742,7 @@ function MoveDialog({
|
|
|
739
742
|
{folders.map((f) => (
|
|
740
743
|
<FolderOption
|
|
741
744
|
key={f.id}
|
|
742
|
-
icon={f.icon}
|
|
745
|
+
chip={<FolderIconChip icon={f.icon} />}
|
|
743
746
|
label={f.name}
|
|
744
747
|
active={selected === f.id}
|
|
745
748
|
onClick={() => setSelected(f.id)}
|
|
@@ -760,12 +763,12 @@ function MoveDialog({
|
|
|
760
763
|
}
|
|
761
764
|
|
|
762
765
|
function FolderOption({
|
|
763
|
-
|
|
766
|
+
chip,
|
|
764
767
|
label,
|
|
765
768
|
active,
|
|
766
769
|
onClick,
|
|
767
770
|
}: {
|
|
768
|
-
|
|
771
|
+
chip: React.ReactNode;
|
|
769
772
|
label: string;
|
|
770
773
|
active: boolean;
|
|
771
774
|
onClick: () => void;
|
|
@@ -776,11 +779,11 @@ function FolderOption({
|
|
|
776
779
|
type="button"
|
|
777
780
|
onClick={onClick}
|
|
778
781
|
className={cn(
|
|
779
|
-
'flex w-full items-center gap-2 border-b border-hairline px-3 py-2 text-left text-[13px] transition-colors last:border-b-0',
|
|
782
|
+
'flex w-full items-center gap-2 border-b border-hairline px-3 py-2 text-left text-[13px] outline-none transition-colors duration-100 last:border-b-0 focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-brand active:bg-muted',
|
|
780
783
|
active ? 'bg-muted text-foreground' : 'hover:bg-muted/60',
|
|
781
784
|
)}
|
|
782
785
|
>
|
|
783
|
-
|
|
786
|
+
{chip}
|
|
784
787
|
<span className="truncate">{label}</span>
|
|
785
788
|
{active && (
|
|
786
789
|
<span className="ml-auto inline-flex items-center gap-1 text-[10.5px] text-brand">
|
package/src/app/routes/page.tsx
CHANGED
|
@@ -12,9 +12,10 @@ import {
|
|
|
12
12
|
X,
|
|
13
13
|
} from 'lucide-react';
|
|
14
14
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
|
-
import {
|
|
15
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
16
16
|
import { toast } from 'sonner';
|
|
17
|
-
import { Button } from '~/components/ui/button';
|
|
17
|
+
import { Button, buttonVariants } from '~/components/ui/button';
|
|
18
|
+
import { readLastHomeLocation } from '~/lib/last-home-location';
|
|
18
19
|
import { cn } from '~/lib/utils';
|
|
19
20
|
import { type FrameMessage, frameUrl, isFrameMessage, type WorkspaceMessage } from '../lib/frame';
|
|
20
21
|
import { pageChangeIncludes, pageIds, pageKinds } from '../lib/pages';
|
|
@@ -53,8 +54,22 @@ export function PageView() {
|
|
|
53
54
|
|
|
54
55
|
const title = pageModule?.meta?.title ?? pageId;
|
|
55
56
|
useEffect(() => {
|
|
56
|
-
document.title = `${title} —
|
|
57
|
+
document.title = `${title} — Autono`;
|
|
58
|
+
return () => {
|
|
59
|
+
document.title = 'Autono';
|
|
60
|
+
};
|
|
57
61
|
}, [title]);
|
|
62
|
+
const navigate = useNavigate();
|
|
63
|
+
// react-router records its entry index in history.state; going back is only
|
|
64
|
+
// safe when we actually pushed an entry, otherwise land on the last home view.
|
|
65
|
+
const goBack = useCallback(() => {
|
|
66
|
+
const idx = (window.history.state as { idx?: number } | null)?.idx ?? 0;
|
|
67
|
+
if (idx > 0) {
|
|
68
|
+
navigate(-1);
|
|
69
|
+
} else {
|
|
70
|
+
navigate(readLastHomeLocation(), { replace: true });
|
|
71
|
+
}
|
|
72
|
+
}, [navigate]);
|
|
58
73
|
|
|
59
74
|
const frameRef = useRef<HTMLIFrameElement | null>(null);
|
|
60
75
|
const [frameKey, setFrameKey] = useState(0);
|
|
@@ -178,7 +193,7 @@ export function PageView() {
|
|
|
178
193
|
{showPageUi && (
|
|
179
194
|
<header className="flex h-12 shrink-0 items-center gap-2 border-b bg-background px-3">
|
|
180
195
|
{showPageBrowser && (
|
|
181
|
-
<Button variant="ghost" size="icon" aria-label="Back to pages"
|
|
196
|
+
<Button variant="ghost" size="icon" aria-label="Back to pages" onClick={goBack}>
|
|
182
197
|
<ChevronLeft className="size-4" />
|
|
183
198
|
</Button>
|
|
184
199
|
)}
|
|
@@ -238,17 +253,22 @@ export function PageView() {
|
|
|
238
253
|
Inspect
|
|
239
254
|
</Button>
|
|
240
255
|
)}
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
render={<a href={src} target="_blank" rel="noreferrer" />}
|
|
256
|
+
<a
|
|
257
|
+
href={known ? src : undefined}
|
|
258
|
+
target="_blank"
|
|
259
|
+
rel="noreferrer"
|
|
246
260
|
aria-label="Open page in a new tab"
|
|
261
|
+
aria-disabled={!known || undefined}
|
|
262
|
+
tabIndex={known ? undefined : -1}
|
|
247
263
|
title="Open the page by itself in a new tab"
|
|
264
|
+
className={cn(
|
|
265
|
+
buttonVariants({ variant: 'outline', size: 'sm' }),
|
|
266
|
+
!known && 'pointer-events-none opacity-50',
|
|
267
|
+
)}
|
|
248
268
|
>
|
|
249
269
|
<ExternalLink className="size-4" />
|
|
250
270
|
Open
|
|
251
|
-
</
|
|
271
|
+
</a>
|
|
252
272
|
</div>
|
|
253
273
|
</header>
|
|
254
274
|
)}
|