litvis-integration-mume
Version:
Enables litvis functionality in mume and markdown-preview-enhanced
253 lines • 13.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.enhanceWithLitvisLiterateElm = void 0;
const cheerio_1 = __importDefault(require("cheerio"));
const html_entities_1 = require("html-entities");
const litvis_1 = require("litvis");
const lodash_1 = __importDefault(require("lodash"));
const object_hash_1 = __importDefault(require("object-hash"));
const flattenJsonToRawMarkdown = (data) => {
if (data instanceof Array) {
return data.map(flattenJsonToRawMarkdown).join(" ");
}
if (typeof data === "object" && data !== null) {
return flattenJsonToRawMarkdown(Object.entries(data));
}
return `${data}`;
};
const generate$output = (arrayOf$outputItems) => {
return (0, cheerio_1.default)('<div data-role="litvisOutput" style="display: inline;" />').append("", ...arrayOf$outputItems);
};
const generateArrayOf$outputItems = (outputExpressions, outputFormat, derivatives) => {
return outputExpressions.map((outputExpression) => {
const $outputItem = (0, cheerio_1.default)(`<span data-role="litvisOutputItem" data-context-name="${(0, html_entities_1.encode)(derivatives.contextName)}" data-expression="${(0, html_entities_1.encode)(outputExpression)}" data-interactive="${derivatives.interactive}" data-output-format="${(0, html_entities_1.encode)(outputFormat)}"><code>${(0, html_entities_1.encode)(outputFormat)}=${(0, html_entities_1.encode)(outputExpression)}</code></span>`);
return $outputItem;
});
};
const mapAutogeneratedContextNames = (litvisContextNames, mumeContextNames, autogeneratedPrefix) => {
const autogeneratedLitvisContextNames = lodash_1.default.sortBy(lodash_1.default.filter(litvisContextNames, (name) => lodash_1.default.startsWith(name, autogeneratedPrefix)));
const autogeneratedMumeContextNames = lodash_1.default.sortBy(lodash_1.default.filter(mumeContextNames, (name) => lodash_1.default.startsWith(name, autogeneratedPrefix)));
const minLength = Math.min(autogeneratedLitvisContextNames.length, autogeneratedMumeContextNames.length);
const pickedAutogeneratedLitvisContextNames = lodash_1.default.slice(autogeneratedLitvisContextNames, -minLength);
const pickedAutogeneratedMumeContextNames = lodash_1.default.slice(autogeneratedMumeContextNames, -minLength);
const result = {
// key - litvis context name
// value - mume context name
};
lodash_1.default.forEach(litvisContextNames, (name) => {
const index = lodash_1.default.indexOf(autogeneratedLitvisContextNames, name);
if (index !== -1) {
result[pickedAutogeneratedLitvisContextNames[index]] =
pickedAutogeneratedMumeContextNames[index];
}
else {
result[name] = name;
}
});
return result;
};
const enhanceWithLitvisLiterateElm = ($, processedNarrative, cache, parseMd) => __awaiter(void 0, void 0, void 0, function* () {
// search for all elm code blocks and surround them
// with output items if they reference expressions to output
const mumeContextNames = [];
$('[data-role="codeBlock"]').each((i, container) => {
const $container = $(container);
if ($container.data("executor")) {
return;
}
const info = $container.data("parsedInfo");
if (`${info.language}`.toLowerCase() !== "elm") {
return;
}
const derivatives = (0, litvis_1.extractAttributeDerivatives)(info.attributes);
if (!derivatives) {
return;
}
$container.data("executor", "litvis");
if (derivatives.outputFormats.indexOf("l") === -1) {
$container.data("hiddenByEnhancer", true);
}
mumeContextNames.push(derivatives.contextName);
const derivativesWithResolvedExpressions = (0, litvis_1.resolveExpressions)(derivatives, $container.text());
const arrayOf$outputItemsBeforeCodeBlock = [];
const arrayOf$outputItemsAfterCodeBlock = [];
let currentArrayOf$outputItems = arrayOf$outputItemsBeforeCodeBlock;
derivativesWithResolvedExpressions.outputFormats.forEach((outputFormat) => {
switch (outputFormat) {
case "l":
currentArrayOf$outputItems = arrayOf$outputItemsAfterCodeBlock;
break;
default: {
const expressions = derivativesWithResolvedExpressions.outputExpressionsByFormat[outputFormat];
if (expressions) {
currentArrayOf$outputItems.push(...generateArrayOf$outputItems(expressions, outputFormat, derivativesWithResolvedExpressions));
}
}
}
});
if (arrayOf$outputItemsBeforeCodeBlock.length) {
$container.before(generate$output(arrayOf$outputItemsBeforeCodeBlock));
}
if (arrayOf$outputItemsAfterCodeBlock.length) {
$container.after(generate$output(arrayOf$outputItemsAfterCodeBlock));
}
});
// search for all triple hat references and turn them into output items
$('[data-role="litvis:triple-hat-reference"]').each((i, el) => {
const $el = $(el);
const info = JSON.parse($el.attr("data-parsedinfo") || "");
if (`${info.language}`.toLowerCase() !== "elm") {
return;
}
const derivatives = (0, litvis_1.extractAttributeDerivatives)(info.attributes);
if (!derivatives) {
return;
}
const arrayOf$outputItems = [];
derivatives.outputFormats.forEach((outputFormat) => {
switch (outputFormat) {
case "l":
break;
default: {
const expressions = derivatives.outputExpressionsByFormat[outputFormat];
if (expressions) {
arrayOf$outputItems.push(...generateArrayOf$outputItems(expressions, outputFormat, derivatives));
}
}
}
});
$el.replaceWith(generate$output(arrayOf$outputItems));
});
// Some context names in sidings are autogenerated by litvis,
// but their ids may not match ids autogenerated by mume.
// Because it is known that the number of contexts starting with '_autogenerated__X' match
// And X is incremental, is possible to generate a mapping between lit names.
const litvisContextNameToMumeContextName = mapAutogeneratedContextNames(lodash_1.default.map(processedNarrative.contexts, "name"), lodash_1.default.uniq(mumeContextNames), "_autogenerated__");
const contextsByMumeContextName = lodash_1.default.keyBy(processedNarrative.contexts, ({ name }) => litvisContextNameToMumeContextName[name]);
// walk through all litvis output items and render them
const $outputItems = $('[data-role="litvisOutputItem"]');
const mappedOutputItems = $outputItems.map((i, el) => __awaiter(void 0, void 0, void 0, function* () {
const $el = $(el);
const contextName = $el.data("contextName");
const outputFormat = $el.data("outputFormat");
const expressionText = $el.data("expression");
const interactive = $el.data("interactive");
const renderKey = (0, object_hash_1.default)({
contextName,
outputFormat,
expressionText,
path: processedNarrative.documents[processedNarrative.documents.length - 1].path,
});
const context = contextsByMumeContextName[contextName];
// const evaluatedOutputExpressionsByText = keyBy(context.evaluatedOutputExpressions, oe => oe.data.text);
try {
if (!context) {
throw new Error(`Non-existing context ${contextName}`);
}
if (context.status !== "succeeded") {
throw new Error(`Code execution in context ${contextName} was not successful`);
}
// TODO: find() is expensive, consider optimizing by indexing
const evaluatedOutputExpression = lodash_1.default.find(context.evaluatedOutputExpressions, (oe) => oe.data.text === expressionText);
if (!evaluatedOutputExpression) {
throw new Error(`Could not find expression ${expressionText}`);
}
if (typeof evaluatedOutputExpression.data.valueStringRepresentation !==
"string") {
throw new Error(`Could not evaluate expression ${expressionText}`);
}
if (outputFormat !== "r" &&
evaluatedOutputExpression.data.value instanceof Error) {
throw new Error(`Could not parse value of ${expressionText}`);
}
let $result;
let resultNormalizedInfo = null;
switch (outputFormat) {
case "r":
$result = $("<span/>").text(evaluatedOutputExpression.data.valueStringRepresentation);
break;
case "j":
$result = $(`<pre data-role="codeBlock" />`);
resultNormalizedInfo = {
language: "json",
attributes: { style: "display: inline-block" },
};
$result.text(JSON.stringify(evaluatedOutputExpression.data.value, null, 2));
break;
case "m": {
const rawMarkdown = flattenJsonToRawMarkdown(evaluatedOutputExpression.data.value);
const { html } = yield parseMd(rawMarkdown.length ? rawMarkdown : " ", // parseMD accepts non-empty strings only
{
useRelativeFilePath: true,
isForPreview: false,
hideFrontMatter: true,
});
$result = $(html);
$result.filter("h1,h2,h3,h4,h5,h6").removeClass("mume-header");
break;
}
case "v": {
const vegaOrVegaLiteJson = evaluatedOutputExpression.data.value;
const language = lodash_1.default.get(vegaOrVegaLiteJson, "$schema", "")
.toLowerCase()
.indexOf("lite") !== -1
? "vega-lite"
: "vega";
$result = $(`<pre data-role="codeBlock" />`);
resultNormalizedInfo = {
language,
attributes: {
interactive: interactive === true,
style: "display: inline-block",
},
};
$result.text(JSON.stringify(vegaOrVegaLiteJson, null, 2));
break;
}
default:
return;
}
// because serializing/deserializing data attributes works inconsistently
// in Cheerio, setting normalized info as attribute first...
$result.attr("data-normalized-info", JSON.stringify(resultNormalizedInfo));
cache.successfulRenders.set(renderKey, $("<div/>").append($result).html() || "");
// ...and then as data
$result.removeAttr("data-normalized-info");
$result.data("normalizedInfo", resultNormalizedInfo);
$el.replaceWith($result);
}
catch (e) {
const $error = $("<span/>").attr("style", "background: rgba(255,200,200,0.1); min-height: 1em; min-width: 1em; display: inline-block;");
const successfulRender = cache.successfulRenders.get(renderKey);
if (successfulRender) {
const $successfulRender = $(successfulRender);
const $fadedSuccessfulRender = $("<span />");
$fadedSuccessfulRender.attr("style", "opacity: 0.8; display: inline-block; filter: sepia(0.7) hue-rotate(-50deg);");
$fadedSuccessfulRender.append($successfulRender);
$error.append($fadedSuccessfulRender);
}
else {
const $errorText = $("<span/>").attr("style", "color: red; padding: 0 0.3em; font-weight: bold; white-space: nowrap");
$errorText.text(`${outputFormat}=${expressionText}${contextName !== "default" ? ` [${contextName}]` : ``}`);
$error.append($errorText);
}
$error.attr("title", `${outputFormat}=${expressionText}${contextName !== "default" ? ` [context ${contextName}]` : ``}: ${e.message}`);
$el.empty().append($error);
}
}));
yield Promise.all(mappedOutputItems.get());
});
exports.enhanceWithLitvisLiterateElm = enhanceWithLitvisLiterateElm;
//# sourceMappingURL=enhanceWithLitvisLiterateElm.js.map