discourse/app/assets/javascripts/pretty-text/engines/discourse-markdown/onebox.js.es6
2019-06-11 09:14:53 +08:00

123 lines
3.4 KiB
JavaScript

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