discourse/app/assets/javascripts/pretty-text/engines/discourse-markdown/onebox.js
Bianca Nenciu d7bd62d9cf
FIX: Replace censored watched word consistently (#12486)
Applying oneboxes and replacing censored watched words does not happen
in a strict order which often lead to inconsistencies. This commit
fixes the behavior and will never censor oneboxes.

To make it always censor oneboxes implies significant changes to the
PrettyText pipeline.
2021-03-23 13:09:24 +02:00

119 lines
3.3 KiB
JavaScript

import { cachedInlineOnebox } from "pretty-text/inline-oneboxer";
import { lookupCache } from "pretty-text/oneboxer-cache";
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;
child.onebox = 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"]);
} else if (!onebox) {
attrs.push(["class", "inline-onebox-loading"]);
}
}
}
}
}
}
}
export function setup(helper) {
helper.registerPlugin((md) => {
md.core.ruler.after("linkify", "onebox", applyOnebox);
});
}