discourse/app/assets/javascripts/pretty-text/addon/oneboxer.js
Natalie Tay dd3ed27930
DEV: Allow utility class domFromString to take in strings with multiple top level elements (#15548)
Previously only `<div>one top element</div>` was allowed because we use `firstChild` instead of `children`.
We also want `<div>one</div><div>two</div>` to work with this method.
2022-01-12 19:49:24 +08:00

158 lines
3.5 KiB
JavaScript

import domFromString from "discourse-common/lib/dom-from-string";
import {
failedCache,
lookupCache,
normalize,
resetFailedCache,
resetLocalCache,
setFailedCache,
setLocalCache,
} from "pretty-text/oneboxer-cache";
import { later } from "@ember/runloop";
let timeout;
const loadingQueue = [];
export const LOADING_ONEBOX_CSS_CLASS = "loading-onebox";
export function resetCache() {
loadingQueue.clear();
resetLocalCache();
resetFailedCache();
}
function resolveSize(img) {
img.classList.add("size-resolved");
if (img.width > 0 && img.width === img.height) {
img.classList.add("onebox-avatar");
}
}
// Detect square images and apply smaller onebox-avatar class
function applySquareGenericOnebox(elem) {
if (!elem.classList.contains("allowlistedgeneric")) {
return;
}
let img = elem.querySelector(".onebox-body img.thumbnail");
// already resolved... skip
if (!img || img.classList.contains("size-resolved")) {
return;
}
if (img.complete) {
resolveSize(img);
} else {
img.addEventListener("load", _handleLoadingOneboxImages);
}
}
function _handleLoadingOneboxImages() {
resolveSize(this);
this.removeEventListener("load", _handleLoadingOneboxImages);
}
function loadNext(ajax) {
if (loadingQueue.length === 0) {
timeout = null;
return;
}
let timeoutMs = 150;
let removeLoading = true;
const { url, refresh, elem, categoryId, topicId } = loadingQueue.shift();
// Retrieve the onebox
return ajax("/onebox", {
dataType: "html",
data: {
url,
refresh,
category_id: categoryId,
topic_id: topicId,
},
})
.then(
(template) => {
const node = domFromString(template)[0];
setLocalCache(normalize(url), node);
elem.replaceWith(node);
applySquareGenericOnebox(node);
},
(result) => {
if (result?.jqXHR?.status === 429) {
timeoutMs = 2000;
removeLoading = false;
loadingQueue.unshift({ url, refresh, elem, categoryId, topicId });
} else {
setFailedCache(normalize(url), true);
}
}
)
.finally(() => {
timeout = later(() => loadNext(ajax), timeoutMs);
if (removeLoading) {
elem.classList.remove(LOADING_ONEBOX_CSS_CLASS);
elem.dataset.oneboxLoaded = "";
}
});
}
// Perform a lookup of a onebox based an anchor element.
// It will insert a loading indicator and remove it when the loading is complete or fails.
export function load({
elem,
ajax,
topicId,
categoryId,
refresh = true,
offline = false,
synchronous = false,
}) {
// If the onebox has loaded or is loading, return
if (elem.dataset.oneboxLoaded) {
return;
}
if (elem.classList.contains(LOADING_ONEBOX_CSS_CLASS)) {
return;
}
const url = elem.href;
// Unless we're forcing a refresh...
if (!refresh) {
// If we have it in our cache, return it.
const cached = lookupCache(url);
if (cached) {
return cached;
}
// If the request failed, don't do anything
const failed = failedCache[normalize(url)];
if (failed) {
return;
}
if (offline) {
return;
}
}
// Add the loading CSS class
elem.classList.add(LOADING_ONEBOX_CSS_CLASS);
// Add to the loading queue
loadingQueue.push({ url, refresh, elem, categoryId, topicId });
// Load next url in queue
if (synchronous) {
return loadNext(ajax);
} else {
timeout = timeout || later(() => loadNext(ajax), 150);
}
}