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

Markdownlint: note-box-headings custom rule #28372

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"./markdownlint/TOP007_useMarkdownLinks/TOP007_useMarkdownLinks.js",
"./markdownlint/TOP008_useBackticksForFencedCodeBlocks/TOP008_useBackticksForFencedCodeBlocks.js",
"./markdownlint/TOP009_lessonOverviewItemsSentenceStructure/TOP009_lessonOverviewItemsSentenceStructure.js",
"./markdownlint/TOP010_useLazyNumbering/TOP010_useLazyNumbering.js"
"./markdownlint/TOP010_useLazyNumbering/TOP010_useLazyNumbering.js",
"./markdownlint/TOP011_noteBoxHeadings/TOP011_noteBoxHeadings.js"
]
}
19 changes: 14 additions & 5 deletions LAYOUT_STYLE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,9 +408,7 @@ Will result in the following output:

Note boxes can be added by wrapping the content in a `div` with the class `lesson-note`. This will add styling to make the note stand out visually to users.

For nested markdown inside note boxes to be displayed properly additional `markdown="1" attribute` is needed.

A heading can be added to a note by using a `####` heading. When adding a heading, be sure to provide text that helps describe the note rather than "A note" or "Warning".
Note boxes will also need a `markdown="1"` attribute to render nested markdown properly.

The opening and closing tags must each be wrapped with a single blank line on either side, or a codeblock delimiter (triple backticks). This applies to any line that contains only a single HTML tag. The only exceptions to this rule are HTML tags inside `html`, `jsx`, `erb`, `ejs`, `javascript` or `ruby` codeblocks.

Expand All @@ -422,12 +420,23 @@ Different types of note boxes can be set by adding an extra class together with
- `lesson-note--warning` for warnings about potential issues/pitfalls, and are more severe than a tip
- `lesson-note--critical` for the most important warnings, such as critical information about handling sensitive data

### Note box headings

For better accessibility, note boxes must open with a level 4 heading (`####`) that starts with the note box variation.

- Standard note boxes (no variation class) must start their heading with `Note:` e.g. `#### Note: HTTP verbs`.
- Tip note box headings must start with `Tip:`.
- Warning note box headings must start with `Warning:`.
- Critical note box headings must start with `Critical:`.

All other normal heading rules apply.

#### Example

```markdown
<div class="lesson-note" markdown="1">

#### An optional title
#### Note: A standard note box heading

A sample note box.

Expand All @@ -437,7 +446,7 @@ A sample note box.
```markdown
<div class="lesson-note lesson-note--tip" markdown="1">

#### An optional title
#### Tip: A tip note box heading

A sample note box, variation: tip.

Expand Down
100 changes: 100 additions & 0 deletions markdownlint/TOP011_noteBoxHeadings/TOP011_noteBoxHeadings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
function capitalize(text) {
return `${text[0].toUpperCase()}${text.substring(1)}`;
}

function isNoteBoxOpenTag(token) {
return token?.type === "html_block" && token?.content.includes("lesson-note");
}

function lacksHeading(token, index, tokens) {
return isNoteBoxOpenTag(token) && tokens[index + 1]?.type !== "heading_open";
}

function categorizeHeadingsByNoteBoxType(
headings,
currentToken,
index,
tokens,
) {
const previousToken = tokens[index - 1];
if (
currentToken.type !== "heading_open" ||
!isNoteBoxOpenTag(previousToken)
) {
return headings;
}

// https://regexr.com/86jmp to test the regex below
const noteBoxType = capitalize(
previousToken.content.match(/(?<=lesson-note--).+?(?=\W)/)?.[0] ?? "note",
);

headings[noteBoxType].push({
text: currentToken.line,
hashes: currentToken.markup,
lineNumber: currentToken.lineNumber,
});
return headings;
}

module.exports = {
names: ["TOP011", "note-box-headings"],
description: "Note boxes have appropriate headings",
information: new URL(
"https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md",
),
tags: ["headings"],
parser: "markdownit",
function: function TOP011(params, onError) {
const { tokens } = params.parsers.markdownit;
const noteBoxesWithoutHeadings = tokens.filter(lacksHeading);
const noteBoxHeadings = tokens.reduce(categorizeHeadingsByNoteBoxType, {
Note: [],
Tip: [],
Warning: [],
Critical: [],
});

noteBoxesWithoutHeadings.forEach((noteBox) => {
onError({
lineNumber: noteBox.lineNumber,
detail:
"Note box is missing a heading. Note boxes must start with a level 4 heading (####).",
});
});

Object.entries(noteBoxHeadings).forEach(([noteBoxType, headings]) => {
headings.forEach(({ text, hashes, lineNumber }) => {
if (text.trim().startsWith(`#### ${capitalize(noteBoxType)}: `)) {
return;
}

const leadingSpaces = text.indexOf(hashes);
const firstWordIndex = leadingSpaces.length + hashes.length + 1;
const firstWord = text.slice(firstWordIndex, text.indexOf(":"));
const baseErrorInfo = {
lineNumber: lineNumber,
context: text,
};

if (firstWord !== noteBoxType) {
onError({
...baseErrorInfo,
detail: `${noteBoxType} box heading text must start with "${noteBoxType}: "`,
});
}
if (hashes.length !== 4) {
onError({
...baseErrorInfo,
detail: `Expected a level 4 heading (####) but got a level ${hashes.length} heading (${hashes}) instead.`,
fixInfo: {
editColumn: leadingSpaces + 1,
deleteCount: hashes.length,
insertText: "####",
},
});
}
});
});
},
};
117 changes: 117 additions & 0 deletions markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
### Introduction

This file should flag with TOP011 errors, and no other linting errors.

### Lesson overview

This section contains a general overview of topics that you will learn in this lesson.

- A LESSON OVERVIEW ITEM.

### Non-note box heading will not flag any TOP011 errors

Custom subsection content

<div class="lesson-note" markdown="1">

Check failure on line 15 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:15 TOP011/note-box-headings Note boxes have appropriate headings [Note box is missing a heading. Note boxes must start with a level 4 heading (####).] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Note boxes without headings will flag a TOP011 "missing header" error.

</div>

<div class="lesson-note lesson-note--tip" markdown="1">

## Level 2 note box heading: Will flag error as it should be level 4

Check failure on line 23 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:23 TOP011/note-box-headings Note boxes have appropriate headings [Tip box heading text must start with "Tip: "] [Context: "## Level 2 note box heading: Will flag error as it should be level 4"] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Check failure on line 23 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:23 TOP011/note-box-headings Note boxes have appropriate headings [Expected a level 4 heading (####) but got a level 2 heading (##) instead.] [Context: "## Level 2 note box heading: Will flag error as it should be level 4"] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Note box contents.

</div>

<div class="lesson-note" markdown="1">

### Level 3 note box heading: Will flag error as it should be level 4

Check failure on line 31 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:31 TOP011/note-box-headings Note boxes have appropriate headings [Note box heading text must start with "Note: "] [Context: "### Level 3 note box heading: Will flag error as it should be level 4"] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Check failure on line 31 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:31 TOP011/note-box-headings Note boxes have appropriate headings [Expected a level 4 heading (####) but got a level 3 heading (###) instead.] [Context: "### Level 3 note box heading: Will flag error as it should be level 4"] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Note box contents.

</div>

<div class="lesson-note" markdown="1">

#### Note: Correct heading format for standard note boxes

Note box contents.

</div>

<div class="lesson-note" markdown="1">

#### Standard note box heading not starting with "Note: "

Check failure on line 47 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:47 TOP011/note-box-headings Note boxes have appropriate headings [Note box heading text must start with "Note: "] [Context: "#### Standard note box heading not starting with "Note: ""] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Note box contents.

</div>

<div class="lesson-note lesson-note--tip" markdown="1">

#### Tip: Correct heading format for tip note boxes

Note box contents.

</div>

<div class="lesson-note lesson-note--tip" markdown="1">

#### Tip note box heading not starting with "Tip: "

Check failure on line 63 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:63 TOP011/note-box-headings Note boxes have appropriate headings [Tip box heading text must start with "Tip: "] [Context: "#### Tip note box heading not starting with "Tip: ""] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Note box contents.

</div>

<div class="lesson-note lesson-note--warning" markdown="1">

#### Warning: Correct heading format for warning note boxes

Note box contents.

</div>

<div class="lesson-note lesson-note--warning" markdown="1">

#### Warning note box heading not starting with "Warning: "

Check failure on line 79 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:79 TOP011/note-box-headings Note boxes have appropriate headings [Warning box heading text must start with "Warning: "] [Context: "#### Warning note box heading not starting with "Warning: ""] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Note box contents.

</div>

<div class="lesson-note lesson-note--critical" markdown="1">

#### Critical: Correct heading format for critical note boxes

Note box contents.

</div>

<div class="lesson-note lesson-note--critical" markdown="1">

#### Critical note box heading not starting with "Critical: "

Check failure on line 95 in markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md

View workflow job for this annotation

GitHub Actions / Lint lesson files

Note boxes have appropriate headings

markdownlint/TOP011_noteBoxHeadings/tests/TOP011_test.md:95 TOP011/note-box-headings Note boxes have appropriate headings [Critical box heading text must start with "Critical: "] [Context: "#### Critical note box heading not starting with "Critical: ""] https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md

Note box contents.

</div>

### Assignment

<div class="lesson-content__panel" markdown="1">

</div>

### Knowledge check

The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge.

- [A KNOWLEDGE CHECK QUESTION](A-KNOWLEDGE-CHECK-URL)

### Additional resources

This section contains helpful links to related content. It isn't required, so consider it supplemental.

- It looks like this lesson doesn't have any additional resources yet. Help us expand this section by contributing to our curriculum.
65 changes: 65 additions & 0 deletions markdownlint/docs/TOP011.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# `TOP011` - Note box headings

Tags: `headings`

Aliases: `note-box-headings`

This rule is triggered when a [note box](https://github.com/TheOdinProject/curriculum/blob/main/LAYOUT_STYLE_GUIDE.md#note-boxes) does not open with a level 4 heading and/or the heading text does not start with appropriate note box type (as described in the layout style guide).

```markdown
<div class="lesson-note" markdown="1">

This note box does not have a heading, so will flag a TOP011 error.

</div>
```

```markdown
<div class="lesson-note lesson-note--warning" markdown="1">

#### Heading not starting with "Warning:"

Note box contents.

</div>
```

The warning box above does open with a level 4 heading but the heading text does not start with `Warning:`. To resolve this error, the heading text can be changed to:

```markdown
#### Warning: Heading now starting with "Warning:"
```

A TOP011 error will also be thrown if the note box heading is not level 4:

```markdown
<div class="lesson-note" markdown="1">

### Note: Non-level 4 note box heading: Will flag a TOP011 error as it should be level 4

Note box contents.

</div>
```

This can be resolved by changing the note box heading to a level 4 heading:

```markdown
<div class="lesson-note" markdown="1">

#### Note: Level 4 note box heading: Correct and will not flag a TOP011 error

Note box contents.

</div>
```

Incorrect heading level errors are fixable with our `fix:*` npm scripts, which will convert the heading to be level 4. Missing heading errors and note box type errors are not fixable via our `fix:*` scripts, so must be manually addressed.

## Rationale

Our lesson headings are linkable via ID fragments. By enforcing headings for note boxes, they can be better summarised at a glance while always being linkable to.

While different note box types are distinguishable via border colors and icons, this may not be sufficiently accessible for some people with visual deficiencies. It is more accessible to convey the note box type through the heading text itself.

Consistent use of heading levels for note boxes also looks better on the website. Since note boxes are not main sections themselves, they should not use level 3 headings. The website also uses specific CSS for when note box headings are hovered over. Having non-level 4 headings in these note boxes causes behaviour inconsistent with what we expect.
6 changes: 3 additions & 3 deletions templates/lesson-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ Now that you understand the basic syntax of HTML and CSS, we’re going to get s

This section contains a general overview of topics that you will learn in this lesson.

- What the box model is
- Margins, padding, and borders
- What the box model is.
- Margins, padding, and borders.

### The box model

The first important concept that you need to understand to be successful in CSS is the box model. It isn’t complicated, but skipping over it now will cause you much frustration down the line.

<div class="lesson-note" markdown="1">

#### Everything is a box
#### Note: Everything is a box

Every single thing on a webpage is a rectangular box. These boxes can have other boxes in them and can sit alongside one another.

Expand Down
8 changes: 5 additions & 3 deletions templates/lesson-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,32 @@ CUSTOM SECTION CONTENT.

<div class="lesson-note" markdown="1">

#### A sample title
#### Note: A sample title

A sample note box.

</div>

<div class="lesson-note lesson-note--tip" markdown="1">

#### level 4 heading for title is recommended
#### Tip: A level 4 heading as a title is required

A sample note box, variation: tip.

</div>

<div class="lesson-note lesson-note--warning" markdown="1">

#### But title is also optional
#### Warning: And must start with the note box type

A sample note box, variation: warning.

</div>

<div class="lesson-note lesson-note--critical" markdown="1">

#### Critical: Otherwise you'll get a lint error

A sample note box, variation: critical.

</div>
Expand Down
Loading