diff --git a/README.md b/README.md index 1557abe6..1a58e32d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,15 @@
+# How to restrict access to an _article_ or _category_: + +1. add `restrictedAccessHref` custom prop in `_category_.json` or article header
+ _value should be uniq across all docs_ +2. add full route to `config/restrictedAccessRoutes.json`
+ _to exclude it from search_ + +
+ # How to translate a new article There is a couple of pathes for each translated file: diff --git a/config/restrictedAccessRoutes.json b/config/restrictedAccessRoutes.json new file mode 100644 index 00000000..bb428d55 --- /dev/null +++ b/config/restrictedAccessRoutes.json @@ -0,0 +1 @@ +["/ecom/", "/automations/automations-by-event/welcome-series"] diff --git a/config/searchExcludeRoutes.json b/config/searchExcludeRoutes.json index d02c25ee..aabec3d8 100644 --- a/config/searchExcludeRoutes.json +++ b/config/searchExcludeRoutes.json @@ -1 +1 @@ -["/exclude-from-search/**", "/ecom/**"] +["/exclude-from-search/**"] diff --git a/docs/automations/automations-by-event/welcome-series.md b/docs/automations/automations-by-event/welcome-series.md index c53c57fa..e15c934b 100644 --- a/docs/automations/automations-by-event/welcome-series.md +++ b/docs/automations/automations-by-event/welcome-series.md @@ -1,5 +1,6 @@ --- sidebar_position: 2 +sidebar_custom_props: { restrictedAccessHref: 'automations-by-event/welcome-series' } --- # Как настроить приветственную серию писем diff --git a/docs/ecom/_category_.json b/docs/ecom/_category_.json index a95db8da..c107a584 100644 --- a/docs/ecom/_category_.json +++ b/docs/ecom/_category_.json @@ -1,6 +1,10 @@ { "label": "Продажи", "position": 14, + "link": { + "type": "generated-index", + "slug": "ecom" + }, "customProps": { "restrictedAccessHref": "ecom" } diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js index 2603b9f9..b32eca44 100644 --- a/src/theme/SearchBar/index.js +++ b/src/theme/SearchBar/index.js @@ -5,6 +5,8 @@ import { translate } from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import { usePluginData } from '@docusaurus/useGlobalData'; import useIsBrowser from '@docusaurus/useIsBrowser'; +import { filterSearchExcludeOptions } from '../utils/filterSearchExcludeOptions'; + const Search = (props) => { const initialized = useRef(false); const searchBarRef = useRef(null); @@ -54,10 +56,19 @@ const Search = (props) => { import('./DocSearch'), import('./algolia.css'), ]).then(([searchDocs, searchIndex, { default: DocSearch }]) => { - if (searchDocs.length === 0) { + const filteredSearchDocs = filterSearchExcludeOptions(searchDocs, isBrowser); + console.log( + '!!filteredSearchDocs', + searchDocs.length, + filteredSearchDocs.length, + searchIndex + ); + + if (filteredSearchDocs.length === 0) { return; } - initAlgolia(searchDocs, searchIndex, DocSearch); + + initAlgolia(filteredSearchDocs, searchIndex, DocSearch); setIndexReady(true); }); initialized.current = true; diff --git a/src/theme/SearchBar/lunar-search.js b/src/theme/SearchBar/lunar-search.js index c497c2e1..50fcb733 100644 --- a/src/theme/SearchBar/lunar-search.js +++ b/src/theme/SearchBar/lunar-search.js @@ -51,15 +51,20 @@ class LunrSearchAdapter { }; } getTitleHit(doc, position, length) { - const start = position[0]; - const end = position[0] + length; - let formattedTitle = - doc.title.substring(0, start) + - '' + - doc.title.substring(start, end) + - '' + - doc.title.substring(end, doc.title.length); - return this.getHit(doc, formattedTitle); + try { + const start = position[0]; + const end = position[0] + length; + let formattedTitle = + doc.title.substring(0, start) + + '' + + doc.title.substring(start, end) + + '' + + doc.title.substring(end, doc.title.length); + + return this.getHit(doc, formattedTitle); + } catch (err) { + console.log('!!err', doc, position, length); + } } getKeywordHit(doc, position, length) { diff --git a/src/theme/hooks/useResctrictedPath.ts b/src/theme/hooks/useResctrictedPath.ts index 81f26c69..d83748be 100644 --- a/src/theme/hooks/useResctrictedPath.ts +++ b/src/theme/hooks/useResctrictedPath.ts @@ -2,20 +2,23 @@ import { useLayoutEffect } from 'react'; import { PropSidebarItem } from '@docusaurus/plugin-content-docs'; import useIsBrowser from '@docusaurus/useIsBrowser'; import { checkAllowedRoutes, ResctrictedAccessStorage, checkHiddenSidebarItem } from '../utils'; -import { RestrictedHref } from '../types'; +import { RestrictedHref, PropSidebarItemType } from '../types'; import { useRouteAllowance } from './useRouteAllowance'; export const useResctrictedPath = (item: PropSidebarItem) => { - const isBrowser = useIsBrowser(); + const isStorageAllowed = useIsBrowser(); const routeHref = item.customProps?.restrictedAccessHref as RestrictedHref; - const { allowedRoutes, isNewAccessToRoute } = useRouteAllowance(routeHref, isBrowser); + const { allowedRoutes, isNewAccessToRoute } = useRouteAllowance(routeHref, { + isStorageAllowed, + type: item.type as PropSidebarItemType, + }); useLayoutEffect(() => { - if (isNewAccessToRoute && isBrowser) { + if (isNewAccessToRoute && isStorageAllowed) { ResctrictedAccessStorage.setJSON(allowedRoutes); } - }, [isBrowser]); + }, [isStorageAllowed, isNewAccessToRoute]); return { isRestricted: checkHiddenSidebarItem(item) || !checkAllowedRoutes(allowedRoutes, routeHref), diff --git a/src/theme/hooks/useRouteAllowance.ts b/src/theme/hooks/useRouteAllowance.ts index 02d86c01..99d54705 100644 --- a/src/theme/hooks/useRouteAllowance.ts +++ b/src/theme/hooks/useRouteAllowance.ts @@ -1,16 +1,17 @@ import { useHistory } from '@docusaurus/router'; import { checkNewAccessToRoute, getAllowedRoutes } from '../utils'; -import { RestrictedHref } from '../types'; +import { RestrictedHref, AllowedRoutesOptions } from '../types'; -export const useRouteAllowance = (routeHref: RestrictedHref, isStorageAllowed: boolean) => { +export const useRouteAllowance = (newRouteHref: RestrictedHref, options: AllowedRoutesOptions) => { const { location: { pathname: path }, } = useHistory(); - const isNewAccessToRoute = checkNewAccessToRoute(routeHref, path); - const allowedRoutes = getAllowedRoutes(routeHref, { + const isNewAccessToRoute = checkNewAccessToRoute(newRouteHref, path); + const allowedRoutes = getAllowedRoutes({ + newRouteHref, + ...options, isNewAccessToRoute, - isStorageAllowed, }); return { diff --git a/src/theme/types/enums.ts b/src/theme/types/enums.ts index aea2d519..b4e42533 100644 --- a/src/theme/types/enums.ts +++ b/src/theme/types/enums.ts @@ -6,4 +6,10 @@ export const enum ResctrictedAccessStatus { export const enum PropSidebarItemType { Category = 'category', Link = 'link', + Html = 'html', +} + +export const enum ResctrictedAccessStorageKeys { + Categories = 'categories', + Articles = 'articles', } diff --git a/src/theme/types/types.ts b/src/theme/types/types.ts index ba33cd9e..8eba10ef 100644 --- a/src/theme/types/types.ts +++ b/src/theme/types/types.ts @@ -1,10 +1,21 @@ -import { ResctrictedAccessStatus } from './enums'; +import { + ResctrictedAccessStatus, + ResctrictedAccessStorageKeys, + PropSidebarItemType, +} from './enums'; export type RestrictedHref = string | undefined; export type ResctrictedAccessItem = Record; export interface ResctrictedAccessItems { - categories?: ResctrictedAccessItem; - articles?: ResctrictedAccessItem; + [ResctrictedAccessStorageKeys.Categories]?: ResctrictedAccessItem; + [ResctrictedAccessStorageKeys.Articles]?: ResctrictedAccessItem; +} + +export interface AllowedRoutesOptions { + isStorageAllowed: boolean; + type?: PropSidebarItemType; + newRouteHref?: string; + isNewAccessToRoute?: boolean; } diff --git a/src/theme/utils/filterSearchExcludeOptions.ts b/src/theme/utils/filterSearchExcludeOptions.ts new file mode 100644 index 00000000..61d69f0f --- /dev/null +++ b/src/theme/utils/filterSearchExcludeOptions.ts @@ -0,0 +1,23 @@ +import { getAllowedRoutes, flatRoutesResponse } from './routeAccessUtils'; +import restrictedAccessRoutes from '../../../config/restrictedAccessRoutes.json'; + +const checkPartialExistanceInArray = (target: string, supportArray: string[]) => + supportArray.some((item) => target.includes(item)); + +const filterRestrictedRoutes = (restrictedRoutes: string[], itemsToExclude: string[]) => + restrictedRoutes.filter( + (restrictedRoute) => !checkPartialExistanceInArray(restrictedRoute, itemsToExclude) + ); + +export const filterSearchExcludeOptions = (searchDocs, isStorageAllowed: boolean) => { + const allowedRoutes = getAllowedRoutes({ isStorageAllowed }); + const flatAllowedRoutes = flatRoutesResponse(allowedRoutes); + const filteredRestrictedRoutes = filterRestrictedRoutes( + restrictedAccessRoutes, + flatAllowedRoutes + ); + + return searchDocs.filter( + ({ url }) => !checkPartialExistanceInArray(url, filteredRestrictedRoutes) + ); +}; diff --git a/src/theme/utils/index.ts b/src/theme/utils/index.ts index 8025d130..9cbf4c4c 100644 --- a/src/theme/utils/index.ts +++ b/src/theme/utils/index.ts @@ -1,8 +1,4 @@ export { getTermination } from './getTermination'; -export { - checkHiddenSidebarItem, - checkNewAccessToRoute, - getAllowedRoutes, - checkAllowedRoutes, -} from './routeAccessUtils'; +export * from './routeAccessUtils'; +export { filterSearchExcludeOptions } from './filterSearchExcludeOptions'; export { ResctrictedAccessStorage } from './ResctrictedAccessStorage'; diff --git a/src/theme/utils/routeAccessUtils.ts b/src/theme/utils/routeAccessUtils.ts index e5043a00..b68d4b01 100644 --- a/src/theme/utils/routeAccessUtils.ts +++ b/src/theme/utils/routeAccessUtils.ts @@ -1,10 +1,12 @@ import { PropSidebarItem } from '@docusaurus/plugin-content-docs'; import { ResctrictedAccessItems, - ResctrictedAccessItem, ResctrictedAccessStatus, PropSidebarItemType, RestrictedHref, + ResctrictedAccessItem, + ResctrictedAccessStorageKeys, + AllowedRoutesOptions, } from '../types'; import { ResctrictedAccessStorage } from './ResctrictedAccessStorage'; import { HIDDEN_CATEGORIES_LABELS } from '../constants'; @@ -12,38 +14,39 @@ import { HIDDEN_CATEGORIES_LABELS } from '../constants'; const getRoutesFromStorage = (): ResctrictedAccessItems => ResctrictedAccessStorage.getJSON() ?? {}; -interface AllowedRoutesOptions { - isNewAccessToRoute: boolean; - isStorageAllowed: boolean; -} - -export const getAllowedRoutes = ( - routeHref: string, - { isNewAccessToRoute, isStorageAllowed }: AllowedRoutesOptions -) => { +export const getAllowedRoutes = (options?: AllowedRoutesOptions) => { + const { isStorageAllowed, type, isNewAccessToRoute, newRouteHref } = options ?? {}; const previouslyAccessed: ResctrictedAccessItems = isStorageAllowed ? getRoutesFromStorage() : {}; if (!isNewAccessToRoute) { return previouslyAccessed; } + const storeKey = + type === PropSidebarItemType.Category + ? ResctrictedAccessStorageKeys.Categories + : ResctrictedAccessStorageKeys.Articles; + return { ...previouslyAccessed, - categories: { - ...(previouslyAccessed.categories ?? {}), - [routeHref]: ResctrictedAccessStatus.Allowed, + [storeKey]: { + ...(previouslyAccessed[storeKey] ?? {}), + [newRouteHref]: ResctrictedAccessStatus.Allowed, }, }; }; +export const flatRoutesResponse = (routesByTypes: ResctrictedAccessItems) => + Object.values(routesByTypes).flatMap((routes) => Object.keys(routes)); + export const checkAllowedRoutes = (allowedRoutes: ResctrictedAccessItems, routeHref: string) => { if (!routeHref) { return true; } return Object.values(allowedRoutes).reduce( - (acc: boolean, type: ResctrictedAccessItem) => - acc || type[routeHref] === ResctrictedAccessStatus.Allowed, + (acc: boolean, routes: ResctrictedAccessItem) => + acc || routes[routeHref] === ResctrictedAccessStatus.Allowed, false ); };