Add number of occurence to tags
It can be a bit frustrating to click on a tag only to realise it was the only example with that tag.
This commit is contained in:
@@ -43,15 +43,15 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
words.forEach(function (word) {
|
words.forEach(function (word) {
|
||||||
const dict = info.index[word];
|
const dict = info.wordIndex[word];
|
||||||
if (dict) {
|
if (dict) {
|
||||||
updateScores(dict, word);
|
updateScores(dict, word);
|
||||||
} else {
|
} else {
|
||||||
const r = new RegExp(word);
|
const r = new RegExp(word);
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
for (let idx in info.index) {
|
for (let idx in info.wordIndex) {
|
||||||
if (r.test(idx)) {
|
if (r.test(idx)) {
|
||||||
updateScores(info.index[idx], word);
|
updateScores(info.wordIndex[idx], word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<p class="tags">
|
<p class="tags">
|
||||||
{{#each tags}}
|
{{#each tags}}
|
||||||
<a href="./index.html?q={{.}}" class="badge badge-info">{{.}}</a>
|
<a href="./index.html?q={{./tag}}" class="badge badge-info">{{ ./tag }} ({{ ./amount }})</a>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</p>
|
</p>
|
||||||
{{{ contents }}}
|
{{{ contents }}}
|
||||||
|
|||||||
@@ -30,6 +30,43 @@ handlebars.registerHelper('indent', (text, options) => {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the object with the keys inserted in alphabetic order.
|
||||||
|
* When exporting with `JSON.stringify(obj)` the keys are sorted.
|
||||||
|
* @param {Object<string, *>} obj Any object
|
||||||
|
* @return {Object<string, *>} New object
|
||||||
|
*/
|
||||||
|
function sortObjectByKey(obj) {
|
||||||
|
return Object.keys(obj)
|
||||||
|
.sort() // sort twice to get predictable, case insensitve order
|
||||||
|
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'}))
|
||||||
|
.reduce((idx, tag) => {
|
||||||
|
idx[tag] = obj[tag];
|
||||||
|
return idx;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an index of tags belonging to examples
|
||||||
|
* @param {Array<Object>} exampleData Array of example data objects.
|
||||||
|
* @return {Object} Word index.
|
||||||
|
*/
|
||||||
|
function createTagIndex(exampleData) {
|
||||||
|
const index = {};
|
||||||
|
exampleData.forEach((data, i) => {
|
||||||
|
data.tags.forEach((tag) => {
|
||||||
|
tag = tag.toLowerCase();
|
||||||
|
let tagIndex = index[tag];
|
||||||
|
if (!tagIndex) {
|
||||||
|
tagIndex = [];
|
||||||
|
index[tag] = tagIndex;
|
||||||
|
}
|
||||||
|
tagIndex.push(i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an inverted index of keywords from examples. Property names are
|
* Create an inverted index of keywords from examples. Property names are
|
||||||
* lowercased words. Property values are objects mapping example index to word
|
* lowercased words. Property values are objects mapping example index to word
|
||||||
@@ -140,59 +177,68 @@ class ExampleBuilder {
|
|||||||
.chunks.filter((chunk) => chunk.names[0] !== this.common);
|
.chunks.filter((chunk) => chunk.names[0] !== this.common);
|
||||||
|
|
||||||
const exampleData = [];
|
const exampleData = [];
|
||||||
const uniqueTags = new Set();
|
await Promise.all(
|
||||||
const promises = chunks.map(async (chunk) => {
|
chunks.map(async (chunk) => {
|
||||||
const [assets, data] = await this.render(compiler.context, chunk);
|
const data = await this.readHtml(compiler.context, chunk);
|
||||||
|
exampleData.push(data);
|
||||||
// collect tags for main page... TODO: implement index tag links
|
})
|
||||||
data.tags.forEach((tag) => uniqueTags.add(tag));
|
);
|
||||||
|
const examples = exampleData.map((data) => {
|
||||||
exampleData.push({
|
return {
|
||||||
link: data.filename,
|
link: data.filename,
|
||||||
example: data.filename,
|
example: data.filename,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
shortdesc: data.shortdesc,
|
shortdesc: data.shortdesc,
|
||||||
tags: data.tags,
|
tags: data.tags,
|
||||||
});
|
};
|
||||||
|
|
||||||
for (const file in assets) {
|
|
||||||
compilation.assets[file] = new RawSource(assets[file]);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(promises);
|
examples.sort((a, b) =>
|
||||||
|
|
||||||
exampleData.sort((a, b) =>
|
|
||||||
a.title.localeCompare(b.title, 'en', {sensitivity: 'base'})
|
a.title.localeCompare(b.title, 'en', {sensitivity: 'base'})
|
||||||
);
|
);
|
||||||
|
const tagIndex = createTagIndex(examples);
|
||||||
const info = {
|
const info = {
|
||||||
examples: exampleData,
|
examples: examples,
|
||||||
index: createWordIndex(exampleData),
|
// Tags for main page... TODO: implement index tag links
|
||||||
tags: Array.from(uniqueTags)
|
// tagIndex: sortObjectByKey(tagIndex),
|
||||||
.sort() // sort twice to get predictable, case insensitve order
|
wordIndex: sortObjectByKey(createWordIndex(examples)),
|
||||||
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'})),
|
|
||||||
};
|
};
|
||||||
|
exampleData.forEach((data) => {
|
||||||
|
data.tags = data.tags.map((tag) => {
|
||||||
|
return {
|
||||||
|
tag: tag,
|
||||||
|
amount: tagIndex[tag.toLowerCase()].length,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
exampleData.map(async (data) => {
|
||||||
|
const assets = await this.render(data, data.chunk);
|
||||||
|
for (const file in assets) {
|
||||||
|
compilation.assets[file] = new RawSource(assets[file]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
const indexSource = `const info = ${JSON.stringify(info)};`;
|
const indexSource = `const info = ${JSON.stringify(info)};`;
|
||||||
compilation.assets['examples-info.js'] = new RawSource(indexSource);
|
compilation.assets['examples-info.js'] = new RawSource(indexSource);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(dir, chunk) {
|
async readHtml(dir, chunk) {
|
||||||
const name = chunk.names[0];
|
const name = chunk.names[0];
|
||||||
|
|
||||||
const assets = {};
|
|
||||||
const readOptions = {encoding: 'utf8'};
|
|
||||||
|
|
||||||
const htmlName = `${name}.html`;
|
const htmlName = `${name}.html`;
|
||||||
const htmlPath = path.join(dir, htmlName);
|
const htmlPath = path.join(dir, htmlName);
|
||||||
const htmlSource = await readFile(htmlPath, readOptions);
|
const htmlSource = await readFile(htmlPath, {encoding: 'utf8'});
|
||||||
|
|
||||||
const {attributes, body} = frontMatter(htmlSource);
|
const {attributes, body} = frontMatter(htmlSource);
|
||||||
|
assert(!!attributes.layout, `missing layout in ${htmlPath}`);
|
||||||
const data = Object.assign(attributes, {contents: body});
|
const data = Object.assign(attributes, {contents: body});
|
||||||
|
|
||||||
data.olVersion = pkg.version;
|
data.olVersion = pkg.version;
|
||||||
data.filename = htmlName;
|
data.filename = htmlName;
|
||||||
|
data.dir = dir;
|
||||||
|
data.chunk = chunk;
|
||||||
|
|
||||||
// process tags
|
// process tags
|
||||||
if (data.tags) {
|
if (data.tags) {
|
||||||
@@ -200,6 +246,14 @@ class ExampleBuilder {
|
|||||||
} else {
|
} else {
|
||||||
data.tags = [];
|
data.tags = [];
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(data, chunk) {
|
||||||
|
const name = chunk.names[0];
|
||||||
|
|
||||||
|
const assets = {};
|
||||||
|
const readOptions = {encoding: 'utf8'};
|
||||||
|
|
||||||
// add in script tag
|
// add in script tag
|
||||||
const jsName = `${name}.js`;
|
const jsName = `${name}.js`;
|
||||||
@@ -234,7 +288,7 @@ class ExampleBuilder {
|
|||||||
|
|
||||||
// check for worker js
|
// check for worker js
|
||||||
const workerName = `${name}.worker.js`;
|
const workerName = `${name}.worker.js`;
|
||||||
const workerPath = path.join(dir, workerName);
|
const workerPath = path.join(data.dir, workerName);
|
||||||
let workerSource;
|
let workerSource;
|
||||||
try {
|
try {
|
||||||
workerSource = await readFile(workerPath, readOptions);
|
workerSource = await readFile(workerPath, readOptions);
|
||||||
@@ -280,7 +334,7 @@ class ExampleBuilder {
|
|||||||
|
|
||||||
// check for example css
|
// check for example css
|
||||||
const cssName = `${name}.css`;
|
const cssName = `${name}.css`;
|
||||||
const cssPath = path.join(dir, cssName);
|
const cssPath = path.join(data.dir, cssName);
|
||||||
let cssSource;
|
let cssSource;
|
||||||
try {
|
try {
|
||||||
cssSource = await readFile(cssPath, readOptions);
|
cssSource = await readFile(cssPath, readOptions);
|
||||||
@@ -321,7 +375,7 @@ class ExampleBuilder {
|
|||||||
'Invalid value for resource: ' +
|
'Invalid value for resource: ' +
|
||||||
resource +
|
resource +
|
||||||
' is not .js or .css: ' +
|
' is not .js or .css: ' +
|
||||||
htmlName
|
data.filename
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,12 +388,11 @@ class ExampleBuilder {
|
|||||||
: '';
|
: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!!attributes.layout, `missing layout in ${htmlPath}`);
|
const templatePath = path.join(this.templates, data.layout);
|
||||||
const templatePath = path.join(this.templates, attributes.layout);
|
|
||||||
const templateSource = await readFile(templatePath, readOptions);
|
const templateSource = await readFile(templatePath, readOptions);
|
||||||
|
|
||||||
assets[htmlName] = handlebars.compile(templateSource)(data);
|
assets[data.filename] = handlebars.compile(templateSource)(data);
|
||||||
return [assets, data];
|
return assets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user