create-ampless 0.2.0-alpha.9 → 1.0.0-alpha.101
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/README.ja.md +77 -0
- package/README.md +6 -3
- package/dist/index.js +743 -84
- package/dist/templates/_shared/AGENTS.ja.md +94 -0
- package/dist/templates/_shared/AGENTS.md +94 -0
- package/dist/templates/_shared/README.ja.md +239 -0
- package/dist/templates/_shared/README.md +239 -0
- package/dist/templates/_shared/RUNBOOK.ja.md +120 -0
- package/dist/templates/_shared/RUNBOOK.md +32 -58
- package/dist/templates/_shared/THEMES.ja.md +495 -0
- package/dist/templates/_shared/THEMES.md +523 -0
- package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +7 -0
- package/dist/templates/_shared/amplify/backend.ts +10 -0
- package/dist/templates/_shared/amplify/data/get-media-by-src.js +45 -0
- package/dist/templates/_shared/amplify/data/get-published-post.js +9 -12
- package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +6 -9
- package/dist/templates/_shared/amplify/data/list-published-posts.js +10 -11
- package/dist/templates/_shared/amplify/data/resource.custom.ts +1 -2
- package/dist/templates/_shared/amplify/data/resource.ts +28 -7
- package/dist/templates/_shared/amplify/events/dispatcher/resource.ts +1 -0
- package/dist/templates/_shared/amplify/events/processor-trusted/resource.ts +1 -0
- package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +5 -0
- package/dist/templates/_shared/amplify/functions/api-key-renewer/resource.ts +1 -0
- package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
- package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +12 -0
- package/dist/templates/_shared/amplify/functions/plugin-secret-handler/handler.ts +11 -0
- package/dist/templates/_shared/amplify/functions/plugin-secret-handler/resource.ts +12 -0
- package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +7 -0
- package/dist/templates/_shared/amplify/secrets/.gitkeep +0 -0
- package/dist/templates/_shared/amplify/secrets/encryption-key.ts +9 -0
- package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
- package/dist/templates/_shared/app/(admin)/admin/plugins/page.tsx +5 -0
- package/dist/templates/_shared/app/globals.css +55 -39
- package/dist/templates/_shared/app/layout.tsx +51 -16
- package/dist/templates/_shared/app/providers.tsx +0 -2
- package/dist/templates/_shared/app/raw/[slug]/route.ts +10 -0
- package/dist/templates/_shared/app/static/[slug]/[[...path]]/route.ts +14 -0
- package/dist/templates/_shared/cms.config.ts +64 -23
- package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +3 -4
- package/dist/templates/_shared/components/tag-list.tsx +9 -2
- package/dist/templates/_shared/components.json +1 -1
- package/dist/templates/_shared/docs/plugin-author-guide.ja.md +934 -0
- package/dist/templates/_shared/docs/plugin-author-guide.md +1336 -0
- package/dist/templates/_shared/lib/admin.ts +12 -12
- package/dist/templates/_shared/lib/ampless.ts +2 -8
- package/dist/templates/_shared/lib/amplify.ts +0 -5
- package/dist/templates/_shared/package.json +51 -43
- package/dist/templates/_shared/plugins/README.ja.md +139 -0
- package/dist/templates/_shared/plugins/README.md +143 -0
- package/dist/templates/_shared/proxy.ts +23 -8
- package/dist/templates/blog/README.ja.md +22 -0
- package/dist/templates/blog/README.md +17 -47
- package/dist/templates/blog/manifest.ts +1 -1
- package/dist/templates/blog/pages/feed.ts +4 -5
- package/dist/templates/blog/pages/home.tsx +10 -15
- package/dist/templates/blog/pages/post.tsx +42 -14
- package/dist/templates/blog/pages/sitemap.ts +4 -5
- package/dist/templates/blog/pages/tag.tsx +8 -10
- package/dist/templates/blog/tokens.css +26 -40
- package/dist/templates/corporate/README.ja.md +18 -0
- package/dist/templates/corporate/README.md +12 -14
- package/dist/templates/corporate/pages/feed.ts +3 -4
- package/dist/templates/corporate/pages/home.tsx +7 -12
- package/dist/templates/corporate/pages/post.tsx +20 -14
- package/dist/templates/corporate/pages/sitemap.ts +3 -4
- package/dist/templates/corporate/pages/tag.tsx +8 -10
- package/dist/templates/corporate/tokens.css +17 -39
- package/dist/templates/dads/README.ja.md +31 -0
- package/dist/templates/dads/README.md +13 -17
- package/dist/templates/dads/pages/feed.ts +3 -4
- package/dist/templates/dads/pages/home.tsx +7 -12
- package/dist/templates/dads/pages/post.tsx +20 -14
- package/dist/templates/dads/pages/sitemap.ts +3 -4
- package/dist/templates/dads/pages/tag.tsx +8 -10
- package/dist/templates/dads/tokens.css +22 -42
- package/dist/templates/docs/README.ja.md +24 -0
- package/dist/templates/docs/README.md +10 -13
- package/dist/templates/docs/pages/feed.ts +3 -4
- package/dist/templates/docs/pages/home.tsx +6 -9
- package/dist/templates/docs/pages/post.tsx +19 -13
- package/dist/templates/docs/pages/sitemap.ts +3 -4
- package/dist/templates/docs/pages/tag.tsx +8 -10
- package/dist/templates/docs/tokens.css +17 -39
- package/dist/templates/landing/README.ja.md +20 -0
- package/dist/templates/landing/README.md +14 -19
- package/dist/templates/landing/pages/feed.ts +4 -5
- package/dist/templates/landing/pages/home.tsx +7 -12
- package/dist/templates/landing/pages/post.tsx +20 -14
- package/dist/templates/landing/pages/sitemap.ts +3 -4
- package/dist/templates/landing/pages/tag.tsx +8 -10
- package/dist/templates/landing/tokens.css +17 -39
- package/dist/templates/minimal/README.ja.md +14 -0
- package/dist/templates/minimal/README.md +9 -47
- package/dist/templates/minimal/pages/feed.ts +4 -5
- package/dist/templates/minimal/pages/home.tsx +6 -8
- package/dist/templates/minimal/pages/post.tsx +19 -12
- package/dist/templates/minimal/pages/sitemap.ts +4 -5
- package/dist/templates/minimal/pages/tag.tsx +7 -8
- package/dist/templates/minimal/tokens.css +17 -39
- package/dist/templates/plugin-local/README.md +34 -0
- package/dist/templates/plugin-local/index.ts +39 -0
- package/dist/templates/plugin-standalone/CHANGELOG.md +1 -0
- package/dist/templates/plugin-standalone/README.ja.md +43 -0
- package/dist/templates/plugin-standalone/README.md +43 -0
- package/dist/templates/plugin-standalone/package.json +47 -0
- package/dist/templates/plugin-standalone/src/index.test.ts +16 -0
- package/dist/templates/plugin-standalone/src/index.ts +29 -0
- package/dist/templates/plugin-standalone/tsconfig.json +16 -0
- package/dist/templates/plugin-standalone/tsup.config.ts +8 -0
- package/package.json +1 -1
- package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +0 -5
- package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +0 -5
- package/dist/templates/_shared/components/i18n-provider.tsx +0 -15
- package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
- package/dist/templates/_shared/lib/admin-site.ts +0 -12
- package/dist/templates/_shared/lib/amplify-server.ts +0 -7
- package/dist/templates/_shared/lib/auth-server.ts +0 -15
- package/dist/templates/_shared/lib/cn.ts +0 -5
- package/dist/templates/_shared/lib/i18n.ts +0 -35
- package/dist/templates/_shared/lib/kv-provider.ts +0 -7
- package/dist/templates/_shared/lib/media.ts +0 -6
- package/dist/templates/_shared/lib/posts-provider.ts +0 -7
- package/dist/templates/_shared/lib/posts-public.ts +0 -27
- package/dist/templates/_shared/lib/posts.ts +0 -12
- package/dist/templates/_shared/lib/seo.ts +0 -11
- package/dist/templates/_shared/lib/site-settings.ts +0 -11
- package/dist/templates/_shared/lib/storage.ts +0 -10
- package/dist/templates/_shared/lib/theme-actions.ts +0 -5
- package/dist/templates/_shared/lib/theme-active.ts +0 -10
- package/dist/templates/_shared/lib/theme-config.ts +0 -12
- package/dist/templates/_shared/lib/upload.ts +0 -6
- /package/dist/templates/_shared/app/{site/[siteId]/[slug] → [slug]}/page.tsx +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/feed.xml → feed.xml}/route.ts +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/og → og}/[slug]/route.ts +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/page.tsx → page.tsx} +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/sitemap.xml → sitemap.xml}/route.ts +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/tag → tag}/[tag]/page.tsx +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
> English: [README.md](./README.md)
|
|
2
|
+
>
|
|
3
|
+
# Landing テーマ
|
|
4
|
+
|
|
5
|
+
1 ページ完結型のヒーロー中心レイアウト。任意で「最新記事」グリッドも併設可能。ヘッダー / フッターナビ設定とウォームコーラル系アクセントパレットを備えます。
|
|
6
|
+
|
|
7
|
+
## カスタマイズ
|
|
8
|
+
|
|
9
|
+
`/admin/sites/<siteId>/theme` で設定:
|
|
10
|
+
|
|
11
|
+
- **ヒーロー見出し** — 空欄ならサイト名を使用
|
|
12
|
+
- **ヒーローサブ見出し** — 空欄ならサイト説明を使用
|
|
13
|
+
- **CTA ボタンのテキスト**
|
|
14
|
+
- **CTA リンク先**
|
|
15
|
+
- **プライマリカラー**
|
|
16
|
+
- **角丸**
|
|
17
|
+
- **フィーチャー記事のスラッグ** — 公開済み投稿を 1 件ホームに固定
|
|
18
|
+
- **ロゴ画像 URL**
|
|
19
|
+
- **ヘッダーナビ** — ラベル + URL のペア
|
|
20
|
+
- **フッターリンク** — ラベル + URL のペア
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
> 日本語版: [README.ja.md](./README.ja.md)
|
|
2
|
+
>
|
|
3
|
+
# Landing theme
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
Single-page hero focus with optional "Latest" post grid. Hero-led layout with configurable header / footer nav and a warm-coral accent palette.
|
|
4
6
|
|
|
5
7
|
## Customizing
|
|
6
8
|
|
|
7
9
|
In `/admin/sites/<siteId>/theme`:
|
|
8
10
|
|
|
9
|
-
- Hero headline
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm install
|
|
21
|
-
npx ampx sandbox # provision the AWS backend
|
|
22
|
-
npm run dev # start Next.js
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
See the project README for full setup.
|
|
11
|
+
- **Hero headline** — empty falls back to the site name
|
|
12
|
+
- **Hero subheadline** — empty falls back to the site description
|
|
13
|
+
- **CTA button text**
|
|
14
|
+
- **CTA URL**
|
|
15
|
+
- **Primary color**
|
|
16
|
+
- **Corner radius**
|
|
17
|
+
- **Featured post slug** — pin one published post to the home page
|
|
18
|
+
- **Logo image URL**
|
|
19
|
+
- **Header navigation** — label + URL pairs
|
|
20
|
+
- **Footer links** — label + URL pairs
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ampless } from '@/lib/ampless'
|
|
2
2
|
|
|
3
3
|
interface Ctx {
|
|
4
|
-
siteId: string
|
|
5
4
|
request: Request
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
// /feed.xml proxy — plugin-rss writes the feed to
|
|
9
|
-
// `public/plugins/rss/
|
|
10
|
-
export async function landingFeedHandler(
|
|
11
|
-
const url = publicAssetUrl(
|
|
8
|
+
// `public/plugins/rss/feed.xml`. Same flow as the blog theme.
|
|
9
|
+
export async function landingFeedHandler(_ctx: Ctx): Promise<Response> {
|
|
10
|
+
const url = ampless.publicAssetUrl('public/plugins/rss/feed.xml')
|
|
12
11
|
const upstream = await fetch(url, { cache: 'no-store' })
|
|
13
12
|
if (!upstream.ok) {
|
|
14
13
|
return new Response(
|
|
@@ -1,30 +1,25 @@
|
|
|
1
1
|
import Link from 'next/link'
|
|
2
2
|
import { formatDate, type ThemeRouteContext } from 'ampless'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { loadThemeConfig } from '@/lib/theme-config'
|
|
6
|
-
import { renderBody } from '@/lib/posts'
|
|
3
|
+
import { renderBody } from '@ampless/runtime'
|
|
4
|
+
import { ampless } from '@/lib/ampless'
|
|
7
5
|
import { SiteHeader } from '@/components/site-chrome/site-header'
|
|
8
6
|
import { SiteFooter } from '@/components/site-chrome/site-footer'
|
|
9
7
|
|
|
10
8
|
// Hero-led landing layout: big headline + subhead + CTA, then an
|
|
11
9
|
// optional "Latest" grid sourced from published posts. Falls back to
|
|
12
10
|
// site name / description when the manifest hero fields are empty.
|
|
13
|
-
export default async function LandingHome(
|
|
14
|
-
const { siteId } = await params
|
|
11
|
+
export default async function LandingHome(_: ThemeRouteContext) {
|
|
15
12
|
const [settings, theme, postsResult] = await Promise.all([
|
|
16
|
-
loadSiteSettings(
|
|
17
|
-
loadThemeConfig(
|
|
18
|
-
listPublishedPosts({
|
|
13
|
+
ampless.loadSiteSettings(),
|
|
14
|
+
ampless.loadThemeConfig(),
|
|
15
|
+
ampless.listPublishedPosts({ limit: 6 }),
|
|
19
16
|
])
|
|
20
17
|
|
|
21
18
|
// Featured embed below the hero — typical use is a short "About"
|
|
22
19
|
// or "Welcome" article. Filtered out of the Latest grid below to
|
|
23
20
|
// avoid showing the same post twice.
|
|
24
21
|
const featuredSlug = theme.values.featuredSlug?.trim()
|
|
25
|
-
const featured = featuredSlug
|
|
26
|
-
? await getPublishedPost(featuredSlug, { siteId })
|
|
27
|
-
: null
|
|
22
|
+
const featured = featuredSlug ? await ampless.getPublishedPost(featuredSlug) : null
|
|
28
23
|
const posts = featured
|
|
29
24
|
? postsResult.items.filter((p) => p.slug !== featured.slug)
|
|
30
25
|
: postsResult.items
|
|
@@ -2,35 +2,35 @@ import type { Metadata } from 'next'
|
|
|
2
2
|
import Link from 'next/link'
|
|
3
3
|
import { notFound } from 'next/navigation'
|
|
4
4
|
import { formatDate, type ThemeRouteContext } from 'ampless'
|
|
5
|
-
import { renderBody } from '
|
|
5
|
+
import { renderBody } from '@ampless/runtime'
|
|
6
|
+
import { ampless } from '@/lib/ampless'
|
|
7
|
+
import { admin } from '@/lib/admin'
|
|
6
8
|
import { LightboxBinder } from '@/components/lightbox-content'
|
|
7
9
|
import { TagList } from '@/components/tag-list'
|
|
8
|
-
import { postMetadata } from '@/lib/seo'
|
|
9
|
-
import { loadSiteSettings } from '@/lib/site-settings'
|
|
10
|
-
import { loadThemeConfig } from '@/lib/theme-config'
|
|
11
|
-
import { getPublishedPost } from '@/lib/posts-public'
|
|
12
10
|
import { SiteHeader } from '@/components/site-chrome/site-header'
|
|
13
11
|
import { SiteFooter } from '@/components/site-chrome/site-footer'
|
|
14
|
-
import { t } from '@/lib/i18n'
|
|
15
12
|
|
|
16
13
|
type PostCtx = ThemeRouteContext<{ slug: string }>
|
|
17
14
|
|
|
18
15
|
export async function generatePostMetadata({ params }: PostCtx): Promise<Metadata> {
|
|
19
|
-
const {
|
|
20
|
-
const post = await getPublishedPost(slug
|
|
16
|
+
const { slug } = await params
|
|
17
|
+
const post = await ampless.getPublishedPost(slug)
|
|
21
18
|
if (!post) return {}
|
|
22
|
-
return postMetadata(post
|
|
19
|
+
return ampless.postMetadata(post)
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
export default async function LandingPost({ params }: PostCtx) {
|
|
26
|
-
const {
|
|
23
|
+
const { slug } = await params
|
|
27
24
|
const [post, settings, theme] = await Promise.all([
|
|
28
|
-
getPublishedPost(slug
|
|
29
|
-
loadSiteSettings(
|
|
30
|
-
loadThemeConfig(
|
|
25
|
+
ampless.getPublishedPost(slug),
|
|
26
|
+
ampless.loadSiteSettings(),
|
|
27
|
+
ampless.loadThemeConfig(),
|
|
31
28
|
])
|
|
32
29
|
if (!post) notFound()
|
|
33
30
|
|
|
31
|
+
const postBody = await ampless.publicBodyForPost(post)
|
|
32
|
+
const html = await ampless.publicHtmlForPost(post)
|
|
33
|
+
|
|
34
34
|
const defaultLightbox = settings.media.imageDisplay === 'lightbox'
|
|
35
35
|
const maxWidth = settings.media.imageMaxWidth ?? '100%'
|
|
36
36
|
const proseStyle: React.CSSProperties = {
|
|
@@ -52,7 +52,7 @@ export default async function LandingPost({ params }: PostCtx) {
|
|
|
52
52
|
href="/"
|
|
53
53
|
className="text-sm text-[var(--muted-foreground)] hover:text-[var(--primary)]"
|
|
54
54
|
>
|
|
55
|
-
{t('public.back')}
|
|
55
|
+
{admin.t('public.back')}
|
|
56
56
|
</Link>
|
|
57
57
|
</nav>
|
|
58
58
|
|
|
@@ -69,6 +69,10 @@ export default async function LandingPost({ params }: PostCtx) {
|
|
|
69
69
|
)}
|
|
70
70
|
</header>
|
|
71
71
|
|
|
72
|
+
{postBody}
|
|
73
|
+
|
|
74
|
+
{html.beforeContent}
|
|
75
|
+
|
|
72
76
|
<div
|
|
73
77
|
id="post-body"
|
|
74
78
|
className="prose prose-neutral dark:prose-invert max-w-none [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
|
|
@@ -76,6 +80,8 @@ export default async function LandingPost({ params }: PostCtx) {
|
|
|
76
80
|
dangerouslySetInnerHTML={{ __html: renderBody(post) }}
|
|
77
81
|
/>
|
|
78
82
|
|
|
83
|
+
{html.afterContent}
|
|
84
|
+
|
|
79
85
|
<TagList tags={post.tags} className="mt-10 border-t pt-6" />
|
|
80
86
|
</article>
|
|
81
87
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ampless } from '@/lib/ampless'
|
|
2
2
|
|
|
3
3
|
interface Ctx {
|
|
4
|
-
siteId: string
|
|
5
4
|
request: Request
|
|
6
5
|
}
|
|
7
6
|
|
|
8
|
-
export async function landingSitemapHandler(
|
|
9
|
-
const url = publicAssetUrl(
|
|
7
|
+
export async function landingSitemapHandler(_ctx: Ctx): Promise<Response> {
|
|
8
|
+
const url = ampless.publicAssetUrl('public/plugins/seo/sitemap.xml')
|
|
10
9
|
const upstream = await fetch(url, { cache: 'no-store' })
|
|
11
10
|
if (!upstream.ok) {
|
|
12
11
|
return new Response(
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import Link from 'next/link'
|
|
2
2
|
import { notFound } from 'next/navigation'
|
|
3
3
|
import { formatDate, type ThemeRouteContext } from 'ampless'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { loadThemeConfig } from '@/lib/theme-config'
|
|
4
|
+
import { ampless } from '@/lib/ampless'
|
|
5
|
+
import { admin } from '@/lib/admin'
|
|
7
6
|
import { SiteHeader } from '@/components/site-chrome/site-header'
|
|
8
7
|
import { SiteFooter } from '@/components/site-chrome/site-footer'
|
|
9
|
-
import { t } from '@/lib/i18n'
|
|
10
8
|
|
|
11
9
|
export default async function LandingTag({ params }: ThemeRouteContext<{ tag: string }>) {
|
|
12
|
-
const {
|
|
10
|
+
const { tag } = await params
|
|
13
11
|
const decodedTag = decodeURIComponent(tag)
|
|
14
12
|
const [{ items: posts }, settings, theme] = await Promise.all([
|
|
15
|
-
listPostsByTag(decodedTag, {
|
|
16
|
-
loadSiteSettings(
|
|
17
|
-
loadThemeConfig(
|
|
13
|
+
ampless.listPostsByTag(decodedTag, { limit: 50 }),
|
|
14
|
+
ampless.loadSiteSettings(),
|
|
15
|
+
ampless.loadThemeConfig(),
|
|
18
16
|
])
|
|
19
17
|
|
|
20
18
|
if (posts.length === 0) notFound()
|
|
@@ -34,12 +32,12 @@ export default async function LandingTag({ params }: ThemeRouteContext<{ tag: st
|
|
|
34
32
|
href="/"
|
|
35
33
|
className="text-sm text-[var(--muted-foreground)] hover:text-[var(--primary)]"
|
|
36
34
|
>
|
|
37
|
-
{t('public.home')}
|
|
35
|
+
{admin.t('public.home')}
|
|
38
36
|
</Link>
|
|
39
37
|
</nav>
|
|
40
38
|
|
|
41
39
|
<header className="mb-12">
|
|
42
|
-
<p className="text-sm text-[var(--muted-foreground)]">{t('public.tagLabel')}</p>
|
|
40
|
+
<p className="text-sm text-[var(--muted-foreground)]">{admin.t('public.tagLabel')}</p>
|
|
43
41
|
<h1 className="text-4xl font-bold tracking-tight">#{decodedTag}</h1>
|
|
44
42
|
</header>
|
|
45
43
|
|
|
@@ -3,45 +3,23 @@
|
|
|
3
3
|
* templates/blog/tokens.css for the scoped-selector pattern. */
|
|
4
4
|
|
|
5
5
|
[data-theme='landing'] {
|
|
6
|
-
--background: oklch(0.99 0.01 60);
|
|
7
|
-
--foreground: oklch(0.18 0.02 30);
|
|
8
|
-
--card: oklch(1 0 0);
|
|
9
|
-
--card-foreground: oklch(0.18 0.02 30);
|
|
10
|
-
--primary: oklch(0.6 0.18 35);
|
|
11
|
-
--primary-foreground: oklch(0.99 0.005 60);
|
|
12
|
-
--secondary: oklch(0.96 0.015 50);
|
|
13
|
-
--secondary-foreground: oklch(0.25 0.04 30);
|
|
14
|
-
--muted: oklch(0.96 0.015 50);
|
|
15
|
-
--muted-foreground: oklch(0.5 0.02 40);
|
|
16
|
-
--accent: oklch(0.95 0.05 35);
|
|
17
|
-
--accent-foreground: oklch(0.3 0.1 30);
|
|
18
|
-
--destructive: oklch(0.55 0.22 25);
|
|
19
|
-
--destructive-foreground: oklch(0.99 0 0);
|
|
20
|
-
--border: oklch(0.92 0.01 60);
|
|
21
|
-
--input: oklch(0.92 0.01 60);
|
|
22
|
-
--ring: oklch(0.6 0.18 35);
|
|
6
|
+
--background: light-dark(oklch(0.99 0.01 60), oklch(0.16 0.02 30));
|
|
7
|
+
--foreground: light-dark(oklch(0.18 0.02 30), oklch(0.96 0.01 60));
|
|
8
|
+
--card: light-dark(oklch(1 0 0), oklch(0.22 0.02 30));
|
|
9
|
+
--card-foreground: light-dark(oklch(0.18 0.02 30), oklch(0.96 0.01 60));
|
|
10
|
+
--primary: light-dark(oklch(0.6 0.18 35), oklch(0.72 0.16 35));
|
|
11
|
+
--primary-foreground: light-dark(oklch(0.99 0.005 60), oklch(0.16 0.02 30));
|
|
12
|
+
--secondary: light-dark(oklch(0.96 0.015 50), oklch(0.28 0.03 30));
|
|
13
|
+
--secondary-foreground: light-dark(oklch(0.25 0.04 30), oklch(0.96 0.01 60));
|
|
14
|
+
--muted: light-dark(oklch(0.96 0.015 50), oklch(0.28 0.03 30));
|
|
15
|
+
--muted-foreground: light-dark(oklch(0.5 0.02 40), oklch(0.7 0.02 40));
|
|
16
|
+
--accent: light-dark(oklch(0.95 0.05 35), oklch(0.32 0.06 30));
|
|
17
|
+
--accent-foreground: light-dark(oklch(0.3 0.1 30), oklch(0.96 0.01 60));
|
|
18
|
+
--destructive: light-dark(oklch(0.55 0.22 25), oklch(0.55 0.22 25));
|
|
19
|
+
--destructive-foreground: light-dark(oklch(0.99 0 0), oklch(0.99 0 0));
|
|
20
|
+
--border: light-dark(oklch(0.92 0.01 60), oklch(0.32 0.03 30));
|
|
21
|
+
--input: light-dark(oklch(0.92 0.01 60), oklch(0.32 0.03 30));
|
|
22
|
+
--ring: light-dark(oklch(0.6 0.18 35), oklch(0.72 0.16 35));
|
|
23
23
|
--radius: 0.75rem;
|
|
24
24
|
--ampless-body-font: system-ui, -apple-system, sans-serif;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
@media (prefers-color-scheme: dark) {
|
|
28
|
-
[data-theme='landing'] {
|
|
29
|
-
--background: oklch(0.16 0.02 30);
|
|
30
|
-
--foreground: oklch(0.96 0.01 60);
|
|
31
|
-
--card: oklch(0.22 0.02 30);
|
|
32
|
-
--card-foreground: oklch(0.96 0.01 60);
|
|
33
|
-
--primary: oklch(0.72 0.16 35);
|
|
34
|
-
--primary-foreground: oklch(0.16 0.02 30);
|
|
35
|
-
--secondary: oklch(0.28 0.03 30);
|
|
36
|
-
--secondary-foreground: oklch(0.96 0.01 60);
|
|
37
|
-
--muted: oklch(0.28 0.03 30);
|
|
38
|
-
--muted-foreground: oklch(0.7 0.02 40);
|
|
39
|
-
--accent: oklch(0.32 0.06 30);
|
|
40
|
-
--accent-foreground: oklch(0.96 0.01 60);
|
|
41
|
-
--destructive: oklch(0.55 0.22 25);
|
|
42
|
-
--destructive-foreground: oklch(0.99 0 0);
|
|
43
|
-
--border: oklch(0.32 0.03 30);
|
|
44
|
-
--input: oklch(0.32 0.03 30);
|
|
45
|
-
--ring: oklch(0.72 0.16 35);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
> English: [README.md](./README.md)
|
|
2
|
+
>
|
|
3
|
+
# Minimal テーマ
|
|
4
|
+
|
|
5
|
+
shadcn/ui のカラートークンをベースにした、暖色系ニュートラル地にソフトブルーをアクセントとした構成。カスタマイズ項目を絞ったシンプルなブログレイアウトで、デザインを主張させたくない場合に向きます。
|
|
6
|
+
|
|
7
|
+
## カスタマイズ
|
|
8
|
+
|
|
9
|
+
`/admin/sites/<siteId>/theme` で設定:
|
|
10
|
+
|
|
11
|
+
- **プライマリカラー**
|
|
12
|
+
- **角丸**
|
|
13
|
+
|
|
14
|
+
以上です。フォントや複数のアクセントカラー、フッター / ナビゲーションの制御など、より細かい調整が必要な場合は `blog` テーマを利用してください。
|
|
@@ -1,52 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
> 日本語版: [README.ja.md](./README.ja.md)
|
|
2
|
+
>
|
|
3
|
+
# Minimal theme
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
Soft blue accent on a warm-neutral background, derived from shadcn/ui's color tokens. Stripped-down blog layout with very few customization knobs — pick this when you want the design to disappear.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Customizing
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
In `/admin/sites/<siteId>/theme`:
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
npm install
|
|
11
|
+
- **Primary color**
|
|
12
|
+
- **Corner radius**
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
# Requires AWS credentials configured (`aws configure`).
|
|
15
|
-
# First run takes ~5–10 min to provision resources.
|
|
16
|
-
# Generates amplify_outputs.json when ready.
|
|
17
|
-
npx ampx sandbox
|
|
18
|
-
|
|
19
|
-
# 3. Start the Next.js dev server (terminal 2)
|
|
20
|
-
npm run dev
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Then open [http://localhost:3000](http://localhost:3000).
|
|
24
|
-
|
|
25
|
-
## First admin user
|
|
26
|
-
|
|
27
|
-
Open [http://localhost:3000/login](http://localhost:3000/login) and click **Create admin account**. The first user to register is automatically added to the `ampless-admin` Cognito group.
|
|
28
|
-
|
|
29
|
-
After that, manage content from `/admin`:
|
|
30
|
-
|
|
31
|
-
- `/admin` — dashboard
|
|
32
|
-
- `/admin/posts` — list / create / edit posts (tiptap editor)
|
|
33
|
-
- `/admin/media` — upload images to S3
|
|
34
|
-
|
|
35
|
-
## Production deploy
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
git init && git add . && git commit -m "init"
|
|
39
|
-
git remote add origin <your-repo>
|
|
40
|
-
git push
|
|
41
|
-
# Then connect the repo to AWS Amplify Hosting in the AWS console.
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Customize
|
|
45
|
-
|
|
46
|
-
- `cms.config.ts` — site name, media delivery mode, plugins
|
|
47
|
-
- `app/` — Next.js App Router pages (`(public)/` for the blog, `(admin)/` for the CMS)
|
|
48
|
-
- `amplify/` — Amplify Gen 2 backend definitions (auth / data / storage)
|
|
49
|
-
|
|
50
|
-
## Plugins
|
|
51
|
-
|
|
52
|
-
Enabled: {{plugins}}
|
|
14
|
+
That's it. For richer customization (fonts, multiple accent colors, footer / nav controls), use the `blog` theme.
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ampless } from '@/lib/ampless'
|
|
2
2
|
|
|
3
3
|
interface Ctx {
|
|
4
|
-
siteId: string
|
|
5
4
|
request: Request
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
// /feed.xml proxy — plugin-rss regenerates the feed on content events
|
|
9
|
-
// and writes it to `public/plugins/rss/
|
|
10
|
-
export async function minimalFeedHandler(
|
|
11
|
-
const url = publicAssetUrl(
|
|
8
|
+
// and writes it to `public/plugins/rss/feed.xml`.
|
|
9
|
+
export async function minimalFeedHandler(_ctx: Ctx): Promise<Response> {
|
|
10
|
+
const url = ampless.publicAssetUrl('public/plugins/rss/feed.xml')
|
|
12
11
|
const upstream = await fetch(url, { cache: 'no-store' })
|
|
13
12
|
if (!upstream.ok) {
|
|
14
13
|
return new Response(
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import Link from 'next/link'
|
|
2
2
|
import { formatDate, type ThemeRouteContext } from 'ampless'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { ampless } from '@/lib/ampless'
|
|
4
|
+
import { admin } from '@/lib/admin'
|
|
5
5
|
import { TagList } from '@/components/tag-list'
|
|
6
|
-
import { t } from '@/lib/i18n'
|
|
7
6
|
|
|
8
|
-
export default async function MinimalHome(
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const { items: posts } = await listPublishedPosts({ siteId })
|
|
7
|
+
export default async function MinimalHome(_: ThemeRouteContext) {
|
|
8
|
+
const settings = await ampless.loadSiteSettings()
|
|
9
|
+
const { items: posts } = await ampless.listPublishedPosts()
|
|
12
10
|
|
|
13
11
|
return (
|
|
14
12
|
<main className="mx-auto max-w-2xl px-6 py-12">
|
|
@@ -20,7 +18,7 @@ export default async function MinimalHome({ params }: ThemeRouteContext) {
|
|
|
20
18
|
</header>
|
|
21
19
|
|
|
22
20
|
{posts.length === 0 ? (
|
|
23
|
-
<p className="text-gray-500">{t('public.noPosts')}</p>
|
|
21
|
+
<p className="text-gray-500">{admin.t('public.noPosts')}</p>
|
|
24
22
|
) : (
|
|
25
23
|
<ul className="space-y-8">
|
|
26
24
|
{posts.map((post) => (
|
|
@@ -2,31 +2,32 @@ import type { Metadata } from 'next'
|
|
|
2
2
|
import Link from 'next/link'
|
|
3
3
|
import { notFound } from 'next/navigation'
|
|
4
4
|
import { formatDate, type ThemeRouteContext } from 'ampless'
|
|
5
|
-
import { renderBody } from '
|
|
5
|
+
import { renderBody } from '@ampless/runtime'
|
|
6
|
+
import { ampless } from '@/lib/ampless'
|
|
7
|
+
import { admin } from '@/lib/admin'
|
|
6
8
|
import { LightboxBinder } from '@/components/lightbox-content'
|
|
7
9
|
import { TagList } from '@/components/tag-list'
|
|
8
|
-
import { postMetadata } from '@/lib/seo'
|
|
9
|
-
import { loadSiteSettings } from '@/lib/site-settings'
|
|
10
|
-
import { getPublishedPost } from '@/lib/posts-public'
|
|
11
|
-
import { t } from '@/lib/i18n'
|
|
12
10
|
|
|
13
11
|
type PostCtx = ThemeRouteContext<{ slug: string }>
|
|
14
12
|
|
|
15
13
|
export async function generatePostMetadata({ params }: PostCtx): Promise<Metadata> {
|
|
16
|
-
const {
|
|
17
|
-
const post = await getPublishedPost(slug
|
|
14
|
+
const { slug } = await params
|
|
15
|
+
const post = await ampless.getPublishedPost(slug)
|
|
18
16
|
if (!post) return {}
|
|
19
|
-
return postMetadata(post
|
|
17
|
+
return ampless.postMetadata(post)
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export default async function MinimalPost({ params }: PostCtx) {
|
|
23
|
-
const {
|
|
21
|
+
const { slug } = await params
|
|
24
22
|
const [post, settings] = await Promise.all([
|
|
25
|
-
getPublishedPost(slug
|
|
26
|
-
loadSiteSettings(
|
|
23
|
+
ampless.getPublishedPost(slug),
|
|
24
|
+
ampless.loadSiteSettings(),
|
|
27
25
|
])
|
|
28
26
|
if (!post) notFound()
|
|
29
27
|
|
|
28
|
+
const postBody = await ampless.publicBodyForPost(post)
|
|
29
|
+
const html = await ampless.publicHtmlForPost(post)
|
|
30
|
+
|
|
30
31
|
const defaultLightbox = settings.media.imageDisplay === 'lightbox'
|
|
31
32
|
const maxWidth = settings.media.imageMaxWidth ?? '100%'
|
|
32
33
|
const proseStyle: React.CSSProperties = {
|
|
@@ -36,7 +37,7 @@ export default async function MinimalPost({ params }: PostCtx) {
|
|
|
36
37
|
return (
|
|
37
38
|
<main className="mx-auto max-w-2xl px-6 py-12">
|
|
38
39
|
<nav className="mb-8">
|
|
39
|
-
<Link href="/" className="text-sm text-gray-500 hover:underline">{t('public.back')}</Link>
|
|
40
|
+
<Link href="/" className="text-sm text-gray-500 hover:underline">{admin.t('public.back')}</Link>
|
|
40
41
|
</nav>
|
|
41
42
|
|
|
42
43
|
<article>
|
|
@@ -49,6 +50,10 @@ export default async function MinimalPost({ params }: PostCtx) {
|
|
|
49
50
|
)}
|
|
50
51
|
</header>
|
|
51
52
|
|
|
53
|
+
{postBody}
|
|
54
|
+
|
|
55
|
+
{html.beforeContent}
|
|
56
|
+
|
|
52
57
|
<div
|
|
53
58
|
id="post-body"
|
|
54
59
|
className="prose prose-neutral dark:prose-invert max-w-none [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
|
|
@@ -56,6 +61,8 @@ export default async function MinimalPost({ params }: PostCtx) {
|
|
|
56
61
|
dangerouslySetInnerHTML={{ __html: renderBody(post) }}
|
|
57
62
|
/>
|
|
58
63
|
|
|
64
|
+
{html.afterContent}
|
|
65
|
+
|
|
59
66
|
<TagList tags={post.tags} className="mt-8 border-t pt-6" />
|
|
60
67
|
</article>
|
|
61
68
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ampless } from '@/lib/ampless'
|
|
2
2
|
|
|
3
3
|
interface Ctx {
|
|
4
|
-
siteId: string
|
|
5
4
|
request: Request
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
// /sitemap.xml proxy — plugin-seo regenerates the sitemap on every
|
|
9
|
-
// content event and writes it to `public/plugins/seo/
|
|
10
|
-
export async function minimalSitemapHandler(
|
|
11
|
-
const url = publicAssetUrl(
|
|
8
|
+
// content event and writes it to `public/plugins/seo/sitemap.xml`.
|
|
9
|
+
export async function minimalSitemapHandler(_ctx: Ctx): Promise<Response> {
|
|
10
|
+
const url = ampless.publicAssetUrl('public/plugins/seo/sitemap.xml')
|
|
12
11
|
const upstream = await fetch(url, { cache: 'no-store' })
|
|
13
12
|
if (!upstream.ok) {
|
|
14
13
|
return new Response(
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import Link from 'next/link'
|
|
2
2
|
import { notFound } from 'next/navigation'
|
|
3
3
|
import { formatDate, type ThemeRouteContext } from 'ampless'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { t } from '@/lib/i18n'
|
|
4
|
+
import { ampless } from '@/lib/ampless'
|
|
5
|
+
import { admin } from '@/lib/admin'
|
|
7
6
|
|
|
8
7
|
export default async function MinimalTag({ params }: ThemeRouteContext<{ tag: string }>) {
|
|
9
|
-
const {
|
|
8
|
+
const { tag } = await params
|
|
10
9
|
const decodedTag = decodeURIComponent(tag)
|
|
11
10
|
const [{ items: posts }, settings] = await Promise.all([
|
|
12
|
-
listPostsByTag(decodedTag, {
|
|
13
|
-
loadSiteSettings(
|
|
11
|
+
ampless.listPostsByTag(decodedTag, { limit: 50 }),
|
|
12
|
+
ampless.loadSiteSettings(),
|
|
14
13
|
])
|
|
15
14
|
|
|
16
15
|
if (posts.length === 0) notFound()
|
|
@@ -18,11 +17,11 @@ export default async function MinimalTag({ params }: ThemeRouteContext<{ tag: st
|
|
|
18
17
|
return (
|
|
19
18
|
<main className="mx-auto max-w-2xl px-6 py-12">
|
|
20
19
|
<nav className="mb-8">
|
|
21
|
-
<Link href="/" className="text-sm text-gray-500 hover:underline">{t('public.home')}</Link>
|
|
20
|
+
<Link href="/" className="text-sm text-gray-500 hover:underline">{admin.t('public.home')}</Link>
|
|
22
21
|
</nav>
|
|
23
22
|
|
|
24
23
|
<header className="mb-12 border-b pb-6">
|
|
25
|
-
<p className="text-sm text-gray-500">{t('public.tagLabel')}</p>
|
|
24
|
+
<p className="text-sm text-gray-500">{admin.t('public.tagLabel')}</p>
|
|
26
25
|
<h1 className="text-4xl font-bold tracking-tight">#{decodedTag}</h1>
|
|
27
26
|
</header>
|
|
28
27
|
|
|
@@ -2,45 +2,23 @@
|
|
|
2
2
|
* pattern as templates/blog/tokens.css; see that file for context. */
|
|
3
3
|
|
|
4
4
|
[data-theme='minimal'] {
|
|
5
|
-
--background: oklch(0.99 0.005 90);
|
|
6
|
-
--foreground: oklch(0.18 0.02 250);
|
|
7
|
-
--card: oklch(1 0 0);
|
|
8
|
-
--card-foreground: oklch(0.18 0.02 250);
|
|
9
|
-
--primary: oklch(0.55 0.18 250);
|
|
10
|
-
--primary-foreground: oklch(0.985 0 0);
|
|
11
|
-
--secondary: oklch(0.95 0.01 250);
|
|
12
|
-
--secondary-foreground: oklch(0.25 0.05 250);
|
|
13
|
-
--muted: oklch(0.95 0.01 250);
|
|
14
|
-
--muted-foreground: oklch(0.5 0.02 250);
|
|
15
|
-
--accent: oklch(0.95 0.03 250);
|
|
16
|
-
--accent-foreground: oklch(0.3 0.08 250);
|
|
17
|
-
--destructive: oklch(0.577 0.245 27.325);
|
|
18
|
-
--destructive-foreground: oklch(0.985 0 0);
|
|
19
|
-
--border: oklch(0.9 0.01 250);
|
|
20
|
-
--input: oklch(0.9 0.01 250);
|
|
21
|
-
--ring: oklch(0.55 0.18 250);
|
|
5
|
+
--background: light-dark(oklch(0.99 0.005 90), oklch(0.18 0.02 250));
|
|
6
|
+
--foreground: light-dark(oklch(0.18 0.02 250), oklch(0.95 0.01 250));
|
|
7
|
+
--card: light-dark(oklch(1 0 0), oklch(0.22 0.02 250));
|
|
8
|
+
--card-foreground: light-dark(oklch(0.18 0.02 250), oklch(0.95 0.01 250));
|
|
9
|
+
--primary: light-dark(oklch(0.55 0.18 250), oklch(0.75 0.18 250));
|
|
10
|
+
--primary-foreground: light-dark(oklch(0.985 0 0), oklch(0.18 0.02 250));
|
|
11
|
+
--secondary: light-dark(oklch(0.95 0.01 250), oklch(0.28 0.03 250));
|
|
12
|
+
--secondary-foreground: light-dark(oklch(0.25 0.05 250), oklch(0.95 0.01 250));
|
|
13
|
+
--muted: light-dark(oklch(0.95 0.01 250), oklch(0.28 0.03 250));
|
|
14
|
+
--muted-foreground: light-dark(oklch(0.5 0.02 250), oklch(0.7 0.02 250));
|
|
15
|
+
--accent: light-dark(oklch(0.95 0.03 250), oklch(0.32 0.05 250));
|
|
16
|
+
--accent-foreground: light-dark(oklch(0.3 0.08 250), oklch(0.95 0.01 250));
|
|
17
|
+
--destructive: light-dark(oklch(0.577 0.245 27.325), oklch(0.5 0.2 27));
|
|
18
|
+
--destructive-foreground: light-dark(oklch(0.985 0 0), oklch(0.985 0 0));
|
|
19
|
+
--border: light-dark(oklch(0.9 0.01 250), oklch(0.32 0.03 250));
|
|
20
|
+
--input: light-dark(oklch(0.9 0.01 250), oklch(0.32 0.03 250));
|
|
21
|
+
--ring: light-dark(oklch(0.55 0.18 250), oklch(0.55 0.18 250));
|
|
22
22
|
--radius: 0.375rem;
|
|
23
23
|
--ampless-body-font: system-ui, -apple-system, sans-serif;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
@media (prefers-color-scheme: dark) {
|
|
27
|
-
[data-theme='minimal'] {
|
|
28
|
-
--background: oklch(0.18 0.02 250);
|
|
29
|
-
--foreground: oklch(0.95 0.01 250);
|
|
30
|
-
--card: oklch(0.22 0.02 250);
|
|
31
|
-
--card-foreground: oklch(0.95 0.01 250);
|
|
32
|
-
--primary: oklch(0.75 0.18 250);
|
|
33
|
-
--primary-foreground: oklch(0.18 0.02 250);
|
|
34
|
-
--secondary: oklch(0.28 0.03 250);
|
|
35
|
-
--secondary-foreground: oklch(0.95 0.01 250);
|
|
36
|
-
--muted: oklch(0.28 0.03 250);
|
|
37
|
-
--muted-foreground: oklch(0.7 0.02 250);
|
|
38
|
-
--accent: oklch(0.32 0.05 250);
|
|
39
|
-
--accent-foreground: oklch(0.95 0.01 250);
|
|
40
|
-
--destructive: oklch(0.5 0.2 27);
|
|
41
|
-
--destructive-foreground: oklch(0.985 0 0);
|
|
42
|
-
--border: oklch(0.32 0.03 250);
|
|
43
|
-
--input: oklch(0.32 0.03 250);
|
|
44
|
-
--ring: oklch(0.55 0.18 250);
|
|
45
|
-
}
|
|
46
|
-
}
|