Merge pull request #11313 from MoonE/examples-add-tag-count

Add direct link to other examples to tags
This commit is contained in:
Andreas Hocevar
2020-07-26 12:20:20 +02:00
committed by GitHub
4 changed files with 172 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

@@ -64,6 +64,35 @@ a:hover, a:focus, footer a:hover, footer a:focus {
margin-top: 0;
}
.badge-group {
display: inline-block;
}
.badge-group > .badge:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.badge-group > .badge:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.tag-modal-toggle {
cursor: pointer;
}
.modal-tag-example .modal-body {
padding: 0;
}
.modal-tag-example .list-group-item:focus,
.modal-tag-example .list-group-item:hover,
.modal-tag-example .list-group-item:active {
background-color: rgba(31, 107, 117, .6875);
border-color: #1F6B75;
color: white;
}
.modal-tag-example .list-group-item.active {
background-color: #1F6B75;
color: white;
}
#docs {
margin-top: 1em;
}

View File

@@ -113,9 +113,36 @@
</h4>
<p class="tags">
{{#each tags}}
<a href="./index.html?q={{.}}" class="badge badge-info">{{.}}</a>
<span class="badge-group">
<a
href="./index.html?q={{ ./tag }}" class="badge badge-info">{{ ./tag }}</a
><a
class="badge badge-info tag-modal-toggle text-white"
data-toggle="modal"
data-target="#tag-example-list"
data-title="{{ ./tag }}"
data-content="{{#each ./examples}}
&lt;a class=&quot;list-group-item list-group-item-action{{#if ./isCurrent}} active{{/if}}&quot; href=&quot;./{{ ./link }}&quot;&gt;{{escape ./title}}&lt;/a&gt;{{/each}}"
tabindex="0"
>{{ ./examples.length }}</a>
</span>
{{/each}}
</p>
<div class="modal modal-tag-example" id="tag-example-list" tabindex="-1" role="dialog" aria-labelledby="tag-example-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="tag-example-title"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="list-group"></div>
</div>
</div>
</div>
</div>
{{{ contents }}}
</div>
<form method="POST" id="codepen-form" target="_blank" action="https://codesandbox.io/api/v1/sandboxes/define">
@@ -186,6 +213,16 @@
<script src="./resources/common.js"></script>
<script src="./resources/prism/prism.min.js"></script>
{{{ js.tag }}}
<script>
$('#tag-example-list').on('show.bs.modal', function (event) {
const button = $(event.relatedTarget); // Button that triggered the modal
const title = button.data('title');
const content = button.data('content');
const modal = $(this)
modal.find('.modal-title').text(title);
modal.find('.modal-body').html(content);
});
</script>
</body>
<script>
var packageUrl = 'https://raw.githubusercontent.com/openlayers/openlayers.github.io/build/package.json';

View File

@@ -18,6 +18,13 @@ handlebars.registerHelper(
(str) => new handlebars.SafeString(marked(str))
);
/**
* Used to doube-escape the title when stored as data-* attribute.
*/
handlebars.registerHelper('escape', (text) => {
return handlebars.Utils.escapeExpression(text);
});
handlebars.registerHelper('indent', (text, options) => {
if (!text) {
return text;
@@ -30,6 +37,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 +184,76 @@ 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) => {
const tagExamples = tagIndex[tag.toLowerCase()];
return {
tag: tag,
examples: tagExamples.map((exampleIdx) => {
const example = examples[exampleIdx];
return {
link: example.link,
title: example.title,
isCurrent: data.filename === example.link,
};
}),
};
});
});
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 +261,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 +303,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 +349,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 +390,7 @@ class ExampleBuilder {
'Invalid value for resource: ' +
resource +
' is not .js or .css: ' +
htmlName
data.filename
);
}
}
@@ -334,12 +403,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;
}
}