Changes: - Add FAL_KEY and GEMINI_API_KEY to .env.example - Update picture-it to use ~/.config/opencode/.env (unified creds) - Remove shodh-memory skill (no longer used) - Remove alphaear-* skills (deprecated) - Remove thai-frontend-dev skill (replaced by website-creator) - Remove theme-factory skill - Add mql-developer skill (MQL5 trading) - Add ecommerce-astro skill (Astro e-commerce) - Add website-creator skill (Next.js + Payload CMS) - Update install script for new skills
291 lines
9.5 KiB
Markdown
291 lines
9.5 KiB
Markdown
---
|
|
name: payload-lexical-integration
|
|
description: แนวทางการรวม Payload CMS Lexical richText content กับ design system components — อธิบายว่าทำไม design skill output กับ Payload content ถึงอยู่คนละ layer และวิธี integrate มันเข้าด้วยกัน
|
|
category: software-development
|
|
---
|
|
|
|
# Payload Lexical Integration
|
|
|
|
## ปัญหา
|
|
|
|
เวลาใช้ design skill (ui-ux-pro-max) กับ Payload CMS มักเกิดความสับสน:
|
|
|
|
- Design skill ให้โค้ดแบบไหน?
|
|
- Payload Lexical เก็บ content ยังไง?
|
|
- ทำไม content ไม่แสดงหลังสร้าง fields เสร็จ?
|
|
|
|
## สิ่งที่ต้องเข้าใจก่อน
|
|
|
|
### Two Layers — แยกกันทำ
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ DESIGN LAYER (ui-ux-pro-max, ckm:design, ckm:ui-styling)│
|
|
│ • Component structure (Hero, Card, Navbar) │
|
|
│ • Color tokens, typography, spacing │
|
|
│ • Animation specs (150-300ms, ease-out) │
|
|
│ • Layout grid, responsive breakpoints │
|
|
│ • Interaction states │
|
|
│ │
|
|
│ Output: React + Tailwind code — "ภาชนะ" ไม่ใช่ "เนื้อหา"│
|
|
└─────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ CONTENT LAYER (Payload CMS) │
|
|
│ • ข้อความ + format (bold, italic, link) │
|
|
│ • Headings (H1-H6) │
|
|
│ • Lists, blockquotes, code blocks │
|
|
│ • Images, links │
|
|
│ • Tables │
|
|
│ │
|
|
│ Output: Lexical JSON — "เนื้อหา" ไม่ใช่ "ภาชนะ" │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Design skill สร้าง "ภาชนะ" — Payload สร้าง "เนื้อหา" — ต้องรวมกันตอน render**
|
|
|
|
---
|
|
|
|
## ขั้นตอน
|
|
|
|
```
|
|
[1] Design Phase
|
|
ui-ux-pro-max → Component structure, tokens, animations
|
|
Output: Component skeleton (ไม่มี content)
|
|
↓
|
|
[2] Payload Phase
|
|
สร้าง Collections + richText Fields
|
|
Output: Content structure ใน Payload
|
|
↓
|
|
[3] Content Phase
|
|
พิมพ์ content ใน /admin (Lexical visual editor)
|
|
Output: Lexical JSON
|
|
↓
|
|
[4] Integration Phase
|
|
ครอบ Payload content ด้วย Design components
|
|
```
|
|
|
|
---
|
|
|
|
## Step 1: Payload Collection
|
|
|
|
กำหนด content fields ตาม section:
|
|
|
|
```ts
|
|
// src/collections/Posts.ts
|
|
const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
fields: [
|
|
{ name: 'title', type: 'text', required: true },
|
|
{ name: 'slug', type: 'text', required: true },
|
|
{ name: 'heroContent', type: 'richText' }, // content สำหรับ Hero
|
|
{ name: 'features', type: 'array',
|
|
fields: [
|
|
{ name: 'heading', type: 'text' },
|
|
{ name: 'content', type: 'richText' }, // content ในแต่ละ card
|
|
]
|
|
},
|
|
{ name: 'testimonial', type: 'richText' },
|
|
{ name: 'featuredImage', type: 'upload', relationTo: 'media' },
|
|
{ name: 'status', type: 'select', options: [...], defaultValue: 'draft' },
|
|
],
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Step 2: สร้าง Payload Helpers
|
|
|
|
```ts
|
|
// src/lib/payload-helpers.ts
|
|
import { getPayload } from 'payload'
|
|
import config from '@/payload.config'
|
|
|
|
export async function getPost(slug: string) {
|
|
const p = await getPayload({ config })
|
|
const { docs } = await p.find({
|
|
collection: 'posts',
|
|
where: { slug: { equals: slug } },
|
|
depth: 2,
|
|
})
|
|
return docs[0] ?? null
|
|
}
|
|
|
|
export async function getAllPosts() {
|
|
const p = await getPayload({ config })
|
|
return p.find({
|
|
collection: 'posts',
|
|
where: { status: { equals: 'published' } },
|
|
depth: 1,
|
|
})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Step 3: Integration — Design Component + RichText
|
|
|
|
```tsx
|
|
// src/app/(frontend)/posts/[slug]/page.tsx
|
|
import { getPost } from '@/lib/payload-helpers'
|
|
import { RichText } from '@payloadcms/richtext-lexical'
|
|
|
|
// Design tokens จาก ui-ux-pro-max
|
|
const tokens = {
|
|
hero: 'text-5xl md:text-7xl font-bold tracking-tight',
|
|
section: 'py-20 px-6 max-w-7xl mx-auto',
|
|
card: 'rounded-2xl border border-slate-200 p-6 shadow-sm',
|
|
animate: 'animate-fade-in duration-300 ease-out',
|
|
}
|
|
|
|
// Design component ครอบ Payload richText
|
|
function HeroSection({ title, content }: { title: string; content: any }) {
|
|
return (
|
|
<section className={`${tokens.section} text-center`}>
|
|
<h1 className={`${tokens.hero} mb-6`}>{title}</h1>
|
|
{content && (
|
|
<div className="max-w-3xl mx-auto">
|
|
{/* Payload content → RichText → design wrapper */}
|
|
<RichText data={content} className="prose prose-lg" />
|
|
</div>
|
|
)}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
function FeatureCard({ heading, content }: { heading: string; content: any }) {
|
|
return (
|
|
<div className={`${tokens.card} ${tokens.animate}`}>
|
|
<h3 className="text-xl font-semibold mb-3">{heading}</h3>
|
|
{content && <RichText data={content} className="prose prose-sm" />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default async function PostPage({ params }: { params: { slug: string } }) {
|
|
const post = await getPost(params.slug)
|
|
if (!post) return <div>Not found</div>
|
|
|
|
return (
|
|
<main className="min-h-screen">
|
|
<HeroSection title={post.title} content={post.heroContent} />
|
|
|
|
{post.features?.length > 0 && (
|
|
<section className={`${tokens.section} bg-slate-50`}>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
{post.features.map((f: any, i: number) => (
|
|
<FeatureCard key={i} heading={f.heading} content={f.content} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Animation
|
|
|
|
Animation apply ที่ **wrapper element** ไม่ใช่ที่ content — เพราะ Lexical JSON เก็บแค่ content structure ไม่เก็บ animation metadata
|
|
|
|
```tsx
|
|
// ✅ ถูก — animation ที่ wrapper
|
|
<div className="animate-hero-in">
|
|
<RichText data={post.content} />
|
|
</div>
|
|
|
|
// ❌ ผิด — พยายามใส่ animation ใน Lexical JSON
|
|
```
|
|
|
|
Design skill จะให้ animation spec เป็น CSS class — แค่ apply ที่ element ที่ wrap `<RichText>`
|
|
|
|
---
|
|
|
|
## Tailwind Typography Setup
|
|
|
|
```bash
|
|
pnpm add @tailwindcss/typography
|
|
```
|
|
|
|
```ts
|
|
// tailwind.config.ts
|
|
plugins: [require('@tailwindcss/typography')],
|
|
```
|
|
|
|
ใช้ class `prose` กับ `<RichText>`:
|
|
|
|
```tsx
|
|
<RichText data={post.content} className="prose prose-lg max-w-none" />
|
|
```
|
|
|
|
---
|
|
|
|
## Payload Config: เปิด Lexical Editor
|
|
|
|
```ts
|
|
// payload.config.ts
|
|
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
|
|
|
export default buildConfig({
|
|
editor: lexicalEditor(), // ← ต้องมีถึงจะใช้ visual editor ได้
|
|
// ...
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Common Mistakes
|
|
|
|
### 1. Design skill ให้ hardcode content
|
|
|
|
Design skill อาจให้แบบนี้:
|
|
|
|
```tsx
|
|
// ❌ สิ่งที่ design skill อาจให้มา
|
|
<div className="hero">
|
|
<h1>Welcome to Our Site</h1> // hardcode
|
|
<p>Amazing content here...</p> // hardcode
|
|
</div>
|
|
```
|
|
|
|
ต้องแปลงเป็น:
|
|
|
|
```tsx
|
|
// ✅
|
|
<div className="hero animate-hero-in">
|
|
<h1>{post.title}</h1>
|
|
{post.heroContent && (
|
|
<RichText data={post.heroContent} className="prose" />
|
|
)}
|
|
</div>
|
|
```
|
|
|
|
### 2. ลืม lexicalEditor() ใน payload.config
|
|
|
|
ถ้าไม่มี `editor: lexicalEditor()` → visual editor จะไม่ขึ้น
|
|
|
|
### 3. ลืม Tailwind typography plugin
|
|
|
|
ถ้าไม่มี `@tailwindcss/typography` → richText output จะไม่มี styling
|
|
|
|
---
|
|
|
|
## สรุป: ใครทำอะไร
|
|
|
|
| Design Layer ทำ | Payload Layer ทำ | Integration ทำ |
|
|
|-----------------|------------------|----------------|
|
|
| Component structure | Content storage | ครอบ `RichText` ด้วย design component |
|
|
| Color/tokens | richText fields | Apply design tokens กับ Payload output |
|
|
| Typography system | Visual editor (/admin) | Style richText output ด้วย prose class |
|
|
| Animation specs | Content rendering | Wrap output ด้วย animation classes |
|
|
| Layout grid | SEO fields (via plugin) | Layout คงที่ + content จาก Payload |
|
|
|
|
---
|
|
|
|
## Related
|
|
|
|
- `website-creator` — workflow หลักในการสร้างเว็บด้วย Next.js + Payload
|
|
- `payload` — Payload CMS skill (fields, hooks, queries, plugins)
|