mirror of
https://github.com/explosion/spaCy.git
synced 2024-12-25 17:36:30 +03:00
Update JavaScript
This commit is contained in:
parent
a8ff8423bb
commit
49b58d35fd
|
@ -1,27 +1,46 @@
|
||||||
//- 💫 INCLUDES > SCRIPTS
|
//- 💫 INCLUDES > SCRIPTS
|
||||||
|
|
||||||
script(src="/assets/js/main.js?v#{V_JS}")
|
if quickstart
|
||||||
script(src="/assets/js/prism.js")
|
script(src="/assets/js/quickstart.min.js")
|
||||||
|
|
||||||
if SECTION == "docs"
|
if IS_PAGE
|
||||||
if quickstart
|
script(src="/assets/js/in-view.min.js")
|
||||||
script(src="/assets/js/quickstart.js")
|
|
||||||
script var qs = new Quickstart("#qs")
|
|
||||||
|
|
||||||
script.
|
if HAS_MODELS
|
||||||
((window.gitter = {}).chat = {}).options = {
|
script(src="/assets/js/chart.min.js")
|
||||||
useStyles: false,
|
|
||||||
activationElement: '.js-gitter-button',
|
|
||||||
targetElement: '.js-gitter',
|
|
||||||
room: '!{SOCIAL.gitter}'
|
|
||||||
};
|
|
||||||
|
|
||||||
script(src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer)
|
|
||||||
|
|
||||||
if environment == "deploy"
|
if environment == "deploy"
|
||||||
script
|
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
|
||||||
|
| 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_ACCURACY)});
|
||||||
|
|
||||||
|
if environment == "deploy"
|
||||||
| window.ga=window.ga||function(){
|
| window.ga=window.ga||function(){
|
||||||
| (ga.q=ga.q||[]).push(arguments)}; ga.l=+new Date;
|
| (ga.q=ga.q||[]).push(arguments)}; ga.l=+new Date;
|
||||||
| ga('create', '#{ANALYTICS}', 'auto'); ga('send', 'pageview');
|
| ga('create', '#{ANALYTICS}', 'auto'); ga('send', 'pageview');
|
||||||
|
|
||||||
script(async src="https://www.google-analytics.com/analytics.js")
|
if IS_PAGE
|
||||||
|
script(src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer)
|
||||||
|
|
14
website/assets/js/chart.min.js
vendored
Normal file
14
website/assets/js/chart.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
website/assets/js/in-view.min.js
vendored
Normal file
6
website/assets/js/in-view.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,23 +1,324 @@
|
||||||
//- 💫 MAIN JAVASCRIPT
|
//- 💫 MAIN JAVASCRIPT
|
||||||
|
//- Note: Will be compiled using Babel before deployment.
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
{
|
const $ = document.querySelector.bind(document);
|
||||||
const nav = document.querySelector('.js-nav')
|
const $$ = document.querySelectorAll.bind(document);
|
||||||
const fixedClass = 'is-fixed'
|
|
||||||
let vh, scrollY = 0, scrollUp = false
|
|
||||||
|
|
||||||
const updateVh = () => Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
|
|
||||||
|
|
||||||
const updateNav = () => {
|
class ProgressBar {
|
||||||
const vh = updateVh()
|
/**
|
||||||
const newScrollY = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0)
|
* Animated reading progress bar.
|
||||||
if (newScrollY != scrollY) scrollUp = newScrollY <= scrollY
|
* @param {String} selector – CSS selector of progress bar element.
|
||||||
scrollY = newScrollY
|
*/
|
||||||
|
constructor(selector) {
|
||||||
if(scrollUp && !(isNaN(scrollY) || scrollY <= vh)) nav.classList.add(fixedClass)
|
this.el = $(selector);
|
||||||
else if (!scrollUp || (isNaN(scrollY) || scrollY <= vh/2)) nav.classList.remove(fixedClass)
|
this.scrollY = 0;
|
||||||
|
this.sizes = this.updateSizes();
|
||||||
|
this.el.setAttribute('max', 100);
|
||||||
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('scroll', () => requestAnimationFrame(updateNav))
|
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 = {}, accKeys = {}) {
|
||||||
|
this.url = `https://raw.githubusercontent.com/${repo}/master`;
|
||||||
|
this.repo = `https://github.com/${repo}`;
|
||||||
|
this.modelIds = models;
|
||||||
|
this.licenses = licenses;
|
||||||
|
this.accKeys = accKeys;
|
||||||
|
this.chartColor = '#09a3d5';
|
||||||
|
this.chartOptions = {
|
||||||
|
type: 'bar',
|
||||||
|
options: { responsive: true, scales: {
|
||||||
|
yAxes: [{ label: 'Accuracy', ticks: { suggestedMin: 70 }}],
|
||||||
|
xAxes: [{ barPercentage: 0.425 }]
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
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'";
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
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', 'author', 'license']) {
|
||||||
|
template.get(key).parentElement.parentElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update model details in tables. Currently quite hacky :(
|
||||||
|
*/
|
||||||
|
render({ lang, name, version, sources, pipeline, url, author, license, accuracy, 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 => `<code>${comp}</code>`).join(', ');
|
||||||
|
const getLink = (t, l) => `<a href="${l}" target="_blank">${t}</a>`;
|
||||||
|
|
||||||
|
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 (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}`);
|
||||||
|
if (accuracy) this.renderAccuracy(template, accuracy, modelId);
|
||||||
|
this.renderCompat(template, modelId);
|
||||||
|
template.get('table').removeAttribute('data-loading');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCompat(template, modelId) {
|
||||||
|
template.get('compat-wrapper').style.display = 'table-row';
|
||||||
|
const options = Object.keys(this.compat).map(v => `<option value="${v}">v${v}</option>`).join('');
|
||||||
|
template
|
||||||
|
.fill('compat', '<option selected disabled>spaCy version</option>' + options, true)
|
||||||
|
.addEventListener('change', ev => {
|
||||||
|
const result = this.compat[ev.target.value][modelId];
|
||||||
|
if (result) template.fill('compat-versions', `<code>${modelId}-${result[0]}</code>`, true);
|
||||||
|
else template.fill('compat-versions', '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAccuracy(template, accuracy, modelId, compare=false) {
|
||||||
|
template.get('accuracy-wrapper').style.display = 'block';
|
||||||
|
const metaKeys = Object.keys(this.accKeys).map(k => accuracy[k] ? k : false).filter(k => k);
|
||||||
|
for (let key of metaKeys) {
|
||||||
|
template.fill(key, accuracy[key].toFixed(2)).parentElement.style.display = 'table-row';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartOptions.options.legend = { display: compare }
|
||||||
|
new Chart(`chart_${modelId}`, Object.assign({}, this.chartOptions, { data: {
|
||||||
|
datasets: [{
|
||||||
|
label: modelId,
|
||||||
|
data: metaKeys.map(key => accuracy[key].toFixed(2)),
|
||||||
|
backgroundColor: this.chartColor
|
||||||
|
}],
|
||||||
|
labels: metaKeys.map(key => this.accKeys[key])
|
||||||
|
}}))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = `<a href="${url}" target="_blank"><code>${tag}</code></a>`;
|
||||||
|
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: <pre><code data-gh-embed="spacy/master/examples/x.py"></code><pre>
|
||||||
|
*
|
||||||
|
* @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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user