Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💬 Add epigraph, pull-quote and blockquote directive #961

Merged
merged 36 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7095951
wip: epigraph directive
agoose77 Mar 13, 2024
adca09e
fix: transform all quotes
agoose77 Mar 13, 2024
8289049
fix: add pull-quote default directive
agoose77 Mar 13, 2024
f5e54e7
chore: run prettier
agoose77 Mar 13, 2024
6c7e3d5
test: fix test
agoose77 Mar 13, 2024
2c30fd3
fix: proper transform signature
agoose77 Mar 13, 2024
ae99140
fix: update package lock
agoose77 Mar 13, 2024
fed6ecd
fix: only match start of attribution line
agoose77 Mar 13, 2024
6d90547
fix: drop new Epigraph node
agoose77 Mar 13, 2024
4abab89
test: add more testing
agoose77 Mar 13, 2024
780378e
feat: don't run transform for empty attributions
agoose77 Mar 13, 2024
f9c2877
docs: add pull-quote & epigraph
agoose77 Mar 13, 2024
7bc7759
feat: add support for label and class
agoose77 Mar 13, 2024
5a8c73b
Update packages/myst-transforms/src/containers.ts
agoose77 Mar 13, 2024
6df8530
chore: lint
agoose77 Mar 13, 2024
1ba389d
refactor: simplify(?) logic
agoose77 Mar 13, 2024
e3b1134
fix: make quote transform re-entrant
agoose77 Mar 13, 2024
19f7146
docs: add note about pull-quote and block quotes
agoose77 Mar 13, 2024
6ae3d71
feat: change variant to be a class
agoose77 Mar 13, 2024
626287d
docs: change to block-quote
agoose77 Mar 13, 2024
9bbe875
fix: add missing dependency
agoose77 Mar 13, 2024
cf15100
fix: add label
agoose77 Mar 13, 2024
a514896
feat: drop unused nodes
agoose77 Mar 13, 2024
06d7f3a
fix: set identifier
agoose77 Mar 13, 2024
d6d56ce
chore: changeset
agoose77 Mar 13, 2024
bcf747d
chore: rename quote to blockquote
agoose77 Mar 13, 2024
c71f0b9
fix: add ts-expect-error
agoose77 Mar 13, 2024
b5f7ce8
Update packages/myst-directives/src/blockquote.ts
agoose77 Mar 13, 2024
d4e11b2
Update .changeset/seven-roses-cough.md
agoose77 Mar 13, 2024
25e30c1
Update packages/myst-transforms/src/blockquote.ts
agoose77 Mar 13, 2024
85a7bab
feat: change name o directive
agoose77 Mar 13, 2024
7a1f214
feat!: make container kind required
agoose77 Mar 13, 2024
93ecb71
chore: add changeset for myst-spec-ext
agoose77 Mar 13, 2024
63ad459
fix: rename use of blockquote
agoose77 Mar 13, 2024
cc4f0c4
changeset
rowanc1 Mar 13, 2024
65f2ae9
Fix up docs
rowanc1 Mar 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/great-cobras-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"myst-cli": patch
---

Add pullquotes, blockquotes and epigraph directives
5 changes: 5 additions & 0 deletions .changeset/orange-shrimps-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'myst-spec-ext': patch
---

Require `kind` for `Container`
6 changes: 6 additions & 0 deletions .changeset/seven-roses-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"myst-directives": minor
"myst-transforms": minor
---

Add support for epigraphs and pull-quotes using a new blockquote directive.
3 changes: 3 additions & 0 deletions docs/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ description: Code and code-blocks can be used to show programming languages.
:::{myst:directive} bibliography
:::

:::{myst:directive} blockquote
:::

:::{myst:directive} code
:::

Expand Down
4 changes: 2 additions & 2 deletions docs/directives.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ function createOption(directive, optName, option) {
optName === 'arg'
? 'Directive Argument'
: optName === 'body'
? 'Directive Body'
: optName,
? 'Directive Body'
: optName,
),
]),
...(optType
Expand Down
15 changes: 13 additions & 2 deletions docs/typography.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,22 @@ See [](#abbreviations) for more information.

## Quotations

Quotations are controlled with standard Markdown syntax, by inserting a caret (`>`) symbol in front of one or more lines of text. You can provide an attribution to a blockquote by adding `- author or source` to the final line.
Quotations are controlled with standard Markdown syntax, by inserting a caret (`>`) symbol in front of one or more lines of text. You can provide an attribution to a blockquote by adding a final paragraph whose text starts with `--` or an em-dash (`—`), followed by the author or source. The blank `>` line is required!

```{myst}
> We know what we are, but know not what we may be.
> - Hamlet act 4, Scene 5
>
> -- Hamlet act 4, Scene 5
```

The quotes can also be created using a directive (either {myst:directive}`blockquote` or {myst:directive}`epigraph`), which allows you to add classes or labels to the quote. MyST also supports {myst:directive}`pull-quote` directive, which act as attention-grabbing visual elements.

```{myst}
:::{pull-quote}
We know what we are, but know not what we may be.
:::

They say the owl was a baker’s daughter. Lord, we know what we are, but know not what we may be. God be at your table.
```

(definition-lists)=
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/myst-directives/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"url": "https://github.com/executablebooks/mystmd/issues"
},
"dependencies": {
"classnames": "^2.3.2",
"js-yaml": "^4.1.0",
"myst-common": "^1.1.29",
"myst-spec-ext": "^1.1.29",
Expand Down
50 changes: 50 additions & 0 deletions packages/myst-directives/src/blockquote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { DirectiveSpec, DirectiveData, GenericNode } from 'myst-common';
import { normalizeLabel } from 'myst-common';
import type { Container } from 'myst-spec-ext';
import classNames from 'classnames';

export const blockquoteDirective: DirectiveSpec = {
name: 'blockquote',
alias: ['epigraph', 'pull-quote'],
doc: 'Block quotes are used to indicate that the enclosed content forms an extended quotation. They may be followed by an inscription or attribution formed of a paragraph beginning with `--`, `---`, or an em-dash.',
options: {
label: {
type: String,
alias: ['name'],
},
class: {
type: String,
doc: `CSS classes to add to your blockquote. Special classes include:

- \`pull-quote\`: used for a blockquote node which should attract attention
- \`epigraph\`: used for a blockquote node that are usually found at the beginning of a document`,
},
},
body: {
type: 'myst',
doc: 'The body of the quote, which may contain a special attribution paragraph that is turned into a caption',
},
run(data: DirectiveData): GenericNode[] {
const children: GenericNode[] = [];
if (data.body) {
children.push(...(data.body as GenericNode[]));
}
const { label, identifier } = normalizeLabel(data.options?.label as string | undefined) || {};
const className = data.options?.class as string;
const container: Container = {
type: 'container',
kind: 'quote',
label,
identifier,
class: classNames({ [className]: className, [data.name]: data.name !== 'blockquote' }),
children: [
{
// @ts-expect-error: myst-spec needs updating to support blockquote
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this handled in the myst-spec updates?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No! Actually now I understand where my confusion came from. Let me fix that up.

type: 'blockquote',
children: children as any[],
},
],
};
return [container];
},
};
3 changes: 3 additions & 0 deletions packages/myst-directives/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { mdastDirective } from './mdast.js';
import { mermaidDirective } from './mermaid.js';
import { mystdemoDirective } from './mystdemo.js';
import { outputDirective } from './output.js';
import { blockquoteDirective } from './blockquote.js';
import { rawDirective } from './raw.js';

export const defaultDirectives = [
Expand All @@ -24,6 +25,7 @@ export const defaultDirectives = [
codeCellDirective,
dropdownDirective,
embedDirective,
blockquoteDirective,
figureDirective,
iframeDirective,
imageDirective,
Expand Down Expand Up @@ -56,4 +58,5 @@ export { mdastDirective } from './mdast.js';
export { mermaidDirective } from './mermaid.js';
export { mystdemoDirective } from './mystdemo.js';
export { outputDirective } from './output.js';
export { blockquoteDirective } from './blockquote.js';
export { rawDirective } from './raw.js';
2 changes: 1 addition & 1 deletion packages/myst-spec-ext/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export type Raw = {
};

export type Container = Omit<SpecContainer, 'kind'> & {
kind?: 'figure' | 'table' | 'quote' | 'code' | string;
kind: 'figure' | 'table' | 'quote' | 'code' | string;
source?: Dependency;
subcontainer?: boolean;
noSubcontainers?: boolean;
Expand Down
187 changes: 184 additions & 3 deletions packages/myst-transforms/src/blockquote.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,175 @@ import { u } from 'unist-builder';
import { blockquoteTransform } from './blockquote';

describe('Test blockquoteTransform', () => {
test('simple code block returns self', async () => {
test('block-quote with attribution inside container preserves container', async (quote) => {
const mdast = u('root', [
u('container', { kind: 'quote', class: 'pull-quote' }, [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', '-- Hamlet'), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('container', { kind: 'quote', class: 'pull-quote' }, [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
]),
u('caption', [
u('paragraph', [u('text', 'Hamlet'), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]),
);
});
test('block-quote without attribution inside container drops container', async (quote) => {
const mdast = u('root', [
u('container', { kind: 'quote' }, [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', 'Hamlet'), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', 'Hamlet'), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
);
});
test('block-quote without attribution inside container with class preserves container', async (quote) => {
const mdast = u('root', [
u('container', { kind: 'quote', class: 'foo' }, [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', 'Hamlet'), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('container', { kind: 'quote', class: 'foo' }, [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', 'Hamlet'), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]),
);
});
test('blockquote without attribution is unchanged', async () => {
const mdast = u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('list', { ordered: false }, [u('listItem', [u('text', 'Hamlet act 4, Scene 5')])]),
u('paragraph', [u('text', '(From Hamlet act 4, Scene 5)')]),
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', '(From Hamlet act 4, Scene 5)')]),
]),
]),
);
});
test('blockquote with malformed attribution is unchanged', async () => {
const mdast = u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', '?? -- Hamlet act 4, Scene 5')]),
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', '?? -- Hamlet act 4, Scene 5')]),
]),
]),
);
});
test.each(['---', '--', '—'])(
"blockquote with '%s'-format attribution becomes container",
async (quote) => {
const mdast = u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [
u('text', `${quote} Hamlet`),
u('strong', [u('text', 'act 4, Scene 5')]),
]),
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('container', { kind: 'quote' }, [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
]),
u('caption', [
u('paragraph', [u('text', `Hamlet`), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]),
);
},
);
test.each(['---', '--', '—'])(
"blockquote with nested '%s'-format attribution becomes container of container",
async (quote) => {
const mdast = u('root', [
u('blockquote', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [
u('text', `${quote} Hamlet`),
u('strong', [u('text', 'act 4, Scene 5')]),
]),
]),
u('paragraph', [
u('text', `${quote} Hamlet`),
u('strong', [u('text', 'act 4, Scene 5')]),
]),
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('container', { kind: 'quote' }, [
u('blockquote', [
u('container', { kind: 'quote' }, [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
]),
u('caption', [
u('paragraph', [u('text', `Hamlet`), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]),
u('caption', [
u('paragraph', [u('text', `Hamlet`), u('strong', [u('text', 'act 4, Scene 5')])]),
]),
]),
]),
);
},
);
test('blockquote with only-markup attribution loses text node', async () => {
const mdast = u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', '-- '), u('strong', [u('text', 'Hamlet act 4, Scene 5')])]),
]),
]);
blockquoteTransform(mdast);
agoose77 marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -17,7 +181,24 @@ describe('Test blockquoteTransform', () => {
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
]),
u('caption', [u('paragraph', [u('text', 'Hamlet act 4, Scene 5')])]),
u('caption', [u('paragraph', [u('strong', [u('text', 'Hamlet act 4, Scene 5')])])]),
]),
]),
);
});
test('blockquote with empty attribution is unchanged', async () => {
const mdast = u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', '-- ')]),
rowanc1 marked this conversation as resolved.
Show resolved Hide resolved
]),
]);
blockquoteTransform(mdast);
expect(mdast).toEqual(
u('root', [
u('blockquote', [
u('paragraph', [u('text', 'We know what we are, but know not what we may be.')]),
u('paragraph', [u('text', '-- ')]),
]),
]),
);
Expand Down
Loading
Loading