Skip to content

Commit

Permalink
feat: disable attributes copy to parent (#157)
Browse files Browse the repository at this point in the history
* feat: disabled copying of heading attributes into sections

* docs: update documents

* chore: added a "test:watch" command

* feat: implement a "aria-labelledby" attribute for a section

* docs: update a documents

* chore: updated lock file from yarn command

* feat: Copying heading id attribute value to section aria-labelledby attribute

In addition, the type definitions of KeyValue are commonized.

* docs: fixed copy description as the `id` attribute of the heading is always created
  • Loading branch information
akabekobeko authored Oct 16, 2022
1 parent 4a174f1 commit 3690e17
Show file tree
Hide file tree
Showing 12 changed files with 5,844 additions and 6,002 deletions.
40 changes: 14 additions & 26 deletions docs/ja/vfm.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,10 +565,8 @@ ruby rt {
見出しを階層的なセクションにします。

- 親が `blockquote` の場合はセクションを分けません
- 見出しの属性は基本的にセクションへコピーされます
- `id` 属性はセクションに移動します
- `hidden` 属性はコピーされず、見出しだけに適用されます
- 見出しの深さへ一致するように、セクションの `levelN` クラスを設定します
- 見出しの `id` 属性値をセクションの `aria-labelledby` 属性へ値をコピーします

**VFM**

Expand All @@ -589,28 +587,24 @@ ruby rt {
**HTML**

```html
<section id="plain" class="level1">
<h1>Plain</h1>
<section class="level1" aria-labelledby="plain">
<h1 id="plain">Plain</h1>
</section>

<section id="intro" class="level1">
<h1>Introduction</h1>
<section class="level1" aria-labelledby="intro">
<h1 id="intro">Introduction</h1>
</section>

<section class="level1 title" id="welcome">
<h1 class="title">Welcome</h1>
<section class="level1" aria-labelledby="welcome">
<h1 class="title" id="welcome">Welcome</h1>
</section>

<section id="level-1" class="level1">
<h1>Level 1</h1>
<section id="level-2" class="level2">
<h2>Level 2</h2>
<section class="level1" aria-labelledby="level-1">
<h1 id="level-1">Level 1</h1>
<section class="level2" aria-labelledby="level-2">
<h2 id="level-2">Level 2</h2>
<blockquote>
<h1 id="not-sectionize">Not Sectionize</h1>
</blockquote>
</section>
</section>

<blockquote>
<h1 id="not-sectionize">Not Sectionize</h1>
</blockquote>
```

**CSS**
Expand All @@ -621,12 +615,6 @@ body > section {
body > section > h1:first-child {
}

section.title {
}

section.title > h1:first-child {
}

.level1 {
}
.level2 {
Expand Down
40 changes: 14 additions & 26 deletions docs/vfm.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,10 +565,8 @@ If want to escape the delimiter pipe `|`, add `\` immediately before it.
Make the heading a hierarchical section.

- Do not sectionize if parent is `blockquote`.
- The attributes of the heading are basically copied to the section.
- The `id` attribute is moved to the section.
- The `hidden` attribute is not copied and only the heading applies.
- Set the `levelN` class in the section to match the heading depth.
- Copy the value of the `id` attribute of the heading to the `aria-labelledby` attribute of the section.

**VFM**

Expand All @@ -589,28 +587,24 @@ Make the heading a hierarchical section.
**HTML**

```html
<section id="plain" class="level1">
<h1>Plain</h1>
<section class="level1" aria-labelledby="plain">
<h1 id="plain">Plain</h1>
</section>

<section id="intro" class="level1">
<h1>Introduction</h1>
<section class="level1" aria-labelledby="intro">
<h1 id="intro">Introduction</h1>
</section>

<section class="level1 title" id="welcome">
<h1 class="title">Welcome</h1>
<section class="level1" aria-labelledby="welcome">
<h1 class="title" id="welcome">Welcome</h1>
</section>

<section id="level-1" class="level1">
<h1>Level 1</h1>
<section id="level-2" class="level2">
<h2>Level 2</h2>
<section class="level1" aria-labelledby="level-1">
<h1 id="level-1">Level 1</h1>
<section class="level2" aria-labelledby="level-2">
<h2 id="level-2">Level 2</h2>
<blockquote>
<h1 id="not-sectionize">Not Sectionize</h1>
</blockquote>
</section>
</section>

<blockquote>
<h1 id="not-sectionize">Not Sectionize</h1>
</blockquote>
```

**CSS**
Expand All @@ -621,12 +615,6 @@ body > section {
body > section > h1:first-child {
}

section.title {
}

section.title > h1:first-child {
}

.level1 {
}
.level2 {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"release:dry": "release-it --dry-run",
"release:pre": "release-it --preRelease --npm.tag=latest",
"test": "jest",
"test:watch": "jest --runInBand --watch",
"test:debug": "jest tests/xxx.test.ts --silent=false --verbose false"
},
"dependencies": {
Expand Down
3 changes: 0 additions & 3 deletions src/plugins/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { Node } from 'unist';
import { VFile } from 'vfile';
import { Attribute, Metadata } from './metadata';

/** Key/Value pair. */
type KeyValue = { [key: string]: any };

/**
* Create AST properties from attributes.
* @param attributes Attributes.
Expand Down
6 changes: 0 additions & 6 deletions src/plugins/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@ export type Metadata = {
};
};

/**
* Key/Value pair.
* Definition to enable subscript access of `Object`.
*/
type KeyValue = { [key: string]: any };

/**
* Extension of VFM metadata to VFile data.
*/
Expand Down
47 changes: 16 additions & 31 deletions src/plugins/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,26 @@ import visit from 'unist-util-visit-parents';
const MAX_HEADING_DEPTH = 6;

/**
* Check the heading properties to generate properties for the parent `<section>` and update the heading style.
* @param node Node of Markdown AST.
* Create the attribute properties of a section.
* @param depth - Depth of heading elements that are sections.
* @param node - Node of Markdown AST.
* @returns Properties.
*/
const checkProperties = (node: any, depth: number) => {
if (!node.data?.hProperties) {
return undefined;
}

// Remove `id` attribute and copy otherwise for the parent `<section>`
const hProperties = { ...node.data.hProperties };
if (node.data.hProperties.id) {
delete node.data.hProperties.id;
}

// {hidden} specifier
if (Object.keys(hProperties).includes('hidden')) {
node.data.hProperties.hidden = 'hidden';
}
const createProperties = (depth: number, node: any): KeyValue => {
const properties: KeyValue = {
class: [`level${depth}`],
};

// output section levels like Pandoc
if (Array.isArray(hProperties.class)) {
// Switch references as they do not affect the heading,
// and `remark-attr` may add classes, so make sure they come before them (always top)
const classes = [...hProperties.class];
classes.unshift(`level${depth}`);
hProperties.class = classes;
} else {
hProperties.class = [`level${depth}`];
if (node?.data?.hProperties?.id) {
properties['aria-labelledby'] = node?.data.hProperties.id;
}

return hProperties;
return properties;
};

/**
* Wrap the header in sections.
* - Do not sectionize if parent is `blockquote`.
* - The attributes of the heading are basically copied to the section.
* - The `id` attribute is moved to the section.
* - The `hidden` attribute is not copied and only the heading applies.
* - Set the `levelN` class in the section to match the heading depth.
* @param node Node of Markdown AST.
* @param ancestors Parents.
Expand All @@ -80,7 +60,12 @@ const sectionize = (node: any, ancestors: Parent[]) => {
endIndex > 0 ? endIndex : undefined,
);

const hProperties = checkProperties(node, depth);
const hProperties = createProperties(depth, node);

// {hidden} specifier
if (Object.keys(node.data.hProperties).includes('hidden')) {
node.data.hProperties.hidden = 'hidden';
}

const isDuplicated = parent.type === 'section';
if (isDuplicated) {
Expand Down
92 changes: 25 additions & 67 deletions tests/attr.test.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,28 @@
import { stripIndent } from 'common-tags';
import { buildProcessorTestingCode } from './utils';
import { stringify } from '../src/index';

it(
'Heading with attributes',
buildProcessorTestingCode(
`# Heading {#foo}`,
stripIndent`
root[1]
└─0 heading[1]
│ depth: 1
│ data: {"hProperties":{"id":"foo"}}
└─0 text "Heading"
`,
`<section id="foo" class="level1"><h1>Heading</h1></section>`,
),
);
it('Heading with attributes', () => {
const received = stringify('# Heading {#foo}', {
partial: true,
disableFormatHtml: true,
});
const expected = `<section class="level1" aria-labelledby="foo"><h1 id="foo">Heading</h1></section>`;
expect(received).toBe(expected);
});

it(
'Heading with attributes, specification by line break',
buildProcessorTestingCode(
`# Heading\n{#foo}`,
stripIndent`
root[1]
└─0 heading[1]
│ depth: 1
│ data: {"hProperties":{"id":"foo"}}
└─0 text "Heading"
`,
`<section id="foo" class="level1"><h1>Heading</h1></section>`,
),
);
it('Heading with attributes, specification by line break', () => {
const received = stringify('# Heading\n{#foo}', {
partial: true,
disableFormatHtml: true,
});
const expected = `<section class="level1" aria-labelledby="foo"><h1 id="foo">Heading</h1></section>`;
expect(received).toBe(expected);
});

it(
'Heading with attributes and inline elements, specification by line break',
buildProcessorTestingCode(
`# Heading *test*\n{#foo}`,
stripIndent`
root[1]
└─0 heading[2]
│ depth: 1
│ data: {"hProperties":{"id":"foo"}}
├─0 text "Heading "
└─1 emphasis[1]
└─0 text "test"
`,
`<section id="foo" class="level1"><h1>Heading <em>test</em></h1></section>`,
),
);

// `remark-attr` needs to be fixed
// https://github.com/arobase-che/remark-attr/issues/24
/*
it(
'Heading with attributes and inline elements',
buildProcessorTestingCode(
`# Heading *test* {#foo}`,
stripIndent`
root[1]
└─0 heading[2]
│ depth: 1
│ data: {"hProperties":{"id":"foo"}}
├─0 text "Heading "
└─1 emphasis[1]
└─0 text "test"
`,
`<section id="foo"><h1>Heading <em>test</em></h1></section>`,
),
);
*/
it('Heading with attributes and inline elements, specification by line break', () => {
const received = stringify('# Heading *test*\n{#foo}', {
partial: true,
disableFormatHtml: true,
});
const expected = `<section class="level1" aria-labelledby="foo"><h1 id="foo">Heading <em>test</em></h1></section>`;
expect(received).toBe(expected);
});
4 changes: 2 additions & 2 deletions tests/footnotes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ it('Heading title and section id without inline footnotes text', () => {
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<section id="test" class="level1">
<h1>Test<a id="fnref1" href="#fn1" class="footnote-ref" role="doc-noteref"><sup>1</sup></a></h1>
<section class="level1" aria-labelledby="test">
<h1 id="test">Test<a id="fnref1" href="#fn1" class="footnote-ref" role="doc-noteref"><sup>1</sup></a></h1>
</section>
<section class="footnotes" role="doc-endnotes">
<hr>
Expand Down
8 changes: 4 additions & 4 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ it('Raw HTML with Markdown', () => {
);
const expected = `
<div class="custom">
<section id="heading" class="level1">
<h1>Heading</h1>
<section class="level1" aria-labelledby="heading">
<h1 id="heading">Heading</h1>
</section>
</div>
`;
Expand All @@ -115,8 +115,8 @@ it('User-specified metadata (without Frontmatter)', () => {
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body id="page">
<section id="title" class="level1">
<h1>Title</h1>
<section class="level1" aria-labelledby="title">
<h1 id="title">Title</h1>
</section>
</body>
</html>
Expand Down
Loading

0 comments on commit 3690e17

Please sign in to comment.