diff --git a/config/plugins.ts b/config/plugins.ts index 32b756f..21c4aae 100644 --- a/config/plugins.ts +++ b/config/plugins.ts @@ -5,6 +5,9 @@ export default ({ env }) => { resolve: './src/plugins/strapi-plugin-internal-links', config: { environment: 'test', + pageBuilder: { + enabled: true + }, domains: { default: { test: 'https://webbio.nl', diff --git a/src/api/page/content-types/page/schema.json b/src/api/page/content-types/page/schema.json index 75f7e3b..ac48669 100644 --- a/src/api/page/content-types/page/schema.json +++ b/src/api/page/content-types/page/schema.json @@ -1,75 +1,78 @@ { - "kind": "collectionType", - "collectionName": "pages", - "info": { - "singularName": "page", - "pluralName": "pages", - "displayName": "Pagina's", - "description": "" - }, - "options": { - "draftAndPublish": true - }, - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "attributes": { - "title": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string", - "required": true - }, - "path": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "string", - "required": false - }, - "parent": { - "type": "relation", - "relation": "oneToOne", - "target": "api::page.page" - }, - "excerpt": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "text" - }, - "modules": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "dynamiczone", - "components": [ - "modules.text" - ] - }, - "link": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "customField", - "options": { - "title": "title", - "slug": "path" - }, - "customField": "plugin::internal-links.internal-link" - } - } + "kind": "collectionType", + "collectionName": "pages", + "info": { + "singularName": "page", + "pluralName": "pages", + "displayName": "Pagina's", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": { + "i18n": { + "localized": true + }, + "internal-links": { + "title": "title", + "slug": "path" + } + }, + "attributes": { + "title": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "string", + "required": true + }, + "path": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "string", + "required": false + }, + "parent": { + "type": "relation", + "relation": "oneToOne", + "target": "api::page.page" + }, + "excerpt": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "text" + }, + "modules": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "dynamiczone", + "components": ["modules.text"] + }, + "link": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "customField", + "customField": "plugin::internal-links.internal-link" + }, + "platform": { + "type": "relation", + "relation": "oneToOne", + "target": "api::platform.platform" + } + } } diff --git a/src/api/platform/content-types/platform/schema.json b/src/api/platform/content-types/platform/schema.json new file mode 100644 index 0000000..51a7f72 --- /dev/null +++ b/src/api/platform/content-types/platform/schema.json @@ -0,0 +1,32 @@ +{ + "kind": "collectionType", + "collectionName": "platforms", + "info": { + "singularName": "platform", + "pluralName": "platforms", + "displayName": "Platform" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "attributes": { + "title": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "string", + "required": true, + "unique": true + }, + "domain": { + "type": "string" + } + } +} diff --git a/src/api/platform/controllers/platform.ts b/src/api/platform/controllers/platform.ts new file mode 100644 index 0000000..401ee96 --- /dev/null +++ b/src/api/platform/controllers/platform.ts @@ -0,0 +1,7 @@ +/** + * platform controller + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreController('api::platform.platform'); diff --git a/src/api/platform/routes/platform.ts b/src/api/platform/routes/platform.ts new file mode 100644 index 0000000..525b472 --- /dev/null +++ b/src/api/platform/routes/platform.ts @@ -0,0 +1,7 @@ +/** + * platform router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::platform.platform'); diff --git a/src/api/platform/services/platform.ts b/src/api/platform/services/platform.ts new file mode 100644 index 0000000..3cd54c1 --- /dev/null +++ b/src/api/platform/services/platform.ts @@ -0,0 +1,7 @@ +/** + * platform service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::platform.platform'); diff --git a/src/api/post/content-types/post/schema.json b/src/api/post/content-types/post/schema.json new file mode 100644 index 0000000..bc2eedc --- /dev/null +++ b/src/api/post/content-types/post/schema.json @@ -0,0 +1,27 @@ +{ + "kind": "collectionType", + "collectionName": "posts", + "info": { + "singularName": "post", + "pluralName": "posts", + "displayName": "Post" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "attributes": { + "title": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "string" + } + } +} diff --git a/src/api/post/controllers/post.ts b/src/api/post/controllers/post.ts new file mode 100644 index 0000000..21d58d9 --- /dev/null +++ b/src/api/post/controllers/post.ts @@ -0,0 +1,7 @@ +/** + * post controller + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreController('api::post.post'); diff --git a/src/api/post/routes/post.ts b/src/api/post/routes/post.ts new file mode 100644 index 0000000..ee3c015 --- /dev/null +++ b/src/api/post/routes/post.ts @@ -0,0 +1,7 @@ +/** + * post router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::post.post'); diff --git a/src/api/post/services/post.ts b/src/api/post/services/post.ts new file mode 100644 index 0000000..159ed7f --- /dev/null +++ b/src/api/post/services/post.ts @@ -0,0 +1,7 @@ +/** + * post service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::post.post'); diff --git a/src/plugins/strapi-plugin-internal-links/README.md b/src/plugins/strapi-plugin-internal-links/README.md index b2c4024..9db73f0 100644 --- a/src/plugins/strapi-plugin-internal-links/README.md +++ b/src/plugins/strapi-plugin-internal-links/README.md @@ -29,10 +29,22 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nulla ex, ma ```js module.exports = ({ env }) => ({ // ... - internal-links: { + 'internal-links': { enabled: true, config: { - // ... + environment: 'test', + pageBuilder: { + enabled: true, // When enabled, pageBuilder plugin logic is applied. + pageUid: 'undefined | string', + pathField: 'undefined | string', + platformUid: 'undefined |string' + }, + domains: { + default: { + test: 'https://webbio.nl', + production: 'https://webbio.nl' + } + } } } // ... diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/components/factory/index.ts b/src/plugins/strapi-plugin-internal-links/admin/src/components/factory/index.ts index e217c36..35d85f0 100644 --- a/src/plugins/strapi-plugin-internal-links/admin/src/components/factory/index.ts +++ b/src/plugins/strapi-plugin-internal-links/admin/src/components/factory/index.ts @@ -31,7 +31,7 @@ export const createInternalLink = ( url: '', text: initialText || '', type: INTERNAL_LINK_TYPE.INTERNAL, - domain: 'https://gelderland.nl' + domain: '' }); export default createInternalLink; diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-content-type-options.ts b/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-content-type-options.ts index 71b0816..72a3f5d 100644 --- a/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-content-type-options.ts +++ b/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-content-type-options.ts @@ -1,6 +1,8 @@ import { useState } from 'react'; import { useQuery } from 'react-query'; -import axios from '../../../utils/axiosInstance'; + +import { useFetchClient } from '@strapi/helper-plugin'; + import pluginId from '../../../plugin-id'; export interface IContentTypeOption { @@ -13,8 +15,9 @@ export interface IContentTypeOption { domain?: string; } -const fetchContentTypeOptions = async (): Promise => { - const { data }: { data: IContentTypeOption[] } = await axios.get(`/${pluginId}/content-types`); +const fetchContentTypeOptions = async (fetchClient: any): Promise => { + const { get } = fetchClient; + const { data }: { data: IContentTypeOption[] } = await get(`/${pluginId}/content-types`); const options = data.map((item) => ({ ...item, @@ -27,7 +30,10 @@ const fetchContentTypeOptions = async (): Promise => { }; export const useContentTypeOptions = (initialUid?: string) => { - const { data, status, isLoading, isFetching, isError } = useQuery(['content-type-options'], fetchContentTypeOptions); + const fetchClient = useFetchClient(); + const { data, status, isLoading, isFetching, isError } = useQuery(['content-type-options'], () => + fetchContentTypeOptions(fetchClient) + ); const [contentTypeUid, setContentTypeUid] = useState(initialUid); const contentType = data?.find((item) => item.uid === (contentTypeUid || initialUid)); diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-page-options.ts b/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-page-options.ts index 16168e8..62696eb 100644 --- a/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-page-options.ts +++ b/src/plugins/strapi-plugin-internal-links/admin/src/components/form/hooks/use-page-options.ts @@ -1,9 +1,12 @@ import { useState } from 'react'; -import { IContentTypeOption } from './use-content-type-options'; - import { useQuery } from 'react-query'; -import axios from '../../../utils/axiosInstance'; +import trim from 'lodash/trim'; + +import { useFetchClient } from '@strapi/helper-plugin'; + +import { IContentTypeOption } from './use-content-type-options'; import pluginId from '../../../plugin-id'; +import { useGetDefaultStrapiLocale } from '../../../utils/use-get-default-locale'; export interface IPageOption { id: number; @@ -12,25 +15,30 @@ export interface IPageOption { slugLabel: string; showIndicator: boolean; locale: string; + platform?: { domain?: string }; } // Single- and collection type data not typed in Strapi -const mapPageData = (data: any, contentType: IContentTypeOption): IPageOption[] => { +const mapPageData = ( + data: Record, + contentType: IContentTypeOption, + defaultLocale: string +): IPageOption[] => { const options = data.map((item: any) => { const titlePath = contentType.titleField.split('.'); - const locale = item.locale && item.locale !== 'nl' ? `${item.locale}/` : ''; + const locale = item.locale && item.locale !== defaultLocale ? `${item.locale}` : ''; const label = titlePath.length < 2 ? item[contentType.titleField] - : titlePath.reduce((previousValue, currentValue) => { - return previousValue[currentValue]; - }, item); + : titlePath.reduce((previousValue, currentValue) => previousValue[currentValue], item); + + const slugField = item?.[contentType?.slugField] === '/' ? '' : item[contentType?.slugField] || ''; return { ...item, label, value: String(item.id), - slugLabel: contentType?.slugField ? `${locale}${item[contentType?.slugField] || ''}` : '', + slugLabel: contentType?.slugField ? trim(`${locale}/${slugField}`, '/') : '', showIndicator: true }; }); @@ -38,18 +46,25 @@ const mapPageData = (data: any, contentType: IContentTypeOption): IPageOption[] return options; }; -const fetchPageOptions = async (contentType?: IContentTypeOption): Promise => { +const fetchPageOptions = async ( + defaultLocale: string, + fetchClient?: any, + contentType?: IContentTypeOption +): Promise => { if (!contentType) return []; const type = contentType.kind === 'singleType' ? 'single-type' : 'collection-type'; - const { data } = await axios.get(`/${pluginId}/${type}/${contentType.uid}`); + const { data } = await fetchClient.get(`/${pluginId}/${type}/${contentType.uid}`); - return mapPageData(data, contentType); + return mapPageData(data, contentType, defaultLocale); }; export const usePageOptions = (contentType?: IContentTypeOption, initialId?: string | number | null) => { - const { data, status, isLoading, isFetching, isError } = useQuery(['page-options', contentType], () => - fetchPageOptions(contentType) + const { defaultLocale } = useGetDefaultStrapiLocale(); + const fetchClient = useFetchClient(); + + const { data, status, isLoading, isFetching, isError } = useQuery(['page-options', contentType, defaultLocale], () => + fetchPageOptions(defaultLocale, fetchClient, contentType) ); const [pageId, setPageId] = useState(Number(initialId)); diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/components/form/index.tsx b/src/plugins/strapi-plugin-internal-links/admin/src/components/form/index.tsx index cf26167..402cb29 100644 --- a/src/plugins/strapi-plugin-internal-links/admin/src/components/form/index.tsx +++ b/src/plugins/strapi-plugin-internal-links/admin/src/components/form/index.tsx @@ -10,6 +10,7 @@ import usePageOptions, { IPageOption } from './hooks/use-page-options'; import getTrad from '../../utils/get-trad'; import { INTERNAL_LINK_TYPE } from '../factory'; import { IUseInternalLinkInputReturn } from '../input/use-internal-link-input'; +import { useGetPluginConfig } from '../../utils/use-get-plugin-config'; interface IProps extends Omit { attribute?: { @@ -20,6 +21,8 @@ interface IProps extends Omit { const { formatMessage } = useIntl(); + const { config: pluginConfig, isLoading: isLoadingConfig } = useGetPluginConfig(); + // More information including tests: https://regexr.com/7b2ai const defaultUrlRegex = new RegExp( /(^https?:\/\/(www.)?[a-zA-Z0-9]{1,}.[^s]{2,}((\/[a-zA-Z0-9\-\_\=\?\%\&\#]{1,}){1,})?)$|^mailto:[\w-\. +]+@([\w-]+\.)+[\w-]{2,4}$|^tel:((\+|00(\s|\s?\-\s?)?)[0-9]{2}(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[0-9](((\s|\s?\-\s?)?[0-9]){1,})$/ @@ -55,6 +58,12 @@ const InternalLinkForm = ({ link, setLink, errors, setErrors, attribute }: IProp })); }; + useEffect(() => { + if (pluginConfig?.pageBuilder.enabled) { + setContentTypeUid(pluginConfig.pageBuilder.pageUid); + } + }, [pluginConfig]); + const onContentTypeChange = (value: IContentTypeOption) => { setPageId(undefined); setContentTypeUid(value.uid); @@ -63,14 +72,14 @@ const InternalLinkForm = ({ link, setLink, errors, setErrors, attribute }: IProp const onPageChange = (value: IPageOption) => { if (!contentType) return; + const domain = value.platform?.domain || link.domain; + setPageId(value.id); setLink((previousValue) => ({ ...previousValue, targetContentTypeUid: contentType.uid, targetContentTypeId: value.id || null, - url: [link.domain, value.locale !== 'nl' ? value.locale : undefined, value[contentType.slugField] || ''] - .filter((item) => !!item) - .join('/') + url: [domain, value.slugLabel].filter((item) => !!item).join('/') })); }; @@ -191,13 +200,13 @@ const InternalLinkForm = ({ link, setLink, errors, setErrors, attribute }: IProp }, []); useEffect(() => { - if (contentType?.domain) { + if (page?.platform?.domain || contentType?.domain) { setLink((previousValue) => ({ ...previousValue, - domain: contentType?.domain + domain: page?.platform?.domain || contentType?.domain })); } - }, [contentType?.domain]); + }, [contentType?.domain, page]); return ( @@ -228,7 +237,7 @@ const InternalLinkForm = ({ link, setLink, errors, setErrors, attribute }: IProp - {!checked && ( + {!checked && !isLoadingConfig && !pluginConfig?.pageBuilder.enabled && ( {formatMessage({ diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/components/input/index.tsx b/src/plugins/strapi-plugin-internal-links/admin/src/components/input/index.tsx index 4d06f56..66580e4 100644 --- a/src/plugins/strapi-plugin-internal-links/admin/src/components/input/index.tsx +++ b/src/plugins/strapi-plugin-internal-links/admin/src/components/input/index.tsx @@ -3,7 +3,6 @@ import { useIntl, MessageDescriptor, IntlFormatters } from 'react-intl'; import { useCMEditViewDataManager } from '@strapi/helper-plugin'; import { Stack, IconButton, Field, FieldHint, FieldError, FieldLabel, FieldInput } from '@strapi/design-system'; - import { Pencil, Link, Trash } from '@strapi/icons'; import getTrad from '../../utils/get-trad'; diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/components/input/use-internal-link-input.ts b/src/plugins/strapi-plugin-internal-links/admin/src/components/input/use-internal-link-input.ts index a4c36c2..7e85998 100644 --- a/src/plugins/strapi-plugin-internal-links/admin/src/components/input/use-internal-link-input.ts +++ b/src/plugins/strapi-plugin-internal-links/admin/src/components/input/use-internal-link-input.ts @@ -71,7 +71,7 @@ const useInternalLinkInput = ( url: data.url, text: data.text || data.url }; - }, []); + }, [initialDataId]); const [link, setLink] = useState(parsedInitialValue); const [errors, setErrors] = useState({ @@ -87,6 +87,10 @@ const useInternalLinkInput = ( setLink(initialLink.current); }; + useEffect(() => { + setLink(parsedInitialValue); + }, [initialDataId]); + useEffect(() => { setErrors((previousValue) => ({ ...previousValue, diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/utils/axiosInstance.ts b/src/plugins/strapi-plugin-internal-links/admin/src/utils/axiosInstance.ts deleted file mode 100644 index d49cb45..0000000 --- a/src/plugins/strapi-plugin-internal-links/admin/src/utils/axiosInstance.ts +++ /dev/null @@ -1,37 +0,0 @@ -import axios from 'axios'; -import { auth } from '@strapi/helper-plugin'; - -const instance = axios.create({ - baseURL: process.env.STRAPI_ADMIN_BACKEND_URL -}); - -instance.interceptors.request.use( - async (config) => { - if (config?.headers?.set) { - (config.headers.set as any)({ - Authorization: `Bearer ${auth.getToken()}`, - Accept: 'application/json', - 'Content-Type': 'application/json' - }); - } - - return config; - }, - (error) => { - Promise.reject(error); - } -); - -instance.interceptors.response.use( - (response) => response, - (error) => { - if (error.response?.status === 401) { - auth.clearAppStorage(); - window.location.reload(); - } - - throw error; - } -); - -export default instance; diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/utils/get-request-url.ts b/src/plugins/strapi-plugin-internal-links/admin/src/utils/get-request-url.ts new file mode 100644 index 0000000..39bfcc8 --- /dev/null +++ b/src/plugins/strapi-plugin-internal-links/admin/src/utils/get-request-url.ts @@ -0,0 +1,11 @@ +import pluginId from '../plugin-id'; + +const getRequestUrl = (path: string) => { + if (path.startsWith('/')) { + return `/${pluginId}${path}`; + } + + return `/${pluginId}/${path}`; +}; + +export default getRequestUrl; diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/utils/use-get-default-locale.ts b/src/plugins/strapi-plugin-internal-links/admin/src/utils/use-get-default-locale.ts new file mode 100644 index 0000000..d9b7d3c --- /dev/null +++ b/src/plugins/strapi-plugin-internal-links/admin/src/utils/use-get-default-locale.ts @@ -0,0 +1,21 @@ +import { useFetchClient } from '@strapi/helper-plugin'; +import { useEffect, useState } from 'react'; + +export const useGetDefaultStrapiLocale = () => { + const { get } = useFetchClient(); + const [defaultLocale, setDefaultLocale] = useState(''); + + useEffect(() => { + getDefaultLocale(); + }, []); + + const getDefaultLocale = async () => { + const { data } = await get('i18n/locales'); + + setDefaultLocale(data.find((l) => l.isDefault)?.code || ''); + }; + + return { + defaultLocale + }; +}; diff --git a/src/plugins/strapi-plugin-internal-links/admin/src/utils/use-get-plugin-config.ts b/src/plugins/strapi-plugin-internal-links/admin/src/utils/use-get-plugin-config.ts new file mode 100644 index 0000000..9aa2646 --- /dev/null +++ b/src/plugins/strapi-plugin-internal-links/admin/src/utils/use-get-plugin-config.ts @@ -0,0 +1,26 @@ +import { useFetchClient } from '@strapi/helper-plugin'; +import { useEffect, useState } from 'react'; +import getRequestUrl from './get-request-url'; + +export const useGetPluginConfig = () => { + const { get } = useFetchClient(); + const [config, setConfig] = useState | undefined>(undefined); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + getConfig(); + }, []); + + const getConfig = async () => { + setIsLoading(true); + const { data } = await get(getRequestUrl('config')); + + setConfig(data); + setIsLoading(false); + }; + + return { + config, + isLoading + }; +}; diff --git a/src/plugins/strapi-plugin-internal-links/server/bootstrap.ts b/src/plugins/strapi-plugin-internal-links/server/bootstrap.ts index d1c122b..ec7e95d 100644 --- a/src/plugins/strapi-plugin-internal-links/server/bootstrap.ts +++ b/src/plugins/strapi-plugin-internal-links/server/bootstrap.ts @@ -2,8 +2,17 @@ import { Common, Strapi } from '@strapi/strapi'; import { getPopulatedEntity, sanitizeEntity } from './utils/strapi'; export default async ({ strapi }: { strapi: Strapi }) => { + const { pageBuilder } = await strapi.service('plugin::internal-links.config').getGlobalConfig(); const contentTypeUids = Object.values(strapi.contentTypes) - .filter((contentType: any) => contentType?.uid?.startsWith('api::')) + .filter((contentType: any) => { + if (contentType?.pluginOptions?.['internal-links']?.enabled === false) { + return false; + } + + return pageBuilder?.enabled + ? contentType.uid === pageBuilder?.pageUid || contentType.uid === pageBuilder?.platformUid + : contentType.uid?.startsWith('api::'); + }) .map((contentType: any) => contentType.uid); // Lifecycles for all entitities @@ -30,15 +39,22 @@ export default async ({ strapi }: { strapi: Strapi }) => { } const uid = event.model.uid as Common.UID.ContentType; + // @ts-ignore const id = event.result.id; const entity: any = await getPopulatedEntity(uid, id); - const sanitizedEntity = await sanitizeEntity(entity, uid); + const sanitizedEntity: Record = (await sanitizeEntity(entity, uid)) as any; - await strapi.service('plugin::internal-links.internal-link').updateSourceEntities(uid, id, sanitizedEntity); - await strapi - .service('plugin::internal-links.internal-link') - .updateInternalLinksFromTargetContentType(uid, id, sanitizedEntity); + if (uid === pageBuilder?.platformUid && sanitizedEntity?.id) { + await strapi + .service('plugin::internal-links.internal-link') + .updateAllLinkedDomains(sanitizedEntity?.id, sanitizedEntity?.locale); + } else { + await strapi.service('plugin::internal-links.internal-link').updateSourceEntities(uid, id, sanitizedEntity); + await strapi + .service('plugin::internal-links.internal-link') + .updateInternalLinksFromTargetContentType(uid, id, sanitizedEntity); + } }, async beforeDelete(event) { // TODO diff --git a/src/plugins/strapi-plugin-internal-links/server/controllers/collection-type.ts b/src/plugins/strapi-plugin-internal-links/server/controllers/collection-type.ts index 8c1b599..aebb17e 100644 --- a/src/plugins/strapi-plugin-internal-links/server/controllers/collection-type.ts +++ b/src/plugins/strapi-plugin-internal-links/server/controllers/collection-type.ts @@ -1,3 +1,4 @@ +import { Strapi } from '@strapi/strapi'; // Import Strapi so TS doesn't complain about unknown strapi type import { object, string } from 'yup'; const findParamsSchema = object({ @@ -17,7 +18,10 @@ const find = async (ctx) => { const locales: string[] = localesArray.map((locale: Record) => locale.code); return await strapi.entityService.findMany(ctx.params.uid, { locale: locales, - sort: [{ locale: 'desc' }, { publishedAt: 'desc' }, { [titleField]: 'asc' }] + sort: [{ locale: 'desc' }, { publishedAt: 'desc' }, { [titleField]: 'asc' }], + populate: { + platform: true + } } as Record); }; diff --git a/src/plugins/strapi-plugin-internal-links/server/controllers/config.ts b/src/plugins/strapi-plugin-internal-links/server/controllers/config.ts new file mode 100644 index 0000000..d82f55c --- /dev/null +++ b/src/plugins/strapi-plugin-internal-links/server/controllers/config.ts @@ -0,0 +1,7 @@ +const find = async () => { + const config = await strapi.service('plugin::internal-links.config').getGlobalConfig(); + + return config; +}; + +export default { find }; diff --git a/src/plugins/strapi-plugin-internal-links/server/controllers/content-type.ts b/src/plugins/strapi-plugin-internal-links/server/controllers/content-type.ts index b700278..40f8b89 100644 --- a/src/plugins/strapi-plugin-internal-links/server/controllers/content-type.ts +++ b/src/plugins/strapi-plugin-internal-links/server/controllers/content-type.ts @@ -1,12 +1,19 @@ -const find = () => { - const filter = Object.values(strapi.contentTypes).filter( - (contentType: any) => - contentType.uid?.startsWith('api::') && contentType?.pluginOptions?.['internal-links']?.enabled !== false - ); +const find = async () => { + const { pageBuilder } = await strapi.service('plugin::internal-links.config').getGlobalConfig(); + const pageBuilderPathField = pageBuilder?.pathField; + + const filter = Object.values(strapi.contentTypes).filter((contentType: any) => { + if (contentType?.pluginOptions?.['internal-links']?.enabled === false) { + return false; + } + + return pageBuilder?.enabled ? contentType.uid === pageBuilder?.pageUid : contentType.uid?.startsWith('api::'); + }); + return filter?.map((contentType: any) => { const domain = strapi.service('plugin::internal-links.url').getDomain(contentType?.uid); const titleField = contentType?.pluginOptions?.['internal-links']?.title || 'title'; - const slugField = contentType?.pluginOptions?.['internal-links']?.slug || 'fullPath'; + const slugField = pageBuilderPathField || contentType?.pluginOptions?.['internal-links']?.slug || 'fullPath'; const basePathField = contentType?.pluginOptions?.['internal-links']?.basePath || 'basePath'; return { @@ -15,7 +22,7 @@ const find = () => { displayName: contentType.info.displayName, titleField: titleField, slugField: slugField, - basePath: contentType.attributes[basePathField]?.default, + basePath: contentType.attributes?.[basePathField]?.default, domain }; }); diff --git a/src/plugins/strapi-plugin-internal-links/server/controllers/index.ts b/src/plugins/strapi-plugin-internal-links/server/controllers/index.ts index d8da9cf..baca8f8 100644 --- a/src/plugins/strapi-plugin-internal-links/server/controllers/index.ts +++ b/src/plugins/strapi-plugin-internal-links/server/controllers/index.ts @@ -2,10 +2,12 @@ import internalLink from './internal-link'; import singleType from './single-type'; import collectionType from './collection-type'; import contentType from './content-type'; +import config from './config'; export default { 'internal-link': internalLink, 'content-type': contentType, 'single-type': singleType, - 'collection-type': collectionType + 'collection-type': collectionType, + config }; diff --git a/src/plugins/strapi-plugin-internal-links/server/controllers/single-type.ts b/src/plugins/strapi-plugin-internal-links/server/controllers/single-type.ts index 11f23db..5f02a88 100644 --- a/src/plugins/strapi-plugin-internal-links/server/controllers/single-type.ts +++ b/src/plugins/strapi-plugin-internal-links/server/controllers/single-type.ts @@ -16,7 +16,7 @@ const find = async (ctx) => { const locales = localesArray.map((locale) => locale.code); const entityManager = strapi.plugin('content-manager').service('entity-manager'); - let data = []; + let data: any[] = []; for (let locale of locales) { query.locale = locale; diff --git a/src/plugins/strapi-plugin-internal-links/server/routes/index.ts b/src/plugins/strapi-plugin-internal-links/server/routes/index.ts index 3eff89f..60b9335 100644 --- a/src/plugins/strapi-plugin-internal-links/server/routes/index.ts +++ b/src/plugins/strapi-plugin-internal-links/server/routes/index.ts @@ -2,6 +2,14 @@ export default { admin: { type: 'admin', routes: [ + { + method: 'GET', + path: '/config', + handler: 'config.find', + config: { + policies: [] + } + }, { method: 'GET', path: '/content-types', diff --git a/src/plugins/strapi-plugin-internal-links/server/services/config.ts b/src/plugins/strapi-plugin-internal-links/server/services/config.ts index 1bdb81b..251248b 100644 --- a/src/plugins/strapi-plugin-internal-links/server/services/config.ts +++ b/src/plugins/strapi-plugin-internal-links/server/services/config.ts @@ -1,8 +1,29 @@ import { Common } from '@strapi/strapi'; import isEmpty from 'lodash/isEmpty'; +import set from 'lodash/set'; +import { + DEFAULT_PAGEBUILDER_COLLECTION, + DEFAULT_PAGEBUILDER_PATH_FIELD, + DEFAULT_PAGEBUILDER_PLATFORM_UID +} from '../utils/constants'; const getGlobalConfig = () => { - const config = strapi.config?.get('plugin.internal-links'); + const config: Record | undefined = strapi.config?.get('plugin.internal-links'); + + if (config?.pageBuilder?.enabled) { + if (!config?.pageBuilder?.pageUid) { + set(config, 'pageBuilder.pageUid', DEFAULT_PAGEBUILDER_COLLECTION); + } + + if (!config?.pageBuilder?.pathField) { + set(config, 'pageBuilder.pathField', DEFAULT_PAGEBUILDER_PATH_FIELD); + } + + if (!config?.pageBuilder?.platformUid) { + set(config, 'pageBuilder.platformUid', DEFAULT_PAGEBUILDER_PLATFORM_UID); + } + } + return isEmpty(config) ? null : config; }; const getContentTypeConfig = (uid: Common.UID.ContentType) => { diff --git a/src/plugins/strapi-plugin-internal-links/server/services/internal-link.ts b/src/plugins/strapi-plugin-internal-links/server/services/internal-link.ts index 0f33d3e..f3cf4a9 100644 --- a/src/plugins/strapi-plugin-internal-links/server/services/internal-link.ts +++ b/src/plugins/strapi-plugin-internal-links/server/services/internal-link.ts @@ -3,6 +3,7 @@ import { Common } from '@strapi/strapi'; import { getCustomFields, getPopulatedEntity, sanitizeEntity } from '../utils/strapi'; import { InternalLink } from '../interfaces/link'; +import { DEFAULT_PAGEBUILDER_COLLECTION } from '../utils/constants'; const mapInternalLinks = ( sourceContentTypeUid: Common.UID.ContentType, @@ -44,6 +45,20 @@ const findManyInternalLinksByTarget = async ( }); }; +const findManyInternalLinksByUid = async (targetContentTypeUid: Common.UID.ContentType): Promise => { + return strapi.db.query('plugin::internal-links.internal-link').findMany({ + where: { + $and: [ + { + sourceContentTypeUid: { + $eq: targetContentTypeUid + } + } + ] + } + }); +}; + const findManyInternalLinksBySource = async (sourceContentTypeUid: string, sourceContentTypeId: string) => { return strapi.db.query('plugin::internal-links.internal-link').findMany({ where: { @@ -108,7 +123,7 @@ const getInternalLinksFromCustomFields = async ( uid: Common.UID.ContentType, id: string ): Promise => { - // Get all internal lkink custom fields + // Get all internal link custom fields const internalLinkFields = getCustomFields(sanitizedEntity, uid, 'plugin::internal-links.internal-link'); const internalLinks = internalLinkFields @@ -131,7 +146,9 @@ const updateManyInternalLinksByTarget = async ( targetContentTypeId: string, sanitizedEntity: any ) => { - const updatedUrl = strapi.service('plugin::internal-links.url').constructURL(targetContentTypeUid, sanitizedEntity); + const updatedUrl = await strapi + .service('plugin::internal-links.url') + .constructURL(targetContentTypeUid, sanitizedEntity); await strapi.db.query('plugin::internal-links.internal-link').updateMany({ where: { $and: [ @@ -153,6 +170,30 @@ const updateManyInternalLinksByTarget = async ( }); }; +const updateAllLinkedDomains = async (platformId: number, locale: string) => { + const pages: Record[] = + ((await strapi.entityService.findMany(DEFAULT_PAGEBUILDER_COLLECTION, { + filters: { + platform: { + id: platformId + } + }, + locale, + populate: { + platform: true + } + })) as any) || []; + + pages.forEach(async (page) => { + await strapi + .service('plugin::internal-links.internal-link') + .updateSourceEntities(DEFAULT_PAGEBUILDER_COLLECTION, page.id, page), + await strapi + .service('plugin::internal-links.internal-link') + .updateInternalLinksFromTargetContentType(DEFAULT_PAGEBUILDER_COLLECTION, page.id, page); + }); +}; + const updateSourceEntities = async (uid: Common.UID.ContentType, id: string, sanitizedEntity: any) => { // Update internal links for normal internal links const internalLinksFromNormal = await getInternalLinksFromCustomFields(sanitizedEntity, uid, id); @@ -223,5 +264,6 @@ const updateInternalLinksFromTargetContentType = async ( export default { updateInternalLinksFromTargetContentType, updateSourceEntities, - deleteManyInternalLinksBySource + deleteManyInternalLinksBySource, + updateAllLinkedDomains }; diff --git a/src/plugins/strapi-plugin-internal-links/server/services/url.ts b/src/plugins/strapi-plugin-internal-links/server/services/url.ts index a504e6e..e8ce1cb 100644 --- a/src/plugins/strapi-plugin-internal-links/server/services/url.ts +++ b/src/plugins/strapi-plugin-internal-links/server/services/url.ts @@ -1,8 +1,13 @@ import get from 'lodash/get'; +import trim from 'lodash/trim'; -const getDomain = (uid: string) => { +const getDomain = async (uid: string, entity?: Record) => { const pluginConfig = strapi.service('plugin::internal-links.config').getGlobalConfig(); + if (pluginConfig?.pageBuilder?.enabled) { + return entity?.platform?.domain; + } + const environment = pluginConfig?.environment || 'production'; if (!pluginConfig?.domains) return ''; @@ -17,18 +22,20 @@ const getDomain = (uid: string) => { return pluginConfig.domains.default[environment]; }; -const constructURL = (uid: string, entity: any) => { - const domain = getDomain(uid); +const constructURL = async (uid: string, entity: Record) => { + const domain = await getDomain(uid, entity); const slug = getSlug(uid, entity); - return `${domain}/${slug}`; + + return trim(`${domain}/${slug}`, '/'); }; -const getSlug = (uid: string, entity: any) => { +const getSlug = (uid: string, entity: Record) => { const globalConfig = strapi.service('plugin::internal-links.config').getGlobalConfig(); const entityConfig = strapi.service('plugin::internal-links.config').getContentTypeConfig(uid); const slugField = entityConfig?.slug ?? 'fullPath'; const slug = get(entity, slugField, null); - return slug ?? globalConfig?.notFoundSlug ?? '404'; + + return trim(slug, '/') ?? globalConfig?.notFoundSlug ?? '404'; }; export default { diff --git a/src/plugins/strapi-plugin-internal-links/server/utils/constants.ts b/src/plugins/strapi-plugin-internal-links/server/utils/constants.ts new file mode 100644 index 0000000..068907e --- /dev/null +++ b/src/plugins/strapi-plugin-internal-links/server/utils/constants.ts @@ -0,0 +1,5 @@ +const DEFAULT_PAGEBUILDER_COLLECTION = 'api::page.page'; +const DEFAULT_PAGEBUILDER_PATH_FIELD = 'path'; +const DEFAULT_PAGEBUILDER_PLATFORM_UID = 'api::platform.platform'; + +export { DEFAULT_PAGEBUILDER_COLLECTION, DEFAULT_PAGEBUILDER_PATH_FIELD, DEFAULT_PAGEBUILDER_PLATFORM_UID }; diff --git a/src/plugins/strapi-plugin-internal-links/server/utils/strapi.ts b/src/plugins/strapi-plugin-internal-links/server/utils/strapi.ts index 2653e21..649a91f 100644 --- a/src/plugins/strapi-plugin-internal-links/server/utils/strapi.ts +++ b/src/plugins/strapi-plugin-internal-links/server/utils/strapi.ts @@ -46,7 +46,7 @@ export const getDeepPopulate = ( }; export const sanitizeEntity = async (entity: Record, uid: Common.UID.ContentType) => { - return sanitize.contentAPI.output(entity, strapi.getModel(uid)); + return await sanitize.contentAPI.output(entity, strapi.getModel(uid)); }; export const getPopulatedEntity = async (uid: Common.UID.ContentType, id: string) => { @@ -57,6 +57,19 @@ export const getPopulatedEntity = async (uid: Common.UID.ContentType, id: string }); }; +export const getPopulatedEntities = async (uid: Common.UID.ContentType, ids: string[]) => { + const populate = getDeepPopulate(uid); + + return strapi.entityService.findMany(uid, { + filters: { + id: { + $in: ids + } + }, + populate + }); +}; + export const getCustomFields = ( entity: any, uid: Common.UID.ContentType, diff --git a/types/generated/contentTypes.d.ts b/types/generated/contentTypes.d.ts index efa1f66..861c839 100644 --- a/types/generated/contentTypes.d.ts +++ b/types/generated/contentTypes.d.ts @@ -285,72 +285,6 @@ export interface AdminTransferTokenPermission extends Schema.CollectionType { }; } -export interface ApiPagePage extends Schema.CollectionType { - collectionName: 'pages'; - info: { - singularName: 'page'; - pluralName: 'pages'; - displayName: "Pagina's"; - description: ''; - }; - options: { - draftAndPublish: true; - }; - pluginOptions: { - i18n: { - localized: true; - }; - }; - attributes: { - title: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - path: Attribute.String & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - parent: Attribute.Relation<'api::page.page', 'oneToOne', 'api::page.page'>; - excerpt: Attribute.Text & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - modules: Attribute.DynamicZone<['modules.text']> & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - link: Attribute.JSON & - Attribute.CustomField< - 'plugin::internal-links.internal-link', - { - title: 'title'; - slug: 'path'; - } - > & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation<'api::page.page', 'oneToOne', 'admin::user'> & Attribute.Private; - updatedBy: Attribute.Relation<'api::page.page', 'oneToOne', 'admin::user'> & Attribute.Private; - localizations: Attribute.Relation<'api::page.page', 'oneToMany', 'api::page.page'>; - locale: Attribute.String; - }; -} - export interface PluginUploadFile extends Schema.CollectionType { collectionName: 'files'; info: { @@ -617,6 +551,138 @@ export interface PluginUsersPermissionsUser extends Schema.CollectionType { }; } +export interface ApiPagePage extends Schema.CollectionType { + collectionName: 'pages'; + info: { + singularName: 'page'; + pluralName: 'pages'; + displayName: "Pagina's"; + description: ''; + }; + options: { + draftAndPublish: true; + }; + pluginOptions: { + i18n: { + localized: true; + }; + 'internal-links': { + title: 'title'; + slug: 'path'; + }; + }; + attributes: { + title: Attribute.String & + Attribute.Required & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + path: Attribute.String & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + parent: Attribute.Relation<'api::page.page', 'oneToOne', 'api::page.page'>; + excerpt: Attribute.Text & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + modules: Attribute.DynamicZone<['modules.text']> & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + link: Attribute.JSON & + Attribute.CustomField<'plugin::internal-links.internal-link'> & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + platform: Attribute.Relation<'api::page.page', 'oneToOne', 'api::platform.platform'>; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation<'api::page.page', 'oneToOne', 'admin::user'> & Attribute.Private; + updatedBy: Attribute.Relation<'api::page.page', 'oneToOne', 'admin::user'> & Attribute.Private; + localizations: Attribute.Relation<'api::page.page', 'oneToMany', 'api::page.page'>; + locale: Attribute.String; + }; +} + +export interface ApiPlatformPlatform extends Schema.CollectionType { + collectionName: 'platforms'; + info: { + singularName: 'platform'; + pluralName: 'platforms'; + displayName: 'Platform'; + }; + options: { + draftAndPublish: true; + }; + pluginOptions: { + i18n: { + localized: true; + }; + }; + attributes: { + title: Attribute.String & + Attribute.Required & + Attribute.Unique & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + domain: Attribute.String; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation<'api::platform.platform', 'oneToOne', 'admin::user'> & Attribute.Private; + updatedBy: Attribute.Relation<'api::platform.platform', 'oneToOne', 'admin::user'> & Attribute.Private; + localizations: Attribute.Relation<'api::platform.platform', 'oneToMany', 'api::platform.platform'>; + locale: Attribute.String; + }; +} + +export interface ApiPostPost extends Schema.CollectionType { + collectionName: 'posts'; + info: { + singularName: 'post'; + pluralName: 'posts'; + displayName: 'Post'; + }; + options: { + draftAndPublish: true; + }; + pluginOptions: { + i18n: { + localized: true; + }; + }; + attributes: { + title: Attribute.String & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation<'api::post.post', 'oneToOne', 'admin::user'> & Attribute.Private; + updatedBy: Attribute.Relation<'api::post.post', 'oneToOne', 'admin::user'> & Attribute.Private; + localizations: Attribute.Relation<'api::post.post', 'oneToMany', 'api::post.post'>; + locale: Attribute.String; + }; +} + declare module '@strapi/types' { export module Shared { export interface ContentTypes { @@ -627,7 +693,6 @@ declare module '@strapi/types' { 'admin::api-token-permission': AdminApiTokenPermission; 'admin::transfer-token': AdminTransferToken; 'admin::transfer-token-permission': AdminTransferTokenPermission; - 'api::page.page': ApiPagePage; 'plugin::upload.file': PluginUploadFile; 'plugin::upload.folder': PluginUploadFolder; 'plugin::internal-links.internal-link': PluginInternalLinksInternalLink; @@ -635,6 +700,9 @@ declare module '@strapi/types' { 'plugin::users-permissions.permission': PluginUsersPermissionsPermission; 'plugin::users-permissions.role': PluginUsersPermissionsRole; 'plugin::users-permissions.user': PluginUsersPermissionsUser; + 'api::page.page': ApiPagePage; + 'api::platform.platform': ApiPlatformPlatform; + 'api::post.post': ApiPostPost; } } }