diff --git a/website/_harp.json b/website/_harp.json
index 7c69beef0..bc1a0b5e5 100644
--- a/website/_harp.json
+++ b/website/_harp.json
@@ -84,8 +84,8 @@
],
"ALPHA": true,
- "V_CSS": "2.0a1",
- "V_JS": "2.0a0",
+ "V_CSS": "2.0a2",
+ "V_JS": "2.0a1",
"DEFAULT_SYNTAX": "python",
"ANALYTICS": "UA-58931649-1",
"MAILCHIMP": {
diff --git a/website/_includes/_scripts.jade b/website/_includes/_scripts.jade
index 5ecdd0711..e1d9f773a 100644
--- a/website/_includes/_scripts.jade
+++ b/website/_includes/_scripts.jade
@@ -1,43 +1,80 @@
//- 💫 INCLUDES > SCRIPTS
if quickstart
- script(src="/assets/js/quickstart.min.js")
+ script(src="/assets/js/vendor/quickstart.min.js")
if IS_PAGE
- script(src="/assets/js/in-view.min.js")
+ script(src="/assets/js/vendor/in-view.min.js")
if environment == "deploy"
script(async src="https://www.google-analytics.com/analytics.js")
-script(src="/assets/js/prism.min.js")
-script(src="/assets/js/main.js?v#{V_JS}")
+script(src="/assets/js/vendor/prism.min.js")
+
+if SECTION == "models"
+ script(src="/assets/js/vendor/chart.min.js")
+ script(src="/assets/js/models.js?v#{V_JS}" type="module")
script
- | new ProgressBar('.js-progress');
-
- if changelog
- | new Changelog('!{SOCIAL.github}', 'spacy');
-
if quickstart
| new Quickstart("#qs");
- if IS_PAGE
- | new SectionHighlighter('data-section', 'data-nav');
- | new GitHubEmbed('!{SOCIAL.github}', 'data-gh-embed');
- | ((window.gitter = {}).chat = {}).options = {
- | useStyles: false,
- | activationElement: '.js-gitter-button',
- | targetElement: '.js-gitter',
- | room: '!{SOCIAL.gitter}'
- | };
-
- if HAS_MODELS
- | new ModelLoader('!{MODELS_REPO}', !{JSON.stringify(CURRENT_MODELS)}, !{JSON.stringify(MODEL_LICENSES)}, !{JSON.stringify(MODEL_BENCHMARKS)});
-
if environment == "deploy"
| window.ga=window.ga||function(){
| (ga.q=ga.q||[]).push(arguments)}; ga.l=+new Date;
| ga('create', '#{ANALYTICS}', 'auto'); ga('send', 'pageview');
+
if IS_PAGE
+ script
+ | ((window.gitter = {}).chat = {}).options = {
+ | useStyles: false,
+ | activationElement: '.js-gitter-button',
+ | targetElement: '.js-gitter',
+ | room: '!{SOCIAL.gitter}'
+ | };
script(src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer)
+
+
+//- JS modules – slightly hacky, but necessary to dynamically instantiate the
+ classes with data from the Harp JSON files, while still being able to
+ support older browsers that can't handle JS modules. More details:
+ https://medium.com/dev-channel/es6-modules-in-chrome-canary-m60-ba588dfb8ab7
+
+- ProgressBar = "new ProgressBar('.js-progress');"
+- Changelog = "new Changelog('" + SOCIAL.github + "', 'spacy');"
+- NavHighlighter = "new NavHighlighter('data-section', 'data-nav');"
+- GitHubEmbed = "new GitHubEmbed('" + SOCIAL.github + "', 'data-gh-embed');"
+- ModelLoader = "new ModelLoader('" + MODELS_REPO + "'," + JSON.stringify(CURRENT_MODELS) + "," + JSON.stringify(MODEL_LICENSES) + "," + JSON.stringify(MODEL_BENCHMARKS) + ");"
+
+//- Browsers with JS module support.
+ Will be ignored otherwise.
+
+script(type="module")
+ | import ProgressBar from '/assets/js/progress.js';
+ !=ProgressBar
+ if changelog
+ | import Changelog from '/assets/js/changelog.js';
+ !=Changelog
+ if IS_PAGE
+ | import NavHighlighter from '/assets/js/nav-highlighter.js';
+ !=NavHighlighter
+ | import GitHubEmbed from '/assets/js/github-embed.js';
+ !=GitHubEmbed
+ if HAS_MODELS
+ | import { ModelLoader } from '/assets/js/models.js';
+ !=ModelLoader
+
+//- Browsers with no JS module support.
+ Won't be fetched or interpreted otherwise.
+
+script(nomodule src="/assets/js/rollup.js")
+script(nomodule)
+ !=ProgressBar
+ if changelog
+ !=Changelog
+ if IS_PAGE
+ !=NavHighlighter
+ !=GitHubEmbed
+ if HAS_MODELS
+ !=ModeLoader
diff --git a/website/assets/js/changelog.js b/website/assets/js/changelog.js
new file mode 100644
index 000000000..94f2149ad
--- /dev/null
+++ b/website/assets/js/changelog.js
@@ -0,0 +1,72 @@
+'use strict';
+
+import { Templater, handleResponse } from './util.js';
+
+export default class Changelog {
+ /**
+ * Fetch and render changelog from GitHub. Clones a template node (table row)
+ * to avoid doubling templating markup in JavaScript.
+ * @param {string} user - GitHub username.
+ * @param {string} repo - Repository to fetch releases from.
+ */
+ constructor(user, repo) {
+ this.url = `https://api.github.com/repos/${user}/${repo}/releases`;
+ this.template = new Templater('changelog');
+ this.fetchChangelog()
+ .then(json => this.render(json))
+ .catch(this.showError.bind(this));
+ // make sure scroll positions for progress bar etc. are recalculated
+ window.dispatchEvent(new Event('resize'));
+ }
+
+ fetchChangelog() {
+ return new Promise((resolve, reject) =>
+ fetch(this.url)
+ .then(res => handleResponse(res))
+ .then(json => json.ok ? resolve(json) : reject()))
+ }
+
+ showError() {
+ this.template.get('error').style.display = 'block';
+ }
+
+ /**
+ * Get template section from template row. Hacky, but does make sense.
+ * @param {node} item - Parent element.
+ * @param {string} id - ID of child element, set via data-changelog.
+ */
+ getField(item, id) {
+ return item.querySelector(`[data-changelog="${id}"]`);
+ }
+
+ render(json) {
+ this.template.get('table').style.display = 'block';
+ this.row = this.template.get('item');
+ this.releases = this.template.get('releases');
+ this.prereleases = this.template.get('prereleases');
+ Object.values(json)
+ .filter(release => release.name)
+ .forEach(release => this.renderRelease(release));
+ this.row.remove();
+ }
+
+ /**
+ * Clone the template row and populate with content from API response.
+ * https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
+ * @param {string} name - Release title.
+ * @param {string} tag (tag_name) - Release tag.
+ * @param {string} url (html_url) - URL to the release page on GitHub.
+ * @param {string} date (published_at) - Timestamp of release publication.
+ * @param {boolean} prerelease - Whether the release is a prerelease.
+ */
+ renderRelease({ name, tag_name: tag, html_url: url, published_at: date, prerelease }) {
+ const container = prerelease ? this.prereleases : this.releases;
+ const tagLink = `${tag}
`;
+ const title = (name.split(': ').length == 2) ? name.split(': ')[1] : name;
+ const row = this.row.cloneNode(true);
+ this.getField(row, 'date').textContent = date.split('T')[0];
+ this.getField(row, 'tag').innerHTML = tagLink;
+ this.getField(row, 'title').textContent = title;
+ container.appendChild(row);
+ }
+}
diff --git a/website/assets/js/github-embed.js b/website/assets/js/github-embed.js
new file mode 100644
index 000000000..58e80ee1a
--- /dev/null
+++ b/website/assets/js/github-embed.js
@@ -0,0 +1,36 @@
+'use strict';
+
+import { $$ } from './util.js';
+
+export default class GitHubEmbed {
+ /**
+ * Embed code from GitHub repositories, similar to Gist embeds. Fetches the
+ * raw text and places it inside element.
+ * Usage:
+ * @param {string} user - GitHub user or organization. + * @param {string} attr - Data attribute used to select containers. Attribute + * value should be path to file relative to user. + */ + constructor(user, attr) { + this.url = `https://raw.githubusercontent.com/${user}`; + this.attr = attr; + this.error = `\nCan't fetch code example from GitHub :(\n\nPlease use the link below to view the example. If you've come across\na broken link, we always appreciate a pull request to the repository,\nor a report on the issue tracker. Thanks!`; + [...$$(`[${this.attr}]`)].forEach(el => this.embed(el)); + } + + /** + * Fetch code from GitHub and insert it as element content. File path is + * read off the container's data attribute. + * @param {node} el - The element. + */ + embed(el) { + el.parentElement.setAttribute('data-loading', ''); + fetch(`${this.url}/${el.getAttribute(this.attr)}`) + .then(res => res.text().then(text => ({ text, ok: res.ok }))) + .then(({ text, ok }) => { + el.textContent = ok ? text : this.error; + if (ok && window.Prism) Prism.highlightElement(el); + }) + el.parentElement.removeAttribute('data-loading'); + } +} diff --git a/website/assets/js/main.js b/website/assets/js/main.js deleted file mode 100644 index d9465bb67..000000000 --- a/website/assets/js/main.js +++ /dev/null @@ -1,323 +0,0 @@ -//- 💫 MAIN JAVASCRIPT -//- Note: Will be compiled using Babel before deployment. - -'use strict' - -const $ = document.querySelector.bind(document); -const $$ = document.querySelectorAll.bind(document); - - -class ProgressBar { - /** - * Animated reading progress bar. - * @param {String} selector – CSS selector of progress bar element. - */ - constructor(selector) { - this.el = $(selector); - this.scrollY = 0; - this.sizes = this.updateSizes(); - this.el.setAttribute('max', 100); - this.init(); - } - - init() { - window.addEventListener('scroll', () => { - this.scrollY = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0); - requestAnimationFrame(this.update.bind(this)); - }, false); - window.addEventListener('resize', () => { - this.sizes = this.updateSizes(); - requestAnimationFrame(this.update.bind(this)); - }) - } - - update() { - const offset = 100 - ((this.sizes.height - this.scrollY - this.sizes.vh) / this.sizes.height * 100); - this.el.setAttribute('value', (this.scrollY == 0) ? 0 : offset || 0); - } - - updateSizes() { - const body = document.body; - const html = document.documentElement; - return { - height: Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), - vh: Math.max(html.clientHeight, window.innerHeight || 0) - } - } -} - - -class SectionHighlighter { - /** - * Hightlight section in viewport in sidebar, using in-view library. - * @param {String} sectionAttr - Data attribute of sections. - * @param {String} navAttr - Data attribute of navigation items. - * @param {String} activeClass – Class name of active element. - */ - constructor(sectionAttr, navAttr, activeClass = 'is-active') { - this.sections = [...$$(`[${navAttr}]`)]; - this.navAttr = navAttr; - this.sectionAttr = sectionAttr; - this.activeClass = activeClass; - inView(`[${sectionAttr}]`).on('enter', this.highlightSection.bind(this)); - } - - highlightSection(section) { - const id = section.getAttribute(this.sectionAttr); - const el = $(`[${this.navAttr}="${id}"]`); - if (el) { - this.sections.forEach(el => el.classList.remove(this.activeClass)); - el.classList.add(this.activeClass); - } - } -} - - -class Templater { - /** - * Mini templating engine based on data attributes. Selects elements based - * on a data-tpl and data-tpl-key attribute and can set textContent - * and innterHtml. - * - * @param {String} templateId - Template section, e.g. value of data-tpl. - */ - constructor(templateId) { - this.templateId = templateId; - } - - get(key) { - return $(`[data-tpl="${this.templateId}"][data-tpl-key="${key}"]`); - } - - fill(key, value, html = false) { - const el = this.get(key); - if (html) el.innerHTML = value || ''; - else el.textContent = value || ''; - return el; - } -} - - -class ModelLoader { - /** - * Load model meta from GitHub and update model details on site. Uses the - * Templater mini template engine to update DOM. - * - * @param {String} repo - Path tp GitHub repository containing releases. - * @param {Array} models - List of model IDs, e.g. "en_core_web_sm". - * @param {Object} licenses - License IDs mapped to URLs. - * @param {Object} accKeys - Available accuracy keys mapped to display labels. - */ - constructor(repo, models = [], licenses = {}, benchmarkKeys = {}) { - this.url = `https://raw.githubusercontent.com/${repo}/master`; - this.repo = `https://github.com/${repo}`; - this.modelIds = models; - this.licenses = licenses; - this.benchKeys = benchmarkKeys; - this.init(); - } - - init() { - this.modelIds.forEach(modelId => - new Templater(modelId).get('table').setAttribute('data-loading', '')); - fetch(`${this.url}/compatibility.json`) - .then(res => this.handleResponse(res)) - .then(json => json.ok ? this.getModels(json['spacy']) : this.modelIds.forEach(modelId => this.showError(modelId))) - } - - handleResponse(res) { - if (res.ok) return res.json().then(json => Object.assign({}, json, { ok: res.ok })) - else return ({ ok: res.ok }) - } - - convertNumber(num, separator = ',') { - return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); - } - - getModels(compat) { - this.compat = compat; - for (let modelId of this.modelIds) { - const version = this.getLatestVersion(modelId, compat); - if (!version) { - this.showError(modelId); return; - } - fetch(`${this.url}/meta/${modelId}-${version}.json`) - .then(res => this.handleResponse(res)) - .then(json => json.ok ? this.render(json) : this.showError(modelId)) - } - // make sure scroll positions for progress bar etc. are recalculated - window.dispatchEvent(new Event('resize')); - } - - showError(modelId) { - const template = new Templater(modelId); - template.get('table').removeAttribute('data-loading'); - template.get('error').style.display = 'block'; - for (let key of ['sources', 'pipeline', 'vectors', 'author', 'license']) { - template.get(key).parentElement.parentElement.style.display = 'none'; - } - } - - /** - * Update model details in tables. Currently quite hacky :( - */ - render({ lang, name, version, sources, pipeline, vectors, url, author, license, accuracy, speed, size, description, notes }) { - const modelId = `${lang}_${name}`; - const model = `${modelId}-${version}`; - const template = new Templater(modelId); - - const getSources = s => (s instanceof Array) ? s.join(', ') : s; - const getPipeline = p => p.map(comp => `${comp}
`).join(', '); - const getVectors = v => `${this.convertNumber(v.entries)} (${v.width} dimensions)`; - const getLink = (t, l) => `${t}`; - - const keys = { version, size, description, notes } - Object.keys(keys).forEach(key => template.fill(key, keys[key])); - - if (sources) template.fill('sources', getSources(sources)); - if (pipeline && pipeline.length) template.fill('pipeline', getPipeline(pipeline), true); - else template.get('pipeline').parentElement.parentElement.style.display = 'none'; - if (vectors) template.fill('vectors', getVectors(vectors)); - else template.get('vectors').parentElement.parentElement.style.display = 'none'; - - if (author) template.fill('author', url ? getLink(author, url) : author, true); - if (license) template.fill('license', this.licenses[license] ? getLink(license, this.licenses[license]) : license, true); - - template.get('download').setAttribute('href', `${this.repo}/releases/tag/${model}`); - - this.renderBenchmarks(template, accuracy, speed); - this.renderCompat(template, modelId); - template.get('table').removeAttribute('data-loading'); - } - - renderBenchmarks(template, accuracy = {}, speed = {}) { - if (!accuracy && !speed) return; - template.get('benchmarks').style.display = 'block'; - this.renderTable(template, 'parser', accuracy, val => val.toFixed(2)); - this.renderTable(template, 'ner', accuracy, val => val.toFixed(2)); - this.renderTable(template, 'speed', speed, Math.round); - } - - renderTable(template, id, benchmarks, convertVal = val => val) { - if (!this.benchKeys[id]Â || !Object.keys(this.benchKeys[id]).some(key => benchmarks[key])) return; - const keys = Object.keys(this.benchKeys[id]).map(k => benchmarks[k] ? k : false).filter(k => k); - template.get(id).style.display = 'block'; - for (let key of keys) { - template - .fill(key, this.convertNumber(convertVal(benchmarks[key]))) - .parentElement.style.display = 'table-row'; - } - } - - renderCompat(template, modelId) { - template.get('compat-wrapper').style.display = 'table-row'; - const options = Object.keys(this.compat).map(v => ``).join(''); - template - .fill('compat', '' + options, true) - .addEventListener('change', ev => { - const result = this.compat[ev.target.value][modelId]; - if (result) template.fill('compat-versions', `${modelId}-${result[0]}
`, true); - else template.fill('compat-versions', ''); - }); - } - - getLatestVersion(model, compat = {}) { - for (let spacy_v of Object.keys(compat)) { - const models = compat[spacy_v]; - if (models[model]) return models[model][0]; - } - } -} - - -class Changelog { - /** - * Fetch and render changelog from GitHub. Clones a template node (table row) - * to avoid doubling templating markup in JavaScript. - * - * @param {String} user - GitHub username. - * @param {String} repo - Repository to fetch releases from. - */ - constructor(user, repo) { - this.url = `https://api.github.com/repos/${user}/${repo}/releases`; - this.template = new Templater('changelog'); - fetch(this.url) - .then(res => this.handleResponse(res)) - .then(json => json.ok ? this.render(json) : false) - } - - /** - * Get template section from template row. Slightly hacky, but does make sense. - */ - $(item, id) { - return item.querySelector(`[data-changelog="${id}"]`); - } - - handleResponse(res) { - if (res.ok) return res.json().then(json => Object.assign({}, json, { ok: res.ok })) - else return ({ ok: res.ok }) - } - - render(json) { - this.template.get('error').style.display = 'none'; - this.template.get('table').style.display = 'block'; - this.row = this.template.get('item'); - this.releases = this.template.get('releases'); - this.prereleases = this.template.get('prereleases'); - Object.values(json) - .filter(release => release.name) - .forEach(release => this.renderRelease(release)); - this.row.remove(); - // make sure scroll positions for progress bar etc. are recalculated - window.dispatchEvent(new Event('resize')); - } - - /** - * Clone the template row and populate with content from API response. - * https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository - * - * @param {String} name - Release title. - * @param {String} tag (tag_name) - Release tag. - * @param {String} url (html_url) - URL to the release page on GitHub. - * @param {String} date (published_at) - Timestamp of release publication. - * @param {Boolean} pre (prerelease) - Whether the release is a prerelease. - */ - renderRelease({ name, tag_name: tag, html_url: url, published_at: date, prerelease: pre }) { - const container = pre ? this.prereleases : this.releases; - const row = this.row.cloneNode(true); - this.$(row, 'date').textContent = date.split('T')[0]; - this.$(row, 'tag').innerHTML = `${tag}
`; - this.$(row, 'title').textContent = (name.split(': ').length == 2) ? name.split(': ')[1] : name; - container.appendChild(row); - } -} - - -class GitHubEmbed { - /** - * Embed code from GitHub repositories, similar to Gist embeds. Fetches the - * raw text and places it inside element. - * Usage:- * - * @param {String} user - GitHub user or organization. - * @param {String} attr - Data attribute used to select containers. Attribute - * value should be path to file relative to user. - */ - constructor(user, attr) { - this.url = `https://raw.githubusercontent.com/${user}`; - this.attr = attr; - this.error = `\nCan't fetch code example from GitHub :(\n\nPlease use the link below to view the example. If you've come across\na broken link, we always appreciate a pull request to the repository,\nor a report on the issue tracker. Thanks!`; - [...$$(`[${this.attr}]`)].forEach(el => this.embed(el)); - } - - embed(el) { - el.parentElement.setAttribute('data-loading', ''); - fetch(`${this.url}/${el.getAttribute(this.attr)}`) - .then(res => res.text().then(text => ({ text, ok: res.ok }))) - .then(({ text, ok }) => { - el.textContent = ok ? text : this.error; - if (ok && window.Prism) Prism.highlightElement(el); - }) - el.parentElement.removeAttribute('data-loading'); - } -} diff --git a/website/assets/js/models.js b/website/assets/js/models.js new file mode 100644 index 000000000..5fe7ff54a --- /dev/null +++ b/website/assets/js/models.js @@ -0,0 +1,160 @@ +'use strict'; + +import { Templater, handleResponse, convertNumber } from './util.js'; + +/** + * Chart.js defaults + */ +Chart.defaults.global.legend.position = 'bottom'; +Chart.defaults.global.defaultFontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'"; +const CHART_COLORS = { model1: '#09a3d5', model2: '#066B8C' }; + +/** + * Formatters for model details. + * @property {function} author – Format model author with optional link. + * @property {function} license - Format model license with optional link. + * @property {function} sources - Format training data sources (list or string). + * @property {function} pipeline - Format list of pipeline components. + * @property {function} vectors - Format vector data (entries and dimensions). + * @property {function} version - Format model version number. + */ +export const formats = { + author: (author, url) => url ? `${author}` : author, + license: (license, url) => url ? `${license}` : license, + sources: sources => (sources instanceof Array) ? sources.join(', ') : sources, + pipeline: pipes => (pipes && pipes.length) ? pipes.map(p => `${p}
`).join(', ') : '-', + vectors: vec => vec ? `${convertNumber(vec.entries)} (${vec.width} dimensions)` : 'n/a', + version: version => `v${version}
` +}; + +/** + * Find the latest version of a model in a compatibility table. + * @param {string} model - The model name. + * @param {Object} compat - Compatibility table, keyed by spaCy version. + */ +export const getLatestVersion = (model, compat = {}) => { + for (let [spacy_v, models] of Object.entries(compat)) { + if (models[model]) return models[model][0]; + } +}; + +export class ModelLoader { + /** + * Load model meta from GitHub and update model details on site. Uses the + * Templater mini template engine to update DOM. + * @param {string} repo - Path tp GitHub repository containing releases. + * @param {Array} models - List of model IDs, e.g. "en_core_web_sm". + * @param {Object} licenses - License IDs mapped to URLs. + * @param {Object} benchmarkKeys - Objects of available keys by type, e.g. + * 'parser', 'ner', 'speed', mapped to labels. + */ + constructor(repo, models = [], licenses = {}, benchmarkKeys = {}) { + this.url = `https://raw.githubusercontent.com/${repo}/master`; + this.repo = `https://github.com/${repo}`; + this.modelIds = models; + this.licenses = licenses; + this.benchKeys = benchmarkKeys; + this.init(); + } + + init() { + this.modelIds.forEach(modelId => + new Templater(modelId).get('table').setAttribute('data-loading', '')); + this.fetch(`${this.url}/compatibility.json`) + .then(json => this.getModels(json.spacy)) + .catch(_ => this.modelIds.forEach(modelId => this.showError(modelId))); + // make sure scroll positions for progress bar etc. are recalculated + window.dispatchEvent(new Event('resize')); + } + + fetch(url) { + return new Promise((resolve, reject) => + fetch(url).then(res => handleResponse(res)) + .then(json => json.ok ? resolve(json) : reject())) + } + + getModels(compat) { + this.compat = compat; + for (let modelId of this.modelIds) { + const version = getLatestVersion(modelId, compat); + if (version) this.fetch(`${this.url}/meta/${modelId}-${version}.json`) + .then(json => this.render(json)) + .catch(_ => this.showError(modelId)) + else this.showError(modelId); + } + } + + showError(modelId) { + const tpl = new Templater(modelId); + tpl.get('table').removeAttribute('data-loading'); + tpl.get('error').style.display = 'block'; + for (let key of ['sources', 'pipeline', 'vectors', 'author', 'license']) { + tpl.get(key).parentElement.parentElement.style.display = 'none'; + } + } + + /** + * Update model details in tables. Currently quite hacky :( + */ + render(data) { + const modelId = `${data.lang}_${data.name}`; + const model = `${modelId}-${data.version}`; + const tpl = new Templater(modelId); + this.renderDetails(tpl, data) + this.renderBenchmarks(tpl, data.accuracy, data.speed); + this.renderCompat(tpl, modelId); + tpl.get('download').setAttribute('href', `${this.repo}/releases/tag/${model}`); + tpl.get('table').removeAttribute('data-loading'); + } + + renderDetails(tpl, { version, size, description, notes, author, url, + license, sources, vectors, pipeline }) { + const basics = { version, size, description, notes } + for (let [key, value] of Object.entries(basics)) { + if (value) tpl.fill(key, value); + } + if (author) tpl.fill('author', formats.author(author, url), true); + if (license) tpl.fill('license', formats.license(license, this.licenses[license]), true); + if (sources) tpl.fill('sources', formats.sources(sources)); + if (vectors) tpl.fill('vectors', formats.vectors(vectors)); + else tpl.get('vectors').parentElement.parentElement.style.display = 'none'; + if (pipeline && pipeline.length) tpl.fill('pipeline', formats.pipeline(pipeline), true); + else tpl.get('pipeline').parentElement.parentElement.style.display = 'none'; + } + + renderBenchmarks(tpl, accuracy = {}, speed = {}) { + if (!accuracy && !speed) return; + this.renderTable(tpl, 'parser', accuracy, val => val.toFixed(2)); + this.renderTable(tpl, 'ner', accuracy, val => val.toFixed(2)); + this.renderTable(tpl, 'speed', speed, Math.round); + tpl.get('benchmarks').style.display = 'block'; + } + + renderTable(tpl, id, benchmarks, converter = val => val) { + if (!this.benchKeys[id]Â || !Object.keys(this.benchKeys[id]).some(key => benchmarks[key])) return; + for (let key of Object.keys(this.benchKeys[id])) { + if (benchmarks[key]) tpl + .fill(key, convertNumber(converter(benchmarks[key]))) + .parentElement.style.display = 'table-row'; + } + tpl.get(id).style.display = 'block'; + } + + renderCompat(tpl, modelId) { + tpl.get('compat-wrapper').style.display = 'table-row'; + const header = ''; + const options = Object.keys(this.compat) + .map(v => ``) + .join(''); + tpl + .fill('compat', header + options, true) + .addEventListener('change', ({ target: { value }}) => + tpl.fill('compat-versions', this.getCompat(value, modelId), true)) + } + + getCompat(version, model) { + const res = this.compat[version][model]; + return res ? `${model}-${res[0]}
` : 'not compatible'; + } +} + diff --git a/website/assets/js/nav-highlighter.js b/website/assets/js/nav-highlighter.js new file mode 100644 index 000000000..40f708e5e --- /dev/null +++ b/website/assets/js/nav-highlighter.js @@ -0,0 +1,33 @@ +'use strict'; + +import { $, $$ } from './util.js'; + +export default class NavHighlighter { + /** + * Hightlight section in viewport in sidebar, using in-view library. + * @param {string} sectionAttr - Data attribute of sections. + * @param {string} navAttr - Data attribute of navigation items. + * @param {string} activeClass – Class name of active element. + */ + constructor(sectionAttr, navAttr, activeClass = 'is-active') { + this.sections = [...$$(`[${navAttr}]`)]; + this.navAttr = navAttr; + this.sectionAttr = sectionAttr; + this.activeClass = activeClass; + if (window.inView) inView(`[${sectionAttr}]`) + .on('enter', this.highlightSection.bind(this)); + } + + /** + * Check if section in view exists in sidebar and mark as active. + * @param {node} section - The section in view. + */ + highlightSection(section) { + const id = section.getAttribute(this.sectionAttr); + const el = $(`[${this.navAttr}="${id}"]`); + if (el) { + this.sections.forEach(el => el.classList.remove(this.activeClass)); + el.classList.add(this.activeClass); + } + } +} diff --git a/website/assets/js/progress.js b/website/assets/js/progress.js new file mode 100644 index 000000000..1497547d8 --- /dev/null +++ b/website/assets/js/progress.js @@ -0,0 +1,52 @@ +'use strict'; + +import { $ } from './util.js'; + +export default class ProgressBar { + /** + * Animated reading progress bar. + * @param {string} selector – CSS selector of progress bar element. + */ + constructor(selector) { + this.scrollY = 0; + this.sizes = this.updateSizes(); + this.el = $(selector); + this.el.setAttribute('max', 100); + window.addEventListener('scroll', this.onScroll.bind(this)); + window.addEventListener('resize', this.onResize.bind(this)); + } + + onScroll(ev) { + this.scrollY = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0); + requestAnimationFrame(this.update.bind(this)); + } + + onResize(ev) { + this.sizes = this.updateSizes(); + requestAnimationFrame(this.update.bind(this)); + } + + update() { + const offset = 100 - ((this.sizes.height - this.scrollY - this.sizes.vh) / this.sizes.height * 100); + this.el.setAttribute('value', (this.scrollY == 0) ? 0 : offset || 0); + } + + /** + * Update scroll and viewport height. Called on load and window resize. + */ + updateSizes() { + return { + height: Math.max( + document.body.scrollHeight, + document.body.offsetHeight, + document.documentElement.clientHeight, + document.documentElement.scrollHeight, + document.documentElement.offsetHeight + ), + vh: Math.max( + document.documentElement.clientHeight, + window.innerHeight || 0 + ) + } + } +} diff --git a/website/assets/js/rollup.js b/website/assets/js/rollup.js new file mode 100644 index 000000000..00ff92fa9 --- /dev/null +++ b/website/assets/js/rollup.js @@ -0,0 +1,23 @@ +/** + * This file is bundled by Rollup, compiled with Babel and included as + *