Skip to content

Commit

Permalink
feat: add algolia confurable site search
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Dec 23, 2020
1 parent c586ce6 commit e136c2d
Show file tree
Hide file tree
Showing 69 changed files with 1,717 additions and 476 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ dist
*.log
public
.next
out
out
**/.env
90 changes: 50 additions & 40 deletions core/core/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ActionItems } from './utility';
import { StoryRenderFn } from './utility';
import { ReactElement } from 'react';
import { Story, Document } from './document';
import { SearchOptions } from './search';

/**
* render function by framework. By default 'react'
Expand Down Expand Up @@ -181,6 +182,10 @@ export type BuildConfiguration = BuildProps & {
* instrumentation configuration
*/
instrument?: any;
/**
* search options
*/
search?: SearchOptions;
};

export interface ToolbarConfig {
Expand Down Expand Up @@ -332,46 +337,6 @@ export interface RunOnlyConfiguration {
export type RunConfiguration = RunOnlyConfiguration &
Omit<BuildConfiguration, 'pages'>;

export const defaultRunConfig: RunConfiguration = {
title: 'Component controls',
description:
'Component controls stories. Write your components documentation with MDX and JSX. Design, develop, test and review in a single site.',
language: 'en',
author: '@component-controls',
controls: {
threshold: 10,
},
pages: {
story: {
label: 'Docs',
navSidebar: true,
contextSidebar: true,
topMenu: true,
tabs: [{ title: 'Documentation', type: 'ClassicPage' }],
},
blog: {
label: 'Blog',
contextSidebar: true,
topMenu: true,
indexHome: true,
},
author: {
label: 'Authors',
},
page: {
label: 'Page',
container: null,
},
tags: {
label: 'Tags',
},
},
};

export const convertConfig = (config: RunConfiguration): RunConfiguration => {
return config;
};

export const defaultBuildConfig: BuildConfiguration = {
siteRoot: '/',
siteMap: {
Expand Down Expand Up @@ -417,4 +382,49 @@ export const defaultBuildConfig: BuildConfiguration = {
basePath: 'tags/',
},
},
search: {
/**
* the search plugin search routine
*/
searchingModule: require.resolve('@component-controls/search-fusejs'),
},
};
export const defaultRunConfig: RunConfiguration = {
title: 'Component controls',
description:
'Component controls stories. Write your components documentation with MDX and JSX. Design, develop, test and review in a single site.',
language: 'en',
author: '@component-controls',
controls: {
threshold: 10,
},
pages: {
story: {
label: 'Docs',
navSidebar: true,
contextSidebar: true,
topMenu: true,
tabs: [{ title: 'Documentation', type: 'ClassicPage' }],
},
blog: {
label: 'Blog',
contextSidebar: true,
topMenu: true,
indexHome: true,
},
author: {
label: 'Authors',
},
page: {
label: 'Page',
container: null,
},
tags: {
label: 'Tags',
},
},
};

export const convertConfig = (config: RunConfiguration): RunConfiguration => {
return config;
};
10 changes: 5 additions & 5 deletions core/core/src/document-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ export const ensureTrailingSlash = (route: string): string =>
export const ensureStartingSlash = (route: string): string =>
route.startsWith('/') ? route : '/' + route;

export const removeTrailingSlash = (route: string): string => {
export const removeTrailingSlash = (route: string, index = 1): string => {
let result = route;
while (result.length > 1 && result.endsWith('/')) {
while (result.length > index && result.endsWith('/')) {
result = result.substr(0, result.length - 1);
}
return result;
};

export const removeStartingSlash = (route: string): string => {
export const removeStartingSlash = (route: string, index = 1): string => {
let result = route;
while (result.length > 1 && result.startsWith('/')) {
while (result.length > index && result.startsWith('/')) {
result = result.substr(1);
}
return result;
Expand Down Expand Up @@ -62,7 +62,7 @@ export const getDocPath = (
: `${ensureTrailingSlash(basePath)}${strToId(name)}${
activeTab ? ensureStartingSlash(activeTab) : ''
}`;
return encodeURI(`${siteRoot}${removeStartingSlash(route)}`);
return encodeURI(`${siteRoot}${removeStartingSlash(route, 0)}`);
};

export const getStoryPath = (
Expand Down
17 changes: 17 additions & 0 deletions core/core/src/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CodeLocation, PackageInfo, StoryRenderFn } from './utility';
import { Component } from './components';
import { ComponentControls, ComponentControl } from './controls';
import { RunConfiguration, DocType, PageLayoutProps } from './configuration';
import { SearchResult } from './search';
/**
* an identifier/variable.argument in the source code
*/
Expand Down Expand Up @@ -331,6 +332,21 @@ export const dateToLocalString = (date?: Date): string =>
timeZone: 'UTC',
})
: '';
/**
* short document information. used in search results, or index page
*/
export type DocInfo = Pick<
Document,
'title' | 'description' | 'type' | 'tags' | 'date' | 'author'
> & {
/**
* following fields are useful for highlighting search results
*/
link: string;
authorLink?: string;
rawTags?: string[];
rawType?: string;
};
/**
* list of components used in stories
*/
Expand Down Expand Up @@ -397,6 +413,7 @@ export interface Store {
* update store, for example controls or state
*/
updateStory: (story: Story) => void;
search?: (store: Store) => SearchResult;
}

class DefaultStore {
Expand Down
1 change: 1 addition & 0 deletions core/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './document-utils';
export * from './utility';
export { randomizeData, canRandomizeControl } from './controls-randomize';
export * from './controls-smart';
export * from './search';
export * from './source';
export * from './faker';

Expand Down
143 changes: 143 additions & 0 deletions core/core/src/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { ReactNode } from 'react';
import { Store, DocInfo, defDocType, Document } from './document';
import { getDocPath } from './document-utils';

export interface SearchFields {
objectID: string;
title?: string;
description?: string;
source?: string;
author?: string;
stories?: string[];
tags?: string[];
components?: string[];
type?: string;
}

export interface SearchOptions {
/**
* if the search plugin is using indexing, specify the indexing routine here
*/
indexingModule?: string;
/**
* the search plugin search routine
*/
searchingModule: string;
/**
* a selection of the fields to search by
*/
fields?: (keyof SearchFields)[];
/**
* the list of documents to use when an empty search
*/
emptySearchDocuments?: string[];
/**
* how many search results per page
* Default 20
*/
hitsPerPage?: number;
/**
* provider options
*/
options?: any;
}

export const DefaultSearchFields: SearchOptions['fields'] = [
'title',
'description',
'stories',
'components',
];
export type SearchFn = (search: string) => void;

export type SearchItem = DocInfo;

export interface SearchResult {
items: SearchItem[];
searchFn: SearchFn;
provider?: {
logo: ReactNode;
url: string;
name: string;
};
}

export const docToSearchObject = (
doc: Document,
fields: SearchOptions['fields'] = DefaultSearchFields,
): SearchFields => {
const value: SearchFields = {
objectID: doc.title,
};
if (fields.includes('title')) {
value.title = doc.title.replace('/', ' ');
}
if (fields.includes('description') && typeof doc.description === 'string') {
value.description = doc.description;
}
if (fields.includes('source')) {
value.source = doc.source;
}
if (fields.includes('author')) {
value.author = doc.author;
}
if (fields.includes('stories')) {
value.stories = doc.stories?.map(story => story.split('-').join(' '));
}
if (fields.includes('tags')) {
value.tags = doc.tags;
}
if (fields.includes('components') && doc.componentsLookup) {
value.components = Object.keys(doc.componentsLookup);
}
if (fields.includes('type')) {
value.type = doc.type;
}
return value;
};

export const highlightString = (
text: string | undefined,
search: string,
fieldName: keyof SearchFields,
fields: SearchOptions['fields'] = DefaultSearchFields,
): string | undefined =>
text && fields.includes(fieldName)
? text.replace(new RegExp(search, 'i'), match => `<em>${match}</em>`)
: text;

export const docToSearchItem = (
store: Store,
docId: string,
search: string,
): DocInfo => {
const doc = store.docs[docId];
const fields = store.config.search?.fields;
return {
title: highlightString(doc.title, search, 'title', fields) as string,
author: highlightString(doc.author, search, 'author', fields),
authorLink: doc.author
? getDocPath('author', undefined, store, doc.author)
: undefined,
description:
typeof doc.description === 'string'
? highlightString(doc.description, search, 'description', fields)
: doc.description,
type: doc.type,
tags: doc.tags,
rawTags: doc.tags
? doc.tags.map(
tag => highlightString(tag, search, 'tags', fields) as string,
)
: undefined,
date: doc.date,
link: getDocPath(doc.type || defDocType, doc, store, doc.title),
};
};

export const emptySearchDocuments = (store: Store): SearchItem[] => {
const { emptySearchDocuments = [] } = store.config.search || {};
return emptySearchDocuments
.filter(docId => store.docs[docId])
.map(docId => docToSearchItem(store, docId, ''));
};
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ Object {
"@component-controls/core": "^2.2.0",
"@component-controls/editors": "^2.2.4",
"@component-controls/render": "^2.2.0",
"@component-controls/search-algolia": "^2.2.0",
"@component-controls/store": "^2.2.4",
"@theme-ui/css": "^0.6.0-alpha.1",
"copy-to-clipboard": "^3.2.1",
"fuse.js": "^6.4.1",
"global": "^4.3.2",
"js-string-escape": "^1.0.1",
"query-string": "^6.13.5",
Expand Down
7 changes: 7 additions & 0 deletions core/loader/src/replaceSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export const replaceSource = (
const configJSON = ${
configFilePath ? `require("${normalizePath(configFilePath)}")` : 'undefined'
};
const search = ${
typeof config?.search?.searchingModule === 'string'
? `require("${normalizePath(config.search.searchingModule)}")`
: 'undefined'
};
const contexts = [];
${contexts
.map(
Expand Down Expand Up @@ -97,6 +103,7 @@ ${contexts
const path = require('path');
${imports}
${storeConst}
store.search = search.default || search;
store.config = ${configFilePath ? 'configJSON.default ||' : ''} configJSON;
store.buildConfig = ${config ? JSON.stringify(config) : '{}'};
${loadStories}
Expand Down
Loading

0 comments on commit e136c2d

Please sign in to comment.