chore: eslint and prettier

This commit is contained in:
Jacob Fletcher
2023-05-16 23:44:43 -04:00
parent 715e154817
commit ee403b79f4
15 changed files with 4018 additions and 1901 deletions

View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@payloadcms'],
}

View File

@@ -0,0 +1,8 @@
module.exports = {
printWidth: 100,
parser: "typescript",
semi: false,
singleQuote: true,
trailingComma: "all",
arrowParens: "avoid",
};

View File

@@ -1,4 +1,4 @@
import { CollectionConfig } from 'payload/types';
import type { CollectionConfig } from 'payload/types'
export const Pages: CollectionConfig = {
slug: 'pages',
@@ -10,7 +10,7 @@ export const Pages: CollectionConfig = {
useAsTitle: 'title',
},
versions: {
drafts: true
drafts: true,
},
fields: [
{
@@ -23,6 +23,6 @@ export const Pages: CollectionConfig = {
name: 'excerpt',
label: 'Excerpt',
type: 'text',
}
},
],
};
}

View File

@@ -1,4 +1,4 @@
import { CollectionConfig } from 'payload/types';
import type { CollectionConfig } from 'payload/types'
export const Posts: CollectionConfig = {
slug: 'posts',
@@ -10,7 +10,7 @@ export const Posts: CollectionConfig = {
useAsTitle: 'title',
},
versions: {
drafts: true
drafts: true,
},
fields: [
{
@@ -23,6 +23,6 @@ export const Posts: CollectionConfig = {
name: 'excerpt',
label: 'Excerpt',
type: 'text',
}
},
],
};
}

View File

@@ -1,4 +1,4 @@
import { CollectionConfig } from 'payload/types';
import type { CollectionConfig } from 'payload/types'
export const Users: CollectionConfig = {
slug: 'users',
@@ -13,4 +13,4 @@ export const Users: CollectionConfig = {
// Email added by default
// Add more fields as needed
],
};
}

View File

@@ -1,43 +1,37 @@
import { buildConfig } from 'payload/config';
import path from 'path';
import path from 'path'
import { buildConfig } from 'payload/config'
// import searchPlugin from '../../dist';
import searchPlugin from '../../src';
import { Users } from './collections/Users';
import { Pages } from './collections/Pages';
import { Posts } from './collections/Posts';
import searchPlugin from '../../src'
import { Pages } from './collections/Pages'
import { Posts } from './collections/Posts'
import { Users } from './collections/Users'
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
webpack: (config) => {
webpack: config => {
const newConfig = {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
react: path.join(__dirname, "../node_modules/react"),
"react-dom": path.join(__dirname, "../node_modules/react-dom"),
"payload": path.join(__dirname, "../node_modules/payload")
react: path.join(__dirname, '../node_modules/react'),
'react-dom': path.join(__dirname, '../node_modules/react-dom'),
payload: path.join(__dirname, '../node_modules/payload'),
},
},
};
}
return newConfig;
return newConfig
},
},
collections: [
Users,
Pages,
Posts
],
collections: [Users, Pages, Posts],
plugins: [
searchPlugin({
collections: [
'pages',
'posts'
],
collections: ['pages', 'posts'],
searchOverrides: {
fields: [
{
@@ -46,21 +40,21 @@ export default buildConfig({
type: 'text',
admin: {
readOnly: true,
}
}
]
},
},
],
},
beforeSync: ({ originalDoc, searchDoc }) => ({
...searchDoc,
excerpt: originalDoc?.excerpt || 'This is a fallback excerpt'
excerpt: originalDoc?.excerpt || 'This is a fallback excerpt',
}),
defaultPriorities: {
pages: 10,
posts: ({ title }) => title === 'Hello, world!' ? 30 : 20
}
posts: ({ title }) => (title === 'Hello, world!' ? 30 : 20),
},
}),
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts')
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
});
})

View File

@@ -1,13 +1,14 @@
import express from 'express';
import payload from 'payload';
import dotenv from 'dotenv'
import express from 'express'
import payload from 'payload'
require('dotenv').config();
const app = express();
dotenv.config()
const app = express()
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
res.redirect('/admin')
})
// Initialize Payload
payload.init({
@@ -15,10 +16,10 @@ payload.init({
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
},
});
})
// Add your own express routes here
app.listen(3000);
app.listen(3000)

View File

@@ -3,12 +3,14 @@
"version": "1.0.0",
"homepage:": "https://payloadcms.com",
"repository": "git@github.com:payloadcms/plugin-search.git",
"description": "Search plugin for Payload CMS",
"description": "Search plugin for Payload",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint src",
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"keywords": [
"payload",
@@ -25,17 +27,35 @@
"payload": "^0.18.5",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"dependencies": {
"ts-deepmerge": "^2.0.1"
},
"devDependencies": {
"@payloadcms/eslint-config": "^0.0.1",
"@types/express": "^4.17.9",
"@types/node": "18.11.3",
"@types/react": "18.0.21",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
"eslint": "^8.19.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"nodemon": "^2.0.6",
"payload": "^0.18.5",
"react": "^17.0.2",
"typescript": "^4.5.5"
"prettier": "^2.7.1",
"react": "^18.0.0",
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
},
"files": [
"dist",
"types.js",
"types.d.ts"
],
"dependencies": {
"ts-deepmerge": "^2.0.1"
}
]
}

View File

@@ -1,8 +1,8 @@
import { CollectionAfterDeleteHook } from 'payload/types';
import type { CollectionAfterDeleteHook } from 'payload/types'
const deleteFromSearch: CollectionAfterDeleteHook = ({ req: { payload }, doc }) => {
try {
const deleteSearchDoc = async () => {
const deleteSearchDoc = async (): Promise<any> => {
const searchDocQuery = await payload.find({
collection: 'search',
where: {
@@ -11,22 +11,24 @@ const deleteFromSearch: CollectionAfterDeleteHook = ({ req: { payload }, doc })
},
},
depth: 0,
}) as any;
})
if (searchDocQuery?.docs?.[0]) {
payload.delete({
collection: 'search',
id: searchDocQuery?.docs?.[0]?.id,
});
})
}
};
}
deleteSearchDoc();
} catch (err) {
console.error(err);
deleteSearchDoc()
} catch (err: unknown) {
payload.logger.error({
err: `Error deleting search doc: ${err}`,
})
}
return doc;
};
return doc
}
export default deleteFromSearch;
export default deleteFromSearch

View File

@@ -1,28 +1,19 @@
import { SearchConfig, SyncWithSearch } from '../../types';
import type { SearchConfig, SyncWithSearch } from '../../types'
const syncWithSearch: SyncWithSearch = async (args) => {
const syncWithSearch: SyncWithSearch = async args => {
const {
req: { payload },
doc,
operation,
// @ts-ignore
// @ts-expect-error
collection,
// @ts-ignore
searchConfig
} = args;
// @ts-expect-error
searchConfig,
} = args
const {
title,
id,
_status: status,
} = doc || {};
const { title, id, _status: status } = doc || {}
const {
beforeSync,
syncDrafts,
deleteDrafts,
defaultPriorities,
} = searchConfig as SearchConfig; // todo fix SyncWithSearch type, see note in ./types.ts
const { beforeSync, syncDrafts, deleteDrafts, defaultPriorities } = searchConfig as SearchConfig // todo fix SyncWithSearch type, see note in ./types.ts
let dataToSave = {
title,
@@ -30,35 +21,35 @@ const syncWithSearch: SyncWithSearch = async (args) => {
relationTo: collection,
value: id,
},
};
}
if (typeof beforeSync === 'function') {
dataToSave = await beforeSync({
originalDoc: doc,
searchDoc: dataToSave,
payload
});
payload,
})
}
let defaultPriority = 0;
let defaultPriority = 0
if (defaultPriorities) {
const {
[collection]: priority,
} = defaultPriorities;
const { [collection]: priority } = defaultPriorities
if (typeof priority === 'function') {
try {
defaultPriority = await priority(doc);
} catch (err) {
payload.logger.error(err);
payload.logger.error(`Error gathering default priority for search documents related to ${collection}`);
defaultPriority = await priority(doc)
} catch (err: unknown) {
payload.logger.error(err)
payload.logger.error(
`Error gathering default priority for search documents related to ${collection}`,
)
}
} else {
defaultPriority = priority;
defaultPriority = priority
}
}
const doSync = syncDrafts || (!syncDrafts && status !== 'draft');
const doSync = syncDrafts || (!syncDrafts && status !== 'draft')
try {
if (operation === 'create') {
@@ -69,7 +60,7 @@ const syncWithSearch: SyncWithSearch = async (args) => {
...dataToSave,
priority: defaultPriority,
},
});
})
}
}
@@ -84,32 +75,34 @@ const syncWithSearch: SyncWithSearch = async (args) => {
},
},
depth: 0,
}) as any;
})
const docs: {
const docs: Array<{
id: string
priority?: number
}[] = searchDocQuery?.docs || [];
}> = searchDocQuery?.docs || []
const [foundDoc, ...duplicativeDocs] = docs;
const [foundDoc, ...duplicativeDocs] = docs
// delete all duplicative search docs (docs that reference the same page)
// to ensure the same, out-of-date result does not appear twice (where only syncing the first found doc)
if (duplicativeDocs.length > 0) {
try {
Promise.all(duplicativeDocs.map(({ id: duplicativeDocID }) => payload.delete({
collection: 'search',
id: duplicativeDocID,
})));
} catch (err) {
payload.logger.error(`Error deleting duplicative search documents.`);
Promise.all(
duplicativeDocs.map(({ id: duplicativeDocID }) =>
payload.delete({
collection: 'search',
id: duplicativeDocID,
}),
), // eslint-disable-line function-paren-newline
)
} catch (err: unknown) {
payload.logger.error(`Error deleting duplicative search documents.`)
}
}
if (foundDoc) {
const {
id: searchDocID,
} = foundDoc;
const { id: searchDocID } = foundDoc
if (doSync) {
// update the doc normally
@@ -121,9 +114,9 @@ const syncWithSearch: SyncWithSearch = async (args) => {
...dataToSave,
priority: foundDoc.priority || defaultPriority,
},
});
} catch (err) {
payload.logger.error(`Error updating search document.`);
})
} catch (err: unknown) {
payload.logger.error(`Error updating search document.`)
}
}
if (deleteDrafts && status === 'draft') {
@@ -132,9 +125,9 @@ const syncWithSearch: SyncWithSearch = async (args) => {
payload.delete({
collection: 'search',
id: searchDocID,
});
} catch (err) {
payload.logger.error(`Error deleting search document.`);
})
} catch (err: unknown) {
payload.logger.error(`Error deleting search document: ${err}`)
}
}
} else if (doSync) {
@@ -145,22 +138,22 @@ const syncWithSearch: SyncWithSearch = async (args) => {
...dataToSave,
priority: defaultPriority,
},
});
} catch (err) {
payload.logger.error(err);
payload.logger.error(`Error creating search document.`);
})
} catch (err: unknown) {
payload.logger.error(`Error creating search document: ${err}`)
}
}
} catch (err) {
payload.logger.error(`Error finding search document.`);
} catch (err: unknown) {
payload.logger.error(`Error finding search document: ${err}`)
}
}
} catch (err) {
payload.logger.error(err);
payload.logger.error(`Error syncing search document related to ${collection} with id: '${id}'`);
} catch (err: unknown) {
payload.logger.error(
`Error syncing search document related to ${collection} with id: '${id}': ${err}`,
)
}
return doc;
};
return doc
}
export default syncWithSearch;
export default syncWithSearch

View File

@@ -1,63 +1,67 @@
import { CollectionConfig } from 'payload/types';
import { SearchConfig } from '../types';
import deepMerge from 'ts-deepmerge';
import { LinkToDoc } from './ui';
import type { CollectionConfig } from 'payload/types'
import deepMerge from 'ts-deepmerge'
import type { SearchConfig } from '../types'
import { LinkToDoc } from './ui'
// all settings can be overridden by the config
export const generateSearchCollection = (searchConfig: SearchConfig): CollectionConfig => deepMerge({
slug: 'search',
labels: {
singular: 'Search Result',
plural: 'Search Results',
},
admin: {
useAsTitle: 'title',
defaultColumns: [
'title',
],
description: 'This is a collection of automatically created search results. These results are used by the global site search and will be updated automatically as documents in the CMS are created or updated.',
enableRichTextRelationship: false,
},
access: {
read: (): boolean => true,
create: (): boolean => false,
},
fields: [
export const generateSearchCollection = (searchConfig: SearchConfig): CollectionConfig =>
deepMerge(
{
name: 'title',
type: 'text',
admin: {
readOnly: true,
}
},
{
name: 'priority',
type: 'number',
admin: {
position: 'sidebar'
}
},
{
name: 'doc',
type: 'relationship',
relationTo: searchConfig?.collections || [],
required: true,
index: true,
maxDepth: 0,
admin: {
readOnly: true,
position: 'sidebar'
slug: 'search',
labels: {
singular: 'Search Result',
plural: 'Search Results',
},
},
{
name: 'docUrl',
type: 'ui',
admin: {
position: 'sidebar',
components: {
Field: LinkToDoc
}
useAsTitle: 'title',
defaultColumns: ['title'],
description:
'This is a collection of automatically created search results. These results are used by the global site search and will be updated automatically as documents in the CMS are created or updated.',
enableRichTextRelationship: false,
},
access: {
read: (): boolean => true,
create: (): boolean => false,
},
fields: [
{
name: 'title',
type: 'text',
admin: {
readOnly: true,
},
},
{
name: 'priority',
type: 'number',
admin: {
position: 'sidebar',
},
},
{
name: 'doc',
type: 'relationship',
relationTo: searchConfig?.collections || [],
required: true,
index: true,
maxDepth: 0,
admin: {
readOnly: true,
position: 'sidebar',
},
},
{
name: 'docUrl',
type: 'ui',
admin: {
position: 'sidebar',
components: {
Field: LinkToDoc,
},
},
},
],
},
],
}, searchConfig?.searchOverrides || {});
searchConfig?.searchOverrides || {},
)

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { useWatchForm } from 'payload/components/forms';
import CopyToClipboard from 'payload/dist/admin/components/elements/CopyToClipboard';
import { UIField } from 'payload/dist/fields/config/types';
import { Fields } from 'payload/dist/admin/components/forms/Form/types';
import { useConfig } from 'payload/components/utilities';
import React from 'react'
import { useWatchForm } from 'payload/components/forms'
import { useConfig } from 'payload/components/utilities'
import CopyToClipboard from 'payload/dist/admin/components/elements/CopyToClipboard'
import { Fields } from 'payload/dist/admin/components/forms/Form/types'
import { UIField } from 'payload/dist/fields/config/types'
type FieldsWithDoc = Fields & {
doc: {
@@ -15,26 +15,23 @@ type FieldsWithDoc = Fields & {
}
export const LinkToDoc: React.FC<UIField> = () => {
const form = useWatchForm();
const fields = form.fields as FieldsWithDoc;
const form = useWatchForm()
const fields = form.fields as FieldsWithDoc
const {
doc: {
value: {
relationTo,
value: docId
}
}
} = fields;
value: { relationTo, value: docId },
},
} = fields
const config = useConfig();
const config = useConfig()
const {
serverURL,
routes: {
admin: adminRoute, // already includes leading slash
} = {},
} = config;
} = config
const href = `${serverURL}${adminRoute}/collections/${relationTo}/${docId}`
@@ -44,26 +41,22 @@ export const LinkToDoc: React.FC<UIField> = () => {
<span
className="label"
style={{
color: '#9A9A9A'
color: '#9A9A9A',
}}
>
Doc URL
</span>
<CopyToClipboard
value={href as string}
/>
<CopyToClipboard value={href as string} />
</div>
<div
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
fontWeight: '600'
fontWeight: '600',
}}
>
<a href={href as string}>
{href}
</a>
<a href={href as string}>{href}</a>
</div>
</div >
);
};
</div>
)
}

View File

@@ -1,66 +1,64 @@
import { Config } from 'payload/config';
import { generateSearchCollection } from './Search';
import syncWithSearch from './Search/hooks/syncWithSearch';
import deleteFromSearch from './Search/hooks/deleteFromSearch';
import { SearchConfig } from './types';
import type { Config } from 'payload/config'
const Search = (incomingSearchConfig: SearchConfig) => (config: Config): Config => {
const {
collections
} = config;
import { generateSearchCollection } from './Search'
import deleteFromSearch from './Search/hooks/deleteFromSearch'
import syncWithSearch from './Search/hooks/syncWithSearch'
import type { SearchConfig } from './types'
if (collections) {
const searchConfig: SearchConfig = {
...incomingSearchConfig,
syncDrafts: false,
deleteDrafts: true
// write any config defaults here
};
const Search =
(incomingSearchConfig: SearchConfig) =>
(config: Config): Config => {
const { collections } = config
// add a beforeChange hook to every search-enabled collection
const collectionsWithSearchHooks = config?.collections?.map((collection) => {
const {
hooks: existingHooks
} = collection;
const enabledCollections = searchConfig.collections || [];
const isEnabled = enabledCollections.indexOf(collection.slug) > -1;
if (isEnabled) {
return {
...collection,
hooks: {
...collection.hooks,
afterChange: [
...(existingHooks?.afterChange || []),
async (args: any) => {
syncWithSearch({
...args,
collection: collection.slug,
searchConfig
})
},
],
afterDelete: [
...(existingHooks?.afterDelete || []),
deleteFromSearch,
],
},
};
if (collections) {
const searchConfig: SearchConfig = {
...incomingSearchConfig,
syncDrafts: false,
deleteDrafts: true,
// write any config defaults here
}
return collection;
}).filter(Boolean);
// add a beforeChange hook to every search-enabled collection
const collectionsWithSearchHooks = config?.collections
?.map(collection => {
const { hooks: existingHooks } = collection
return {
...config,
collections: [
...collectionsWithSearchHooks || [],
generateSearchCollection(searchConfig),
],
};
const enabledCollections = searchConfig.collections || []
const isEnabled = enabledCollections.indexOf(collection.slug) > -1
if (isEnabled) {
return {
...collection,
hooks: {
...collection.hooks,
afterChange: [
...(existingHooks?.afterChange || []),
async (args: any) => {
syncWithSearch({
...args,
collection: collection.slug,
searchConfig,
})
},
],
afterDelete: [...(existingHooks?.afterDelete || []), deleteFromSearch],
},
}
}
return collection
})
.filter(Boolean)
return {
...config,
collections: [
...(collectionsWithSearchHooks || []),
generateSearchCollection(searchConfig),
],
}
}
return config
}
return config;
};
export default Search;
export default Search

View File

@@ -1,7 +1,7 @@
import { Payload } from 'payload';
import { CollectionAfterChangeHook, CollectionConfig } from 'payload/types';
import type { Payload } from 'payload'
import type { CollectionAfterChangeHook, CollectionConfig } from 'payload/types'
export type DocToSync = {
export interface DocToSync {
[key: string]: any
title: string
doc: {
@@ -16,9 +16,9 @@ export type BeforeSync = (args: {
}
searchDoc: DocToSync
payload: Payload
}) => DocToSync | Promise<DocToSync>;
}) => DocToSync | Promise<DocToSync>
export type SearchConfig = {
export interface SearchConfig {
searchOverrides?: Partial<CollectionConfig>
collections?: string[]
defaultPriorities?: {
@@ -32,4 +32,4 @@ export type SearchConfig = {
// TODO: extend this hook with additional args
// searchConfig: SearchConfig
// collection: string
export type SyncWithSearch = CollectionAfterChangeHook;
export type SyncWithSearch = CollectionAfterChangeHook

File diff suppressed because it is too large Load Diff