├── types.d.ts ├── src ├── mocks │ └── mockFile.js ├── index.ts ├── webpack.ts ├── types.ts ├── components │ └── IframeComponent │ │ └── index.tsx └── plugin.ts ├── dev ├── src │ ├── mocks │ │ └── fileStub.js │ ├── collections │ │ ├── Users.ts │ │ └── Examples.ts │ ├── globals │ │ └── Homepage.ts │ ├── server.ts │ └── payload.config.ts ├── .gitignore ├── .env.example ├── nodemon.json ├── docker-compose.yml ├── jest.config.js ├── Dockerfile ├── plugin.spec.ts └── package.json ├── .prettierrc.js ├── eslint-config ├── rules │ ├── prettier.js │ ├── style.js │ ├── import.js │ └── typescript.js └── index.js ├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ ├── test.yml │ └── publish.yml ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md └── .gitignore /types.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/' 2 | -------------------------------------------------------------------------------- /src/mocks/mockFile.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /dev/src/mocks/fileStub.js: -------------------------------------------------------------------------------- 1 | export default 'file-stub' 2 | -------------------------------------------------------------------------------- /dev/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | /media 4 | node_modules 5 | .DS_Store 6 | .env 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { iframeTabsPlugin } from './plugin' 2 | export type { PluginTypes } from './types' 3 | 4 | export default iframeTabsPlugin 5 | -------------------------------------------------------------------------------- /dev/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URI=mongodb://127.0.0.1/plugin-development 2 | PAYLOAD_SECRET=hellohereisasecretforyou 3 | SERVER_URL=http://localhost:3012 4 | PORT=3012 5 | -------------------------------------------------------------------------------- /dev/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nodemon.json", 3 | "ext": "ts", 4 | "exec": "ts-node src/server.ts -- -I", 5 | "stdin": false 6 | } -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | parser: "typescript", 4 | semi: false, 5 | singleQuote: true, 6 | trailingComma: "all", 7 | arrowParens: "avoid", 8 | }; 9 | -------------------------------------------------------------------------------- /eslint-config/rules/prettier.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['prettier'], 3 | extends: ['plugin:prettier/recommended'], 4 | rules: { 5 | 'prettier/prettier': 'error', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = null 11 | -------------------------------------------------------------------------------- /dev/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mongo: 5 | image: mongo:latest 6 | ports: 7 | - '27017:27017' 8 | command: 9 | - --storageEngine=wiredTiger 10 | volumes: 11 | - data:/data/db 12 | logging: 13 | driver: none 14 | 15 | volumes: 16 | data: 17 | node_modules: 18 | -------------------------------------------------------------------------------- /dev/src/collections/Users.ts: -------------------------------------------------------------------------------- 1 | import { CollectionConfig } from 'payload/types'; 2 | 3 | const Users: CollectionConfig = { 4 | slug: 'users', 5 | auth: true, 6 | admin: { 7 | useAsTitle: 'email', 8 | }, 9 | fields: [ 10 | // Email added by default 11 | // Add more fields as needed 12 | ], 13 | }; 14 | 15 | export default Users; -------------------------------------------------------------------------------- /dev/src/globals/Homepage.ts: -------------------------------------------------------------------------------- 1 | import { GlobalConfig } from 'payload/types' 2 | 3 | const Homepage: GlobalConfig = { 4 | slug: 'Homepage', 5 | fields: [ 6 | { 7 | name: 'someField', 8 | type: 'text', 9 | }, 10 | { 11 | name: 'source', 12 | type: 'text', 13 | }, 14 | ], 15 | } 16 | 17 | export default Homepage 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['./eslint-config'], 4 | overrides: [ 5 | // Temporary overrides 6 | { 7 | files: ['dev/**/*.ts'], 8 | rules: { 9 | 'import/no-relative-packages': 'off', 10 | 'no-process-env': 'off', 11 | }, 12 | }, 13 | ], 14 | excludes: [ 15 | 'dev/plugin.spec.ts', 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /eslint-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'airbnb-base', 5 | require.resolve('./rules/style.js'), 6 | require.resolve('./rules/import.js'), 7 | require.resolve('./rules/typescript.js'), 8 | require.resolve('./rules/prettier.js'), 9 | ], 10 | env: { 11 | es6: true, 12 | browser: true, 13 | node: true, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /dev/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.(t|j)sx?$': '/node_modules/@swc/jest', 6 | }, 7 | moduleNameMapper: { 8 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 9 | '/src/mocks/fileStub.js', 10 | '\\.(css|scss)$': '/src/mocks/fileStub.js', 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | #on: 4 | # push: 5 | # branches: [ master ] 6 | # pull_request: 7 | # branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: '14.x' 17 | registry-url: 'https://registry.npmjs.org' 18 | - run: yarn install 19 | - run: cd dev && yarn install 20 | - run: yarn test 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "target": "es5", 9 | "outDir": "./dist", 10 | "allowJs": true, 11 | "module": "commonjs", 12 | "sourceMap": true, 13 | "jsx": "react", 14 | "esModuleInterop": true, 15 | "declaration": true, 16 | "declarationDir": "./dist", 17 | "skipLibCheck": true, 18 | "strict": true, 19 | }, 20 | "include": [ 21 | "src/**/*", 22 | ], 23 | } 24 | -------------------------------------------------------------------------------- /dev/src/collections/Examples.ts: -------------------------------------------------------------------------------- 1 | import { CollectionConfig } from 'payload/types' 2 | 3 | // Example Collection - For reference only, this must be added to payload.config.ts to be used. 4 | const Examples: CollectionConfig = { 5 | slug: 'examples', 6 | admin: { 7 | useAsTitle: 'someField', 8 | }, 9 | fields: [ 10 | { 11 | name: 'someField', 12 | type: 'text', 13 | }, 14 | { 15 | name: 'source', 16 | type: 'text', 17 | }, 18 | ], 19 | } 20 | 21 | export default Examples 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish New Version 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: '16.x' 15 | registry-url: 'https://registry.npmjs.org' 16 | - run: yarn install 17 | #- run: yarn test 18 | - run: yarn build 19 | - run: yarn publish --access public 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.8-alpine as base 2 | 3 | FROM base as builder 4 | 5 | WORKDIR /home/node/app 6 | COPY package*.json ./ 7 | 8 | COPY . . 9 | RUN yarn install 10 | RUN yarn build 11 | 12 | FROM base as runtime 13 | 14 | ENV NODE_ENV=production 15 | ENV PAYLOAD_CONFIG_PATH=dist/payload.config.js 16 | 17 | WORKDIR /home/node/app 18 | COPY package*.json ./ 19 | COPY yarn.lock ./ 20 | 21 | RUN yarn install --production 22 | COPY --from=builder /home/node/app/dist ./dist 23 | COPY --from=builder /home/node/app/build ./build 24 | 25 | EXPOSE 3000 26 | 27 | CMD ["node", "dist/server.js"] 28 | -------------------------------------------------------------------------------- /dev/src/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import payload from 'payload' 3 | import { InitOptions } from 'payload/config' 4 | 5 | require('dotenv').config() 6 | const app = express() 7 | 8 | // Redirect root to Admin panel 9 | app.get('/', (_, res) => { 10 | res.redirect('/admin') 11 | }) 12 | 13 | export const start = async (args?: Partial) => { 14 | // Initialize Payload 15 | await payload.init({ 16 | secret: process.env.PAYLOAD_SECRET!, 17 | express: app, 18 | onInit: async () => { 19 | payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`) 20 | }, 21 | ...(args || {}), 22 | }) 23 | 24 | // Add your own express routes here 25 | 26 | app.listen(process.env.PORT) 27 | } 28 | 29 | start() 30 | -------------------------------------------------------------------------------- /eslint-config/rules/style.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'prefer-named-exports': 'off', 4 | 5 | 'prefer-destructuring': 'off', 6 | // 'prefer-destructuring': ['warn', { object: true, array: true }], 7 | // ensure all object/arrays end with a comma 8 | 'comma-dangle': ['error', 'always-multiline'], 9 | 'class-methods-use-this': 'off', 10 | // consistent new lines 11 | 'function-paren-newline': ['error', 'consistent'], 12 | 'eol-last': ['error', 'always'], 13 | // allow restricted syntax like for...of loops 14 | 'no-restricted-syntax': 'off', 15 | 'no-await-in-loop': 'off', 16 | 'no-console': 'error', 17 | // 'no-floating-promises': true, 18 | // do not allow process.env access in files 19 | 'no-process-env': 'warn', 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /dev/plugin.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Server } from 'http' 2 | import mongoose from 'mongoose' 3 | import payload from 'payload' 4 | import { start } from './src/server' 5 | 6 | describe('Plugin tests', () => { 7 | let server: Server 8 | 9 | beforeAll(async () => { 10 | await start({ local: true }) 11 | }) 12 | 13 | afterAll(async () => { 14 | await mongoose.connection.dropDatabase() 15 | await mongoose.connection.close() 16 | server.close() 17 | }) 18 | 19 | // Add tests to ensure that the plugin works as expected 20 | 21 | // Example test to check for seeded data 22 | it('seeds data accordingly', async () => { 23 | const newCollectionQuery = await payload.find({ 24 | collection: 'new-collection', 25 | sort: 'createdAt', 26 | }) 27 | 28 | expect(newCollectionQuery.totalDocs).toEqual(1) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /src/webpack.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import type { Config } from 'payload/config' 3 | import type { Configuration as WebpackConfig } from 'webpack' 4 | 5 | export const extendWebpackConfig = 6 | (config: Config): ((webpackConfig: WebpackConfig) => WebpackConfig) => 7 | webpackConfig => { 8 | const existingWebpackConfig = 9 | typeof config.admin?.webpack === 'function' 10 | ? config.admin.webpack(webpackConfig) 11 | : webpackConfig 12 | 13 | const mockModulePath = path.resolve(__dirname, './mocks/mockFile.js') 14 | 15 | const newWebpack = { 16 | ...existingWebpackConfig, 17 | resolve: { 18 | ...(existingWebpackConfig.resolve || {}), 19 | alias: { 20 | ...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}), 21 | // Add additional aliases here like so: 22 | [path.resolve(__dirname, './yourFileHere')]: mockModulePath, 23 | }, 24 | }, 25 | } 26 | 27 | return newWebpack 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Payload 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eslint-config/rules/import.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es6: true, 4 | }, 5 | extends: ['plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript'], 6 | plugins: ['import'], 7 | settings: { 8 | 'import/parsers': { 9 | '@typescript-eslint/parser': ['.ts'], 10 | }, 11 | }, 12 | rules: { 13 | /** 14 | * https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unresolved.md 15 | */ 16 | 'import/no-unresolved': ['error', { commonjs: true, caseSensitive: true }], 17 | 'import/no-default-export': 'off', 18 | 'import/prefer-default-export': 'off', 19 | 'import/extensions': [ 20 | 'error', 21 | 'ignorePackages', 22 | { 23 | ts: 'never', 24 | tsx: 'never', 25 | js: 'never', 26 | jsx: 'never', 27 | }, 28 | ], 29 | 'import/no-extraneous-dependencies': 'off', 30 | /** 31 | * https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/named.md#when-not-to-use-it 32 | */ 33 | 'import/named': 'error', 34 | 'import/no-relative-packages': 'warn', 35 | 'import/no-import-module-exports': 'warn', 36 | 'import/no-cycle': 'warn', 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Payload2Blank", 3 | "description": "A blank template to get started with Payload", 4 | "version": "1.0.0", 5 | "main": "dist/server.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon", 9 | "build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build", 10 | "build:server": "tsc", 11 | "build": "yarn copyfiles && yarn build:payload && yarn build:server", 12 | "serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js", 13 | "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/", 14 | "generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types", 15 | "generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema", 16 | "payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload" 17 | }, 18 | "dependencies": { 19 | "@payloadcms/bundler-webpack": "^1.0.0", 20 | "@payloadcms/db-mongodb": "^1.0.0", 21 | "@payloadcms/plugin-cloud": "^2.0.0", 22 | "@payloadcms/richtext-slate": "^1.0.0", 23 | "cross-env": "^7.0.3", 24 | "dotenv": "^8.2.0", 25 | "express": "^4.17.1", 26 | "payload": "^2.0.0" 27 | }, 28 | "devDependencies": { 29 | "@types/express": "^4.17.9", 30 | "copyfiles": "^2.4.1", 31 | "nodemon": "^2.0.6", 32 | "ts-node": "^9.1.1", 33 | "typescript": "^5.3.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | 3 | interface BaseTab { 4 | /** 5 | * A machine name for the tab, ideally in PascalCase 6 | */ 7 | name: string 8 | /** 9 | * Admin label of the tab 10 | */ 11 | label: string 12 | /** 13 | * Path of the tab 14 | */ 15 | path: string 16 | } 17 | 18 | interface TabWithSrc extends BaseTab { 19 | /** 20 | * Admin label of the tab 21 | */ 22 | src: string 23 | /** 24 | * Optionally add props to the iframe element 25 | */ 26 | iframeProps?: React.IframeHTMLAttributes 27 | } 28 | 29 | interface TabWithCode extends BaseTab { 30 | /** 31 | * Full HTML code for the embed (not recommended) 32 | */ 33 | code: string 34 | } 35 | 36 | interface TabWithDynamic extends BaseTab { 37 | /** 38 | * Field path to get value for to use as src 39 | */ 40 | watchField: string 41 | /** 42 | * Optionally add props to the iframe element 43 | */ 44 | iframeProps?: React.IframeHTMLAttributes 45 | } 46 | 47 | export type TabConfig = TabWithSrc | TabWithCode | TabWithDynamic 48 | 49 | export interface CollectionTabConfig { 50 | /** 51 | * Slug of the collection to attach the tab to 52 | */ 53 | slug: string 54 | tabs: TabConfig[] 55 | } 56 | 57 | export interface PluginTypes { 58 | /** 59 | * Enable or disable plugin 60 | * @default false 61 | */ 62 | enabled?: boolean 63 | collections?: CollectionTabConfig[] 64 | globals?: CollectionTabConfig[] 65 | } 66 | 67 | export interface NewCollectionTypes { 68 | title: string 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nouance/payload-iframe-tabs-plugin", 3 | "version": "1.0.2", 4 | "homepage:": "https://github.com/NouanceLabs/payload-iframe-tabs-plugin", 5 | "repository": "git@github.com:NouanceLabs/payload-iframe-tabs-plugin.git", 6 | "description": "A plugin that easily allows you add iframes into separate tabs in the admin panel", 7 | "main": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "keywords": [ 10 | "payload", 11 | "cms", 12 | "plugin", 13 | "typescript", 14 | "react", 15 | "template" 16 | ], 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsc", 22 | "test": "cd dev && yarn test", 23 | "lint": "eslint src", 24 | "lint:fix": "eslint --fix --ext .ts,.tsx src", 25 | "clean": "rimraf dist && rimraf dev/yarn.lock" 26 | }, 27 | "author": "dev@nouance.io", 28 | "license": "MIT", 29 | "peerDependencies": { 30 | "payload": "^2.0.0" 31 | }, 32 | "devDependencies": { 33 | "@payloadcms/bundler-webpack": "^1.0.5", 34 | "@payloadcms/eslint-config": "^0.0.1", 35 | "@swc/jest": "^0.2.28", 36 | "@types/jest": "^29.5.11", 37 | "@types/react": "^18.2.46", 38 | "@typescript-eslint/eslint-plugin": "5.12.1", 39 | "@typescript-eslint/parser": "5.12.1", 40 | "dotenv": "^8.2.0", 41 | "eslint": "^8.19.0", 42 | "eslint-config-airbnb-base": "^14.2.1", 43 | "eslint-config-prettier": "^8.5.0", 44 | "eslint-plugin-import": "2.25.4", 45 | "eslint-plugin-prettier": "^4.0.0", 46 | "jest": "^29.7.0", 47 | "payload": "^2.5.0", 48 | "prettier": "^2.7.1", 49 | "react": "^18.2.0", 50 | "typescript": "^5.3.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/IframeComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect, useState } from 'react' 2 | import { useStepNav } from 'payload/dist/admin/components/elements/StepNav' 3 | import { useDocumentInfo } from 'payload/dist/admin/components/utilities/DocumentInfo' 4 | import type { AdminViewComponent, AdminViewProps } from 'payload/config' 5 | import { Label, useField, useFormFields } from 'payload/components/forms' 6 | import { Gutter } from 'payload/dist/admin/components/elements/Gutter' 7 | import { TabConfig } from '../../types' 8 | 9 | const IframeComponent: React.FC = ({ user }) => { 10 | const { setStepNav } = useStepNav() 11 | const { collection, global } = useDocumentInfo() 12 | const [tab, setTab] = useState() 13 | 14 | const field = useFormFields(([fields, dispatch]) => { 15 | if (tab && 'watchField' in tab) { 16 | return fields[tab.watchField] 17 | } 18 | return null 19 | }) 20 | 21 | useEffect(() => { 22 | if (tab?.label) { 23 | setStepNav([ 24 | { 25 | label: tab.label, 26 | }, 27 | ]) 28 | } 29 | }, [setStepNav, tab]) 30 | 31 | useEffect(() => { 32 | if (window) { 33 | const pathname = window.location.pathname 34 | 35 | if (collection) { 36 | Object.entries(collection.custom['iframesTabPlugin'])?.forEach(([key, value]) => { 37 | if (pathname.includes(key)) { 38 | // @ts-expect-error 39 | setTab(value) 40 | } 41 | }) 42 | } else if (global) { 43 | Object.entries(global.custom['iframesTabPlugin'])?.forEach(([key, value]) => { 44 | if (pathname.includes(key)) { 45 | // @ts-expect-error 46 | setTab(value) 47 | } 48 | }) 49 | } 50 | } 51 | }, []) 52 | 53 | return ( 54 | 55 | {tab && 'src' in tab && `, 85 | }, 86 | { 87 | name: 'Dynamic', 88 | label: 'Dynamic', 89 | path: '/dynamic', 90 | watchField: 'source', 91 | iframeProps: { 92 | height: 1080, 93 | width: 1920, 94 | frameBorder: '0', 95 | style: { aspectRatio: '19/10', maxWidth: '100%', height: 'auto' }, 96 | }, 97 | }, 98 | ], 99 | }, 100 | ], 101 | }), 102 | ], 103 | db: mongooseAdapter({ 104 | url: process.env.DATABASE_URI!, 105 | }), 106 | }) 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Payload Iframe Tabs Plugin 2 | 3 | A plugin that easily allows you add iframes into separate tabs in the admin panel. 4 | 5 | ![iframepluginshowcase (2)](https://github.com/NouanceLabs/payload-iframe-tabs-plugin/assets/35137243/938769e5-988a-46ff-bf9d-a9e0f7e9106c) 6 | 7 | 8 | ## Installation 9 | 10 | ```bash 11 | yarn add @nouance/payload-iframe-tabs-plugin 12 | # OR 13 | npm i @nouance/payload-iframe-tabs-plugin 14 | ``` 15 | 16 | ## Features 17 | 18 | - [Src](#src) 19 | - [Code](#code) 20 | - [Dynamic](#dynamic) 21 | 22 | ## Supported tabs 23 | 24 | Currently usage is the same for `globals` and `collections` as well. 25 | 26 | Basic usage: 27 | 28 | ```ts 29 | import iframeTabsPlugin from '@nouance/payload-iframe-tabs-plugin' 30 | 31 | // ... 32 | 33 | iframeTabsPlugin({ 34 | enabled: true, 35 | collections: [ 36 | { 37 | slug: 'examples', 38 | tabs: [ 39 | { 40 | name: 'FirstYoutube', 41 | label: 'Michael Scott', 42 | path: '/michael-scott', 43 | src: 'https://www.youtube.com/embed/B9MNITrHu9E', 44 | }, 45 | ], 46 | }, 47 | ], 48 | globals: [ 49 | { 50 | slug: 'Homepage', 51 | tabs: [ 52 | { 53 | name: 'Spongebob', 54 | label: 'Spongebob', 55 | path: '/spongebob', 56 | src: 'https://www.youtube.com/embed/hzond0fF4MM', 57 | iframeProps: { 58 | height: 1080, 59 | width: 1920, 60 | frameBorder: '0', 61 | style: { aspectRatio: '19/10', maxWidth: '100%', height: 'auto' }, 62 | }, 63 | }, 64 | ], 65 | }, 66 | ], 67 | }), 68 | ``` 69 | 70 | ### Base config 71 | 72 | This config is required for all tabs. 73 | 74 | - `name` | required | A machine name used for the tab, it must be unique and preferably written in PascalCase 75 | 76 | - `label` | required 77 | 78 | - `path` | required | A unique path that dictates what URL this tab will be available in 79 | 80 | ### Src 81 | 82 | In src mode, you only need to provide the right link for the iframe to render its content. You can then pass an additional `iframeProps` to style and configure your iframe element. 83 | 84 | - `src` | required | Full URL to embeddable content 85 | 86 | - `iframeProps` | optional | HTML props for the Iframe element 87 | 88 | ```ts 89 | globals: [ 90 | { 91 | slug: 'Homepage', 92 | tabs: [ 93 | { 94 | name: 'Spongebob', 95 | label: 'Spongebob', 96 | path: '/spongebob', 97 | src: 'https://www.youtube.com/embed/hzond0fF4MM', 98 | iframeProps: { 99 | height: 1080, 100 | width: 1920, 101 | frameBorder: '0', 102 | style: { aspectRatio: '19/10', maxWidth: '100%', height: 'auto' }, 103 | }, 104 | }, 105 | ], 106 | }, 107 | ], 108 | ``` 109 | 110 | ### Code 111 | 112 | By providing code instead we will **dangerously** render any code you provide here. 113 | **Use with caution.** 114 | 115 | - `code` | required | Full markup in string format to **dangerously** render 116 | 117 | ```ts 118 | { 119 | name: 'FigmaDesign', 120 | label: 'Figma Design', 121 | path: '/design', 122 | code: ``, 123 | }, 124 | ``` 125 | 126 | ### Dynamic 127 | 128 | Dynamic mode is similar to src mode except that we can fetch your URL from a field value instead. 129 | 130 | - `watchField` | required | Field path to watch for updates 131 | 132 | - `iframeProps` | optional | HTML props for the Iframe element 133 | 134 | ```ts 135 | { 136 | name: 'Dynamic', 137 | label: 'Dynamic', 138 | path: '/dynamic', 139 | watchField: 'source', 140 | iframeProps: { 141 | height: 1080, 142 | width: 1920, 143 | frameBorder: '0', 144 | style: { aspectRatio: '19/10', maxWidth: '100%', height: 'auto' }, 145 | }, 146 | }, 147 | ``` 148 | 149 | ## Contributing 150 | 151 | For development purposes, there is a full working example of how this plugin might be used in the [dev](./dev) directory of this repo. 152 | 153 | ```bash 154 | git clone git@github.com:NouanceLabs/payload-iframe-tabs-plugin.git \ 155 | cd payload-iframe-tabs-plugin && yarn \ 156 | cd dev && yarn \ 157 | cp .env.example .env \ 158 | vim .env \ # add your payload details 159 | yarn dev 160 | ``` 161 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Config, Plugin } from 'payload/config' 2 | 3 | import type { PluginTypes } from './types' 4 | import { extendWebpackConfig } from './webpack' 5 | import IframeComponent from './components/IframeComponent' 6 | import type { CollectionConfig, GlobalConfig } from 'payload/dist/exports/types' 7 | 8 | export const iframeTabsPlugin = 9 | (pluginOptions: PluginTypes): Plugin => 10 | incomingConfig => { 11 | let config = { ...incomingConfig } 12 | const { collections, globals } = config 13 | 14 | const webpack = extendWebpackConfig(incomingConfig) 15 | 16 | config.admin = { 17 | ...(config.admin || {}), 18 | webpack, 19 | } 20 | 21 | if (pluginOptions.enabled === false) { 22 | return config 23 | } 24 | 25 | const processedConfig: Config = { 26 | ...config, 27 | collections: [ 28 | ...(collections 29 | ? collections.map(collection => { 30 | const targetCollection = pluginOptions.collections?.find(pluginCollection => { 31 | if (pluginCollection.slug === collection.slug) return true 32 | return false 33 | }) 34 | 35 | if (targetCollection) { 36 | const tabs = {} 37 | const customProps = {} 38 | 39 | targetCollection.tabs.forEach(tab => { 40 | Object.assign(tabs, { 41 | [tab.name]: { 42 | Component: IframeComponent, 43 | path: tab.path, 44 | Tab: { 45 | label: tab.label, 46 | href: tab.path, 47 | }, 48 | }, 49 | }) 50 | 51 | Object.assign(customProps, { 52 | [tab.path]: { 53 | ...tab, 54 | }, 55 | }) 56 | }) 57 | 58 | const collectionConfigWithHooks: CollectionConfig = { 59 | ...collection, 60 | admin: { 61 | ...collection.admin, 62 | components: { 63 | ...collection.admin?.components, 64 | views: { 65 | ...collection.admin?.components?.views, 66 | // @ts-expect-error 67 | Edit: { 68 | ...collection.admin?.components?.views?.Edit, 69 | ...tabs, 70 | }, 71 | }, 72 | }, 73 | }, 74 | custom: { 75 | ...collection.custom, 76 | iframesTabPlugin: { 77 | ...customProps, 78 | }, 79 | }, 80 | } 81 | 82 | return collectionConfigWithHooks 83 | } 84 | 85 | return collection 86 | }) 87 | : []), 88 | ], 89 | 90 | globals: [ 91 | ...(globals 92 | ? globals.map(global => { 93 | const targetGlobal = pluginOptions.globals?.find(pluginGlobal => { 94 | if (pluginGlobal.slug === global.slug) return true 95 | return false 96 | }) 97 | 98 | if (targetGlobal) { 99 | const tabs = {} 100 | const customProps = {} 101 | 102 | targetGlobal.tabs.forEach(tab => { 103 | Object.assign(tabs, { 104 | [tab.name]: { 105 | Component: IframeComponent, 106 | path: tab.path, 107 | Tab: { 108 | label: tab.label, 109 | href: tab.path, 110 | }, 111 | }, 112 | }) 113 | 114 | Object.assign(customProps, { 115 | [tab.path]: { 116 | ...tab, 117 | }, 118 | }) 119 | }) 120 | 121 | const globalConfigWithHooks: GlobalConfig = { 122 | ...global, 123 | admin: { 124 | ...global.admin, 125 | components: { 126 | ...global.admin?.components, 127 | views: { 128 | ...global.admin?.components?.views, 129 | // @ts-expect-error 130 | Edit: { 131 | ...global.admin?.components?.views?.Edit, 132 | ...tabs, 133 | }, 134 | }, 135 | }, 136 | }, 137 | custom: { 138 | ...global.custom, 139 | iframesTabPlugin: { 140 | ...customProps, 141 | }, 142 | }, 143 | } 144 | 145 | return globalConfigWithHooks 146 | } 147 | 148 | return global 149 | }) 150 | : []), 151 | ], 152 | } 153 | 154 | return processedConfig 155 | } 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dev/tmp 2 | dev/yarn.lock 3 | 4 | # Created by https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode 5 | 6 | ### macOS ### 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | ### Node ### 31 | # Logs 32 | logs 33 | *.log 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | 38 | # Runtime data 39 | pids 40 | *.pid 41 | *.seed 42 | *.pid.lock 43 | 44 | # Directory for instrumented libs generated by jscoverage/JSCover 45 | lib-cov 46 | 47 | # Coverage directory used by tools like istanbul 48 | coverage 49 | 50 | # nyc test coverage 51 | .nyc_output 52 | 53 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 54 | .grunt 55 | 56 | # Bower dependency directory (https://bower.io/) 57 | bower_components 58 | 59 | # node-waf configuration 60 | .lock-wscript 61 | 62 | # Compiled binary addons (http://nodejs.org/api/addons.html) 63 | build/Release 64 | 65 | # Dependency directories 66 | node_modules/ 67 | jspm_packages/ 68 | 69 | # Typescript v1 declaration files 70 | typings/ 71 | 72 | # Optional npm cache directory 73 | .npm 74 | 75 | # Optional eslint cache 76 | .eslintcache 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # Yarn Berry 88 | .yarn/* 89 | !.yarn/patches 90 | !.yarn/plugins 91 | !.yarn/releases 92 | !.yarn/sdks 93 | !.yarn/versions 94 | .pnp.* 95 | 96 | # dotenv environment variables file 97 | .env 98 | 99 | 100 | ### SublimeText ### 101 | # cache files for sublime text 102 | *.tmlanguage.cache 103 | *.tmPreferences.cache 104 | *.stTheme.cache 105 | 106 | # workspace files are user-specific 107 | *.sublime-workspace 108 | 109 | # project files should be checked into the repository, unless a significant 110 | # proportion of contributors will probably not be using SublimeText 111 | # *.sublime-project 112 | 113 | # sftp configuration file 114 | sftp-config.json 115 | 116 | # Package control specific files 117 | Package Control.last-run 118 | Package Control.ca-list 119 | Package Control.ca-bundle 120 | Package Control.system-ca-bundle 121 | Package Control.cache/ 122 | Package Control.ca-certs/ 123 | Package Control.merged-ca-bundle 124 | Package Control.user-ca-bundle 125 | oscrypto-ca-bundle.crt 126 | bh_unicode_properties.cache 127 | 128 | # Sublime-github package stores a github token in this file 129 | # https://packagecontrol.io/packages/sublime-github 130 | GitHub.sublime-settings 131 | 132 | ### VisualStudioCode ### 133 | .vscode/* 134 | !.vscode/tasks.json 135 | !.vscode/launch.json 136 | !.vscode/extensions.json 137 | .history 138 | 139 | ### WebStorm ### 140 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 141 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 142 | 143 | .idea/* 144 | # User-specific stuff: 145 | .idea/**/workspace.xml 146 | .idea/**/tasks.xml 147 | .idea/dictionaries 148 | 149 | # Sensitive or high-churn files: 150 | .idea/**/dataSources/ 151 | .idea/**/dataSources.ids 152 | .idea/**/dataSources.xml 153 | .idea/**/dataSources.local.xml 154 | .idea/**/sqlDataSources.xml 155 | .idea/**/dynamic.xml 156 | .idea/**/uiDesigner.xml 157 | 158 | # Gradle: 159 | .idea/**/gradle.xml 160 | .idea/**/libraries 161 | 162 | # CMake 163 | cmake-build-debug/ 164 | 165 | # Mongo Explorer plugin: 166 | .idea/**/mongoSettings.xml 167 | 168 | ## File-based project format: 169 | *.iws 170 | 171 | ## Plugin-specific files: 172 | 173 | # IntelliJ 174 | /out/ 175 | 176 | # mpeltonen/sbt-idea plugin 177 | .idea_modules/ 178 | 179 | # JIRA plugin 180 | atlassian-ide-plugin.xml 181 | 182 | # Cursive Clojure plugin 183 | .idea/replstate.xml 184 | 185 | # Ruby plugin and RubyMine 186 | /.rakeTasks 187 | 188 | # Crashlytics plugin (for Android Studio and IntelliJ) 189 | com_crashlytics_export_strings.xml 190 | crashlytics.properties 191 | crashlytics-build.properties 192 | fabric.properties 193 | 194 | ### WebStorm Patch ### 195 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 196 | 197 | # *.iml 198 | # modules.xml 199 | # .idea/misc.xml 200 | # *.ipr 201 | 202 | # Sonarlint plugin 203 | .idea/sonarlint 204 | 205 | ### Windows ### 206 | # Windows thumbnail cache files 207 | Thumbs.db 208 | ehthumbs.db 209 | ehthumbs_vista.db 210 | 211 | # Folder config file 212 | Desktop.ini 213 | 214 | # Recycle Bin used on file shares 215 | $RECYCLE.BIN/ 216 | 217 | # Windows Installer files 218 | *.cab 219 | *.msi 220 | *.msm 221 | *.msp 222 | 223 | # Windows shortcuts 224 | *.lnk 225 | 226 | # End of https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode 227 | 228 | # Ignore all uploads 229 | demo/upload 230 | demo/media 231 | demo/files 232 | 233 | # Ignore build folder 234 | build 235 | 236 | # Ignore built components 237 | components/index.js 238 | components/styles.css 239 | 240 | # Ignore generated 241 | dev/generated-types.ts 242 | dev/generated-schema.graphql 243 | 244 | # Ignore dist, no need for git 245 | dist 246 | 247 | dev/src/uploads 248 | -------------------------------------------------------------------------------- /eslint-config/rules/typescript.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['@typescript-eslint'], 3 | overrides: [ 4 | { 5 | files: ['**/**.ts', '**/**.d.ts'], 6 | rules: { 7 | 'no-undef': 'off', 8 | camelcase: 'off', 9 | '@typescript-eslint/adjacent-overload-signatures': 'error', 10 | /** 11 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md 12 | */ 13 | '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], 14 | /** 15 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/await-thenable.md 16 | */ 17 | '@typescript-eslint/await-thenable': 'off', 18 | /** 19 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md 20 | */ 21 | '@typescript-eslint/consistent-type-assertions': [ 22 | 'error', 23 | { assertionStyle: 'as', objectLiteralTypeAssertions: 'allow-as-parameter' }, 24 | ], 25 | /** 26 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-definitions.md 27 | */ 28 | '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], 29 | /** 30 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-imports.md 31 | */ 32 | '@typescript-eslint/consistent-type-imports': 'warn', 33 | /** 34 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-function-return-type.md 35 | */ 36 | '@typescript-eslint/explicit-function-return-type': [ 37 | 'error', 38 | { 39 | // TODO: come back and check if we need those 40 | allowExpressions: true, 41 | allowTypedFunctionExpressions: true, 42 | allowHigherOrderFunctions: true, 43 | allowConciseArrowFunctionExpressionsStartingWithVoid: false, 44 | }, 45 | ], 46 | /** 47 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md 48 | */ 49 | '@typescript-eslint/explicit-member-accessibility': [ 50 | 'error', 51 | { accessibility: 'no-public' }, 52 | ], 53 | /** 54 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md 55 | */ 56 | '@typescript-eslint/member-delimiter-style': [ 57 | 'error', 58 | { 59 | multiline: { 60 | delimiter: 'none', 61 | requireLast: true, 62 | }, 63 | singleline: { 64 | delimiter: 'semi', 65 | requireLast: false, 66 | }, 67 | }, 68 | ], 69 | /** 70 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/method-signature-style.md 71 | */ 72 | '@typescript-eslint/method-signature-style': 'off', 73 | /** 74 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md 75 | */ 76 | '@typescript-eslint/naming-convention': [ 77 | 'off', 78 | { 79 | selector: 'default', 80 | format: ['camelCase'], 81 | leadingUnderscore: 'forbid', 82 | trailingUnderscore: 'forbid', 83 | }, 84 | { 85 | selector: 'variable', 86 | format: ['camelCase', 'UPPER_CASE'], 87 | leadingUnderscore: 'forbid', 88 | trailingUnderscore: 'forbid', 89 | }, 90 | // Enforce that type parameters (generics) are prefixed with T or U 91 | { 92 | selector: 'typeParameter', 93 | format: ['PascalCase'], 94 | prefix: ['T', 'U'], 95 | }, 96 | // enforce boolean variables to start with proper prefix. 97 | { 98 | selector: 'variable', 99 | types: ['boolean'], 100 | format: ['PascalCase'], 101 | prefix: ['is', 'should', 'has', 'can', 'did', 'will'], 102 | }, 103 | // Enforce that interface names do not begin with an I 104 | { 105 | selector: 'interface', 106 | format: ['PascalCase'], 107 | custom: { 108 | regex: '^I[A-Z]', 109 | match: false, 110 | }, 111 | }, 112 | { 113 | selector: [ 114 | 'function', 115 | 'parameter', 116 | 'property', 117 | 'parameterProperty', 118 | 'method', 119 | 'accessor', 120 | ], 121 | format: ['camelCase'], 122 | leadingUnderscore: 'forbid', 123 | trailingUnderscore: 'forbid', 124 | }, 125 | { 126 | selector: ['class', 'interface', 'typeAlias', 'enum', 'typeParameter'], 127 | format: ['PascalCase'], 128 | leadingUnderscore: 'forbid', 129 | trailingUnderscore: 'forbid', 130 | }, 131 | ], 132 | /** 133 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-base-to-string.md 134 | */ 135 | '@typescript-eslint/no-base-to-string': 'off', 136 | /** 137 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md 138 | */ 139 | '@typescript-eslint/no-confusing-non-null-assertion': 'error', 140 | /** 141 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-dynamic-delete.md 142 | */ 143 | '@typescript-eslint/no-dynamic-delete': 'error', 144 | /** 145 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-interface.md 146 | */ 147 | '@typescript-eslint/no-empty-interface': 'off', 148 | /** 149 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md 150 | */ 151 | '@typescript-eslint/no-explicit-any': [ 152 | 'warn', 153 | { 154 | ignoreRestArgs: true, 155 | // enable later 156 | fixToUnknown: false, 157 | }, 158 | ], 159 | /** 160 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.md 161 | */ 162 | '@typescript-eslint/no-extra-non-null-assertion': 'error', 163 | /** 164 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extraneous-class.md 165 | */ 166 | '@typescript-eslint/no-extraneous-class': [ 167 | 'error', 168 | { 169 | allowConstructorOnly: false, 170 | allowEmpty: false, 171 | allowStaticOnly: false, 172 | allowWithDecorator: false, 173 | }, 174 | ], 175 | /** 176 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-floating-promises.md 177 | */ 178 | '@typescript-eslint/no-floating-promises': 'off', 179 | /** 180 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md 181 | */ 182 | '@typescript-eslint/no-for-in-array': 'off', 183 | /** 184 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-implicit-any-catch.md 185 | */ 186 | '@typescript-eslint/no-implicit-any-catch': [ 187 | 'error', 188 | { 189 | allowExplicitAny: false, 190 | }, 191 | ], 192 | /** 193 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-implied-eval.md 194 | */ 195 | '@typescript-eslint/no-implied-eval': 'off', 196 | /** 197 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-inferrable-types.md 198 | */ 199 | '@typescript-eslint/no-inferrable-types': [ 200 | 'error', 201 | { 202 | ignoreParameters: false, 203 | ignoreProperties: false, 204 | }, 205 | ], 206 | /** 207 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-void-type.md 208 | */ 209 | '@typescript-eslint/no-invalid-void-type': [ 210 | 'off', 211 | { 212 | allowInGenericTypeArguments: true, 213 | }, 214 | ], 215 | /** 216 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-new.md 217 | */ 218 | '@typescript-eslint/no-misused-new': 'error', 219 | /** 220 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-promises.md 221 | */ 222 | '@typescript-eslint/no-misused-promises': 'off', 223 | /** 224 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-namespace.md 225 | */ 226 | '@typescript-eslint/no-namespace': 'off', 227 | /** 228 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md 229 | */ 230 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', 231 | /** 232 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md 233 | */ 234 | '@typescript-eslint/no-non-null-assertion': 'warn', 235 | /** 236 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-parameter-properties.md 237 | */ 238 | '@typescript-eslint/no-parameter-properties': 'error', 239 | /** 240 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-require-imports.md 241 | */ 242 | '@typescript-eslint/no-require-imports': 'error', 243 | /** 244 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-this-alias.md 245 | */ 246 | '@typescript-eslint/no-this-alias': 'error', 247 | /** 248 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-throw-literal.md 249 | */ 250 | '@typescript-eslint/no-throw-literal': 'off', 251 | /** 252 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-type-alias.md 253 | */ 254 | '@typescript-eslint/no-type-alias': [ 255 | 'off', 256 | { 257 | allowAliases: 'always', 258 | allowCallbacks: 'always', 259 | allowConditionalTypes: 'always', 260 | allowConstructors: 'never', 261 | allowLiterals: 'in-unions-and-intersections', 262 | allowMappedTypes: 'always', 263 | allowTupleTypes: 'always', 264 | }, 265 | ], 266 | /** 267 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md 268 | */ 269 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off', 270 | /** 271 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md 272 | */ 273 | '@typescript-eslint/no-unnecessary-condition': 'off', 274 | /** 275 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md 276 | */ 277 | '@typescript-eslint/no-unnecessary-qualifier': 'off', 278 | /** 279 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md 280 | */ 281 | '@typescript-eslint/no-unnecessary-type-arguments': 'off', 282 | /** 283 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md 284 | */ 285 | '@typescript-eslint/no-unnecessary-type-assertion': 'off', 286 | /** 287 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md 288 | */ 289 | '@typescript-eslint/no-unsafe-assignment': 'off', 290 | /** 291 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-call.md 292 | */ 293 | '@typescript-eslint/no-unsafe-call': 'off', 294 | /** 295 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md 296 | */ 297 | '@typescript-eslint/no-unsafe-member-access': 'off', 298 | /** 299 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-return.md 300 | */ 301 | '@typescript-eslint/no-unsafe-return': 'off', 302 | /** 303 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-var-requires.md 304 | */ 305 | '@typescript-eslint/no-var-requires': 'error', 306 | /** 307 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-as-const.md 308 | */ 309 | '@typescript-eslint/prefer-as-const': 'error', 310 | /** 311 | * We don't care about enums having implicit values. 312 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-enum-initializers.md 313 | */ 314 | '@typescript-eslint/prefer-enum-initializers': 'off', 315 | /** 316 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-for-of.md 317 | */ 318 | '@typescript-eslint/prefer-for-of': 'error', 319 | /** 320 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-includes.md 321 | */ 322 | '@typescript-eslint/prefer-includes': 'off', 323 | /** 324 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md 325 | */ 326 | '@typescript-eslint/prefer-literal-enum-member': 'error', 327 | /** 328 | * using ES2015 syntax so this rule can be safetly turned off 329 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.md 330 | */ 331 | '@typescript-eslint/prefer-namespace-keyword': 'off', 332 | /** 333 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md 334 | */ 335 | '@typescript-eslint/prefer-nullish-coalescing': 'off', 336 | /** 337 | * only set to warn because there are some cases this behavior doesnt work because 338 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-optional-chain.md 339 | */ 340 | '@typescript-eslint/prefer-optional-chain': 'warn', 341 | /** 342 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-readonly.md 343 | */ 344 | '@typescript-eslint/prefer-readonly': 'off', 345 | /** 346 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md 347 | */ 348 | '@typescript-eslint/prefer-readonly-parameter-types': 'off', 349 | /** 350 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md 351 | */ 352 | '@typescript-eslint/prefer-reduce-type-parameter': 'off', 353 | /** 354 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md 355 | */ 356 | '@typescript-eslint/prefer-regexp-exec': 'off', 357 | /** 358 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md 359 | */ 360 | '@typescript-eslint/prefer-string-starts-ends-with': 'off', 361 | /** 362 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md 363 | */ 364 | '@typescript-eslint/prefer-ts-expect-error': 'warn', 365 | /** 366 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md 367 | */ 368 | '@typescript-eslint/promise-function-async': 'off', 369 | /** 370 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/require-array-sort-compare.md 371 | */ 372 | '@typescript-eslint/require-array-sort-compare': 'off', 373 | /** 374 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-plus-operands.md 375 | */ 376 | '@typescript-eslint/restrict-plus-operands': 'off', 377 | /** 378 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-template-expressions.md 379 | */ 380 | '@typescript-eslint/restrict-template-expressions': 'off', 381 | /** 382 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md 383 | */ 384 | '@typescript-eslint/strict-boolean-expressions': 'off', 385 | /** 386 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md 387 | */ 388 | '@typescript-eslint/switch-exhaustiveness-check': 'off', 389 | /** 390 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/triple-slash-reference.md 391 | */ 392 | '@typescript-eslint/triple-slash-reference': 'error', 393 | /** 394 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/type-annotation-spacing.md 395 | */ 396 | '@typescript-eslint/type-annotation-spacing': [ 397 | 'error', 398 | { 399 | before: false, 400 | after: true, 401 | overrides: { 402 | arrow: { 403 | before: true, 404 | after: true, 405 | }, 406 | }, 407 | }, 408 | ], 409 | /** 410 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/typedef.md 411 | */ 412 | '@typescript-eslint/typedef': [ 413 | 'error', 414 | { 415 | arrayDestructuring: false, 416 | arrowParameter: false, 417 | memberVariableDeclaration: false, 418 | objectDestructuring: false, 419 | parameter: false, 420 | propertyDeclaration: true, 421 | variableDeclaration: false, 422 | variableDeclarationIgnoreFunction: false, 423 | }, 424 | ], 425 | /** 426 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unbound-method.md 427 | */ 428 | '@typescript-eslint/unbound-method': 'off', 429 | /** 430 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unified-signatures.md 431 | */ 432 | '@typescript-eslint/unified-signatures': 'off', 433 | 434 | // @typescript-eslint Extension Rules 435 | // ================================================================================== 436 | /** 437 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/brace-style.md 438 | */ 439 | 'brace-style': 'off', 440 | '@typescript-eslint/brace-style': 'error', 441 | /** 442 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/comma-spacing.md 443 | */ 444 | 'comma-spacing': 'off', 445 | '@typescript-eslint/comma-spacing': 'error', 446 | /** 447 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/default-param-last.md 448 | */ 449 | 'default-param-last': 'off', 450 | '@typescript-eslint/default-param-last': 'error', 451 | /** 452 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/dot-notation.md 453 | */ 454 | 'dot-notation': 'error', 455 | '@typescript-eslint/dot-notation': 'off', 456 | /** 457 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/func-call-spacing.md 458 | */ 459 | 'func-call-spacing': 'off', 460 | '@typescript-eslint/func-call-spacing': 'error', 461 | /** 462 | * use prettier instead 463 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md 464 | */ 465 | indent: 'off', 466 | '@typescript-eslint/indent': 'off', 467 | /** 468 | * Allow a mix between the two 469 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/init-declarations.md 470 | */ 471 | '@typescript-eslint/init-declarations': 'off', 472 | /** 473 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/keyword-spacing.md 474 | */ 475 | 'keyword-spacing': 'off', 476 | '@typescript-eslint/keyword-spacing': 'error', 477 | /** 478 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/lines-between-class-members.md 479 | */ 480 | 'lines-between-class-members': 'off', 481 | '@typescript-eslint/lines-between-class-members': [ 482 | 'error', 483 | 'always', 484 | { 485 | // base eslint config 486 | exceptAfterSingleLine: true, 487 | // typescript specific 488 | exceptAfterOverload: true, 489 | }, 490 | ], 491 | /** 492 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md 493 | */ 494 | 'no-array-constructor': 'off', 495 | '@typescript-eslint/no-array-constructor': 'error', 496 | /** 497 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-dupe-class-members.md 498 | */ 499 | 'no-dupe-class-members': 'off', 500 | '@typescript-eslint/no-dupe-class-members': 'error', 501 | /** 502 | * Use prettier 503 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-parens.md 504 | */ 505 | 'no-extra-parens': 'off', 506 | '@typescript-eslint/no-extra-parens': 'off', 507 | /** 508 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-semi.md 509 | */ 510 | 'no-extra-semi': 'off', 511 | '@typescript-eslint/no-extra-semi': 'error', 512 | /** 513 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-this.md 514 | */ 515 | 'no-invalid-this': 'off', 516 | '@typescript-eslint/no-invalid-this': 'error', 517 | /** 518 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-loss-of-precision.md 519 | */ 520 | 'no-loss-of-precision': 'off', 521 | '@typescript-eslint/no-loss-of-precision': 'error', 522 | /** 523 | * https://eslint.org/docs/rules/no-magic-numbers 524 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-magic-numbers.md 525 | */ 526 | 'no-magic-numbers': 'off', 527 | '@typescript-eslint/no-magic-numbers': [ 528 | 'off', 529 | { 530 | // base eslint configs 531 | ignoreArrayIndexes: true, 532 | ignoreDefaultValues: true, 533 | enforceConst: true, 534 | // typescript specific configs 535 | ignoreEnums: true, 536 | ignoreNumericLiteralTypes: true, 537 | ignoreReadonlyClassProperties: true, 538 | }, 539 | ], 540 | /** 541 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-redeclare.md 542 | */ 543 | 'no-redeclare': 'off', 544 | '@typescript-eslint/no-redeclare': [ 545 | 'error', 546 | { 547 | // prevents variables from being created with global variable naming 548 | builtinGlobals: true, 549 | }, 550 | ], 551 | /** 552 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md 553 | */ 554 | 'no-shadow': 'off', 555 | '@typescript-eslint/no-shadow': [ 556 | 'error', 557 | { 558 | // No variables + types with same naming 559 | ignoreTypeValueShadow: false, 560 | }, 561 | ], 562 | /** 563 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md 564 | */ 565 | 'no-unused-expressions': 'off', 566 | '@typescript-eslint/no-unused-expressions': 'error', 567 | /** 568 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md 569 | */ 570 | 'no-unused-vars': 'off', 571 | '@typescript-eslint/no-unused-vars': [ 572 | 'error', 573 | { 574 | ignoreRestSiblings: true, 575 | }, 576 | ], 577 | /** 578 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md 579 | */ 580 | 'no-use-before-define': 'off', 581 | '@typescript-eslint/no-use-before-define': 'off', 582 | /** 583 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md 584 | */ 585 | 'no-useless-constructor': 'off', 586 | '@typescript-eslint/no-useless-constructor': 'error', 587 | /** 588 | * https://eslint.org/docs/rules/quotes 589 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/quotes.md 590 | */ 591 | quotes: 'off', 592 | '@typescript-eslint/quotes': [ 593 | 'error', 594 | 'single', 595 | { 596 | avoidEscape: true, 597 | allowTemplateLiterals: true, 598 | }, 599 | ], 600 | /** 601 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/require-await.md 602 | */ 603 | '@typescript-eslint/require-await': 'off', 604 | /** 605 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/return-await.md 606 | */ 607 | '@typescript-eslint/return-await': 'off', 608 | /** 609 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md 610 | */ 611 | semi: 'off', 612 | '@typescript-eslint/semi': ['error', 'never'], 613 | /** 614 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/space-before-function-paren.md 615 | */ 616 | 'space-before-function-paren': 'off', 617 | '@typescript-eslint/space-before-function-paren': [ 618 | 'error', 619 | { 620 | anonymous: 'never', 621 | named: 'never', 622 | asyncArrow: 'always', 623 | }, 624 | ], 625 | }, 626 | }, 627 | ], 628 | } 629 | --------------------------------------------------------------------------------