Skip to content

Commit

Permalink
fix: transform all quotes
Browse files Browse the repository at this point in the history
  • Loading branch information
agoose77 committed Mar 11, 2024
1 parent e3e8f30 commit 87e4189
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 73 deletions.
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/myst-directives/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { mdastDirective } from './mdast.js';
import { mermaidDirective } from './mermaid.js';
import { mystdemoDirective } from './mystdemo.js';
import { outputDirective } from './output.js';
import { epigraphDirective } from './quote.js';
import { epigraphDirective, pullQuoteDirective } from './quote.js';

export const defaultDirectives = [
admonitionDirective,
Expand Down Expand Up @@ -56,4 +56,4 @@ export { mdastDirective } from './mdast.js';
export { mermaidDirective } from './mermaid.js';
export { mystdemoDirective } from './mystdemo.js';
export { outputDirective } from './output.js';
export { epigraphDirective } from './quote.js';
export { epigraphDirective, pullQuoteDirective } from './quote.js';
90 changes: 36 additions & 54 deletions packages/myst-directives/src/quote.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import type { DirectiveSpec, DirectiveData, GenericNode } from 'myst-common';
import { remove } from 'unist-util-remove';
import { copyNode } from 'myst-common';

const ATTRIBUTION_PATTERN = /(?:---?(?!-)|\u2014) +(.*)/;

export const epigraphDirective: DirectiveSpec = {
name: 'epigraph',
doc: 'Inscriptions, or "epigraphs", provide a short quote or inscription at the beginning of a topic. They are usually pertinent to the subsequent content, either to set the theme, or establish a counter-example.',
Expand All @@ -17,59 +12,46 @@ export const epigraphDirective: DirectiveSpec = {
children.push(...(data.body as GenericNode[]));
}

// Find any captions
for (const node of children) {
if (node.type !== 'paragraph') {
continue;
}
if (!node.children) {
continue;
}
const child = node.children[0];
if (child.type !== 'text') {
continue;
}
const match = child.value?.match(ATTRIBUTION_PATTERN);
if (!match) {
continue;
}
// Mutate matched node to drop `-- `
child.value = match[1];

// Remove matched node if text is now empty
if (!child.value) {
child.type = '__delete__';
}

// Save copy of node
const captionParagraph = copyNode(node);
const container = {
type: 'container',
kind: 'quote',
class: 'epigraph',
children: [
{
type: 'blockquote',
children: children as any[],
},
],
};
return [container];
},
};

// Delete original node
node.type = '__delete__';

const container = {
type: 'container',
kind: 'quote',
children: [
{
type: 'blockquote',
children: children as any[],
},
{
type: 'caption',
children: [captionParagraph],
},
],
};
// Delete duplicate caption
remove(container, '__delete__');
return [container];
export const pullQuoteDirective: DirectiveSpec = {
name: 'pull-quote',
doc: 'Pull-quotes add emphasis to a small selection of text by pulling it out into a separate, quoted block - usually of a larger typeface. They are used to attract attention, especially in documents that consist of considerable prose.',
body: {
type: 'myst',
doc: 'The body of the pull-quote.',
},
run(data: DirectiveData): GenericNode[] {
const children: GenericNode[] = [];
if (data.body) {
children.push(...(data.body as GenericNode[]));
}

const quote = {
type: 'blockquote',
children: children as any[],
const container = {
type: 'container',
kind: 'quote',
class: 'pull-quote',
children: [
{
type: 'blockquote',
children: children as any[],
},
],
};
return [quote];
return [container];
},
};
99 changes: 82 additions & 17 deletions packages/myst-transforms/src/blockquote.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,88 @@
import type { Plugin } from 'unified';
import type { Blockquote, Caption, Container } from 'myst-spec';
import { selectAll } from 'unist-util-select';
import type { GenericParent } from 'myst-common';
import type { Blockquote, Caption, Container, Paragraph, Text } from 'myst-spec';
import { selectAll, select, matches } from 'unist-util-select';
import type { GenericParent, GenericNode } from 'myst-common';
import { remove } from 'unist-util-remove';
import { copyNode, liftChildren } from 'myst-common';
import { visit } from 'unist-util-visit';

const ATTRIBUTION_PATTERN = /(?:---?(?!-)|\u2014) +(.*)/;

function maybeLiftAttribution(container: Container, quote: Blockquote): boolean {
// Make this idempotent
if (select('caption', container)) {
return false;
}
// Do we have a final paragraph
const maybeCaptionParagraph = quote.children[quote.children.length-1];//.at(-1);
if (maybeCaptionParagraph?.type !== 'paragraph') {
return false;
}
// Do we have a leading text element?
const maybeCaptionText = maybeCaptionParagraph.children[0];
if (maybeCaptionText?.type !== 'text') {
return false;
}
// Does the text match the attribution pattern?
const match = maybeCaptionText.value?.match(ATTRIBUTION_PATTERN);
if (!match) {
return false;
}

// We've found an attribution, now we want to strip the prefix
if (match[1]) {
maybeCaptionText.value = match[1];
}
// There's no remainder, delete the text node entirely
else {
(maybeCaptionText as GenericNode).type = '__delete__';
remove(maybeCaptionParagraph, '__delete__');
}
// Delete original (final) paragraph
quote.children.pop();

// Create caption
const caption: Caption = {
type: 'caption',
children: [maybeCaptionParagraph],
};

// Add caption
container.children.push(caption);
return true;
}
export function blockquoteTransform(mdast: GenericParent) {
const quotes = selectAll('blockquote', mdast) as Blockquote[];
quotes.forEach((node) => {
if (node.children.length < 2) return;
const possibleList = node.children[node.children.length - 1];
if (possibleList.type !== 'list' || possibleList.children?.length !== 1) return;
const container = node as unknown as Container;
container.type = 'container';
(container as any).kind = 'quote';
const caption: Caption = {
type: 'caption',
children: [{ type: 'paragraph', children: possibleList.children[0].children as any }],
};
const blockquote: Blockquote = { type: 'blockquote', children: node.children.slice(0, -1) };
container.children = [blockquote as any, caption];
visit(mdast, 'blockquote', (quote: Blockquote, quoteParent: GenericNode | undefined) => {
const isContainer = matches('container[kind=quote]', quoteParent);

// If there's already a `container`, then we just lift the attribution into the container
if (isContainer) {
maybeLiftAttribution(quoteParent as unknown as Container, quote);
return 'skip';
}
// Otherwise, we create a container, and replace the blockquote with a container
// containing the blockquote
else {
const container = {
type: 'container',
kind: 'quote',
children: [quote],
};
if (maybeLiftAttribution(container as unknown as Container, quote)) {
// Copy container before we modify the quote node
const nextContainer = copyNode(container);
// Erase the original blockquote node, using it as a mechanism
// to lift the new container into the right place
(quote as GenericNode).type = '__lift__';
quote.children = [nextContainer as unknown as Container];
// Let's be safe for now (due to ! assertion below)
if (quoteParent === undefined) {
throw new Error("Encountered root-level blockquote, can't replace parent");
}
liftChildren(quoteParent!, '__lift__');
}
return 'skip';
}
});
}

Expand Down
3 changes: 3 additions & 0 deletions packages/myst-transforms/src/containers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ export function containerChildrenTransform(tree: GenericParent, vfile: VFile) {
const containers = selectAll('container', tree) as GenericParent[];
// Process in reverse so subfigure processing persists
containers.reverse().forEach((container) => {
if (container.kind === "quote") {
return;
}
hoistContentOutOfParagraphs(container);
let subfigures: GenericNode[] = [];
let placeholderImage: GenericNode | undefined;
Expand Down

0 comments on commit 87e4189

Please sign in to comment.