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:
Maximilian Krög
2020-07-19 17:21:32 +02:00
parent aa0a895212
commit 50404ca409
3 changed files with 91 additions and 38 deletions

View File

@@ -43,15 +43,15 @@
}
};
words.forEach(function (word) {
const dict = info.index[word];
const dict = info.wordIndex[word];
if (dict) {
updateScores(dict, word);
} else {
const r = new RegExp(word);
// eslint-disable-next-line prefer-const
for (let idx in info.index) {
for (let idx in info.wordIndex) {
if (r.test(idx)) {
updateScores(info.index[idx], word);
updateScores(info.wordIndex[idx], word);
}
}
}

View File

@@ -113,7 +113,7 @@
</h4>
<p class="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}}
</p>
{{{ contents }}}

View File

@@ -30,6 +30,43 @@ handlebars.registerHelper('indent', (text, options) => {
.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
* 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);
const exampleData = [];
const uniqueTags = new Set();
const promises = chunks.map(async (chunk) => {
const [assets, data] = await this.render(compiler.context, chunk);
// collect tags for main page... TODO: implement index tag links
data.tags.forEach((tag) => uniqueTags.add(tag));
exampleData.push({
await Promise.all(
chunks.map(async (chunk) => {
const data = await this.readHtml(compiler.context, chunk);
exampleData.push(data);
})
);
const examples = exampleData.map((data) => {
return {
link: data.filename,
example: data.filename,
title: data.title,
shortdesc: data.shortdesc,
tags: data.tags,
};
});
examples.sort((a, b) =>
a.title.localeCompare(b.title, 'en', {sensitivity: 'base'})
);
const tagIndex = createTagIndex(examples);
const info = {
examples: examples,
// Tags for main page... TODO: implement index tag links
// tagIndex: sortObjectByKey(tagIndex),
wordIndex: sortObjectByKey(createWordIndex(examples)),
};
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]);
}
});
await Promise.all(promises);
exampleData.sort((a, b) =>
a.title.localeCompare(b.title, 'en', {sensitivity: 'base'})
})
);
const info = {
examples: exampleData,
index: createWordIndex(exampleData),
tags: Array.from(uniqueTags)
.sort() // sort twice to get predictable, case insensitve order
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'})),
};
const indexSource = `const info = ${JSON.stringify(info)};`;
compilation.assets['examples-info.js'] = new RawSource(indexSource);
});
}
async render(dir, chunk) {
async readHtml(dir, chunk) {
const name = chunk.names[0];
const assets = {};
const readOptions = {encoding: 'utf8'};
const htmlName = `${name}.html`;
const htmlPath = path.join(dir, htmlName);
const htmlSource = await readFile(htmlPath, readOptions);
const htmlSource = await readFile(htmlPath, {encoding: 'utf8'});
const {attributes, body} = frontMatter(htmlSource);
assert(!!attributes.layout, `missing layout in ${htmlPath}`);
const data = Object.assign(attributes, {contents: body});
data.olVersion = pkg.version;
data.filename = htmlName;
data.dir = dir;
data.chunk = chunk;
// process tags
if (data.tags) {
@@ -200,6 +246,14 @@ class ExampleBuilder {
} else {
data.tags = [];
}
return data;
}
async render(data, chunk) {
const name = chunk.names[0];
const assets = {};
const readOptions = {encoding: 'utf8'};
// add in script tag
const jsName = `${name}.js`;
@@ -234,7 +288,7 @@ class ExampleBuilder {
// check for worker js
const workerName = `${name}.worker.js`;
const workerPath = path.join(dir, workerName);
const workerPath = path.join(data.dir, workerName);
let workerSource;
try {
workerSource = await readFile(workerPath, readOptions);
@@ -280,7 +334,7 @@ class ExampleBuilder {
// check for example css
const cssName = `${name}.css`;
const cssPath = path.join(dir, cssName);
const cssPath = path.join(data.dir, cssName);
let cssSource;
try {
cssSource = await readFile(cssPath, readOptions);
@@ -321,7 +375,7 @@ class ExampleBuilder {
'Invalid value for resource: ' +
resource +
' 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, attributes.layout);
const templatePath = path.join(this.templates, data.layout);
const templateSource = await readFile(templatePath, readOptions);
assets[htmlName] = handlebars.compile(templateSource)(data);
return [assets, data];
assets[data.filename] = handlebars.compile(templateSource)(data);
return assets;
}
}