Clone starter

This commit is contained in:
2024-03-07 15:31:41 +01:00
commit 1b7ee20d05
87 changed files with 17015 additions and 0 deletions

1
apps/api/.dockerignore Normal file
View File

@ -0,0 +1 @@
**/node_modules

5
apps/api/.eslintrc.cjs Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
root: true,
extends: ["custom"],
rules: {},
};

169
apps/api/.gitignore vendored Normal file
View File

@ -0,0 +1,169 @@
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
.vscode/*.code-snippets
# Ignore code-workspaces
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode
# Local media files
media

39
apps/api/Dockerfile Normal file
View File

@ -0,0 +1,39 @@
FROM node:18-alpine as base
RUN npm i -g pnpm turbo
FROM base AS pruner
WORKDIR /app
COPY . .
RUN turbo prune --scope=@turbopress/api --docker
# remove all empty node_modules folder structure
RUN rm -rf /app/out/full/*/*/node_modules
FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
# Add lockfile and package.json's of isolated subworkspace
COPY .gitignore .gitignore
COPY --from=pruner /app/out/json/ .
RUN pnpm install
# Build the project and its dependencies
COPY --from=pruner /app/out/full/ .
ENV PAYLOAD_CONFIG_PATH=src/payload.config.ts
RUN pnpm build:api
# Run App
FROM base as runner
WORKDIR /app
ENV NODE_ENV=production
ENV PAYLOAD_CONFIG_PATH=dist/payload.config.js
COPY --from=builder /app/apps/api/package.json .
RUN pnpm install --prod
COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/apps/api/build ./build
EXPOSE 3000
CMD ["node", "dist/server.js"]

19
apps/api/README.md Normal file
View File

@ -0,0 +1,19 @@
# api
This project was created using create-payload-app using the blog template.
## How to Use
`yarn dev` will start up your application and reload on any changes.
### Docker
If you have docker and docker-compose installed, you can run `docker-compose up`
To build the docker image, run `docker build -t my-tag -f Dockerfile ../..`
Ensure you are passing all needed environment variables when starting up your container via `--env-file` or setting them with your deployment.
The 3 typical env vars will be `MONGODB_URI`, `PAYLOAD_SECRET`, and `PAYLOAD_CONFIG_PATH`
`docker run --env-file .env -p 3000:3000 my-tag`

5
apps/api/nodemon.json Normal file
View File

@ -0,0 +1,5 @@
{
"watch": ["src"],
"ext": "ts",
"exec": "ts-node src/server.ts"
}

36
apps/api/package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "@turbopress/api",
"description": "Headless CMS based on Payload",
"version": "1.0.0",
"main": "dist/server.js",
"license": "MIT",
"scripts": {
"dev": "nodemon",
"build:payload": "payload build",
"build:server": "tsc",
"build": "pnpm copyfiles && pnpm build:payload && pnpm build:server",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"generate:types": "payload generate:types",
"lint": "eslint \"./src/**/*.{js,ts}\" --fix",
"serve": "PAYLOAD_CONFIG_PATH=dist/payload.config.js node dist/server.js"
},
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest",
"@payloadcms/plugin-seo": "latest",
"@payloadcms/plugin-cloud-storage": "latest",
"@aws-sdk/client-s3": "latest",
"@aws-sdk/lib-storage": "latest"
},
"devDependencies": {
"eslint": "latest",
"@types/express": "^4.17.9",
"copyfiles": "^2.4.1",
"nodemon": "^2.0.6",
"ts-node": "^9.1.1",
"typescript": "^4.8.4",
"webpack-hot-middleware": "^2.25.4",
"eslint-config-custom": "*"
}
}

128
apps/api/src/blocks/Menu.ts Normal file
View File

@ -0,0 +1,128 @@
import payload from "payload";
import { Block } from "payload/types";
import Pages from "../collections/Pages";
import linkField from "../fields/linkField";
export const Menu: Block = {
slug: "menu",
interfaceName: "Menu",
fields: [
{
name: "type",
type: "select",
options: ["default"],
required: true,
defaultValue: "default",
},
{
name: "menus",
type: "array",
fields: [
{
name: "mainMenu",
type: "group",
interfaceName: "MainMenu",
fields: [
{
type: "row",
fields: [
{
name: "type",
type: "radio",
options: [
{
label: "Internal link",
value: "reference",
},
{
label: "Custom URL",
value: "custom",
},
{
label: "None",
value: "none",
},
],
defaultValue: "reference",
admin: {
layout: "horizontal",
width: "50%",
},
},
{
name: "newTab",
label: "Open in new tab",
type: "checkbox",
admin: {
condition: (_, siblingData) => siblingData?.type != "none",
width: "50%",
style: {
alignSelf: "flex-end",
},
},
},
{
name: "reference",
label: "Document to link to",
type: "relationship",
relationTo: [Pages.slug],
required: true,
maxDepth: 0,
admin: {
condition: (_, siblingData) =>
siblingData?.type === "reference",
width: "50%",
},
hooks: {
afterRead: [
async ({ value, siblingData }) => {
if (value && siblingData.type === "reference") {
const id = value.value;
const pages = await payload.find({
collection: "pages",
where: {
id: { equals: id },
},
depth: 0,
});
if (pages.docs[0]?.slug)
siblingData.url = pages.docs[0].slug;
}
},
],
},
},
{
name: "url",
label: "Custom URL",
type: "text",
required: true,
admin: {
condition: (_, siblingData) =>
siblingData?.type === "custom",
width: "50%",
},
},
{
name: "label",
label: "Label",
type: "text",
required: true,
admin: {
width: "50%",
},
},
],
},
{
name: "subMenu",
type: "array",
fields: [linkField()],
},
],
},
],
},
],
};

View File

@ -0,0 +1,17 @@
import { Block } from "payload/types";
export const PageContent: Block = {
slug: "pageContent",
interfaceName: "PageContent",
fields: [
{
name: "description",
type: "textarea",
defaultValue:
"This block will display the content of the page (if any). Please edit the original page change the value.",
admin: {
readOnly: true,
},
},
],
};

View File

@ -0,0 +1,52 @@
import { Block } from "payload/types";
import Categories from "../collections/Categories";
import { PagesField } from "../collections/Pages";
import Tags from "../collections/Tags";
const PageListField = {
numberOfItems: "numberOfItems",
filterByCategories: "filterByCategories",
filterByTags: "filterByTags",
sortBy: "sortBy",
pages: "pages",
};
type PageListField = (typeof PageListField)[keyof typeof PageListField];
export const PageList: Block = {
slug: "pageList",
interfaceName: "PageList",
fields: [
{
name: PageListField.numberOfItems,
type: "number",
defaultValue: 5,
},
{
name: PageListField.filterByCategories,
type: "relationship",
relationTo: [Categories.slug],
maxDepth: 0,
hasMany: true,
},
{
name: PageListField.filterByTags,
type: "relationship",
relationTo: [Tags.slug],
hasMany: true,
maxDepth: 0,
},
{
name: PageListField.sortBy,
type: "select",
options: [
PagesField.title,
PagesField.createdAt,
PagesField.updatedAt,
`-${PagesField.title}`,
`-${PagesField.createdAt}`,
`-${PagesField.updatedAt}`,
],
},
],
};

View File

@ -0,0 +1,15 @@
import { Block } from "payload/types";
import Contents from "../collections/Contents";
export const ReusableContent: Block = {
slug: "reusableContent",
interfaceName: "ReusableContent",
fields: [
{
name: "reference",
type: "relationship",
// maxDepth: 0,
relationTo: [Contents.slug],
},
],
};

View File

@ -0,0 +1,9 @@
import { Block } from "payload/types";
export const SiteTitle: Block = {
slug: "siteTitle",
interfaceName: "SiteTitle",
fields: [
{ name: "siteName", type: "text", required: true, admin: { width: "50%" } },
],
};

View File

@ -0,0 +1,37 @@
import type { CollectionConfig } from "payload/types";
const CategoriesField = {
name: "name",
slug: "slug",
};
type CategoriesField = (typeof CategoriesField)[keyof typeof CategoriesField];
const Categories: CollectionConfig = {
slug: "categories",
admin: {
useAsTitle: CategoriesField.name,
},
access: {
read: () => true,
},
fields: [
{
name: CategoriesField.name,
type: "text",
required: true,
},
{
name: CategoriesField.slug,
type: "text",
required: true,
unique: true,
admin: {
position: "sidebar",
},
},
],
timestamps: false,
};
export default Categories;

View File

@ -0,0 +1,48 @@
import { CollectionConfig } from "payload/types";
import { Menu } from "../blocks/Menu";
import { PageContent } from "../blocks/PageContent";
import { PageList } from "../blocks/PageList";
import { SiteTitle } from "../blocks/SiteTitle";
const ContentsField = {
name: "name",
slug: "slug",
description: "description",
};
type ContentsField = (typeof ContentsField)[keyof typeof ContentsField];
const Contents: CollectionConfig = {
slug: "contents",
access: {
read: () => true,
},
admin: {
useAsTitle: ContentsField.name,
},
fields: [
{
name: ContentsField.name,
type: "text",
required: true,
},
{
name: ContentsField.slug,
type: "text",
unique: true,
admin: {
position: "sidebar",
},
},
{
name: ContentsField.description,
type: "text",
},
{
name: "blocks",
type: "blocks",
blocks: [Menu, PageContent, PageList, SiteTitle],
},
],
};
export default Contents;

View File

@ -0,0 +1,79 @@
import { CollectionConfig } from "payload/types";
import { Menu } from "../blocks/Menu";
import { PageContent } from "../blocks/PageContent";
import { PageList } from "../blocks/PageList";
import { ReusableContent } from "../blocks/ReusableContent";
import { SiteTitle } from "../blocks/SiteTitle";
const LayoutsField = {
name: "name",
slug: "slug",
description: "description",
};
type LayoutsField = (typeof LayoutsField)[keyof typeof LayoutsField];
const blocks = [ReusableContent];
const Layouts: CollectionConfig = {
slug: "layouts",
access: {
read: () => true,
},
admin: {
useAsTitle: LayoutsField.name,
},
fields: [
{
name: LayoutsField.name,
type: "text",
required: true,
},
{
name: LayoutsField.slug,
type: "text",
unique: true,
admin: {
position: "sidebar",
},
},
{
name: LayoutsField.description,
type: "text",
},
{
name: "header",
type: "group",
fields: [
{
name: "blocks",
type: "blocks",
blocks: [...blocks, Menu, SiteTitle],
},
],
},
{
name: "body",
type: "group",
fields: [
{
name: "blocks",
type: "blocks",
blocks: [...blocks, PageContent, PageList],
},
],
},
{
name: "footer",
type: "group",
fields: [
{
name: "blocks",
type: "blocks",
blocks: blocks,
},
],
},
],
};
export default Layouts;

View File

@ -0,0 +1,29 @@
import type { CollectionConfig } from "payload/types";
const Media: CollectionConfig = {
slug: "media",
access: {
read: () => true,
},
upload: {
disableLocalStorage: true,
adminThumbnail: "thumbnail",
imageSizes: [
{
height: 400,
width: 400,
crop: "center",
name: "thumbnail",
},
{
width: 900,
height: 450,
crop: "center",
name: "sixteenByNineMedium",
},
],
},
fields: [],
};
export default Media;

View File

@ -0,0 +1,113 @@
import type { CollectionConfig } from "payload/types";
export const PagesField = {
title: "title",
slug: "slug",
author: "author",
publishedDate: "publishedDate",
categories: "categories",
tags: "tags",
content: "content",
status: "status",
layout: "layout",
createdAt: "createdAt",
updatedAt: "updatedAt",
};
type PagesField = (typeof PagesField)[keyof typeof PagesField];
const PagesFieldStatus = {
Draft: "Draft",
Published: "Published",
};
type PagesFieldStatus =
(typeof PagesFieldStatus)[keyof typeof PagesFieldStatus];
const Pages: CollectionConfig = {
slug: "pages",
admin: {
defaultColumns: [
PagesField.title,
PagesField.slug,
PagesField.author,
PagesField.categories,
PagesField.tags,
PagesField.status,
],
useAsTitle: PagesField.title,
},
access: {
read: () => true,
},
fields: [
{
name: PagesField.title,
type: "text",
required: true,
},
{
name: PagesField.slug,
type: "text",
required: true,
admin: {
position: "sidebar",
},
},
{
name: PagesField.author,
type: "relationship",
relationTo: "users",
admin: {
position: "sidebar",
},
},
{
name: PagesField.publishedDate,
type: "date",
admin: {
position: "sidebar",
},
},
{
name: PagesField.categories,
type: "relationship",
relationTo: "categories",
hasMany: true,
admin: {
position: "sidebar",
},
},
{
name: PagesField.tags,
type: "relationship",
relationTo: "tags",
hasMany: true,
admin: {
position: "sidebar",
},
},
{
name: PagesField.status,
type: "select",
options: Object.entries(PagesFieldStatus).map((e) => {
return { label: e[0], value: e[1] };
}),
defaultValue: PagesFieldStatus.Draft,
admin: {
position: "sidebar",
},
},
{
name: PagesField.layout,
type: "relationship",
relationTo: "layouts",
},
{
name: PagesField.content,
type: "richText",
},
],
};
export default Pages;

View File

@ -0,0 +1,37 @@
import type { CollectionConfig } from "payload/types";
const TagsField = {
name: "name",
slug: "slug",
};
type TagsField = (typeof TagsField)[keyof typeof TagsField];
const Tags: CollectionConfig = {
slug: "tags",
admin: {
useAsTitle: TagsField.name,
},
access: {
read: () => true,
},
fields: [
{
name: TagsField.name,
type: "text",
required: true,
},
{
name: TagsField.slug,
type: "text",
required: true,
unique: true,
admin: {
position: "sidebar",
},
},
],
timestamps: false,
};
export default Tags;

View File

@ -0,0 +1,18 @@
import type { CollectionConfig } from "payload/types";
const Users: CollectionConfig = {
slug: "users",
auth: true,
admin: {
useAsTitle: "email",
},
fields: [
// Email added by default
{
name: "name",
type: "text",
},
],
};
export default Users;

View File

@ -0,0 +1,15 @@
import { SelectField } from "payload/types";
function enableField(fieldOverrides?: Partial<SelectField>): SelectField {
return {
label: "Enable",
name: "enable",
type: "select",
options: ["Yes", "No"],
defaultValue: "Yes",
required: true,
...fieldOverrides,
};
}
export default enableField;

View File

@ -0,0 +1,100 @@
import payload from "payload";
import { GroupField } from "payload/types";
function linkField(fieldOverrides?: Partial<GroupField>): GroupField {
return {
name: "link",
type: "group",
interfaceName: "Link",
fields: [
{
type: "row",
fields: [
{
name: "type",
type: "radio",
options: [
{
label: "Internal link",
value: "reference",
},
{
label: "Custom URL",
value: "custom",
},
],
defaultValue: "reference",
admin: {
layout: "horizontal",
width: "50%",
},
},
{
name: "newTab",
label: "Open in new tab",
type: "checkbox",
admin: {
width: "50%",
style: {
alignSelf: "flex-end",
},
},
},
{
name: "reference",
label: "Document to link to",
type: "relationship",
relationTo: ["pages"],
required: true,
maxDepth: 0,
admin: {
condition: (_, siblingData) => siblingData?.type === "reference",
width: "50%",
},
hooks: {
afterRead: [
async ({ value, siblingData }) => {
if (value && siblingData.type === "reference") {
const id = value.value;
const pages = await payload.find({
collection: "pages",
where: {
id: { equals: id },
},
depth: 0,
});
if (pages.docs[0]?.slug)
if (pages.docs[0]) siblingData.url = pages.docs[0].slug;
}
},
],
},
},
{
name: "url",
label: "Custom URL",
type: "text",
required: true,
admin: {
condition: (_, siblingData) => siblingData?.type === "custom",
width: "50%",
},
},
{
name: "label",
label: "Label",
type: "text",
required: true,
admin: {
width: "50%",
},
},
],
},
],
...fieldOverrides,
};
}
export default linkField;

View File

@ -0,0 +1,59 @@
import { cloudStorage } from "@payloadcms/plugin-cloud-storage";
import { s3Adapter } from "@payloadcms/plugin-cloud-storage/s3";
import seo from "@payloadcms/plugin-seo";
import { GenerateTitle } from "@payloadcms/plugin-seo/dist/types";
import path from "path";
import { buildConfig } from "payload/config";
import Categories from "./collections/Categories";
import Contents from "./collections/Contents";
import Layouts from "./collections/Layouts";
import Media from "./collections/Media";
import Pages from "./collections/Pages";
import Tags from "./collections/Tags";
import Users from "./collections/Users";
const generateTitle: GenerateTitle = ({ slug, doc }) => {
let title = "TurboPress";
if (slug == "pages") {
const page = doc as any;
return (title = `TurboPress - ${page?.title?.value}`);
}
return title;
};
const adapter = s3Adapter({
config: {
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
region: process.env.S3_REGION,
endpoint: process.env.S3_ENDPOINT,
},
bucket: process.env.S3_BUCKET,
});
export default buildConfig({
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL ?? "http://localhost:3000",
admin: {
user: Users.slug,
},
collections: [Categories, Contents, Layouts, Media, Pages, Tags, Users],
typescript: {
outputFile: path.join(__dirname, "../types", "payload.ts"),
},
plugins: [
seo({
collections: ["pages"],
uploadsCollection: "media",
generateTitle: generateTitle,
}),
cloudStorage({
collections: {
media: {
adapter: adapter,
},
},
}),
],
cors: "*",
});

30
apps/api/src/server.ts Normal file
View File

@ -0,0 +1,30 @@
import express from "express";
import payload from "payload";
const app = express();
// Redirect root to Admin panel
app.get("/", (_, res) => {
res.redirect("/admin");
});
const start = async () => {
// Initialize Payload
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: async () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
},
mongoOptions: {
dbName: process.env.DB_NAME,
},
});
// Add your own express routes here
app.listen(process.env.PAYLOAD_PORT ?? 3000);
};
start();

22
apps/api/tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react",
"paths": {
"payload/generated-types": ["./types/payload.ts"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist", "build"],
"ts-node": {
"transpileOnly": true,
"swc": true
}
}

2
apps/api/types/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from "./payload";
export * from "./rich-text-export";

200
apps/api/types/payload.ts Normal file
View File

@ -0,0 +1,200 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
export interface Config {
collections: {
categories: Category;
contents: Content;
layouts: Layout;
media: Media;
pages: Page;
tags: Tag;
users: User;
};
globals: {};
}
export interface Category {
id: string;
name: string;
slug: string;
}
export interface Content {
id: string;
name: string;
slug?: string;
description?: string;
blocks?: (Menu | PageContent | PageList | SiteTitle)[];
updatedAt: string;
createdAt: string;
}
export interface Menu {
type: 'default';
menus?: {
mainMenu: MainMenu;
id?: string;
}[];
id?: string;
blockName?: string;
blockType: 'menu';
}
export interface MainMenu {
type?: 'reference' | 'custom' | 'none';
newTab?: boolean;
reference: {
value: string | Page;
relationTo: 'pages';
};
url: string;
label: string;
subMenu?: {
link: Link;
id?: string;
}[];
}
export interface Page {
id: string;
title: string;
slug: string;
author?: string | User;
publishedDate?: string;
categories?: string[] | Category[];
tags?: string[] | Tag[];
status?: 'Draft' | 'Published';
layout?: string | Layout;
content?: {
[k: string]: unknown;
}[];
meta?: {
title?: string;
description?: string;
image?: string | Media;
};
updatedAt: string;
createdAt: string;
}
export interface User {
id: string;
name?: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string;
resetPasswordExpiration?: string;
salt?: string;
hash?: string;
loginAttempts?: number;
lockUntil?: string;
password?: string;
}
export interface Tag {
id: string;
name: string;
slug: string;
}
export interface Layout {
id: string;
name: string;
slug?: string;
description?: string;
header?: {
blocks?: (ReusableContent | Menu | SiteTitle)[];
};
body?: {
blocks?: (ReusableContent | PageContent | PageList)[];
};
footer?: {
blocks?: ReusableContent[];
};
updatedAt: string;
createdAt: string;
}
export interface ReusableContent {
reference?: {
value: string | Content;
relationTo: 'contents';
};
id?: string;
blockName?: string;
blockType: 'reusableContent';
}
export interface SiteTitle {
siteName: string;
id?: string;
blockName?: string;
blockType: 'siteTitle';
}
export interface PageContent {
description?: string;
id?: string;
blockName?: string;
blockType: 'pageContent';
}
export interface PageList {
numberOfItems?: number;
filterByCategories?:
| {
value: string;
relationTo: 'categories';
}[]
| {
value: Category;
relationTo: 'categories';
}[];
filterByTags?:
| {
value: string;
relationTo: 'tags';
}[]
| {
value: Tag;
relationTo: 'tags';
}[];
sortBy?: 'title' | 'createdAt' | 'updatedAt' | '-title' | '-createdAt' | '-updatedAt';
id?: string;
blockName?: string;
blockType: 'pageList';
}
export interface Media {
id: string;
updatedAt: string;
createdAt: string;
url?: string;
filename?: string;
mimeType?: string;
filesize?: number;
width?: number;
height?: number;
sizes?: {
thumbnail?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
sixteenByNineMedium?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
};
}
export interface Link {
type?: 'reference' | 'custom';
newTab?: boolean;
reference: {
value: string | Page;
relationTo: 'pages';
};
url: string;
label: string;
}

View File

@ -0,0 +1,22 @@
import type {
RichTextElement,
RichTextLeaf,
} from "payload/dist/fields/config/types";
import type { RichTextCustomElement, RichTextCustomLeaf } from "payload/types";
type DefaultRichTextLeaf = Exclude<RichTextLeaf, RichTextCustomLeaf>;
export type FormattedText = {
[key in DefaultRichTextLeaf]?: boolean;
} & {
text: string;
};
type DefaultRichTextElement =
| Exclude<RichTextElement, RichTextCustomElement>
| "li"
| "quote";
export type FormattedElement = {
type: DefaultRichTextElement;
url?: string;
children: FormattedText[];
};