build(aio): add terms from heading to the search index
This commit is contained in:
parent
3a0886dc12
commit
e8bbf86e66
@ -18,7 +18,8 @@ self.onmessage = handleMessage;
|
|||||||
function createIndex(addFn) {
|
function createIndex(addFn) {
|
||||||
return lunr(/** @this */function() {
|
return lunr(/** @this */function() {
|
||||||
this.ref('path');
|
this.ref('path');
|
||||||
this.field('titleWords', {boost: 50});
|
this.field('titleWords', {boost: 100});
|
||||||
|
this.field('headingWords', {boost: 50});
|
||||||
this.field('members', {boost: 40});
|
this.field('members', {boost: 40});
|
||||||
this.field('keywords', {boost: 20});
|
this.field('keywords', {boost: 20});
|
||||||
addFn(this);
|
addFn(this);
|
||||||
|
@ -50,19 +50,14 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
|||||||
|
|
||||||
var ignoreWordsMap = convertToMap(wordsToIgnore);
|
var ignoreWordsMap = convertToMap(wordsToIgnore);
|
||||||
|
|
||||||
// If the title contains a name starting with ng, e.g. "ngController", then add the module
|
// If the heading contains a name starting with ng, e.g. "ngController", then add the
|
||||||
// name
|
// name without the ng to the text, e.g. "controller".
|
||||||
// without the ng to the title text, e.g. "controller".
|
function preprocessText(text) {
|
||||||
function extractTitleWords(title) {
|
return text.replace(/(^|\s)([nN]g([A-Z]\w*))/g, '$1$2 $3');
|
||||||
var match = /ng([A-Z]\w*)/.exec(title);
|
|
||||||
if (match) {
|
|
||||||
title = title + ' ' + match[1].toLowerCase();
|
|
||||||
}
|
|
||||||
return title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractWords(text, words, keywordMap) {
|
function extractWords(text, words, keywordMap) {
|
||||||
var tokens = text.toLowerCase().split(/[.\s,`'"#]+/mg);
|
var tokens = preprocessText(text).toLowerCase().split(/[.\s,`'"#]+/mg);
|
||||||
tokens.forEach(function(token) {
|
tokens.forEach(function(token) {
|
||||||
var match = token.match(KEYWORD_REGEX);
|
var match = token.match(KEYWORD_REGEX);
|
||||||
if (match) {
|
if (match) {
|
||||||
@ -82,13 +77,15 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
|||||||
// Ignore internals and private exports (indicated by the ɵ prefix)
|
// Ignore internals and private exports (indicated by the ɵ prefix)
|
||||||
.filter(function(doc) { return !doc.internal && !doc.privateExport; });
|
.filter(function(doc) { return !doc.internal && !doc.privateExport; });
|
||||||
|
|
||||||
filteredDocs.forEach(function(doc) {
|
|
||||||
|
|
||||||
|
filteredDocs.forEach(function(doc) {
|
||||||
|
|
||||||
var words = [];
|
var words = [];
|
||||||
var keywordMap = Object.assign({}, ignoreWordsMap);
|
var keywordMap = Object.assign({}, ignoreWordsMap);
|
||||||
var members = [];
|
var members = [];
|
||||||
var membersMap = {};
|
var membersMap = Object.assign({}, ignoreWordsMap);
|
||||||
|
const headingWords = [];
|
||||||
|
const headingWordMap = Object.assign({}, ignoreWordsMap);
|
||||||
|
|
||||||
// Search each top level property of the document for search terms
|
// Search each top level property of the document for search terms
|
||||||
Object.keys(doc).forEach(function(key) {
|
Object.keys(doc).forEach(function(key) {
|
||||||
@ -98,25 +95,43 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
|||||||
extractWords(value, words, keywordMap);
|
extractWords(value, words, keywordMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case properties that contain content relating to "members"
|
||||||
|
// of a doc that represents, say, a class or interface
|
||||||
if (key === 'methods' || key === 'properties' || key === 'events') {
|
if (key === 'methods' || key === 'properties' || key === 'events') {
|
||||||
value.forEach(function(member) { extractWords(member.name, members, membersMap); });
|
value.forEach(function(member) { extractWords(member.name, members, membersMap); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
doc.searchTitle = doc.searchTitle || doc.title || doc.vFile && doc.vFile.title || doc.name;
|
// Extract all the keywords from the headings
|
||||||
|
if (doc.vFile && doc.vFile.headings) {
|
||||||
|
Object.keys(doc.vFile.headings).forEach(function(headingTag) {
|
||||||
|
doc.vFile.headings[headingTag].forEach(function(headingText) {
|
||||||
|
extractWords(headingText, headingWords, headingWordMap);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the title to use in searches
|
||||||
|
doc.searchTitle = doc.searchTitle || doc.title || doc.vFile && doc.vFile.title || doc.name || '';
|
||||||
|
|
||||||
|
// Attach all this search data to the document
|
||||||
doc.searchTerms = {
|
doc.searchTerms = {
|
||||||
titleWords: extractTitleWords(doc.searchTitle),
|
titleWords: preprocessText(doc.searchTitle),
|
||||||
|
headingWords: headingWords.sort().join(' '),
|
||||||
keywords: words.sort().join(' '),
|
keywords: words.sort().join(' '),
|
||||||
members: members.sort().join(' ')
|
members: members.sort().join(' ')
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var searchData =
|
// Now process all the search data and collect it up to be used in creating a new document
|
||||||
filteredDocs.filter(function(page) { return page.searchTerms; }).map(function(page) {
|
var searchData = filteredDocs.map(function(page) {
|
||||||
return Object.assign(
|
// Copy the properties from the searchTerms object onto the search data object
|
||||||
{path: page.path, title: page.searchTitle, type: page.docType}, page.searchTerms);
|
return Object.assign({
|
||||||
|
path: page.path,
|
||||||
|
title: page.searchTitle,
|
||||||
|
type: page.docType
|
||||||
|
}, page.searchTerms);
|
||||||
});
|
});
|
||||||
|
|
||||||
docs.push({
|
docs.push({
|
||||||
|
@ -42,9 +42,9 @@ describe('generateKeywords processor', () => {
|
|||||||
it('should compute `doc.searchTitle` from the doc properties if not already provided', () => {
|
it('should compute `doc.searchTitle` from the doc properties if not already provided', () => {
|
||||||
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
|
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
|
||||||
const docs = [
|
const docs = [
|
||||||
{ docType: 'class', name: 'A', searchTitle: 'searchTitle A', title: 'title A', vFile: { title: 'vFile A'} },
|
{ docType: 'class', name: 'A', searchTitle: 'searchTitle A', title: 'title A', vFile: { headings: { h1: ['vFile A'] } } },
|
||||||
{ docType: 'class', name: 'B', title: 'title B', vFile: { title: 'vFile B'} },
|
{ docType: 'class', name: 'B', title: 'title B', vFile: { headings: { h1: ['vFile B'] } } },
|
||||||
{ docType: 'class', name: 'C', vFile: { title: 'vFile C'} },
|
{ docType: 'class', name: 'C', vFile: { title: 'vFile C', headings: { h1: ['vFile C'] } } },
|
||||||
{ docType: 'class', name: 'D' },
|
{ docType: 'class', name: 'D' },
|
||||||
];
|
];
|
||||||
processor.$process(docs);
|
processor.$process(docs);
|
||||||
@ -62,19 +62,82 @@ describe('generateKeywords processor', () => {
|
|||||||
{ docType: 'class', name: 'PublicExport', searchTitle: 'class PublicExport' },
|
{ docType: 'class', name: 'PublicExport', searchTitle: 'class PublicExport' },
|
||||||
];
|
];
|
||||||
processor.$process(docs);
|
processor.$process(docs);
|
||||||
expect(docs[docs.length - 1].data).toEqual([
|
const keywordsDoc = docs[docs.length - 1];
|
||||||
|
expect(keywordsDoc.data).toEqual([
|
||||||
jasmine.objectContaining({ title: 'class PublicExport', type: 'class'})
|
jasmine.objectContaining({ title: 'class PublicExport', type: 'class'})
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add title words to the search terms', () => {
|
||||||
|
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
|
||||||
|
const docs = [
|
||||||
|
{
|
||||||
|
docType: 'class',
|
||||||
|
name: 'PublicExport',
|
||||||
|
searchTitle: 'class PublicExport',
|
||||||
|
vFile: { headings: { h2: ['heading A', 'heading B'] } }
|
||||||
|
},
|
||||||
|
];
|
||||||
|
processor.$process(docs);
|
||||||
|
const keywordsDoc = docs[docs.length - 1];
|
||||||
|
expect(keywordsDoc.data[0].titleWords).toEqual('class PublicExport');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add heading words to the search terms', () => {
|
||||||
|
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
|
||||||
|
const docs = [
|
||||||
|
{
|
||||||
|
docType: 'class',
|
||||||
|
name: 'PublicExport',
|
||||||
|
searchTitle: 'class PublicExport',
|
||||||
|
vFile: { headings: { h2: ['Important heading', 'Secondary heading'] } }
|
||||||
|
},
|
||||||
|
];
|
||||||
|
processor.$process(docs);
|
||||||
|
const keywordsDoc = docs[docs.length - 1];
|
||||||
|
expect(keywordsDoc.data[0].headingWords).toEqual('heading important secondary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process terms prefixed with "ng" to include the term stripped of "ng"', () => {
|
||||||
|
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
|
||||||
|
const docs = [
|
||||||
|
{
|
||||||
|
docType: 'class',
|
||||||
|
name: 'PublicExport',
|
||||||
|
searchTitle: 'ngController',
|
||||||
|
vFile: { headings: { h2: ['ngModel'] } },
|
||||||
|
content: 'Some content with ngClass in it.'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
processor.$process(docs);
|
||||||
|
const keywordsDoc = docs[docs.length - 1];
|
||||||
|
expect(keywordsDoc.data[0].titleWords).toEqual('ngController Controller');
|
||||||
|
expect(keywordsDoc.data[0].headingWords).toEqual('model ngmodel');
|
||||||
|
expect(keywordsDoc.data[0].keywords).toContain('class');
|
||||||
|
expect(keywordsDoc.data[0].keywords).toContain('ngclass');
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate renderedContent property', () => {
|
it('should generate renderedContent property', () => {
|
||||||
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
|
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
|
||||||
const docs = [
|
const docs = [
|
||||||
{ docType: 'class', name: 'SomeClass', description: 'The is the documentation for the SomeClass API.' },
|
{
|
||||||
|
docType: 'class',
|
||||||
|
name: 'SomeClass',
|
||||||
|
description: 'The is the documentation for the SomeClass API.',
|
||||||
|
vFile: { headings: { h1: ['SomeClass'], h2: ['Some heading'] } }
|
||||||
|
},
|
||||||
];
|
];
|
||||||
processor.$process(docs);
|
processor.$process(docs);
|
||||||
expect(docs[docs.length - 1].renderedContent).toEqual(
|
const keywordsDoc = docs[docs.length - 1];
|
||||||
'[{"title":"SomeClass","type":"class","titleWords":"SomeClass","keywords":"api class documentation for is someclass the","members":""}]'
|
expect(JSON.parse(keywordsDoc.renderedContent)).toEqual(
|
||||||
|
[{
|
||||||
|
'title':'SomeClass',
|
||||||
|
'type':'class',
|
||||||
|
'titleWords':'SomeClass',
|
||||||
|
'headingWords':'heading some someclass',
|
||||||
|
'keywords':'api class documentation for is someclass the',
|
||||||
|
'members':''
|
||||||
|
}]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user