Compare commits
6 Commits
eslint/3.9
...
feat/sched
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1af94901a3 | ||
|
|
d6f44f9c94 | ||
|
|
a7109ed048 | ||
|
|
dec87e971a | ||
|
|
a501e604d6 | ||
|
|
6bba7bec4e |
@@ -53,7 +53,6 @@ export async function GET(
|
||||
headers: req.headers,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log({ token, payloadSecret: payload.secret })
|
||||
payload.logger.error({ err: error }, 'Error verifying token for live preview')
|
||||
return new Response('You are not allowed to preview this page', { status: 403 })
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ export const create: Create = async function create(
|
||||
})
|
||||
|
||||
try {
|
||||
const { insertedId } = await Model.collection.insertOne(data, { session })
|
||||
data._id = insertedId
|
||||
const { insertedId: insertedID } = await Model.collection.insertOne(data, { session })
|
||||
data._id = insertedID
|
||||
|
||||
transform({
|
||||
adapter: this,
|
||||
|
||||
@@ -25,8 +25,8 @@ export const createGlobal: CreateGlobal = async function createGlobal(
|
||||
|
||||
const session = await getSession(this, req)
|
||||
|
||||
const { insertedId } = await Model.collection.insertOne(data, { session })
|
||||
;(data as any)._id = insertedId
|
||||
const { insertedId: insertedID } = await Model.collection.insertOne(data, { session })
|
||||
;(data as any)._id = insertedID
|
||||
|
||||
transform({
|
||||
adapter: this,
|
||||
|
||||
@@ -46,15 +46,15 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
||||
operation: 'create',
|
||||
})
|
||||
|
||||
const { insertedId } = await VersionModel.collection.insertOne(data, { session })
|
||||
;(data as any)._id = insertedId
|
||||
const { insertedId: insertedID } = await VersionModel.collection.insertOne(data, { session })
|
||||
;(data as any)._id = insertedID
|
||||
|
||||
await VersionModel.collection.updateMany(
|
||||
{
|
||||
$and: [
|
||||
{
|
||||
_id: {
|
||||
$ne: insertedId,
|
||||
$ne: insertedID,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -47,8 +47,8 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
operation: 'create',
|
||||
})
|
||||
|
||||
const { insertedId } = await VersionModel.collection.insertOne(data, { session })
|
||||
data._id = insertedId
|
||||
const { insertedId: insertedID } = await VersionModel.collection.insertOne(data, { session })
|
||||
data._id = insertedID
|
||||
|
||||
const parentQuery = {
|
||||
$or: [
|
||||
@@ -72,7 +72,7 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
$and: [
|
||||
{
|
||||
_id: {
|
||||
$ne: insertedId,
|
||||
$ne: insertedID,
|
||||
},
|
||||
},
|
||||
parentQuery,
|
||||
|
||||
@@ -106,12 +106,12 @@ async function migrateCollectionDocs({
|
||||
return
|
||||
}
|
||||
|
||||
const remainingDocIds = remainingDocs.map((doc) => doc._versionID)
|
||||
const remainingDocIDs = remainingDocs.map((doc) => doc._versionID)
|
||||
|
||||
await VersionsModel.updateMany(
|
||||
{
|
||||
_id: {
|
||||
$in: remainingDocIds,
|
||||
$in: remainingDocIDs,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -214,7 +214,7 @@ const PreviewView: React.FC<Props> = ({
|
||||
setDocumentIsLocked(true)
|
||||
|
||||
if (isLockingEnabled) {
|
||||
const previousOwnerId =
|
||||
const previousOwnerID =
|
||||
typeof documentLockStateRef.current?.user === 'object'
|
||||
? documentLockStateRef.current?.user?.id
|
||||
: documentLockStateRef.current?.user
|
||||
@@ -225,8 +225,8 @@ const PreviewView: React.FC<Props> = ({
|
||||
? lockedState.user
|
||||
: lockedState.user.id
|
||||
|
||||
if (!documentLockStateRef.current || lockedUserID !== previousOwnerId) {
|
||||
if (previousOwnerId === user.id && lockedUserID !== user.id) {
|
||||
if (!documentLockStateRef.current || lockedUserID !== previousOwnerID) {
|
||||
if (previousOwnerID === user.id && lockedUserID !== user.id) {
|
||||
setShowTakeOverModal(true)
|
||||
documentLockStateRef.current.hasShownLockedModal = true
|
||||
}
|
||||
@@ -270,7 +270,7 @@ const PreviewView: React.FC<Props> = ({
|
||||
|
||||
const currentPath = window.location.pathname
|
||||
|
||||
const documentId = id || globalSlug
|
||||
const documentID = id || globalSlug
|
||||
|
||||
// Routes where we do NOT want to unlock the document
|
||||
const stayWithinDocumentPaths = ['preview', 'api', 'versions']
|
||||
@@ -280,7 +280,7 @@ const PreviewView: React.FC<Props> = ({
|
||||
)
|
||||
|
||||
// Unlock the document only if we're actually navigating away from the document
|
||||
if (documentId && documentIsLocked && !isStayingWithinDocument) {
|
||||
if (documentID && documentIsLocked && !isStayingWithinDocument) {
|
||||
// Check if this user is still the current editor
|
||||
if (
|
||||
typeof documentLockStateRef.current?.user === 'object'
|
||||
|
||||
@@ -241,7 +241,7 @@ export const getViewFromConfig = ({
|
||||
// --> /collections/:collectionSlug/:id/api
|
||||
// --> /collections/:collectionSlug/:id/preview
|
||||
// --> /collections/:collectionSlug/:id/versions
|
||||
// --> /collections/:collectionSlug/:id/versions/:versionId
|
||||
// --> /collections/:collectionSlug/:id/versions/:versionID
|
||||
|
||||
ViewToRender = {
|
||||
Component: DocumentView,
|
||||
@@ -311,7 +311,7 @@ export const getViewFromConfig = ({
|
||||
// Custom Views
|
||||
// --> /globals/:globalSlug/versions
|
||||
// --> /globals/:globalSlug/preview
|
||||
// --> /globals/:globalSlug/versions/:versionId
|
||||
// --> /globals/:globalSlug/versions/:versionID
|
||||
// --> /globals/:globalSlug/api
|
||||
|
||||
ViewToRender = {
|
||||
|
||||
@@ -179,6 +179,12 @@ export type TypedUploadCollection = NonNever<{
|
||||
: never
|
||||
}>
|
||||
|
||||
export type TypedVersionedCollection = NonNever<{
|
||||
[K in keyof TypedCollection]: '_status' extends keyof TypedCollection[K]
|
||||
? TypedCollection[K]
|
||||
: never
|
||||
}>
|
||||
|
||||
export type TypedCollectionSelect = ResolveCollectionSelectType<GeneratedTypes>
|
||||
|
||||
export type TypedCollectionJoins = ResolveCollectionJoinsType<GeneratedTypes>
|
||||
@@ -195,6 +201,8 @@ export type CollectionSlug = StringKeyOf<TypedCollection>
|
||||
|
||||
export type UploadCollectionSlug = StringKeyOf<TypedUploadCollection>
|
||||
|
||||
export type VersionedCollectionSlug = StringKeyOf<TypedVersionedCollection>
|
||||
|
||||
type ResolveDbType<T> = 'db' extends keyof T
|
||||
? T['db']
|
||||
: // @ts-expect-error
|
||||
|
||||
247
packages/plugin-scheduled-publish/.gitignore
vendored
Normal file
247
packages/plugin-scheduled-publish/.gitignore
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
dev/yarn.lock
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
### macOS ###
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# 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
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# Yarn Berry
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
|
||||
### SublimeText ###
|
||||
# cache files for sublime text
|
||||
*.tmlanguage.cache
|
||||
*.tmPreferences.cache
|
||||
*.stTheme.cache
|
||||
|
||||
# workspace files are user-specific
|
||||
*.sublime-workspace
|
||||
|
||||
# project files should be checked into the repository, unless a significant
|
||||
# proportion of contributors will probably not be using SublimeText
|
||||
# *.sublime-project
|
||||
|
||||
# sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
# Package control specific files
|
||||
Package Control.last-run
|
||||
Package Control.ca-list
|
||||
Package Control.ca-bundle
|
||||
Package Control.system-ca-bundle
|
||||
Package Control.cache/
|
||||
Package Control.ca-certs/
|
||||
Package Control.merged-ca-bundle
|
||||
Package Control.user-ca-bundle
|
||||
oscrypto-ca-bundle.crt
|
||||
bh_unicode_properties.cache
|
||||
|
||||
# Sublime-github package stores a github token in this file
|
||||
# https://packagecontrol.io/packages/sublime-github
|
||||
GitHub.sublime-settings
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history
|
||||
|
||||
### WebStorm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/*
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Ruby plugin and RubyMine
|
||||
/.rakeTasks
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
### WebStorm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
# Ignore all uploads
|
||||
demo/upload
|
||||
demo/media
|
||||
demo/files
|
||||
|
||||
# Ignore build folder
|
||||
build
|
||||
|
||||
# Ignore built components
|
||||
components/index.js
|
||||
components/styles.css
|
||||
|
||||
# Ignore generated
|
||||
demo/generated-types.ts
|
||||
demo/generated-schema.graphql
|
||||
|
||||
# Ignore dist, no need for git
|
||||
dist
|
||||
|
||||
# Ignore emulator volumes
|
||||
src/adapters/s3/emulator/.localstack/
|
||||
12
packages/plugin-scheduled-publish/.prettierignore
Normal file
12
packages/plugin-scheduled-publish/.prettierignore
Normal file
@@ -0,0 +1,12 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
**/docs/**
|
||||
tsconfig.json
|
||||
25
packages/plugin-scheduled-publish/.swcrc-build
Normal file
25
packages/plugin-scheduled-publish/.swcrc-build
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"exclude": ["/**/mocks", "/**/*.spec.ts"],
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
},
|
||||
"transform": {
|
||||
"react": {
|
||||
"runtime": "automatic",
|
||||
"pragmaFrag": "React.Fragment",
|
||||
"throwIfNamespace": true,
|
||||
"development": false,
|
||||
"useBuiltins": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
22
packages/plugin-scheduled-publish/LICENSE.md
Normal file
22
packages/plugin-scheduled-publish/LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2024 Payload CMS, Inc. <info@payloadcms.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
7
packages/plugin-scheduled-publish/README.md
Normal file
7
packages/plugin-scheduled-publish/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Payload Sentry Plugin
|
||||
|
||||
This plugin allows you to integrate [Sentry](https://sentry.io/) seamlessly with your [Payload](https://github.com/payloadcms/payload) application.
|
||||
|
||||
- [Source code](https://github.com/payloadcms/payload/tree/main/packages/plugin-sentry)
|
||||
- [Documentation](https://payloadcms.com/docs/plugins/sentry)
|
||||
- [Documentation source](https://github.com/payloadcms/payload/tree/main/docs/plugins/sentry.mdx)
|
||||
18
packages/plugin-scheduled-publish/eslint.config.js
Normal file
18
packages/plugin-scheduled-publish/eslint.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js'
|
||||
|
||||
/** @typedef {import('eslint').Linter.Config} Config */
|
||||
|
||||
/** @type {Config[]} */
|
||||
export const index = [
|
||||
...rootEslintConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
...rootParserOptions,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export default index
|
||||
32
packages/plugin-scheduled-publish/jest.config.js
Normal file
32
packages/plugin-scheduled-publish/jest.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import baseConfig from '../../jest.config.js'
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
const customJestConfig = {
|
||||
...baseConfig,
|
||||
setupFilesAfterEnv: null,
|
||||
testMatch: ['**/src/**/?(*.)+(spec|test|it-test).[tj]s?(x)'],
|
||||
testTimeout: 20000,
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': [
|
||||
'@swc/jest',
|
||||
{
|
||||
$schema: 'https://json.schemastore.org/swcrc',
|
||||
sourceMaps: true,
|
||||
exclude: ['/**/mocks'],
|
||||
jsc: {
|
||||
target: 'esnext',
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
dts: true,
|
||||
},
|
||||
},
|
||||
module: {
|
||||
type: 'es6',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default customJestConfig
|
||||
78
packages/plugin-scheduled-publish/package.json
Normal file
78
packages/plugin-scheduled-publish/package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-scheduled-publish",
|
||||
"version": "3.9.0",
|
||||
"description": "Scheduled publish plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
"cms",
|
||||
"plugin",
|
||||
"typescript"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/plugin-scheduled-publish"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Payload",
|
||||
"email": "info@payloadcms.com",
|
||||
"url": "https://payloadcms.com"
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
},
|
||||
"./rsc": {
|
||||
"import": "./src/exports/rsc.ts",
|
||||
"types": "./src/exports/rsc.ts",
|
||||
"default": "./src/exports/rsc.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc-build --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/react": "19.0.1",
|
||||
"@types/react-dom": "19.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./rsc": {
|
||||
"import": "./dist/exports/rsc.js",
|
||||
"types": "./dist/exports/rsc.d.ts",
|
||||
"default": "./dist/exports/rsc.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"homepage:": "https://payloadcms.com"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { UIFieldServerComponent } from 'payload'
|
||||
|
||||
export const ScheduledPublishField: UIFieldServerComponent = async ({ data, payload }) => {
|
||||
payload.find({ collection: 'payload-jobs' })
|
||||
return <div>{data.title}</div>
|
||||
}
|
||||
1
packages/plugin-scheduled-publish/src/exports/rsc.ts
Normal file
1
packages/plugin-scheduled-publish/src/exports/rsc.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ScheduledPublishField } from '../components/ScheduledPublishField.js'
|
||||
124
packages/plugin-scheduled-publish/src/index.ts
Normal file
124
packages/plugin-scheduled-publish/src/index.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { Config, Field, TaskHandlerArgs, VersionedCollectionSlug } from 'payload'
|
||||
|
||||
type Options = {
|
||||
collections: Partial<Record<VersionedCollectionSlug, true>>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
type PublishDocumentInput = {
|
||||
collectionSlug: VersionedCollectionSlug
|
||||
documentID: number | string
|
||||
}
|
||||
|
||||
export const pluginScheduledPublish = (options: Options) => {
|
||||
const inputSchema: Field[] = [
|
||||
{
|
||||
name: 'collectionSlug',
|
||||
type: 'select',
|
||||
options: Object.keys(options.collections),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'documentID',
|
||||
type: 'json',
|
||||
jsonSchema: {
|
||||
fileMatch: ['a://b/foo.json'],
|
||||
schema: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
uri: 'a://b/foo.json',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
return (config: Config) => {
|
||||
if (options.disabled) {
|
||||
return config
|
||||
}
|
||||
|
||||
if (config.collections) {
|
||||
for (const collection of config.collections) {
|
||||
if (!options.collections[collection.slug]) {
|
||||
continue
|
||||
}
|
||||
|
||||
collection.fields.push({
|
||||
name: 'scheduled_publish_ui',
|
||||
type: 'ui',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/plugin-scheduled-publish/rsc#ScheduledPublishField',
|
||||
},
|
||||
position: 'sidebar',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.jobs) {
|
||||
config.jobs = {
|
||||
tasks: [],
|
||||
}
|
||||
}
|
||||
|
||||
config.jobs.tasks.push({
|
||||
slug: 'PublishDocument',
|
||||
handler: async ({
|
||||
input,
|
||||
req,
|
||||
}: TaskHandlerArgs<{
|
||||
input: PublishDocumentInput
|
||||
output: any
|
||||
}>) => {
|
||||
const { collectionSlug, documentID } = input
|
||||
|
||||
await req.payload.update({
|
||||
id: documentID,
|
||||
collection: collectionSlug,
|
||||
data: { _status: 'published' },
|
||||
})
|
||||
|
||||
return {
|
||||
output: null,
|
||||
state: 'succeeded',
|
||||
}
|
||||
},
|
||||
inputSchema,
|
||||
})
|
||||
|
||||
config.jobs.tasks.push({
|
||||
slug: 'UnpublishDocument',
|
||||
handler: async ({
|
||||
input,
|
||||
req,
|
||||
}: TaskHandlerArgs<{
|
||||
input: PublishDocumentInput
|
||||
output: any
|
||||
}>) => {
|
||||
const { collectionSlug, documentID } = input
|
||||
|
||||
await req.payload.update({
|
||||
id: documentID,
|
||||
collection: collectionSlug,
|
||||
data: { _status: 'draft' },
|
||||
})
|
||||
|
||||
return {
|
||||
output: null,
|
||||
state: 'succeeded',
|
||||
}
|
||||
},
|
||||
inputSchema,
|
||||
})
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
||||
7
packages/plugin-scheduled-publish/tsconfig.json
Normal file
7
packages/plugin-scheduled-publish/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
},
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
@@ -124,7 +124,7 @@ function debounce(func, wait, options) {
|
||||
maxing = false,
|
||||
maxWait,
|
||||
result,
|
||||
timerId,
|
||||
timerID,
|
||||
trailing = true
|
||||
|
||||
if (typeof func != 'function') {
|
||||
@@ -152,7 +152,7 @@ function debounce(func, wait, options) {
|
||||
// Reset any `maxWait` timer.
|
||||
lastInvokeTime = time
|
||||
// Start the timer for the trailing edge.
|
||||
timerId = setTimeout(timerExpired, wait)
|
||||
timerID = setTimeout(timerExpired, wait)
|
||||
// Invoke the leading edge.
|
||||
return leading ? invokeFunc(time) : result
|
||||
}
|
||||
@@ -186,11 +186,11 @@ function debounce(func, wait, options) {
|
||||
return trailingEdge(time)
|
||||
}
|
||||
// Restart the timer.
|
||||
timerId = setTimeout(timerExpired, remainingWait(time))
|
||||
timerID = setTimeout(timerExpired, remainingWait(time))
|
||||
}
|
||||
|
||||
function trailingEdge(time) {
|
||||
timerId = undefined
|
||||
timerID = undefined
|
||||
|
||||
// Only invoke if we have `lastArgs` which means `func` has been
|
||||
// debounced at least once.
|
||||
@@ -202,15 +202,15 @@ function debounce(func, wait, options) {
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
if (timerId !== undefined) {
|
||||
clearTimeout(timerId)
|
||||
if (timerID !== undefined) {
|
||||
clearTimeout(timerID)
|
||||
}
|
||||
lastInvokeTime = 0
|
||||
lastArgs = lastCallTime = lastThis = timerId = undefined
|
||||
lastArgs = lastCallTime = lastThis = timerID = undefined
|
||||
}
|
||||
|
||||
function flush() {
|
||||
return timerId === undefined ? result : trailingEdge(Date.now())
|
||||
return timerID === undefined ? result : trailingEdge(Date.now())
|
||||
}
|
||||
|
||||
function debounced() {
|
||||
@@ -224,18 +224,18 @@ function debounce(func, wait, options) {
|
||||
lastCallTime = time
|
||||
|
||||
if (isInvoking) {
|
||||
if (timerId === undefined) {
|
||||
if (timerID === undefined) {
|
||||
return leadingEdge(lastCallTime)
|
||||
}
|
||||
if (maxing) {
|
||||
// Handle invocations in a tight loop.
|
||||
clearTimeout(timerId)
|
||||
timerId = setTimeout(timerExpired, wait)
|
||||
clearTimeout(timerID)
|
||||
timerID = setTimeout(timerExpired, wait)
|
||||
return invokeFunc(lastCallTime)
|
||||
}
|
||||
}
|
||||
if (timerId === undefined) {
|
||||
timerId = setTimeout(timerExpired, wait)
|
||||
if (timerID === undefined) {
|
||||
timerID = setTimeout(timerExpired, wait)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ export { StrikethroughLeaf } from '../../field/leaves/strikethrough/Strikethroug
|
||||
export { UnderlineLeafButton } from '../../field/leaves/underline/LeafButton.js'
|
||||
export { UnderlineLeaf } from '../../field/leaves/underline/Underline/index.js'
|
||||
|
||||
export { useElementButton } from '../../field/providers/ElementButtonProvider.js'
|
||||
export { useElement } from '../../field/providers/ElementProvider.js'
|
||||
export { useLeafButton } from '../../field/providers/LeafButtonProvider.js'
|
||||
export { useLeaf } from '../../field/providers/LeafProvider.js'
|
||||
export { useSlatePlugin } from '../../utilities/useSlatePlugin.js'
|
||||
|
||||
@@ -45,11 +45,11 @@ export const Tooltip: React.FC<Props> = (props) => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let timerId: NodeJS.Timeout
|
||||
let timerID: NodeJS.Timeout
|
||||
|
||||
// do not use the delay on transition-out
|
||||
if (delay && showFromProps) {
|
||||
timerId = setTimeout(() => {
|
||||
timerID = setTimeout(() => {
|
||||
setShow(showFromProps)
|
||||
}, delay)
|
||||
} else {
|
||||
@@ -57,8 +57,8 @@ export const Tooltip: React.FC<Props> = (props) => {
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerId) {
|
||||
clearTimeout(timerId)
|
||||
if (timerID) {
|
||||
clearTimeout(timerID)
|
||||
}
|
||||
}
|
||||
}, [showFromProps, delay])
|
||||
|
||||
@@ -124,6 +124,7 @@ export { EditUpload } from '../../elements/EditUpload/index.js'
|
||||
export { FileDetails } from '../../elements/FileDetails/index.js'
|
||||
export { PreviewSizes } from '../../elements/PreviewSizes/index.js'
|
||||
export { PreviewButton } from '../../elements/PreviewButton/index.js'
|
||||
export { RelationshipTable } from '../../elements/RelationshipTable/index.js'
|
||||
|
||||
export { BlocksDrawer } from '../../fields/Blocks/BlocksDrawer/index.js'
|
||||
export { SectionTitle } from '../../fields/Blocks/SectionTitle/index.js'
|
||||
|
||||
@@ -447,24 +447,24 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
})
|
||||
|
||||
const currentValue = valueRef.current
|
||||
const docId = args.doc.id
|
||||
const docID = args.doc.id
|
||||
|
||||
if (hasMany) {
|
||||
const unchanged = (currentValue as Option[]).some((option) =>
|
||||
typeof option === 'string' ? option === docId : option.value === docId,
|
||||
typeof option === 'string' ? option === docID : option.value === docID,
|
||||
)
|
||||
|
||||
const valuesToSet = (currentValue as Option[]).map((option) =>
|
||||
option.value === docId
|
||||
? { relationTo: args.collectionConfig.slug, value: docId }
|
||||
option.value === docID
|
||||
? { relationTo: args.collectionConfig.slug, value: docID }
|
||||
: option,
|
||||
)
|
||||
|
||||
setValue(valuesToSet, unchanged)
|
||||
} else {
|
||||
const unchanged = currentValue === docId
|
||||
const unchanged = currentValue === docID
|
||||
|
||||
setValue({ relationTo: args.collectionConfig.slug, value: docId }, unchanged)
|
||||
setValue({ relationTo: args.collectionConfig.slug, value: docID }, unchanged)
|
||||
}
|
||||
},
|
||||
[i18n, config, hasMany, setValue],
|
||||
|
||||
@@ -133,21 +133,21 @@ const DocumentInfo: React.FC<
|
||||
}
|
||||
|
||||
const unlockDocument = useCallback(
|
||||
async (docId: number | string, slug: string) => {
|
||||
async (docID: number | string, slug: string) => {
|
||||
try {
|
||||
const isGlobal = slug === globalSlug
|
||||
|
||||
const query = isGlobal
|
||||
? `where[globalSlug][equals]=${slug}`
|
||||
: `where[document.value][equals]=${docId}&where[document.relationTo][equals]=${slug}`
|
||||
: `where[document.value][equals]=${docID}&where[document.relationTo][equals]=${slug}`
|
||||
|
||||
const request = await requests.get(`${serverURL}${api}/payload-locked-documents?${query}`)
|
||||
|
||||
const { docs } = await request.json()
|
||||
|
||||
if (docs.length > 0) {
|
||||
const lockId = docs[0].id
|
||||
await requests.delete(`${serverURL}${api}/payload-locked-documents/${lockId}`, {
|
||||
const lockID = docs[0].id
|
||||
await requests.delete(`${serverURL}${api}/payload-locked-documents/${lockID}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
@@ -163,13 +163,13 @@ const DocumentInfo: React.FC<
|
||||
)
|
||||
|
||||
const updateDocumentEditor = useCallback(
|
||||
async (docId: number | string, slug: string, user: ClientUser | number | string) => {
|
||||
async (docID: number | string, slug: string, user: ClientUser | number | string) => {
|
||||
try {
|
||||
const isGlobal = slug === globalSlug
|
||||
|
||||
const query = isGlobal
|
||||
? `where[globalSlug][equals]=${slug}`
|
||||
: `where[document.value][equals]=${docId}&where[document.relationTo][equals]=${slug}`
|
||||
: `where[document.value][equals]=${docID}&where[document.relationTo][equals]=${slug}`
|
||||
|
||||
// Check if the document is already locked
|
||||
const request = await requests.get(`${serverURL}${api}/payload-locked-documents?${query}`)
|
||||
@@ -177,7 +177,7 @@ const DocumentInfo: React.FC<
|
||||
const { docs } = await request.json()
|
||||
|
||||
if (docs.length > 0) {
|
||||
const lockId = docs[0].id
|
||||
const lockID = docs[0].id
|
||||
|
||||
const userData =
|
||||
typeof user === 'object'
|
||||
@@ -185,7 +185,7 @@ const DocumentInfo: React.FC<
|
||||
: { relationTo: 'users', value: user }
|
||||
|
||||
// Send a patch request to update the _lastEdited info
|
||||
await requests.patch(`${serverURL}${api}/payload-locked-documents/${lockId}`, {
|
||||
await requests.patch(`${serverURL}${api}/payload-locked-documents/${lockID}`, {
|
||||
body: JSON.stringify({
|
||||
user: userData,
|
||||
}),
|
||||
|
||||
@@ -70,9 +70,9 @@ export type DocumentInfoContext = {
|
||||
setMostRecentVersionIsAutosaved: React.Dispatch<React.SetStateAction<boolean>>
|
||||
setUnpublishedVersionCount: React.Dispatch<React.SetStateAction<number>>
|
||||
title: string
|
||||
unlockDocument: (docId: number | string, slug: string) => Promise<void>
|
||||
unlockDocument: (docID: number | string, slug: string) => Promise<void>
|
||||
unpublishedVersionCount: number
|
||||
updateDocumentEditor: (docId: number | string, slug: string, user: ClientUser) => Promise<void>
|
||||
updateDocumentEditor: (docID: number | string, slug: string, user: ClientUser) => Promise<void>
|
||||
updateSavedDocumentData: (data: Data) => void
|
||||
versionCount: number
|
||||
} & DocumentInfoProps
|
||||
|
||||
@@ -74,12 +74,12 @@ export const handleFormStateLocking = async ({
|
||||
user: lockedDocument.docs[0]?.user?.value,
|
||||
}
|
||||
|
||||
const lockOwnerId =
|
||||
const lockOwnerID =
|
||||
typeof lockedDocument.docs[0]?.user?.value === 'object'
|
||||
? lockedDocument.docs[0]?.user?.value?.id
|
||||
: lockedDocument.docs[0]?.user?.value
|
||||
// Should only update doc if the incoming / current user is also the owner of the locked doc
|
||||
if (updateLastEdited && req.user && lockOwnerId === req.user.id) {
|
||||
if (updateLastEdited && req.user && lockOwnerID === req.user.id) {
|
||||
await req.payload.db.updateOne({
|
||||
id: lockedDocument.docs[0].id,
|
||||
collection: 'payload-locked-documents',
|
||||
|
||||
@@ -7,7 +7,7 @@ export const handleTakeOver = (
|
||||
user: ClientUser | number | string,
|
||||
isWithinDoc: boolean,
|
||||
updateDocumentEditor: (
|
||||
docId: number | string,
|
||||
docID: number | string,
|
||||
slug: string,
|
||||
user: ClientUser | number | string,
|
||||
) => Promise<void>,
|
||||
|
||||
@@ -207,7 +207,7 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
const handleDocumentLocking = useCallback(
|
||||
(lockedState: LockedState) => {
|
||||
setDocumentIsLocked(true)
|
||||
const previousOwnerId =
|
||||
const previousOwnerID =
|
||||
typeof documentLockStateRef.current?.user === 'object'
|
||||
? documentLockStateRef.current?.user?.id
|
||||
: documentLockStateRef.current?.user
|
||||
@@ -218,8 +218,8 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
? lockedState.user
|
||||
: lockedState.user.id
|
||||
|
||||
if (!documentLockStateRef.current || lockedUserID !== previousOwnerId) {
|
||||
if (previousOwnerId === user.id && lockedUserID !== user.id) {
|
||||
if (!documentLockStateRef.current || lockedUserID !== previousOwnerID) {
|
||||
if (previousOwnerID === user.id && lockedUserID !== user.id) {
|
||||
setShowTakeOverModal(true)
|
||||
documentLockStateRef.current.hasShownLockedModal = true
|
||||
}
|
||||
|
||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -1055,6 +1055,22 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../payload
|
||||
|
||||
packages/plugin-scheduled-publish:
|
||||
dependencies:
|
||||
payload:
|
||||
specifier: workspace:*
|
||||
version: link:../payload
|
||||
devDependencies:
|
||||
'@payloadcms/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-config
|
||||
'@types/react':
|
||||
specifier: 19.0.1
|
||||
version: 19.0.1
|
||||
'@types/react-dom':
|
||||
specifier: 19.0.1
|
||||
version: 19.0.1
|
||||
|
||||
packages/plugin-search:
|
||||
dependencies:
|
||||
'@payloadcms/next':
|
||||
@@ -1667,6 +1683,9 @@ importers:
|
||||
'@payloadcms/plugin-redirects':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/plugin-redirects
|
||||
'@payloadcms/plugin-scheduled-publish':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/plugin-scheduled-publish
|
||||
'@payloadcms/plugin-search':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/plugin-search
|
||||
|
||||
@@ -51,7 +51,6 @@ export async function GET(
|
||||
headers: req.headers,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log({ token, payloadSecret: payload.secret })
|
||||
payload.logger.error({ err: error }, 'Error verifying token for live preview')
|
||||
return new Response('You are not allowed to preview this page', { status: 403 })
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ export async function GET(
|
||||
headers: req.headers,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log({ token, payloadSecret: payload.secret })
|
||||
payload.logger.error({ err: error }, 'Error verifying token for live preview')
|
||||
return new Response('You are not allowed to preview this page', { status: 403 })
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const PrePopulateFieldUI: React.FC<{
|
||||
)
|
||||
const json = await res.json()
|
||||
if (hasMany) {
|
||||
const docIds = json.docs.map((doc) => {
|
||||
const docIDs = json.docs.map((doc) => {
|
||||
if (hasMultipleRelations) {
|
||||
return {
|
||||
relationTo: collection1Slug,
|
||||
@@ -29,7 +29,7 @@ export const PrePopulateFieldUI: React.FC<{
|
||||
|
||||
return doc.id
|
||||
})
|
||||
setValue(docIds)
|
||||
setValue(docIDs)
|
||||
} else {
|
||||
// value that does not appear in first 10 docs fetch
|
||||
setValue(json.docs[6].id)
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"@payloadcms/plugin-sentry": "workspace:*",
|
||||
"@payloadcms/plugin-seo": "workspace:*",
|
||||
"@payloadcms/plugin-stripe": "workspace:*",
|
||||
"@payloadcms/plugin-scheduled-publish": "workspace:*",
|
||||
"@payloadcms/richtext-lexical": "workspace:*",
|
||||
"@payloadcms/richtext-slate": "workspace:*",
|
||||
"@payloadcms/storage-azure": "workspace:*",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'url'
|
||||
|
||||
import type { Config, Page as PayloadPage } from './payload-types.js'
|
||||
|
||||
import { ensureCompilationIsDone, initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
|
||||
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
@@ -16,10 +16,10 @@ const dirname = path.dirname(filename)
|
||||
const { beforeAll, describe } = test
|
||||
let url: AdminUrlUtil
|
||||
let page: Page
|
||||
let draftParentId: string
|
||||
let parentId: string
|
||||
let draftChildId: string
|
||||
let childId: string
|
||||
let draftParentID: string
|
||||
let parentID: string
|
||||
let draftChildID: string
|
||||
let childID: string
|
||||
|
||||
describe('Nested Docs Plugin', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
@@ -50,25 +50,25 @@ describe('Nested Docs Plugin', () => {
|
||||
}
|
||||
|
||||
const parentPage = await createPage({ slug: 'parent-slug' })
|
||||
parentId = parentPage.id
|
||||
parentID = parentPage.id
|
||||
|
||||
const childPage = await createPage({
|
||||
slug: 'child-slug',
|
||||
title: 'Child page',
|
||||
parent: parentId,
|
||||
parent: parentID,
|
||||
})
|
||||
childId = childPage.id
|
||||
childID = childPage.id
|
||||
|
||||
const draftParentPage = await createPage({ slug: 'parent-slug-draft', _status: 'draft' })
|
||||
draftParentId = draftParentPage.id
|
||||
draftParentID = draftParentPage.id
|
||||
|
||||
const draftChildPage = await createPage({
|
||||
slug: 'child-slug-draft',
|
||||
title: 'Child page',
|
||||
parent: draftParentId,
|
||||
parent: draftParentID,
|
||||
_status: 'draft',
|
||||
})
|
||||
draftChildId = draftChildPage.id
|
||||
draftChildID = draftChildPage.id
|
||||
})
|
||||
|
||||
describe('Core functionality', () => {
|
||||
@@ -77,7 +77,7 @@ describe('Nested Docs Plugin', () => {
|
||||
const draftButtonClass = '#action-save-draft'
|
||||
|
||||
test('Parent slug updates breadcrumbs in child', async () => {
|
||||
await page.goto(url.edit(childId))
|
||||
await page.goto(url.edit(childID))
|
||||
let slug = page.locator(slugClass).nth(0)
|
||||
await expect(slug).toHaveValue('child-slug')
|
||||
|
||||
@@ -92,13 +92,13 @@ describe('Nested Docs Plugin', () => {
|
||||
// const parentSlugInChild = page.locator(parentSlugInChildClass).nth(0)
|
||||
// await expect(parentSlugInChild).toHaveValue('/parent-slug')
|
||||
|
||||
await page.goto(url.edit(parentId))
|
||||
await page.goto(url.edit(parentID))
|
||||
slug = page.locator(slugClass).nth(0)
|
||||
await slug.fill('updated-parent-slug')
|
||||
await expect(slug).toHaveValue('updated-parent-slug')
|
||||
await page.locator(publishButtonClass).nth(0).click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await page.goto(url.edit(childId))
|
||||
await page.goto(url.edit(childID))
|
||||
|
||||
// TODO: remove when error states are fixed
|
||||
await apiTabButton.click()
|
||||
@@ -110,7 +110,7 @@ describe('Nested Docs Plugin', () => {
|
||||
})
|
||||
|
||||
test('Draft parent slug does not update child', async () => {
|
||||
await page.goto(url.edit(draftChildId))
|
||||
await page.goto(url.edit(draftChildID))
|
||||
|
||||
// TODO: remove when error states are fixed
|
||||
const apiTabButton = page.locator('text=API')
|
||||
@@ -123,11 +123,11 @@ describe('Nested Docs Plugin', () => {
|
||||
// const parentSlugInChild = page.locator(parentSlugInChildClass).nth(0)
|
||||
// await expect(parentSlugInChild).toHaveValue('/parent-slug-draft')
|
||||
|
||||
await page.goto(url.edit(parentId))
|
||||
await page.goto(url.edit(parentID))
|
||||
await page.locator(slugClass).nth(0).fill('parent-updated-draft')
|
||||
await page.locator(draftButtonClass).nth(0).click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await page.goto(url.edit(draftChildId))
|
||||
await page.goto(url.edit(draftChildID))
|
||||
|
||||
await apiTabButton.click()
|
||||
const updatedBreadcrumbs = page.locator('text=/parent-slug-draft').first()
|
||||
@@ -138,15 +138,15 @@ describe('Nested Docs Plugin', () => {
|
||||
})
|
||||
|
||||
test('Publishing parent doc should not publish child', async () => {
|
||||
await page.goto(url.edit(childId))
|
||||
await page.goto(url.edit(childID))
|
||||
await page.locator(slugClass).nth(0).fill('child-updated-draft')
|
||||
await page.locator(draftButtonClass).nth(0).click()
|
||||
|
||||
await page.goto(url.edit(parentId))
|
||||
await page.goto(url.edit(parentID))
|
||||
await page.locator(slugClass).nth(0).fill('parent-updated-published')
|
||||
await page.locator(publishButtonClass).nth(0).click()
|
||||
|
||||
await page.goto(url.edit(childId))
|
||||
await page.goto(url.edit(childID))
|
||||
await expect(page.locator(slugClass).nth(0)).toHaveValue('child-updated-draft')
|
||||
})
|
||||
})
|
||||
|
||||
2
test/plugin-scheduled-publish/.gitignore
vendored
Normal file
2
test/plugin-scheduled-publish/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/media
|
||||
/media-gif
|
||||
54
test/plugin-scheduled-publish/config.ts
Normal file
54
test/plugin-scheduled-publish/config.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { pluginScheduledPublish } from '@payloadcms/plugin-scheduled-publish'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
// ...extend config here
|
||||
collections: [
|
||||
{
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
},
|
||||
editor: lexicalEditor({}),
|
||||
globals: [],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
},
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
plugins: [
|
||||
pluginScheduledPublish({
|
||||
collections: {
|
||||
posts: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
37
test/plugin-scheduled-publish/e2e.spec.ts
Normal file
37
test/plugin-scheduled-publish/e2e.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import * as path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
test.describe('Admin Panel', () => {
|
||||
let page: Page
|
||||
let url: AdminUrlUtil
|
||||
|
||||
test.beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
|
||||
const { payload, serverURL } = await initPayloadE2ENoConfig({ dirname })
|
||||
url = new AdminUrlUtil(serverURL, 'posts')
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
test('example test', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
const textCell = page.locator('.row-1 .cell-title')
|
||||
await expect(textCell).toHaveText('example post')
|
||||
})
|
||||
})
|
||||
19
test/plugin-scheduled-publish/eslint.config.js
Normal file
19
test/plugin-scheduled-publish/eslint.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { rootParserOptions } from '../../eslint.config.js'
|
||||
import { testEslintConfig } from '../eslint.config.js'
|
||||
|
||||
/** @typedef {import('eslint').Linter.Config} Config */
|
||||
|
||||
/** @type {Config[]} */
|
||||
export const index = [
|
||||
...testEslintConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
...rootParserOptions,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export default index
|
||||
43
test/plugin-scheduled-publish/int.spec.ts
Normal file
43
test/plugin-scheduled-publish/int.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
|
||||
import { devUser } from '../credentials.js'
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
|
||||
let payload: Payload
|
||||
let token: string
|
||||
let restClient: NextRESTClient
|
||||
|
||||
const { email, password } = devUser
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
describe('@payloadcms/plugin-scheduled-publish', () => {
|
||||
// --__--__--__--__--__--__--__--__--__
|
||||
// Boilerplate test setup/teardown
|
||||
// --__--__--__--__--__--__--__--__--__
|
||||
beforeAll(async () => {
|
||||
const initialized = await initPayloadInt(dirname)
|
||||
;({ payload, restClient } = initialized)
|
||||
|
||||
const data = await restClient
|
||||
.POST('/users/login', {
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
||||
token = data.token
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (typeof payload.db.destroy === 'function') {
|
||||
await payload.db.destroy()
|
||||
}
|
||||
})
|
||||
})
|
||||
372
test/plugin-scheduled-publish/payload-types.ts
Normal file
372
test/plugin-scheduled-publish/payload-types.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
/* 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 {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
collections: {
|
||||
posts: Post;
|
||||
users: User;
|
||||
'payload-jobs': PayloadJob;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
posts: PostsSelect<false> | PostsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-jobs': PayloadJobsSelect<false> | PayloadJobsSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {};
|
||||
globalsSelect: {};
|
||||
locale: null;
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs: {
|
||||
tasks: {
|
||||
PublishDocument: TaskPublishDocument;
|
||||
UnpublishDocument: TaskUnpublishDocument;
|
||||
inline: {
|
||||
input: unknown;
|
||||
output: unknown;
|
||||
};
|
||||
};
|
||||
workflows: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
login: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-jobs".
|
||||
*/
|
||||
export interface PayloadJob {
|
||||
id: string;
|
||||
/**
|
||||
* Input data provided to the job
|
||||
*/
|
||||
input?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
taskStatus?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
completedAt?: string | null;
|
||||
totalTried?: number | null;
|
||||
/**
|
||||
* If hasError is true this job will not be retried
|
||||
*/
|
||||
hasError?: boolean | null;
|
||||
/**
|
||||
* If hasError is true, this is the error that caused it
|
||||
*/
|
||||
error?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* Task execution log
|
||||
*/
|
||||
log?:
|
||||
| {
|
||||
executedAt: string;
|
||||
completedAt: string;
|
||||
taskSlug: 'inline' | 'PublishDocument' | 'UnpublishDocument';
|
||||
taskID: string;
|
||||
input?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
output?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
state: 'failed' | 'succeeded';
|
||||
error?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
taskSlug?: ('inline' | 'PublishDocument' | 'UnpublishDocument') | null;
|
||||
queue?: string | null;
|
||||
waitUntil?: string | null;
|
||||
processing?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'payload-jobs';
|
||||
value: string | PayloadJob;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
*/
|
||||
export interface UsersSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
email?: T;
|
||||
resetPasswordToken?: T;
|
||||
resetPasswordExpiration?: T;
|
||||
salt?: T;
|
||||
hash?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-jobs_select".
|
||||
*/
|
||||
export interface PayloadJobsSelect<T extends boolean = true> {
|
||||
input?: T;
|
||||
taskStatus?: T;
|
||||
completedAt?: T;
|
||||
totalTried?: T;
|
||||
hasError?: T;
|
||||
error?: T;
|
||||
log?:
|
||||
| T
|
||||
| {
|
||||
executedAt?: T;
|
||||
completedAt?: T;
|
||||
taskSlug?: T;
|
||||
taskID?: T;
|
||||
input?: T;
|
||||
output?: T;
|
||||
state?: T;
|
||||
error?: T;
|
||||
id?: T;
|
||||
};
|
||||
taskSlug?: T;
|
||||
queue?: T;
|
||||
waitUntil?: T;
|
||||
processing?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
*/
|
||||
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||
document?: T;
|
||||
globalSlug?: T;
|
||||
user?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences_select".
|
||||
*/
|
||||
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||
user?: T;
|
||||
key?: T;
|
||||
value?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations_select".
|
||||
*/
|
||||
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
batch?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "TaskPublishDocument".
|
||||
*/
|
||||
export interface TaskPublishDocument {
|
||||
input: {
|
||||
collectionSlug: 'posts';
|
||||
documentID: string | number;
|
||||
};
|
||||
output?: unknown;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "TaskUnpublishDocument".
|
||||
*/
|
||||
export interface TaskUnpublishDocument {
|
||||
input: {
|
||||
collectionSlug: 'posts';
|
||||
documentID: string | number;
|
||||
};
|
||||
output?: unknown;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auth".
|
||||
*/
|
||||
export interface Auth {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
declare module 'payload' {
|
||||
// @ts-ignore
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
13
test/plugin-scheduled-publish/tsconfig.eslint.json
Normal file
13
test/plugin-scheduled-publish/tsconfig.eslint.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
// extend your base config to share compilerOptions, etc
|
||||
//"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// ensure that nobody can accidentally use this config for a build
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
// whatever paths you intend to lint
|
||||
"./**/*.ts",
|
||||
"./**/*.tsx"
|
||||
]
|
||||
}
|
||||
3
test/plugin-scheduled-publish/tsconfig.json
Normal file
3
test/plugin-scheduled-publish/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
}
|
||||
9
test/plugin-scheduled-publish/types.d.ts
vendored
Normal file
9
test/plugin-scheduled-publish/types.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { RequestContext as OriginalRequestContext } from 'payload'
|
||||
|
||||
declare module 'payload' {
|
||||
// Create a new interface that merges your additional fields with the original one
|
||||
export interface RequestContext extends OriginalRequestContext {
|
||||
myObject?: string
|
||||
// ...
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./test/_community/config.ts"
|
||||
"./test/plugin-scheduled-publish/config.ts"
|
||||
],
|
||||
"@payloadcms/live-preview": [
|
||||
"./packages/live-preview/src"
|
||||
@@ -87,6 +87,9 @@
|
||||
"@payloadcms/plugin-search/client": [
|
||||
"./packages/plugin-search/src/exports/client.ts"
|
||||
],
|
||||
"@payloadcms/plugin-scheduled-publish/rsc": [
|
||||
"./packages/plugin-scheduled-publish/src/exports/rsc.ts"
|
||||
],
|
||||
"@payloadcms/plugin-form-builder/client": [
|
||||
"./packages/plugin-form-builder/src/exports/client.ts"
|
||||
],
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
{
|
||||
"path": "./packages/plugin-stripe"
|
||||
},
|
||||
{
|
||||
"path": "./packages/plugin-scheduled-publish"
|
||||
},
|
||||
{
|
||||
"path": "./packages/richtext-slate"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user