mirror of https://github.com/freeCodeCamp/devdocs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
5.3 KiB
246 lines
5.3 KiB
app.views.EntryPage = class EntryPage extends app.View {
|
|
static className = "_page";
|
|
static errorClass = "_page-error";
|
|
|
|
static events = { click: "onClick" };
|
|
|
|
static shortcuts = {
|
|
altC: "onAltC",
|
|
altO: "onAltO",
|
|
};
|
|
|
|
static routes = { before: "beforeRoute" };
|
|
|
|
static LINKS = {
|
|
home: "Homepage",
|
|
code: "Source code",
|
|
};
|
|
|
|
init() {
|
|
this.cacheMap = {};
|
|
this.cacheStack = [];
|
|
}
|
|
|
|
deactivate() {
|
|
if (super.deactivate(...arguments)) {
|
|
this.empty();
|
|
this.entry = null;
|
|
}
|
|
}
|
|
|
|
loading() {
|
|
this.empty();
|
|
this.trigger("loading");
|
|
}
|
|
|
|
render(content, fromCache) {
|
|
if (content == null) {
|
|
content = "";
|
|
}
|
|
if (fromCache == null) {
|
|
fromCache = false;
|
|
}
|
|
if (!this.activated) {
|
|
return;
|
|
}
|
|
this.empty();
|
|
this.subview = new (this.subViewClass())(this.el, this.entry);
|
|
|
|
$.batchUpdate(this.el, () => {
|
|
this.subview.render(content, fromCache);
|
|
if (!fromCache) {
|
|
this.addCopyButtons();
|
|
}
|
|
});
|
|
|
|
if (app.disabledDocs.findBy("slug", this.entry.doc.slug)) {
|
|
this.hiddenView = new app.views.HiddenPage(this.el, this.entry);
|
|
}
|
|
|
|
setFaviconForDoc(this.entry.doc);
|
|
this.delay(this.polyfillMathML);
|
|
this.trigger("loaded");
|
|
}
|
|
|
|
addCopyButtons() {
|
|
if (!this.copyButton) {
|
|
this.copyButton = document.createElement("button");
|
|
this.copyButton.innerHTML = '<svg><use xlink:href="#icon-copy"/></svg>';
|
|
this.copyButton.type = "button";
|
|
this.copyButton.className = "_pre-clip";
|
|
this.copyButton.title = "Copy to clipboard";
|
|
this.copyButton.setAttribute("aria-label", "Copy to clipboard");
|
|
}
|
|
for (var el of this.findAllByTag("pre")) {
|
|
el.appendChild(this.copyButton.cloneNode(true));
|
|
}
|
|
}
|
|
|
|
polyfillMathML() {
|
|
if (
|
|
window.supportsMathML !== false ||
|
|
!!this.polyfilledMathML ||
|
|
!this.findByTag("math")
|
|
) {
|
|
return;
|
|
}
|
|
this.polyfilledMathML = true;
|
|
$.append(
|
|
document.head,
|
|
`<link rel="stylesheet" href="${app.config.mathml_stylesheet}">`,
|
|
);
|
|
}
|
|
|
|
prepareContent(content) {
|
|
if (!this.entry.isIndex() || !this.entry.doc.links) {
|
|
return content;
|
|
}
|
|
|
|
const links = (() => {
|
|
const result = [];
|
|
for (var link in this.entry.doc.links) {
|
|
var url = this.entry.doc.links[link];
|
|
result.push(
|
|
`<a href="${url}" class="_links-link">${EntryPage.LINKS[link]}</a>`,
|
|
);
|
|
}
|
|
return result;
|
|
})();
|
|
|
|
return `<p class="_links">${links.join("")}</p>${content}`;
|
|
}
|
|
|
|
empty() {
|
|
if (this.subview != null) {
|
|
this.subview.deactivate();
|
|
}
|
|
this.subview = null;
|
|
|
|
if (this.hiddenView != null) {
|
|
this.hiddenView.deactivate();
|
|
}
|
|
this.hiddenView = null;
|
|
|
|
this.resetClass();
|
|
super.empty(...arguments);
|
|
}
|
|
|
|
subViewClass() {
|
|
return (
|
|
app.views[`${$.classify(this.entry.doc.type)}Page`] || app.views.BasePage
|
|
);
|
|
}
|
|
|
|
getTitle() {
|
|
return (
|
|
this.entry.doc.fullName +
|
|
(this.entry.isIndex() ? " documentation" : ` / ${this.entry.name}`)
|
|
);
|
|
}
|
|
|
|
beforeRoute() {
|
|
this.cache();
|
|
this.abort();
|
|
}
|
|
|
|
onRoute(context) {
|
|
const isSameFile = context.entry.filePath() === this.entry?.filePath?.();
|
|
this.entry = context.entry;
|
|
if (!isSameFile) {
|
|
this.restore() || this.load();
|
|
}
|
|
}
|
|
|
|
load() {
|
|
this.loading();
|
|
this.xhr = this.entry.loadFile(
|
|
(response) => this.onSuccess(response),
|
|
() => this.onError(),
|
|
);
|
|
}
|
|
|
|
abort() {
|
|
if (this.xhr) {
|
|
this.xhr.abort();
|
|
this.xhr = this.entry = null;
|
|
}
|
|
}
|
|
|
|
onSuccess(response) {
|
|
if (!this.activated) {
|
|
return;
|
|
}
|
|
this.xhr = null;
|
|
this.render(this.prepareContent(response));
|
|
}
|
|
|
|
onError() {
|
|
this.xhr = null;
|
|
this.render(this.tmpl("pageLoadError"));
|
|
this.resetClass();
|
|
this.addClass(this.constructor.errorClass);
|
|
if (app.serviceWorker != null) {
|
|
app.serviceWorker.update();
|
|
}
|
|
}
|
|
|
|
cache() {
|
|
let path;
|
|
if (
|
|
this.xhr ||
|
|
!this.entry ||
|
|
this.cacheMap[(path = this.entry.filePath())]
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.cacheMap[path] = this.el.innerHTML;
|
|
this.cacheStack.push(path);
|
|
|
|
while (this.cacheStack.length > app.config.history_cache_size) {
|
|
delete this.cacheMap[this.cacheStack.shift()];
|
|
}
|
|
}
|
|
|
|
restore() {
|
|
let path;
|
|
if (this.cacheMap[(path = this.entry.filePath())]) {
|
|
this.render(this.cacheMap[path], true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
onClick(event) {
|
|
const target = $.eventTarget(event);
|
|
if (target.hasAttribute("data-retry")) {
|
|
$.stopEvent(event);
|
|
this.load();
|
|
} else if (target.classList.contains("_pre-clip")) {
|
|
$.stopEvent(event);
|
|
target.classList.add(
|
|
$.copyToClipboard(target.parentNode.textContent)
|
|
? "_pre-clip-success"
|
|
: "_pre-clip-error",
|
|
);
|
|
setTimeout(() => (target.className = "_pre-clip"), 2000);
|
|
}
|
|
}
|
|
|
|
onAltC() {
|
|
let link;
|
|
if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
|
|
return;
|
|
}
|
|
console.log(link.href + location.hash);
|
|
navigator.clipboard.writeText(link.href + location.hash);
|
|
}
|
|
|
|
onAltO() {
|
|
let link;
|
|
if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
|
|
return;
|
|
}
|
|
this.delay(() => $.popup(link.href + location.hash));
|
|
}
|
|
};
|