@curvenote/schema
Version:
Schema and markdown parser for @curvenote/editor
207 lines • 9.06 kB
JavaScript
import { createLatexStatement } from '../serialize/tex/utils';
import { CaptionKind, NodeGroups } from './types';
import { determineCaptionKind } from '../process/utils';
import { getColumnWidths, getFirstChildWithName, getNumberedAttrs, getNumberedDefaultAttrs, readBooleanDomAttr, normalizeLabel, readBooleanAttr, setNumberedAttrs, hasFancyTable, writeMdastSnippet, } from './utils';
import { nodeNames } from '../types';
import { toMarkdown as codeToMarkdown } from './code';
import { writeDirectiveOptions } from '../serialize/markdown/utils';
const figure = {
group: NodeGroups.block,
content: NodeGroups.insideFigure,
isolating: true,
attrs: Object.assign(Object.assign({}, getNumberedDefaultAttrs()), { align: { default: 'center' }, multipage: { default: false }, landscape: { default: false }, fullpage: { default: false } }),
toDOM(node) {
const { align, multipage, landscape, fullpage } = node.attrs;
return [
'figure',
Object.assign(Object.assign({}, setNumberedAttrs(node.attrs)), { align,
multipage,
landscape,
fullpage }),
0,
];
},
parseDOM: [
{
tag: 'figure',
getAttrs(dom) {
var _a;
return Object.assign(Object.assign({}, getNumberedAttrs(dom)), { align: (_a = dom.getAttribute('align')) !== null && _a !== void 0 ? _a : 'center', multipage: readBooleanDomAttr(dom, 'multipage'), landscape: readBooleanDomAttr(dom, 'landscape'), fullpage: readBooleanDomAttr(dom, 'fullpage') });
},
},
],
attrsFromMyst: (token) => {
var _a;
const match = (_a = token.class) === null || _a === void 0 ? void 0 : _a.match(/align-(left|right|center)/);
return {
id: token.identifier || null,
label: null,
numbered: token.enumerated || false,
align: (match ? match[1] : 'center'),
multipage: false,
landscape: false,
fullpage: false,
};
},
toMyst: (props, options) => {
var _a, _b, _c, _d, _e;
let containerKind = 'figure';
(_a = props.children) === null || _a === void 0 ? void 0 : _a.forEach((child) => {
if (child.type === 'image' || child.type === 'table') {
child.align = props.align || undefined;
}
if (child.type === 'table') {
containerKind = 'table';
}
});
const localizedId = (_e = (_d = (_b = options.localizeId) === null || _b === void 0 ? void 0 : _b.call(options, (_c = props.id) !== null && _c !== void 0 ? _c : '')) !== null && _d !== void 0 ? _d : props.id) !== null && _e !== void 0 ? _e : '';
return Object.assign(Object.assign({ type: 'container', kind: containerKind }, normalizeLabel(localizedId)), { enumerated: readBooleanAttr(props.numbered), class: props.align ? `align-${props.align}` : undefined, children: (props.children || []) });
},
};
export const toMarkdown = (state, node, parent, index) => {
var _a, _b, _c, _d, _e, _f, _g;
state.ensureNewLine();
const kind = determineCaptionKind(node);
const { id } = node.attrs;
// TODO: Translate between callout/admonition
const caption = getFirstChildWithName(node, nodeNames.figcaption);
state.nextCaptionId = (_d = (_c = (_b = (_a = state.options).localizeId) === null || _b === void 0 ? void 0 : _b.call(_a, id !== null && id !== void 0 ? id : '')) !== null && _c !== void 0 ? _c : id) !== null && _d !== void 0 ? _d : undefined;
switch (kind) {
case CaptionKind.fig: {
const image = getFirstChildWithName(node, [nodeNames.image, nodeNames.iframe]);
if (!image)
return;
const { src, align, width } = image === null || image === void 0 ? void 0 : image.attrs;
const href = (_g = (_f = (_e = state.options).localizeImageSrc) === null || _f === void 0 ? void 0 : _f.call(_e, src)) !== null && _g !== void 0 ? _g : src;
if (image.type.name === nodeNames.iframe) {
state.render(image, parent, index);
return;
}
state.write(`\`\`\`{figure} ${href}\n`);
const opts = { name: state.nextCaptionId, align, width: `${width}%` };
writeDirectiveOptions(state, opts);
if (caption) {
state.renderInline(caption);
state.ensureNewLine();
}
state.write('```');
state.closeBlock(node);
return;
}
case CaptionKind.eq: {
state.renderContent(node);
return;
}
case CaptionKind.table: {
const table = getFirstChildWithName(node, [nodeNames.table]);
if (table && hasFancyTable(table)) {
writeMdastSnippet(state, node);
return;
}
state.nextTableCaption = caption;
if (table)
state.render(table, parent, index);
state.closeBlock(node);
return;
}
case CaptionKind.code: {
const code = getFirstChildWithName(node, [nodeNames.code_block]);
// TODO: add a figure caption for code
if (code)
codeToMarkdown(state, code, node, index);
state.closeBlock(node);
return;
}
default:
console.log(`Unknown figure kind: "${kind}", id=${id}, children=${node.childCount}`);
return;
}
};
function nodeToCommand(node) {
const kind = determineCaptionKind(node);
switch (kind) {
case CaptionKind.fig:
return node.attrs.fullpage ? 'figure*' : 'figure';
case CaptionKind.table:
return node.attrs.fullpage ? 'table*' : 'table';
case CaptionKind.code:
// TODO full width code
return 'code';
case CaptionKind.eq:
return 'figure'; // not sure what to do here.
default:
return 'figure';
}
}
function nodeToLaTeXOptions(node) {
const kind = determineCaptionKind(node);
switch (kind) {
case CaptionKind.fig:
case CaptionKind.table:
return '!htbp';
case CaptionKind.code:
return 'H';
case CaptionKind.eq:
default:
return undefined;
}
}
function figureContainsTable(node) {
const table = node.content.content.find((n) => n.type.name === nodeNames.table);
return table;
}
export const toTex = createLatexStatement((state, node) => {
// if the figure is in a table, skip to child content
if (state.isInTable)
return null;
// if figure contains a table, we need find out which table environment to use
state.containsTable = false;
const table = figureContainsTable(node);
let tableInfo;
if (table && node.attrs.multipage) {
state.containsTable = true;
tableInfo = getColumnWidths(table);
}
let before;
let after;
if (node.attrs.landscape) {
// requires pdflscape package to be loaded
before = '\\begin{landscape}';
after = '\\end{landscape}';
}
// TODO for longtable to work with two columns we need to flip out to single column first
// and then back to multi column, if we were in multicolumn mode
// Q: we can know if we are in a two column mode from the template we are using, but how is this made available at this level?
return {
command: state.containsTable && node.attrs.multipage ? 'longtable' : nodeToCommand(node),
commandOpts: state.containsTable && tableInfo ? tableInfo.columnSpec : undefined,
bracketOpts: state.containsTable ? undefined : nodeToLaTeXOptions(node),
before,
after,
};
}, (state, node) => {
var _a, _b, _c, _d;
// if the figure is in a table, skip to child content
if (state.isInTable) {
state.renderContent(node);
return;
}
const { numbered, id, multipage } = node.attrs;
const localId = (_d = (_c = (_b = (_a = state.options).localizeId) === null || _b === void 0 ? void 0 : _b.call(_a, id !== null && id !== void 0 ? id : '')) !== null && _c !== void 0 ? _c : id) !== null && _d !== void 0 ? _d : undefined;
// TODO: Based on align attr
// may have to modify string returned by state.renderContent(n);
// https://tex.stackexchange.com/questions/91566/syntax-similar-to-centering-for-right-and-left
// centering does not work in a longtable environment
if (!multipage || !state.containsTable)
state.write('\\centering');
state.ensureNewLine();
// Pass the relevant information to the child nodes
state.nextCaptionNumbered = numbered;
state.nextCaptionId = localId;
state.longFigure = multipage;
state.renderContent(node);
state.longFigure = undefined;
state.containsTable = false;
});
export default figure;
//# sourceMappingURL=figure.js.map