Skip to content

Commit

Permalink
implement column layout as on BOLDtools
Browse files Browse the repository at this point in the history
  • Loading branch information
dmca-glasgow committed Apr 29, 2022
1 parent 1ad7417 commit e959b55
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 0 deletions.
38 changes: 38 additions & 0 deletions compiler/src/linter/assert-columns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Root } from 'mdast';
import { ContainerDirective } from 'mdast-util-directive';
import { visit } from 'unist-util-visit';
import { VFile } from 'vfile';

import { failMessage } from '../utils/message';

export function assertColumnStructure() {
return (tree: Root, file: VFile) => {
visit(
tree,
'containerDirective',
(node: ContainerDirective, index, _parent) => {
if (node.name === 'columns') {
const children = node.children as ContainerDirective[];
const columns = children.filter((o) => o.name === 'column');
if (columns.length < 2) {
failMessage(
file,
'Columns must contain at least 2 columns',
node.position
);
}
}
if (node.name === 'column') {
const parent = _parent as ContainerDirective;
if (!parent || parent.name !== 'columns') {
failMessage(
file,
'Column must be nested inside columns',
node.position
);
}
}
}
);
};
}
2 changes: 2 additions & 0 deletions compiler/src/linter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { VFile } from 'vfile';

import { Context } from '../context';
import { assertAssetExists } from './assert-asset-exists';
import { assertColumnStructure } from './assert-columns';
import { assertNoH1 } from './assert-no-h1';
import { assertTaskAnswerStructure } from './assert-task-answer';
import { assertVideoAttributes } from './assert-video-attributes';
Expand Down Expand Up @@ -51,6 +52,7 @@ export async function createReport(
.use(assertAssetExists)
.use(assertVideoAttributes)
.use(assertTaskAnswerStructure)
.use(assertColumnStructure)
.use(assertWeblinkTarget)
.use(assertNoH1)
.use(lintLatex)
Expand Down
213 changes: 213 additions & 0 deletions compiler/src/mdast/__test__/columns.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import {
ignoreWhitespace,
testProcessor,
} from '../../test-utils/test-processor';

describe('columns', () => {
it('should render columns from macro', async () => {
const { md } = await testProcessor(`
##[columns]
###[column]
Column 1
###[/column]
###[column]
Column 2
###[/column]
##[/columns]
`);

expect(ignoreWhitespace(md)).toBe(
ignoreWhitespace(`
::::columns
:::column
Column 1
:::
:::column
Column 2
:::
::::
`)
);
});

it('should fail on columns with no column', async () => {
const { hasFailingMessage } = await testProcessor(
`
::::columns
::::
`,
{ shouldFail: true }
);
expect(
hasFailingMessage('Columns must contain at least 2 columns')
).toBe(true);
});

it('should fail on columns with one column', async () => {
const { hasFailingMessage } = await testProcessor(
`
::::columns
:::column
Column 1
:::
::::
`,
{ shouldFail: true }
);
expect(
hasFailingMessage('Columns must contain at least 2 columns')
).toBe(true);
});

it('should fail on columns with one column', async () => {
const { hasFailingMessage } = await testProcessor(
`
:::column
Column 1
:::
`,
{ shouldFail: true }
);
expect(hasFailingMessage('Column must be nested inside columns')).toBe(
true
);
});

it('should render columns', async () => {
const { html } = await testProcessor(`
::::columns
:::column
Column 1
:::
:::column
Column 2
:::
::::
`);

expect(ignoreWhitespace(html)).toBe(
ignoreWhitespace(`
<div class="columns">
<div class="column">
<p>Column 1</p>
</div>
<div class="column">
<p>Column 2</p>
</div>
</div>
`)
);
});

it('should render columns with image', async () => {
const { html } = await testProcessor(`
##[columns]
###[column, imgsrc="LMpr.pdf"]
###[/column]
###[column, imgsrc="LMpo.pdf"]
###[/column]
##[/columns]
`);

expect(ignoreWhitespace(html)).toBe(
ignoreWhitespace(`
<div class="columns">
<div class="column">
<figure class="img-wrapper" id="figure-1">
<div class="img-bg">
<img src="LMpr.pdf">
</div>
<figcaption><a href="#figure-1"><span class="caption-count">Figure 1</span></a></figcaption>
</figure>
</div>
<div class="column">
<figure class="img-wrapper" id="figure-2">
<div class="img-bg">
<img src="LMpo.pdf">
</div>
<figcaption><a href="#figure-2"><span class="caption-count">Figure 2</span></a></figcaption>
</figure>
</div>
</div>
`)
);
});

it('should render columns with image and alt text', async () => {
const { html } = await testProcessor(`
##[columns]
###[column, imgsrc="LMpr.pdf"]
Alt text 1
###[/column]
###[column, imgsrc="LMpo.pdf"]
Alt text 2
###[/column]
##[/columns]
`);

expect(ignoreWhitespace(html)).toBe(
ignoreWhitespace(`
<div class="columns">
<div class="column">
<figure class="img-wrapper" id="alt-text-1">
<div class="img-bg">
<img src="LMpr.pdf" alt="Alt text 1">
</div>
<figcaption><a href="#alt-text-1"><span class="caption-count">Figure 1:</span> Alt text 1</a></figcaption>
</figure>
</div>
<div class="column">
<figure class="img-wrapper" id="alt-text-2">
<div class="img-bg">
<img src="LMpo.pdf" alt="Alt text 2">
</div>
<figcaption><a href="#alt-text-2"><span class="caption-count">Figure 2:</span> Alt text 2</a></figcaption>
</figure>
</div>
</div>
`)
);
});

it('should render columns with image and alt text and other text', async () => {
const { html } = await testProcessor(`
##[columns]
###[column, imgsrc="LMpr.pdf"]
Alt text 1
More text 1
###[/column]
###[column, imgsrc="LMpo.pdf"]
Alt text 2
More text 2
###[/column]
##[/columns]
`);

expect(ignoreWhitespace(html)).toBe(
ignoreWhitespace(`
<div class="columns">
<div class="column">
<figure class="img-wrapper" id="alt-text-1">
<div class="img-bg">
<img src="LMpr.pdf" alt="Alt text 1">
</div>
<figcaption><a href="#alt-text-1"><span class="caption-count">Figure 1:</span> Alt text 1</a></figcaption>
</figure>
<p>More text 1</p>
</div>
<div class="column">
<figure class="img-wrapper" id="alt-text-2">
<div class="img-bg">
<img src="LMpo.pdf" alt="Alt text 2">
</div>
<figcaption><a href="#alt-text-2"><span class="caption-count">Figure 2:</span> Alt text 2</a></figcaption>
</figure>
<p>More text 2</p>
</div>
</div>
`)
);
});
});
59 changes: 59 additions & 0 deletions compiler/src/mdast/columns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { BlockContent, Text } from 'mdast';
import { ContainerDirective } from 'mdast-util-directive';
import { Node, Parent } from 'unist';
import { visit } from 'unist-util-visit';

export function columns() {
return (tree: Node) => {
visit(tree, 'containerDirective', (node: ContainerDirective) => {
if (node.name === 'columns') {
node.data = {
hProperties: {
className: 'columns',
},
};
} else if (node.name === 'column') {
node.data = {
hProperties: {
className: 'column',
},
};

if (node.attributes?.imgsrc) {
const altText = getAltText(node);

const img = {
type: 'image',
url: node.attributes.imgsrc,
alt: altText,
} as unknown as BlockContent;

if (altText) {
Object.assign(node.children[0], img);
} else {
node.children.unshift(img);
}
}
}
});
};
}

function getAltText(column: ContainerDirective) {
const firstChild = column.children[0] as Parent;
if (!firstChild) {
return false;
}

const firstChildChildren = firstChild.children as Node[];
if (!Array.isArray(firstChildChildren)) {
return false;
}

const firstChildFirstChild = firstChildChildren[0] as Text;
if (!firstChildFirstChild) {
return false;
}

return firstChildFirstChild.value;
}
2 changes: 2 additions & 0 deletions compiler/src/mdast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { aliasDirectiveToSvg } from '../latex/directive-to-svg';
// import { aliasDirectiveToTex } from '../latex/directive-to-tex';
import { createSvg } from '../utils/icons';
import { codeBlocks } from './code-blocks';
import { columns } from './columns';
import { embedAssetUrl } from './embed-asset-url';
import { images } from './images';
import { pagebreaks } from './pagebreaks';
Expand Down Expand Up @@ -43,6 +44,7 @@ export async function mdastPhase(file: VFile, ctx: Context) {
.use(removeEmptyParagraphs)
// .use(aliasDirectiveToTex, ctx)
.use(codeBlocks, ctx)
.use(columns)
.use(images, ctx)
.use(pagebreaks);

Expand Down
1 change: 1 addition & 0 deletions compiler/src/pre-parse/convert-macro-to-directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function renderContainerDirective({ name, title, attributes }: Container) {
function getColons(name: string) {
switch (name.replace('/', '')) {
case 'task':
case 'columns':
return '::::';
case 'video':
return '::';
Expand Down

0 comments on commit e959b55

Please sign in to comment.