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) { 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);
} }
} }
} }

View File

@@ -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 }}}

View File

@@ -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;
} }
} }