Compare commits
33 Commits
fix/publis
...
fix/client
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dae26941f | ||
|
|
10eab87653 | ||
|
|
bbf35a62d8 | ||
|
|
a108986f1b | ||
|
|
4cc6f4cee4 | ||
|
|
cb691e0642 | ||
|
|
c9ce3501a0 | ||
|
|
ef8d3c9bf4 | ||
|
|
d3232b947d | ||
|
|
28c6b2a66b | ||
|
|
a11243e288 | ||
|
|
19ddd3cfc6 | ||
|
|
1ab3be65f8 | ||
|
|
de53f2a826 | ||
|
|
7def6b7ddf | ||
|
|
840dde2b17 | ||
|
|
c2ff9b1dd8 | ||
|
|
97aca5fde7 | ||
|
|
97d3bb1c11 | ||
|
|
8f785e1fde | ||
|
|
89db8fb7f2 | ||
|
|
3d1305de5c | ||
|
|
0829a350ce | ||
|
|
1fc9c47f20 | ||
|
|
61a4656ef5 | ||
|
|
d8f7034ab8 | ||
|
|
fa39b37a44 | ||
|
|
fa7ed3f621 | ||
|
|
2321970fcc | ||
|
|
84a5b4066d | ||
|
|
f12b4dc6b0 | ||
|
|
8e26824bf8 | ||
|
|
12a8bba852 |
48
.github/actions/setup/action.yml
vendored
48
.github/actions/setup/action.yml
vendored
@@ -1,15 +1,33 @@
|
||||
name: Setup node and pnpm
|
||||
description: Configure the Node.js and pnpm versions
|
||||
description: |
|
||||
Configures Node, pnpm, cache, performs pnpm install
|
||||
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'The Node.js version to use'
|
||||
description: Node.js version
|
||||
required: true
|
||||
default: 22.6.2
|
||||
default: 22.6.0
|
||||
pnpm-version:
|
||||
description: 'The pnpm version to use'
|
||||
description: Pnpm version
|
||||
required: true
|
||||
default: 9.7.1
|
||||
pnpm-run-install:
|
||||
description: Whether to run pnpm install
|
||||
required: false
|
||||
default: true
|
||||
pnpm-restore-cache:
|
||||
description: Whether to restore cache
|
||||
required: false
|
||||
default: true
|
||||
pnpm-install-cache-key:
|
||||
description: The cache key for the pnpm install cache
|
||||
default: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
outputs:
|
||||
pnpm-store-path:
|
||||
description: The resolved pnpm store path
|
||||
pnpm-install-cache-key:
|
||||
description: The cache key used for pnpm install cache
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
@@ -30,19 +48,29 @@ runs:
|
||||
version: ${{ inputs.pnpm-version }}
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
- name: Get pnpm store path
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
STORE_PATH=$(pnpm store path --silent)
|
||||
echo "STORE_PATH=$STORE_PATH" >> $GITHUB_ENV
|
||||
echo "Pnpm store path resolved to: $STORE_PATH"
|
||||
|
||||
- name: Setup pnpm cache
|
||||
- name: Restore pnpm install cache
|
||||
if: ${{ inputs.pnpm-restore-cache == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
key: ${{ inputs.pnpm-install-cache-key }}
|
||||
restore-keys: |
|
||||
pnpm-store-${{ inputs.pnpm-version }}-
|
||||
pnpm-store-
|
||||
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- shell: bash
|
||||
- name: Run pnpm install
|
||||
if: ${{ inputs.pnpm-run-install == 'true' }}
|
||||
shell: bash
|
||||
run: pnpm install
|
||||
|
||||
# Set the cache key output
|
||||
- run: |
|
||||
echo "pnpm-install-cache-key=${{ inputs.pnpm-install-cache-key }}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
12
.github/workflows/label-on-change.yml
vendored
12
.github/workflows/label-on-change.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
on-labeled-ensure-one-status:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
issues: write
|
||||
# Only run on issue labeled and if label starts with 'status:'
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
}
|
||||
|
||||
on-issue-close:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
issues: write
|
||||
if: github.event.action == 'closed'
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
}
|
||||
|
||||
on-issue-reopen:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
issues: write
|
||||
if: github.event.action == 'reopened'
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
labels: 'status: needs-triage'
|
||||
|
||||
on-issue-assigned:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
issues: write
|
||||
if: >
|
||||
@@ -106,11 +106,11 @@ jobs:
|
||||
labels: 'status: needs-triage'
|
||||
|
||||
# on-pr-merge:
|
||||
# runs-on: ubuntu-latest
|
||||
# runs-on: ubuntu-24.04
|
||||
# if: github.event.pull_request.merged == true
|
||||
# steps:
|
||||
|
||||
# on-pr-close:
|
||||
# runs-on: ubuntu-latest
|
||||
# runs-on: ubuntu-24.04
|
||||
# if: github.event_name == 'pull_request_target' && github.event.pull_request.merged == false
|
||||
# steps:
|
||||
|
||||
2
.github/workflows/lock-issues.yml
vendored
2
.github/workflows/lock-issues.yml
vendored
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
lock_issues:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Lock issues
|
||||
uses: dessant/lock-threads@v5
|
||||
|
||||
214
.github/workflows/main.yml
vendored
214
.github/workflows/main.yml
vendored
@@ -23,11 +23,12 @@ env:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
needs_build: ${{ steps.filter.outputs.needs_build }}
|
||||
needs_tests: ${{ steps.filter.outputs.needs_tests }}
|
||||
templates: ${{ steps.filter.outputs.templates }}
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
@@ -35,8 +36,6 @@ jobs:
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
@@ -48,53 +47,36 @@ jobs:
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'package.json'
|
||||
- 'templates/**'
|
||||
needs_tests:
|
||||
- '.github/workflows/**'
|
||||
- 'packages/**'
|
||||
- 'test/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'package.json'
|
||||
templates:
|
||||
- 'templates/**'
|
||||
- name: Log all filter results
|
||||
run: |
|
||||
echo "needs_build: ${{ steps.filter.outputs.needs_build }}"
|
||||
echo "needs_tests: ${{ steps.filter.outputs.needs_tests }}"
|
||||
echo "templates: ${{ steps.filter.outputs.templates }}"
|
||||
|
||||
lint:
|
||||
if: >
|
||||
github.event_name == 'pull_request' && !contains(github.event.pull_request.title, 'no-lint') && !contains(github.event.pull_request.title, 'skip-lint')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 720
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
pnpm-store-
|
||||
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- run: pnpm install
|
||||
- name: Lint staged
|
||||
run: |
|
||||
git diff --name-only --diff-filter=d origin/${GITHUB_BASE_REF}...${GITHUB_SHA}
|
||||
@@ -103,78 +85,46 @@ jobs:
|
||||
build:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.needs_build == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 720
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
pnpm-store-
|
||||
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- run: pnpm install
|
||||
- run: pnpm run build:all
|
||||
env:
|
||||
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
|
||||
|
||||
- name: Cache build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
tests-unit:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
@@ -185,8 +135,9 @@ jobs:
|
||||
NODE_OPTIONS: --max-old-space-size=8096
|
||||
|
||||
tests-int:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
name: int-${{ matrix.database }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -222,24 +173,21 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- run: pnpm install
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Start LocalStack
|
||||
run: pnpm docker:start
|
||||
@@ -284,11 +232,12 @@ jobs:
|
||||
max_attempts: 5
|
||||
timeout_minutes: 15
|
||||
command: pnpm test:int
|
||||
on_retry_command: pnpm clean:all && pnpm install
|
||||
on_retry_command: pnpm clean:build && pnpm install --no-frozen-lockfile
|
||||
|
||||
tests-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
name: e2e-${{ matrix.suite }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -331,24 +280,19 @@ jobs:
|
||||
env:
|
||||
SUITE_NAME: ${{ matrix.suite }}
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
@@ -386,7 +330,7 @@ jobs:
|
||||
max_attempts: 5
|
||||
timeout_minutes: 20
|
||||
command: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci ${{ matrix.suite }}
|
||||
on_retry_command: pnpm clean:all && pnpm install && pnpm build:all
|
||||
on_retry_command: pnpm clean:build && pnpm install --no-frozen-lockfile && pnpm build:all
|
||||
env:
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
@@ -408,7 +352,7 @@ jobs:
|
||||
|
||||
# Build listed templates with packed local packages
|
||||
build-templates:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -439,24 +383,19 @@ jobs:
|
||||
POSTGRES_DB: payloadtests
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
@@ -492,28 +431,23 @@ jobs:
|
||||
pnpm runts scripts/build-template-with-local-pkgs.ts ${{ matrix.template }} $POSTGRES_URL
|
||||
|
||||
tests-type-generation:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
@@ -527,7 +461,7 @@ jobs:
|
||||
all-green:
|
||||
name: All Green
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- lint
|
||||
- build
|
||||
@@ -541,7 +475,7 @@ jobs:
|
||||
|
||||
publish-canary:
|
||||
name: Publish Canary
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ needs.all-green.result == 'success' && github.ref_name == 'main' }}
|
||||
needs:
|
||||
- all-green
|
||||
|
||||
27
.github/workflows/post-release-templates.yml
vendored
27
.github/workflows/post-release-templates.yml
vendored
@@ -5,10 +5,6 @@ on:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to process (optional)'
|
||||
required: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.6.0
|
||||
@@ -18,7 +14,7 @@ env:
|
||||
|
||||
jobs:
|
||||
update_templates:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
@@ -64,12 +60,15 @@ jobs:
|
||||
- name: Determine Release Tag
|
||||
id: determine_tag
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.tag }}" != "" ]; then
|
||||
echo "Using tag from input: ${{ github.event.inputs.tag }}"
|
||||
echo "release_tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
if [ "${{ github.event.release.tag_name }}" != "" ]; then
|
||||
echo "Using tag from release event: ${{ github.event.release.tag_name }}"
|
||||
echo "release_tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# pull latest tag from github, must match any version except v2. Should match v3, v4, v99, etc.
|
||||
echo "Fetching latest tag from github..."
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0 --match 'v[0-9]*' --exclude 'v2*')
|
||||
echo "Latest tag: $LATEST_TAG"
|
||||
echo "release_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Commit and push changes
|
||||
@@ -83,17 +82,21 @@ jobs:
|
||||
|
||||
git diff --name-only
|
||||
|
||||
export BRANCH_NAME=templates/bump-${{ steps.determine_tag.outputs.release_tag }}
|
||||
export BRANCH_NAME=templates/bump-${{ steps.determine_tag.outputs.release_tag }}-$(date +%s)
|
||||
echo "branch=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GH_TOKEN_POST_RELEASE_TEMPLATES }}
|
||||
labels: 'area: templates'
|
||||
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||
commit-message: 'templates: bump templates for ${{ steps.determine_tag.outputs.release_tag }}'
|
||||
branch: ${{ steps.commit.outputs.branch }}
|
||||
base: main
|
||||
assignees: ${{ github.actor }}
|
||||
title: 'templates: bump for ${{ steps.determine_tag.outputs.release_tag }}'
|
||||
body: 'Automated bump of templates for ${{ steps.determine_tag.outputs.release_tag }}'
|
||||
body: |
|
||||
🤖 Automated bump of templates for ${{ steps.determine_tag.outputs.release_tag }}
|
||||
|
||||
Triggered by user: @${{ github.actor }}
|
||||
|
||||
4
.github/workflows/post-release.yml
vendored
4
.github/workflows/post-release.yml
vendored
@@ -19,7 +19,7 @@ env:
|
||||
|
||||
jobs:
|
||||
post_release:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
🚀 This is included in version {release_link}
|
||||
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
6
.github/workflows/pr-title.yml
vendored
6
.github/workflows/pr-title.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: pr-title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
@@ -12,7 +12,7 @@ permissions:
|
||||
jobs:
|
||||
main:
|
||||
name: lint-pr-title
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
|
||||
label-pr-on-open:
|
||||
name: label-pr-on-open
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Tag with 2.x branch with v2
|
||||
|
||||
2
.github/workflows/release-canary.yml
vendored
2
.github/workflows/release-canary.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: release-canary-${{ github.ref_name }}-${{ github.sha }}
|
||||
permissions:
|
||||
id-token: write
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
10
.github/workflows/triage.yml
vendored
10
.github/workflows/triage.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: triage
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
issues:
|
||||
@@ -18,7 +18,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
debug-context:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: View context attributes
|
||||
uses: actions/github-script@v7
|
||||
@@ -27,11 +27,10 @@ jobs:
|
||||
|
||||
label-created-by:
|
||||
name: label-on-open
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Tag with 'created-by'
|
||||
uses: actions/github-script@v7
|
||||
if: github.event.action == 'opened'
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -89,10 +88,11 @@ jobs:
|
||||
triage:
|
||||
name: initial-triage
|
||||
if: github.event_name == 'issues'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: ./.github/actions/triage
|
||||
with:
|
||||
|
||||
@@ -108,6 +108,8 @@ export const Posts: CollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
The `preview` property resolves to a string that points to your front-end application with additional URL parameters. This can be an absolute URL or a relative path. If you are using a relative path, Payload will prepend the application's origin onto it, creating a fully qualified URL.
|
||||
|
||||
The preview function receives two arguments:
|
||||
|
||||
| Argument | Description |
|
||||
|
||||
@@ -445,7 +445,7 @@ Then to colorize your Custom Component's background, for example, you can use th
|
||||
Payload also exports its [SCSS](https://sass-lang.com) library for reuse which includes mixins, etc. To use this, simply import it as follows into your `.scss` file:
|
||||
|
||||
```scss
|
||||
@import '~payload/scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
.my-component {
|
||||
@include mid-break {
|
||||
|
||||
@@ -42,7 +42,7 @@ Examples:
|
||||
|
||||
**Offloading complex operations**
|
||||
|
||||
You may run into the need to perform computationally expensive functions which might slow down your main Payload API server(s). The Jobs Queue allows you to offload these tasks a separate compute resource rather than slowing down the server(s) that run your Payload APIs. With Payload Task definitions, you can even keep large dependencies out of your main Next.js bundle by dynamically importing them only when they are used. This keeps your Next.js + Payload compilation fast and ensures large dependencies do not get bundled into your Payload production build.
|
||||
You may run into the need to perform computationally expensive functions which might slow down your main Payload API server(s). The Jobs Queue allows you to offload these tasks to a separate compute resource rather than slowing down the server(s) that run your Payload APIs. With Payload Task definitions, you can even keep large dependencies out of your main Next.js bundle by dynamically importing them only when they are used. This keeps your Next.js + Payload compilation fast and ensures large dependencies do not get bundled into your Payload production build.
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
### URL
|
||||
|
||||
The `url` property is a string that points to your front-end application. This value is used as the `src` attribute of the iframe rendering your front-end. Once loaded, the Admin Panel will communicate directly with your app through `window.postMessage` events.
|
||||
The `url` property resolves to a string that points to your front-end application. This value is used as the `src` attribute of the iframe rendering your front-end. Once loaded, the Admin Panel will communicate directly with your app through `window.postMessage` events.
|
||||
|
||||
This can be an absolute URL or a relative path. If you are using a relative path, Payload will prepend the application's origin onto it, creating a fully qualified URL. This is useful for Vercel preview deployments, for example, where URLs are not known ahead of time.
|
||||
|
||||
To set the URL, use the `admin.livePreview.url` property in your [Payload Config](../configuration/overview):
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ This plugin is a great way to implement a fast, immersive search experience such
|
||||
- Allows you to query search results using first-party Payload APIs
|
||||
- Allows you to query documents without triggering any of their underlying hooks
|
||||
- Allows you to easily prioritize search results by collection or document
|
||||
- Allows you to reindex search results by collection on demand
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -81,7 +82,7 @@ export default config
|
||||
|
||||
The `collections` property is an array of collection slugs to enable syncing to search. Enabled collections receive a `beforeChange` and `afterDelete` hook that creates, updates, and deletes its respective search record as it changes over time.
|
||||
|
||||
### `localize`
|
||||
#### `localize`
|
||||
|
||||
By default, the search plugin will add `localization: true` to the `title` field of the newly added `search` collection if you have localization enabled. If you would like to disable this behavior, you can set this to `false`.
|
||||
|
||||
@@ -159,6 +160,14 @@ When `syncDrafts` is true, draft documents will be synced to search. This is fal
|
||||
|
||||
If true, will delete documents from search whose status changes to draft. This is true by default. You must have [Payload Drafts](https://payloadcms.com/docs/versions/drafts) enabled for this to apply.
|
||||
|
||||
#### `reindexBatchSize`
|
||||
|
||||
A number that, when specified, will be used as the value to determine how many search documents to fetch for reindexing at a time in each batch. If not set, this will default to `50`.
|
||||
|
||||
### Collection reindexing
|
||||
|
||||
Collection reindexing allows you to recreate search documents from your search-enabled collections on demand. This is useful if you have existing documents that don't already have search indexes, commonly when adding `plugin-search` to an existing project. To get started, navigate to your search collection and click the pill in the top right actions slot of the list view labelled `Reindex`. This will open a popup with options to select one of your search-enabled collections, or all, for reindexing.
|
||||
|
||||
## TypeScript
|
||||
|
||||
All types can be directly imported:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { withPayload } from "@payloadcms/next/withPayload";
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "payload-3-custom-server",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "next build && tsc --project tsconfig.server.json",
|
||||
"start": "cross-env NODE_ENV=production node dist/server.js",
|
||||
"dev": "nodemon",
|
||||
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
|
||||
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload"
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"start": "cross-env NODE_ENV=production node dist/server.js"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/next": "latest",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function B() {
|
||||
return <div>b</div>;
|
||||
return <div>b</div>
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
|
||||
|
||||
export const importMap = {
|
||||
|
||||
}
|
||||
export const importMap = {}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"http-status": "1.6.2",
|
||||
"mongoose": "8.8.1",
|
||||
"mongoose": "8.8.3",
|
||||
"mongoose-aggregate-paginate-v2": "1.1.2",
|
||||
"mongoose-paginate-v2": "1.8.5",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -34,6 +34,11 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re
|
||||
req,
|
||||
token,
|
||||
})
|
||||
|
||||
// Support relative URLs by prepending the origin, if necessary
|
||||
if (previewURL && previewURL.startsWith('/')) {
|
||||
previewURL = `${req.protocol}//${req.host}${previewURL}`
|
||||
}
|
||||
} catch (err) {
|
||||
return routeError({
|
||||
collection,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = a
|
||||
},
|
||||
]
|
||||
|
||||
const url =
|
||||
let url =
|
||||
typeof livePreviewConfig?.url === 'function'
|
||||
? await livePreviewConfig.url({
|
||||
collectionConfig,
|
||||
@@ -47,5 +47,10 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = a
|
||||
})
|
||||
: livePreviewConfig?.url
|
||||
|
||||
// Support relative URLs by prepending the origin, if necessary
|
||||
if (url && url.startsWith('/')) {
|
||||
url = `${initPageResult.req.protocol}//${initPageResult.req.host}${url}`
|
||||
}
|
||||
|
||||
return <LivePreviewClient breakpoints={breakpoints} initialData={doc} url={url} />
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ImportMap } from '../../bin/generateImportMap/index.js'
|
||||
import type { SanitizedConfig } from '../../config/types.js'
|
||||
import type { PaginatedDocs } from '../../database/types.js'
|
||||
import type { PayloadRequest, Where } from '../../types/index.js'
|
||||
import type { PayloadRequest, Sort, Where } from '../../types/index.js'
|
||||
|
||||
export type DefaultServerFunctionArgs = {
|
||||
importMap: ImportMap
|
||||
@@ -43,7 +43,7 @@ export type ListQuery = {
|
||||
When provided, is automatically injected into the `where` object
|
||||
*/
|
||||
search?: string
|
||||
sort?: string
|
||||
sort?: Sort
|
||||
where?: Where
|
||||
}
|
||||
|
||||
|
||||
@@ -106,12 +106,15 @@ export const createClientCollectionConfig = ({
|
||||
if (serverOnlyCollectionProperties.includes(key as any)) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'admin':
|
||||
if (!collection.admin) {
|
||||
break
|
||||
}
|
||||
|
||||
clientCollection.admin = {} as ClientCollectionConfig['admin']
|
||||
|
||||
for (const adminKey in collection.admin) {
|
||||
if (serverOnlyCollectionAdminProperties.includes(adminKey as any)) {
|
||||
continue
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { Payload } from '../../types/index.js'
|
||||
|
||||
import { getFromImportMap } from '../../bin/generateImportMap/getFromImportMap.js'
|
||||
import { MissingEditorProp } from '../../errors/MissingEditorProp.js'
|
||||
import { fieldAffectsData } from '../../fields/config/types.js'
|
||||
import { fieldAffectsData, fieldIsHiddenOrDisabled } from '../../fields/config/types.js'
|
||||
import { flattenTopLevelFields, type ImportMap } from '../../index.js'
|
||||
import { removeUndefined } from '../../utilities/removeUndefined.js'
|
||||
|
||||
@@ -75,6 +75,24 @@ export const createClientField = ({
|
||||
}): ClientField => {
|
||||
const clientField: ClientField = {} as ClientField
|
||||
|
||||
if (fieldAffectsData(incomingField) && fieldIsHiddenOrDisabled(incomingField)) {
|
||||
clientField.type = incomingField.type
|
||||
|
||||
if (incomingField.hidden) {
|
||||
clientField.hidden = true
|
||||
}
|
||||
|
||||
if (incomingField.admin?.disabled) {
|
||||
if (!clientField.admin) {
|
||||
clientField.admin = {} as AdminClient
|
||||
}
|
||||
|
||||
clientField.admin.disabled = true
|
||||
}
|
||||
|
||||
return clientField
|
||||
}
|
||||
|
||||
for (const key in incomingField) {
|
||||
if (serverOnlyFieldProperties.includes(key as any)) {
|
||||
continue
|
||||
|
||||
@@ -1410,7 +1410,10 @@ export type JoinField = {
|
||||
export type JoinFieldClient = {
|
||||
admin?: AdminClient & Pick<JoinField['admin'], 'allowCreate' | 'disableBulkEdit' | 'readOnly'>
|
||||
} & FieldBaseClient &
|
||||
Pick<JoinField, 'collection' | 'index' | 'maxDepth' | 'on' | 'type' | 'where'>
|
||||
Pick<
|
||||
JoinField,
|
||||
'collection' | 'defaultLimit' | 'defaultSort' | 'index' | 'maxDepth' | 'on' | 'type' | 'where'
|
||||
>
|
||||
|
||||
export type FlattenedBlock = {
|
||||
flattenedFields: FlattenedField[]
|
||||
|
||||
@@ -350,10 +350,13 @@ export const promise = async ({
|
||||
case 'array': {
|
||||
const rows = siblingDoc[field.name] as JsonObject
|
||||
|
||||
const arraySelect = select?.[field.name]
|
||||
let arraySelect = select?.[field.name]
|
||||
|
||||
if (selectMode === 'include' && typeof arraySelect === 'object') {
|
||||
arraySelect.id = true
|
||||
arraySelect = {
|
||||
...arraySelect,
|
||||
id: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
@@ -427,7 +430,7 @@ export const promise = async ({
|
||||
case 'blocks': {
|
||||
const rows = siblingDoc[field.name]
|
||||
|
||||
const blocksSelect = select?.[field.name]
|
||||
let blocksSelect = select?.[field.name]
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
rows.forEach((row, i) => {
|
||||
@@ -438,6 +441,10 @@ export const promise = async ({
|
||||
let blockSelectMode = selectMode
|
||||
|
||||
if (typeof blocksSelect === 'object') {
|
||||
blocksSelect = {
|
||||
...blocksSelect,
|
||||
}
|
||||
|
||||
// sanitize blocks: {cta: false} to blocks: {cta: {id: true, blockType: true}}
|
||||
if (selectMode === 'exclude' && blocksSelect[block.slug] === false) {
|
||||
blockSelectMode = 'include'
|
||||
@@ -451,6 +458,10 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
if (typeof blocksSelect[block.slug] === 'object') {
|
||||
blocksSelect[block.slug] = {
|
||||
...(blocksSelect[block.slug] as object),
|
||||
}
|
||||
|
||||
blocksSelect[block.slug]['id'] = true
|
||||
blocksSelect[block.slug]['blockType'] = true
|
||||
}
|
||||
@@ -535,7 +546,6 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
traverseFields({
|
||||
collection,
|
||||
|
||||
@@ -102,6 +102,10 @@ export const createClientGlobalConfig = ({
|
||||
importMap,
|
||||
})
|
||||
break
|
||||
case 'label':
|
||||
clientGlobal.label =
|
||||
typeof global.label === 'function' ? global.label({ t: i18n.t }) : global.label
|
||||
break
|
||||
default: {
|
||||
clientGlobal[key] = global[key]
|
||||
break
|
||||
|
||||
@@ -14,8 +14,10 @@ import type {
|
||||
EntityDescription,
|
||||
EntityDescriptionComponent,
|
||||
GeneratePreviewURL,
|
||||
LabelFunction,
|
||||
LivePreviewConfig,
|
||||
MetaConfig,
|
||||
StaticLabel,
|
||||
} from '../../config/types.js'
|
||||
import type { DBIdentifierName } from '../../database/types.js'
|
||||
import type { Field, FlattenedField } from '../../fields/config/types.js'
|
||||
@@ -165,7 +167,7 @@ export type GlobalConfig = {
|
||||
beforeRead?: BeforeReadHook[]
|
||||
beforeValidate?: BeforeValidateHook[]
|
||||
}
|
||||
label?: Record<string, string> | string
|
||||
label?: LabelFunction | StaticLabel
|
||||
/**
|
||||
* Enables / Disables the ability to lock documents while editing
|
||||
* @default true
|
||||
|
||||
@@ -807,7 +807,7 @@ export const getPayload = async (
|
||||
// will reach `if (cached.reload instanceof Promise) {` which then waits for the first reload to finish.
|
||||
cached.reload = new Promise((res) => (resolve = res))
|
||||
const config = await options.config
|
||||
await reload(config, cached.payload)
|
||||
await reload(config, cached.payload, !options.importMap)
|
||||
|
||||
resolve()
|
||||
}
|
||||
@@ -815,7 +815,6 @@ export const getPayload = async (
|
||||
if (cached.reload instanceof Promise) {
|
||||
await cached.reload
|
||||
}
|
||||
|
||||
if (options?.importMap) {
|
||||
cached.payload.importMap = options.importMap
|
||||
}
|
||||
@@ -839,6 +838,7 @@ export const getPayload = async (
|
||||
) {
|
||||
try {
|
||||
const port = process.env.PORT || '3000'
|
||||
|
||||
cached.ws = new WebSocket(
|
||||
`ws://localhost:${port}${process.env.NEXT_BASE_PATH ?? ''}/_next/webpack-hmr`,
|
||||
)
|
||||
|
||||
@@ -134,14 +134,14 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
alignRightLabel: 'Aliniați la dreapta',
|
||||
},
|
||||
rs: {
|
||||
alignCenterLabel: 'Centriraj',
|
||||
alignJustifyLabel: 'Poravnaj opravdaj',
|
||||
alignLeftLabel: 'Poravnaj levo',
|
||||
alignRightLabel: 'Poravnaj desno',
|
||||
alignCenterLabel: 'Поравнај по средини',
|
||||
alignJustifyLabel: 'Поравнај обострано',
|
||||
alignLeftLabel: 'Поравнај лево',
|
||||
alignRightLabel: 'Поравнај десно',
|
||||
},
|
||||
'rs-latin': {
|
||||
alignCenterLabel: 'Poravnaj centar',
|
||||
alignJustifyLabel: 'Poravnaj opravdanje',
|
||||
alignCenterLabel: 'Poravnaj po sredini',
|
||||
alignJustifyLabel: 'Poravnaj obostrano',
|
||||
alignLeftLabel: 'Poravnaj levo',
|
||||
alignRightLabel: 'Poravnaj desno',
|
||||
},
|
||||
|
||||
@@ -68,7 +68,7 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Citat',
|
||||
},
|
||||
rs: {
|
||||
label: 'Blok citat',
|
||||
label: 'Блок цитата',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Blok citata',
|
||||
|
||||
@@ -201,18 +201,18 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
},
|
||||
rs: {
|
||||
inlineBlocks: {
|
||||
create: 'Kreiraj {{label}}',
|
||||
edit: 'Izmeni {{label}}',
|
||||
label: 'Umetnuti blokovi',
|
||||
remove: 'Ukloni {{label}}',
|
||||
create: 'Креирај {{label}}',
|
||||
edit: 'Измени {{label}}',
|
||||
label: 'Уметнути блокови',
|
||||
remove: 'Уклони {{label}}',
|
||||
},
|
||||
label: 'Blokovi',
|
||||
label: 'Блокови',
|
||||
},
|
||||
'rs-latin': {
|
||||
inlineBlocks: {
|
||||
create: 'Kreiraj {{label}}',
|
||||
edit: 'Izmeni {{label}}',
|
||||
label: 'Unutar blokovi',
|
||||
label: 'Umetnuti blokovi',
|
||||
remove: 'Ukloni {{oznaka}}',
|
||||
},
|
||||
label: 'Blokovi',
|
||||
|
||||
@@ -68,7 +68,7 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Titlu {{headingLevel}}',
|
||||
},
|
||||
rs: {
|
||||
label: 'Naslov {{headingLevel}}',
|
||||
label: 'Наслов {{headingLevel}}',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Naslov {{headingLevel}}',
|
||||
|
||||
@@ -68,7 +68,7 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Linie orizontală',
|
||||
},
|
||||
rs: {
|
||||
label: 'Horizontalna linija',
|
||||
label: 'Хоризонтална линија',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Horizontalna linija',
|
||||
|
||||
@@ -90,8 +90,8 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
increaseLabel: 'Crește indentarea',
|
||||
},
|
||||
rs: {
|
||||
decreaseLabel: 'Smanji uvlačenje',
|
||||
increaseLabel: 'Povećaj uvlačenje',
|
||||
decreaseLabel: 'Смањи увлачење',
|
||||
increaseLabel: 'Повећај увлачење',
|
||||
},
|
||||
'rs-latin': {
|
||||
decreaseLabel: 'Smanji uvlačenje',
|
||||
|
||||
@@ -90,7 +90,7 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
loadingWithEllipsis: 'Se încarcă...',
|
||||
},
|
||||
rs: {
|
||||
label: 'Veza',
|
||||
label: 'Веза',
|
||||
loadingWithEllipsis: 'Учитавање...',
|
||||
},
|
||||
'rs-latin': {
|
||||
|
||||
@@ -68,10 +68,10 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Listă de verificare',
|
||||
},
|
||||
rs: {
|
||||
label: 'Lista provere',
|
||||
label: 'Контролна листа',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Lista provere',
|
||||
label: 'Kontrolna lista',
|
||||
},
|
||||
ru: {
|
||||
label: 'Список Проверки',
|
||||
|
||||
@@ -68,10 +68,10 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Lista ordonată',
|
||||
},
|
||||
rs: {
|
||||
label: 'Naručeni Spisak',
|
||||
label: 'Уређена листа',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Naručeni spisak',
|
||||
label: 'Uređena lista',
|
||||
},
|
||||
ru: {
|
||||
label: 'Упорядоченный список',
|
||||
|
||||
@@ -68,10 +68,10 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Listă neordonată',
|
||||
},
|
||||
rs: {
|
||||
label: 'Neporedani spisak',
|
||||
label: 'Неуређена листа',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Neuređena Lista',
|
||||
label: 'Neuređena lista',
|
||||
},
|
||||
ru: {
|
||||
label: 'Несортированный список',
|
||||
|
||||
@@ -90,12 +90,12 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label2: 'Text normal',
|
||||
},
|
||||
rs: {
|
||||
label: 'Paragraf',
|
||||
label2: 'Normalan tekst',
|
||||
label: 'Параграф',
|
||||
label2: 'Oбичан текст',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Paragraf',
|
||||
label2: 'Normalan tekst',
|
||||
label2: 'Običan tekst',
|
||||
},
|
||||
ru: {
|
||||
label: 'Параграф',
|
||||
|
||||
@@ -68,10 +68,10 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Relație',
|
||||
},
|
||||
rs: {
|
||||
label: 'Veza',
|
||||
label: 'Релација',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Odnos',
|
||||
label: 'Relacija',
|
||||
},
|
||||
ru: {
|
||||
label: 'Отношения',
|
||||
|
||||
@@ -68,7 +68,7 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
label: 'Încarcă',
|
||||
},
|
||||
rs: {
|
||||
label: 'Otpremi',
|
||||
label: 'Отпреми',
|
||||
},
|
||||
'rs-latin': {
|
||||
label: 'Otpremi',
|
||||
|
||||
@@ -134,10 +134,10 @@ export const i18n: Partial<GenericLanguages> = {
|
||||
toolbarItemsActive: '{{count}} activ',
|
||||
},
|
||||
rs: {
|
||||
placeholder: "Počnite da kucate, ili pritisnite '/' za komande...",
|
||||
slashMenuBasicGroupLabel: 'Osnovno',
|
||||
slashMenuListGroupLabel: 'Liste',
|
||||
toolbarItemsActive: '{{count}} aktivno',
|
||||
placeholder: "Почните да куцате, или притисните '/' за команде...",
|
||||
slashMenuBasicGroupLabel: 'Основно',
|
||||
slashMenuListGroupLabel: 'Листе',
|
||||
toolbarItemsActive: '{{count}} активно',
|
||||
},
|
||||
'rs-latin': {
|
||||
placeholder: "Počnite da kucate, ili pritisnite '/' za komande...",
|
||||
|
||||
@@ -183,6 +183,7 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, Props>((
|
||||
button={<ChevronIcon />}
|
||||
buttonSize={size}
|
||||
className={disabled ? `${baseClass}--popup-disabled` : ''}
|
||||
disabled={disabled}
|
||||
horizontalAlign="right"
|
||||
noBackground
|
||||
render={({ close }) => SubMenuPopupContent({ close: () => close() })}
|
||||
|
||||
@@ -27,5 +27,9 @@
|
||||
&--size-large {
|
||||
padding: base(0.8);
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export const PopupTrigger: React.FC<PopupTriggerProps> = (props) => {
|
||||
`${baseClass}--${buttonType}`,
|
||||
!noBackground && `${baseClass}--background`,
|
||||
size && `${baseClass}--size-${size}`,
|
||||
disabled && `${baseClass}--disabled`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
|
||||
@@ -31,7 +31,7 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
const { submit } = useForm()
|
||||
const modified = useFormModified()
|
||||
const editDepth = useEditDepth()
|
||||
const { code: locale } = useLocale()
|
||||
const { code: localeCode } = useLocale()
|
||||
|
||||
const {
|
||||
localization,
|
||||
@@ -40,7 +40,6 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
} = config
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
const { code } = useLocale()
|
||||
const label = labelProp || t('version:publishChanges')
|
||||
|
||||
const hasNewerVersions = unpublishedVersionCount > 0
|
||||
@@ -54,7 +53,7 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
return
|
||||
}
|
||||
|
||||
const search = `?locale=${locale}&depth=0&fallback-locale=null&draft=true`
|
||||
const search = `?locale=${localeCode}&depth=0&fallback-locale=null&draft=true`
|
||||
let action
|
||||
let method = 'POST'
|
||||
|
||||
@@ -77,7 +76,7 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
},
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [submit, collectionSlug, globalSlug, serverURL, api, locale, id, forceDisable])
|
||||
}, [submit, collectionSlug, globalSlug, serverURL, api, localeCode, id, forceDisable])
|
||||
|
||||
useHotkey({ cmdCtrlKey: true, editDepth, keyCodes: ['s'] }, (e) => {
|
||||
e.preventDefault()
|
||||
@@ -140,7 +139,8 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
? locale.label
|
||||
: locale.label && locale.label[i18n?.language]
|
||||
|
||||
const isActive = typeof locale === 'string' ? locale === code : locale.code === code
|
||||
const isActive =
|
||||
typeof locale === 'string' ? locale === localeCode : locale.code === localeCode
|
||||
|
||||
if (isActive) {
|
||||
return (
|
||||
|
||||
@@ -53,6 +53,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
|
||||
allowCreate = true,
|
||||
BeforeInput,
|
||||
disableTable = false,
|
||||
field,
|
||||
filterOptions,
|
||||
initialData: initialDataFromProps,
|
||||
initialDrawerData,
|
||||
@@ -104,6 +105,8 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
|
||||
const renderTable = useCallback(
|
||||
async (docs?: PaginatedDocs['docs']) => {
|
||||
const newQuery: ListQuery = {
|
||||
limit: String(field.defaultLimit || collectionConfig.admin.pagination.defaultLimit),
|
||||
sort: field.defaultSort || collectionConfig.defaultSort,
|
||||
...(query || {}),
|
||||
where: { ...(query?.where || {}) },
|
||||
}
|
||||
@@ -130,7 +133,16 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
|
||||
setColumnState(newColumnState)
|
||||
setIsLoadingTable(false)
|
||||
},
|
||||
[getTableState, relationTo, filterOptions, query],
|
||||
[
|
||||
query,
|
||||
field.defaultLimit,
|
||||
field.defaultSort,
|
||||
collectionConfig.admin.pagination.defaultLimit,
|
||||
collectionConfig.defaultSort,
|
||||
filterOptions,
|
||||
getTableState,
|
||||
relationTo,
|
||||
],
|
||||
)
|
||||
|
||||
useIgnoredEffect(
|
||||
@@ -227,7 +239,9 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
|
||||
<ListQueryProvider
|
||||
collectionSlug={relationTo}
|
||||
data={data}
|
||||
defaultLimit={collectionConfig?.admin?.pagination?.defaultLimit}
|
||||
defaultLimit={
|
||||
field.defaultLimit ?? collectionConfig?.admin?.pagination?.defaultLimit
|
||||
}
|
||||
modifySearchParams={false}
|
||||
onQueryChange={setQuery}
|
||||
preferenceKey={preferenceKey}
|
||||
|
||||
@@ -71,31 +71,33 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
|
||||
>
|
||||
<GroupProvider>
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<div className={`${baseClass}__header`}>
|
||||
{Boolean(Label || Description || label) && (
|
||||
<header>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Label}
|
||||
Fallback={
|
||||
<h3 className={`${baseClass}__title`}>
|
||||
<FieldLabel
|
||||
as="span"
|
||||
label={getTranslation(label, i18n)}
|
||||
localized={false}
|
||||
path={path}
|
||||
required={false}
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
/>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Description}
|
||||
Fallback={<FieldDescription description={description} path={path} />}
|
||||
/>
|
||||
</header>
|
||||
)}
|
||||
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
||||
</div>
|
||||
{Boolean(Label || Description || label || fieldHasErrors) && (
|
||||
<div className={`${baseClass}__header`}>
|
||||
{Boolean(Label || Description || label) && (
|
||||
<header>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Label}
|
||||
Fallback={
|
||||
<h3 className={`${baseClass}__title`}>
|
||||
<FieldLabel
|
||||
as="span"
|
||||
label={getTranslation(label, i18n)}
|
||||
localized={false}
|
||||
path={path}
|
||||
required={false}
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
/>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Description}
|
||||
Fallback={<FieldDescription description={description} path={path} />}
|
||||
/>
|
||||
</header>
|
||||
)}
|
||||
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
||||
</div>
|
||||
)}
|
||||
{BeforeInput}
|
||||
<RenderFields
|
||||
fields={fields}
|
||||
|
||||
@@ -27,11 +27,11 @@ import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { mergeFieldStyles } from '../mergeFieldStyles.js'
|
||||
import { fieldBaseClass } from '../shared/index.js'
|
||||
import { createRelationMap } from './createRelationMap.js'
|
||||
import './index.scss'
|
||||
import { findOptionsByValue } from './findOptionsByValue.js'
|
||||
import { optionsReducer } from './optionsReducer.js'
|
||||
import { MultiValueLabel } from './select-components/MultiValueLabel/index.js'
|
||||
import { SingleValue } from './select-components/SingleValue/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const maxResultsPerRequest = 10
|
||||
|
||||
@@ -310,7 +310,6 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
// ///////////////////////////////////
|
||||
// Ensure we have an option for each value
|
||||
// ///////////////////////////////////
|
||||
|
||||
useIgnoredEffect(
|
||||
() => {
|
||||
const relationMap = createRelationMap({
|
||||
|
||||
@@ -30,6 +30,7 @@ export type SelectInputProps = {
|
||||
readonly localized?: boolean
|
||||
readonly name: string
|
||||
readonly onChange?: ReactSelectAdapterProps['onChange']
|
||||
readonly onInputChange?: ReactSelectAdapterProps['onInputChange']
|
||||
readonly options?: OptionObject[]
|
||||
readonly path: string
|
||||
readonly readOnly?: boolean
|
||||
@@ -54,6 +55,7 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
Label,
|
||||
localized,
|
||||
onChange,
|
||||
onInputChange,
|
||||
options,
|
||||
path,
|
||||
readOnly,
|
||||
@@ -115,6 +117,7 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
isMulti={hasMany}
|
||||
isSortable={isSortable}
|
||||
onChange={onChange}
|
||||
onInputChange={onInputChange}
|
||||
options={options.map((option) => ({
|
||||
...option,
|
||||
label: getTranslation(option.label, i18n),
|
||||
|
||||
@@ -485,6 +485,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
docPermissions,
|
||||
docPreferences,
|
||||
globalSlug,
|
||||
locale,
|
||||
operation,
|
||||
renderAllFields: true,
|
||||
schemaPath: collectionSlug ? collectionSlug : globalSlug,
|
||||
@@ -504,6 +505,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
getFormState,
|
||||
docPermissions,
|
||||
getDocPreferences,
|
||||
locale,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { ListQuery, PaginatedDocs, Where } from 'payload'
|
||||
import type { ListQuery, PaginatedDocs, Sort, Where } from 'payload'
|
||||
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import { isNumber } from 'payload/shared'
|
||||
@@ -27,7 +27,7 @@ export type ListQueryProps = {
|
||||
readonly collectionSlug: string
|
||||
readonly data: PaginatedDocs
|
||||
readonly defaultLimit?: number
|
||||
readonly defaultSort?: string
|
||||
readonly defaultSort?: Sort
|
||||
readonly modifySearchParams?: boolean
|
||||
readonly onQueryChange?: (query: ListQuery) => void
|
||||
readonly preferenceKey?: string
|
||||
@@ -36,7 +36,7 @@ export type ListQueryProps = {
|
||||
export type ListQueryContext = {
|
||||
data: PaginatedDocs
|
||||
defaultLimit?: number
|
||||
defaultSort?: string
|
||||
defaultSort?: Sort
|
||||
query: ListQuery
|
||||
refineListData: (args: ListQuery) => Promise<void>
|
||||
} & ContextHandlers
|
||||
@@ -103,10 +103,13 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
}
|
||||
|
||||
const newQuery: ListQuery = {
|
||||
limit: 'limit' in query ? query.limit : (currentQuery?.limit as string),
|
||||
limit:
|
||||
'limit' in query
|
||||
? query.limit
|
||||
: ((currentQuery?.limit as string) ?? String(defaultLimit)),
|
||||
page: pageQuery as string,
|
||||
search: 'search' in query ? query.search : (currentQuery?.search as string),
|
||||
sort: 'sort' in query ? query.sort : (currentQuery?.sort as string),
|
||||
sort: 'sort' in query ? query.sort : ((currentQuery?.sort as string) ?? defaultSort),
|
||||
where: 'where' in query ? query.where : (currentQuery?.where as Where),
|
||||
}
|
||||
|
||||
|
||||
@@ -21,73 +21,64 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
|
||||
const defaultLocale =
|
||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||
|
||||
const { getPreference, setPreference } = usePreferences()
|
||||
const searchParams = useSearchParams()
|
||||
const localeFromParams = searchParams.get('locale')
|
||||
|
||||
const [localeCode, setLocaleCode] = useState<string>(localeFromParams || defaultLocale)
|
||||
const [localeCode, setLocaleCode] = useState<string>(defaultLocale)
|
||||
|
||||
const [locale, setLocale] = useState<Locale | null>(
|
||||
localization && findLocaleFromCode(localization, localeCode),
|
||||
)
|
||||
const locale: Locale = React.useMemo(() => {
|
||||
if (!localization) {
|
||||
// TODO: return null V4
|
||||
return {} as Locale
|
||||
}
|
||||
|
||||
const { getPreference, setPreference } = usePreferences()
|
||||
|
||||
const switchLocale = React.useCallback(
|
||||
async (newLocale: string) => {
|
||||
if (!localization) {
|
||||
return
|
||||
}
|
||||
|
||||
const localeToSet =
|
||||
localization.localeCodes.indexOf(newLocale) > -1 ? newLocale : defaultLocale
|
||||
|
||||
if (localeToSet !== localeCode) {
|
||||
setLocaleCode(localeToSet)
|
||||
setLocale(findLocaleFromCode(localization, localeToSet))
|
||||
try {
|
||||
if (user) {
|
||||
await setPreference('locale', localeToSet)
|
||||
}
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
}
|
||||
}
|
||||
},
|
||||
[localization, setPreference, user, defaultLocale, localeCode],
|
||||
)
|
||||
return (
|
||||
findLocaleFromCode(localization, localeFromParams || localeCode) ||
|
||||
findLocaleFromCode(localization, defaultLocale)
|
||||
)
|
||||
}, [localeCode, localeFromParams, localization, defaultLocale])
|
||||
|
||||
useEffect(() => {
|
||||
async function setInitialLocale() {
|
||||
let localeToSet = defaultLocale
|
||||
|
||||
if (typeof localeFromParams === 'string') {
|
||||
localeToSet = localeFromParams
|
||||
} else if (user) {
|
||||
try {
|
||||
localeToSet = await getPreference<string>('locale')
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
if (localization && user) {
|
||||
if (typeof localeFromParams !== 'string') {
|
||||
try {
|
||||
const localeToSet = await getPreference<string>('locale')
|
||||
setLocaleCode(localeToSet)
|
||||
} catch (_) {
|
||||
setLocaleCode(defaultLocale)
|
||||
}
|
||||
} else {
|
||||
void setPreference(
|
||||
'locale',
|
||||
findLocaleFromCode(localization, localeFromParams)?.code || defaultLocale,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await switchLocale(localeToSet)
|
||||
}
|
||||
|
||||
void setInitialLocale()
|
||||
}, [
|
||||
defaultLocale,
|
||||
getPreference,
|
||||
localization,
|
||||
localeFromParams,
|
||||
setPreference,
|
||||
user,
|
||||
switchLocale,
|
||||
])
|
||||
}, [defaultLocale, getPreference, localization, localeFromParams, setPreference, user])
|
||||
|
||||
return <LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the current locale object.
|
||||
* @deprecated A hook that returns the current locale object.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* #### 🚨 V4 Breaking Change
|
||||
* The `useLocale` return type now reflects `null | Locale` instead of `false | Locale`.
|
||||
*
|
||||
* **Old (V3):**
|
||||
* ```ts
|
||||
* const { code } = useLocale();
|
||||
* ```
|
||||
* **New (V4):**
|
||||
* ```ts
|
||||
* const locale = useLocale();
|
||||
* ```
|
||||
*/
|
||||
export const useLocale = (): Locale => useContext(LocaleContext)
|
||||
|
||||
@@ -4,8 +4,6 @@ import { useSearchParams as useNextSearchParams } from 'next/navigation.js'
|
||||
import * as qs from 'qs-esm'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
|
||||
export type SearchParamsContext = {
|
||||
searchParams: qs.ParsedQs
|
||||
stringifyParams: ({ params, replace }: { params: qs.ParsedQs; replace?: boolean }) => string
|
||||
@@ -28,8 +26,16 @@ const Context = createContext(initialContext)
|
||||
*/
|
||||
export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const nextSearchParams = useNextSearchParams()
|
||||
const searchString = nextSearchParams.toString()
|
||||
|
||||
const [searchParams, setSearchParams] = React.useState(() => parseSearchParams(nextSearchParams))
|
||||
const searchParams = React.useMemo(
|
||||
() =>
|
||||
qs.parse(searchString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
}),
|
||||
[searchString],
|
||||
)
|
||||
|
||||
const stringifyParams = React.useCallback(
|
||||
({ params, replace = false }: { params: qs.ParsedQs; replace?: boolean }) => {
|
||||
@@ -44,10 +50,6 @@ export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({
|
||||
[searchParams],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
setSearchParams(parseSearchParams(nextSearchParams))
|
||||
}, [nextSearchParams])
|
||||
|
||||
return <Context.Provider value={{ searchParams, stringifyParams }}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,14 @@ export function groupNavItems(
|
||||
if (permissions?.[entityToGroup.type.toLowerCase()]?.[entityToGroup.entity.slug]?.read) {
|
||||
const translatedGroup = getTranslation(entityToGroup.entity.admin.group, i18n)
|
||||
|
||||
const labelOrFunction =
|
||||
'labels' in entityToGroup.entity
|
||||
? entityToGroup.entity.labels.plural
|
||||
: entityToGroup.entity.label
|
||||
|
||||
const label =
|
||||
typeof labelOrFunction === 'function' ? labelOrFunction({ t: i18n.t }) : labelOrFunction
|
||||
|
||||
if (entityToGroup.entity.admin.group) {
|
||||
const existingGroup = groups.find(
|
||||
(group) => getTranslation(group.label, i18n) === translatedGroup,
|
||||
@@ -57,12 +65,7 @@ export function groupNavItems(
|
||||
matchedGroup.entities.push({
|
||||
slug: entityToGroup.entity.slug,
|
||||
type: entityToGroup.type,
|
||||
label:
|
||||
'labels' in entityToGroup.entity
|
||||
? typeof entityToGroup.entity.labels.plural === 'function'
|
||||
? entityToGroup.entity.labels.plural({ t: i18n.t })
|
||||
: entityToGroup.entity.labels.plural
|
||||
: entityToGroup.entity.label,
|
||||
label,
|
||||
})
|
||||
} else {
|
||||
const defaultGroup = groups.find((group) => {
|
||||
@@ -71,12 +74,7 @@ export function groupNavItems(
|
||||
defaultGroup.entities.push({
|
||||
slug: entityToGroup.entity.slug,
|
||||
type: entityToGroup.type,
|
||||
label:
|
||||
'labels' in entityToGroup.entity
|
||||
? typeof entityToGroup.entity.labels.plural === 'function'
|
||||
? entityToGroup.entity.labels.plural({ t: i18n.t })
|
||||
: entityToGroup.entity.labels.plural
|
||||
: entityToGroup.entity.label,
|
||||
label,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -269,8 +269,8 @@ importers:
|
||||
specifier: 1.6.2
|
||||
version: 1.6.2
|
||||
mongoose:
|
||||
specifier: 8.8.1
|
||||
version: 8.8.1(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
|
||||
specifier: 8.8.3
|
||||
version: 8.8.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
|
||||
mongoose-aggregate-paginate-v2:
|
||||
specifier: 1.1.2
|
||||
version: 1.1.2
|
||||
@@ -1745,8 +1745,8 @@ importers:
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
mongoose:
|
||||
specifier: 8.8.1
|
||||
version: 8.8.1(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
|
||||
specifier: 8.8.3
|
||||
version: 8.8.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
|
||||
next:
|
||||
specifier: 15.0.2
|
||||
version: 15.0.2(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-df7b47d-20241124)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.77.4)
|
||||
@@ -8272,8 +8272,8 @@ packages:
|
||||
resolution: {integrity: sha512-kFxhot+yw9KmpAGSSrF/o+f00aC2uawgNUbhyaM0USS9L7dln1NA77/pLg4lgOaRgXMtfgCENamjqZwIM1Zrig==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
mongoose@8.8.1:
|
||||
resolution: {integrity: sha512-l7DgeY1szT98+EKU8GYnga5WnyatAu+kOQ2VlVX1Mxif6A0Umt0YkSiksCiyGxzx8SPhGe9a53ND1GD4yVDrPA==}
|
||||
mongoose@8.8.3:
|
||||
resolution: {integrity: sha512-/I4n/DcXqXyIiLRfAmUIiTjj3vXfeISke8dt4U4Y8Wfm074Wa6sXnQrXN49NFOFf2mM1kUdOXryoBvkuCnr+Qw==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
|
||||
mpath@0.9.0:
|
||||
@@ -14568,7 +14568,7 @@ snapshots:
|
||||
'@types/mongoose-aggregate-paginate-v2@1.0.12(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)':
|
||||
dependencies:
|
||||
'@types/node': 22.5.4
|
||||
mongoose: 8.8.1(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
|
||||
mongoose: 8.8.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@mongodb-js/zstd'
|
||||
@@ -18416,7 +18416,7 @@ snapshots:
|
||||
|
||||
mongoose-paginate-v2@1.8.5: {}
|
||||
|
||||
mongoose@8.8.1(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3):
|
||||
mongoose@8.8.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3):
|
||||
dependencies:
|
||||
bson: 6.9.0
|
||||
kareem: 2.6.3
|
||||
|
||||
@@ -112,7 +112,7 @@ export const generateReleaseNotes = async (args: Args = {}): Promise<ChangelogRe
|
||||
|
||||
return sections
|
||||
},
|
||||
{} as Record<Sections | 'breaking', GitCommit[]>,
|
||||
{} as Record<'breaking' | Sections, GitCommit[]>,
|
||||
)
|
||||
|
||||
// Sort commits by scope, unscoped first
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
const SITE_URL =
|
||||
process.env.NEXT_PUBLIC_SERVER_URL ||
|
||||
process.env.VERCEL_PROJECT_PRODUCTION_URL ||
|
||||
'https://example.com'
|
||||
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
siteUrl: SITE_URL,
|
||||
generateRobotsTxt: true,
|
||||
exclude: ['/posts-sitemap.xml', '/pages-sitemap.xml', '/*', '/posts/*'],
|
||||
robotsTxtOptions: {
|
||||
policies: [
|
||||
{
|
||||
userAgent: '*',
|
||||
disallow: '/admin/*',
|
||||
},
|
||||
],
|
||||
additionalSitemaps: [`${SITE_URL}/pages-sitemap.xml`, `${SITE_URL}/posts-sitemap.xml`],
|
||||
},
|
||||
}
|
||||
|
||||
521
templates/website/pnpm-lock.yaml
generated
521
templates/website/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
13
templates/website/src/Footer/RowLabel.tsx
Normal file
13
templates/website/src/Footer/RowLabel.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
import { Header } from '@/payload-types'
|
||||
import { RowLabelProps, useRowLabel } from '@payloadcms/ui'
|
||||
|
||||
export const RowLabel: React.FC<RowLabelProps> = (props) => {
|
||||
const data = useRowLabel<NonNullable<Header['navItems']>[number]>()
|
||||
|
||||
const label = data?.data?.link?.label
|
||||
? `Nav item ${data.rowNumber !== undefined ? data.rowNumber + 1 : ''}: ${data?.data?.link?.label}`
|
||||
: 'Row'
|
||||
|
||||
return <div>{label}</div>
|
||||
}
|
||||
@@ -18,6 +18,12 @@ export const Footer: GlobalConfig = {
|
||||
}),
|
||||
],
|
||||
maxRows: 6,
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
components: {
|
||||
RowLabel: '@/Footer/RowLabel#RowLabel',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
|
||||
@@ -2,10 +2,12 @@ import type { GlobalAfterChangeHook } from 'payload'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload } }) => {
|
||||
payload.logger.info(`Revalidating footer`)
|
||||
export const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
|
||||
if (!context.disableRevalidate) {
|
||||
payload.logger.info(`Revalidating footer`)
|
||||
|
||||
revalidateTag('global_footer')
|
||||
revalidateTag('global_footer')
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
13
templates/website/src/Header/RowLabel.tsx
Normal file
13
templates/website/src/Header/RowLabel.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
import { Header } from '@/payload-types'
|
||||
import { RowLabelProps, useRowLabel } from '@payloadcms/ui'
|
||||
|
||||
export const RowLabel: React.FC<RowLabelProps> = (props) => {
|
||||
const data = useRowLabel<NonNullable<Header['navItems']>[number]>()
|
||||
|
||||
const label = data?.data?.link?.label
|
||||
? `Nav item ${data.rowNumber !== undefined ? data.rowNumber + 1 : ''}: ${data?.data?.link?.label}`
|
||||
: 'Row'
|
||||
|
||||
return <div>{label}</div>
|
||||
}
|
||||
@@ -18,6 +18,12 @@ export const Header: GlobalConfig = {
|
||||
}),
|
||||
],
|
||||
maxRows: 6,
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
components: {
|
||||
RowLabel: '@/Header/RowLabel#RowLabel',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
|
||||
@@ -2,10 +2,12 @@ import type { GlobalAfterChangeHook } from 'payload'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export const revalidateHeader: GlobalAfterChangeHook = ({ doc, req: { payload } }) => {
|
||||
payload.logger.info(`Revalidating header`)
|
||||
export const revalidateHeader: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
|
||||
if (!context.disableRevalidate) {
|
||||
payload.logger.info(`Revalidating header`)
|
||||
|
||||
revalidateTag('global_header')
|
||||
revalidateTag('global_header')
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { seed } from '@/endpoints/seed'
|
||||
import config from '@payload-config'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
const payloadToken = 'payload-token'
|
||||
export const maxDuration = 60 // This function can run for a maximum of 60 seconds
|
||||
|
||||
export async function POST(
|
||||
|
||||
@@ -58,7 +58,7 @@ export default async function Post({ params: paramsPromise }: Args) {
|
||||
|
||||
<div className="flex flex-col items-center gap-4 pt-8">
|
||||
<div className="container">
|
||||
<RichText className="max-w-[48rem] mx-auto" content={post.content} enableGutter={false} />
|
||||
<RichText className="max-w-[48rem] mx-auto" data={post.content} enableGutter={false} />
|
||||
{post.relatedPosts && post.relatedPosts.length > 0 && (
|
||||
<RelatedPosts
|
||||
className="mt-12 max-w-[52rem] lg:grid lg:grid-cols-subgrid col-start-1 col-span-3 grid-rows-[2fr]"
|
||||
|
||||
@@ -18,6 +18,8 @@ import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f
|
||||
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'
|
||||
import { RowLabel as RowLabel_ec255a65fa6fa8d1faeb09cf35284224 } from '@/Header/RowLabel'
|
||||
import { RowLabel as RowLabel_1f6ff6ff633e3695d348f4f3c58f1466 } from '@/Footer/RowLabel'
|
||||
import { default as default_1a7510af427896d367a49dbf838d2de6 } from '@/components/BeforeDashboard'
|
||||
import { default as default_8a7ab0eb7ab5c511aba12e68480bfe5e } from '@/components/BeforeLogin'
|
||||
|
||||
@@ -59,6 +61,8 @@ export const importMap = {
|
||||
BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
'@payloadcms/plugin-search/client#LinkToDoc': LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
|
||||
'@payloadcms/plugin-search/client#ReindexButton': ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,
|
||||
'@/Header/RowLabel#RowLabel': RowLabel_ec255a65fa6fa8d1faeb09cf35284224,
|
||||
'@/Footer/RowLabel#RowLabel': RowLabel_1f6ff6ff633e3695d348f4f3c58f1466,
|
||||
'@/components/BeforeDashboard#default': default_1a7510af427896d367a49dbf838d2de6,
|
||||
'@/components/BeforeLogin#default': default_8a7ab0eb7ab5c511aba12e68480bfe5e,
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export const ArchiveBlock: React.FC<
|
||||
<div className="my-16" id={`block-${id}`}>
|
||||
{introContent && (
|
||||
<div className="container mb-16">
|
||||
<RichText className="ml-0 max-w-[48rem]" content={introContent} enableGutter={false} />
|
||||
<RichText className="ml-0 max-w-[48rem]" data={introContent} enableGutter={false} />
|
||||
</div>
|
||||
)}
|
||||
<CollectionArchive posts={posts} />
|
||||
|
||||
@@ -19,7 +19,7 @@ export const BannerBlock: React.FC<Props> = ({ className, content, style }) => {
|
||||
'border-warning bg-warning/30': style === 'warning',
|
||||
})}
|
||||
>
|
||||
<RichText content={content} enableGutter={false} enableProse={false} />
|
||||
<RichText data={content} enableGutter={false} enableProse={false} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ export const CallToActionBlock: React.FC<CTABlockProps> = ({ links, richText })
|
||||
<div className="container">
|
||||
<div className="bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center">
|
||||
<div className="max-w-[48rem] flex items-center">
|
||||
{richText && <RichText className="mb-0" content={richText} enableGutter={false} />}
|
||||
{richText && <RichText className="mb-0" data={richText} enableGutter={false} />}
|
||||
</div>
|
||||
<div className="flex flex-col gap-8">
|
||||
{(links || []).map(({ link }, i) => {
|
||||
|
||||
@@ -31,7 +31,7 @@ export const ContentBlock: React.FC<ContentBlockProps> = (props) => {
|
||||
})}
|
||||
key={index}
|
||||
>
|
||||
{richText && <RichText content={richText} enableGutter={false} />}
|
||||
{richText && <RichText data={richText} enableGutter={false} />}
|
||||
|
||||
{enableLink && <CMSLink {...link} />}
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,9 @@ export const Content: Block = {
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'array',
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: columnFields,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, { useCallback, useState } from 'react'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import RichText from '@/components/RichText'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { buildInitialFormState } from './buildInitialFormState'
|
||||
import { fields } from './fields'
|
||||
@@ -26,9 +27,7 @@ export type FormBlockType = {
|
||||
blockType?: 'formBlock'
|
||||
enableIntro: boolean
|
||||
form: FormType
|
||||
introContent?: {
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
introContent?: SerializedEditorState
|
||||
}
|
||||
|
||||
export const FormBlock: React.FC<
|
||||
@@ -128,12 +127,12 @@ export const FormBlock: React.FC<
|
||||
return (
|
||||
<div className="container lg:max-w-[48rem]">
|
||||
{enableIntro && introContent && !hasSubmitted && (
|
||||
<RichText className="mb-8 lg:mb-12" content={introContent} enableGutter={false} />
|
||||
<RichText className="mb-8 lg:mb-12" data={introContent} enableGutter={false} />
|
||||
)}
|
||||
<div className="p-4 lg:p-6 border border-border rounded-[0.8rem]">
|
||||
<FormProvider {...formMethods}>
|
||||
{!isLoading && hasSubmitted && confirmationType === 'message' && (
|
||||
<RichText content={confirmationMessage} />
|
||||
<RichText data={confirmationMessage} />
|
||||
)}
|
||||
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
||||
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
|
||||
|
||||
@@ -2,11 +2,12 @@ import RichText from '@/components/RichText'
|
||||
import React from 'react'
|
||||
|
||||
import { Width } from '../Width'
|
||||
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
export const Message: React.FC = ({ message }: { message: Record<string, any> }) => {
|
||||
export const Message: React.FC = ({ message }: { message: SerializedEditorState }) => {
|
||||
return (
|
||||
<Width className="my-12" width="100">
|
||||
{message && <RichText content={message} />}
|
||||
{message && <RichText data={message} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const MediaBlock: React.FC<Props> = (props) => {
|
||||
captionClassName,
|
||||
)}
|
||||
>
|
||||
<RichText content={caption} enableGutter={false} />
|
||||
<RichText data={caption} enableGutter={false} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -16,8 +16,8 @@ export const RelatedPosts: React.FC<RelatedPostsProps> = (props) => {
|
||||
const { className, docs, introContent } = props
|
||||
|
||||
return (
|
||||
<div className={clsx('container', className)}>
|
||||
{introContent && <RichText content={introContent} enableGutter={false} />}
|
||||
<div className={clsx('lg:container', className)}>
|
||||
{introContent && <RichText data={introContent} enableGutter={false} />}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8 items-stretch">
|
||||
{docs?.map((doc, index) => {
|
||||
|
||||
@@ -7,34 +7,37 @@ import type { Page } from '../../../payload-types'
|
||||
export const revalidatePage: CollectionAfterChangeHook<Page> = ({
|
||||
doc,
|
||||
previousDoc,
|
||||
req: { payload },
|
||||
req: { payload, context },
|
||||
}) => {
|
||||
if (doc._status === 'published') {
|
||||
const path = doc.slug === 'home' ? '/' : `/${doc.slug}`
|
||||
if (!context.disableRevalidate) {
|
||||
if (doc._status === 'published') {
|
||||
const path = doc.slug === 'home' ? '/' : `/${doc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating page at path: ${path}`)
|
||||
payload.logger.info(`Revalidating page at path: ${path}`)
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('pages-sitemap')
|
||||
}
|
||||
|
||||
// If the page was previously published, we need to revalidate the old path
|
||||
if (previousDoc?._status === 'published' && doc._status !== 'published') {
|
||||
const oldPath = previousDoc.slug === 'home' ? '/' : `/${previousDoc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating old page at path: ${oldPath}`)
|
||||
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('pages-sitemap')
|
||||
}
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Page> = ({ doc, req: { context } }) => {
|
||||
if (!context.disableRevalidate) {
|
||||
const path = doc?.slug === 'home' ? '/' : `/${doc?.slug}`
|
||||
revalidatePath(path)
|
||||
revalidateTag('pages-sitemap')
|
||||
}
|
||||
|
||||
// If the page was previously published, we need to revalidate the old path
|
||||
if (previousDoc?._status === 'published' && doc._status !== 'published') {
|
||||
const oldPath = previousDoc.slug === 'home' ? '/' : `/${previousDoc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating old page at path: ${oldPath}`)
|
||||
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('pages-sitemap')
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Page> = ({ doc }) => {
|
||||
const path = doc?.slug === 'home' ? '/' : `/${doc?.slug}`
|
||||
revalidatePath(path)
|
||||
revalidateTag('pages-sitemap')
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||
type: 'blocks',
|
||||
blocks: [CallToAction, Content, MediaBlock, Archive, FormBlock],
|
||||
required: true,
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: 'Content',
|
||||
|
||||
@@ -7,35 +7,38 @@ import type { Post } from '../../../payload-types'
|
||||
export const revalidatePost: CollectionAfterChangeHook<Post> = ({
|
||||
doc,
|
||||
previousDoc,
|
||||
req: { payload },
|
||||
req: { payload, context },
|
||||
}) => {
|
||||
if (doc._status === 'published') {
|
||||
const path = `/posts/${doc.slug}`
|
||||
if (!context.disableRevalidate) {
|
||||
if (doc._status === 'published') {
|
||||
const path = `/posts/${doc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating post at path: ${path}`)
|
||||
payload.logger.info(`Revalidating post at path: ${path}`)
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('posts-sitemap')
|
||||
}
|
||||
|
||||
// If the post was previously published, we need to revalidate the old path
|
||||
if (previousDoc._status === 'published' && doc._status !== 'published') {
|
||||
const oldPath = `/posts/${previousDoc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating old post at path: ${oldPath}`)
|
||||
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('posts-sitemap')
|
||||
}
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Post> = ({ doc, req: { context } }) => {
|
||||
if (!context.disableRevalidate) {
|
||||
const path = `/posts/${doc?.slug}`
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('posts-sitemap')
|
||||
}
|
||||
|
||||
// If the post was previously published, we need to revalidate the old path
|
||||
if (previousDoc._status === 'published' && doc._status !== 'published') {
|
||||
const oldPath = `/posts/${previousDoc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating old post at path: ${oldPath}`)
|
||||
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('posts-sitemap')
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Post> = ({ doc }) => {
|
||||
const path = `/posts/${doc?.slug}`
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('posts-sitemap')
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -1,43 +1,65 @@
|
||||
import { cn } from '@/utilities/cn'
|
||||
import React from 'react'
|
||||
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
||||
import { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
|
||||
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
import {
|
||||
JSXConvertersFunction,
|
||||
RichText as RichTextWithoutBlocks,
|
||||
} from '@payloadcms/richtext-lexical/react'
|
||||
|
||||
import { serializeLexical } from './serialize'
|
||||
import { CodeBlock, CodeBlockProps } from '@/blocks/Code/Component'
|
||||
|
||||
import type {
|
||||
BannerBlock as BannerBlockProps,
|
||||
CallToActionBlock as CTABlockProps,
|
||||
MediaBlock as MediaBlockProps,
|
||||
} from '@/payload-types'
|
||||
import { BannerBlock } from '@/blocks/Banner/Component'
|
||||
import { CallToActionBlock } from '@/blocks/CallToAction/Component'
|
||||
import { cn } from '@/utilities/cn'
|
||||
|
||||
type NodeTypes =
|
||||
| DefaultNodeTypes
|
||||
| SerializedBlockNode<CTABlockProps | MediaBlockProps | BannerBlockProps | CodeBlockProps>
|
||||
|
||||
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
|
||||
...defaultConverters,
|
||||
blocks: {
|
||||
banner: ({ node }) => <BannerBlock className="col-start-2 mb-4" {...node.fields} />,
|
||||
mediaBlock: ({ node }) => (
|
||||
<MediaBlock
|
||||
className="col-start-1 col-span-3"
|
||||
imgClassName="m-0"
|
||||
{...node.fields}
|
||||
captionClassName="mx-auto max-w-[48rem]"
|
||||
enableGutter={false}
|
||||
disableInnerContainer={true}
|
||||
/>
|
||||
),
|
||||
code: ({ node }) => <CodeBlock className="col-start-2" {...node.fields} />,
|
||||
cta: ({ node }) => <CallToActionBlock {...node.fields} />,
|
||||
},
|
||||
})
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
content: Record<string, any>
|
||||
data: SerializedEditorState
|
||||
enableGutter?: boolean
|
||||
enableProse?: boolean
|
||||
}
|
||||
|
||||
const RichText: React.FC<Props> = ({
|
||||
className,
|
||||
content,
|
||||
enableGutter = true,
|
||||
enableProse = true,
|
||||
}) => {
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export default function RichText(props: Props) {
|
||||
const { className, enableProse = true, enableGutter = true, ...rest } = props
|
||||
return (
|
||||
<div
|
||||
<RichTextWithoutBlocks
|
||||
converters={jsxConverters}
|
||||
className={cn(
|
||||
{
|
||||
'container ': enableGutter,
|
||||
'max-w-none': !enableGutter,
|
||||
'mx-auto prose dark:prose-invert ': enableProse,
|
||||
'mx-auto prose md:prose-md dark:prose-invert ': enableProse,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{content &&
|
||||
!Array.isArray(content) &&
|
||||
typeof content === 'object' &&
|
||||
'root' in content &&
|
||||
serializeLexical({ nodes: content?.root?.children })}
|
||||
</div>
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default RichText
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
// @ts-nocheck
|
||||
//This copy-and-pasted from lexical here: https://github.com/facebook/lexical/blob/c2ceee223f46543d12c574e62155e619f9a18a5d/packages/lexical/src/LexicalConstants.ts
|
||||
|
||||
import type { ElementFormatType, TextFormatType } from '@payloadcms/richtext-lexical/lexical'
|
||||
import type {
|
||||
TextDetailType,
|
||||
TextModeType,
|
||||
} from '@payloadcms/richtext-lexical/lexical/nodes/LexicalTextNode'
|
||||
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
// DOM
|
||||
export const DOM_ELEMENT_TYPE = 1
|
||||
export const DOM_TEXT_TYPE = 3
|
||||
|
||||
// Reconciling
|
||||
export const NO_DIRTY_NODES = 0
|
||||
export const HAS_DIRTY_NODES = 1
|
||||
export const FULL_RECONCILE = 2
|
||||
|
||||
// Text node modes
|
||||
export const IS_NORMAL = 0
|
||||
export const IS_TOKEN = 1
|
||||
export const IS_SEGMENTED = 2
|
||||
// IS_INERT = 3
|
||||
|
||||
// Text node formatting
|
||||
export const IS_BOLD = 1
|
||||
export const IS_ITALIC = 1 << 1
|
||||
export const IS_STRIKETHROUGH = 1 << 2
|
||||
export const IS_UNDERLINE = 1 << 3
|
||||
export const IS_CODE = 1 << 4
|
||||
export const IS_SUBSCRIPT = 1 << 5
|
||||
export const IS_SUPERSCRIPT = 1 << 6
|
||||
export const IS_HIGHLIGHT = 1 << 7
|
||||
|
||||
export const IS_ALL_FORMATTING =
|
||||
IS_BOLD |
|
||||
IS_ITALIC |
|
||||
IS_STRIKETHROUGH |
|
||||
IS_UNDERLINE |
|
||||
IS_CODE |
|
||||
IS_SUBSCRIPT |
|
||||
IS_SUPERSCRIPT |
|
||||
IS_HIGHLIGHT
|
||||
|
||||
// Text node details
|
||||
export const IS_DIRECTIONLESS = 1
|
||||
export const IS_UNMERGEABLE = 1 << 1
|
||||
|
||||
// Element node formatting
|
||||
export const IS_ALIGN_LEFT = 1
|
||||
export const IS_ALIGN_CENTER = 2
|
||||
export const IS_ALIGN_RIGHT = 3
|
||||
export const IS_ALIGN_JUSTIFY = 4
|
||||
export const IS_ALIGN_START = 5
|
||||
export const IS_ALIGN_END = 6
|
||||
|
||||
// Reconciliation
|
||||
export const NON_BREAKING_SPACE = '\u00A0'
|
||||
const ZERO_WIDTH_SPACE = '\u200b'
|
||||
|
||||
export const DOUBLE_LINE_BREAK = '\n\n'
|
||||
|
||||
// For FF, we need to use a non-breaking space, or it gets composition
|
||||
// in a stuck state.
|
||||
|
||||
const RTL = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'
|
||||
const LTR =
|
||||
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6' +
|
||||
'\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C' +
|
||||
'\uFE00-\uFE6F\uFEFD-\uFFFF'
|
||||
|
||||
export const RTL_REGEX = new RegExp('^[^' + LTR + ']*[' + RTL + ']')
|
||||
|
||||
export const LTR_REGEX = new RegExp('^[^' + RTL + ']*[' + LTR + ']')
|
||||
|
||||
export const TEXT_TYPE_TO_FORMAT: Record<TextFormatType | string, number> = {
|
||||
bold: IS_BOLD,
|
||||
code: IS_CODE,
|
||||
highlight: IS_HIGHLIGHT,
|
||||
italic: IS_ITALIC,
|
||||
strikethrough: IS_STRIKETHROUGH,
|
||||
subscript: IS_SUBSCRIPT,
|
||||
superscript: IS_SUPERSCRIPT,
|
||||
underline: IS_UNDERLINE,
|
||||
}
|
||||
|
||||
export const DETAIL_TYPE_TO_DETAIL: Record<TextDetailType | string, number> = {
|
||||
directionless: IS_DIRECTIONLESS,
|
||||
unmergeable: IS_UNMERGEABLE,
|
||||
}
|
||||
|
||||
export const ELEMENT_TYPE_TO_FORMAT: Record<Exclude<ElementFormatType, ''>, number> = {
|
||||
center: IS_ALIGN_CENTER,
|
||||
end: IS_ALIGN_END,
|
||||
justify: IS_ALIGN_JUSTIFY,
|
||||
left: IS_ALIGN_LEFT,
|
||||
right: IS_ALIGN_RIGHT,
|
||||
start: IS_ALIGN_START,
|
||||
}
|
||||
|
||||
export const ELEMENT_FORMAT_TO_TYPE: Record<number, ElementFormatType> = {
|
||||
[IS_ALIGN_CENTER]: 'center',
|
||||
[IS_ALIGN_END]: 'end',
|
||||
[IS_ALIGN_JUSTIFY]: 'justify',
|
||||
[IS_ALIGN_LEFT]: 'left',
|
||||
[IS_ALIGN_RIGHT]: 'right',
|
||||
[IS_ALIGN_START]: 'start',
|
||||
}
|
||||
|
||||
export const TEXT_MODE_TO_TYPE: Record<TextModeType, 0 | 1 | 2> = {
|
||||
normal: IS_NORMAL,
|
||||
segmented: IS_SEGMENTED,
|
||||
token: IS_TOKEN,
|
||||
}
|
||||
|
||||
export const TEXT_TYPE_TO_MODE: Record<number, TextModeType> = {
|
||||
[IS_NORMAL]: 'normal',
|
||||
[IS_SEGMENTED]: 'segmented',
|
||||
[IS_TOKEN]: 'token',
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
import { BannerBlock } from '@/blocks/Banner/Component'
|
||||
import { CallToActionBlock } from '@/blocks/CallToAction/Component'
|
||||
import { CodeBlock, CodeBlockProps } from '@/blocks/Code/Component'
|
||||
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
||||
import React, { Fragment, JSX } from 'react'
|
||||
import { CMSLink } from '@/components/Link'
|
||||
import { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
|
||||
import type { BannerBlock as BannerBlockProps } from '@/payload-types'
|
||||
|
||||
import {
|
||||
IS_BOLD,
|
||||
IS_CODE,
|
||||
IS_ITALIC,
|
||||
IS_STRIKETHROUGH,
|
||||
IS_SUBSCRIPT,
|
||||
IS_SUPERSCRIPT,
|
||||
IS_UNDERLINE,
|
||||
} from './nodeFormat'
|
||||
import type {
|
||||
CallToActionBlock as CTABlockProps,
|
||||
MediaBlock as MediaBlockProps,
|
||||
} from '@/payload-types'
|
||||
|
||||
export type NodeTypes =
|
||||
| DefaultNodeTypes
|
||||
| SerializedBlockNode<CTABlockProps | MediaBlockProps | BannerBlockProps | CodeBlockProps>
|
||||
|
||||
type Props = {
|
||||
nodes: NodeTypes[]
|
||||
}
|
||||
|
||||
export function serializeLexical({ nodes }: Props): JSX.Element {
|
||||
return (
|
||||
<Fragment>
|
||||
{nodes?.map((node, index): JSX.Element | null => {
|
||||
if (node == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (node.type === 'text') {
|
||||
let text = <React.Fragment key={index}>{node.text}</React.Fragment>
|
||||
if (node.format & IS_BOLD) {
|
||||
text = <strong key={index}>{text}</strong>
|
||||
}
|
||||
if (node.format & IS_ITALIC) {
|
||||
text = <em key={index}>{text}</em>
|
||||
}
|
||||
if (node.format & IS_STRIKETHROUGH) {
|
||||
text = (
|
||||
<span key={index} style={{ textDecoration: 'line-through' }}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
if (node.format & IS_UNDERLINE) {
|
||||
text = (
|
||||
<span key={index} style={{ textDecoration: 'underline' }}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
if (node.format & IS_CODE) {
|
||||
text = <code key={index}>{node.text}</code>
|
||||
}
|
||||
if (node.format & IS_SUBSCRIPT) {
|
||||
text = <sub key={index}>{text}</sub>
|
||||
}
|
||||
if (node.format & IS_SUPERSCRIPT) {
|
||||
text = <sup key={index}>{text}</sup>
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
// NOTE: Hacky fix for
|
||||
// https://github.com/facebook/lexical/blob/d10c4e6e55261b2fdd7d1845aed46151d0f06a8c/packages/lexical-list/src/LexicalListItemNode.ts#L133
|
||||
// which does not return checked: false (only true - i.e. there is no prop for false)
|
||||
const serializedChildrenFn = (node: NodeTypes): JSX.Element | null => {
|
||||
if (node.children == null) {
|
||||
return null
|
||||
} else {
|
||||
if (node?.type === 'list' && node?.listType === 'check') {
|
||||
for (const item of node.children) {
|
||||
if ('checked' in item) {
|
||||
if (!item?.checked) {
|
||||
item.checked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return serializeLexical({ nodes: node.children as NodeTypes[] })
|
||||
}
|
||||
}
|
||||
|
||||
const serializedChildren = 'children' in node ? serializedChildrenFn(node) : ''
|
||||
|
||||
if (node.type === 'block') {
|
||||
const block = node.fields
|
||||
|
||||
const blockType = block?.blockType
|
||||
|
||||
if (!block || !blockType) {
|
||||
return null
|
||||
}
|
||||
|
||||
switch (blockType) {
|
||||
case 'cta':
|
||||
return <CallToActionBlock key={index} {...block} />
|
||||
case 'mediaBlock':
|
||||
return (
|
||||
<MediaBlock
|
||||
className="col-start-1 col-span-3"
|
||||
imgClassName="m-0"
|
||||
key={index}
|
||||
{...block}
|
||||
captionClassName="mx-auto max-w-[48rem]"
|
||||
enableGutter={false}
|
||||
disableInnerContainer={true}
|
||||
/>
|
||||
)
|
||||
case 'banner':
|
||||
return <BannerBlock className="col-start-2 mb-4" key={index} {...block} />
|
||||
case 'code':
|
||||
return <CodeBlock className="col-start-2" key={index} {...block} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
switch (node.type) {
|
||||
case 'linebreak': {
|
||||
return <br className="col-start-2" key={index} />
|
||||
}
|
||||
case 'paragraph': {
|
||||
return (
|
||||
<p className="col-start-2" key={index}>
|
||||
{serializedChildren}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
case 'heading': {
|
||||
const Tag = node?.tag
|
||||
return (
|
||||
<Tag className="col-start-2" key={index}>
|
||||
{serializedChildren}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
case 'list': {
|
||||
const Tag = node?.tag
|
||||
return (
|
||||
<Tag className="list col-start-2" key={index}>
|
||||
{serializedChildren}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
case 'listitem': {
|
||||
if (node?.checked != null) {
|
||||
return (
|
||||
<li
|
||||
aria-checked={node.checked ? 'true' : 'false'}
|
||||
className={` ${node.checked ? '' : ''}`}
|
||||
key={index}
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
|
||||
role="checkbox"
|
||||
tabIndex={-1}
|
||||
value={node?.value}
|
||||
>
|
||||
{serializedChildren}
|
||||
</li>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<li key={index} value={node?.value}>
|
||||
{serializedChildren}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
}
|
||||
case 'quote': {
|
||||
return (
|
||||
<blockquote className="col-start-2" key={index}>
|
||||
{serializedChildren}
|
||||
</blockquote>
|
||||
)
|
||||
}
|
||||
case 'link': {
|
||||
const fields = node.fields
|
||||
|
||||
return (
|
||||
<CMSLink
|
||||
key={index}
|
||||
newTab={Boolean(fields?.newTab)}
|
||||
reference={fields.doc as any}
|
||||
type={fields.linkType === 'internal' ? 'reference' : 'custom'}
|
||||
url={fields.url}
|
||||
>
|
||||
{serializedChildren}
|
||||
</CMSLink>
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
})}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
@@ -37,40 +37,33 @@ export const seed = async ({
|
||||
// as well as the collections and globals
|
||||
// this is because while `yarn seed` drops the database
|
||||
// the custom `/api/seed` endpoint does not
|
||||
|
||||
payload.logger.info(`— Clearing media...`)
|
||||
payload.logger.info(`— Clearing collections and globals...`)
|
||||
|
||||
// clear the database
|
||||
for (const global of globals) {
|
||||
await payload.updateGlobal({
|
||||
slug: global,
|
||||
data: {
|
||||
navItems: [],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for (const collection of collections) {
|
||||
await payload.delete({
|
||||
collection: collection,
|
||||
where: {
|
||||
id: {
|
||||
exists: true,
|
||||
await Promise.all(
|
||||
globals.map((global) =>
|
||||
payload.updateGlobal({
|
||||
slug: global,
|
||||
data: {
|
||||
navItems: [],
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
depth: 0,
|
||||
context: {
|
||||
disableRevalidate: true,
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const pages = await payload.delete({
|
||||
collection: 'pages',
|
||||
where: {},
|
||||
})
|
||||
await Promise.all(
|
||||
collections.map((collection) => payload.db.deleteMany({ collection, req, where: {} })),
|
||||
)
|
||||
|
||||
payload.logger.info(`— Seeding demo author and user...`)
|
||||
|
||||
await payload.delete({
|
||||
collection: 'users',
|
||||
depth: 0,
|
||||
where: {
|
||||
email: {
|
||||
equals: 'demo-author@payloadcms.com',
|
||||
@@ -78,18 +71,8 @@ export const seed = async ({
|
||||
},
|
||||
})
|
||||
|
||||
const demoAuthor = await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
name: 'Demo Author',
|
||||
email: 'demo-author@payloadcms.com',
|
||||
password: 'password',
|
||||
},
|
||||
})
|
||||
|
||||
let demoAuthorID: number | string = demoAuthor.id
|
||||
|
||||
payload.logger.info(`— Seeding media...`)
|
||||
|
||||
const [image1Buffer, image2Buffer, image3Buffer, hero1Buffer] = await Promise.all([
|
||||
fetchFileByURL(
|
||||
'https://raw.githubusercontent.com/payloadcms/payload/refs/heads/main/templates/website/src/endpoints/seed/image-post1.webp',
|
||||
@@ -105,69 +88,91 @@ export const seed = async ({
|
||||
),
|
||||
])
|
||||
|
||||
const image1Doc = await payload.create({
|
||||
collection: 'media',
|
||||
data: image1,
|
||||
file: image1Buffer,
|
||||
})
|
||||
const image2Doc = await payload.create({
|
||||
collection: 'media',
|
||||
data: image2,
|
||||
file: image2Buffer,
|
||||
})
|
||||
const image3Doc = await payload.create({
|
||||
collection: 'media',
|
||||
data: image2,
|
||||
file: image3Buffer,
|
||||
})
|
||||
const imageHomeDoc = await payload.create({
|
||||
collection: 'media',
|
||||
data: image2,
|
||||
file: hero1Buffer,
|
||||
})
|
||||
const [
|
||||
demoAuthor,
|
||||
image1Doc,
|
||||
image2Doc,
|
||||
image3Doc,
|
||||
imageHomeDoc,
|
||||
technologyCategory,
|
||||
newsCategory,
|
||||
financeCategory,
|
||||
designCategory,
|
||||
softwareCategory,
|
||||
engineeringCategory,
|
||||
] = await Promise.all([
|
||||
payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
name: 'Demo Author',
|
||||
email: 'demo-author@payloadcms.com',
|
||||
password: 'password',
|
||||
},
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'media',
|
||||
data: image1,
|
||||
file: image1Buffer,
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'media',
|
||||
data: image2,
|
||||
file: image2Buffer,
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'media',
|
||||
data: image2,
|
||||
file: image3Buffer,
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'media',
|
||||
data: image2,
|
||||
file: hero1Buffer,
|
||||
}),
|
||||
|
||||
payload.logger.info(`— Seeding categories...`)
|
||||
const technologyCategory = await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Technology',
|
||||
},
|
||||
})
|
||||
payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Technology',
|
||||
},
|
||||
}),
|
||||
|
||||
const newsCategory = await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'News',
|
||||
},
|
||||
})
|
||||
payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'News',
|
||||
},
|
||||
}),
|
||||
|
||||
const financeCategory = await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Finance',
|
||||
},
|
||||
})
|
||||
payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Finance',
|
||||
},
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Design',
|
||||
},
|
||||
}),
|
||||
|
||||
await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Design',
|
||||
},
|
||||
})
|
||||
payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Software',
|
||||
},
|
||||
}),
|
||||
|
||||
await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Software',
|
||||
},
|
||||
})
|
||||
payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Engineering',
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
await payload.create({
|
||||
collection: 'categories',
|
||||
data: {
|
||||
title: 'Engineering',
|
||||
},
|
||||
})
|
||||
let demoAuthorID: number | string = demoAuthor.id
|
||||
|
||||
let image1ID: number | string = image1Doc.id
|
||||
let image2ID: number | string = image2Doc.id
|
||||
@@ -188,6 +193,10 @@ export const seed = async ({
|
||||
// This way we can sort them by `createdAt` or `publishedAt` and they will be in the expected order
|
||||
const post1Doc = await payload.create({
|
||||
collection: 'posts',
|
||||
depth: 0,
|
||||
context: {
|
||||
disableRevalidate: true,
|
||||
},
|
||||
data: JSON.parse(
|
||||
JSON.stringify({ ...post1, categories: [technologyCategory.id] })
|
||||
.replace(/"\{\{IMAGE_1\}\}"/g, String(image1ID))
|
||||
@@ -198,6 +207,10 @@ export const seed = async ({
|
||||
|
||||
const post2Doc = await payload.create({
|
||||
collection: 'posts',
|
||||
depth: 0,
|
||||
context: {
|
||||
disableRevalidate: true,
|
||||
},
|
||||
data: JSON.parse(
|
||||
JSON.stringify({ ...post2, categories: [newsCategory.id] })
|
||||
.replace(/"\{\{IMAGE_1\}\}"/g, String(image2ID))
|
||||
@@ -208,6 +221,10 @@ export const seed = async ({
|
||||
|
||||
const post3Doc = await payload.create({
|
||||
collection: 'posts',
|
||||
depth: 0,
|
||||
context: {
|
||||
disableRevalidate: true,
|
||||
},
|
||||
data: JSON.parse(
|
||||
JSON.stringify({ ...post3, categories: [financeCategory.id] })
|
||||
.replace(/"\{\{IMAGE_1\}\}"/g, String(image3ID))
|
||||
@@ -217,43 +234,35 @@ export const seed = async ({
|
||||
})
|
||||
|
||||
// update each post with related posts
|
||||
await payload.update({
|
||||
id: post1Doc.id,
|
||||
collection: 'posts',
|
||||
data: {
|
||||
relatedPosts: [post2Doc.id, post3Doc.id],
|
||||
},
|
||||
})
|
||||
await payload.update({
|
||||
id: post2Doc.id,
|
||||
collection: 'posts',
|
||||
data: {
|
||||
relatedPosts: [post1Doc.id, post3Doc.id],
|
||||
},
|
||||
})
|
||||
await payload.update({
|
||||
id: post3Doc.id,
|
||||
collection: 'posts',
|
||||
data: {
|
||||
relatedPosts: [post1Doc.id, post2Doc.id],
|
||||
},
|
||||
})
|
||||
|
||||
payload.logger.info(`— Seeding home page...`)
|
||||
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: JSON.parse(
|
||||
JSON.stringify(home)
|
||||
.replace(/"\{\{IMAGE_1\}\}"/g, String(imageHomeID))
|
||||
.replace(/"\{\{IMAGE_2\}\}"/g, String(image2ID)),
|
||||
),
|
||||
})
|
||||
await Promise.all([
|
||||
payload.update({
|
||||
id: post1Doc.id,
|
||||
collection: 'posts',
|
||||
data: {
|
||||
relatedPosts: [post2Doc.id, post3Doc.id],
|
||||
},
|
||||
}),
|
||||
payload.update({
|
||||
id: post2Doc.id,
|
||||
collection: 'posts',
|
||||
data: {
|
||||
relatedPosts: [post1Doc.id, post3Doc.id],
|
||||
},
|
||||
}),
|
||||
payload.update({
|
||||
id: post3Doc.id,
|
||||
collection: 'posts',
|
||||
data: {
|
||||
relatedPosts: [post1Doc.id, post2Doc.id],
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
payload.logger.info(`— Seeding contact form...`)
|
||||
|
||||
const contactForm = await payload.create({
|
||||
collection: 'forms',
|
||||
depth: 0,
|
||||
data: JSON.parse(JSON.stringify(contactFormData)),
|
||||
})
|
||||
|
||||
@@ -263,74 +272,88 @@ export const seed = async ({
|
||||
contactFormID = `"${contactFormID}"`
|
||||
}
|
||||
|
||||
payload.logger.info(`— Seeding contact page...`)
|
||||
payload.logger.info(`— Seeding pages...`)
|
||||
|
||||
const contactPage = await payload.create({
|
||||
collection: 'pages',
|
||||
data: JSON.parse(
|
||||
JSON.stringify(contactPageData).replace(/"\{\{CONTACT_FORM_ID\}\}"/g, String(contactFormID)),
|
||||
),
|
||||
})
|
||||
const [_, contactPage] = await Promise.all([
|
||||
payload.create({
|
||||
collection: 'pages',
|
||||
depth: 0,
|
||||
data: JSON.parse(
|
||||
JSON.stringify(home)
|
||||
.replace(/"\{\{IMAGE_1\}\}"/g, String(imageHomeID))
|
||||
.replace(/"\{\{IMAGE_2\}\}"/g, String(image2ID)),
|
||||
),
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'pages',
|
||||
depth: 0,
|
||||
data: JSON.parse(
|
||||
JSON.stringify(contactPageData).replace(
|
||||
/"\{\{CONTACT_FORM_ID\}\}"/g,
|
||||
String(contactFormID),
|
||||
),
|
||||
),
|
||||
}),
|
||||
])
|
||||
|
||||
payload.logger.info(`— Seeding header...`)
|
||||
payload.logger.info(`— Seeding globals...`)
|
||||
|
||||
await payload.updateGlobal({
|
||||
slug: 'header',
|
||||
data: {
|
||||
navItems: [
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Posts',
|
||||
url: '/posts',
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
label: 'Contact',
|
||||
reference: {
|
||||
relationTo: 'pages',
|
||||
value: contactPage.id,
|
||||
await Promise.all([
|
||||
payload.updateGlobal({
|
||||
slug: 'header',
|
||||
data: {
|
||||
navItems: [
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Posts',
|
||||
url: '/posts',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
payload.logger.info(`— Seeding footer...`)
|
||||
|
||||
await payload.updateGlobal({
|
||||
slug: 'footer',
|
||||
data: {
|
||||
navItems: [
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Admin',
|
||||
url: '/admin',
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
label: 'Contact',
|
||||
reference: {
|
||||
relationTo: 'pages',
|
||||
value: contactPage.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Source Code',
|
||||
newTab: true,
|
||||
url: 'https://github.com/payloadcms/payload/tree/main/templates/website',
|
||||
],
|
||||
},
|
||||
}),
|
||||
payload.updateGlobal({
|
||||
slug: 'footer',
|
||||
data: {
|
||||
navItems: [
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Admin',
|
||||
url: '/admin',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Payload',
|
||||
newTab: true,
|
||||
url: 'https://payloadcms.com/',
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Source Code',
|
||||
newTab: true,
|
||||
url: 'https://github.com/payloadcms/payload/tree/main/templates/website',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
{
|
||||
link: {
|
||||
type: 'custom',
|
||||
label: 'Payload',
|
||||
newTab: true,
|
||||
url: 'https://payloadcms.com/',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
payload.logger.info('Seeded database successfully!')
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { type PayloadHandler } from 'payload'
|
||||
|
||||
import { seed as seedScript } from '@/endpoints/seed'
|
||||
|
||||
export const seedHandler: PayloadHandler = async (req): Promise<Response> => {
|
||||
const { payload, user } = req
|
||||
|
||||
if (!user) {
|
||||
return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
await seedScript({ payload, req })
|
||||
return Response.json({ success: true })
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
payload.logger.error(message)
|
||||
return Response.json({ error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ export const linkGroup: LinkGroupType = ({ appearances, overrides = {} } = {}) =
|
||||
appearances,
|
||||
}),
|
||||
],
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
}
|
||||
|
||||
return deepMerge(generatedLinkGroup, overrides)
|
||||
|
||||
@@ -21,10 +21,10 @@ export const HighImpactHero: React.FC<Page['hero']> = ({ links, media, richText
|
||||
data-theme="dark"
|
||||
>
|
||||
<div className="container mb-8 z-10 relative flex items-center justify-center">
|
||||
<div className="max-w-[36.5rem] text-center">
|
||||
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
|
||||
<div className="max-w-[36.5rem] md:text-center">
|
||||
{richText && <RichText className="mb-6" data={richText} enableGutter={false} />}
|
||||
{Array.isArray(links) && links.length > 0 && (
|
||||
<ul className="flex justify-center gap-4">
|
||||
<ul className="flex md:justify-center gap-4">
|
||||
{links.map(({ link }, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const LowImpactHero: React.FC<LowImpactHeroType> = ({ children, richText
|
||||
return (
|
||||
<div className="container mt-16">
|
||||
<div className="max-w-[48rem]">
|
||||
{children || (richText && <RichText content={richText} enableGutter={false} />)}
|
||||
{children || (richText && <RichText data={richText} enableGutter={false} />)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
|
||||
return (
|
||||
<div className="">
|
||||
<div className="container mb-8">
|
||||
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
|
||||
{richText && <RichText className="mb-6" data={richText} enableGutter={false} />}
|
||||
|
||||
{Array.isArray(links) && links.length > 0 && (
|
||||
<ul className="flex gap-4">
|
||||
@@ -36,7 +36,7 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
|
||||
/>
|
||||
{media?.caption && (
|
||||
<div className="mt-3">
|
||||
<RichText content={media.caption} enableGutter={false} />
|
||||
<RichText data={media.caption} enableGutter={false} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -106,15 +106,41 @@ export default {
|
||||
},
|
||||
typography: ({ theme }) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
'--tw-prose-body': 'var(--text)',
|
||||
'--tw-prose-headings': 'var(--text)',
|
||||
h1: {
|
||||
fontSize: '3.5rem',
|
||||
fontWeight: 'normal',
|
||||
marginBottom: '0.25em',
|
||||
css: [
|
||||
{
|
||||
'--tw-prose-body': 'var(--text)',
|
||||
'--tw-prose-headings': 'var(--text)',
|
||||
h1: {
|
||||
fontWeight: 'normal',
|
||||
marginBottom: '0.25em',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
base: {
|
||||
css: [
|
||||
{
|
||||
h1: {
|
||||
fontSize: '2.5rem',
|
||||
},
|
||||
h2: {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
md: {
|
||||
css: [
|
||||
{
|
||||
h1: {
|
||||
fontSize: '3.5rem',
|
||||
},
|
||||
h2: {
|
||||
fontSize: '1.5rem',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "7b4ead3c-1d17-460a-b8a8-7b202a4fa16c",
|
||||
"id": "99a9cc07-6ed3-4eeb-bc7d-a20cc21ec135",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as migration_20241204_154138_initial from './20241204_154138_initial'
|
||||
import * as migration_20241205_211431_initial from './20241205_211431_initial'
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20241204_154138_initial.up,
|
||||
down: migration_20241204_154138_initial.down,
|
||||
name: '20241204_154138_initial',
|
||||
up: migration_20241205_211431_initial.up,
|
||||
down: migration_20241205_211431_initial.down,
|
||||
name: '20241205_211431_initial',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "965f2138-755a-4bb6-b83c-ad7827a6a425",
|
||||
"id": "caad9ebf-d475-40cd-930e-059daf04fde8",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as migration_20241204_154113_initial from './20241204_154113_initial'
|
||||
import * as migration_20241205_211405_initial from './20241205_211405_initial'
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20241204_154113_initial.up,
|
||||
down: migration_20241204_154113_initial.down,
|
||||
name: '20241204_154113_initial',
|
||||
up: migration_20241205_211405_initial.up,
|
||||
down: migration_20241205_211405_initial.down,
|
||||
name: '20241205_211405_initial',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
const SITE_URL =
|
||||
process.env.NEXT_PUBLIC_SERVER_URL ||
|
||||
process.env.VERCEL_PROJECT_PRODUCTION_URL ||
|
||||
'https://example.com'
|
||||
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
siteUrl: SITE_URL,
|
||||
generateRobotsTxt: true,
|
||||
exclude: ['/posts-sitemap.xml', '/pages-sitemap.xml', '/*', '/posts/*'],
|
||||
robotsTxtOptions: {
|
||||
policies: [
|
||||
{
|
||||
userAgent: '*',
|
||||
disallow: '/admin/*',
|
||||
},
|
||||
],
|
||||
additionalSitemaps: [`${SITE_URL}/pages-sitemap.xml`, `${SITE_URL}/posts-sitemap.xml`],
|
||||
},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user