discourse/app/assets/javascripts/pretty-text/engines/discourse-markdown/onebox.js.es6
Guo Xiang Tan a1e77aa2ed
FEATURE: Reimplement SiteSetting.max_oneboxes_per_post. (#6668)
Previously, the site setting was only effective on the client side of
things. Once the site setting was been reached, all oneboxes are not
rendered. This commit changes it such that the site setting is respected
both on the client and server side. The first N oneboxes are rendered and
once the limit has been reached, subsequent oneboxes will not be
rendered.
2018-11-27 16:00:31 +08:00

123 lines
3.3 KiB
JavaScript

import { lookupCache } from "pretty-text/oneboxer";
import {
cachedInlineOnebox,
INLINE_ONEBOX_LOADING_CSS_CLASS,
INLINE_ONEBOX_CSS_CLASS
} from "pretty-text/inline-oneboxer";
const ONEBOX = 1;
const INLINE = 2;
function isTopLevel(href) {
let split = href.split(/https?:\/\/[^\/]+[\/?]/i);
let hasExtra = split && split[1] && split[1].length > 0;
return !hasExtra;
}
function applyOnebox(state, silent) {
if (silent || !state.tokens) {
return;
}
for (let i = 1; i < state.tokens.length; i++) {
let token = state.tokens[i];
let prev = state.tokens[i - 1];
let mode =
prev.type === "paragraph_open" && prev.level === 0 ? ONEBOX : INLINE;
if (token.type === "inline") {
let children = token.children;
for (let j = 0; j < children.length - 2; j++) {
let child = children[j];
if (
child.type === "link_open" &&
child.markup === "linkify" &&
child.info === "auto"
) {
if (j > children.length - 3) {
continue;
}
if (j === 0 && token.leading_space) {
mode = INLINE;
} else if (j > 0) {
let prevSibling = children[j - 1];
if (prevSibling.tag !== "br" || prevSibling.leading_space) {
mode = INLINE;
}
}
// look ahead for soft or hard break
let text = children[j + 1];
let close = children[j + 2];
let lookahead = children[j + 3];
if (lookahead && lookahead.tag !== "br") {
mode = INLINE;
}
// check attrs only include a href
let attrs = child.attrs;
if (!attrs || attrs.length !== 1 || attrs[0][0] !== "href") {
continue;
}
let href = attrs[0][1];
// edge case ... what if this is not http or protocoless?
if (!/^http|^\/\//i.test(href)) {
continue;
}
// we already know text matches cause it is an auto link
if (!close || close.type !== "link_close") {
continue;
}
if (mode === ONEBOX) {
// we already determined earlier that 0 0 was href
let cached = lookupCache(attrs[0][1]);
if (cached) {
// replace link with 2 blank text nodes and inline html for onebox
child.type = "html_raw";
child.content = cached;
child.inline = true;
text.type = "html_raw";
text.content = "";
text.inline = true;
close.type = "html_raw";
close.content = "";
close.inline = true;
} else {
// decorate...
attrs.push(["class", "onebox"]);
attrs.push(["target", "_blank"]);
}
} else if (mode === INLINE && !isTopLevel(href)) {
const onebox = cachedInlineOnebox(href);
if (onebox && onebox.title) {
text.content = onebox.title;
attrs.push(["class", INLINE_ONEBOX_CSS_CLASS]);
} else {
attrs.push(["class", INLINE_ONEBOX_LOADING_CSS_CLASS]);
}
}
}
}
}
}
}
export function setup(helper) {
helper.registerPlugin(md => {
md.core.ruler.after("linkify", "onebox", applyOnebox);
});
}