feat(editor): add advanced rich text features and update deployment configuration
Some checks failed
Deploy to Coolify / deploy (push) Failing after 7m5s

- Enhance lexical editor with formatting options (strikethrough, inline code, subscript, superscript), lists (unordered, ordered, checklist), tables, alignment, indentation, blockquotes, horizontal rules, uploads, and relationships
- Update Dockerfile for improved build process, including .npmrc support, pnpm config, telemetry disable, and production optimizations
- Modify CI workflow to deploy only, removing local build steps and updating webhook trigger
- Add .dockerignore file and deployment documentation for Docker build and push workflow
- Configure Next.js for standalone output in next.config.js

BREAKING CHANGE: CI workflow now requires local Docker builds before pushing code, as automated builds have been removed.
This commit is contained in:
2025-12-03 14:59:13 +08:00
parent 794e2144e8
commit b6349bf173
7 changed files with 252 additions and 38 deletions

View File

@@ -0,0 +1,112 @@
---
description: Build Docker image and push to Docker Hub for Coolify deployment
---
# Docker Build and Push Workflow
This workflow builds a Docker image locally and pushes it to Docker Hub so it can be deployed to Coolify.
## Prerequisites
1. **Docker Hub Account**: Make sure you have a Docker Hub account
2. **Docker Hub Login**: Log in to Docker Hub from your terminal
```bash
docker login
```
Enter your Docker Hub username and password when prompted.
## Workflow Steps
### 1. Build the Docker Image
Build your Docker image with a tag that includes your Docker Hub username:
```bash
docker build -t YOUR_DOCKERHUB_USERNAME/website-ricenoodletw-cms:latest .
```
Replace `YOUR_DOCKERHUB_USERNAME` with your actual Docker Hub username.
You can also add a specific version tag:
```bash
docker build -t YOUR_DOCKERHUB_USERNAME/website-ricenoodletw-cms:v1.0.0 .
```
### 2. (Optional) Test the Image Locally
Before pushing, you can test the image locally:
```bash
docker run -p 3000:3000 YOUR_DOCKERHUB_USERNAME/website-ricenoodletw-cms:latest
```
### 3. Push to Docker Hub
Push the image to Docker Hub:
```bash
docker push YOUR_DOCKERHUB_USERNAME/website-ricenoodletw-cms:latest
```
If you tagged a specific version, push that too:
```bash
docker push YOUR_DOCKERHUB_USERNAME/website-ricenoodletw-cms:v1.0.0
```
### 4. Deploy to Coolify
In your Coolify dashboard:
1. Create a new service or edit your existing service
2. Select **Docker Image** as the source
3. Enter your image name: `YOUR_DOCKERHUB_USERNAME/website-ricenoodletw-cms:latest`
4. Configure environment variables if needed
5. Deploy
Coolify will pull the image from Docker Hub and deploy it.
## Automated Deployment with Gitea Actions
Since building happens locally, the Gitea workflow only triggers Coolify deployment. Here's a sample workflow for `.gitea/workflows/deploy.yaml`:
```yaml
name: Deploy to Coolify
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger Coolify Deployment
run: |
curl -X POST "${{ secrets.COOLIFY_WEBHOOK_URL }}"
```
### Setup Instructions:
1. **Get Coolify Webhook URL**:
- In Coolify, go to your service settings
- Find the "Webhooks" section
- Copy the webhook URL
2. **Add to Gitea Secrets**:
- Go to your repository in Gitea
- Navigate to Settings → Secrets
- Add `COOLIFY_WEBHOOK_URL` with the webhook URL from Coolify
### Workflow:
1. **Build and push locally** (steps 1-3 above)
2. **Push code to Gitea** - This triggers the Gitea workflow
3. **Gitea notifies Coolify** - Coolify pulls the latest image from Docker Hub
4. **Coolify redeploys** - Your service is updated with the new image
**Note**: Make sure your Coolify service is configured to use the Docker image from Docker Hub (`YOUR_DOCKERHUB_USERNAME/website-ricenoodletw-cms:latest`).

12
.dockerignore Normal file
View File

@@ -0,0 +1,12 @@
node_modules
.next
.git
.gitignore
README.md
.env*.local
.vercel
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store

View File

@@ -1,5 +1,5 @@
name: Build and Deploy
run-name: ${{ gitea.actor }} is building and deploying to Coolify 🚀
name: Deploy to Coolify
run-name: ${{ gitea.actor }} is deploying to Coolify 🚀
on:
push:
@@ -7,28 +7,9 @@ on:
- master
jobs:
build-and-deploy:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/website-ricenoodletw-cms:latest
- name: Trigger Coolify Deployment
run: |
curl --request GET '${{ secrets.COOLIFY_WEBHOOK }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
curl -X POST "${{ secrets.COOLIFY_WEBHOOK_URL }}"

View File

@@ -10,11 +10,11 @@ RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm config set shamefully-hoist true && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
@@ -22,13 +22,14 @@ RUN \
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
COPY --from=deps /app/node_modules ./node_modules
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_OPTIONS="--no-deprecation"
RUN \
if [ -f yarn.lock ]; then yarn run build; \
@@ -41,7 +42,7 @@ RUN \
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
@@ -64,8 +65,8 @@ USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV PORT=3000
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js
CMD ["node", "server.js"]

View File

@@ -8,6 +8,7 @@ const NEXT_PUBLIC_SERVER_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
images: {
remotePatterns: [
...[NEXT_PUBLIC_SERVER_URL /* 'https://example.com' */].map((item) => {

View File

@@ -5,17 +5,30 @@ import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93
import { FixedToolbarFeatureClient as FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { TableFeatureClient as TableFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { OverviewComponent as OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MetaTitleComponent as MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MetaImageComponent as MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MetaDescriptionComponent as MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { PreviewComponent as PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { SlugComponent as SlugComponent_92cc057d0a2abb4f6cf0307edf59f986 } from '@/fields/slug/SlugComponent'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BlocksFeatureClient as BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { LinkToDoc as LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634 } from '@payloadcms/plugin-search/client'
import { ReindexButton as ReindexButton_aead06e4cbf6b2620c5c51c9ab283634 } from '@payloadcms/plugin-search/client'
@@ -35,17 +48,30 @@ export const importMap = {
"@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#TableFeatureClient": TableFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@/fields/slug/SlugComponent#SlugComponent": SlugComponent_92cc057d0a2abb4f6cf0307edf59f986,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/plugin-search/client#LinkToDoc": LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
"@payloadcms/plugin-search/client#ReindexButton": ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,

View File

@@ -1,20 +1,78 @@
import type { TextFieldSingleValidation } from 'payload'
import {
// Text Formatting Features
BoldFeature,
ItalicFeature,
LinkFeature,
ParagraphFeature,
lexicalEditor,
UnderlineFeature,
StrikethroughFeature,
InlineCodeFeature,
SubscriptFeature,
SuperscriptFeature,
// Block Features
ParagraphFeature,
HeadingFeature,
// List Features
UnorderedListFeature,
OrderedListFeature,
ChecklistFeature,
// Table Feature (Experimental)
EXPERIMENTAL_TableFeature,
// Layout Features
AlignFeature,
IndentFeature,
BlockquoteFeature,
HorizontalRuleFeature,
// Media & Link Features
LinkFeature,
UploadFeature,
RelationshipFeature,
// Toolbar Features
InlineToolbarFeature,
FixedToolbarFeature,
// Core
lexicalEditor,
type LinkFields,
} from '@payloadcms/richtext-lexical'
export const defaultLexical = lexicalEditor({
features: [
// Essential Block Features
ParagraphFeature(),
UnderlineFeature(),
HeadingFeature({
enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
}),
// Text Formatting
BoldFeature(),
ItalicFeature(),
UnderlineFeature(),
StrikethroughFeature(),
InlineCodeFeature(),
SubscriptFeature(),
SuperscriptFeature(),
// Lists - NOW INCLUDED!
UnorderedListFeature(),
OrderedListFeature(),
ChecklistFeature(),
// Tables - EXPERIMENTAL BUT FULLY FUNCTIONAL!
EXPERIMENTAL_TableFeature(),
// Layout & Structure
AlignFeature(),
IndentFeature(),
BlockquoteFeature(),
HorizontalRuleFeature(),
// Links with Custom Validation
LinkFeature({
enabledCollections: ['pages', 'posts'],
fields: ({ defaultFields }) => {
@@ -43,5 +101,28 @@ export const defaultLexical = lexicalEditor({
]
},
}),
// Media Features
UploadFeature({
collections: {
media: {
fields: [
{
name: 'caption',
type: 'richText',
editor: lexicalEditor(),
},
],
},
},
}),
RelationshipFeature({
enabledCollections: ['pages', 'posts'],
}),
// Toolbar Features for Better UX
InlineToolbarFeature(),
FixedToolbarFeature(),
],
})