Update skills: add website-creator, mql-developer, ecommerce-astro
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
This commit is contained in:
290
skills/website-creator/payload-lexical-integration/SKILL.md
Normal file
290
skills/website-creator/payload-lexical-integration/SKILL.md
Normal file
@@ -0,0 +1,290 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user