Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/search for restricted articles #118

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@

<br>

# How to restrict access to an _article_ or _category_:

1. add `restrictedAccessHref` custom prop in `_category_.json` or article header<br>
_value should be uniq across all docs_
2. add full route to `config/restrictedAccessRoutes.json` <br>
_to exclude it from search_

<br>

# How to translate a new article

There is a couple of pathes for each translated file:
Expand Down
1 change: 1 addition & 0 deletions config/restrictedAccessRoutes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["/ecom/", "/automations/automations-by-event/welcome-series"]
2 changes: 1 addition & 1 deletion config/searchExcludeRoutes.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
["/exclude-from-search/**", "/ecom/**"]
["/exclude-from-search/**"]
1 change: 1 addition & 0 deletions docs/automations/automations-by-event/welcome-series.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 2
sidebar_custom_props: { restrictedAccessHref: 'automations-by-event/welcome-series' }
---

# Как настроить приветственную серию писем
Expand Down
4 changes: 4 additions & 0 deletions docs/ecom/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"label": "Продажи",
"position": 14,
"link": {
"type": "generated-index",
"slug": "ecom"
},
"customProps": {
"restrictedAccessHref": "ecom"
}
Expand Down
15 changes: 13 additions & 2 deletions src/theme/SearchBar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
23 changes: 14 additions & 9 deletions src/theme/SearchBar/lunar-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) +
'<span class="algolia-docsearch-suggestion--highlight">' +
doc.title.substring(start, end) +
'</span>' +
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) +
'<span class="algolia-docsearch-suggestion--highlight">' +
doc.title.substring(start, end) +
'</span>' +
doc.title.substring(end, doc.title.length);

return this.getHit(doc, formattedTitle);
} catch (err) {
console.log('!!err', doc, position, length);
}
}

getKeywordHit(doc, position, length) {
Expand Down
13 changes: 8 additions & 5 deletions src/theme/hooks/useResctrictedPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
11 changes: 6 additions & 5 deletions src/theme/hooks/useRouteAllowance.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/theme/types/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
17 changes: 14 additions & 3 deletions src/theme/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { ResctrictedAccessStatus } from './enums';
import {
ResctrictedAccessStatus,
ResctrictedAccessStorageKeys,
PropSidebarItemType,
} from './enums';

export type RestrictedHref = string | undefined;

export type ResctrictedAccessItem = Record<string, ResctrictedAccessStatus>;

export interface ResctrictedAccessItems {
categories?: ResctrictedAccessItem;
articles?: ResctrictedAccessItem;
[ResctrictedAccessStorageKeys.Categories]?: ResctrictedAccessItem;
[ResctrictedAccessStorageKeys.Articles]?: ResctrictedAccessItem;
}

export interface AllowedRoutesOptions {
isStorageAllowed: boolean;
type?: PropSidebarItemType;
newRouteHref?: string;
isNewAccessToRoute?: boolean;
}
23 changes: 23 additions & 0 deletions src/theme/utils/filterSearchExcludeOptions.ts
Original file line number Diff line number Diff line change
@@ -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)
);
};
8 changes: 2 additions & 6 deletions src/theme/utils/index.ts
Original file line number Diff line number Diff line change
@@ -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';
33 changes: 18 additions & 15 deletions src/theme/utils/routeAccessUtils.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
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';

const getRoutesFromStorage = (): ResctrictedAccessItems =>
ResctrictedAccessStorage.getJSON<ResctrictedAccessItems>() ?? {};

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
);
};
Expand Down