Repository: zestedesavoir/zmarkdown Branch: master Commit: adf87936e8ec Files: 694 Total size: 1.9 MB Directory structure: gitextract_4t6_2vzi/ ├── .editorconfig ├── .github/ │ └── workflows/ │ ├── ci.yml │ └── prepublish.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE-MIT ├── README.md ├── babel.config.js ├── eslint.config.mjs ├── lerna.json ├── package.json └── packages/ ├── mdast-util-split-by-heading/ │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── rebber/ │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── mdast.tests.js.snap │ │ ├── fixtures/ │ │ │ ├── amps-and-angles-encoding.text │ │ │ ├── auto-link-invalid.text │ │ │ ├── auto-link-lines.text │ │ │ ├── auto-link-output.output.text │ │ │ ├── auto-link-url-invalid.text │ │ │ ├── auto-link-url.text │ │ │ ├── auto-link.text │ │ │ ├── backslash-escapes.text │ │ │ ├── block-elements.text │ │ │ ├── blockquote-indented.text │ │ │ ├── blockquote-lazy-code.text │ │ │ ├── blockquote-lazy-fence.text │ │ │ ├── blockquote-lazy-list.text │ │ │ ├── blockquote-lazy-rule.text │ │ │ ├── blockquote-list-item.text │ │ │ ├── blockquotes-empty-lines.output.text │ │ │ ├── blockquotes-with-code-blocks.text │ │ │ ├── blockquotes.text │ │ │ ├── bom.text │ │ │ ├── breaks-hard.text │ │ │ ├── case-insensitive-refs.text │ │ │ ├── code-block-escape.text │ │ │ ├── code-block-indentation.nooutput.text │ │ │ ├── code-block-nesting-bug.nooutput.text │ │ │ ├── code-block.output.fence=`.text │ │ │ ├── code-block.output.fence=~.text │ │ │ ├── code-blocks.output.fences.text │ │ │ ├── code-blocks.output.text │ │ │ ├── code-blocks.text │ │ │ ├── code-spans.text │ │ │ ├── def-blocks.text │ │ │ ├── definition-newline.text │ │ │ ├── definition-unclosed-attribute.text │ │ │ ├── definition-unclosed.text │ │ │ ├── deletion.text │ │ │ ├── double-link.text │ │ │ ├── emphasis-empty.text │ │ │ ├── emphasis-escaped-final-marker.text │ │ │ ├── emphasis-internal.text │ │ │ ├── emphasis.output.emphasis=-asterisk-.strong=_.text │ │ │ ├── emphasis.output.emphasis=_.strong=-asterisk-.text │ │ │ ├── empty.text │ │ │ ├── entities-advanced.text │ │ │ ├── entities.output.entities.text │ │ │ ├── entities.output.entities=escape.text │ │ │ ├── entities.output.entities=numbers.text │ │ │ ├── entities.output.noentities.text │ │ │ ├── entities.text │ │ │ ├── escaped-angles.text │ │ │ ├── fenced-code-empty.text │ │ │ ├── fenced-code-trailing-characters-2.nooutput.text │ │ │ ├── fenced-code-trailing-characters.nooutput.text │ │ │ ├── fenced-code-white-space-after-flag.text │ │ │ ├── fenced-code.text │ │ │ ├── hard-wrapped-paragraphs-with-list-like-lines.text │ │ │ ├── heading-atx-closed-trailing-white-space.text │ │ │ ├── heading-atx-empty.text │ │ │ ├── heading-in-blockquote.text │ │ │ ├── heading-in-paragraph.text │ │ │ ├── heading-not-atx.text │ │ │ ├── heading-setext-with-initial-spacing.text │ │ │ ├── heading.output.close-atx.text │ │ │ ├── heading.output.setext.text │ │ │ ├── horizontal-rules-adjacent.text │ │ │ ├── horizontal-rules.text │ │ │ ├── hr-list-break.text │ │ │ ├── hr.output.norule-spaces.text │ │ │ ├── hr.output.rule-repetition=5.text │ │ │ ├── hr.output.rule=-.text │ │ │ ├── hr.output.rule=-asterisk-.text │ │ │ ├── hr.output.rule=_.text │ │ │ ├── html-advanced.text │ │ │ ├── html-attributes.text │ │ │ ├── html-cdata.text │ │ │ ├── html-comments.text │ │ │ ├── html-declaration.text │ │ │ ├── html-indented.text │ │ │ ├── html-processing-instruction.text │ │ │ ├── html-simple.text │ │ │ ├── html-tags.text │ │ │ ├── image-basename-dots.text │ │ │ ├── image-empty-alt.text │ │ │ ├── image-in-link.text │ │ │ ├── image-path-escape.text │ │ │ ├── image-with-pipe.text │ │ │ ├── images.output.noreference-images.text │ │ │ ├── invalid-link-definition.text │ │ │ ├── lazy-blockquotes.text │ │ │ ├── link-in-link.text │ │ │ ├── link-spaces.text │ │ │ ├── link-whitespace.text │ │ │ ├── link-with-spaces.text │ │ │ ├── links-inline-style.text │ │ │ ├── links-reference-proto.text │ │ │ ├── links-reference-style.text │ │ │ ├── links-shortcut-references.text │ │ │ ├── links-text-delimiters.text │ │ │ ├── links-text-empty.text │ │ │ ├── links-text-entity-delimiters.text │ │ │ ├── links-text-escaped-delimiters.text │ │ │ ├── links-text-mismatched-delimiters.text │ │ │ ├── links-title-double-quotes-delimiters.text │ │ │ ├── links-title-double-quotes-entity-delimiters.text │ │ │ ├── links-title-double-quotes-escaped-delimiters.text │ │ │ ├── links-title-double-quotes-mismatched-delimiters.text │ │ │ ├── links-title-double-quotes.text │ │ │ ├── links-title-empty-double-quotes.text │ │ │ ├── links-title-empty-parentheses.text │ │ │ ├── links-title-empty-single-quotes.text │ │ │ ├── links-title-parentheses.text │ │ │ ├── links-title-single-quotes-delimiters.text │ │ │ ├── links-title-single-quotes-entity-delimiters.text │ │ │ ├── links-title-single-quotes-escaped-delimiters.text │ │ │ ├── links-title-single-quotes-mismatched-delimiters.text │ │ │ ├── links-title-single-quotes.text │ │ │ ├── links-title-unclosed.text │ │ │ ├── links-url-empty-title-double-quotes.text │ │ │ ├── links-url-empty-title-parentheses.text │ │ │ ├── links-url-empty-title-single-quotes.text │ │ │ ├── links-url-empty.text │ │ │ ├── links-url-entity-parentheses.text │ │ │ ├── links-url-escaped-parentheses.text │ │ │ ├── links-url-mismatched-parentheses.text │ │ │ ├── links-url-nested-parentheses.text │ │ │ ├── links-url-new-line.text │ │ │ ├── links-url-unclosed.text │ │ │ ├── links-url-white-space.text │ │ │ ├── links.output.noreference-links.text │ │ │ ├── list-after-list.text │ │ │ ├── list-and-code.text │ │ │ ├── list-continuation.text │ │ │ ├── list-indentation.nooutput.text │ │ │ ├── list-item-empty-with-white-space.text │ │ │ ├── list-item-empty.text │ │ │ ├── list-item-indent.list-item-indent=1.output.text │ │ │ ├── list-item-indent.list-item-indent=mixed.output.text │ │ │ ├── list-item-indent.list-item-indent=tab.output.text │ │ │ ├── list-item-newline.nooutput.text │ │ │ ├── list-item-text.text │ │ │ ├── list-ordered.increment-list-marker.output.text │ │ │ ├── list-ordered.noincrement-list-marker.output.text │ │ │ ├── list.output.bullet=+.text │ │ │ ├── list.output.bullet=-.text │ │ │ ├── list.output.bullet=-asterisk-.text │ │ │ ├── lists-with-code-and-rules.text │ │ │ ├── loose-lists.text │ │ │ ├── main.text │ │ │ ├── markdown-documentation-basics.text │ │ │ ├── markdown-documentation-syntax.text │ │ │ ├── mixed-indentation.text │ │ │ ├── nested-blockquotes.text │ │ │ ├── nested-code.text │ │ │ ├── nested-em.nooutput.text │ │ │ ├── nested-references.text │ │ │ ├── nested-square-link.text │ │ │ ├── no-positionals.nooutput.text │ │ │ ├── not-a-link.text │ │ │ ├── ordered-and-unordered-lists.text │ │ │ ├── ordered-different-types.text │ │ │ ├── ordered-with-parentheses.text │ │ │ ├── paragraphs-and-indentation.text │ │ │ ├── paragraphs-empty.text │ │ │ ├── ref-paren.text │ │ │ ├── reference-image-empty-alt.text │ │ │ ├── reference-link-escape.nooutput.text │ │ │ ├── reference-link-not-closed.text │ │ │ ├── reference-link-with-angle-brackets.text │ │ │ ├── reference-link-with-multiple-definitions.text │ │ │ ├── same-bullet.text │ │ │ ├── stringify-escape.output.commonmark.text │ │ │ ├── stringify-escape.output.nogfm.commonmark.text │ │ │ ├── stringify-escape.output.nogfm.text │ │ │ ├── stringify-escape.output.noposition.pedantic.text │ │ │ ├── stringify-escape.output.pedantic.text │ │ │ ├── stringify-escape.output.text │ │ │ ├── stringify-escape.text │ │ │ ├── strong-and-em-together-one.text │ │ │ ├── strong-and-em-together-two.nooutput.text │ │ │ ├── strong-emphasis.text │ │ │ ├── strong-initial-white-space.text │ │ │ ├── table-empty-initial-cell.text │ │ │ ├── table-escaped-pipes.nooutput.text │ │ │ ├── table-in-list.text │ │ │ ├── table-invalid-alignment.text │ │ │ ├── table-loose.output.loose-table.text │ │ │ ├── table-loose.output.text │ │ │ ├── table-no-body.text │ │ │ ├── table-no-end-of-line.text │ │ │ ├── table-one-column.text │ │ │ ├── table-one-row.text │ │ │ ├── table-padded.output.nopadded-table.text │ │ │ ├── table-padded.output.text │ │ │ ├── table-pipes-in-code.text │ │ │ ├── table-spaced.output.nospaced-table.text │ │ │ ├── table-spaced.output.text │ │ │ ├── table-with-image.text │ │ │ ├── table.text │ │ │ ├── tabs-and-spaces.text │ │ │ ├── tabs.text │ │ │ ├── task-list-ordered.text │ │ │ ├── task-list-unordered-asterisk.text │ │ │ ├── task-list-unordered-dash.text │ │ │ ├── task-list-unordered-plus.text │ │ │ ├── task-list.text │ │ │ ├── tidyness.text │ │ │ ├── title-attributes.text │ │ │ ├── toplevel-paragraphs.text │ │ │ └── tricky-list.text │ │ └── mdast.tests.js │ ├── dist/ │ │ ├── all.js │ │ ├── escaper.js │ │ ├── index.js │ │ ├── one.js │ │ ├── preprocessors/ │ │ │ ├── index.js │ │ │ └── referenceVisitors.js │ │ └── types/ │ │ ├── blockquote.js │ │ ├── break.js │ │ ├── code.js │ │ ├── definition.js │ │ ├── delete.js │ │ ├── emphasis.js │ │ ├── heading.js │ │ ├── html.js │ │ ├── image.js │ │ ├── inlinecode.js │ │ ├── link.js │ │ ├── linkReference.js │ │ ├── list.js │ │ ├── listItem.js │ │ ├── paragraph.js │ │ ├── raw.js │ │ ├── root.js │ │ ├── strong.js │ │ ├── table.js │ │ ├── tableCell.js │ │ ├── tableRow.js │ │ ├── text.js │ │ └── thematic-break.js │ ├── package.json │ └── src/ │ ├── all.js │ ├── escaper.js │ ├── index.js │ ├── one.js │ ├── preprocessors/ │ │ ├── index.js │ │ └── referenceVisitors.js │ └── types/ │ ├── blockquote.js │ ├── break.js │ ├── code.js │ ├── definition.js │ ├── delete.js │ ├── emphasis.js │ ├── heading.js │ ├── html.js │ ├── image.js │ ├── inlinecode.js │ ├── link.js │ ├── linkReference.js │ ├── list.js │ ├── listItem.js │ ├── paragraph.js │ ├── raw.js │ ├── root.js │ ├── strong.js │ ├── table.js │ ├── tableCell.js │ ├── tableRow.js │ ├── text.js │ └── thematic-break.js ├── rebber-plugins/ │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── rebber.test.js.snap │ │ ├── fixtures/ │ │ │ ├── abbr.fixture.md │ │ │ ├── blockquote.fixture.md │ │ │ ├── blocks.fixture.md │ │ │ ├── code.fixture.md │ │ │ ├── emoticon.fixture.md │ │ │ ├── figure-code.fixture.md │ │ │ ├── figure.fixture.md │ │ │ ├── footnote.fixture.md │ │ │ ├── gridTable.fixture.md │ │ │ ├── heading.fixture.md │ │ │ ├── inline-code.fixture.md │ │ │ ├── link-prepend.fixture.md │ │ │ ├── link.fixture.md │ │ │ ├── list.fixture.md │ │ │ ├── mix-1.fixture.md │ │ │ ├── mix-2.fixture.md │ │ │ ├── mix-3.fixture.md │ │ │ ├── mix-4.fixture.md │ │ │ ├── mix-5.fixture.md │ │ │ ├── mix-6.fixture.md │ │ │ ├── mix-7.fixture.md │ │ │ ├── mix-math-escape.fixture.md │ │ │ ├── paragraph.fixture.md │ │ │ └── table.fixture.md │ │ └── rebber.test.js │ ├── dist/ │ │ ├── preprocessors/ │ │ │ ├── codeVisitor.js │ │ │ ├── footnoteProtect.js │ │ │ ├── iframe.js │ │ │ ├── mathEscape.js │ │ │ ├── prepareQuizz.js │ │ │ └── spoilerFlatten.js │ │ └── type/ │ │ ├── abbr.js │ │ ├── align.js │ │ ├── appendix.js │ │ ├── comments.js │ │ ├── conclusion.js │ │ ├── customBlocks.js │ │ ├── emoticon.js │ │ ├── figure.js │ │ ├── footnote.js │ │ ├── footnoteDefinition.js │ │ ├── footnoteReference.js │ │ ├── gridTable.js │ │ ├── introduction.js │ │ ├── kbd.js │ │ ├── math.js │ │ ├── ping.js │ │ ├── sub.js │ │ ├── sup.js │ │ └── tableHeader.js │ ├── package.json │ └── src/ │ ├── preprocessors/ │ │ ├── codeVisitor.js │ │ ├── footnoteProtect.js │ │ ├── iframe.js │ │ ├── katexConstants.json │ │ ├── mathEscape.js │ │ ├── prepareQuizz.js │ │ └── spoilerFlatten.js │ └── type/ │ ├── abbr.js │ ├── align.js │ ├── appendix.js │ ├── comments.js │ ├── conclusion.js │ ├── customBlocks.js │ ├── emoticon.js │ ├── figure.js │ ├── footnote.js │ ├── footnoteDefinition.js │ ├── footnoteReference.js │ ├── gridTable.js │ ├── introduction.js │ ├── kbd.js │ ├── math.js │ ├── ping.js │ ├── sub.js │ ├── sup.js │ └── tableHeader.js ├── rehype-footnotes-title/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── rehype-html-blocks/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── rehype-postfix-footnote-anchors/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ ├── fixtures/ │ │ │ ├── footnote-split.fixture.md │ │ │ ├── footnotes.fixture.md │ │ │ ├── regression-1.fixture.md │ │ │ └── regression-2.fixture.md │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-abbr/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-align/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-captions/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-comments/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-custom-blocks/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-disable-tokenizers/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-emoticons/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-escape-escaped/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-fix-guillemets/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-grid-tables/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ ├── grid-tables.double.md │ │ ├── grid-tables.md │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-heading-shift/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-heading-trailing-spaces/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-iframes/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-images-download/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __mock__/ │ │ ├── files/ │ │ │ ├── empty │ │ │ ├── world │ │ │ └── wrong-mime.txt │ │ └── server.js │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-kbd/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-numbered-footnotes/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ ├── fixtures/ │ │ │ ├── footnote-split.fixture.md │ │ │ ├── footnotes.fixture.md │ │ │ ├── regression-1.fixture.md │ │ │ └── regression-2.fixture.md │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-ping/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── remark-sub-super/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── index.js.snap │ │ └── index.js │ ├── dist/ │ │ └── index.js │ ├── package.json │ └── src/ │ └── index.js ├── typographic-colon/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js ├── typographic-em-dash/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js ├── typographic-exclamation-mark/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js ├── typographic-guillemets/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js ├── typographic-percent/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js ├── typographic-permille/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js ├── typographic-question-mark/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js ├── typographic-semicolon/ │ ├── .npmignore │ ├── LICENSE-MIT │ ├── README.md │ ├── __tests__/ │ │ └── index.js │ ├── dist/ │ │ ├── db.js │ │ └── index.js │ ├── package.json │ └── src/ │ ├── db.js │ └── index.js └── zmarkdown/ ├── .gitignore ├── README.md ├── __tests__/ │ ├── __snapshots__/ │ │ ├── api.test.js.snap │ │ ├── html-suite.test.js.snap │ │ ├── latex-suite.test.js.snap │ │ ├── legacy-suite.test.js.snap │ │ ├── mdast-suite.test.js.snap │ │ ├── misc.test.js.snap │ │ ├── regressions.test.js.snap │ │ └── server.test.js.snap │ ├── api.test.js │ ├── html-suite.test.js │ ├── latex-suite.test.js │ ├── legacy-suite.test.js │ ├── mdast-suite.test.js │ ├── misc.test.js │ ├── regressions.test.js │ └── server.test.js ├── client/ │ ├── zhlite.js │ ├── zhtml.js │ ├── zlatex.js │ └── zmdast.js ├── common.js ├── config/ │ ├── html/ │ │ ├── iframe-wrappers.js │ │ └── index.js │ ├── latex/ │ │ └── index.js │ ├── mdast/ │ │ ├── custom-blocks.js │ │ ├── emoticons.js │ │ ├── iframes.js │ │ ├── images-download.js │ │ ├── index.js │ │ └── textr.js │ └── sanitize/ │ ├── index.js │ └── katex.json ├── munin/ │ └── zmd.sh ├── package.json ├── plugins/ │ ├── remark-code-meta.js │ ├── remark-image-to-figure.js │ └── remark-textr.js ├── postprocessors/ │ ├── html-footnotes-reorder.js │ ├── html-iframe-wrappers.js │ ├── html-lazy-load-images.js │ ├── html-wrap-code.js │ ├── md-detect-quizzes.js │ ├── md-get-stats.js │ ├── md-limit-depth.js │ ├── md-list-languages.js │ └── md-wrap-intro-ccl.js ├── public/ │ ├── README.md │ ├── css/ │ │ ├── main.css │ │ └── zmd.css │ ├── index.html │ ├── main.css │ └── script.js ├── renderers/ │ ├── html.js │ ├── latex.js │ ├── mdast.js │ └── renderer-forge.js ├── server/ │ ├── controllers/ │ │ └── munin.js │ ├── factories/ │ │ ├── config-factory.js │ │ ├── controller-factory.js │ │ ├── io-factory.js │ │ └── processor-factory.js │ ├── index.js │ ├── routes/ │ │ ├── endpoints.js │ │ └── munin.js │ ├── templates/ │ │ └── latex-document.js │ └── utils/ │ └── manifest.js ├── utils/ │ ├── code-handler.js │ ├── create-wrappers.js │ ├── latex-code.js │ └── renderer-tests.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*.{js,json}] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: [master, next] pull_request: branches: [master, next] env: NODE_VERSION: "22" jobs: lint: name: Check linting problems runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Install local dependencies run: npm ci - name: Run linter run: npm run lint unit-testing: name: Run unit testing runs-on: ubuntu-latest strategy: matrix: node-version: [20.x, 22.x, 24.x] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install global dependencies run: npm install -g pm2 - name: Install local dependencies run: npm ci - name: Run tests run: npm test - name: Update coverage report uses: coverallsapp/github-action@master if: matrix.node-version == env.NODE_VERSION with: github-token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/prepublish.yml ================================================ name: Prepare publication on: push: branches: [master] env: NODE_VERSION: "22" jobs: deploy-demo: if: github.repository_owner == 'zestedesavoir' name: Deploy the live demo runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Install local dependencies run: npm ci - name: Build the demo run: npm run build-demo - name: GitHub Push uses: JamesIves/github-pages-deploy-action@v4 with: branch: gh-pages folder: ./packages/zmarkdown/public github_token: ${{ secrets.GITHUB_TOKEN }} single-commit: true ================================================ FILE: .gitignore ================================================ .DS_Store /node_modules /packages/*/node_modules /packages/*/coverage /packages/*/.nyc_output /.nyc_output/* /coverage/* npm-debug.log lerna-debug.log .idea .tern-port .vscode .nx ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ZMarkdown [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This repository contains all the plugins for ZMarkdown, the Markdown engine powering [Zeste de Savoir][zds]. It is a collection of packages extending the [**remark** processor][processor] and its [**MDAST**][mdast] syntax tree, [**rehype**][rehype] (for HTML processing) and [**textr**][textr] (text transformation framework). It also provides [**MDAST**][mdast] to LaTeX compilation via [**rebber**][rebber] (and its [plugins][rebber-plugins]). Currently, all the plugins provided only work for remark versions **lesser than** 13.0.0 (i.e. previous to [**micromark**][micromark]). While we intend to switch to the new system, no due date has been planned, and it requires a significant amount of work, so please be patient, or, even better, help us making the switch! ## Install ### Prerequisites * node >= 18 * npm >= 9 ### Installation 1. `git clone git@github.com:zestedesavoir/zmarkdown.git` 1. `npm install` This project uses [Jest][jest] for testing. It is recommended to use the locally installed version using `npx`, and run Jest in watch mode when developing `npx jest --watch --notify` (`--notify` sends desktop notifications when tests run). ### Useful commands * `npm run test` : tests all packages. * `npm run clean` : clears local dependencies, reinstalls the project and runs all tests. * `npm run lint` : runs [eslint][eslint] to check the syntax of the full codebase. * `npm run build` : builds packages using [babel][babel]. * `npm run build -- --scope=` : same as above, but builds only ``. ## Packages * [**mdast-util-split-by-heading**][mdast-util-split-by-heading] A MDAST tool to split a markdown tree into list of subtrees representing the chapters. It relies on heading depth. * [**rebber**][rebber] transformation of MDAST into `latex` code. This code must be included inside a custom latex to be compiled. Have a look at `https://github.com/zestedesavoir/latex-template/blob/master/zmdocument.cls` to get a working example. * [**remark-abbr**][remark-abbr] This plugin parses `*[ABBR]: abbr definition` and then replace all ABBR instance in text with a new MDAST node so that `rehype` can parse it into `abbr` html tag. * [**rehype-footnotes-title**][rehype-footnotes-title] This plugin adds a `title` attribute to the footnote links, mainly for accessibility purpose. * [**rehype-html-blocks**][rehype-html-blocks] This plugin wraps (multi-line) raw HTML in `p`. * [**remark-align**][remark-align] This plugin parses custom Markdown syntax to center- or right-align elements. * [**remark-captions**][remark-captions] Allow to add caption to such element as image, table or blockquote. * [**remark-comments**][remark-comments] This plugin parses custom Markdown syntax for Markdown source comments. * [**remark-custom-blocks**][remark-custom-blocks] This plugin parses custom Markdown syntax to create new custom blocks. * [**remark-emoticons**][remark-emoticons] This plugins replaces ASCII emoticons with associated image. Compatible with [rehype][rehype] * [**remark-escape-escaped**][remark-escape-escaped] This plugin escapes HTML entities from Markdown input. * [**remark-grid-tables**][remark-grid-tables] This plugin parses custom Markdown syntax to describe tables. * [**remark-heading-shift**][remark-heading-shift] Allows to shift heading to custimize the way you will integrate the generated tree inside your application. * [**remark-heading-trailing-spaces**][remark-heading-trailing-spaces] This plugin removes trailing spaces from Markdown headers. * [**remark-iframes**][remark-iframes] Allows to add `iframe` inclusion through `!(url)` code. * [**remark-kbd**][remark-kbd] This plugin parses custom Markdown syntax to handle keyboard keys. * [**remark-numbered-footnotes**][remark-numbered-footnotes] This plugin changes how [mdast][mdast] footnotes are displayed by using sequential numbers as footnote references instead of user-specified strings. * [**remark-sub-super**][remark-sub-super] This plugin parses custom Markdown syntax to handle subscript and superscript. * [**typographic-colon**][typographic-colon] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. * [**typographic-permille**][typographic-permille] Micro module to replace `%o` with `‰` and optionally replace the preceding space. * [**zmarkdown**][zmarkdown] Fully integrated package to be used in [zeste de savoir website](https://zestedesavoir.com) ## License [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://travis-ci.com/zestedesavoir/zmarkdown.svg?branch=master [build-status]: https://travis-ci.com/zestedesavoir/zmarkdown [coverage-badge]: https://coveralls.io/repos/github/zestedesavoir/zmarkdown/badge.svg?branch=master [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown?branch=master [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/LICENSE-MIT [processor]: https://github.com/remarkjs/remark/blob/master/packages/remark [mdast]: https://github.com/wooorm/mdast [micromark]: https://github.com/micromark/micromark [pyzmd]: https://github.com/zestedesavoir/Python-ZMarkdown [zds]: https://zestedesavoir.com [rehype]: https://github.com/rehypejs/rehype [textr]: https://github.com/A/textr [jest]: https://facebook.github.io/jest/ [eslint]: https://github.com/eslint/eslint [babel]: https://github.com/babel/babel [mdast-util-split-by-heading]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/mdast-util-split-by-heading#mdast-util-split-by-heading-- [rebber]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rebber#rebber-- [rebber-plugins]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rebber-plugins#rebber-plugins-- [remark-abbr]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-abbr#remark-abbr-- [rehype-footnotes-title]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rehype-footnotes-title#rehype-footnotes-title-- [rehype-html-blocks]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rehype-html-blocks#rehype-html-blocks-- [remark-align]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-align#remark-align-- [remark-captions]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-captions#remark-captions-- [remark-comments]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-comments#remark-comments-- [remark-custom-blocks]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-custom-blocks#remark-custom-blocks-- [remark-emoticons]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-emoticons#remark-emoticons-- [remark-escape-escaped]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-escape-escaped#remark-escape-escaped-- [remark-grid-tables]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-grid-tables#remark-grid-tables-- [remark-heading-shift]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-heading-shift#remark-heading-shift-- [remark-heading-trailing-spaces]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-heading-trailing-spaces#remark-heading-trailing-spaces-- [remark-iframes]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-iframes#remark-iframes-- [remark-kbd]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-kbd#remark-kbd-- [remark-numbered-footnotes]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-numbered-footnotes#remark-numbered-footnotes-- [remark-sub-super]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-sub-super#remark-sub-super-- [typographic-colon]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-colon#typographic-colon-- [typographic-permille]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-permille#typographic-permille-- [zmarkdown]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/zmarkdown#zmarkdown-- ================================================ FILE: babel.config.js ================================================ module.exports = { presets: [ [ '@babel/preset-env', { targets: { browsers: '> 1%, not dead', node: '16.0' } } ] ], ignore: ['node_modules'] } ================================================ FILE: eslint.config.mjs ================================================ import globals from 'globals' import path from 'path' import { fileURLToPath } from 'url' import { FlatCompat } from '@eslint/eslintrc' import pluginJs from '@eslint/js' // mimic CommonJS variables -- not needed if using CommonJS const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const compat = new FlatCompat({ baseDirectory: __dirname, recommendedConfig: pluginJs.configs.recommended }) export default [ Object.assign({}, ...compat.extends('standard'), { files: ['packages/**/*.js'], ignores: [ 'packages/**/__tests__/*.js', 'packages/**/dist/**/*.js', 'packages/zmarkdown/webpack.config.js', // Should not be ignored, but requires ESM 'packages/zmarkdown/client/*.js', 'packages/zmarkdown/public/*.js' ], languageOptions: { sourceType: 'commonjs', globals: { ...globals.browser, ...globals.node } } }) ] ================================================ FILE: lerna.json ================================================ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "version": "independent" } ================================================ FILE: package.json ================================================ { "jest": { "testPathIgnorePatterns": [ "/node_modules/" ], "collectCoverage": true, "collectCoverageFrom": [ "packages/**/src/*.js", "packages/zmarkdown/plugins/*.js", "packages/zmarkdown/postprocessors/*.js", "packages/zmarkdown/renderers/*.js", "packages/zmarkdown/server/utils/*.js", "packages/zmarkdown/utils/*.js", "packages/zmarkdown/*.js", "!**/*.config.js" ] }, "devDependencies": { "@babel/cli": "^7.24.1", "@babel/core": "^7.24.4", "@babel/preset-env": "^7.24.4", "@eslint/eslintrc": "^3.0.2", "@eslint/js": "^9.0.0", "axios": "^0.21.1", "babel-loader": "^9.1.3", "clone": "^2.1.2", "core-js": "^3.6.5", "coveralls": "^3.1.0", "cross-env": "^7.0.2", "dedent": "^0.7.0", "del-cli": "^3.0.1", "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-n": "^16.6.2", "eslint-plugin-promise": "^6.1.1", "express": "^4.17.1", "globals": "^15.0.0", "html-differ": "^1.4.0", "jest": "^26.4.2", "jest-environment-node-debug": "^2.0.0", "lerna": "^8.1.2", "mdast-util-to-hast": "^9.1.2", "rehype-stringify": "^8.0.0", "remark": "^12.0.1", "remark-footnotes": "^2.0.0", "remark-math": "^3.0.1", "remark-parse": "^8.0.3", "remark-rehype": "^7.0.0", "remark-stringify": "^8.1.1", "sync-request": "^6.1.0", "textr": "^0.3.0", "unified": "^9.2.0", "unist-util-visit": "^2.0.3", "webpack": "^5.88.2", "webpack-cli": "^5.1.4" }, "scripts": { "pretest": "lerna run pretest --scope zmarkdown", "test": "cross-env DEST=/tmp jest", "lint": "eslint .", "posttest": "lerna run posttest --scope zmarkdown", "build": "lerna run build", "d": "node --inspect --debug-brk ./node_modules/.bin/jest --runInBand -i", "clean": "lerna clean --yes && del-cli node_modules && npm install && lerna run prepare && npm run test", "build-demo": "lerna run release --scope zmarkdown && del-cli ./packages/zmarkdown/public/js && cp -r ./packages/zmarkdown/client/dist ./packages/zmarkdown/public/js" }, "engines": { "node": ">=18", "npm": ">=9" }, "private": true, "name": "zmarkdown-meta", "dependencies": { "deepmerge": "^4.2.2", "hast-util-sanitize": "^3.0.0", "mdast-util-split-by-heading": "file:packages/mdast-util-split-by-heading", "rebber": "file:packages/rebber", "rebber-plugins": "file:packages/rebber-plugins", "rehype-footnotes-title": "file:packages/rehype-footnotes-title", "rehype-html-blocks": "file:packages/rehype-html-blocks", "rehype-postfix-footnote-anchors": "file:packages/rehype-postfix-footnote-anchors", "rehype-sanitize": "^4.0.0", "remark-abbr": "file:packages/remark-abbr", "remark-align": "file:packages/remark-align", "remark-captions": "file:packages/remark-captions", "remark-comments": "file:packages/remark-comments", "remark-custom-blocks": "file:packages/remark-custom-blocks", "remark-disable-tokenizers": "file:packages/remark-disable-tokenizers", "remark-emoticons": "file:packages/remark-emoticons", "remark-escape-escaped": "file:packages/remark-escape-escaped", "remark-fix-guillemets": "file:packages/remark-fix-guillemets", "remark-grid-tables": "file:packages/remark-grid-tables", "remark-heading-shift": "file:packages/remark-heading-shift", "remark-heading-trailing-spaces": "file:packages/remark-heading-trailing-spaces", "remark-iframes": "file:packages/remark-iframes", "remark-images-download": "file:packages/remark-images-download", "remark-kbd": "file:packages/remark-kbd", "remark-numbered-footnotes": "file:packages/remark-numbered-footnotes", "remark-ping": "file:packages/remark-ping", "remark-sub-super": "file:packages/remark-sub-super", "typographic-colon": "file:packages/typographic-colon", "typographic-em-dash": "file:packages/typographic-em-dash", "typographic-exclamation-mark": "file:packages/typographic-exclamation-mark", "typographic-guillemets": "file:packages/typographic-guillemets", "typographic-percent": "file:packages/typographic-percent", "typographic-permille": "file:packages/typographic-permille", "typographic-question-mark": "file:packages/typographic-question-mark", "typographic-semicolon": "file:packages/typographic-semicolon", "zmarkdown": "file:packages/zmarkdown" }, "workspaces": [ "packages/*" ] } ================================================ FILE: packages/mdast-util-split-by-heading/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/mdast-util-split-by-heading/README.md ================================================ # mdast-util-split-by-heading [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] **mdast-util-split-by-heading** splits a markdown AST into several markdown ASTs based on their headings. It is useful when you want to split a document with many headings into several documents, for instance one by chapter. ## Installation [npm][]: ```bash npm install mdast-util-split-by-heading ``` ## Usage ```javascript const unified = require('unified') const parse = require('remark-parse') const split = require('mdast-util-split-by-heading') var tree = unified() .use(parse) .parse('# part\n\n## chapter \n\n Hello world \n\n # part *2*') console.log(split(tree)) ``` ## API ### `split(node, options = { splitDepth: 1 })` Splits a MDAST tree into separate trees by [heading depth](https://github.com/syntax-tree/mdast#heading). #### `options.splitDepth = 1` An integer greater or equal to 1 determining the heading depth you want to match when splitting. ## Examples: ```js import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import split from 'mdast-util-split-by-heading' const doSplit = (text, options) => { const { splitDepth = 1 } = options return split( unified().use(reParse).parse(text), { splitDepth: splitDepth } ) } const text = dedent ` a global introduction # hello a paragraph > a quote to *ensure this is parsed* ## a sub title other paragraph # conclusion title paragraph ` doSplit(text) /* { "introduction": { "type": "root", "children": [ { "type": "paragraph", "children": [ { "type": "text", "value": "a global introduction", "position": { "start": { "line": 1, "column": 1, "offset": 0 }, "end": { "line": 1, "column": 22, "offset": 21 }, "indent": [] } } ], "position": { "start": { "line": 1, "column": 1, "offset": 0 }, "end": { "line": 1, "column": 22, "offset": 21 }, "indent": [] } } ] }, "trees": [ { "title": { "type": "root", "children": { "type": "heading", "depth": 1, "children": [ { "type": "text", "value": "hello", "position": { "start": { "line": 3, "column": 3, "offset": 25 }, "end": { "line": 3, "column": 8, "offset": 30 }, "indent": [] } } ], "position": { "start": { "line": 3, "column": 1, "offset": 23 }, "end": { "line": 3, "column": 8, "offset": 30 }, "indent": [] } } }, "children": { "type": "root", "children": [ { "type": "paragraph", "children": [ { "type": "text", "value": "a paragraph", "position": { "start": { "line": 5, "column": 1, "offset": 32 }, "end": { "line": 5, "column": 12, "offset": 43 }, "indent": [] } } ], "position": { "start": { "line": 5, "column": 1, "offset": 32 }, "end": { "line": 5, "column": 12, "offset": 43 }, "indent": [] } }, { "type": "blockquote", "children": [ { "type": "paragraph", "children": [ { "type": "text", "value": "a quote to ", "position": { "start": { "line": 7, "column": 3, "offset": 47 }, "end": { "line": 7, "column": 14, "offset": 58 }, "indent": [] } }, { "type": "emphasis", "children": [ { "type": "text", "value": "ensure this is parsed", "position": { "start": { "line": 7, "column": 15, "offset": 59 }, "end": { "line": 7, "column": 36, "offset": 80 }, "indent": [] } } ], "position": { "start": { "line": 7, "column": 14, "offset": 58 }, "end": { "line": 7, "column": 37, "offset": 81 }, "indent": [] } } ], "position": { "start": { "line": 7, "column": 3, "offset": 47 }, "end": { "line": 7, "column": 37, "offset": 81 }, "indent": [] } } ], "position": { "start": { "line": 7, "column": 1, "offset": 45 }, "end": { "line": 7, "column": 37, "offset": 81 }, "indent": [] } }, { "type": "heading", "depth": 2, "children": [ { "type": "text", "value": "a sub title", "position": { "start": { "line": 9, "column": 4, "offset": 86 }, "end": { "line": 9, "column": 15, "offset": 97 }, "indent": [] } } ], "position": { "start": { "line": 9, "column": 1, "offset": 83 }, "end": { "line": 9, "column": 15, "offset": 97 }, "indent": [] } }, { "type": "paragraph", "children": [ { "type": "text", "value": "other paragraph", "position": { "start": { "line": 11, "column": 1, "offset": 99 }, "end": { "line": 11, "column": 16, "offset": 114 }, "indent": [] } } ], "position": { "start": { "line": 11, "column": 1, "offset": 99 }, "end": { "line": 11, "column": 16, "offset": 114 }, "indent": [] } } ] } }, { "title": { "type": "root", "children": { "type": "heading", "depth": 1, "children": [ { "type": "text", "value": "conclusion title", "position": { "start": { "line": 13, "column": 3, "offset": 118 }, "end": { "line": 13, "column": 19, "offset": 134 }, "indent": [] } } ], "position": { "start": { "line": 13, "column": 1, "offset": 116 }, "end": { "line": 13, "column": 19, "offset": 134 }, "indent": [] } } }, "children": { "type": "root", "children": [ { "type": "paragraph", "children": [ { "type": "text", "value": "paragraph", "position": { "start": { "line": 15, "column": 1, "offset": 136 }, "end": { "line": 15, "column": 10, "offset": 145 }, "indent": [] } } ], "position": { "start": { "line": 15, "column": 1, "offset": 136 }, "end": { "line": 15, "column": 10, "offset": 145 }, "indent": [] } } ] } } ] } */ ``` ## License [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/rebber/LICENSE-MIT [rebber-plugins]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/rebber-plugins [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/rebber [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark ================================================ FILE: packages/mdast-util-split-by-heading/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import split from '../src' const doSplit = (text, {splitDepth = 1, introductionAsProperty = true, conclusionAsProperty = false}) => { return split(unified().use(reParse).parse(text), { splitDepth: splitDepth, conclusionAsProperty: conclusionAsProperty}) } const text = dedent ` a global introduction # hello a paragraph > a quote to *ensure this is parsed* ## a sub title other paragraph # conclusion title paragraph ` test('default parameter with canonical text', () => { const result = doSplit(text, {}) expect(result.introduction).toMatchObject({ type: 'root', children: [ { type: 'paragraph', children: [ { type: 'text', value: 'a global introduction', }, ], }, ], }) expect(result.trees.length).toBe(2) expect(result.conclusion).toBeFalsy() expect(result.trees[0].children).toMatchObject({ type: 'root', children: [ { type: 'paragraph', children: [ { type: 'text', value: 'a paragraph', }, ], }, { type: 'blockquote', children: [ { type: 'paragraph', children: [ { type: 'text', value: 'a quote to ', }, { type: 'emphasis', children: [ { type: 'text', value: 'ensure this is parsed', }, ], }, ], }, ], }, { type: 'heading', depth: 2, children: [ { type: 'text', value: 'a sub title', }, ], }, { type: 'paragraph', children: [ { type: 'text', value: 'other paragraph', }, ], }, ], }) expect(result.trees[1].children).toMatchObject({ type: 'root', children: [ { type: 'paragraph', children: [ { type: 'text', value: 'paragraph', }, ], }, ], }) }) test('no heading', () => { const headingStripped = text.replace(/#/g, '') const result = doSplit(headingStripped, {}) expect(result.trees).toHaveLength(0) expect(result.introduction.type).toBe('root') }) test('split level 2 titles', () => { const result = doSplit(text, {splitDepth: 2}) expect(result.trees.length).toBe(1) expect(result.trees[0].children).toMatchObject({ type: 'root', children: [ { type: 'paragraph', children: [ { type: 'text', value: 'other paragraph', }, ], }, { type: 'heading', depth: 1, children: [ { type: 'text', value: 'conclusion title', }, ], }, { type: 'paragraph', children: [ { type: 'text', value: 'paragraph', }, ], }, ], }) }) ================================================ FILE: packages/mdast-util-split-by-heading/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); module.exports = splitAtDepth; function splitAtDepth(tree, { splitDepth = 1 }) { const splitter = new Splitter(splitDepth); visit(tree, null, (node, index, parent) => splitter.visit(node, index, parent)); return { introduction: splitter.introduction, trees: splitter.subTrees }; } function newRootTree(children = []) { return { type: 'root', children }; } class Splitter { constructor(depth = 1) { this.lastIndex = -1; this.subTrees = []; this.depth = depth; this.introduction = newRootTree(); } visit(node, index, parent) { if (!parent) { // we are at the root return; } if (node.type === 'heading' && node.depth === this.depth) { this.lastIndex = index; const subtree = { title: newRootTree(node), children: newRootTree() }; this.subTrees.push(subtree); } else if (parent.type === 'root' && this.lastIndex === -1) { this.introduction.children.push(node); } else if (parent.type === 'root') { this.subTrees[this.subTrees.length - 1].children.children.push(node); } } } ================================================ FILE: packages/mdast-util-split-by-heading/package.json ================================================ { "name": "mdast-util-split-by-heading", "version": "1.1.2", "description": "Split MDAST into subtrees relying on the header hierarchy.", "repository": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/mdast-util-split-by-heading", "author": "François (artragis) Dambrine ", "contributors": [], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "mdast" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/mdast-util-split-by-heading/src/index.js ================================================ const visit = require('unist-util-visit') module.exports = splitAtDepth function splitAtDepth (tree, { splitDepth = 1 }) { const splitter = new Splitter(splitDepth) visit(tree, null, (node, index, parent) => splitter.visit(node, index, parent)) return { introduction: splitter.introduction, trees: splitter.subTrees } } function newRootTree (children = []) { return { type: 'root', children } } class Splitter { constructor (depth = 1) { this.lastIndex = -1 this.subTrees = [] this.depth = depth this.introduction = newRootTree() } visit (node, index, parent) { if (!parent) { // we are at the root return } if (node.type === 'heading' && node.depth === this.depth) { this.lastIndex = index const subtree = { title: newRootTree(node), children: newRootTree() } this.subTrees.push(subtree) } else if (parent.type === 'root' && this.lastIndex === -1) { this.introduction.children.push(node) } else if (parent.type === 'root') { this.subTrees[this.subTrees.length - 1].children.children.push(node) } } } ================================================ FILE: packages/rebber/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/rebber/README.md ================================================ # rebber [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] **rebber** is a LaTeX stringifier for [remark][] ## remark-rebber version compatibility Starting from version 8.0.0, `remark` dropped support for footnotes; hence, `rebber` also dropped it's support starting from version 6.0.0. Therefore, we have the following compatibility table for remark-rebber versions: | remark | rebber | | ------- | ------- | | < 8.0.0 | < 6.0.0 | | > 8.0.0 | any | ## Installation [npm][]: ```bash npm install rebber ``` ## Usage ```javascript const unified = require('unified') const remarkParser = require('remark-parse') const rebber = require('rebber') const {contents} = unified() .use(remarkParser) .use(rebber) .processSync('### foo') console.log(contents); ``` Yields: ```latex \section{foo} ``` ## API ### `toLaTeX(node[, options])` Stringify the given [MDAST node][mdast]. #### `options.overrides` Overrides are named that way because they can override any MDAST node type to latex stringifier. Their other use is to use custom latex stringifier for custom MDAST node type. Examples: ```js const {contents} = unified() .use(remarkParser) .use(remarkFoobarElementsParser) // creates MDAST nodes of type 'foobar' .use(rebber, { overrides: { // override rebber's method to turn MDAST link nodes into latex link: require('./your-own-link-latexifier') // tell rebber what to use to turn MDAST foobar nodes into latex foobar: require('./your-foobar-latexifier') } }) ``` #### `options.` [MDAST nodes][mdast] are stringified to LaTeX using sensible default LaTeX commands. However, you can customize most of the LaTeX command corresponding to MDAST nodes. Here are documented the function signatures of these customizable commands. Note that the keys of the `options` object are named after the corresponding MDAST node type. For example, by default, `![](/foo.png)` will get compiled to `\includegraphics{/foo.png}`. Setting ```js options.image = (node) => `[inserted image located at "${node.url}"]` ``` will stringify our example Markdown to `[inserted image located at "/foo.png"]` instead of `\includegraphics{/foo.png}`. ###### `options.blockquote` (text) => ``, ###### `options.break` () => ``, ###### `options.code` (textCode, lang) => ``, ###### `options.definition` (options, identifier, url, title) => ``, ###### `options.footnote` (identifier, text, protect) => ``, ###### `options.footnoteDefinition` (identifier, text) => ``, ###### `options.footnoteReference` (identifier) => ``, ###### `options.headings` [ (text) => ``, // level 1 heading (text) => ``, // level 2 heading (text) => ``, // level 3 heading (text) => ``, // level 4 heading (text) => ``, // level 5 heading (text) => ``, // level 6 heading (text) => ``, // level 7 heading ], ###### `options.image` (node) => ``, ###### `options.link` (displayText, url, title) => ``, ###### `options.linkReference` (reference, content) => ``, ###### `options.list` (content, isOrdered) => ``, ###### `options.listItem` (content) => ``, ###### `options.text` (text) => ``, ###### `options.thematicBreak` () => ``, ###### `options.table` (ctx, node) => ``, Table stringification can be configured with some advanced options: ###### `options.tableEnvName` `longtblr` Name of the environment to be used for tables. Allows defining custom environments in LaTeX with `\NewTblrEnviron`. To ensure a flexible rendering, the `longtblr` environment is used by default. ###### `options.headerCounter: (node) => 1` (tableRows) => 1 Function that counts the number of header rows (rows that should be emphasized). ###### `options.headerProperties` `font=\bfseries` LaTeX properties added to header rows, follows the syntax of the underlying LaTeX package. ###### `options.headerParse` (tableRows) => `` Function that computes the "latex header" part of the table environment, this generates strings such as `|c|c|r|`. It gets an array of all the `tableRow` [mdast] nodes for the table as argument. Default function extracts the number of columns for each row and uses the `X[-1]` handler ("find the best available width"). The result for a 3 column-table is `|X[-1]|X[-1]|X[-1]|`. ## Related * [`rebber-plugins`][rebber-plugins] - A collection of rebber plugins able to stringify custom Remark node types. ## License [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/rebber/LICENSE-MIT [rebber-plugins]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/rebber-plugins [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/rebber [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark ================================================ FILE: packages/rebber/__tests__/__snapshots__/mdast.tests.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`rebber: remark specs amps-and-angles-encoding: amps-and-angles-encoding 1`] = ` "AT\\\\&T has an ampersand in their name. AT\\\\&T is another way to write it. This \\\\& that. 4 < 5. 6 > 5. Here's a \\\\hyperref[1]{link} with an ampersand in the URL. Here's a link with an amersand in the link text: \\\\hyperref[2]{AT\\\\&T}. Here's an inline \\\\externalLink{link}{/script?foo=1\\\\&bar=2}. Here's an inline \\\\externalLink{link}{/script?foo=1\\\\&bar=2}. \\\\footnote{\\\\label{1}\\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2}} \\\\footnote{\\\\label{2}\\\\externalLink{http://att.com/}{http://att.com/}}" `; exports[`rebber: remark specs auto-link: auto-link 1`] = ` "Link: \\\\externalLink{http://example.com/}{http://example.com/}. Link to an email: \\\\externalLink{somename@example.com}{mailto:somename@example.com}. Link to an email: \\\\externalLink{somename@example.com}{mailto:somename@example.com}. With an ampersand: \\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2} \\\\begin{itemize} \\\\item\\\\relax In a list? \\\\item\\\\relax \\\\externalLink{http://example.com/}{http://example.com/} \\\\item\\\\relax It should. \\\\end{itemize} \\\\begin{Quotation} Blockquoted: \\\\externalLink{http://example.com/}{http://example.com/} \\\\end{Quotation} Auto-links should not occur here: \\\\texttt{} \\\\begin{CodeBlock}{text} or here: \\\\end{CodeBlock}" `; exports[`rebber: remark specs auto-link-invalid: auto-link-invalid 1`] = ` " Hash: \\\\# Period: . Bang: ! Plus: + Minus: - \\\\textbf{GFM:} Pipe: | Tilde: \\\\textasciitilde{} \\\\textbf{Commonmark:} Quote: \\\\textbackslash{}\\" Dollar: \\\\textbackslash{}\\\\$ Percentage: \\\\textbackslash{}\\\\% Ampersand: \\\\textbackslash{}\\\\& Single quote: \\\\textbackslash{}' Comma: \\\\textbackslash{}, Forward slash: \\\\textbackslash{}/ Colon: \\\\textbackslash{}: Semicolon: \\\\textbackslash{}; Less-than: \\\\textbackslash{}< Equals: \\\\textbackslash{}= Question mark: \\\\textbackslash{}? At-sign: \\\\textbackslash{}@ Caret: \\\\textbackslash{}\\\\textasciicircum{} New line: \\\\textbackslash{} only works in paragraphs. These should not, because they occur within a code block: \\\\begin{CodeBlock}{text} Backslash: \\\\\\\\ Backtick: \\\\\` Asterisk: \\\\* Underscore: \\\\_ Left brace: \\\\{ Right brace: \\\\} Left bracket: \\\\[ Right bracket: \\\\] Left paren: \\\\( Right paren: \\\\) Greater-than: \\\\> Hash: \\\\# Period: \\\\. Bang: \\\\! Plus: \\\\+ Minus: \\\\- \\\\end{CodeBlock} \\\\textbf{GFM:} \\\\begin{CodeBlock}{text} Pipe: \\\\| Tilde: \\\\~ \\\\end{CodeBlock} \\\\textbf{Commonmark:} \\\\begin{CodeBlock}{text} Quote: \\\\\\" Dollar: \\\\$ Percentage: \\\\% Ampersand: \\\\& Single quote: \\\\' Comma: \\\\, Forward slash: \\\\/ Colon: \\\\: Semicolon: \\\\; Less-than: \\\\< Equals: \\\\= Question mark: \\\\? At-sign: \\\\@ Caret: \\\\^ New line: \\\\ only works in paragraphs. \\\\end{CodeBlock} Nor should these, which occur in code spans: Backslash: \\\\texttt{\\\\textbackslash{}\\\\textbackslash{}} Backtick: \\\\texttt{\\\\textbackslash{}\`} Asterisk: \\\\texttt{\\\\textbackslash{}*} Underscore: \\\\texttt{\\\\textbackslash{}\\\\_} Left brace: \\\\texttt{\\\\textbackslash{}\\\\{} Right brace: \\\\texttt{\\\\textbackslash{}\\\\}} Left bracket: \\\\texttt{\\\\textbackslash{}[} Right bracket: \\\\texttt{\\\\textbackslash{}]} Left paren: \\\\texttt{\\\\textbackslash{}(} Right paren: \\\\texttt{\\\\textbackslash{})} Greater-than: \\\\texttt{\\\\textbackslash{}>} Hash: \\\\texttt{\\\\textbackslash{}\\\\#} Period: \\\\texttt{\\\\textbackslash{}.} Bang: \\\\texttt{\\\\textbackslash{}!} Plus: \\\\texttt{\\\\textbackslash{}+} Minus: \\\\texttt{\\\\textbackslash{}-} \\\\textbf{GFM:} Pipe: \\\\texttt{\\\\textbackslash{}|} Tilde: \\\\texttt{\\\\textbackslash{}\\\\textasciitilde{}} \\\\textbf{Commonmark:} Quote: \\\\texttt{\\\\textbackslash{}\\"} Dollar: \\\\texttt{\\\\textbackslash{}\\\\$} Percentage: \\\\texttt{\\\\textbackslash{}\\\\%} Ampersand: \\\\texttt{\\\\textbackslash{}\\\\&} Single quote: \\\\texttt{\\\\textbackslash{}'} Comma: \\\\texttt{\\\\textbackslash{},} Forward slash: \\\\texttt{\\\\textbackslash{}/} Colon: \\\\texttt{\\\\textbackslash{}:} Semicolon: \\\\texttt{\\\\textbackslash{};} Less-than: \\\\texttt{\\\\textbackslash{}<} Equals: \\\\texttt{\\\\textbackslash{}=} Question mark: \\\\texttt{\\\\textbackslash{}?} At-sign: \\\\texttt{\\\\textbackslash{}@} Caret: \\\\texttt{\\\\textbackslash{}\\\\textasciicircum{}} New line: \\\\texttt{\\\\textbackslash{} } only works in paragraphs. These should get escaped, even though they're matching pairs for other Markdown constructs: *asterisks* \\\\_underscores\\\\_ \`backticks\` This is a code span with a literal backslash-backtick sequence: \\\\texttt{\\\\textbackslash{}\`} This is a tag with unescaped backticks bar. This is a tag with backslashes bar." `; exports[`rebber: remark specs block-elements: block-elements 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Different lists should receive two newline characters between them. \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax This is another list. \\\\end{itemize} \\\\begin{Quotation} \\\\begin{itemize} \\\\item\\\\relax The same goes for lists in block quotes. \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax This is another list. \\\\end{itemize} \\\\end{Quotation} \\\\begin{itemize} \\\\item\\\\relax And for lists in lists: \\\\begin{enumerate} \\\\item\\\\relax First sublist. \\\\end{enumerate} \\\\end{itemize} \\\\begin{CodeBlock}{text} 1. Second sublist. \\\\end{CodeBlock} And for lists followed by indented code blocks: \\\\begin{itemize} \\\\item\\\\relax This is a paragraph in a list \\\\end{itemize} \\\\begin{CodeBlock}{text} And this is code(); \\\\end{CodeBlock}" `; exports[`rebber: remark specs blockquote-indented: blockquote-indented 1`] = ` "\\\\begin{Quotation} bar baz \\\\end{Quotation}" `; exports[`rebber: remark specs blockquote-lazy-code: blockquote-lazy-code 1`] = ` "\\\\begin{Quotation} \\\\begin{CodeBlock}{text} foo bar \\\\end{CodeBlock} \\\\end{Quotation}" `; exports[`rebber: remark specs blockquote-lazy-fence: blockquote-lazy-fence 1`] = ` "\\\\begin{Quotation} \\\\begin{CodeBlock}{text} aNormalCodeBlockInABlockqoute(); \\\\end{CodeBlock} \\\\end{Quotation} A paragraph. \\\\begin{Quotation} \\\\begin{CodeBlock}{text} thisIsAlsoSomeCodeInABlockquote(); \\\\end{CodeBlock} \\\\end{Quotation} A paragraph. \\\\begin{Quotation} \\\\begin{CodeBlock}{text} aNonTerminatedCodeBlockInABlockquote(); \\\\end{CodeBlock} aNewCodeBlockFollowingTheBlockQuote(); \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} \\\\end{Quotation} A paragraph. \\\\begin{Quotation} Something in a blockquote. \\\\begin{CodeBlock}{text} aNewCodeBlock(); \\\\end{CodeBlock} \\\\end{Quotation}" `; exports[`rebber: remark specs blockquote-lazy-list: blockquote-lazy-list 1`] = ` "\\\\begin{Quotation} This is a blockquote. \\\\begin{itemize} \\\\item\\\\relax And in normal mode this is an internal list, but in commonmark this is a top level list. \\\\end{itemize} \\\\end{Quotation}" `; exports[`rebber: remark specs blockquote-lazy-rule: blockquote-lazy-rule 1`] = ` "\\\\begin{Quotation} This is a blockquote. Followed by a rule. \\\\horizontalLine \\\\end{Quotation}" `; exports[`rebber: remark specs blockquote-list-item: blockquote-list-item 1`] = ` "This fails in markdown.pl and upskirt: \\\\begin{itemize} \\\\item\\\\relax hello \\\\begin{Quotation} world \\\\end{Quotation} \\\\end{itemize}" `; exports[`rebber: remark specs blockquotes: blockquotes 1`] = ` "\\\\begin{Quotation} This is a blockquote. \\\\end{Quotation} \\\\begin{Quotation} This is, in commonmark mode, another blockquote. \\\\end{Quotation}" `; exports[`rebber: remark specs blockquotes-empty-lines: blockquotes-empty-lines 1`] = ` "\\\\begin{Quotation} Note there is no space on the following line. Note there is no space on the preceding line. \\\\end{Quotation}" `; exports[`rebber: remark specs blockquotes-with-code-blocks: blockquotes-with-code-blocks 1`] = ` "\\\\begin{Quotation} Example: \\\\begin{CodeBlock}{text} sub status { print \\"working\\"; } \\\\end{CodeBlock} Or: \\\\begin{CodeBlock}{text} sub status { return \\"working\\"; } \\\\end{CodeBlock} \\\\end{Quotation}" `; exports[`rebber: remark specs bom: bom 1`] = ` "\\\\part{Hello from a BOM} Be careful when editing this file!" `; exports[`rebber: remark specs breaks-hard: breaks-hard 1`] = ` "These are not breaks: Look at the pretty line breaks. These are breaks: Look at the \\\\\\\\ pretty line \\\\\\\\ breaks. In \\\\texttt{commonmark: true} mode, an escaped newline character is exposed as a \\\\texttt{break} node: Look at the\\\\textbackslash{} pretty line\\\\textbackslash{} breaks." `; exports[`rebber: remark specs case-insensitive-refs: case-insensitive-refs 1`] = ` "\\\\hyperref[hi]{hi} \\\\footnote{\\\\label{hi}\\\\externalLink{/url}{/url}}" `; exports[`rebber: remark specs code-block: code-block 1`] = ` "Tildes: \\\\begin{CodeBlock}{javascript} alert('Hello World!'); \\\\end{CodeBlock}" `; exports[`rebber: remark specs code-block-escape: code-block-escape 1`] = ` "A little flaw: \\\\begin{CodeBlock}{python} \\\\end{CodeBlock} An ingenuous flaw: \\\\begin{CodeBlock}{text} \\\\input{/etc/passwd} \\\\begin{CodeBlock}{text} \\\\end{CodeBlock}" `; exports[`rebber: remark specs code-block-indentation: code-block-indentation 1`] = ` "Fenced code blocks are normally not exdented, however, when the initial fence is indented by spaces, the value of the code is exdented by up to that amount of spaces. \\\\begin{CodeBlock}{text} This is a code block... ...which is not exdented. \\\\end{CodeBlock} But... \\\\begin{CodeBlock}{text} This one... ...is. \\\\end{CodeBlock} And... \\\\begin{CodeBlock}{text} So is this... ...one. \\\\end{CodeBlock}" `; exports[`rebber: remark specs code-block-nesting-bug: code-block-nesting-bug 1`] = ` "GitHub, thus RedCarpet, has a bug where “nested” fenced code blocks, even with shorter fences, can exit their actual “parent” block. Note that this bug does not occur on indented code-blocks. \\\\begin{CodeBlock}{foo} \`\`\`bar baz \`\`\` \\\\end{CodeBlock} Even with a different fence marker: \\\\begin{CodeBlock}{foo} ~~~bar baz ~~~ \\\\end{CodeBlock} And reversed: \\\\begin{CodeBlock}{foo} ~~~bar baz ~~~ \\\\end{CodeBlock} \\\\begin{CodeBlock}{foo} \`\`\`bar baz \`\`\` \\\\end{CodeBlock}" `; exports[`rebber: remark specs code-blocks: code-blocks 1`] = ` "code block on the first line Regular text. \\\\begin{CodeBlock}{text} code block indented by spaces \\\\end{CodeBlock} Regular text. \\\\begin{CodeBlock}{text} the lines in this block all contain trailing spaces \\\\end{CodeBlock} Regular Text. \\\\begin{CodeBlock}{text} code block on the last line \\\\end{CodeBlock}" `; exports[`rebber: remark specs code-spans: code-spans 1`] = ` "\\\\texttt{} Fix for backticks within HTML tag: like this Here's how you put \\\\texttt{\`backticks\`} in a code span. Additionally, empty code spans are NOT supported: \`\`. Here’s an example, \\\\texttt{foo \` bar }. And here, \\\\texttt{\`\`}. \\\\texttt{// this is also inline code} So is this \\\\texttt{foo bar baz}. And this \\\\texttt{foo \`\` bar} And \\\\texttt{this\\\\textbackslash{}}but this is text\`." `; exports[`rebber: remark specs def-blocks: def-blocks 1`] = ` "\\\\begin{Quotation} hello \\\\footnote{\\\\label{1}\\\\externalLink{hello}{hello}} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} hello \\\\end{Quotation} \\\\footnote{\\\\label{2}\\\\externalLink{hello}{hello}} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax \\\\footnote{\\\\label{3}\\\\externalLink{hello}{hello}} \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\end{itemize} \\\\footnote{\\\\label{4}\\\\externalLink{hello}{hello}} \\\\begin{Quotation} foo bar \\\\end{Quotation} \\\\footnote{\\\\label{1-1}\\\\externalLink{foo}{foo}} \\\\begin{Quotation} bar \\\\end{Quotation}" `; exports[`rebber: remark specs definition-newline: definition-newline 1`] = ` "\\\\hyperref[baz]{baz}: /url ( ) [foo]: /url \\" \\" [bar]: /url ' ' \\\\footnote{\\\\label{baz}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{baz-1}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{baz-1-1}\\\\externalLink{/url}{/url}} \\\\hyperref[baz]{baz}: /url 'foo" `; exports[`rebber: remark specs definition-unclosed: definition-unclosed 1`] = ` "[foo]: \\\\footnote{\\\\label{bar}\\\\externalLink{( [foo]: \\" [bar]: '" `; exports[`rebber: remark specs deletion: deletion 1`] = `"hello \\\\sout{hi} world"`; exports[`rebber: remark specs double-link: double-link 1`] = ` "

Already linked: http://example.com/.

Already linked: \\\\externalLink{http://example.com/}{http://example.com/}. Already linked: \\\\textbf{http://example.com/}." `; exports[`rebber: remark specs emphasis: emphasis 1`] = ` "\\\\textit{emphasis}. \\\\textbf{strong}." `; exports[`rebber: remark specs emphasis-empty: emphasis-empty 1`] = ` "Hello ** ** world. Hello \\\\_\\\\_ \\\\_\\\\_ world. Hello * * world. Hello \\\\_ \\\\_ world." `; exports[`rebber: remark specs emphasis-escaped-final-marker: emphasis-escaped-final-marker 1`] = ` "*bar* **bar** \\\\_bar\\\\_ \\\\_\\\\_bar\\\\_\\\\_" `; exports[`rebber: remark specs emphasis-internal: emphasis-internal 1`] = `"These words should\\\\_not\\\\_be\\\\_emphasized."`; exports[`rebber: remark specs empty: empty 1`] = `""`; exports[`rebber: remark specs entities: entities 1`] = ` "Lots of entities are supported in mdast:  , \\\\&, ©, Æ, Ď, ¾, ℋ, ⅆ, ∲, \\\\&c. Even some entities with a missing terminal semicolon are parsed correctly (as per the HTML5 spec): ÿ, á, ©, and \\\\&. However, \\\\&MadeUpEntities; are kept in the document. Entities even work in the language flag of fenced code blocks: \\\\begin{CodeBlock}{some—language} alert('Hello'); \\\\end{CodeBlock} Or in \\\\externalLink{línks}{\\\\textasciitilde{}/some—file} Or in \\\\includegraphics{~/an–image.png} But, entities are not interpreted in \\\\texttt{inline c\\\\öde}, or in code blocks: \\\\begin{CodeBlock}{text} CÖDE block. \\\\end{CodeBlock}" `; exports[`rebber: remark specs entities-advanced: entities-advanced 1`] = ` "\\\\begin{Quotation} However, \\\\&MadeUpEntities; are kept in the document. \\\\end{Quotation} \\\\begin{Quotation} Entities even work in the language flag of fenced code blocks: \\\\end{Quotation} \\\\begin{Quotation} \\\\begin{CodeBlock}{some©language} alert('Hello'); \\\\end{CodeBlock} \\\\end{Quotation} \\\\begin{Quotation} And in an auto-link: \\\\externalLink{http://example©xample.com}{http://example\\\\©xample.com} \\\\end{Quotation} \\\\begin{Quotation} Foo and bar and http://example©xample.com and baz. \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\externalLink{l©nks}{\\\\textasciitilde{}/some\\\\©file} \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\externalLink{l©lnks}{\\\\textasciitilde{}/some\\\\©file} \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\includegraphics{~/some©file} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} Or in \\\\includegraphics{~/some©file} \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\includegraphics{undefined} \\\\end{Quotation} \\\\begin{Quotation} \\\\footnote{\\\\label{1}\\\\externalLink{http://example\\\\©xample.com}{http://example\\\\©xample.com}} \\\\end{Quotation} \\\\begin{Quotation} \\\\footnote{\\\\label{ 1 }\\\\externalLink{http://example\\\\©xample.com}{http://example\\\\©xample.com}} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} But, entities are not interpreted in \\\\texttt{inline c\\\\öde}, or in code blocks: \\\\end{Quotation} \\\\begin{Quotation} \\\\begin{CodeBlock}{text} CÖDE block. \\\\end{CodeBlock} \\\\end{Quotation}" `; exports[`rebber: remark specs escaped-angles: escaped-angles 1`] = `">"`; exports[`rebber: remark specs fenced-code: fenced-code 1`] = ` "\\\\begin{CodeBlock}{js} var a = 'hello'; console.log(a + ' world'); \\\\end{CodeBlock} \\\\begin{CodeBlock}{bash} echo \\"hello, \${WORLD}\\" \\\\end{CodeBlock} \\\\begin{CodeBlock}{longfence} Q: What do you call a tall person who sells stolen goods? \\\\end{CodeBlock} \\\\begin{CodeBlock}{ManyTildes} A longfence! \\\\end{CodeBlock}" `; exports[`rebber: remark specs fenced-code-empty: fenced-code-empty 1`] = ` "Normal with language tag: \\\\begin{CodeBlock}{js} \\\\end{CodeBlock} With white space: \\\\begin{CodeBlock}{bash} \\\\end{CodeBlock} With very long fences: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} With nothing: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock}" `; exports[`rebber: remark specs fenced-code-trailing-characters: fenced-code-trailing-characters 1`] = ` "\\\\begin{CodeBlock}{js} foo(); \`\`\`bash \\\\end{CodeBlock}" `; exports[`rebber: remark specs fenced-code-trailing-characters-2: fenced-code-trailing-characters-2 1`] = ` "\\\\begin{CodeBlock}{text} \`\`\` aaa \\\\end{CodeBlock}" `; exports[`rebber: remark specs fenced-code-white-space-after-flag: fenced-code-white-space-after-flag 1`] = ` "\\\\begin{CodeBlock}{js} foo(); \\\\end{CodeBlock} \\\\begin{CodeBlock}{bash} echo \\"hello, \${WORLD}\\" \\\\end{CodeBlock}" `; exports[`rebber: remark specs hard-wrapped-paragraphs-with-list-like-lines: hard-wrapped-paragraphs-with-list-like-lines 1`] = ` "In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the 123. middle of a paragraph looked like a list item. Here's one with a bullet. \\\\begin{itemize} \\\\item\\\\relax criminey. \\\\end{itemize} Non-GFM does not create a list for either. GFM does not create a list for \\\\texttt{8.}, but does for \\\\texttt{*}. CommonMark creates a list for both. All versions create lists for the following. \\\\begin{itemize} \\\\item\\\\relax Here's one with a bullet. \\\\begin{itemize} \\\\item\\\\relax criminey. \\\\end{itemize} \\\\end{itemize} ...and the following: \\\\begin{enumerate} \\\\item\\\\relax In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. \\\\end{enumerate}" `; exports[`rebber: remark specs heading: heading 1`] = ` "\\\\part{Heading 1} \\\\chapter{Heading 2} \\\\section{Heading 4} \\\\subsection{Heading 4} \\\\subsubsection{Heading 5} \\\\paragraph{Heading 6}" `; exports[`rebber: remark specs heading-atx-closed-trailing-white-space: heading-atx-closed-trailing-white-space 1`] = ` "\\\\part{Foo} \\\\chapter{Bar}" `; exports[`rebber: remark specs heading-atx-empty: heading-atx-empty 1`] = ` "\\\\part{} \\\\chapter{} \\\\section{} \\\\subsection{} \\\\subsubsection{} \\\\paragraph{}" `; exports[`rebber: remark specs heading-in-blockquote: heading-in-blockquote 1`] = ` "\\\\begin{Quotation} A blockquote with some more text. \\\\end{Quotation} A normal paragraph. \\\\begin{Quotation} \\\\chapter{A blockquote followed by a horizontal rule (in CommonMark).} \\\\end{Quotation} \\\\begin{Quotation} \\\\chapter{A heading in a blockquote} \\\\end{Quotation}" `; exports[`rebber: remark specs heading-in-paragraph: heading-in-paragraph 1`] = ` "Hello \\\\part{World}" `; exports[`rebber: remark specs heading-not-atx: heading-not-atx 1`] = ` "\\\\#This is not a heading, per CommonMark: \\\\externalLink{http://spec.commonmark.org/0.17/\\\\#example-25}{http://spec.commonmark.org/0.17/\\\\#example-25} Kramdown (GitHub) neither supports unspaced ATX-headings. \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h7? \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h8? \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h9? More than six \\\\# characters is not a heading: \\\\externalLink{http://spec.commonmark.org/0.26/\\\\#example-33}{http://spec.commonmark.org/0.26/\\\\#example-33}" `; exports[`rebber: remark specs heading-setext-with-initial-spacing: heading-setext-with-initial-spacing 1`] = ` "\\\\part{Heading 1} \\\\chapter{Heading 2} Both these headings caused positional problems in on commit daa344c and before." `; exports[`rebber: remark specs horizontal-rules: horizontal-rules 1`] = ` "Dashes: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} --- \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} - - - \\\\end{CodeBlock} Asterisks: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} * * * \\\\end{CodeBlock} Underscores: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} ___ \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} _ _ _ \\\\end{CodeBlock}" `; exports[`rebber: remark specs horizontal-rules-adjacent: horizontal-rules-adjacent 1`] = ` "\\\\horizontalLine \\\\horizontalLine \\\\horizontalLine The three asterisks are not a Setext header. This is a paragraph. \\\\horizontalLine This is another paragraph. \\\\horizontalLine \\\\chapter{But this is a secondary heading.} \\\\horizontalLine" `; exports[`rebber: remark specs hr: hr 1`] = `"\\\\horizontalLine"`; exports[`rebber: remark specs hr-list-break: hr-list-break 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax hello world \\\\item\\\\relax how are \\\\end{itemize} \\\\horizontalLine you today? The above asterisks do split the list, but the below ones do not. \\\\begin{itemize} \\\\item\\\\relax hello world \\\\item\\\\relax how are \\\\item\\\\relax \\\\horizontalLine you today? \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax Neither do these \\\\item\\\\relax how are \\\\item\\\\relax \\\\begin{itemize} \\\\item\\\\relax \\\\begin{itemize} \\\\item\\\\relax you today? \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax But these do \\\\item\\\\relax how are \\\\end{itemize} \\\\horizontalLine you today?" `; exports[`rebber: remark specs html-advanced: html-advanced 1`] = ` "Simple block on one line:
foo
And nested without indentation:
foo
\\"/>
bar
" `; exports[`rebber: remark specs html-attributes: html-attributes 1`] = ` "\\\\part{Block-level}
foo " `; exports[`rebber: remark specs html-comments: html-comments 1`] = ` "Paragraph one. What follows is not an HTML comment because it contains two consecutive dashes: \\\\externalLink{https://html.spec.whatwg.org/multipage/syntax.html\\\\#comments}{https://html.spec.whatwg.org/multipage/syntax.html\\\\#comments}. But this is fine (in commonmark): And, this is wrong (in commonmark): --> The end." `; exports[`rebber: remark specs html-declaration: html-declaration 1`] = ` " foo " `; exports[`rebber: remark specs html-indented: html-indented 1`] = ` "
*hello*
*hello* alpha " `; exports[`rebber: remark specs html-processing-instruction: html-processing-instruction 1`] = ` "'; ?>" `; exports[`rebber: remark specs html-simple: html-simple 1`] = ` "Here's a simple block:
foo
This should be a code block, though: \\\\begin{CodeBlock}{text}
foo
\\\\end{CodeBlock} As should this: \\\\begin{CodeBlock}{text}
foo
\\\\end{CodeBlock} Now, nested:
foo
This should just be an HTML comment: Multiline: Code block: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} Just plain comment, with trailing spaces on the line: Code: \\\\begin{CodeBlock}{text}
\\\\end{CodeBlock} Hr's:








" `; exports[`rebber: remark specs html-tags: html-tags 1`] = ` "\\\\part{Block}
<-article>
" `; exports[`rebber: remark specs image-basename-dots: image-basename-dots 1`] = ` "\\\\includegraphics{{x.yz}.png} \\\\includegraphics{/a/{w.x.y.z}.png} \\\\includegraphics{/{w.x.y.z}.png} \\\\includegraphics{/foo.bar/{x.yz}.png}" `; exports[`rebber: remark specs image-empty-alt: image-empty-alt 1`] = `"\\\\includegraphics{/xyz.png}"`; exports[`rebber: remark specs image-in-link: image-in-link 1`] = ` "\\\\part{\\\\externalLink{\\\\includegraphics{https://img.shields.io/badge/unicorn-approved-ff69b4.svg}}{http://shields.io}} \\\\externalLink{\\\\includegraphics{https://img.shields.io/travis/wooorm/mdast.svg?style=flat}}{https://travis-ci.org/wooorm/mdast} \\\\externalLink{\\\\includegraphics{https://img.shields.io/badge/style-flat--squared-green.svg?style=flat-square}}{http://example.com}" `; exports[`rebber: remark specs image-path-escape: image-path-escape 1`] = `"\\\\includegraphics{a[b]\\\\ \\\\input{/etc/passwd\\\\image{[a](b)}"`; exports[`rebber: remark specs image-with-pipe: image-with-pipe 1`] = `"f|"`; exports[`rebber: remark specs images: images 1`] = ` "Lorem ipsum dolor sit \\\\includegraphics{http://amet.com/amet.jpeg}, consectetur adipiscing elit. Praesent dictum purus ullamcorper ligula semper pellentesque. Nulla \\\\includegraphics{http://finibus.com/finibus.png} neque et diam rhoncus convallis. Nam dictum sapien nec sem ultrices fermentum. Nulla \\\\includegraphics{http://facilisi.com/facilisi.gif}. In et feugiat massa. Donec sed sodales metus, ut aliquet quam. Suspendisse nec ipsum risus. Interdum et malesuada fames ac ante ipsum primis in \\\\includegraphics{http://faucibus.com/faucibus.tiff}." `; exports[`rebber: remark specs invalid-link-definition: invalid-link-definition 1`] = `"Something[2-3]"`; exports[`rebber: remark specs lazy-blockquotes: lazy-blockquotes 1`] = ` "\\\\begin{Quotation} hi there bud \\\\end{Quotation}" `; exports[`rebber: remark specs link-in-link: link-in-link 1`] = ` "\\\\part{\\\\externalLink{mailto:test@example.com}{http://shields.io}} \\\\externalLink{https://travis-ci.org/wooorm/mdast}{https://travis-ci.org/wooorm/mdast} \\\\externalLink{[](http://example.com \\"An example\\")}{http://example.com}" `; exports[`rebber: remark specs link-spaces: link-spaces 1`] = ` "[alpha] (bravo \\\\includegraphics{undefined} (delta .com) [echo] (\\\\externalLink{http://foxtrot.golf}{http://foxtrot.golf}) \\\\includegraphics{undefined} (india.com/juliett)" `; exports[`rebber: remark specs link-whitespace: link-whitespace 1`] = ` "[alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). [alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). [alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie." `; exports[`rebber: remark specs link-with-spaces: link-with-spaces 1`] = ` "\\\\externalLink{Hello}{./world and some spaces.html} \\\\externalLink{Hello}{./world and some spaces.html}" `; exports[`rebber: remark specs links: links 1`] = ` "Lorem ipsum dolor sit \\\\externalLink{amet}{http://amet.com}, consectetur adipiscing elit. Praesent dictum purus ullamcorper ligula semper pellentesque. Nulla \\\\externalLink{finibus}{http://finibus.com} neque et diam rhoncus convallis. Nam dictum sapien nec sem ultrices fermentum. Nulla \\\\externalLink{facilisi}{http://facilisi.com}. In et feugiat massa. Donec sed sodales metus, ut aliquet quam. Suspendisse nec ipsum risus. Interdum et malesuada fames ac ante ipsum primis in \\\\externalLink{faucibus}{http://faucibus.com}." `; exports[`rebber: remark specs links-inline-style: links-inline-style 1`] = ` "Just a \\\\externalLink{URL}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. [URL and title]( /url/has space ). [URL and title]( /url/has space/ \\"url has space and title\\"). ." `; exports[`rebber: remark specs links-reference-proto: links-reference-proto 1`] = ` "A \\\\hyperref[tostring]{primary}, \\\\hyperref[constructor]{secondary}, and \\\\hyperref[__proto__]{tertiary} link. \\\\footnote{\\\\label{tostring}\\\\externalLink{http://primary.com}{http://primary.com}} \\\\footnote{\\\\label{__proto__}\\\\externalLink{http://tertiary.com}{http://tertiary.com}} \\\\footnote{\\\\label{constructor}\\\\externalLink{http://secondary.com}{http://secondary.com}}" `; exports[`rebber: remark specs links-reference-style: links-reference-style 1`] = ` "Foo \\\\hyperref[1]{bar}. Foo \\\\hyperref[1]{bar}. Foo \\\\hyperref[1]{bar}. \\\\footnote{\\\\label{1}\\\\externalLink{/url/}{/url/}} With \\\\hyperref[b]{embedded [brackets]}. Indented \\\\hyperref[once]{once}. Indented \\\\hyperref[twice]{twice}. Indented \\\\hyperref[thrice]{thrice}. Indented [four] times. \\\\footnote{\\\\label{once}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{twice}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{thrice}\\\\externalLink{/url}{/url}} \\\\begin{CodeBlock}{text} [four]: /url \\\\end{CodeBlock} \\\\footnote{\\\\label{b}\\\\externalLink{/url/}{/url/}} \\\\horizontalLine \\\\hyperref[this]{this} should work So should \\\\hyperref[this]{this}. And \\\\hyperref[this]{this}. And \\\\hyperref[this]{this}. And \\\\hyperref[this]{this}. But not [that]. Nor [that]. Nor [that]. [Something in brackets like \\\\hyperref[this]{this} should work] [Same with \\\\hyperref[this]{this}.] In this case, \\\\externalLink{this}{/somethingelse/} points to something else. Backslashing should suppress [this] and [this]. \\\\footnote{\\\\label{this}\\\\externalLink{foo}{foo}} \\\\horizontalLine Here's one where the \\\\hyperref[link breaks]{link breaks} across lines. Here's another where the \\\\hyperref[link breaks]{link breaks} across lines, but with a line-ending space. \\\\footnote{\\\\label{link breaks}\\\\externalLink{/url/}{/url/}}" `; exports[`rebber: remark specs links-shortcut-references: links-shortcut-references 1`] = ` "This is the \\\\hyperref[simple case]{simple case}. \\\\footnote{\\\\label{simple case}\\\\externalLink{/simple}{/simple}} This one has a \\\\hyperref[line break]{line break}. This one has a \\\\hyperref[line break]{line break} with a line-ending space. \\\\footnote{\\\\label{line break}\\\\externalLink{/foo}{/foo}} \\\\hyperref[that]{this} and the \\\\hyperref[other]{other} \\\\footnote{\\\\label{this}\\\\externalLink{/this}{/this}} \\\\footnote{\\\\label{that}\\\\externalLink{/that}{/that}} \\\\footnote{\\\\label{other}\\\\externalLink{/other}{/other}}" `; exports[`rebber: remark specs links-text-delimiters: links-text-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs links-text-empty: links-text-empty 1`] = ` "\\\\externalLink{}{./hello-world.html}. \\\\externalLink{}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs links-text-entity-delimiters: links-text-entity-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs links-text-escaped-delimiters: links-text-escaped-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs links-text-mismatched-delimiters: links-text-mismatched-delimiters 1`] = ` "[Hello \\\\externalLink{world!}{./hello-world.html}. [Hello \\\\externalLink{world!}{./hello-world.html}. ![Hello \\\\externalLink{world!}{./hello-world.html}. ![Hello \\\\externalLink{world!}{./hello-world.html}." `; exports[`rebber: remark specs links-title-double-quotes: links-title-double-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-double-quotes-delimiters: links-title-double-quotes-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-double-quotes-entity-delimiters: links-title-double-quotes-entity-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-double-quotes-escaped-delimiters: links-title-double-quotes-escaped-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-double-quotes-mismatched-delimiters: links-title-double-quotes-mismatched-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-empty-double-quotes: links-title-empty-double-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-empty-parentheses: links-title-empty-parentheses 1`] = ` "[Hello](./world.html ()). [Hello](<./world.html> ()). \\\\includegraphics{undefined}(./world.html ()). \\\\includegraphics{undefined}(<./world.html> ())." `; exports[`rebber: remark specs links-title-empty-single-quotes: links-title-empty-single-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-parentheses: links-title-parentheses 1`] = ` "[Hello](./world.html (Hello World!)). [Hello](<./world.html> (Hello World!)). \\\\includegraphics{undefined}(./world.html (Hello World!)). \\\\includegraphics{undefined}(<./world.html> (Hello World!))." `; exports[`rebber: remark specs links-title-single-quotes: links-title-single-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-single-quotes-delimiters: links-title-single-quotes-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-single-quotes-entity-delimiters: links-title-single-quotes-entity-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-single-quotes-escaped-delimiters: links-title-single-quotes-escaped-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-single-quotes-mismatched-delimiters: links-title-single-quotes-mismatched-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs links-title-unclosed: links-title-unclosed 1`] = ` "[Hello](./world.html 'Hello [Hello](<./world.html> 'Hello \\\\includegraphics{undefined}(./world.html 'Hello \\\\includegraphics{undefined}(<./world.html> 'Hello [Hello](./world.html \\"Hello [Hello](<./world.html> \\"Hello \\\\includegraphics{undefined}(./world.html \\"Hello \\\\includegraphics{undefined}(<./world.html> \\"Hello [Hello](./world.html (Hello [Hello](<./world.html> (Hello \\\\includegraphics{undefined}(./world.html (Hello \\\\includegraphics{undefined}(<./world.html> (Hello" `; exports[`rebber: remark specs links-url-empty: links-url-empty 1`] = ` ". . \\\\includegraphics{}. \\\\includegraphics{}." `; exports[`rebber: remark specs links-url-empty-title-double-quotes: links-url-empty-title-double-quotes 1`] = ` "\\\\externalLink{Hello}{\\"World!\\"}. \\\\externalLink{Hello}{\\"World!\\"}. . \\\\includegraphics{\\"World!\\"}. \\\\includegraphics{\\"World!\\"}. \\\\includegraphics{}." `; exports[`rebber: remark specs links-url-empty-title-parentheses: links-url-empty-title-parentheses 1`] = ` "\\\\externalLink{Hello}{(World!)}. \\\\externalLink{Hello}{(World!)}. [World](<> (World!)). \\\\includegraphics{(World!)}. \\\\includegraphics{(World!)}. \\\\includegraphics{undefined}(<> (World!))." `; exports[`rebber: remark specs links-url-empty-title-single-quotes: links-url-empty-title-single-quotes 1`] = ` "\\\\externalLink{Hello}{'World!'}. \\\\externalLink{Hello}{'World!'}. . \\\\includegraphics{'World!'}. \\\\includegraphics{'World!'}. \\\\includegraphics{}." `; exports[`rebber: remark specs links-url-entity-parentheses: links-url-entity-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}." `; exports[`rebber: remark specs links-url-escaped-parentheses: links-url-escaped-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}." `; exports[`rebber: remark specs links-url-mismatched-parentheses: links-url-mismatched-parentheses 1`] = ` "[Hello](./world(and-hello(world)). \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld}). \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{undefined}(./world(and-hello(world)). \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld}). \\\\includegraphics{./world(and)helloworld)}." `; exports[`rebber: remark specs links-url-nested-parentheses: links-url-nested-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and)hello(world)}. \\\\externalLink{Hello}{./world(and)hello(world)}. \\\\includegraphics{./world(and)hello(world)}. \\\\includegraphics{./world(and)hello(world)}." `; exports[`rebber: remark specs links-url-new-line: links-url-new-line 1`] = ` "[Hello](./wo rld.html). \\\\externalLink{Hello}{./wo rld.html}. \\\\includegraphics{undefined}(./wo rld.png). \\\\includegraphics{./wo rld.png}." `; exports[`rebber: remark specs links-url-unclosed: links-url-unclosed 1`] = ` "[Hello]( [World](< \\\\includegraphics{undefined}( \\\\includegraphics{undefined}(<" `; exports[`rebber: remark specs links-url-white-space: links-url-white-space 1`] = ` "[Hello](./wo rld.html). \\\\externalLink{Hello}{./wo rld.html}. \\\\includegraphics{undefined}(./wo rld.png). \\\\includegraphics{./wo rld.png}." `; exports[`rebber: remark specs list: list 1`] = ` "\\\\part{List bullets} \\\\begin{itemize} \\\\item\\\\relax One: \\\\begin{itemize} \\\\item\\\\relax Nested one; \\\\item\\\\relax Nested two: \\\\begin{itemize} \\\\item\\\\relax Nested three. \\\\end{itemize} \\\\end{itemize} \\\\item\\\\relax Two; \\\\item\\\\relax Three. \\\\end{itemize}" `; exports[`rebber: remark specs list-after-list: list-after-list 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{enumerate} \\\\horizontalLine \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{enumerate}" `; exports[`rebber: remark specs list-and-code: list-and-code 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax This is a list item \\\\end{itemize} \\\\begin{CodeBlock}{text} This is code \\\\end{CodeBlock}" `; exports[`rebber: remark specs list-continuation: list-continuation 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo \\\\end{enumerate} \\\\horizontalLine \\\\begin{enumerate} \\\\item\\\\relax foo \\\\end{enumerate} \\\\begin{CodeBlock}{js} code(); \\\\end{CodeBlock} \\\\begin{enumerate} \\\\item\\\\relax \\\\hyperref[foo]{foo} \\\\end{enumerate} \\\\footnote{\\\\label{foo}\\\\externalLink{http://google.com}{http://google.com}}" `; exports[`rebber: remark specs list-indentation: list-indentation 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Hello 1a World 1a. \\\\item\\\\relax Hello 1b World 1b. \\\\item\\\\relax Hello 2a World 2a. \\\\item\\\\relax Hello 2b World 2b. \\\\item\\\\relax Hello 3a World 3a. \\\\item\\\\relax Hello 3b World 3b. \\\\item\\\\relax Hello 4a World 4a. \\\\item\\\\relax Hello 4b World 4b. \\\\item\\\\relax \\\\begin{CodeBlock}{text} Hello 5a \\\\end{CodeBlock} World 5a. \\\\item\\\\relax \\\\begin{CodeBlock}{text} Hello 5b World 5b. \\\\end{CodeBlock} \\\\end{itemize}" `; exports[`rebber: remark specs list-item-empty: list-item-empty 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax foo \\\\item\\\\relax \\\\item\\\\relax bar \\\\item\\\\relax foo \\\\item\\\\relax \\\\item\\\\relax bar \\\\end{itemize}" `; exports[`rebber: remark specs list-item-empty-with-white-space: list-item-empty-with-white-space 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax \\\\end{itemize}" `; exports[`rebber: remark specs list-item-indent: list-item-indent 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{enumerate} \\\\begin{itemize} \\\\item\\\\relax foo bar baz. \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{itemize}" `; exports[`rebber: remark specs list-item-newline: list-item-newline 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Foo \\\\item\\\\relax Bar \\\\end{itemize}" `; exports[`rebber: remark specs list-item-text: list-item-text 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax item1 \\\\begin{itemize} \\\\item\\\\relax item2 \\\\end{itemize} text \\\\end{itemize}" `; exports[`rebber: remark specs list-ordered: list-ordered 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo; \\\\item\\\\relax bar; \\\\item\\\\relax baz. \\\\end{enumerate}" `; exports[`rebber: remark specs lists-with-code-and-rules: lists-with-code-and-rules 1`] = ` "\\\\chapter{foo} \\\\begin{enumerate} \\\\item\\\\relax bar: \\\\begin{Quotation} \\\\begin{itemize} \\\\item\\\\relax one \\\\begin{itemize} \\\\item\\\\relax two \\\\begin{itemize} \\\\item\\\\relax three \\\\item\\\\relax four \\\\item\\\\relax five \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} \\\\end{Quotation} \\\\item\\\\relax foo: \\\\begin{CodeBlock}{text} line 1 line 2 \\\\end{CodeBlock} \\\\item\\\\relax foo: \\\\begin{enumerate} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{erb} some code here \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{erb} foo --- bar --- foo bar \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{html} --- foo foo --- bar \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{text} foo --- bar \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\end{enumerate} \\\\end{enumerate}" `; exports[`rebber: remark specs loose-lists: loose-lists 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax hello world how are \\\\item\\\\relax you \\\\end{itemize} better behavior: \\\\begin{itemize} \\\\item\\\\relax hello \\\\begin{itemize} \\\\item\\\\relax world how are you \\\\item\\\\relax today \\\\end{itemize} \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world how \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax how are \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax how are \\\\end{itemize}" `; exports[`rebber: remark specs main: main 1`] = ` "\\\\footnote{\\\\label{test}\\\\externalLink{http://google.com/}{http://google.com/}} \\\\part{A heading} Just a note, I've found that I can't test my markdown parser vs others. For example, both markdown.js and showdown code blocks in lists wrong. They're also completely \\\\hyperref[test]{inconsistent} with regards to paragraphs in list items. A link. Not anymore. \\\\begin{itemize} \\\\item\\\\relax List Item 1 \\\\item\\\\relax List Item 2 \\\\begin{itemize} \\\\item\\\\relax New List Item 1 Hi, this is a list item. \\\\item\\\\relax New List Item 2 Another item Code goes here. Lots of it... \\\\item\\\\relax New List Item 3 The last item \\\\end{itemize} \\\\item\\\\relax List Item 3 The final item. \\\\item\\\\relax List Item 4 The real final item. \\\\end{itemize} Paragraph. \\\\begin{Quotation} \\\\begin{itemize} \\\\item\\\\relax bq Item 1 \\\\item\\\\relax bq Item 2 \\\\begin{itemize} \\\\item\\\\relax New bq Item 1 \\\\item\\\\relax New bq Item 2 Text here \\\\end{itemize} \\\\end{itemize} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} Another blockquote! I really need to get more creative with mockup text.. markdown.js breaks here again \\\\end{Quotation} \\\\chapter{Another Heading} Hello \\\\textit{world}. Here is a \\\\externalLink{link}{//hello}. And an image \\\\includegraphics{src}. And an image with an empty alt attribute \\\\includegraphics{src}. \\\\begin{CodeBlock}{text} Code goes here. Lots of it... \\\\end{CodeBlock}" `; exports[`rebber: remark specs markdown-documentation-basics: markdown-documentation-basics 1`] = ` "\\\\part{Markdown: Basics} \\\\chapter{Getting the Gist of Markdown's Formatting Syntax} This page offers a brief overview of what it's like to use Markdown. The \\\\hyperref[s]{syntax page} provides complete, detailed documentation for every feature, but Markdown should be very easy to pick up simply by looking at a few examples of it in action. The examples on this page are written in a before/after style, showing example syntax and the HTML output produced by Markdown. It's also helpful to simply try Markdown out; the \\\\hyperref[d]{Dingus} is a web application that allows you type your own Markdown-formatted text and translate it to XHTML. \\\\textbf{Note:} This document is itself written using Markdown; you can \\\\hyperref[src]{see the source for it by adding '.text' to the URL}. \\\\footnote{\\\\label{s}\\\\externalLink{/projects/markdown/syntax}{/projects/markdown/syntax}} \\\\footnote{\\\\label{d}\\\\externalLink{/projects/markdown/dingus}{/projects/markdown/dingus}} \\\\footnote{\\\\label{src}\\\\externalLink{/projects/markdown/basics.text}{/projects/markdown/basics.text}} \\\\chapter{Paragraphs, Headers, Blockquotes} A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. Markdown offers two styles of headers: \\\\textit{Setext} and \\\\textit{atx}. Setext-style headers for \\\\texttt{

} and \\\\texttt{

} are created by \\"underlining\\" with equal signs (\\\\texttt{=}) and hyphens (\\\\texttt{-}), respectively. To create an atx-style header, you put 1-6 hash marks (\\\\texttt{\\\\#}) at the beginning of the line -- the number of hashes equals the resulting HTML header level. Blockquotes are indicated using email-style '\\\\texttt{>}' angle brackets. Markdown: \\\\begin{CodeBlock}{text} A First Level Header ==================== A Second Level Header --------------------- Now is the time for all good men to come to the aid of their country. This is just a regular paragraph. The quick brown fox jumped over the lazy dog's back. ### Header 3 > This is a blockquote. > > This is the second paragraph in the blockquote. > > ## This is an H2 in a blockquote \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

A First Level Header

A Second Level Header

Now is the time for all good men to come to the aid of their country. This is just a regular paragraph.

The quick brown fox jumped over the lazy dog's back.

Header 3

This is a blockquote.

This is the second paragraph in the blockquote.

This is an H2 in a blockquote

\\\\end{CodeBlock} \\\\section{Phrase Emphasis} Markdown uses asterisks and underscores to indicate spans of emphasis. Markdown: \\\\begin{CodeBlock}{text} Some of these words *are emphasized*. Some of these words _are emphasized also_. Use two asterisks for **strong emphasis**. Or, if you prefer, __use two underscores instead__. \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

Some of these words are emphasized. Some of these words are emphasized also.

Use two asterisks for strong emphasis. Or, if you prefer, use two underscores instead.

\\\\end{CodeBlock} \\\\chapter{Lists} Unordered (bulleted) lists use asterisks, pluses, and hyphens (\\\\texttt{*}, \\\\texttt{+}, and \\\\texttt{-}) as list markers. These three markers are interchangable; this: \\\\begin{CodeBlock}{text} * Candy. * Gum. * Booze. \\\\end{CodeBlock} this: \\\\begin{CodeBlock}{text} + Candy. + Gum. + Booze. \\\\end{CodeBlock} and this: \\\\begin{CodeBlock}{text} - Candy. - Gum. - Booze. \\\\end{CodeBlock} all produce the same output: \\\\begin{CodeBlock}{text}
  • Candy.
  • Gum.
  • Booze.
\\\\end{CodeBlock} Ordered (numbered) lists use regular numbers, followed by periods, as list markers: \\\\begin{CodeBlock}{text} 1. Red 2. Green 3. Blue \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}
  1. Red
  2. Green
  3. Blue
\\\\end{CodeBlock} If you put blank lines between items, you'll get \\\\texttt{

} tags for the list item text. You can create multi-paragraph list items by indenting the paragraphs by 4 spaces or 1 tab: \\\\begin{CodeBlock}{text} * A list item. With multiple paragraphs. * Another item in the list. \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

  • A list item.

    With multiple paragraphs.

  • Another item in the list.

\\\\end{CodeBlock} \\\\section{Links} Markdown supports two styles for creating links: \\\\textit{inline} and \\\\textit{reference}. With both styles, you use square brackets to delimit the text you want to turn into a link. Inline-style links use parentheses immediately after the link text. For example: \\\\begin{CodeBlock}{text} This is an [example link](http://example.com/). \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

This is an example link.

\\\\end{CodeBlock} Optionally, you may include a title attribute in the parentheses: \\\\begin{CodeBlock}{text} This is an [example link](http://example.com/ \\"With a Title\\"). \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

This is an example link.

\\\\end{CodeBlock} Reference-style links allow you to refer to your links by names, which you define elsewhere in your document: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google][1] than from [Yahoo][2] or [MSN][3]. [1]: http://google.com/ \\"Google\\" [2]: http://search.yahoo.com/ \\"Yahoo Search\\" [3]: http://search.msn.com/ \\"MSN Search\\" \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

I get 10 times more traffic from Google than from Yahoo or MSN.

\\\\end{CodeBlock} The title attribute is optional. Link names may contain letters, numbers and spaces, but are \\\\textit{not} case sensitive: \\\\begin{CodeBlock}{text} I start my morning with a cup of coffee and [The New York Times][NY Times]. [ny times]: http://www.nytimes.com/ \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

I start my morning with a cup of coffee and The New York Times.

\\\\end{CodeBlock} \\\\section{Images} Image syntax is very much like link syntax. Inline (titles are optional): \\\\begin{CodeBlock}{text} ![alt text](/path/to/img.jpg \\"Title\\") \\\\end{CodeBlock} Reference-style: \\\\begin{CodeBlock}{text} ![alt text][id] [id]: /path/to/img.jpg \\"Title\\" \\\\end{CodeBlock} Both of the above examples produce the same output: \\\\begin{CodeBlock}{text} \\"alt \\\\end{CodeBlock} \\\\section{Code} In a regular paragraph, you can create code span by wrapping text in backtick quotes. Any ampersands (\\\\texttt{\\\\&}) and angle brackets (\\\\texttt{<} or \\\\texttt{>}) will automatically be translated into HTML entities. This makes it easy to use Markdown to write about HTML example code: \\\\begin{CodeBlock}{text} I strongly recommend against using any \`\` tags. I wish SmartyPants used named entities like \`—\` instead of decimal-encoded entites like \`—\`. \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

I strongly recommend against using any <blink> tags.

I wish SmartyPants used named entities like &mdash; instead of decimal-encoded entites like &#8212;.

\\\\end{CodeBlock} To specify an entire block of pre-formatted code, indent every line of the block by 4 spaces or 1 tab. Just like with code spans, \\\\texttt{\\\\&}, \\\\texttt{<}, and \\\\texttt{>} characters will be escaped automatically. Markdown: \\\\begin{CodeBlock}{text} If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

For example.

\\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

<blockquote>
    <p>For example.</p>
</blockquote>
\\\\end{CodeBlock}" `; exports[`rebber: remark specs markdown-documentation-syntax: markdown-documentation-syntax 1`] = ` "\\\\part{Markdown: Syntax} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Overview}{\\\\#overview} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Philosophy}{\\\\#philosophy} \\\\item\\\\relax \\\\externalLink{Inline HTML}{\\\\#html} \\\\item\\\\relax \\\\externalLink{Automatic Escaping for Special Characters}{\\\\#autoescape} \\\\end{itemize} \\\\item\\\\relax \\\\externalLink{Block Elements}{\\\\#block} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Paragraphs and Line Breaks}{\\\\#p} \\\\item\\\\relax \\\\externalLink{Headers}{\\\\#header} \\\\item\\\\relax \\\\externalLink{Blockquotes}{\\\\#blockquote} \\\\item\\\\relax \\\\externalLink{Lists}{\\\\#list} \\\\item\\\\relax \\\\externalLink{Code Blocks}{\\\\#precode} \\\\item\\\\relax \\\\externalLink{Horizontal Rules}{\\\\#hr} \\\\end{itemize} \\\\item\\\\relax \\\\externalLink{Span Elements}{\\\\#span} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Links}{\\\\#link} \\\\item\\\\relax \\\\externalLink{Emphasis}{\\\\#em} \\\\item\\\\relax \\\\externalLink{Code}{\\\\#code} \\\\item\\\\relax \\\\externalLink{Images}{\\\\#img} \\\\end{itemize} \\\\item\\\\relax \\\\externalLink{Miscellaneous}{\\\\#misc} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Backslash Escapes}{\\\\#backslash} \\\\item\\\\relax \\\\externalLink{Automatic Links}{\\\\#autolink} \\\\end{itemize} \\\\end{itemize} \\\\textbf{Note:} This document is itself written using Markdown; you can \\\\hyperref[src]{see the source for it by adding '.text' to the URL}. \\\\footnote{\\\\label{src}\\\\externalLink{/projects/markdown/syntax.text}{/projects/markdown/syntax.text}} \\\\horizontalLine

Overview

Philosophy

Markdown is intended to be as easy-to-read and easy-to-write as is feasible. Readability, however, is emphasized above all else. A Markdown-formatted document should be publishable as-is, as plain text, without looking like it's been marked up with tags or formatting instructions. While Markdown's syntax has been influenced by several existing text-to-HTML filters -- including \\\\hyperref[1]{Setext}, \\\\hyperref[2]{atx}, \\\\hyperref[3]{Textile}, \\\\hyperref[4]{reStructuredText}, \\\\hyperref[5]{Grutatext}, and \\\\hyperref[6]{EtText} -- the single biggest source of inspiration for Markdown's syntax is the format of plain text email. \\\\footnote{\\\\label{1}\\\\externalLink{http://docutils.sourceforge.net/mirror/setext.html}{http://docutils.sourceforge.net/mirror/setext.html}} \\\\footnote{\\\\label{2}\\\\externalLink{http://www.aaronsw.com/2002/atx/}{http://www.aaronsw.com/2002/atx/}} \\\\footnote{\\\\label{3}\\\\externalLink{http://textism.com/tools/textile/}{http://textism.com/tools/textile/}} \\\\footnote{\\\\label{4}\\\\externalLink{http://docutils.sourceforge.net/rst.html}{http://docutils.sourceforge.net/rst.html}} \\\\footnote{\\\\label{5}\\\\externalLink{http://www.triptico.com/software/grutatxt.html}{http://www.triptico.com/software/grutatxt.html}} \\\\footnote{\\\\label{6}\\\\externalLink{http://ettext.taint.org/doc/}{http://ettext.taint.org/doc/}} To this end, Markdown's syntax is comprised entirely of punctuation characters, which punctuation characters have been carefully chosen so as to look like what they mean. E.g., asterisks around a word actually look like *emphasis*. Markdown lists look like, well, lists. Even blockquotes look like quoted passages of text, assuming you've ever used email.

Inline HTML

Markdown's syntax is intended for one purpose: to be used as a format for \\\\textit{writing} for the web. Markdown is not a replacement for HTML, or even close to it. Its syntax is very small, corresponding only to a very small subset of HTML tags. The idea is \\\\textit{not} to create a syntax that makes it easier to insert HTML tags. In my opinion, HTML tags are already easy to insert. The idea for Markdown is to make it easy to read, write, and edit prose. HTML is a \\\\textit{publishing} format; Markdown is a \\\\textit{writing} format. Thus, Markdown's formatting syntax only addresses issues that can be conveyed in plain text. For any markup that is not covered by Markdown's syntax, you simply use HTML itself. There's no need to preface it or delimit it to indicate that you're switching from Markdown to HTML; you just use the tags. The only restrictions are that block-level HTML elements -- e.g. \\\\texttt{
}, \\\\texttt{}, \\\\texttt{
}, \\\\texttt{

}, etc. -- must be separated from surrounding content by blank lines, and the start and end tags of the block should not be indented with tabs or spaces. Markdown is smart enough not to add extra (unwanted) \\\\texttt{

} tags around HTML block-level tags. For example, to add an HTML table to a Markdown article: \\\\begin{CodeBlock}{text} This is a regular paragraph.

Foo
This is another regular paragraph. \\\\end{CodeBlock} Note that Markdown formatting syntax is not processed within block-level HTML tags. E.g., you can't use Markdown-style \\\\texttt{*emphasis*} inside an HTML block. Span-level HTML tags -- e.g. \\\\texttt{}, \\\\texttt{}, or \\\\texttt{} -- can be used anywhere in a Markdown paragraph, list item, or header. If you want, you can even use HTML tags instead of Markdown formatting; e.g. if you'd prefer to use HTML \\\\texttt{} or \\\\texttt{} tags instead of Markdown's link or image syntax, go right ahead. Unlike block-level HTML tags, Markdown syntax \\\\textit{is} processed within span-level tags.

Automatic Escaping for Special Characters

In HTML, there are two characters that demand special treatment: \\\\texttt{<} and \\\\texttt{\\\\&}. Left angle brackets are used to start tags; ampersands are used to denote HTML entities. If you want to use them as literal characters, you must escape them as entities, e.g. \\\\texttt{\\\\<}, and \\\\texttt{\\\\&}. Ampersands in particular are bedeviling for web writers. If you want to write about 'AT\\\\&T', you need to write '\\\\texttt{AT\\\\&T}'. You even need to escape ampersands within URLs. Thus, if you want to link to: \\\\begin{CodeBlock}{text} http://images.google.com/images?num=30&q=larry+bird \\\\end{CodeBlock} you need to encode the URL as: \\\\begin{CodeBlock}{text} http://images.google.com/images?num=30&q=larry+bird \\\\end{CodeBlock} in your anchor tag \\\\texttt{href} attribute. Needless to say, this is easy to forget, and is probably the single most common source of HTML validation errors in otherwise well-marked-up web sites. Markdown allows you to use these characters naturally, taking care of all the necessary escaping for you. If you use an ampersand as part of an HTML entity, it remains unchanged; otherwise it will be translated into \\\\texttt{\\\\&}. So, if you want to include a copyright symbol in your article, you can write: \\\\begin{CodeBlock}{text} © \\\\end{CodeBlock} and Markdown will leave it alone. But if you write: \\\\begin{CodeBlock}{text} AT&T \\\\end{CodeBlock} Markdown will translate it to: \\\\begin{CodeBlock}{text} AT&T \\\\end{CodeBlock} Similarly, because Markdown supports \\\\externalLink{inline HTML}{\\\\#html}, if you use angle brackets as delimiters for HTML tags, Markdown will treat them as such. But if you write: \\\\begin{CodeBlock}{text} 4 < 5 \\\\end{CodeBlock} Markdown will translate it to: \\\\begin{CodeBlock}{text} 4 < 5 \\\\end{CodeBlock} However, inside Markdown code spans and blocks, angle brackets and ampersands are \\\\textit{always} encoded automatically. This makes it easy to use Markdown to write about HTML code. (As opposed to raw HTML, which is a terrible format for writing about HTML syntax, because every single \\\\texttt{<} and \\\\texttt{\\\\&} in your example code needs to be escaped.) \\\\horizontalLine

Block Elements

Paragraphs and Line Breaks

A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. The implication of the \\"one or more consecutive lines of text\\" rule is that Markdown supports \\"hard-wrapped\\" text paragraphs. This differs significantly from most other text-to-HTML formatters (including Movable Type's \\"Convert Line Breaks\\" option) which translate every line break character in a paragraph into a \\\\texttt{
} tag. When you \\\\textit{do} want to insert a \\\\texttt{
} break tag using Markdown, you end a line with two or more spaces, then type return. Yes, this takes a tad more effort to create a \\\\texttt{
}, but a simplistic \\"every line break is a \\\\texttt{
}\\" rule wouldn't work for Markdown. Markdown's email-style \\\\hyperref[bq]{blockquoting} and multi-paragraph \\\\hyperref[l]{list items} work best -- and look better -- when you format them with hard breaks. \\\\footnote{\\\\label{bq}\\\\externalLink{\\\\#blockquote}{\\\\#blockquote}} \\\\footnote{\\\\label{l}\\\\externalLink{\\\\#list}{\\\\#list}}

Headers

Markdown supports two styles of headers, \\\\hyperref[1]{Setext} and \\\\hyperref[2]{atx}. Setext-style headers are \\"underlined\\" using equal signs (for first-level headers) and dashes (for second-level headers). For example: \\\\begin{CodeBlock}{text} This is an H1 ============= This is an H2 ------------- \\\\end{CodeBlock} Any number of underlining \\\\texttt{=}'s or \\\\texttt{-}'s will work. Atx-style headers use 1-6 hash characters at the start of the line, corresponding to header levels 1-6. For example: \\\\begin{CodeBlock}{text} # This is an H1 ## This is an H2 ###### This is an H6 \\\\end{CodeBlock} Optionally, you may \\"close\\" atx-style headers. This is purely cosmetic -- you can use this if you think it looks better. The closing hashes don't even need to match the number of hashes used to open the header. (The number of opening hashes determines the header level.) : \\\\begin{CodeBlock}{text} # This is an H1 # ## This is an H2 ## ### This is an H3 ###### \\\\end{CodeBlock}

Blockquotes

Markdown uses email-style \\\\texttt{>} characters for blockquoting. If you're familiar with quoting passages of text in an email message, then you know how to create a blockquote in Markdown. It looks best if you hard wrap the text and put a \\\\texttt{>} before every line: \\\\begin{CodeBlock}{text} > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse > id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} Markdown allows you to be lazy and only put the \\\\texttt{>} before the first line of a hard-wrapped paragraph: \\\\begin{CodeBlock}{text} > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by adding additional levels of \\\\texttt{>}: \\\\begin{CodeBlock}{text} > This is the first level of quoting. > > > This is nested blockquote. > > Back to the first level. \\\\end{CodeBlock} Blockquotes can contain other Markdown elements, including headers, lists, and code blocks: \\\\begin{CodeBlock}{text} > ## This is a header. > > 1. This is the first list item. > 2. This is the second list item. > > Here's some example code: > > return shell_exec(\\"echo $input | $markdown_script\\"); \\\\end{CodeBlock} Any decent text editor should make email-style quoting easy. For example, with BBEdit, you can make a selection and choose Increase Quote Level from the Text menu.

Lists

Markdown supports ordered (numbered) and unordered (bulleted) lists. Unordered lists use asterisks, pluses, and hyphens -- interchangably -- as list markers: \\\\begin{CodeBlock}{text} * Red * Green * Blue \\\\end{CodeBlock} is equivalent to: \\\\begin{CodeBlock}{text} + Red + Green + Blue \\\\end{CodeBlock} and: \\\\begin{CodeBlock}{text} - Red - Green - Blue \\\\end{CodeBlock} Ordered lists use numbers followed by periods: \\\\begin{CodeBlock}{text} 1. Bird 2. McHale 3. Parish \\\\end{CodeBlock} It's important to note that the actual numbers you use to mark the list have no effect on the HTML output Markdown produces. The HTML Markdown produces from the above list is: \\\\begin{CodeBlock}{text}
  1. Bird
  2. McHale
  3. Parish
\\\\end{CodeBlock} If you instead wrote the list in Markdown like this: \\\\begin{CodeBlock}{text} 1. Bird 1. McHale 1. Parish \\\\end{CodeBlock} or even: \\\\begin{CodeBlock}{text} 3. Bird 1. McHale 8. Parish \\\\end{CodeBlock} you'd get the exact same HTML output. The point is, if you want to, you can use ordinal numbers in your ordered Markdown lists, so that the numbers in your source match the numbers in your published HTML. But if you want to be lazy, you don't have to. If you do use lazy list numbering, however, you should still start the list with the number 1. At some point in the future, Markdown may support starting ordered lists at an arbitrary number. List markers typically start at the left margin, but may be indented by up to three spaces. List markers must be followed by one or more spaces or a tab. To make lists look nice, you can wrap items with hanging indents: \\\\begin{CodeBlock}{text} * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} But if you want to be lazy, you don't have to: \\\\begin{CodeBlock}{text} * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} If list items are separated by blank lines, Markdown will wrap the items in \\\\texttt{

} tags in the HTML output. For example, this input: \\\\begin{CodeBlock}{text} * Bird * Magic \\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}

  • Bird
  • Magic
\\\\end{CodeBlock} But this: \\\\begin{CodeBlock}{text} * Bird * Magic \\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}
  • Bird

  • Magic

\\\\end{CodeBlock} List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be intended by either 4 spaces or one tab: \\\\begin{CodeBlock}{text} 1. This is a list item with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 2. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} It looks nice if you indent every line of the subsequent paragraphs, but here again, Markdown will allow you to be lazy: \\\\begin{CodeBlock}{text} * This is a list item with two paragraphs. This is the second paragraph in the list item. You're only required to indent the first line. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. * Another item in the same list. \\\\end{CodeBlock} To put a blockquote within a list item, the blockquote's \\\\texttt{>} delimiters need to be indented: \\\\begin{CodeBlock}{text} * A list item with a blockquote: > This is a blockquote > inside a list item. \\\\end{CodeBlock} To put a code block within a list item, the code block needs to be indented \\\\textit{twice} -- 8 spaces or two tabs: \\\\begin{CodeBlock}{text} * A list item with a code block: \\\\end{CodeBlock} It's worth noting that it's possible to trigger an ordered list by accident, by writing something like this: \\\\begin{CodeBlock}{text} 1986. What a great season. \\\\end{CodeBlock} In other words, a \\\\textit{number-period-space} sequence at the beginning of a line. To avoid this, you can backslash-escape the period: \\\\begin{CodeBlock}{text} 1986\\\\. What a great season. \\\\end{CodeBlock}

Code Blocks

Pre-formatted code blocks are used for writing about programming or markup source code. Rather than forming normal paragraphs, the lines of a code block are interpreted literally. Markdown wraps a code block in both \\\\texttt{
} and \\\\texttt{} tags.



To produce a code block in Markdown, simply indent every line of the
block by at least 4 spaces or 1 tab. For example, given this input:



\\\\begin{CodeBlock}{text}
This is a normal paragraph:

    This is a code block.
\\\\end{CodeBlock}



Markdown will generate:



\\\\begin{CodeBlock}{text}

This is a normal paragraph:

This is a code block.
\\\\end{CodeBlock} One level of indentation -- 4 spaces or 1 tab -- is removed from each line of the code block. For example, this: \\\\begin{CodeBlock}{text} Here is an example of AppleScript: tell application \\"Foo\\" beep end tell \\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}

Here is an example of AppleScript:

tell application \\"Foo\\"
    beep
end tell
\\\\end{CodeBlock} A code block continues until it reaches a line that is not indented (or the end of the article). Within a code block, ampersands (\\\\texttt{\\\\&}) and angle brackets (\\\\texttt{<} and \\\\texttt{>}) are automatically converted into HTML entities. This makes it very easy to include example HTML source code using Markdown -- just paste it and indent it, and Markdown will handle the hassle of encoding the ampersands and angle brackets. For example, this: \\\\begin{CodeBlock}{text}
© 2004 Foo Corporation
\\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}
<div class=\\"footer\\">
    &copy; 2004 Foo Corporation
</div>
\\\\end{CodeBlock} Regular Markdown syntax is not processed within code blocks. E.g., asterisks are just literal asterisks within a code block. This means it's also easy to use Markdown to write about Markdown's own syntax.

Horizontal Rules

You can produce a horizontal rule tag (\\\\texttt{
}) by placing three or more hyphens, asterisks, or underscores on a line by themselves. If you wish, you may use spaces between the hyphens or asterisks. Each of the following lines will produce a horizontal rule: \\\\begin{CodeBlock}{text} * * * *** ***** - - - --------------------------------------- _ _ _ \\\\end{CodeBlock} \\\\horizontalLine

Span Elements

Links

Markdown supports two style of links: \\\\textit{inline} and \\\\textit{reference}. In both styles, the link text is delimited by [square brackets]. To create an inline link, use a set of regular parentheses immediately after the link text's closing square bracket. Inside the parentheses, put the URL where you want the link to point, along with an \\\\textit{optional} title for the link, surrounded in quotes. For example: \\\\begin{CodeBlock}{text} This is [an example](http://example.com/ \\"Title\\") inline link. [This link](http://example.net/) has no title attribute. \\\\end{CodeBlock} Will produce: \\\\begin{CodeBlock}{text}

This is an example inline link.

This link has no title attribute.

\\\\end{CodeBlock} If you're referring to a local resource on the same server, you can use relative paths: \\\\begin{CodeBlock}{text} See my [About](/about/) page for details. \\\\end{CodeBlock} Reference-style links use a second set of square brackets, inside which you place a label of your choosing to identify the link: \\\\begin{CodeBlock}{text} This is [an example][id] reference-style link. \\\\end{CodeBlock} You can optionally use a space to separate the sets of brackets: \\\\begin{CodeBlock}{text} This is [an example] [id] reference-style link. \\\\end{CodeBlock} Then, anywhere in the document, you define your link label like this, on a line by itself: \\\\begin{CodeBlock}{text} [id]: http://example.com/ \\"Optional Title Here\\" \\\\end{CodeBlock} That is: \\\\begin{itemize} \\\\item\\\\relax Square brackets containing the link identifier (optionally indented from the left margin using up to three spaces); \\\\item\\\\relax followed by a colon; \\\\item\\\\relax followed by one or more spaces (or tabs); \\\\item\\\\relax followed by the URL for the link; \\\\item\\\\relax optionally followed by a title attribute for the link, enclosed in double or single quotes. \\\\end{itemize} The link URL may, optionally, be surrounded by angle brackets: \\\\begin{CodeBlock}{text} [id]: \\"Optional Title Here\\" \\\\end{CodeBlock} You can put the title attribute on the next line and use extra spaces or tabs for padding, which tends to look better with longer URLs: \\\\begin{CodeBlock}{text} [id]: http://example.com/longish/path/to/resource/here \\"Optional Title Here\\" \\\\end{CodeBlock} Link definitions are only used for creating links during Markdown processing, and are stripped from your document in the HTML output. Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are \\\\textit{not} case sensitive. E.g. these two links: \\\\begin{CodeBlock}{text} [link text][a] [link text][A] \\\\end{CodeBlock} are equivalent. The \\\\textit{implicit link name} shortcut allows you to omit the name of the link, in which case the link text itself is used as the name. Just use an empty set of square brackets -- e.g., to link the word \\"Google\\" to the google.com web site, you could simply write: \\\\begin{CodeBlock}{text} [Google][] \\\\end{CodeBlock} And then define the link: \\\\begin{CodeBlock}{text} [Google]: http://google.com/ \\\\end{CodeBlock} Because link names may contain spaces, this shortcut even works for multiple words in the link text: \\\\begin{CodeBlock}{text} Visit [Daring Fireball][] for more information. \\\\end{CodeBlock} And then define the link: \\\\begin{CodeBlock}{text} [Daring Fireball]: http://daringfireball.net/ \\\\end{CodeBlock} Link definitions can be placed anywhere in your Markdown document. I tend to put them immediately after each paragraph in which they're used, but if you want, you can put them all at the end of your document, sort of like footnotes. Here's an example of reference links in action: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]. [1]: http://google.com/ \\"Google\\" [2]: http://search.yahoo.com/ \\"Yahoo Search\\" [3]: http://search.msn.com/ \\"MSN Search\\" \\\\end{CodeBlock} Using the implicit link name shortcut, you could instead write: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google][] than from [Yahoo][] or [MSN][]. [google]: http://google.com/ \\"Google\\" [yahoo]: http://search.yahoo.com/ \\"Yahoo Search\\" [msn]: http://search.msn.com/ \\"MSN Search\\" \\\\end{CodeBlock} Both of the above examples will produce the following HTML output: \\\\begin{CodeBlock}{text}

I get 10 times more traffic from Google than from Yahoo or MSN.

\\\\end{CodeBlock} For comparison, here is the same paragraph written using Markdown's inline link style: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google](http://google.com/ \\"Google\\") than from [Yahoo](http://search.yahoo.com/ \\"Yahoo Search\\") or [MSN](http://search.msn.com/ \\"MSN Search\\"). \\\\end{CodeBlock} The point of reference-style links is not that they're easier to write. The point is that with reference-style links, your document source is vastly more readable. Compare the above examples: using reference-style links, the paragraph itself is only 81 characters long; with inline-style links, it's 176 characters; and as raw HTML, it's 234 characters. In the raw HTML, there's more markup than there is text. With Markdown's reference-style links, a source document much more closely resembles the final output, as rendered in a browser. By allowing you to move the markup-related metadata out of the paragraph, you can add links without interrupting the narrative flow of your prose.

Emphasis

Markdown treats asterisks (\\\\texttt{*}) and underscores (\\\\texttt{\\\\_}) as indicators of emphasis. Text wrapped with one \\\\texttt{*} or \\\\texttt{\\\\_} will be wrapped with an HTML \\\\texttt{} tag; double \\\\texttt{*}'s or \\\\texttt{\\\\_}'s will be wrapped with an HTML \\\\texttt{} tag. E.g., this input: \\\\begin{CodeBlock}{text} *single asterisks* _single underscores_ **double asterisks** __double underscores__ \\\\end{CodeBlock} will produce: \\\\begin{CodeBlock}{text} single asterisks single underscores double asterisks double underscores \\\\end{CodeBlock} You can use whichever style you prefer; the lone restriction is that the same character must be used to open and close an emphasis span. Emphasis can be used in the middle of a word: \\\\begin{CodeBlock}{text} un*fucking*believable \\\\end{CodeBlock} But if you surround an \\\\texttt{*} or \\\\texttt{\\\\_} with spaces, it'll be treated as a literal asterisk or underscore. To produce a literal asterisk or underscore at a position where it would otherwise be used as an emphasis delimiter, you can backslash escape it: \\\\begin{CodeBlock}{text} \\\\*this text is surrounded by literal asterisks\\\\* \\\\end{CodeBlock}

Code

To indicate a span of code, wrap it with backtick quotes (\\\\texttt{\`}). Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example: \\\\begin{CodeBlock}{text} Use the \`printf()\` function. \\\\end{CodeBlock} will produce: \\\\begin{CodeBlock}{text}

Use the printf() function.

\\\\end{CodeBlock} To include a literal backtick character within a code span, you can use multiple backticks as the opening and closing delimiters: \\\\begin{CodeBlock}{text} \`\`There is a literal backtick (\`) here.\`\` \\\\end{CodeBlock} which will produce this: \\\\begin{CodeBlock}{text}

There is a literal backtick (\`) here.

\\\\end{CodeBlock} The backtick delimiters surrounding a code span may include spaces -- one after the opening, one before the closing. This allows you to place literal backtick characters at the beginning or end of a code span: \\\\begin{CodeBlock}{text} A single backtick in a code span: \`\` \` \`\` A backtick-delimited string in a code span: \`\` \`foo\` \`\` \\\\end{CodeBlock} will produce: \\\\begin{CodeBlock}{text}

A single backtick in a code span: \`

A backtick-delimited string in a code span: \`foo\`

\\\\end{CodeBlock} With a code span, ampersands and angle brackets are encoded as HTML entities automatically, which makes it easy to include example HTML tags. Markdown will turn this: \\\\begin{CodeBlock}{text} Please don't use any \`\` tags. \\\\end{CodeBlock} into: \\\\begin{CodeBlock}{text}

Please don't use any <blink> tags.

\\\\end{CodeBlock} You can write this: \\\\begin{CodeBlock}{text} \`—\` is the decimal-encoded equivalent of \`—\`. \\\\end{CodeBlock} to produce: \\\\begin{CodeBlock}{text}

&#8212; is the decimal-encoded equivalent of &mdash;.

\\\\end{CodeBlock}

Images

Admittedly, it's fairly difficult to devise a \\"natural\\" syntax for placing images into a plain text document format. Markdown uses an image syntax that is intended to resemble the syntax for links, allowing for two styles: \\\\textit{inline} and \\\\textit{reference}. Inline image syntax looks like this: \\\\begin{CodeBlock}{text} ![Alt text](/path/to/img.jpg) ![Alt text](/path/to/img.jpg \\"Optional title\\") \\\\end{CodeBlock} That is: \\\\begin{itemize} \\\\item\\\\relax An exclamation mark: \\\\texttt{!}; \\\\item\\\\relax followed by a set of square brackets, containing the \\\\texttt{alt} attribute text for the image; \\\\item\\\\relax followed by a set of parentheses, containing the URL or path to the image, and an optional \\\\texttt{title} attribute enclosed in double or single quotes. \\\\end{itemize} Reference-style image syntax looks like this: \\\\begin{CodeBlock}{text} ![Alt text][id] \\\\end{CodeBlock} Where \\"id\\" is the name of a defined image reference. Image references are defined using syntax identical to link references: \\\\begin{CodeBlock}{text} [id]: url/to/image \\"Optional title attribute\\" \\\\end{CodeBlock} As of this writing, Markdown has no syntax for specifying the dimensions of an image; if this is important to you, you can simply use regular HTML \\\\texttt{} tags. \\\\horizontalLine

Miscellaneous

Automatic Links

Markdown supports a shortcut style for creating \\"automatic\\" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} Markdown will turn this into: \\\\begin{CodeBlock}{text} http://example.com/ \\\\end{CodeBlock} Automatic links for email addresses work similarly, except that Markdown will also perform a bit of randomized decimal and hex entity-encoding to help obscure your address from address-harvesting spambots. For example, Markdown will turn this: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} into something like this: \\\\begin{CodeBlock}{text} address@exa mple.com \\\\end{CodeBlock} which will render in a browser as a clickable link to \\"\\\\externalLink{address@example.com}{mailto:address@example.com}\\". (This sort of entity-encoding trick will indeed fool many, if not most, address-harvesting bots, but it definitely won't fool all of them. It's better than nothing, but an address published in this way will probably eventually start receiving spam.)

Backslash Escapes

Markdown allows you to use backslash escapes to generate literal characters which would otherwise have special meaning in Markdown's formatting syntax. For example, if you wanted to surround a word with literal asterisks (instead of an HTML \\\\texttt{} tag), you can backslashes before the asterisks, like this: \\\\begin{CodeBlock}{text} \\\\*literal asterisks\\\\* \\\\end{CodeBlock} Markdown provides backslash escapes for the following characters: \\\\begin{CodeBlock}{text} \\\\ backslash \` backtick * asterisk _ underscore {} curly braces [] square brackets () parentheses # hash mark + plus sign - minus sign (hyphen) . dot ! exclamation mark \\\\end{CodeBlock}" `; exports[`rebber: remark specs mixed-indentation: mixed-indentation 1`] = ` "\\\\part{Mixed spaces and tabs} \\\\begin{itemize} \\\\item\\\\relax Very long paragraph \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax Very long paragraph \\\\end{enumerate} \\\\begin{itemize} \\\\item\\\\relax Very long paragraph \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax Very long paragraph \\\\end{enumerate}" `; exports[`rebber: remark specs nested-blockquotes: nested-blockquotes 1`] = ` "\\\\begin{Quotation} foo \\\\begin{Quotation} bar \\\\end{Quotation} foo \\\\end{Quotation}" `; exports[`rebber: remark specs nested-code: nested-code 1`] = ` "\\\\texttt{hi ther \`\` ok \`\`\`} \\\\texttt{\`hi ther\`}" `; exports[`rebber: remark specs nested-em: nested-em 1`] = ` "\\\\textit{test \\\\textbf{test} test} \\\\textit{test \\\\textbf{test} test}" `; exports[`rebber: remark specs nested-references: nested-references 1`] = ` "This nested image should work: [\\\\includegraphics{undefined}] This nested link should not work: [[Foo][bar]]" `; exports[`rebber: remark specs nested-square-link: nested-square-link 1`] = ` "[the \`]\` character](/url) [the \\\\texttt{[} character](/url) [the \`\` \\\\externalLink{ \`\`\` character}{/url} \\\\externalLink{the \\\\texttt{\`} character}{/url}" `; exports[`rebber: remark specs no-positionals: no-positionals 1`] = ` "This document tests for the working of \\\\texttt{position: false} as a parse option. \\\\begin{Quotation} Block-quotes \\\\begin{itemize} \\\\item\\\\relax With list items. \\\\end{itemize} \\\\end{Quotation} Another block-quote: \\\\begin{Quotation} \\\\begin{enumerate} \\\\item\\\\relax And another list. \\\\end{enumerate} \\\\end{Quotation} Some \\\\externalLink{deeply \\\\textbf{nested \\\\textit{elements}}}{http://example.com} An entity: ©, and an warning entity: ©." `; exports[`rebber: remark specs not-a-link: not-a-link 1`] = `"[test](not a link)"`; exports[`rebber: remark specs ordered-and-unordered-lists: ordered-and-unordered-lists 1`] = ` "\\\\chapter{Unordered} Asterisks tight: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} Asterisks loose: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\horizontalLine Pluses tight: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} Pluses loose: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} \\\\horizontalLine Minuses tight: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} Minuses loose: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} \\\\chapter{Ordered} Tight: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Loose using tabs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and using spaces: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Multiple paragraphs: \\\\begin{enumerate} \\\\item\\\\relax Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. \\\\item\\\\relax Item 2. \\\\item\\\\relax Item 3. \\\\end{enumerate} \\\\chapter{Nested} \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} Here's another: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate} Same thing but with paragraphs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate} This was an error in Markdown 1.0.1: \\\\begin{itemize} \\\\item\\\\relax this \\\\begin{itemize} \\\\item\\\\relax sub \\\\end{itemize} that \\\\end{itemize}" `; exports[`rebber: remark specs ordered-different-types: ordered-different-types 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo \\\\item\\\\relax bar 3) baz \\\\end{enumerate}" `; exports[`rebber: remark specs ordered-with-parentheses: ordered-with-parentheses 1`] = ` "\\\\chapter{Ordered} Tight: 1) First 2) Second 3) Third and: 1) One 2) Two 3) Three Loose using tabs: 1) First 2) Second 3) Third and using spaces: 1) One 2) Two 3) Three Multiple paragraphs: 1) Item 1, graf one. \\\\begin{CodeBlock}{text} Item 2. graf two. The quick brown fox jumped over the lazy dog's back. \\\\end{CodeBlock} 2) Item 2. 3) Item 3." `; exports[`rebber: remark specs paragraphs-and-indentation: paragraphs-and-indentation 1`] = ` "\\\\part{Without lines.} This is a paragraph and this is further text This is a paragraph and this is further text This is a paragraph with some asterisks \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} This is a paragraph followed by a horizontal rule \\\\horizontalLine \\\\part{With lines.} This is a paragraph \\\\begin{CodeBlock}{text} and this is code \\\\end{CodeBlock} This is a paragraph and this is a new paragraph This is a paragraph with some asterisks in a code block \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} This is a paragraph followed by a horizontal rule \\\\horizontalLine" `; exports[`rebber: remark specs paragraphs-empty: paragraphs-empty 1`] = ` "aaa \\\\part{aaa} bbb ccc" `; exports[`rebber: remark specs ref-paren: ref-paren 1`] = ` "\\\\hyperref[hi]{hi} \\\\footnote{\\\\label{hi}\\\\externalLink{/url}{/url}}" `; exports[`rebber: remark specs reference-image-empty-alt: reference-image-empty-alt 1`] = ` "\\\\includegraphics{/xyz.png} \\\\footnote{\\\\label{1}\\\\externalLink{/xyz.png}{/xyz.png}}" `; exports[`rebber: remark specs reference-link-escape: reference-link-escape 1`] = ` "[b*r*], \\\\hyperref[b\\\\*r*]{b*r*}, \\\\hyperref[b\\\\*r*]{b*r*}. \\\\includegraphics{http://google.com}, \\\\includegraphics{http://google.com}, \\\\includegraphics{http://google.com}. \\\\footnote{\\\\label{b\\\\*r*}\\\\externalLink{http://google.com}{http://google.com}}" `; exports[`rebber: remark specs reference-link-not-closed: reference-link-not-closed 1`] = ` "[bar]bar [bar] [bar]" `; exports[`rebber: remark specs reference-link-with-angle-brackets: reference-link-with-angle-brackets 1`] = ` "\\\\hyperref[foo]{foo} \\\\footnote{\\\\label{foo}\\\\externalLink{./url with spaces}{./url with spaces}}" `; exports[`rebber: remark specs reference-link-with-multiple-definitions: reference-link-with-multiple-definitions 1`] = ` "\\\\hyperref[foo]{foo} \\\\footnote{\\\\label{foo}\\\\externalLink{first}{first}} \\\\footnote{\\\\label{foo-1}\\\\externalLink{second}{second}}" `; exports[`rebber: remark specs same-bullet: same-bullet 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax test \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax test \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax test \\\\end{itemize}" `; exports[`rebber: remark specs stringify-escape: stringify-escape 1`] = ` "Characters that should be escaped in general: \\\\textbackslash{} \` * [ Characters that shouldn't: \\\\{\\\\}]()\\\\#+-.!>\\"\\\\$\\\\%',/:;=?@\\\\textasciicircum{}\\\\textasciitilde{} Underscores are \\\\_escaped\\\\_ unless they appear in\\\\_the\\\\_middle\\\\_of\\\\_a\\\\_word. or \\\\textbf{\\\\_here}, or here\\\\_\\\\_ Ampersands are escaped only when they would otherwise start an entity: \\\\begin{itemize} \\\\item\\\\relax \\\\textbackslash{}©cat \\\\textbackslash{}\\\\& \\\\textbackslash{}\\\\& \\\\item\\\\relax \\\\©cat \\\\& \\\\&\\\\#x26 \\\\item\\\\relax But: ©cat; \\\\texttt{\\\\≬} \\\\&foo; \\\\& AT\\\\&T \\\\&c \\\\end{itemize} Open parenthesis should be escaped after a shortcut reference: [ref](text) And after a shortcut reference and a space (for GitHub): [ref] (text) Hyphen should be escaped at the beginning of a line: - not a list item - not a list item + not a list item Same for angle brackets: > not a block quote And hash signs: \\\\# not a heading \\\\#\\\\# not a subheading Text under a shortcut reference should be preserved verbatim: \\\\begin{itemize} \\\\item\\\\relax [two*three] \\\\item\\\\relax [two*three] \\\\item\\\\relax [a\\\\textbackslash{}a] \\\\item\\\\relax [a\\\\textbackslash{}a] \\\\item\\\\relax [a\\\\textbackslash{}\\\\textbackslash{}a] \\\\item\\\\relax [a\\\\_a\\\\_a] \\\\end{itemize} \\\\textbf{GFM:} Colon should be escaped in URLs: \\\\begin{itemize} \\\\item\\\\relax http\\\\textbackslash{}://user:password@host:port/path?key=value\\\\#fragment \\\\item\\\\relax https\\\\textbackslash{}://user:password@host:port/path?key=value\\\\#fragment \\\\item\\\\relax http://user:password@host:port/path?key=value\\\\#fragment \\\\item\\\\relax https://user:password@host:port/path?key=value\\\\#fragment \\\\end{itemize} Double tildes should be \\\\textasciitilde{}\\\\textasciitilde{}escaped\\\\textasciitilde{}\\\\textasciitilde{}. And here: foo\\\\textasciitilde{}\\\\textasciitilde{}. Pipes should not be escaped here: | \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} here & they \\\\\\\\ should & tho|ugh \\\\\\\\ \\\\end{longtblr} And here: | here | they | | ---- | ----- | | should | though | And here: here | they ---- | ------ should | though \\\\textbf{Commonmark:} Open angle bracket should be escaped: \\\\begin{itemize} \\\\item\\\\relax \\\\textbackslash{}
\\\\textbackslash{}
\\\\item\\\\relax \\\\textbackslash{} \\\\item\\\\relax
\\\\item\\\\relax \\\\end{itemize}" `; exports[`rebber: remark specs strong-and-em-together-one: strong-and-em-together-one 1`] = ` "\\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word. \\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word." `; exports[`rebber: remark specs strong-and-em-together-two: strong-and-em-together-two 1`] = ` "perform\\\\_complicated\\\\_task do\\\\_this\\\\_and\\\\_do\\\\_that\\\\_and\\\\_another\\\\_thing perform\\\\textit{complicated}task do\\\\textit{this}and\\\\textit{do}that\\\\textit{and}another*thing" `; exports[`rebber: remark specs strong-emphasis: strong-emphasis 1`] = ` "Foo \\\\textbf{bar} \\\\textbf{baz}. Foo \\\\textbf{bar} \\\\textbf{baz}." `; exports[`rebber: remark specs strong-initial-white-space: strong-initial-white-space 1`] = ` "\\\\textbf{ bar }. \\\\textbf{ bar }." `; exports[`rebber: remark specs table: table 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Heading 1 & \\\\textbf{H}eading 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 & Header 3 & Header 4 \\\\\\\\ Cell 1 & Cell 2 & Cell 3 & Cell 4 \\\\\\\\ Cell 5 & Cell 6 & Cell 7 & Cell 8 \\\\\\\\ \\\\end{longtblr} \\\\begin{CodeBlock}{text} Test code \\\\end{CodeBlock} \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 & Header 3 & Header 4 \\\\\\\\ Cell 1 & Cell 2 & Cell 3 & Cell 4 \\\\\\\\ \\\\textit{Cell 5} & Cell 6 & Cell 7 & Cell 8 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-empty-initial-cell: table-empty-initial-cell 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} & a & c \\\\\\\\ a & b & c \\\\\\\\ a & b & c \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-escaped-pipes: table-escaped-pipes 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} First & Second & third \\\\\\\\ first & second & third \\\\\\\\ first & second | second & third | \\\\\\\\ first & second \\\\textbackslash{} & third \\\\textbackslash{} \\\\\\\\ first & second \\\\textbackslash{}| second & third \\\\textbackslash{}| \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-in-list: table-in-list 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Unordered: \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr} \\\\item\\\\relax Ordered: \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr} \\\\end{itemize}" `; exports[`rebber: remark specs table-invalid-alignment: table-invalid-alignment 1`] = ` "Missing alignment characters: | a | b | c | | |---|---| | d | e | f | \\\\horizontalLine | a | b | c | |---|---| | | d | e | f | Invalid characters: | a | b | c | |---|-*-|---| | d | e | f |" `; exports[`rebber: remark specs table-loose: table-loose 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-no-body: table-no-body 1`] = ` "\\\\part{Foo} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Name & GitHub & Twitter \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-no-end-of-line: table-no-end-of-line 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} foo & bar \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-one-column: table-one-column 1`] = ` "This is a table: \\\\begin{longtblr}{colspec={X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} a \\\\\\\\ b \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-one-row: table-one-row 1`] = ` "This is a table: \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} a & b & c \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-padded: table-padded 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-pipes-in-code: table-pipes-in-code 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} abc & head2 \\\\\\\\ x & \` & & & \` \\\\\\\\ x & \` \\\\\\\\ x & \` & \` \\\\\\\\ x & \\\\texttt{f} \\\\\\\\ x & \`\`\`\` \\\\\\\\ x & \`\\\\texttt{f} \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} abc & head2 \\\\\\\\ x & \` \\\\\\\\ x & \` & \` \\\\\\\\ x & \\\\texttt{f} \\\\\\\\ x & \`\`\`\` \\\\\\\\ x & \`\\\\texttt{f} \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-spaced: table-spaced 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs table-with-image: table-with-image 1`] = ` "Someone wanted to do this, let's implement it! \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} c1 & c2 \\\\\\\\ c3 & \\\\includegraphics{https://zestedesavoir.com/media/galleries/426/56dc4a1e-416b-4a9d-830d-95b45d58a17a.png} \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs tabs: tabs 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax this is a list item indented with tabs \\\\item\\\\relax this is a list item indented with spaces \\\\end{itemize} Code: \\\\begin{CodeBlock}{text} this code block is indented by one tab \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} this code block is indented by two tabs \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} + this is an example list item indented with tabs + this is an example list item indented with spaces \\\\end{CodeBlock}" `; exports[`rebber: remark specs tabs-and-spaces: tabs-and-spaces 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax this is a list item indented with tabs \\\\item\\\\relax this is a list item indented with spaces \\\\end{itemize} Code: \\\\begin{CodeBlock}{text} this code block is indented by one tab \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} this code block is indented by two tabs \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} + this is an example list item indented with tabs + this is an example list item indented with spaces \\\\end{CodeBlock}" `; exports[`rebber: remark specs task-list: task-list 1`] = ` "\\\\part{Empty items} \\\\begin{itemize} \\\\item\\\\relax [ ] \\\\item\\\\relax [ ] \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [x] \\\\item\\\\relax [X] \\\\end{enumerate} \\\\part{Single space} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\item[$\\\\square$]\\\\relax \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\item[$\\\\boxtimes$]\\\\relax \\\\end{enumerate} \\\\part{Tab} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\item[$\\\\square$]\\\\relax \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\item[$\\\\boxtimes$]\\\\relax \\\\end{enumerate} \\\\part{No white space with content} \\\\begin{itemize} \\\\item\\\\relax [ ]Hello; \\\\item\\\\relax [ ]World; \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [x]Foo. \\\\item\\\\relax [X]Bar \\\\end{enumerate} \\\\part{Single space with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Hello; \\\\item[$\\\\square$]\\\\relax World; \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax World :D \\\\end{enumerate} \\\\part{Single tab with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Hello; \\\\item[$\\\\square$]\\\\relax World; \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax Hello. \\\\end{enumerate} \\\\part{Multiple spaces with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} World; \\\\end{CodeBlock} \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax Bar. \\\\end{enumerate} \\\\part{Multiple tabs with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} World; \\\\end{CodeBlock} \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\begin{CodeBlock}{text} Foo. \\\\end{CodeBlock} \\\\item[$\\\\boxtimes$]\\\\relax \\\\begin{CodeBlock}{text} Bar. \\\\end{CodeBlock} \\\\end{enumerate} \\\\part{Mixed tabs and spaces} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\begin{CodeBlock}{text} World; \\\\end{CodeBlock} \\\\end{enumerate} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\item[$\\\\square$]\\\\relax World. \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Bar. \\\\end{enumerate} \\\\part{Line breaks} \\\\begin{itemize} \\\\item\\\\relax [ ] Hello; \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [ ] Hello; \\\\end{enumerate} \\\\part{Multiple unfinished characters} \\\\begin{itemize} \\\\item\\\\relax [ ] Hello; \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [ ] World; \\\\item\\\\relax [ ] Hello; \\\\item\\\\relax [ ] World. \\\\end{enumerate}" `; exports[`rebber: remark specs task-list-ordered: task-list-ordered 1`] = ` "\\\\begin{enumerate} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{enumerate} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{enumerate}" `; exports[`rebber: remark specs task-list-unordered-asterisk: task-list-unordered-asterisk 1`] = ` "\\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{itemize} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{itemize} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{itemize}" `; exports[`rebber: remark specs task-list-unordered-dash: task-list-unordered-dash 1`] = ` "\\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{itemize} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{itemize} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{itemize}" `; exports[`rebber: remark specs task-list-unordered-plus: task-list-unordered-plus 1`] = ` "\\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{itemize} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{itemize} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{itemize}" `; exports[`rebber: remark specs tidyness: tidyness 1`] = ` "\\\\begin{Quotation} A list within a blockquote: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\end{Quotation}" `; exports[`rebber: remark specs title-attributes: title-attributes 1`] = ` "\\\\part{Links} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Implementation & Characters & Nested & Mismatched & Escaped & Named Entities & Numbered Entities \\\\\\\\ Markdown.pl & \\\\texttt{\\"} & Yes & Yes & No & Yes & Yes \\\\\\\\ GitHub & \\\\texttt{\\"} & Yes & Yes & No & No & No \\\\\\\\ CommonMark & \\\\texttt{\\"} & No & No & Yes & Yes & Yes \\\\\\\\ Markdown.pl & \\\\texttt{'} & Yes & Yes & No & Yes & Yes \\\\\\\\ GitHub & \\\\texttt{'} & Yes & Yes & No & No & No \\\\\\\\ CommonMark & \\\\texttt{'} & No & No & Yes & Yes & Yes \\\\\\\\ Markdown.pl & \\\\texttt{()} & - & - & - & - & - \\\\\\\\ GitHub & \\\\texttt{()} & - & - & - & - & - \\\\\\\\ CommonMark & \\\\texttt{()} & No & Yes & Yes & Yes & Yes \\\\\\\\ \\\\end{longtblr} \\\\chapter{Double quotes} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\chapter{Single quotes} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\part{Images} \\\\chapter{Double quotes} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\chapter{Single quotes} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png}" `; exports[`rebber: remark specs toplevel-paragraphs: toplevel-paragraphs 1`] = ` "hello world how are you how are you hello world \\\\begin{CodeBlock}{text} how are you \\\\end{CodeBlock} hello world \\\\horizontalLine hello world \\\\part{how are you} hello world \\\\part{how are you} hello world \\\\begin{Quotation} how are you \\\\end{Quotation} hello world \\\\begin{itemize} \\\\item\\\\relax how are you \\\\end{itemize} hello world
how are you
hello world how are you hello \\\\hyperref[how]{world} \\\\footnote{\\\\label{how}\\\\externalLink{/are/you}{/are/you}}
hello
hello" `; exports[`rebber: remark specs tricky-list: tricky-list 1`] = ` "\\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax hello world \\\\end{itemize} \\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax hello world \\\\end{itemize} \\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax Hello world \\\\end{itemize} \\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax hello world \\\\end{itemize}" `; exports[`rebber: remark specs with config: custom macros amps-and-angles-encoding 1`] = ` "AT\\\\&T has an ampersand in their name. AT\\\\&T is another way to write it. This \\\\& that. 4 < 5. 6 > 5. Here's a [linkReference(reference=1, content=link)] with an ampersand in the URL. Here's a link with an amersand in the link text: [linkReference(reference=2, content=AT\\\\&T)]. Here's an inline \\\\externalLink{link}{/script?foo=1\\\\&bar=2}. Here's an inline \\\\externalLink{link}{/script?foo=1\\\\&bar=2}. [definition(identifier=1, url=http://example.com/?foo=1&bar=2, title=null)] [definition(identifier=2, url=http://att.com/, title=AT&T)]" `; exports[`rebber: remark specs with config: custom macros auto-link 1`] = ` "Link: \\\\externalLink{http://example.com/}{http://example.com/}. Link to an email: \\\\externalLink{somename@example.com}{mailto:somename@example.com}. Link to an email: \\\\externalLink{somename@example.com}{mailto:somename@example.com}. With an ampersand: \\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2} [unorderedList([listItem(In a list?)][listItem(\\\\externalLink{http://example.com/}{http://example.com/})][listItem(It should.)])] [blockquote(Blockquoted: \\\\externalLink{http://example.com/}{http://example.com/})] Auto-links should not occur here: \\\\texttt{} [code(or here: )]" `; exports[`rebber: remark specs with config: custom macros auto-link-invalid 1`] = ` " Hash: \\\\# Period: . Bang: ! Plus: + Minus: - \\\\textbf{GFM:} Pipe: | Tilde: \\\\textasciitilde{} \\\\textbf{Commonmark:} Quote: \\\\textbackslash{}\\" Dollar: \\\\textbackslash{}\\\\$ Percentage: \\\\textbackslash{}\\\\% Ampersand: \\\\textbackslash{}\\\\& Single quote: \\\\textbackslash{}' Comma: \\\\textbackslash{}, Forward slash: \\\\textbackslash{}/ Colon: \\\\textbackslash{}: Semicolon: \\\\textbackslash{}; Less-than: \\\\textbackslash{}< Equals: \\\\textbackslash{}= Question mark: \\\\textbackslash{}? At-sign: \\\\textbackslash{}@ Caret: \\\\textbackslash{}\\\\textasciicircum{} New line: \\\\textbackslash{} only works in paragraphs. These should not, because they occur within a code block: [code(Backslash: \\\\\\\\ Backtick: \\\\\` Asterisk: \\\\* Underscore: \\\\_ Left brace: \\\\{ Right brace: \\\\} Left bracket: \\\\[ Right bracket: \\\\] Left paren: \\\\( Right paren: \\\\) Greater-than: \\\\> Hash: \\\\# Period: \\\\. Bang: \\\\! Plus: \\\\+ Minus: \\\\-)] \\\\textbf{GFM:} [code(Pipe: \\\\| Tilde: \\\\~)] \\\\textbf{Commonmark:} [code(Quote: \\\\\\" Dollar: \\\\$ Percentage: \\\\% Ampersand: \\\\& Single quote: \\\\' Comma: \\\\, Forward slash: \\\\/ Colon: \\\\: Semicolon: \\\\; Less-than: \\\\< Equals: \\\\= Question mark: \\\\? At-sign: \\\\@ Caret: \\\\^ New line: \\\\ only works in paragraphs.)] Nor should these, which occur in code spans: Backslash: \\\\texttt{\\\\textbackslash{}\\\\textbackslash{}} Backtick: \\\\texttt{\\\\textbackslash{}\`} Asterisk: \\\\texttt{\\\\textbackslash{}*} Underscore: \\\\texttt{\\\\textbackslash{}\\\\_} Left brace: \\\\texttt{\\\\textbackslash{}\\\\{} Right brace: \\\\texttt{\\\\textbackslash{}\\\\}} Left bracket: \\\\texttt{\\\\textbackslash{}[} Right bracket: \\\\texttt{\\\\textbackslash{}]} Left paren: \\\\texttt{\\\\textbackslash{}(} Right paren: \\\\texttt{\\\\textbackslash{})} Greater-than: \\\\texttt{\\\\textbackslash{}>} Hash: \\\\texttt{\\\\textbackslash{}\\\\#} Period: \\\\texttt{\\\\textbackslash{}.} Bang: \\\\texttt{\\\\textbackslash{}!} Plus: \\\\texttt{\\\\textbackslash{}+} Minus: \\\\texttt{\\\\textbackslash{}-} \\\\textbf{GFM:} Pipe: \\\\texttt{\\\\textbackslash{}|} Tilde: \\\\texttt{\\\\textbackslash{}\\\\textasciitilde{}} \\\\textbf{Commonmark:} Quote: \\\\texttt{\\\\textbackslash{}\\"} Dollar: \\\\texttt{\\\\textbackslash{}\\\\$} Percentage: \\\\texttt{\\\\textbackslash{}\\\\%} Ampersand: \\\\texttt{\\\\textbackslash{}\\\\&} Single quote: \\\\texttt{\\\\textbackslash{}'} Comma: \\\\texttt{\\\\textbackslash{},} Forward slash: \\\\texttt{\\\\textbackslash{}/} Colon: \\\\texttt{\\\\textbackslash{}:} Semicolon: \\\\texttt{\\\\textbackslash{};} Less-than: \\\\texttt{\\\\textbackslash{}<} Equals: \\\\texttt{\\\\textbackslash{}=} Question mark: \\\\texttt{\\\\textbackslash{}?} At-sign: \\\\texttt{\\\\textbackslash{}@} Caret: \\\\texttt{\\\\textbackslash{}\\\\textasciicircum{}} New line: \\\\texttt{\\\\textbackslash{} } only works in paragraphs. These should get escaped, even though they're matching pairs for other Markdown constructs: *asterisks* \\\\_underscores\\\\_ \`backticks\` This is a code span with a literal backslash-backtick sequence: \\\\texttt{\\\\textbackslash{}\`} This is a tag with unescaped backticks bar. This is a tag with backslashes bar." `; exports[`rebber: remark specs with config: custom macros block-elements 1`] = ` "[unorderedList([listItem(Different lists should receive two newline characters between them.)])] [unorderedList([listItem(This is another list.)])] [blockquote([unorderedList([listItem(The same goes for lists in block quotes.)])][unorderedList([listItem(This is another list.)])])] [unorderedList([listItem(And for lists in lists: [orderedList([listItem(First sublist.)])])])] [code(1. Second sublist.)] And for lists followed by indented code blocks: [unorderedList([listItem(This is a paragraph in a list)])] [code(And this is code();)]" `; exports[`rebber: remark specs with config: custom macros blockquote-indented 1`] = ` "[blockquote(bar baz)]" `; exports[`rebber: remark specs with config: custom macros blockquote-lazy-code 1`] = ` "[blockquote([code(foo bar)])]" `; exports[`rebber: remark specs with config: custom macros blockquote-lazy-fence 1`] = ` "[blockquote([code(aNormalCodeBlockInABlockqoute();)])] A paragraph. [blockquote([code(thisIsAlsoSomeCodeInABlockquote();)])] A paragraph. [blockquote([code(aNonTerminatedCodeBlockInABlockquote();)]aNewCodeBlockFollowingTheBlockQuote(); [code()])] A paragraph. [blockquote(Something in a blockquote. [code(aNewCodeBlock();)])]" `; exports[`rebber: remark specs with config: custom macros blockquote-lazy-list 1`] = ` "[blockquote(This is a blockquote. [unorderedList([listItem(And in normal mode this is an internal list, but in commonmark this is a top level list.)])])]" `; exports[`rebber: remark specs with config: custom macros blockquote-lazy-rule 1`] = ` "[blockquote(This is a blockquote. Followed by a rule. [thematicBreak(---)])]" `; exports[`rebber: remark specs with config: custom macros blockquote-list-item 1`] = ` "This fails in markdown.pl and upskirt: [unorderedList([listItem(hello [blockquote(world)])])]" `; exports[`rebber: remark specs with config: custom macros blockquotes 1`] = ` "[blockquote(This is a blockquote.)] [blockquote(This is, in commonmark mode, another blockquote.)]" `; exports[`rebber: remark specs with config: custom macros blockquotes-empty-lines 1`] = ` "[blockquote(Note there is no space on the following line. Note there is no space on the preceding line.)]" `; exports[`rebber: remark specs with config: custom macros blockquotes-with-code-blocks 1`] = ` "[blockquote(Example: [code(sub status { print \\"working\\"; })]Or: [code(sub status { return \\"working\\"; })])]" `; exports[`rebber: remark specs with config: custom macros bom 1`] = ` "heading1(Hello from a BOM) Be careful when editing this file!" `; exports[`rebber: remark specs with config: custom macros breaks-hard 1`] = ` "These are not breaks: Look at the pretty line breaks. These are breaks: Look at the[break(---)]pretty line[break(---)]breaks. In \\\\texttt{commonmark: true} mode, an escaped newline character is exposed as a \\\\texttt{break} node: Look at the\\\\textbackslash{} pretty line\\\\textbackslash{} breaks." `; exports[`rebber: remark specs with config: custom macros case-insensitive-refs 1`] = ` "[linkReference(reference=hi, content=hi)] [definition(identifier=hi, url=/url, title=null)]" `; exports[`rebber: remark specs with config: custom macros code-block 1`] = ` "Tildes: [codeJavascript(alert('Hello World!');)]" `; exports[`rebber: remark specs with config: custom macros code-block-escape 1`] = ` "A little flaw: [codePython(\\\\end{CodeBlock} \\\\end {CodeBlock})] An ingenuous flaw: [code(\\\\end\\\\end{CodeBlock}{CodeBlock} \\\\input{/etc/passwd} \\\\begin{CodeBlock}{text})]" `; exports[`rebber: remark specs with config: custom macros code-block-indentation 1`] = ` "Fenced code blocks are normally not exdented, however, when the initial fence is indented by spaces, the value of the code is exdented by up to that amount of spaces. [code( This is a code block... ...which is not exdented.)] But... [code( This one... ...is.)] And... [code(So is this... ...one.)]" `; exports[`rebber: remark specs with config: custom macros code-block-nesting-bug 1`] = ` "GitHub, thus RedCarpet, has a bug where “nested” fenced code blocks, even with shorter fences, can exit their actual “parent” block. Note that this bug does not occur on indented code-blocks. [codeFoo(\`\`\`bar baz \`\`\`)] Even with a different fence marker: [codeFoo(~~~bar baz ~~~)] And reversed: [codeFoo(~~~bar baz ~~~)] [codeFoo(\`\`\`bar baz \`\`\`)]" `; exports[`rebber: remark specs with config: custom macros code-blocks 1`] = ` "code block on the first line Regular text. [code(code block indented by spaces)] Regular text. [code(the lines in this block all contain trailing spaces )] Regular Text. [code(code block on the last line)]" `; exports[`rebber: remark specs with config: custom macros code-spans 1`] = ` "\\\\texttt{} Fix for backticks within HTML tag: like this Here's how you put \\\\texttt{\`backticks\`} in a code span. Additionally, empty code spans are NOT supported: \`\`. Here’s an example, \\\\texttt{foo \` bar }. And here, \\\\texttt{\`\`}. \\\\texttt{// this is also inline code} So is this \\\\texttt{foo bar baz}. And this \\\\texttt{foo \`\` bar} And \\\\texttt{this\\\\textbackslash{}}but this is text\`." `; exports[`rebber: remark specs with config: custom macros def-blocks 1`] = ` "[blockquote(hello [definition(identifier=1, url=hello, title=null)])] [thematicBreak(---)] [blockquote(hello)] [definition(identifier=2, url=hello, title=null)] [unorderedList([listItem(hello)][listItem([definition(identifier=3, url=hello, title=null)])])] [unorderedList([listItem(hello)])] [definition(identifier=4, url=hello, title=null)] [blockquote(foo bar)] [definition(identifier=1-1, url=foo, title=null)] [blockquote(bar)]" `; exports[`rebber: remark specs with config: custom macros definition-newline 1`] = ` "[linkReference(reference=baz, content=baz)]: /url ( ) [foo]: /url \\" \\" [bar]: /url ' ' [definition(identifier=baz, url=/url, title=foo bar)] [definition(identifier=baz-1, url=/url, title=foo bar)] [definition(identifier=baz-1-1, url=/url, title=foo bar)] [linkReference(reference=baz, content=baz)]: /url 'foo" `; exports[`rebber: remark specs with config: custom macros definition-unclosed 1`] = ` "[foo]: [definition(identifier=bar, url=( [foo]: \\" [bar]: '" `; exports[`rebber: remark specs with config: custom macros deletion 1`] = `"hello \\\\sout{hi} world"`; exports[`rebber: remark specs with config: custom macros double-link 1`] = ` "

Already linked: http://example.com/.

Already linked: \\\\externalLink{http://example.com/}{http://example.com/}. Already linked: \\\\textbf{http://example.com/}." `; exports[`rebber: remark specs with config: custom macros emphasis 1`] = ` "\\\\textit{emphasis}. \\\\textbf{strong}." `; exports[`rebber: remark specs with config: custom macros emphasis-empty 1`] = ` "Hello ** ** world. Hello \\\\_\\\\_ \\\\_\\\\_ world. Hello * * world. Hello \\\\_ \\\\_ world." `; exports[`rebber: remark specs with config: custom macros emphasis-escaped-final-marker 1`] = ` "*bar* **bar** \\\\_bar\\\\_ \\\\_\\\\_bar\\\\_\\\\_" `; exports[`rebber: remark specs with config: custom macros emphasis-internal 1`] = `"These words should\\\\_not\\\\_be\\\\_emphasized."`; exports[`rebber: remark specs with config: custom macros empty 1`] = `""`; exports[`rebber: remark specs with config: custom macros entities 1`] = ` "Lots of entities are supported in mdast:  , \\\\&, ©, Æ, Ď, ¾, ℋ, ⅆ, ∲, \\\\&c. Even some entities with a missing terminal semicolon are parsed correctly (as per the HTML5 spec): ÿ, á, ©, and \\\\&. However, \\\\&MadeUpEntities; are kept in the document. Entities even work in the language flag of fenced code blocks: [codeSome—language(alert('Hello');)] Or in \\\\externalLink{línks}{\\\\textasciitilde{}/some—file} Or in \\\\includegraphics{~/an–image.png} But, entities are not interpreted in \\\\texttt{inline c\\\\öde}, or in code blocks: [code(CÖDE block.)]" `; exports[`rebber: remark specs with config: custom macros entities-advanced 1`] = ` "[blockquote(However, \\\\&MadeUpEntities; are kept in the document.)] [blockquote(Entities even work in the language flag of fenced code blocks:)] [blockquote([codeSome©language(alert('Hello');)])] [blockquote(And in an auto-link: \\\\externalLink{http://example©xample.com}{http://example\\\\©xample.com})] [blockquote(Foo and bar and http://example©xample.com and baz.)] [blockquote(Or in \\\\externalLink{l©nks}{\\\\textasciitilde{}/some\\\\©file})] [blockquote(Or in \\\\externalLink{l©lnks}{\\\\textasciitilde{}/some\\\\©file})] [blockquote(Or in \\\\includegraphics{~/some©file})] [thematicBreak(---)] [blockquote(Or in \\\\includegraphics{~/some©file})] [blockquote(Or in \\\\includegraphics{undefined})] [blockquote([definition(identifier=1, url=http://example©xample.com, title=in some pl©ce)])] [blockquote([definition(identifier= 1 , url=http://example©xample.com, title=in some pl©ce)])] [thematicBreak(---)] [blockquote(But, entities are not interpreted in \\\\texttt{inline c\\\\öde}, or in code blocks:)] [blockquote([code(CÖDE block.)])]" `; exports[`rebber: remark specs with config: custom macros escaped-angles 1`] = `">"`; exports[`rebber: remark specs with config: custom macros fenced-code 1`] = ` "[codeJs(var a = 'hello'; console.log(a + ' world');)] [codeBash(echo \\"hello, \${WORLD}\\")] [codeLongfence(Q: What do you call a tall person who sells stolen goods?)] [codeManyTildes(A longfence!)]" `; exports[`rebber: remark specs with config: custom macros fenced-code-empty 1`] = ` "Normal with language tag: [codeJs()] With white space: [codeBash()] With very long fences: [code()] With nothing: [code()]" `; exports[`rebber: remark specs with config: custom macros fenced-code-trailing-characters 1`] = ` "[codeJs(foo(); \`\`\`bash)]" `; exports[`rebber: remark specs with config: custom macros fenced-code-trailing-characters-2 1`] = `"[code(\`\`\` aaa)]"`; exports[`rebber: remark specs with config: custom macros fenced-code-white-space-after-flag 1`] = ` "[codeJs(foo();)] [codeBash(echo \\"hello, \${WORLD}\\")]" `; exports[`rebber: remark specs with config: custom macros hard-wrapped-paragraphs-with-list-like-lines 1`] = ` "In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the 123. middle of a paragraph looked like a list item. Here's one with a bullet. [unorderedList([listItem(criminey.)])] Non-GFM does not create a list for either. GFM does not create a list for \\\\texttt{8.}, but does for \\\\texttt{*}. CommonMark creates a list for both. All versions create lists for the following. [unorderedList([listItem(Here's one with a bullet. [unorderedList([listItem(criminey.)])])])] ...and the following: [orderedList([listItem(In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item.)])]" `; exports[`rebber: remark specs with config: custom macros heading 1`] = ` "heading1(Heading 1) heading2(Heading 2) heading3(Heading 4) heading4(Heading 4) heading5(Heading 5) heading6(Heading 6)" `; exports[`rebber: remark specs with config: custom macros heading-atx-closed-trailing-white-space 1`] = ` "heading1(Foo) heading2(Bar)" `; exports[`rebber: remark specs with config: custom macros heading-atx-empty 1`] = ` "heading1() heading2() heading3() heading4() heading5() heading6()" `; exports[`rebber: remark specs with config: custom macros heading-in-blockquote 1`] = ` "[blockquote(A blockquote with some more text.)] A normal paragraph. [blockquote(heading2(A blockquote followed by a horizontal rule (in CommonMark).))] [blockquote(heading2(A heading in a blockquote))]" `; exports[`rebber: remark specs with config: custom macros heading-in-paragraph 1`] = ` "Hello heading1(World)" `; exports[`rebber: remark specs with config: custom macros heading-not-atx 1`] = ` "\\\\#This is not a heading, per CommonMark: \\\\externalLink{http://spec.commonmark.org/0.17/\\\\#example-25}{http://spec.commonmark.org/0.17/\\\\#example-25} Kramdown (GitHub) neither supports unspaced ATX-headings. \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h7? \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h8? \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h9? More than six \\\\# characters is not a heading: \\\\externalLink{http://spec.commonmark.org/0.26/\\\\#example-33}{http://spec.commonmark.org/0.26/\\\\#example-33}" `; exports[`rebber: remark specs with config: custom macros heading-setext-with-initial-spacing 1`] = ` "heading1(Heading 1) heading2(Heading 2) Both these headings caused positional problems in on commit daa344c and before." `; exports[`rebber: remark specs with config: custom macros horizontal-rules 1`] = ` "Dashes: [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [code(---)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [code(- - -)] Asterisks: [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [code(***)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [code(* * *)] Underscores: [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [code(___)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] [code(_ _ _)]" `; exports[`rebber: remark specs with config: custom macros horizontal-rules-adjacent 1`] = ` "[thematicBreak(---)] [thematicBreak(---)] [thematicBreak(---)] The three asterisks are not a Setext header. This is a paragraph. [thematicBreak(---)] This is another paragraph. [thematicBreak(---)] heading2(But this is a secondary heading.) [thematicBreak(---)]" `; exports[`rebber: remark specs with config: custom macros hr 1`] = `"[thematicBreak(---)]"`; exports[`rebber: remark specs with config: custom macros hr-list-break 1`] = ` "[unorderedList([listItem(hello world)][listItem(how are)])] [thematicBreak(---)] you today? The above asterisks do split the list, but the below ones do not. [unorderedList([listItem(hello world)][listItem(how are)][listItem([thematicBreak(---)]you today?)])] [unorderedList([listItem(Neither do these)][listItem(how are)][listItem([unorderedList([listItem([unorderedList([listItem(you today?)])])])])])] [unorderedList([listItem(But these do)][listItem(how are)])] [thematicBreak(---)] you today?" `; exports[`rebber: remark specs with config: custom macros html-advanced 1`] = ` "Simple block on one line:
foo
And nested without indentation:
foo
\\"/>
bar
" `; exports[`rebber: remark specs with config: custom macros html-attributes 1`] = ` "heading1(Block-level)
foo " `; exports[`rebber: remark specs with config: custom macros html-comments 1`] = ` "Paragraph one. What follows is not an HTML comment because it contains two consecutive dashes: \\\\externalLink{https://html.spec.whatwg.org/multipage/syntax.html\\\\#comments}{https://html.spec.whatwg.org/multipage/syntax.html\\\\#comments}. But this is fine (in commonmark): And, this is wrong (in commonmark): --> The end." `; exports[`rebber: remark specs with config: custom macros html-declaration 1`] = ` " foo " `; exports[`rebber: remark specs with config: custom macros html-indented 1`] = ` "
*hello*
*hello* alpha " `; exports[`rebber: remark specs with config: custom macros html-processing-instruction 1`] = ` "'; ?>" `; exports[`rebber: remark specs with config: custom macros html-simple 1`] = ` "Here's a simple block:
foo
This should be a code block, though: [code(
foo
)] As should this: [code(
foo
)] Now, nested:
foo
This should just be an HTML comment: Multiline: Code block: [code()] Just plain comment, with trailing spaces on the line: Code: [code(
)] Hr's:








" `; exports[`rebber: remark specs with config: custom macros html-tags 1`] = ` "heading1(Block)
<-article>
" `; exports[`rebber: remark specs with config: custom macros image-basename-dots 1`] = ` "\\\\includegraphics{{x.yz}.png} \\\\includegraphics{/a/{w.x.y.z}.png} \\\\includegraphics{/{w.x.y.z}.png} \\\\includegraphics{/foo.bar/{x.yz}.png}" `; exports[`rebber: remark specs with config: custom macros image-empty-alt 1`] = `"\\\\includegraphics{/xyz.png}"`; exports[`rebber: remark specs with config: custom macros image-in-link 1`] = ` "heading1(\\\\externalLink{\\\\includegraphics{https://img.shields.io/badge/unicorn-approved-ff69b4.svg}}{http://shields.io}) \\\\externalLink{\\\\includegraphics{https://img.shields.io/travis/wooorm/mdast.svg?style=flat}}{https://travis-ci.org/wooorm/mdast} \\\\externalLink{\\\\includegraphics{https://img.shields.io/badge/style-flat--squared-green.svg?style=flat-square}}{http://example.com}" `; exports[`rebber: remark specs with config: custom macros image-path-escape 1`] = `"\\\\includegraphics{a[b]\\\\ \\\\input{/etc/passwd\\\\image{[a](b)}"`; exports[`rebber: remark specs with config: custom macros image-with-pipe 1`] = `"f|"`; exports[`rebber: remark specs with config: custom macros images 1`] = ` "Lorem ipsum dolor sit \\\\includegraphics{http://amet.com/amet.jpeg}, consectetur adipiscing elit. Praesent dictum purus ullamcorper ligula semper pellentesque. Nulla \\\\includegraphics{http://finibus.com/finibus.png} neque et diam rhoncus convallis. Nam dictum sapien nec sem ultrices fermentum. Nulla \\\\includegraphics{http://facilisi.com/facilisi.gif}. In et feugiat massa. Donec sed sodales metus, ut aliquet quam. Suspendisse nec ipsum risus. Interdum et malesuada fames ac ante ipsum primis in \\\\includegraphics{http://faucibus.com/faucibus.tiff}." `; exports[`rebber: remark specs with config: custom macros invalid-link-definition 1`] = `"Something[2-3]"`; exports[`rebber: remark specs with config: custom macros lazy-blockquotes 1`] = ` "[blockquote(hi there bud)]" `; exports[`rebber: remark specs with config: custom macros link-in-link 1`] = ` "heading1(\\\\externalLink{mailto:test@example.com}{http://shields.io}) \\\\externalLink{https://travis-ci.org/wooorm/mdast}{https://travis-ci.org/wooorm/mdast} \\\\externalLink{[](http://example.com \\"An example\\")}{http://example.com}" `; exports[`rebber: remark specs with config: custom macros link-spaces 1`] = ` "[alpha] (bravo \\\\includegraphics{undefined} (delta .com) [echo] (\\\\externalLink{http://foxtrot.golf}{http://foxtrot.golf}) \\\\includegraphics{undefined} (india.com/juliett)" `; exports[`rebber: remark specs with config: custom macros link-whitespace 1`] = ` "[alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). [alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). [alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie." `; exports[`rebber: remark specs with config: custom macros link-with-spaces 1`] = ` "\\\\externalLink{Hello}{./world and some spaces.html} \\\\externalLink{Hello}{./world and some spaces.html}" `; exports[`rebber: remark specs with config: custom macros links 1`] = ` "Lorem ipsum dolor sit \\\\externalLink{amet}{http://amet.com}, consectetur adipiscing elit. Praesent dictum purus ullamcorper ligula semper pellentesque. Nulla \\\\externalLink{finibus}{http://finibus.com} neque et diam rhoncus convallis. Nam dictum sapien nec sem ultrices fermentum. Nulla \\\\externalLink{facilisi}{http://facilisi.com}. In et feugiat massa. Donec sed sodales metus, ut aliquet quam. Suspendisse nec ipsum risus. Interdum et malesuada fames ac ante ipsum primis in \\\\externalLink{faucibus}{http://faucibus.com}." `; exports[`rebber: remark specs with config: custom macros links-inline-style 1`] = ` "Just a \\\\externalLink{URL}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. [URL and title]( /url/has space ). [URL and title]( /url/has space/ \\"url has space and title\\"). ." `; exports[`rebber: remark specs with config: custom macros links-reference-proto 1`] = ` "A [linkReference(reference=tostring, content=primary)], [linkReference(reference=constructor, content=secondary)], and [linkReference(reference=__proto__, content=tertiary)] link. [definition(identifier=tostring, url=http://primary.com, title=null)] [definition(identifier=__proto__, url=http://tertiary.com, title=null)] [definition(identifier=constructor, url=http://secondary.com, title=null)]" `; exports[`rebber: remark specs with config: custom macros links-reference-style 1`] = ` "Foo [linkReference(reference=1, content=bar)]. Foo [linkReference(reference=1, content=bar)]. Foo [linkReference(reference=1, content=bar)]. [definition(identifier=1, url=/url/, title=Title)] With [linkReference(reference=b, content=embedded [brackets])]. Indented [linkReference(reference=once, content=once)]. Indented [linkReference(reference=twice, content=twice)]. Indented [linkReference(reference=thrice, content=thrice)]. Indented [four] times. [definition(identifier=once, url=/url, title=null)] [definition(identifier=twice, url=/url, title=null)] [definition(identifier=thrice, url=/url, title=null)] [code([four]: /url)] [definition(identifier=b, url=/url/, title=null)] [thematicBreak(---)] [linkReference(reference=this, content=this)] should work So should [linkReference(reference=this, content=this)]. And [linkReference(reference=this, content=this)]. And [linkReference(reference=this, content=this)]. And [linkReference(reference=this, content=this)]. But not [that]. Nor [that]. Nor [that]. [Something in brackets like [linkReference(reference=this, content=this)] should work] [Same with [linkReference(reference=this, content=this)].] In this case, \\\\externalLink{this}{/somethingelse/} points to something else. Backslashing should suppress [this] and [this]. [definition(identifier=this, url=foo, title=null)] [thematicBreak(---)] Here's one where the [linkReference(reference=link breaks, content=link breaks)] across lines. Here's another where the [linkReference(reference=link breaks, content=link breaks)] across lines, but with a line-ending space. [definition(identifier=link breaks, url=/url/, title=null)]" `; exports[`rebber: remark specs with config: custom macros links-shortcut-references 1`] = ` "This is the [linkReference(reference=simple case, content=simple case)]. [definition(identifier=simple case, url=/simple, title=null)] This one has a [linkReference(reference=line break, content=line break)]. This one has a [linkReference(reference=line break, content=line break)] with a line-ending space. [definition(identifier=line break, url=/foo, title=null)] [linkReference(reference=that, content=this)] and the [linkReference(reference=other, content=other)] [definition(identifier=this, url=/this, title=null)] [definition(identifier=that, url=/that, title=null)] [definition(identifier=other, url=/other, title=null)]" `; exports[`rebber: remark specs with config: custom macros links-text-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs with config: custom macros links-text-empty 1`] = ` "\\\\externalLink{}{./hello-world.html}. \\\\externalLink{}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs with config: custom macros links-text-entity-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs with config: custom macros links-text-escaped-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}." `; exports[`rebber: remark specs with config: custom macros links-text-mismatched-delimiters 1`] = ` "[Hello \\\\externalLink{world!}{./hello-world.html}. [Hello \\\\externalLink{world!}{./hello-world.html}. ![Hello \\\\externalLink{world!}{./hello-world.html}. ![Hello \\\\externalLink{world!}{./hello-world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-double-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-double-quotes-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-double-quotes-entity-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-double-quotes-escaped-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-double-quotes-mismatched-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-empty-double-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-empty-parentheses 1`] = ` "[Hello](./world.html ()). [Hello](<./world.html> ()). \\\\includegraphics{undefined}(./world.html ()). \\\\includegraphics{undefined}(<./world.html> ())." `; exports[`rebber: remark specs with config: custom macros links-title-empty-single-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-parentheses 1`] = ` "[Hello](./world.html (Hello World!)). [Hello](<./world.html> (Hello World!)). \\\\includegraphics{undefined}(./world.html (Hello World!)). \\\\includegraphics{undefined}(<./world.html> (Hello World!))." `; exports[`rebber: remark specs with config: custom macros links-title-single-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-single-quotes-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-single-quotes-entity-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-single-quotes-escaped-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-single-quotes-mismatched-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}." `; exports[`rebber: remark specs with config: custom macros links-title-unclosed 1`] = ` "[Hello](./world.html 'Hello [Hello](<./world.html> 'Hello \\\\includegraphics{undefined}(./world.html 'Hello \\\\includegraphics{undefined}(<./world.html> 'Hello [Hello](./world.html \\"Hello [Hello](<./world.html> \\"Hello \\\\includegraphics{undefined}(./world.html \\"Hello \\\\includegraphics{undefined}(<./world.html> \\"Hello [Hello](./world.html (Hello [Hello](<./world.html> (Hello \\\\includegraphics{undefined}(./world.html (Hello \\\\includegraphics{undefined}(<./world.html> (Hello" `; exports[`rebber: remark specs with config: custom macros links-url-empty 1`] = ` ". . \\\\includegraphics{}. \\\\includegraphics{}." `; exports[`rebber: remark specs with config: custom macros links-url-empty-title-double-quotes 1`] = ` "\\\\externalLink{Hello}{\\"World!\\"}. \\\\externalLink{Hello}{\\"World!\\"}. . \\\\includegraphics{\\"World!\\"}. \\\\includegraphics{\\"World!\\"}. \\\\includegraphics{}." `; exports[`rebber: remark specs with config: custom macros links-url-empty-title-parentheses 1`] = ` "\\\\externalLink{Hello}{(World!)}. \\\\externalLink{Hello}{(World!)}. [World](<> (World!)). \\\\includegraphics{(World!)}. \\\\includegraphics{(World!)}. \\\\includegraphics{undefined}(<> (World!))." `; exports[`rebber: remark specs with config: custom macros links-url-empty-title-single-quotes 1`] = ` "\\\\externalLink{Hello}{'World!'}. \\\\externalLink{Hello}{'World!'}. . \\\\includegraphics{'World!'}. \\\\includegraphics{'World!'}. \\\\includegraphics{}." `; exports[`rebber: remark specs with config: custom macros links-url-entity-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}." `; exports[`rebber: remark specs with config: custom macros links-url-escaped-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}." `; exports[`rebber: remark specs with config: custom macros links-url-mismatched-parentheses 1`] = ` "[Hello](./world(and-hello(world)). \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld}). \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{undefined}(./world(and-hello(world)). \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld}). \\\\includegraphics{./world(and)helloworld)}." `; exports[`rebber: remark specs with config: custom macros links-url-nested-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and)hello(world)}. \\\\externalLink{Hello}{./world(and)hello(world)}. \\\\includegraphics{./world(and)hello(world)}. \\\\includegraphics{./world(and)hello(world)}." `; exports[`rebber: remark specs with config: custom macros links-url-new-line 1`] = ` "[Hello](./wo rld.html). \\\\externalLink{Hello}{./wo rld.html}. \\\\includegraphics{undefined}(./wo rld.png). \\\\includegraphics{./wo rld.png}." `; exports[`rebber: remark specs with config: custom macros links-url-unclosed 1`] = ` "[Hello]( [World](< \\\\includegraphics{undefined}( \\\\includegraphics{undefined}(<" `; exports[`rebber: remark specs with config: custom macros links-url-white-space 1`] = ` "[Hello](./wo rld.html). \\\\externalLink{Hello}{./wo rld.html}. \\\\includegraphics{undefined}(./wo rld.png). \\\\includegraphics{./wo rld.png}." `; exports[`rebber: remark specs with config: custom macros list 1`] = ` "heading1(List bullets) [unorderedList([listItem(One: [unorderedList([listItem(Nested one;)][listItem(Nested two: [unorderedList([listItem(Nested three.)])])])])][listItem(Two;)][listItem(Three.)])]" `; exports[`rebber: remark specs with config: custom macros list-after-list 1`] = ` "[unorderedList([listItem(item)][listItem(item)][listItem(item)])] [orderedList([listItem(item)][listItem(item)][listItem(item)])] [thematicBreak(---)] [unorderedList([listItem(item)][listItem(item)][listItem(item)])] [orderedList([listItem(item)][listItem(item)][listItem(item)])]" `; exports[`rebber: remark specs with config: custom macros list-and-code 1`] = ` "[unorderedList([listItem(This is a list item)])] [code(This is code)]" `; exports[`rebber: remark specs with config: custom macros list-continuation 1`] = ` "[orderedList([listItem(foo)])] [thematicBreak(---)] [orderedList([listItem(foo)])] [codeJs(code();)] [orderedList([listItem([linkReference(reference=foo, content=foo)])])] [definition(identifier=foo, url=http://google.com, title=null)]" `; exports[`rebber: remark specs with config: custom macros list-indentation 1`] = ` "[unorderedList([listItem(Hello 1a World 1a.)][listItem(Hello 1b World 1b.)][listItem(Hello 2a World 2a.)][listItem(Hello 2b World 2b.)][listItem(Hello 3a World 3a.)][listItem(Hello 3b World 3b.)][listItem(Hello 4a World 4a.)][listItem(Hello 4b World 4b.)][listItem([code(Hello 5a)]World 5a.)][listItem([code(Hello 5b World 5b.)])])]" `; exports[`rebber: remark specs with config: custom macros list-item-empty 1`] = `"[unorderedList([listItem(foo)][listItem()][listItem(bar)][listItem(foo)][listItem()][listItem(bar)])]"`; exports[`rebber: remark specs with config: custom macros list-item-empty-with-white-space 1`] = `"[unorderedList([listItem()])]"`; exports[`rebber: remark specs with config: custom macros list-item-indent 1`] = ` "[orderedList([listItem(foo bar baz.)])] [orderedList([listItem(foo bar baz.)])] [orderedList([listItem(foo bar baz.)])] [orderedList([listItem(foo bar baz. foo bar baz.)])] [orderedList([listItem(foo bar baz. foo bar baz.)])] [orderedList([listItem(foo bar baz. foo bar baz.)])] [unorderedList([listItem(foo bar baz.)])] [unorderedList([listItem(foo bar baz. foo bar baz.)])]" `; exports[`rebber: remark specs with config: custom macros list-item-newline 1`] = `"[unorderedList([listItem(Foo)][listItem(Bar)])]"`; exports[`rebber: remark specs with config: custom macros list-item-text 1`] = ` "[unorderedList([listItem(item1 [unorderedList([listItem(item2)])]text)])]" `; exports[`rebber: remark specs with config: custom macros list-ordered 1`] = `"[orderedList([listItem(foo;)][listItem(bar;)][listItem(baz.)])]"`; exports[`rebber: remark specs with config: custom macros lists-with-code-and-rules 1`] = ` "heading2(foo) [orderedList([listItem(bar: [blockquote([unorderedList([listItem(one [unorderedList([listItem(two [unorderedList([listItem(three)][listItem(four)][listItem(five)])])])])])])])][listItem(foo: [code(line 1 line 2)])][listItem(foo: [orderedList([listItem(foo \\\\texttt{bar} bar: [codeErb(some code here)])][listItem(foo \\\\texttt{bar} bar: [codeErb(foo --- bar --- foo bar)])][listItem(foo \\\\texttt{bar} bar: [codeHtml(--- foo foo --- bar)])][listItem(foo \\\\texttt{bar} bar: [code(foo --- bar)])][listItem(foo)])])])]" `; exports[`rebber: remark specs with config: custom macros loose-lists 1`] = ` "[unorderedList([listItem(hello world how are)][listItem(you)])] better behavior: [unorderedList([listItem(hello [unorderedList([listItem(world how are you)][listItem(today)])])][listItem(hi)])] [unorderedList([listItem(hello)][listItem(world)][listItem(hi)])] [unorderedList([listItem(hello)][listItem(world)][listItem(hi)])] [unorderedList([listItem(hello)][listItem(world how)][listItem(hi)])] [unorderedList([listItem(hello)][listItem(world)][listItem(how are)])] [unorderedList([listItem(hello)][listItem(world)][listItem(how are)])]" `; exports[`rebber: remark specs with config: custom macros main 1`] = ` "[definition(identifier=test, url=http://google.com/, title=Google)] heading1(A heading) Just a note, I've found that I can't test my markdown parser vs others. For example, both markdown.js and showdown code blocks in lists wrong. They're also completely [linkReference(reference=test, content=inconsistent)] with regards to paragraphs in list items. A link. Not anymore. [unorderedList([listItem(List Item 1)][listItem(List Item 2 [unorderedList([listItem(New List Item 1 Hi, this is a list item.)][listItem(New List Item 2 Another item Code goes here. Lots of it...)][listItem(New List Item 3 The last item)])])][listItem(List Item 3 The final item.)][listItem(List Item 4 The real final item.)])] Paragraph. [blockquote([unorderedList([listItem(bq Item 1)][listItem(bq Item 2 [unorderedList([listItem(New bq Item 1)][listItem(New bq Item 2 Text here)])])])])] [thematicBreak(---)] [blockquote(Another blockquote! I really need to get more creative with mockup text.. markdown.js breaks here again)] heading2(Another Heading) Hello \\\\textit{world}. Here is a \\\\externalLink{link}{//hello}. And an image \\\\includegraphics{src}. And an image with an empty alt attribute \\\\includegraphics{src}. [code(Code goes here. Lots of it...)]" `; exports[`rebber: remark specs with config: custom macros markdown-documentation-basics 1`] = ` "heading1(Markdown: Basics) heading2(Getting the Gist of Markdown's Formatting Syntax) This page offers a brief overview of what it's like to use Markdown. The [linkReference(reference=s, content=syntax page)] provides complete, detailed documentation for every feature, but Markdown should be very easy to pick up simply by looking at a few examples of it in action. The examples on this page are written in a before/after style, showing example syntax and the HTML output produced by Markdown. It's also helpful to simply try Markdown out; the [linkReference(reference=d, content=Dingus)] is a web application that allows you type your own Markdown-formatted text and translate it to XHTML. \\\\textbf{Note:} This document is itself written using Markdown; you can [linkReference(reference=src, content=see the source for it by adding '.text' to the URL)]. [definition(identifier=s, url=/projects/markdown/syntax, title=Markdown Syntax)] [definition(identifier=d, url=/projects/markdown/dingus, title=Markdown Dingus)] [definition(identifier=src, url=/projects/markdown/basics.text, title=null)] heading2(Paragraphs, Headers, Blockquotes) A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. Markdown offers two styles of headers: \\\\textit{Setext} and \\\\textit{atx}. Setext-style headers for \\\\texttt{

} and \\\\texttt{

} are created by \\"underlining\\" with equal signs (\\\\texttt{=}) and hyphens (\\\\texttt{-}), respectively. To create an atx-style header, you put 1-6 hash marks (\\\\texttt{\\\\#}) at the beginning of the line -- the number of hashes equals the resulting HTML header level. Blockquotes are indicated using email-style '\\\\texttt{>}' angle brackets. Markdown: [code(A First Level Header ==================== A Second Level Header --------------------- Now is the time for all good men to come to the aid of their country. This is just a regular paragraph. The quick brown fox jumped over the lazy dog's back. ### Header 3 > This is a blockquote. > > This is the second paragraph in the blockquote. > > ## This is an H2 in a blockquote)] Output: [code(

A First Level Header

A Second Level Header

Now is the time for all good men to come to the aid of their country. This is just a regular paragraph.

The quick brown fox jumped over the lazy dog's back.

Header 3

This is a blockquote.

This is the second paragraph in the blockquote.

This is an H2 in a blockquote

)] heading3(Phrase Emphasis) Markdown uses asterisks and underscores to indicate spans of emphasis. Markdown: [code(Some of these words *are emphasized*. Some of these words _are emphasized also_. Use two asterisks for **strong emphasis**. Or, if you prefer, __use two underscores instead__.)] Output: [code(

Some of these words are emphasized. Some of these words are emphasized also.

Use two asterisks for strong emphasis. Or, if you prefer, use two underscores instead.

)] heading2(Lists) Unordered (bulleted) lists use asterisks, pluses, and hyphens (\\\\texttt{*}, \\\\texttt{+}, and \\\\texttt{-}) as list markers. These three markers are interchangable; this: [code(* Candy. * Gum. * Booze.)] this: [code(+ Candy. + Gum. + Booze.)] and this: [code(- Candy. - Gum. - Booze.)] all produce the same output: [code(
  • Candy.
  • Gum.
  • Booze.
)] Ordered (numbered) lists use regular numbers, followed by periods, as list markers: [code(1. Red 2. Green 3. Blue)] Output: [code(
  1. Red
  2. Green
  3. Blue
)] If you put blank lines between items, you'll get \\\\texttt{

} tags for the list item text. You can create multi-paragraph list items by indenting the paragraphs by 4 spaces or 1 tab: [code(* A list item. With multiple paragraphs. * Another item in the list.)] Output: [code(

  • A list item.

    With multiple paragraphs.

  • Another item in the list.

)] heading3(Links) Markdown supports two styles for creating links: \\\\textit{inline} and \\\\textit{reference}. With both styles, you use square brackets to delimit the text you want to turn into a link. Inline-style links use parentheses immediately after the link text. For example: [code(This is an [example link](http://example.com/).)] Output: [code(

This is an example link.

)] Optionally, you may include a title attribute in the parentheses: [code(This is an [example link](http://example.com/ \\"With a Title\\").)] Output: [code(

This is an example link.

)] Reference-style links allow you to refer to your links by names, which you define elsewhere in your document: [code(I get 10 times more traffic from [Google][1] than from [Yahoo][2] or [MSN][3]. [1]: http://google.com/ \\"Google\\" [2]: http://search.yahoo.com/ \\"Yahoo Search\\" [3]: http://search.msn.com/ \\"MSN Search\\")] Output: [code(

I get 10 times more traffic from Google than from Yahoo or MSN.

)] The title attribute is optional. Link names may contain letters, numbers and spaces, but are \\\\textit{not} case sensitive: [code(I start my morning with a cup of coffee and [The New York Times][NY Times]. [ny times]: http://www.nytimes.com/)] Output: [code(

I start my morning with a cup of coffee and The New York Times.

)] heading3(Images) Image syntax is very much like link syntax. Inline (titles are optional): [code(![alt text](/path/to/img.jpg \\"Title\\"))] Reference-style: [code(![alt text][id] [id]: /path/to/img.jpg \\"Title\\")] Both of the above examples produce the same output: [code(\\"alt)] heading3(Code) In a regular paragraph, you can create code span by wrapping text in backtick quotes. Any ampersands (\\\\texttt{\\\\&}) and angle brackets (\\\\texttt{<} or \\\\texttt{>}) will automatically be translated into HTML entities. This makes it easy to use Markdown to write about HTML example code: [code(I strongly recommend against using any \`\` tags. I wish SmartyPants used named entities like \`—\` instead of decimal-encoded entites like \`—\`.)] Output: [code(

I strongly recommend against using any <blink> tags.

I wish SmartyPants used named entities like &mdash; instead of decimal-encoded entites like &#8212;.

)] To specify an entire block of pre-formatted code, indent every line of the block by 4 spaces or 1 tab. Just like with code spans, \\\\texttt{\\\\&}, \\\\texttt{<}, and \\\\texttt{>} characters will be escaped automatically. Markdown: [code(If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

For example.

)] Output: [code(

If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

<blockquote>
    <p>For example.</p>
</blockquote>
)]" `; exports[`rebber: remark specs with config: custom macros markdown-documentation-syntax 1`] = ` "heading1(Markdown: Syntax) [unorderedList([listItem(\\\\externalLink{Overview}{\\\\#overview} [unorderedList([listItem(\\\\externalLink{Philosophy}{\\\\#philosophy})][listItem(\\\\externalLink{Inline HTML}{\\\\#html})][listItem(\\\\externalLink{Automatic Escaping for Special Characters}{\\\\#autoescape})])])][listItem(\\\\externalLink{Block Elements}{\\\\#block} [unorderedList([listItem(\\\\externalLink{Paragraphs and Line Breaks}{\\\\#p})][listItem(\\\\externalLink{Headers}{\\\\#header})][listItem(\\\\externalLink{Blockquotes}{\\\\#blockquote})][listItem(\\\\externalLink{Lists}{\\\\#list})][listItem(\\\\externalLink{Code Blocks}{\\\\#precode})][listItem(\\\\externalLink{Horizontal Rules}{\\\\#hr})])])][listItem(\\\\externalLink{Span Elements}{\\\\#span} [unorderedList([listItem(\\\\externalLink{Links}{\\\\#link})][listItem(\\\\externalLink{Emphasis}{\\\\#em})][listItem(\\\\externalLink{Code}{\\\\#code})][listItem(\\\\externalLink{Images}{\\\\#img})])])][listItem(\\\\externalLink{Miscellaneous}{\\\\#misc} [unorderedList([listItem(\\\\externalLink{Backslash Escapes}{\\\\#backslash})][listItem(\\\\externalLink{Automatic Links}{\\\\#autolink})])])])] \\\\textbf{Note:} This document is itself written using Markdown; you can [linkReference(reference=src, content=see the source for it by adding '.text' to the URL)]. [definition(identifier=src, url=/projects/markdown/syntax.text, title=null)] [thematicBreak(---)]

Overview

Philosophy

Markdown is intended to be as easy-to-read and easy-to-write as is feasible. Readability, however, is emphasized above all else. A Markdown-formatted document should be publishable as-is, as plain text, without looking like it's been marked up with tags or formatting instructions. While Markdown's syntax has been influenced by several existing text-to-HTML filters -- including [linkReference(reference=1, content=Setext)], [linkReference(reference=2, content=atx)], [linkReference(reference=3, content=Textile)], [linkReference(reference=4, content=reStructuredText)], [linkReference(reference=5, content=Grutatext)], and [linkReference(reference=6, content=EtText)] -- the single biggest source of inspiration for Markdown's syntax is the format of plain text email. [definition(identifier=1, url=http://docutils.sourceforge.net/mirror/setext.html, title=null)] [definition(identifier=2, url=http://www.aaronsw.com/2002/atx/, title=null)] [definition(identifier=3, url=http://textism.com/tools/textile/, title=null)] [definition(identifier=4, url=http://docutils.sourceforge.net/rst.html, title=null)] [definition(identifier=5, url=http://www.triptico.com/software/grutatxt.html, title=null)] [definition(identifier=6, url=http://ettext.taint.org/doc/, title=null)] To this end, Markdown's syntax is comprised entirely of punctuation characters, which punctuation characters have been carefully chosen so as to look like what they mean. E.g., asterisks around a word actually look like *emphasis*. Markdown lists look like, well, lists. Even blockquotes look like quoted passages of text, assuming you've ever used email.

Inline HTML

Markdown's syntax is intended for one purpose: to be used as a format for \\\\textit{writing} for the web. Markdown is not a replacement for HTML, or even close to it. Its syntax is very small, corresponding only to a very small subset of HTML tags. The idea is \\\\textit{not} to create a syntax that makes it easier to insert HTML tags. In my opinion, HTML tags are already easy to insert. The idea for Markdown is to make it easy to read, write, and edit prose. HTML is a \\\\textit{publishing} format; Markdown is a \\\\textit{writing} format. Thus, Markdown's formatting syntax only addresses issues that can be conveyed in plain text. For any markup that is not covered by Markdown's syntax, you simply use HTML itself. There's no need to preface it or delimit it to indicate that you're switching from Markdown to HTML; you just use the tags. The only restrictions are that block-level HTML elements -- e.g. \\\\texttt{
}, \\\\texttt{}, \\\\texttt{
}, \\\\texttt{

}, etc. -- must be separated from surrounding content by blank lines, and the start and end tags of the block should not be indented with tabs or spaces. Markdown is smart enough not to add extra (unwanted) \\\\texttt{

} tags around HTML block-level tags. For example, to add an HTML table to a Markdown article: [code(This is a regular paragraph.

Foo
This is another regular paragraph.)] Note that Markdown formatting syntax is not processed within block-level HTML tags. E.g., you can't use Markdown-style \\\\texttt{*emphasis*} inside an HTML block. Span-level HTML tags -- e.g. \\\\texttt{}, \\\\texttt{}, or \\\\texttt{} -- can be used anywhere in a Markdown paragraph, list item, or header. If you want, you can even use HTML tags instead of Markdown formatting; e.g. if you'd prefer to use HTML \\\\texttt{} or \\\\texttt{} tags instead of Markdown's link or image syntax, go right ahead. Unlike block-level HTML tags, Markdown syntax \\\\textit{is} processed within span-level tags.

Automatic Escaping for Special Characters

In HTML, there are two characters that demand special treatment: \\\\texttt{<} and \\\\texttt{\\\\&}. Left angle brackets are used to start tags; ampersands are used to denote HTML entities. If you want to use them as literal characters, you must escape them as entities, e.g. \\\\texttt{\\\\<}, and \\\\texttt{\\\\&}. Ampersands in particular are bedeviling for web writers. If you want to write about 'AT\\\\&T', you need to write '\\\\texttt{AT\\\\&T}'. You even need to escape ampersands within URLs. Thus, if you want to link to: [code(http://images.google.com/images?num=30&q=larry+bird)] you need to encode the URL as: [code(http://images.google.com/images?num=30&q=larry+bird)] in your anchor tag \\\\texttt{href} attribute. Needless to say, this is easy to forget, and is probably the single most common source of HTML validation errors in otherwise well-marked-up web sites. Markdown allows you to use these characters naturally, taking care of all the necessary escaping for you. If you use an ampersand as part of an HTML entity, it remains unchanged; otherwise it will be translated into \\\\texttt{\\\\&}. So, if you want to include a copyright symbol in your article, you can write: [code(©)] and Markdown will leave it alone. But if you write: [code(AT&T)] Markdown will translate it to: [code(AT&T)] Similarly, because Markdown supports \\\\externalLink{inline HTML}{\\\\#html}, if you use angle brackets as delimiters for HTML tags, Markdown will treat them as such. But if you write: [code(4 < 5)] Markdown will translate it to: [code(4 < 5)] However, inside Markdown code spans and blocks, angle brackets and ampersands are \\\\textit{always} encoded automatically. This makes it easy to use Markdown to write about HTML code. (As opposed to raw HTML, which is a terrible format for writing about HTML syntax, because every single \\\\texttt{<} and \\\\texttt{\\\\&} in your example code needs to be escaped.) [thematicBreak(---)]

Block Elements

Paragraphs and Line Breaks

A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. The implication of the \\"one or more consecutive lines of text\\" rule is that Markdown supports \\"hard-wrapped\\" text paragraphs. This differs significantly from most other text-to-HTML formatters (including Movable Type's \\"Convert Line Breaks\\" option) which translate every line break character in a paragraph into a \\\\texttt{
} tag. When you \\\\textit{do} want to insert a \\\\texttt{
} break tag using Markdown, you end a line with two or more spaces, then type return. Yes, this takes a tad more effort to create a \\\\texttt{
}, but a simplistic \\"every line break is a \\\\texttt{
}\\" rule wouldn't work for Markdown. Markdown's email-style [linkReference(reference=bq, content=blockquoting)] and multi-paragraph [linkReference(reference=l, content=list items)] work best -- and look better -- when you format them with hard breaks. [definition(identifier=bq, url=#blockquote, title=null)] [definition(identifier=l, url=#list, title=null)]

Headers

Markdown supports two styles of headers, [linkReference(reference=1, content=Setext)] and [linkReference(reference=2, content=atx)]. Setext-style headers are \\"underlined\\" using equal signs (for first-level headers) and dashes (for second-level headers). For example: [code(This is an H1 ============= This is an H2 -------------)] Any number of underlining \\\\texttt{=}'s or \\\\texttt{-}'s will work. Atx-style headers use 1-6 hash characters at the start of the line, corresponding to header levels 1-6. For example: [code(# This is an H1 ## This is an H2 ###### This is an H6)] Optionally, you may \\"close\\" atx-style headers. This is purely cosmetic -- you can use this if you think it looks better. The closing hashes don't even need to match the number of hashes used to open the header. (The number of opening hashes determines the header level.) : [code(# This is an H1 # ## This is an H2 ## ### This is an H3 ######)]

Blockquotes

Markdown uses email-style \\\\texttt{>} characters for blockquoting. If you're familiar with quoting passages of text in an email message, then you know how to create a blockquote in Markdown. It looks best if you hard wrap the text and put a \\\\texttt{>} before every line: [code(> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse > id sem consectetuer libero luctus adipiscing.)] Markdown allows you to be lazy and only put the \\\\texttt{>} before the first line of a hard-wrapped paragraph: [code(> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing.)] Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by adding additional levels of \\\\texttt{>}: [code(> This is the first level of quoting. > > > This is nested blockquote. > > Back to the first level.)] Blockquotes can contain other Markdown elements, including headers, lists, and code blocks: [code(> ## This is a header. > > 1. This is the first list item. > 2. This is the second list item. > > Here's some example code: > > return shell_exec(\\"echo $input | $markdown_script\\");)] Any decent text editor should make email-style quoting easy. For example, with BBEdit, you can make a selection and choose Increase Quote Level from the Text menu.

Lists

Markdown supports ordered (numbered) and unordered (bulleted) lists. Unordered lists use asterisks, pluses, and hyphens -- interchangably -- as list markers: [code(* Red * Green * Blue)] is equivalent to: [code(+ Red + Green + Blue)] and: [code(- Red - Green - Blue)] Ordered lists use numbers followed by periods: [code(1. Bird 2. McHale 3. Parish)] It's important to note that the actual numbers you use to mark the list have no effect on the HTML output Markdown produces. The HTML Markdown produces from the above list is: [code(
  1. Bird
  2. McHale
  3. Parish
)] If you instead wrote the list in Markdown like this: [code(1. Bird 1. McHale 1. Parish)] or even: [code(3. Bird 1. McHale 8. Parish)] you'd get the exact same HTML output. The point is, if you want to, you can use ordinal numbers in your ordered Markdown lists, so that the numbers in your source match the numbers in your published HTML. But if you want to be lazy, you don't have to. If you do use lazy list numbering, however, you should still start the list with the number 1. At some point in the future, Markdown may support starting ordered lists at an arbitrary number. List markers typically start at the left margin, but may be indented by up to three spaces. List markers must be followed by one or more spaces or a tab. To make lists look nice, you can wrap items with hanging indents: [code(* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing.)] But if you want to be lazy, you don't have to: [code(* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing.)] If list items are separated by blank lines, Markdown will wrap the items in \\\\texttt{

} tags in the HTML output. For example, this input: [code(* Bird * Magic)] will turn into: [code(

  • Bird
  • Magic
)] But this: [code(* Bird * Magic)] will turn into: [code(
  • Bird

  • Magic

)] List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be intended by either 4 spaces or one tab: [code(1. This is a list item with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 2. Suspendisse id sem consectetuer libero luctus adipiscing.)] It looks nice if you indent every line of the subsequent paragraphs, but here again, Markdown will allow you to be lazy: [code(* This is a list item with two paragraphs. This is the second paragraph in the list item. You're only required to indent the first line. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. * Another item in the same list.)] To put a blockquote within a list item, the blockquote's \\\\texttt{>} delimiters need to be indented: [code(* A list item with a blockquote: > This is a blockquote > inside a list item.)] To put a code block within a list item, the code block needs to be indented \\\\textit{twice} -- 8 spaces or two tabs: [code(* A list item with a code block: )] It's worth noting that it's possible to trigger an ordered list by accident, by writing something like this: [code(1986. What a great season.)] In other words, a \\\\textit{number-period-space} sequence at the beginning of a line. To avoid this, you can backslash-escape the period: [code(1986\\\\. What a great season.)]

Code Blocks

Pre-formatted code blocks are used for writing about programming or markup source code. Rather than forming normal paragraphs, the lines of a code block are interpreted literally. Markdown wraps a code block in both \\\\texttt{
} and \\\\texttt{} tags.



To produce a code block in Markdown, simply indent every line of the
block by at least 4 spaces or 1 tab. For example, given this input:



[code(This is a normal paragraph:

    This is a code block.)]

Markdown will generate:



[code(

This is a normal paragraph:

This is a code block.
)] One level of indentation -- 4 spaces or 1 tab -- is removed from each line of the code block. For example, this: [code(Here is an example of AppleScript: tell application \\"Foo\\" beep end tell)] will turn into: [code(

Here is an example of AppleScript:

tell application \\"Foo\\"
    beep
end tell
)] A code block continues until it reaches a line that is not indented (or the end of the article). Within a code block, ampersands (\\\\texttt{\\\\&}) and angle brackets (\\\\texttt{<} and \\\\texttt{>}) are automatically converted into HTML entities. This makes it very easy to include example HTML source code using Markdown -- just paste it and indent it, and Markdown will handle the hassle of encoding the ampersands and angle brackets. For example, this: [code(
© 2004 Foo Corporation
)] will turn into: [code(
<div class=\\"footer\\">
    &copy; 2004 Foo Corporation
</div>
)] Regular Markdown syntax is not processed within code blocks. E.g., asterisks are just literal asterisks within a code block. This means it's also easy to use Markdown to write about Markdown's own syntax.

Horizontal Rules

You can produce a horizontal rule tag (\\\\texttt{
}) by placing three or more hyphens, asterisks, or underscores on a line by themselves. If you wish, you may use spaces between the hyphens or asterisks. Each of the following lines will produce a horizontal rule: [code(* * * *** ***** - - - --------------------------------------- _ _ _)] [thematicBreak(---)]

Span Elements

Links

Markdown supports two style of links: \\\\textit{inline} and \\\\textit{reference}. In both styles, the link text is delimited by [square brackets]. To create an inline link, use a set of regular parentheses immediately after the link text's closing square bracket. Inside the parentheses, put the URL where you want the link to point, along with an \\\\textit{optional} title for the link, surrounded in quotes. For example: [code(This is [an example](http://example.com/ \\"Title\\") inline link. [This link](http://example.net/) has no title attribute.)] Will produce: [code(

This is an example inline link.

This link has no title attribute.

)] If you're referring to a local resource on the same server, you can use relative paths: [code(See my [About](/about/) page for details.)] Reference-style links use a second set of square brackets, inside which you place a label of your choosing to identify the link: [code(This is [an example][id] reference-style link.)] You can optionally use a space to separate the sets of brackets: [code(This is [an example] [id] reference-style link.)] Then, anywhere in the document, you define your link label like this, on a line by itself: [code([id]: http://example.com/ \\"Optional Title Here\\")] That is: [unorderedList([listItem(Square brackets containing the link identifier (optionally indented from the left margin using up to three spaces);)][listItem(followed by a colon;)][listItem(followed by one or more spaces (or tabs);)][listItem(followed by the URL for the link;)][listItem(optionally followed by a title attribute for the link, enclosed in double or single quotes.)])] The link URL may, optionally, be surrounded by angle brackets: [code([id]: \\"Optional Title Here\\")] You can put the title attribute on the next line and use extra spaces or tabs for padding, which tends to look better with longer URLs: [code([id]: http://example.com/longish/path/to/resource/here \\"Optional Title Here\\")] Link definitions are only used for creating links during Markdown processing, and are stripped from your document in the HTML output. Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are \\\\textit{not} case sensitive. E.g. these two links: [code([link text][a] [link text][A])] are equivalent. The \\\\textit{implicit link name} shortcut allows you to omit the name of the link, in which case the link text itself is used as the name. Just use an empty set of square brackets -- e.g., to link the word \\"Google\\" to the google.com web site, you could simply write: [code([Google][])] And then define the link: [code([Google]: http://google.com/)] Because link names may contain spaces, this shortcut even works for multiple words in the link text: [code(Visit [Daring Fireball][] for more information.)] And then define the link: [code([Daring Fireball]: http://daringfireball.net/)] Link definitions can be placed anywhere in your Markdown document. I tend to put them immediately after each paragraph in which they're used, but if you want, you can put them all at the end of your document, sort of like footnotes. Here's an example of reference links in action: [code(I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]. [1]: http://google.com/ \\"Google\\" [2]: http://search.yahoo.com/ \\"Yahoo Search\\" [3]: http://search.msn.com/ \\"MSN Search\\")] Using the implicit link name shortcut, you could instead write: [code(I get 10 times more traffic from [Google][] than from [Yahoo][] or [MSN][]. [google]: http://google.com/ \\"Google\\" [yahoo]: http://search.yahoo.com/ \\"Yahoo Search\\" [msn]: http://search.msn.com/ \\"MSN Search\\")] Both of the above examples will produce the following HTML output: [code(

I get 10 times more traffic from Google than from Yahoo or MSN.

)] For comparison, here is the same paragraph written using Markdown's inline link style: [code(I get 10 times more traffic from [Google](http://google.com/ \\"Google\\") than from [Yahoo](http://search.yahoo.com/ \\"Yahoo Search\\") or [MSN](http://search.msn.com/ \\"MSN Search\\").)] The point of reference-style links is not that they're easier to write. The point is that with reference-style links, your document source is vastly more readable. Compare the above examples: using reference-style links, the paragraph itself is only 81 characters long; with inline-style links, it's 176 characters; and as raw HTML, it's 234 characters. In the raw HTML, there's more markup than there is text. With Markdown's reference-style links, a source document much more closely resembles the final output, as rendered in a browser. By allowing you to move the markup-related metadata out of the paragraph, you can add links without interrupting the narrative flow of your prose.

Emphasis

Markdown treats asterisks (\\\\texttt{*}) and underscores (\\\\texttt{\\\\_}) as indicators of emphasis. Text wrapped with one \\\\texttt{*} or \\\\texttt{\\\\_} will be wrapped with an HTML \\\\texttt{} tag; double \\\\texttt{*}'s or \\\\texttt{\\\\_}'s will be wrapped with an HTML \\\\texttt{} tag. E.g., this input: [code(*single asterisks* _single underscores_ **double asterisks** __double underscores__)] will produce: [code(single asterisks single underscores double asterisks double underscores)] You can use whichever style you prefer; the lone restriction is that the same character must be used to open and close an emphasis span. Emphasis can be used in the middle of a word: [code(un*fucking*believable)] But if you surround an \\\\texttt{*} or \\\\texttt{\\\\_} with spaces, it'll be treated as a literal asterisk or underscore. To produce a literal asterisk or underscore at a position where it would otherwise be used as an emphasis delimiter, you can backslash escape it: [code(\\\\*this text is surrounded by literal asterisks\\\\*)]

Code

To indicate a span of code, wrap it with backtick quotes (\\\\texttt{\`}). Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example: [code(Use the \`printf()\` function.)] will produce: [code(

Use the printf() function.

)] To include a literal backtick character within a code span, you can use multiple backticks as the opening and closing delimiters: [code(\`\`There is a literal backtick (\`) here.\`\`)] which will produce this: [code(

There is a literal backtick (\`) here.

)] The backtick delimiters surrounding a code span may include spaces -- one after the opening, one before the closing. This allows you to place literal backtick characters at the beginning or end of a code span: [code(A single backtick in a code span: \`\` \` \`\` A backtick-delimited string in a code span: \`\` \`foo\` \`\`)] will produce: [code(

A single backtick in a code span: \`

A backtick-delimited string in a code span: \`foo\`

)] With a code span, ampersands and angle brackets are encoded as HTML entities automatically, which makes it easy to include example HTML tags. Markdown will turn this: [code(Please don't use any \`\` tags.)] into: [code(

Please don't use any <blink> tags.

)] You can write this: [code(\`—\` is the decimal-encoded equivalent of \`—\`.)] to produce: [code(

&#8212; is the decimal-encoded equivalent of &mdash;.

)]

Images

Admittedly, it's fairly difficult to devise a \\"natural\\" syntax for placing images into a plain text document format. Markdown uses an image syntax that is intended to resemble the syntax for links, allowing for two styles: \\\\textit{inline} and \\\\textit{reference}. Inline image syntax looks like this: [code(![Alt text](/path/to/img.jpg) ![Alt text](/path/to/img.jpg \\"Optional title\\"))] That is: [unorderedList([listItem(An exclamation mark: \\\\texttt{!};)][listItem(followed by a set of square brackets, containing the \\\\texttt{alt} attribute text for the image;)][listItem(followed by a set of parentheses, containing the URL or path to the image, and an optional \\\\texttt{title} attribute enclosed in double or single quotes.)])] Reference-style image syntax looks like this: [code(![Alt text][id])] Where \\"id\\" is the name of a defined image reference. Image references are defined using syntax identical to link references: [code([id]: url/to/image \\"Optional title attribute\\")] As of this writing, Markdown has no syntax for specifying the dimensions of an image; if this is important to you, you can simply use regular HTML \\\\texttt{} tags. [thematicBreak(---)]

Miscellaneous

Automatic Links

Markdown supports a shortcut style for creating \\"automatic\\" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: [code()] Markdown will turn this into: [code(http://example.com/)] Automatic links for email addresses work similarly, except that Markdown will also perform a bit of randomized decimal and hex entity-encoding to help obscure your address from address-harvesting spambots. For example, Markdown will turn this: [code()] into something like this: [code(address@exa mple.com)] which will render in a browser as a clickable link to \\"\\\\externalLink{address@example.com}{mailto:address@example.com}\\". (This sort of entity-encoding trick will indeed fool many, if not most, address-harvesting bots, but it definitely won't fool all of them. It's better than nothing, but an address published in this way will probably eventually start receiving spam.)

Backslash Escapes

Markdown allows you to use backslash escapes to generate literal characters which would otherwise have special meaning in Markdown's formatting syntax. For example, if you wanted to surround a word with literal asterisks (instead of an HTML \\\\texttt{} tag), you can backslashes before the asterisks, like this: [code(\\\\*literal asterisks\\\\*)] Markdown provides backslash escapes for the following characters: [code(\\\\ backslash \` backtick * asterisk _ underscore {} curly braces [] square brackets () parentheses # hash mark + plus sign - minus sign (hyphen) . dot ! exclamation mark)]" `; exports[`rebber: remark specs with config: custom macros mixed-indentation 1`] = ` "heading1(Mixed spaces and tabs) [unorderedList([listItem(Very long paragraph)])] [orderedList([listItem(Very long paragraph)])] [unorderedList([listItem(Very long paragraph)])] [orderedList([listItem(Very long paragraph)])]" `; exports[`rebber: remark specs with config: custom macros nested-blockquotes 1`] = ` "[blockquote(foo [blockquote(bar)]foo)]" `; exports[`rebber: remark specs with config: custom macros nested-code 1`] = ` "\\\\texttt{hi ther \`\` ok \`\`\`} \\\\texttt{\`hi ther\`}" `; exports[`rebber: remark specs with config: custom macros nested-em 1`] = ` "\\\\textit{test \\\\textbf{test} test} \\\\textit{test \\\\textbf{test} test}" `; exports[`rebber: remark specs with config: custom macros nested-references 1`] = ` "This nested image should work: [\\\\includegraphics{undefined}] This nested link should not work: [[Foo][bar]]" `; exports[`rebber: remark specs with config: custom macros nested-square-link 1`] = ` "[the \`]\` character](/url) [the \\\\texttt{[} character](/url) [the \`\` \\\\externalLink{ \`\`\` character}{/url} \\\\externalLink{the \\\\texttt{\`} character}{/url}" `; exports[`rebber: remark specs with config: custom macros no-positionals 1`] = ` "This document tests for the working of \\\\texttt{position: false} as a parse option. [blockquote(Block-quotes [unorderedList([listItem(With list items.)])])] Another block-quote: [blockquote([orderedList([listItem(And another list.)])])] Some \\\\externalLink{deeply \\\\textbf{nested \\\\textit{elements}}}{http://example.com} An entity: ©, and an warning entity: ©." `; exports[`rebber: remark specs with config: custom macros not-a-link 1`] = `"[test](not a link)"`; exports[`rebber: remark specs with config: custom macros ordered-and-unordered-lists 1`] = ` "heading2(Unordered) Asterisks tight: [unorderedList([listItem(asterisk 1)][listItem(asterisk 2)][listItem(asterisk 3)])] Asterisks loose: [unorderedList([listItem(asterisk 1)][listItem(asterisk 2)][listItem(asterisk 3)])] [thematicBreak(---)] Pluses tight: [unorderedList([listItem(Plus 1)][listItem(Plus 2)][listItem(Plus 3)])] Pluses loose: [unorderedList([listItem(Plus 1)][listItem(Plus 2)][listItem(Plus 3)])] [thematicBreak(---)] Minuses tight: [unorderedList([listItem(Minus 1)][listItem(Minus 2)][listItem(Minus 3)])] Minuses loose: [unorderedList([listItem(Minus 1)][listItem(Minus 2)][listItem(Minus 3)])] heading2(Ordered) Tight: [orderedList([listItem(First)][listItem(Second)][listItem(Third)])] and: [orderedList([listItem(One)][listItem(Two)][listItem(Three)])] Loose using tabs: [orderedList([listItem(First)][listItem(Second)][listItem(Third)])] and using spaces: [orderedList([listItem(One)][listItem(Two)][listItem(Three)])] Multiple paragraphs: [orderedList([listItem(Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back.)][listItem(Item 2.)][listItem(Item 3.)])] heading2(Nested) [unorderedList([listItem(Tab [unorderedList([listItem(Tab [unorderedList([listItem(Tab)])])])])])] Here's another: [orderedList([listItem(First)][listItem(Second: [unorderedList([listItem(Fee)][listItem(Fie)][listItem(Foe)])])][listItem(Third)])] Same thing but with paragraphs: [orderedList([listItem(First)][listItem(Second: [unorderedList([listItem(Fee)][listItem(Fie)][listItem(Foe)])])][listItem(Third)])] This was an error in Markdown 1.0.1: [unorderedList([listItem(this [unorderedList([listItem(sub)])]that)])]" `; exports[`rebber: remark specs with config: custom macros ordered-different-types 1`] = ` "[orderedList([listItem(foo)][listItem(bar 3) baz)])]" `; exports[`rebber: remark specs with config: custom macros ordered-with-parentheses 1`] = ` "heading2(Ordered) Tight: 1) First 2) Second 3) Third and: 1) One 2) Two 3) Three Loose using tabs: 1) First 2) Second 3) Third and using spaces: 1) One 2) Two 3) Three Multiple paragraphs: 1) Item 1, graf one. [code(Item 2. graf two. The quick brown fox jumped over the lazy dog's back.)] 2) Item 2. 3) Item 3." `; exports[`rebber: remark specs with config: custom macros paragraphs-and-indentation 1`] = ` "heading1(Without lines.) This is a paragraph and this is further text This is a paragraph and this is further text This is a paragraph with some asterisks [code(***)] This is a paragraph followed by a horizontal rule [thematicBreak(---)] heading1(With lines.) This is a paragraph [code(and this is code)] This is a paragraph and this is a new paragraph This is a paragraph with some asterisks in a code block [code(***)] This is a paragraph followed by a horizontal rule [thematicBreak(---)]" `; exports[`rebber: remark specs with config: custom macros paragraphs-empty 1`] = ` "aaa heading1(aaa) bbb ccc" `; exports[`rebber: remark specs with config: custom macros ref-paren 1`] = ` "[linkReference(reference=hi, content=hi)] [definition(identifier=hi, url=/url, title=there)]" `; exports[`rebber: remark specs with config: custom macros reference-image-empty-alt 1`] = ` "\\\\includegraphics{/xyz.png} [definition(identifier=1, url=/xyz.png, title=null)]" `; exports[`rebber: remark specs with config: custom macros reference-link-escape 1`] = ` "[b*r*], [linkReference(reference=b\\\\*r*, content=b*r*)], [linkReference(reference=b\\\\*r*, content=b*r*)]. \\\\includegraphics{http://google.com}, \\\\includegraphics{http://google.com}, \\\\includegraphics{http://google.com}. [definition(identifier=b\\\\*r*, url=http://google.com, title=null)]" `; exports[`rebber: remark specs with config: custom macros reference-link-not-closed 1`] = ` "[bar]bar [bar] [bar]" `; exports[`rebber: remark specs with config: custom macros reference-link-with-angle-brackets 1`] = ` "[linkReference(reference=foo, content=foo)] [definition(identifier=foo, url=./url with spaces, title=null)]" `; exports[`rebber: remark specs with config: custom macros reference-link-with-multiple-definitions 1`] = ` "[linkReference(reference=foo, content=foo)] [definition(identifier=foo, url=first, title=null)] [definition(identifier=foo-1, url=second, title=null)]" `; exports[`rebber: remark specs with config: custom macros same-bullet 1`] = ` "[unorderedList([listItem(test)])] [unorderedList([listItem(test)])] [unorderedList([listItem(test)])]" `; exports[`rebber: remark specs with config: custom macros stringify-escape 1`] = ` "Characters that should be escaped in general: \\\\textbackslash{} \` * [ Characters that shouldn't: \\\\{\\\\}]()\\\\#+-.!>\\"\\\\$\\\\%',/:;=?@\\\\textasciicircum{}\\\\textasciitilde{} Underscores are \\\\_escaped\\\\_ unless they appear in\\\\_the\\\\_middle\\\\_of\\\\_a\\\\_word. or \\\\textbf{\\\\_here}, or here\\\\_\\\\_ Ampersands are escaped only when they would otherwise start an entity: [unorderedList([listItem(\\\\textbackslash{}©cat \\\\textbackslash{}\\\\& \\\\textbackslash{}\\\\&)][listItem(\\\\©cat \\\\& \\\\&\\\\#x26)][listItem(But: ©cat; \\\\texttt{\\\\≬} \\\\&foo; \\\\& AT\\\\&T \\\\&c)])] Open parenthesis should be escaped after a shortcut reference: [ref](text) And after a shortcut reference and a space (for GitHub): [ref] (text) Hyphen should be escaped at the beginning of a line: - not a list item - not a list item + not a list item Same for angle brackets: > not a block quote And hash signs: \\\\# not a heading \\\\#\\\\# not a subheading Text under a shortcut reference should be preserved verbatim: [unorderedList([listItem([two*three])][listItem([two*three])][listItem([a\\\\textbackslash{}a])][listItem([a\\\\textbackslash{}a])][listItem([a\\\\textbackslash{}\\\\textbackslash{}a])][listItem([a\\\\_a\\\\_a])])] \\\\textbf{GFM:} Colon should be escaped in URLs: [unorderedList([listItem(http\\\\textbackslash{}://user:password@host:port/path?key=value\\\\#fragment)][listItem(https\\\\textbackslash{}://user:password@host:port/path?key=value\\\\#fragment)][listItem(http://user:password@host:port/path?key=value\\\\#fragment)][listItem(https://user:password@host:port/path?key=value\\\\#fragment)])] Double tildes should be \\\\textasciitilde{}\\\\textasciitilde{}escaped\\\\textasciitilde{}\\\\textasciitilde{}. And here: foo\\\\textasciitilde{}\\\\textasciitilde{}. Pipes should not be escaped here: | \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} here & they \\\\\\\\ should & tho|ugh \\\\\\\\ \\\\end{longtblr} And here: | here | they | | ---- | ----- | | should | though | And here: here | they ---- | ------ should | though \\\\textbf{Commonmark:} Open angle bracket should be escaped: [unorderedList([listItem(\\\\textbackslash{}
\\\\textbackslash{}
)][listItem(\\\\textbackslash{})][listItem(
)][listItem()])]" `; exports[`rebber: remark specs with config: custom macros strong-and-em-together-one 1`] = ` "\\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word. \\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word." `; exports[`rebber: remark specs with config: custom macros strong-and-em-together-two 1`] = ` "perform\\\\_complicated\\\\_task do\\\\_this\\\\_and\\\\_do\\\\_that\\\\_and\\\\_another\\\\_thing perform\\\\textit{complicated}task do\\\\textit{this}and\\\\textit{do}that\\\\textit{and}another*thing" `; exports[`rebber: remark specs with config: custom macros strong-emphasis 1`] = ` "Foo \\\\textbf{bar} \\\\textbf{baz}. Foo \\\\textbf{bar} \\\\textbf{baz}." `; exports[`rebber: remark specs with config: custom macros strong-initial-white-space 1`] = ` "\\\\textbf{ bar }. \\\\textbf{ bar }." `; exports[`rebber: remark specs with config: custom macros table 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Heading 1 & \\\\textbf{H}eading 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 & Header 3 & Header 4 \\\\\\\\ Cell 1 & Cell 2 & Cell 3 & Cell 4 \\\\\\\\ Cell 5 & Cell 6 & Cell 7 & Cell 8 \\\\\\\\ \\\\end{longtblr} [code(Test code)] \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 & Header 3 & Header 4 \\\\\\\\ Cell 1 & Cell 2 & Cell 3 & Cell 4 \\\\\\\\ \\\\textit{Cell 5} & Cell 6 & Cell 7 & Cell 8 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-empty-initial-cell 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} & a & c \\\\\\\\ a & b & c \\\\\\\\ a & b & c \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-escaped-pipes 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} First & Second & third \\\\\\\\ first & second & third \\\\\\\\ first & second | second & third | \\\\\\\\ first & second \\\\textbackslash{} & third \\\\textbackslash{} \\\\\\\\ first & second \\\\textbackslash{}| second & third \\\\textbackslash{}| \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-in-list 1`] = ` "[unorderedList([listItem(Unordered: \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr})][listItem(Ordered: \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr})])]" `; exports[`rebber: remark specs with config: custom macros table-invalid-alignment 1`] = ` "Missing alignment characters: | a | b | c | | |---|---| | d | e | f | [thematicBreak(---)] | a | b | c | |---|---| | | d | e | f | Invalid characters: | a | b | c | |---|-*-|---| | d | e | f |" `; exports[`rebber: remark specs with config: custom macros table-loose 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-no-body 1`] = ` "heading1(Foo) \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Name & GitHub & Twitter \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-no-end-of-line 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} foo & bar \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-one-column 1`] = ` "This is a table: \\\\begin{longtblr}{colspec={X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} a \\\\\\\\ b \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-one-row 1`] = ` "This is a table: \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} a & b & c \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-padded 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-pipes-in-code 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} abc & head2 \\\\\\\\ x & \` & & & \` \\\\\\\\ x & \` \\\\\\\\ x & \` & \` \\\\\\\\ x & \\\\texttt{f} \\\\\\\\ x & \`\`\`\` \\\\\\\\ x & \`\\\\texttt{f} \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} abc & head2 \\\\\\\\ x & \` \\\\\\\\ x & \` & \` \\\\\\\\ x & \\\\texttt{f} \\\\\\\\ x & \`\`\`\` \\\\\\\\ x & \`\\\\texttt{f} \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-spaced 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros table-with-image 1`] = ` "Someone wanted to do this, let's implement it! \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} c1 & c2 \\\\\\\\ c3 & \\\\includegraphics{https://zestedesavoir.com/media/galleries/426/56dc4a1e-416b-4a9d-830d-95b45d58a17a.png} \\\\\\\\ \\\\end{longtblr}" `; exports[`rebber: remark specs with config: custom macros tabs 1`] = ` "[unorderedList([listItem(this is a list item indented with tabs)][listItem(this is a list item indented with spaces)])] Code: [code(this code block is indented by one tab)] And: [code( this code block is indented by two tabs)] And: [code(+ this is an example list item indented with tabs + this is an example list item indented with spaces)]" `; exports[`rebber: remark specs with config: custom macros tabs-and-spaces 1`] = ` "[unorderedList([listItem(this is a list item indented with tabs)][listItem(this is a list item indented with spaces)])] Code: [code(this code block is indented by one tab)] And: [code( this code block is indented by two tabs)] And: [code(+ this is an example list item indented with tabs + this is an example list item indented with spaces)]" `; exports[`rebber: remark specs with config: custom macros task-list 1`] = ` "heading1(Empty items) [unorderedList([listItem([ ])][listItem([ ])])] [orderedList([listItem([x])][listItem([X])])] heading1(Single space) [unorderedList(\\\\item[$\\\\square$]\\\\relax \\\\item[$\\\\square$]\\\\relax )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax \\\\item[$\\\\boxtimes$]\\\\relax )] heading1(Tab) [unorderedList(\\\\item[$\\\\square$]\\\\relax \\\\item[$\\\\square$]\\\\relax )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax \\\\item[$\\\\boxtimes$]\\\\relax )] heading1(No white space with content) [unorderedList([listItem([ ]Hello;)][listItem([ ]World;)])] [orderedList([listItem([x]Foo.)][listItem([X]Bar)])] heading1(Single space with content) [unorderedList(\\\\item[$\\\\square$]\\\\relax Hello; \\\\item[$\\\\square$]\\\\relax World; )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax World :D )] heading1(Single tab with content) [unorderedList(\\\\item[$\\\\square$]\\\\relax Hello; \\\\item[$\\\\square$]\\\\relax World; )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax Hello. )] heading1(Multiple spaces with content) [unorderedList(\\\\item[$\\\\square$]\\\\relax [code(Hello;)] \\\\item[$\\\\square$]\\\\relax [code(World;)] )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax Bar. )] heading1(Multiple tabs with content) [unorderedList(\\\\item[$\\\\square$]\\\\relax [code(Hello;)] \\\\item[$\\\\square$]\\\\relax [code(World;)] )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax [code(Foo.)] \\\\item[$\\\\boxtimes$]\\\\relax [code(Bar.)] )] heading1(Mixed tabs and spaces) [unorderedList(\\\\item[$\\\\square$]\\\\relax [code( Hello;)] )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax [code(World;)] )] [unorderedList(\\\\item[$\\\\square$]\\\\relax [code( Hello;)] \\\\item[$\\\\square$]\\\\relax World. )] [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax Bar. )] heading1(Line breaks) [unorderedList([listItem([ ] Hello;)])] [orderedList([listItem([ ] Hello;)])] heading1(Multiple unfinished characters) [unorderedList([listItem([ ] Hello;)])] [orderedList([listItem([ ] World;)][listItem([ ] Hello;)][listItem([ ] World.)])]" `; exports[`rebber: remark specs with config: custom macros task-list-ordered 1`] = ` "[orderedList(\\\\item[$\\\\square$]\\\\relax Mercury; [listItem([] Venus (this one’s invalid);)]\\\\item[$\\\\boxtimes$]\\\\relax Earth: [orderedList(\\\\item[$\\\\boxtimes$]\\\\relax Moon. )] \\\\item[$\\\\square$]\\\\relax Mars; [listItem([] Neptune (this one’s also invalid).)])]" `; exports[`rebber: remark specs with config: custom macros task-list-unordered-asterisk 1`] = ` "[unorderedList(\\\\item[$\\\\square$]\\\\relax Mercury; [listItem([] Venus (this one’s invalid);)]\\\\item[$\\\\boxtimes$]\\\\relax Earth: [unorderedList(\\\\item[$\\\\boxtimes$]\\\\relax Moon. )] \\\\item[$\\\\square$]\\\\relax Mars; [listItem([] Neptune (this one’s also invalid).)])]" `; exports[`rebber: remark specs with config: custom macros task-list-unordered-dash 1`] = ` "[unorderedList(\\\\item[$\\\\square$]\\\\relax Mercury; [listItem([] Venus (this one’s invalid);)]\\\\item[$\\\\boxtimes$]\\\\relax Earth: [unorderedList(\\\\item[$\\\\boxtimes$]\\\\relax Moon. )] \\\\item[$\\\\square$]\\\\relax Mars; [listItem([] Neptune (this one’s also invalid).)])]" `; exports[`rebber: remark specs with config: custom macros task-list-unordered-plus 1`] = ` "[unorderedList(\\\\item[$\\\\square$]\\\\relax Mercury; [listItem([] Venus (this one’s invalid);)]\\\\item[$\\\\boxtimes$]\\\\relax Earth: [unorderedList(\\\\item[$\\\\boxtimes$]\\\\relax Moon. )] \\\\item[$\\\\square$]\\\\relax Mars; [listItem([] Neptune (this one’s also invalid).)])]" `; exports[`rebber: remark specs with config: custom macros tidyness 1`] = ` "[blockquote(A list within a blockquote: [unorderedList([listItem(asterisk 1)][listItem(asterisk 2)][listItem(asterisk 3)])])]" `; exports[`rebber: remark specs with config: custom macros title-attributes 1`] = ` "heading1(Links) \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Implementation & Characters & Nested & Mismatched & Escaped & Named Entities & Numbered Entities \\\\\\\\ Markdown.pl & \\\\texttt{\\"} & Yes & Yes & No & Yes & Yes \\\\\\\\ GitHub & \\\\texttt{\\"} & Yes & Yes & No & No & No \\\\\\\\ CommonMark & \\\\texttt{\\"} & No & No & Yes & Yes & Yes \\\\\\\\ Markdown.pl & \\\\texttt{'} & Yes & Yes & No & Yes & Yes \\\\\\\\ GitHub & \\\\texttt{'} & Yes & Yes & No & No & No \\\\\\\\ CommonMark & \\\\texttt{'} & No & No & Yes & Yes & Yes \\\\\\\\ Markdown.pl & \\\\texttt{()} & - & - & - & - & - \\\\\\\\ GitHub & \\\\texttt{()} & - & - & - & - & - \\\\\\\\ CommonMark & \\\\texttt{()} & No & Yes & Yes & Yes & Yes \\\\\\\\ \\\\end{longtblr} heading2(Double quotes) \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} heading2(Single quotes) \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} heading1(Images) heading2(Double quotes) \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} heading2(Single quotes) \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png}" `; exports[`rebber: remark specs with config: custom macros toplevel-paragraphs 1`] = ` "hello world how are you how are you hello world [code(how are you)] hello world [thematicBreak(---)] hello world heading1(how are you) hello world heading1(how are you) hello world [blockquote(how are you)] hello world [unorderedList([listItem(how are you)])] hello world
how are you
hello world how are you hello [linkReference(reference=how, content=world)] [definition(identifier=how, url=/are/you, title=null)]
hello
hello" `; exports[`rebber: remark specs with config: custom macros tricky-list 1`] = ` "\\\\textbf{hello} \\\\textit{world} [unorderedList([listItem(hello world)])] \\\\textbf{hello} \\\\textit{world} [unorderedList([listItem(hello world)])] \\\\textbf{hello} \\\\textit{world} [unorderedList([listItem(Hello world)])] \\\\textbf{hello} \\\\textit{world} [unorderedList([listItem(hello world)])]" `; exports[`toLaTeX: remark specs amps-and-angles-encoding: amps-and-angles-encoding 1`] = ` "AT\\\\&T has an ampersand in their name. AT\\\\&T is another way to write it. This \\\\& that. 4 < 5. 6 > 5. Here's a \\\\hyperref[1]{link} with an ampersand in the URL. Here's a link with an amersand in the link text: \\\\hyperref[2]{AT\\\\&T}. Here's an inline \\\\externalLink{link}{/script?foo=1\\\\&bar=2}. Here's an inline \\\\externalLink{link}{/script?foo=1\\\\&bar=2}. \\\\footnote{\\\\label{1}\\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2}} \\\\footnote{\\\\label{2}\\\\externalLink{http://att.com/}{http://att.com/}}" `; exports[`toLaTeX: remark specs auto-link: auto-link 1`] = ` "Link: \\\\externalLink{http://example.com/}{http://example.com/}. Link to an email: \\\\externalLink{somename@example.com}{mailto:somename@example.com}. Link to an email: \\\\externalLink{somename@example.com}{mailto:somename@example.com}. With an ampersand: \\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2} \\\\begin{itemize} \\\\item\\\\relax In a list? \\\\item\\\\relax \\\\externalLink{http://example.com/}{http://example.com/} \\\\item\\\\relax It should. \\\\end{itemize} \\\\begin{Quotation} Blockquoted: \\\\externalLink{http://example.com/}{http://example.com/} \\\\end{Quotation} Auto-links should not occur here: \\\\texttt{} \\\\begin{CodeBlock}{text} or here: \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs auto-link-invalid: auto-link-invalid 1`] = ` " Hash: \\\\# Period: . Bang: ! Plus: + Minus: - \\\\textbf{GFM:} Pipe: | Tilde: \\\\textasciitilde{} \\\\textbf{Commonmark:} Quote: \\\\textbackslash{}\\" Dollar: \\\\textbackslash{}\\\\$ Percentage: \\\\textbackslash{}\\\\% Ampersand: \\\\textbackslash{}\\\\& Single quote: \\\\textbackslash{}' Comma: \\\\textbackslash{}, Forward slash: \\\\textbackslash{}/ Colon: \\\\textbackslash{}: Semicolon: \\\\textbackslash{}; Less-than: \\\\textbackslash{}< Equals: \\\\textbackslash{}= Question mark: \\\\textbackslash{}? At-sign: \\\\textbackslash{}@ Caret: \\\\textbackslash{}\\\\textasciicircum{} New line: \\\\textbackslash{} only works in paragraphs. These should not, because they occur within a code block: \\\\begin{CodeBlock}{text} Backslash: \\\\\\\\ Backtick: \\\\\` Asterisk: \\\\* Underscore: \\\\_ Left brace: \\\\{ Right brace: \\\\} Left bracket: \\\\[ Right bracket: \\\\] Left paren: \\\\( Right paren: \\\\) Greater-than: \\\\> Hash: \\\\# Period: \\\\. Bang: \\\\! Plus: \\\\+ Minus: \\\\- \\\\end{CodeBlock} \\\\textbf{GFM:} \\\\begin{CodeBlock}{text} Pipe: \\\\| Tilde: \\\\~ \\\\end{CodeBlock} \\\\textbf{Commonmark:} \\\\begin{CodeBlock}{text} Quote: \\\\\\" Dollar: \\\\$ Percentage: \\\\% Ampersand: \\\\& Single quote: \\\\' Comma: \\\\, Forward slash: \\\\/ Colon: \\\\: Semicolon: \\\\; Less-than: \\\\< Equals: \\\\= Question mark: \\\\? At-sign: \\\\@ Caret: \\\\^ New line: \\\\ only works in paragraphs. \\\\end{CodeBlock} Nor should these, which occur in code spans: Backslash: \\\\texttt{\\\\textbackslash{}\\\\textbackslash{}} Backtick: \\\\texttt{\\\\textbackslash{}\`} Asterisk: \\\\texttt{\\\\textbackslash{}*} Underscore: \\\\texttt{\\\\textbackslash{}\\\\_} Left brace: \\\\texttt{\\\\textbackslash{}\\\\{} Right brace: \\\\texttt{\\\\textbackslash{}\\\\}} Left bracket: \\\\texttt{\\\\textbackslash{}[} Right bracket: \\\\texttt{\\\\textbackslash{}]} Left paren: \\\\texttt{\\\\textbackslash{}(} Right paren: \\\\texttt{\\\\textbackslash{})} Greater-than: \\\\texttt{\\\\textbackslash{}>} Hash: \\\\texttt{\\\\textbackslash{}\\\\#} Period: \\\\texttt{\\\\textbackslash{}.} Bang: \\\\texttt{\\\\textbackslash{}!} Plus: \\\\texttt{\\\\textbackslash{}+} Minus: \\\\texttt{\\\\textbackslash{}-} \\\\textbf{GFM:} Pipe: \\\\texttt{\\\\textbackslash{}|} Tilde: \\\\texttt{\\\\textbackslash{}\\\\textasciitilde{}} \\\\textbf{Commonmark:} Quote: \\\\texttt{\\\\textbackslash{}\\"} Dollar: \\\\texttt{\\\\textbackslash{}\\\\$} Percentage: \\\\texttt{\\\\textbackslash{}\\\\%} Ampersand: \\\\texttt{\\\\textbackslash{}\\\\&} Single quote: \\\\texttt{\\\\textbackslash{}'} Comma: \\\\texttt{\\\\textbackslash{},} Forward slash: \\\\texttt{\\\\textbackslash{}/} Colon: \\\\texttt{\\\\textbackslash{}:} Semicolon: \\\\texttt{\\\\textbackslash{};} Less-than: \\\\texttt{\\\\textbackslash{}<} Equals: \\\\texttt{\\\\textbackslash{}=} Question mark: \\\\texttt{\\\\textbackslash{}?} At-sign: \\\\texttt{\\\\textbackslash{}@} Caret: \\\\texttt{\\\\textbackslash{}\\\\textasciicircum{}} New line: \\\\texttt{\\\\textbackslash{} } only works in paragraphs. These should get escaped, even though they're matching pairs for other Markdown constructs: *asterisks* \\\\_underscores\\\\_ \`backticks\` This is a code span with a literal backslash-backtick sequence: \\\\texttt{\\\\textbackslash{}\`} This is a tag with unescaped backticks bar. This is a tag with backslashes bar. " `; exports[`toLaTeX: remark specs block-elements: block-elements 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Different lists should receive two newline characters between them. \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax This is another list. \\\\end{itemize} \\\\begin{Quotation} \\\\begin{itemize} \\\\item\\\\relax The same goes for lists in block quotes. \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax This is another list. \\\\end{itemize} \\\\end{Quotation} \\\\begin{itemize} \\\\item\\\\relax And for lists in lists: \\\\begin{enumerate} \\\\item\\\\relax First sublist. \\\\end{enumerate} \\\\end{itemize} \\\\begin{CodeBlock}{text} 1. Second sublist. \\\\end{CodeBlock} And for lists followed by indented code blocks: \\\\begin{itemize} \\\\item\\\\relax This is a paragraph in a list \\\\end{itemize} \\\\begin{CodeBlock}{text} And this is code(); \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs blockquote-indented: blockquote-indented 1`] = ` "\\\\begin{Quotation} bar baz \\\\end{Quotation} " `; exports[`toLaTeX: remark specs blockquote-lazy-code: blockquote-lazy-code 1`] = ` "\\\\begin{Quotation} \\\\begin{CodeBlock}{text} foo bar \\\\end{CodeBlock} \\\\end{Quotation} " `; exports[`toLaTeX: remark specs blockquote-lazy-fence: blockquote-lazy-fence 1`] = ` "\\\\begin{Quotation} \\\\begin{CodeBlock}{text} aNormalCodeBlockInABlockqoute(); \\\\end{CodeBlock} \\\\end{Quotation} A paragraph. \\\\begin{Quotation} \\\\begin{CodeBlock}{text} thisIsAlsoSomeCodeInABlockquote(); \\\\end{CodeBlock} \\\\end{Quotation} A paragraph. \\\\begin{Quotation} \\\\begin{CodeBlock}{text} aNonTerminatedCodeBlockInABlockquote(); \\\\end{CodeBlock} aNewCodeBlockFollowingTheBlockQuote(); \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} \\\\end{Quotation} A paragraph. \\\\begin{Quotation} Something in a blockquote. \\\\begin{CodeBlock}{text} aNewCodeBlock(); \\\\end{CodeBlock} \\\\end{Quotation} " `; exports[`toLaTeX: remark specs blockquote-lazy-list: blockquote-lazy-list 1`] = ` "\\\\begin{Quotation} This is a blockquote. \\\\begin{itemize} \\\\item\\\\relax And in normal mode this is an internal list, but in commonmark this is a top level list. \\\\end{itemize} \\\\end{Quotation} " `; exports[`toLaTeX: remark specs blockquote-lazy-rule: blockquote-lazy-rule 1`] = ` "\\\\begin{Quotation} This is a blockquote. Followed by a rule. \\\\horizontalLine \\\\end{Quotation} " `; exports[`toLaTeX: remark specs blockquote-list-item: blockquote-list-item 1`] = ` "This fails in markdown.pl and upskirt: \\\\begin{itemize} \\\\item\\\\relax hello \\\\begin{Quotation} world \\\\end{Quotation} \\\\end{itemize} " `; exports[`toLaTeX: remark specs blockquotes: blockquotes 1`] = ` "\\\\begin{Quotation} This is a blockquote. \\\\end{Quotation} \\\\begin{Quotation} This is, in commonmark mode, another blockquote. \\\\end{Quotation} " `; exports[`toLaTeX: remark specs blockquotes-empty-lines: blockquotes-empty-lines 1`] = ` "\\\\begin{Quotation} Note there is no space on the following line. Note there is no space on the preceding line. \\\\end{Quotation} " `; exports[`toLaTeX: remark specs blockquotes-with-code-blocks: blockquotes-with-code-blocks 1`] = ` "\\\\begin{Quotation} Example: \\\\begin{CodeBlock}{text} sub status { print \\"working\\"; } \\\\end{CodeBlock} Or: \\\\begin{CodeBlock}{text} sub status { return \\"working\\"; } \\\\end{CodeBlock} \\\\end{Quotation} " `; exports[`toLaTeX: remark specs bom: bom 1`] = ` "\\\\part{Hello from a BOM} Be careful when editing this file! " `; exports[`toLaTeX: remark specs breaks-hard: breaks-hard 1`] = ` "These are not breaks: Look at the pretty line breaks. These are breaks: Look at the \\\\\\\\ pretty line \\\\\\\\ breaks. In \\\\texttt{commonmark: true} mode, an escaped newline character is exposed as a \\\\texttt{break} node: Look at the\\\\textbackslash{} pretty line\\\\textbackslash{} breaks. " `; exports[`toLaTeX: remark specs case-insensitive-refs: case-insensitive-refs 1`] = ` "\\\\hyperref[hi]{hi} \\\\footnote{\\\\label{hi}\\\\externalLink{/url}{/url}}" `; exports[`toLaTeX: remark specs code-block: code-block 1`] = ` "Tildes: \\\\begin{CodeBlock}{javascript} alert('Hello World!'); \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs code-block-escape: code-block-escape 1`] = ` "A little flaw: \\\\begin{CodeBlock}{python} \\\\end{CodeBlock} An ingenuous flaw: \\\\begin{CodeBlock}{text} \\\\input{/etc/passwd} \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs code-block-indentation: code-block-indentation 1`] = ` "Fenced code blocks are normally not exdented, however, when the initial fence is indented by spaces, the value of the code is exdented by up to that amount of spaces. \\\\begin{CodeBlock}{text} This is a code block... ...which is not exdented. \\\\end{CodeBlock} But... \\\\begin{CodeBlock}{text} This one... ...is. \\\\end{CodeBlock} And... \\\\begin{CodeBlock}{text} So is this... ...one. \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs code-block-nesting-bug: code-block-nesting-bug 1`] = ` "GitHub, thus RedCarpet, has a bug where “nested” fenced code blocks, even with shorter fences, can exit their actual “parent” block. Note that this bug does not occur on indented code-blocks. \\\\begin{CodeBlock}{foo} \`\`\`bar baz \`\`\` \\\\end{CodeBlock} Even with a different fence marker: \\\\begin{CodeBlock}{foo} ~~~bar baz ~~~ \\\\end{CodeBlock} And reversed: \\\\begin{CodeBlock}{foo} ~~~bar baz ~~~ \\\\end{CodeBlock} \\\\begin{CodeBlock}{foo} \`\`\`bar baz \`\`\` \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs code-blocks: code-blocks 1`] = ` "code block on the first line Regular text. \\\\begin{CodeBlock}{text} code block indented by spaces \\\\end{CodeBlock} Regular text. \\\\begin{CodeBlock}{text} the lines in this block all contain trailing spaces \\\\end{CodeBlock} Regular Text. \\\\begin{CodeBlock}{text} code block on the last line \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs code-spans: code-spans 1`] = ` "\\\\texttt{} Fix for backticks within HTML tag: like this Here's how you put \\\\texttt{\`backticks\`} in a code span. Additionally, empty code spans are NOT supported: \`\`. Here’s an example, \\\\texttt{foo \` bar }. And here, \\\\texttt{\`\`}. \\\\texttt{// this is also inline code} So is this \\\\texttt{foo bar baz}. And this \\\\texttt{foo \`\` bar} And \\\\texttt{this\\\\textbackslash{}}but this is text\`. " `; exports[`toLaTeX: remark specs def-blocks: def-blocks 1`] = ` "\\\\begin{Quotation} hello \\\\footnote{\\\\label{1}\\\\externalLink{hello}{hello}} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} hello \\\\end{Quotation} \\\\footnote{\\\\label{2}\\\\externalLink{hello}{hello}} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax \\\\footnote{\\\\label{3}\\\\externalLink{hello}{hello}} \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\end{itemize} \\\\footnote{\\\\label{4}\\\\externalLink{hello}{hello}} \\\\begin{Quotation} foo bar \\\\end{Quotation} \\\\footnote{\\\\label{1-1}\\\\externalLink{foo}{foo}} \\\\begin{Quotation} bar \\\\end{Quotation} " `; exports[`toLaTeX: remark specs definition-newline: definition-newline 1`] = ` "\\\\hyperref[baz]{baz}: /url ( ) [foo]: /url \\" \\" [bar]: /url ' ' \\\\footnote{\\\\label{baz}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{baz-1}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{baz-1-1}\\\\externalLink{/url}{/url}} \\\\hyperref[baz]{baz}: /url 'foo " `; exports[`toLaTeX: remark specs definition-unclosed: definition-unclosed 1`] = ` "[foo]: \\\\footnote{\\\\label{bar}\\\\externalLink{( [foo]: \\" [bar]: ' " `; exports[`toLaTeX: remark specs deletion: deletion 1`] = ` "hello \\\\sout{hi} world " `; exports[`toLaTeX: remark specs double-link: double-link 1`] = ` "

Already linked: http://example.com/.

Already linked: \\\\externalLink{http://example.com/}{http://example.com/}. Already linked: \\\\textbf{http://example.com/}. " `; exports[`toLaTeX: remark specs emphasis: emphasis 1`] = ` "\\\\textit{emphasis}. \\\\textbf{strong}. " `; exports[`toLaTeX: remark specs emphasis-empty: emphasis-empty 1`] = ` "Hello ** ** world. Hello \\\\_\\\\_ \\\\_\\\\_ world. Hello * * world. Hello \\\\_ \\\\_ world. " `; exports[`toLaTeX: remark specs emphasis-escaped-final-marker: emphasis-escaped-final-marker 1`] = ` "*bar* **bar** \\\\_bar\\\\_ \\\\_\\\\_bar\\\\_\\\\_ " `; exports[`toLaTeX: remark specs emphasis-internal: emphasis-internal 1`] = ` "These words should\\\\_not\\\\_be\\\\_emphasized. " `; exports[`toLaTeX: remark specs empty: empty 1`] = `""`; exports[`toLaTeX: remark specs entities: entities 1`] = ` "Lots of entities are supported in mdast:  , \\\\&, ©, Æ, Ď, ¾, ℋ, ⅆ, ∲, \\\\&c. Even some entities with a missing terminal semicolon are parsed correctly (as per the HTML5 spec): ÿ, á, ©, and \\\\&. However, \\\\&MadeUpEntities; are kept in the document. Entities even work in the language flag of fenced code blocks: \\\\begin{CodeBlock}{some—language} alert('Hello'); \\\\end{CodeBlock} Or in \\\\externalLink{línks}{\\\\textasciitilde{}/some—file} Or in \\\\includegraphics{~/an–image.png} But, entities are not interpreted in \\\\texttt{inline c\\\\öde}, or in code blocks: \\\\begin{CodeBlock}{text} CÖDE block. \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs entities-advanced: entities-advanced 1`] = ` "\\\\begin{Quotation} However, \\\\&MadeUpEntities; are kept in the document. \\\\end{Quotation} \\\\begin{Quotation} Entities even work in the language flag of fenced code blocks: \\\\end{Quotation} \\\\begin{Quotation} \\\\begin{CodeBlock}{some©language} alert('Hello'); \\\\end{CodeBlock} \\\\end{Quotation} \\\\begin{Quotation} And in an auto-link: \\\\externalLink{http://example©xample.com}{http://example\\\\©xample.com} \\\\end{Quotation} \\\\begin{Quotation} Foo and bar and http://example©xample.com and baz. \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\externalLink{l©nks}{\\\\textasciitilde{}/some\\\\©file} \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\externalLink{l©lnks}{\\\\textasciitilde{}/some\\\\©file} \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\includegraphics{~/some©file} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} Or in \\\\includegraphics{~/some©file} \\\\end{Quotation} \\\\begin{Quotation} Or in \\\\includegraphics{undefined} \\\\end{Quotation} \\\\begin{Quotation} \\\\footnote{\\\\label{1}\\\\externalLink{http://example\\\\©xample.com}{http://example\\\\©xample.com}} \\\\end{Quotation} \\\\begin{Quotation} \\\\footnote{\\\\label{ 1 }\\\\externalLink{http://example\\\\©xample.com}{http://example\\\\©xample.com}} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} But, entities are not interpreted in \\\\texttt{inline c\\\\öde}, or in code blocks: \\\\end{Quotation} \\\\begin{Quotation} \\\\begin{CodeBlock}{text} CÖDE block. \\\\end{CodeBlock} \\\\end{Quotation} " `; exports[`toLaTeX: remark specs escaped-angles: escaped-angles 1`] = ` "> " `; exports[`toLaTeX: remark specs fenced-code: fenced-code 1`] = ` "\\\\begin{CodeBlock}{js} var a = 'hello'; console.log(a + ' world'); \\\\end{CodeBlock} \\\\begin{CodeBlock}{bash} echo \\"hello, \${WORLD}\\" \\\\end{CodeBlock} \\\\begin{CodeBlock}{longfence} Q: What do you call a tall person who sells stolen goods? \\\\end{CodeBlock} \\\\begin{CodeBlock}{ManyTildes} A longfence! \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs fenced-code-empty: fenced-code-empty 1`] = ` "Normal with language tag: \\\\begin{CodeBlock}{js} \\\\end{CodeBlock} With white space: \\\\begin{CodeBlock}{bash} \\\\end{CodeBlock} With very long fences: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} With nothing: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs fenced-code-trailing-characters: fenced-code-trailing-characters 1`] = ` "\\\\begin{CodeBlock}{js} foo(); \`\`\`bash \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs fenced-code-trailing-characters-2: fenced-code-trailing-characters-2 1`] = ` "\\\\begin{CodeBlock}{text} \`\`\` aaa \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs fenced-code-white-space-after-flag: fenced-code-white-space-after-flag 1`] = ` "\\\\begin{CodeBlock}{js} foo(); \\\\end{CodeBlock} \\\\begin{CodeBlock}{bash} echo \\"hello, \${WORLD}\\" \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs hard-wrapped-paragraphs-with-list-like-lines: hard-wrapped-paragraphs-with-list-like-lines 1`] = ` "In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the 123. middle of a paragraph looked like a list item. Here's one with a bullet. \\\\begin{itemize} \\\\item\\\\relax criminey. \\\\end{itemize} Non-GFM does not create a list for either. GFM does not create a list for \\\\texttt{8.}, but does for \\\\texttt{*}. CommonMark creates a list for both. All versions create lists for the following. \\\\begin{itemize} \\\\item\\\\relax Here's one with a bullet. \\\\begin{itemize} \\\\item\\\\relax criminey. \\\\end{itemize} \\\\end{itemize} ...and the following: \\\\begin{enumerate} \\\\item\\\\relax In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. \\\\end{enumerate} " `; exports[`toLaTeX: remark specs heading: heading 1`] = ` "\\\\part{Heading 1} \\\\chapter{Heading 2} \\\\section{Heading 4} \\\\subsection{Heading 4} \\\\subsubsection{Heading 5} \\\\paragraph{Heading 6} " `; exports[`toLaTeX: remark specs heading-atx-closed-trailing-white-space: heading-atx-closed-trailing-white-space 1`] = ` "\\\\part{Foo} \\\\chapter{Bar} " `; exports[`toLaTeX: remark specs heading-atx-empty: heading-atx-empty 1`] = ` "\\\\part{} \\\\chapter{} \\\\section{} \\\\subsection{} \\\\subsubsection{} \\\\paragraph{} " `; exports[`toLaTeX: remark specs heading-in-blockquote: heading-in-blockquote 1`] = ` "\\\\begin{Quotation} A blockquote with some more text. \\\\end{Quotation} A normal paragraph. \\\\begin{Quotation} \\\\chapter{A blockquote followed by a horizontal rule (in CommonMark).} \\\\end{Quotation} \\\\begin{Quotation} \\\\chapter{A heading in a blockquote} \\\\end{Quotation} " `; exports[`toLaTeX: remark specs heading-in-paragraph: heading-in-paragraph 1`] = ` "Hello \\\\part{World} " `; exports[`toLaTeX: remark specs heading-not-atx: heading-not-atx 1`] = ` "\\\\#This is not a heading, per CommonMark: \\\\externalLink{http://spec.commonmark.org/0.17/\\\\#example-25}{http://spec.commonmark.org/0.17/\\\\#example-25} Kramdown (GitHub) neither supports unspaced ATX-headings. \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h7? \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h8? \\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\#\\\\# h9? More than six \\\\# characters is not a heading: \\\\externalLink{http://spec.commonmark.org/0.26/\\\\#example-33}{http://spec.commonmark.org/0.26/\\\\#example-33} " `; exports[`toLaTeX: remark specs heading-setext-with-initial-spacing: heading-setext-with-initial-spacing 1`] = ` "\\\\part{Heading 1} \\\\chapter{Heading 2} Both these headings caused positional problems in on commit daa344c and before. " `; exports[`toLaTeX: remark specs horizontal-rules: horizontal-rules 1`] = ` "Dashes: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} --- \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} - - - \\\\end{CodeBlock} Asterisks: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} * * * \\\\end{CodeBlock} Underscores: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} ___ \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} _ _ _ \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs horizontal-rules-adjacent: horizontal-rules-adjacent 1`] = ` "\\\\horizontalLine \\\\horizontalLine \\\\horizontalLine The three asterisks are not a Setext header. This is a paragraph. \\\\horizontalLine This is another paragraph. \\\\horizontalLine \\\\chapter{But this is a secondary heading.} \\\\horizontalLine " `; exports[`toLaTeX: remark specs hr: hr 1`] = ` "\\\\horizontalLine " `; exports[`toLaTeX: remark specs hr-list-break: hr-list-break 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax hello world \\\\item\\\\relax how are \\\\end{itemize} \\\\horizontalLine you today? The above asterisks do split the list, but the below ones do not. \\\\begin{itemize} \\\\item\\\\relax hello world \\\\item\\\\relax how are \\\\item\\\\relax \\\\horizontalLine you today? \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax Neither do these \\\\item\\\\relax how are \\\\item\\\\relax \\\\begin{itemize} \\\\item\\\\relax \\\\begin{itemize} \\\\item\\\\relax you today? \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax But these do \\\\item\\\\relax how are \\\\end{itemize} \\\\horizontalLine you today? " `; exports[`toLaTeX: remark specs html-advanced: html-advanced 1`] = ` "Simple block on one line:
foo
And nested without indentation:
foo
\\"/>
bar
" `; exports[`toLaTeX: remark specs html-attributes: html-attributes 1`] = ` "\\\\part{Block-level}
foo " `; exports[`toLaTeX: remark specs html-comments: html-comments 1`] = ` "Paragraph one. What follows is not an HTML comment because it contains two consecutive dashes: \\\\externalLink{https://html.spec.whatwg.org/multipage/syntax.html\\\\#comments}{https://html.spec.whatwg.org/multipage/syntax.html\\\\#comments}. But this is fine (in commonmark): And, this is wrong (in commonmark): --> The end. " `; exports[`toLaTeX: remark specs html-declaration: html-declaration 1`] = ` " foo " `; exports[`toLaTeX: remark specs html-indented: html-indented 1`] = ` "
*hello*
*hello* alpha " `; exports[`toLaTeX: remark specs html-processing-instruction: html-processing-instruction 1`] = ` "'; ?>" `; exports[`toLaTeX: remark specs html-simple: html-simple 1`] = ` "Here's a simple block:
foo
This should be a code block, though: \\\\begin{CodeBlock}{text}
foo
\\\\end{CodeBlock} As should this: \\\\begin{CodeBlock}{text}
foo
\\\\end{CodeBlock} Now, nested:
foo
This should just be an HTML comment: Multiline: Code block: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} Just plain comment, with trailing spaces on the line: Code: \\\\begin{CodeBlock}{text}
\\\\end{CodeBlock} Hr's:








" `; exports[`toLaTeX: remark specs html-tags: html-tags 1`] = ` "\\\\part{Block}
<-article>
" `; exports[`toLaTeX: remark specs image-basename-dots: image-basename-dots 1`] = ` "\\\\includegraphics{{x.yz}.png} \\\\includegraphics{/a/{w.x.y.z}.png} \\\\includegraphics{/{w.x.y.z}.png} \\\\includegraphics{/foo.bar/{x.yz}.png} " `; exports[`toLaTeX: remark specs image-empty-alt: image-empty-alt 1`] = ` "\\\\includegraphics{/xyz.png} " `; exports[`toLaTeX: remark specs image-in-link: image-in-link 1`] = ` "\\\\part{\\\\externalLink{\\\\includegraphics{https://img.shields.io/badge/unicorn-approved-ff69b4.svg}}{http://shields.io}} \\\\externalLink{\\\\includegraphics{https://img.shields.io/travis/wooorm/mdast.svg?style=flat}}{https://travis-ci.org/wooorm/mdast} \\\\externalLink{\\\\includegraphics{https://img.shields.io/badge/style-flat--squared-green.svg?style=flat-square}}{http://example.com} " `; exports[`toLaTeX: remark specs image-path-escape: image-path-escape 1`] = ` "\\\\includegraphics{a[b]\\\\ \\\\input{/etc/passwd\\\\image{[a](b)} " `; exports[`toLaTeX: remark specs image-with-pipe: image-with-pipe 1`] = ` "f| " `; exports[`toLaTeX: remark specs images: images 1`] = ` "Lorem ipsum dolor sit \\\\includegraphics{http://amet.com/amet.jpeg}, consectetur adipiscing elit. Praesent dictum purus ullamcorper ligula semper pellentesque. Nulla \\\\includegraphics{http://finibus.com/finibus.png} neque et diam rhoncus convallis. Nam dictum sapien nec sem ultrices fermentum. Nulla \\\\includegraphics{http://facilisi.com/facilisi.gif}. In et feugiat massa. Donec sed sodales metus, ut aliquet quam. Suspendisse nec ipsum risus. Interdum et malesuada fames ac ante ipsum primis in \\\\includegraphics{http://faucibus.com/faucibus.tiff}. " `; exports[`toLaTeX: remark specs invalid-link-definition: invalid-link-definition 1`] = ` "Something[2-3] " `; exports[`toLaTeX: remark specs lazy-blockquotes: lazy-blockquotes 1`] = ` "\\\\begin{Quotation} hi there bud \\\\end{Quotation} " `; exports[`toLaTeX: remark specs link-in-link: link-in-link 1`] = ` "\\\\part{\\\\externalLink{mailto:test@example.com}{http://shields.io}} \\\\externalLink{https://travis-ci.org/wooorm/mdast}{https://travis-ci.org/wooorm/mdast} \\\\externalLink{[](http://example.com \\"An example\\")}{http://example.com} " `; exports[`toLaTeX: remark specs link-spaces: link-spaces 1`] = ` "[alpha] (bravo \\\\includegraphics{undefined} (delta .com) [echo] (\\\\externalLink{http://foxtrot.golf}{http://foxtrot.golf}) \\\\includegraphics{undefined} (india.com/juliett) " `; exports[`toLaTeX: remark specs link-whitespace: link-whitespace 1`] = ` "[alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). [alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). [alpha](\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). \\\\includegraphics{undefined}(\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie). <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. <\\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie>. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie. \\\\externalLink{https://example.com?bravo}{https://example.com?bravo} charlie. " `; exports[`toLaTeX: remark specs link-with-spaces: link-with-spaces 1`] = ` "\\\\externalLink{Hello}{./world and some spaces.html} \\\\externalLink{Hello}{./world and some spaces.html} " `; exports[`toLaTeX: remark specs links: links 1`] = ` "Lorem ipsum dolor sit \\\\externalLink{amet}{http://amet.com}, consectetur adipiscing elit. Praesent dictum purus ullamcorper ligula semper pellentesque. Nulla \\\\externalLink{finibus}{http://finibus.com} neque et diam rhoncus convallis. Nam dictum sapien nec sem ultrices fermentum. Nulla \\\\externalLink{facilisi}{http://facilisi.com}. In et feugiat massa. Donec sed sodales metus, ut aliquet quam. Suspendisse nec ipsum risus. Interdum et malesuada fames ac ante ipsum primis in \\\\externalLink{faucibus}{http://faucibus.com}. " `; exports[`toLaTeX: remark specs links-inline-style: links-inline-style 1`] = ` "Just a \\\\externalLink{URL}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. \\\\externalLink{URL and title}{/url/}. [URL and title]( /url/has space ). [URL and title]( /url/has space/ \\"url has space and title\\"). . " `; exports[`toLaTeX: remark specs links-reference-proto: links-reference-proto 1`] = ` "A \\\\hyperref[tostring]{primary}, \\\\hyperref[constructor]{secondary}, and \\\\hyperref[__proto__]{tertiary} link. \\\\footnote{\\\\label{tostring}\\\\externalLink{http://primary.com}{http://primary.com}} \\\\footnote{\\\\label{__proto__}\\\\externalLink{http://tertiary.com}{http://tertiary.com}} \\\\footnote{\\\\label{constructor}\\\\externalLink{http://secondary.com}{http://secondary.com}}" `; exports[`toLaTeX: remark specs links-reference-style: links-reference-style 1`] = ` "Foo \\\\hyperref[1]{bar}. Foo \\\\hyperref[1]{bar}. Foo \\\\hyperref[1]{bar}. \\\\footnote{\\\\label{1}\\\\externalLink{/url/}{/url/}} With \\\\hyperref[b]{embedded [brackets]}. Indented \\\\hyperref[once]{once}. Indented \\\\hyperref[twice]{twice}. Indented \\\\hyperref[thrice]{thrice}. Indented [four] times. \\\\footnote{\\\\label{once}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{twice}\\\\externalLink{/url}{/url}} \\\\footnote{\\\\label{thrice}\\\\externalLink{/url}{/url}} \\\\begin{CodeBlock}{text} [four]: /url \\\\end{CodeBlock} \\\\footnote{\\\\label{b}\\\\externalLink{/url/}{/url/}} \\\\horizontalLine \\\\hyperref[this]{this} should work So should \\\\hyperref[this]{this}. And \\\\hyperref[this]{this}. And \\\\hyperref[this]{this}. And \\\\hyperref[this]{this}. But not [that]. Nor [that]. Nor [that]. [Something in brackets like \\\\hyperref[this]{this} should work] [Same with \\\\hyperref[this]{this}.] In this case, \\\\externalLink{this}{/somethingelse/} points to something else. Backslashing should suppress [this] and [this]. \\\\footnote{\\\\label{this}\\\\externalLink{foo}{foo}} \\\\horizontalLine Here's one where the \\\\hyperref[link breaks]{link breaks} across lines. Here's another where the \\\\hyperref[link breaks]{link breaks} across lines, but with a line-ending space. \\\\footnote{\\\\label{link breaks}\\\\externalLink{/url/}{/url/}}" `; exports[`toLaTeX: remark specs links-shortcut-references: links-shortcut-references 1`] = ` "This is the \\\\hyperref[simple case]{simple case}. \\\\footnote{\\\\label{simple case}\\\\externalLink{/simple}{/simple}} This one has a \\\\hyperref[line break]{line break}. This one has a \\\\hyperref[line break]{line break} with a line-ending space. \\\\footnote{\\\\label{line break}\\\\externalLink{/foo}{/foo}} \\\\hyperref[that]{this} and the \\\\hyperref[other]{other} \\\\footnote{\\\\label{this}\\\\externalLink{/this}{/this}} \\\\footnote{\\\\label{that}\\\\externalLink{/that}{/that}} \\\\footnote{\\\\label{other}\\\\externalLink{/other}{/other}}" `; exports[`toLaTeX: remark specs links-text-delimiters: links-text-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}. " `; exports[`toLaTeX: remark specs links-text-empty: links-text-empty 1`] = ` "\\\\externalLink{}{./hello-world.html}. \\\\externalLink{}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}. " `; exports[`toLaTeX: remark specs links-text-entity-delimiters: links-text-entity-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}. " `; exports[`toLaTeX: remark specs links-text-escaped-delimiters: links-text-escaped-delimiters 1`] = ` "\\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\externalLink{Hello [world]!}{./hello-world.html}. \\\\includegraphics{./hello-world.html}. \\\\includegraphics{./hello-world.html}. " `; exports[`toLaTeX: remark specs links-text-mismatched-delimiters: links-text-mismatched-delimiters 1`] = ` "[Hello \\\\externalLink{world!}{./hello-world.html}. [Hello \\\\externalLink{world!}{./hello-world.html}. ![Hello \\\\externalLink{world!}{./hello-world.html}. ![Hello \\\\externalLink{world!}{./hello-world.html}. " `; exports[`toLaTeX: remark specs links-title-double-quotes: links-title-double-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-double-quotes-delimiters: links-title-double-quotes-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-double-quotes-entity-delimiters: links-title-double-quotes-entity-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-double-quotes-escaped-delimiters: links-title-double-quotes-escaped-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-double-quotes-mismatched-delimiters: links-title-double-quotes-mismatched-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-empty-double-quotes: links-title-empty-double-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-empty-parentheses: links-title-empty-parentheses 1`] = ` "[Hello](./world.html ()). [Hello](<./world.html> ()). \\\\includegraphics{undefined}(./world.html ()). \\\\includegraphics{undefined}(<./world.html> ()). " `; exports[`toLaTeX: remark specs links-title-empty-single-quotes: links-title-empty-single-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-parentheses: links-title-parentheses 1`] = ` "[Hello](./world.html (Hello World!)). [Hello](<./world.html> (Hello World!)). \\\\includegraphics{undefined}(./world.html (Hello World!)). \\\\includegraphics{undefined}(<./world.html> (Hello World!)). " `; exports[`toLaTeX: remark specs links-title-single-quotes: links-title-single-quotes 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-single-quotes-delimiters: links-title-single-quotes-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-single-quotes-entity-delimiters: links-title-single-quotes-entity-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-single-quotes-escaped-delimiters: links-title-single-quotes-escaped-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-single-quotes-mismatched-delimiters: links-title-single-quotes-mismatched-delimiters 1`] = ` "\\\\externalLink{Hello}{./world.html}. \\\\externalLink{Hello}{./world.html}. \\\\includegraphics{./world.html}. \\\\includegraphics{./world.html}. " `; exports[`toLaTeX: remark specs links-title-unclosed: links-title-unclosed 1`] = ` "[Hello](./world.html 'Hello [Hello](<./world.html> 'Hello \\\\includegraphics{undefined}(./world.html 'Hello \\\\includegraphics{undefined}(<./world.html> 'Hello [Hello](./world.html \\"Hello [Hello](<./world.html> \\"Hello \\\\includegraphics{undefined}(./world.html \\"Hello \\\\includegraphics{undefined}(<./world.html> \\"Hello [Hello](./world.html (Hello [Hello](<./world.html> (Hello \\\\includegraphics{undefined}(./world.html (Hello \\\\includegraphics{undefined}(<./world.html> (Hello " `; exports[`toLaTeX: remark specs links-url-empty: links-url-empty 1`] = ` ". . \\\\includegraphics{}. \\\\includegraphics{}. " `; exports[`toLaTeX: remark specs links-url-empty-title-double-quotes: links-url-empty-title-double-quotes 1`] = ` "\\\\externalLink{Hello}{\\"World!\\"}. \\\\externalLink{Hello}{\\"World!\\"}. . \\\\includegraphics{\\"World!\\"}. \\\\includegraphics{\\"World!\\"}. \\\\includegraphics{}. " `; exports[`toLaTeX: remark specs links-url-empty-title-parentheses: links-url-empty-title-parentheses 1`] = ` "\\\\externalLink{Hello}{(World!)}. \\\\externalLink{Hello}{(World!)}. [World](<> (World!)). \\\\includegraphics{(World!)}. \\\\includegraphics{(World!)}. \\\\includegraphics{undefined}(<> (World!)). " `; exports[`toLaTeX: remark specs links-url-empty-title-single-quotes: links-url-empty-title-single-quotes 1`] = ` "\\\\externalLink{Hello}{'World!'}. \\\\externalLink{Hello}{'World!'}. . \\\\includegraphics{'World!'}. \\\\includegraphics{'World!'}. \\\\includegraphics{}. " `; exports[`toLaTeX: remark specs links-url-entity-parentheses: links-url-entity-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}. " `; exports[`toLaTeX: remark specs links-url-escaped-parentheses: links-url-escaped-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld)}. \\\\includegraphics{./world(and)helloworld)}. " `; exports[`toLaTeX: remark specs links-url-mismatched-parentheses: links-url-mismatched-parentheses 1`] = ` "[Hello](./world(and-hello(world)). \\\\externalLink{Hello}{./world(and-hello(world)}. \\\\externalLink{Hello}{./world(and)helloworld}). \\\\externalLink{Hello}{./world(and)helloworld)}. \\\\includegraphics{undefined}(./world(and-hello(world)). \\\\includegraphics{./world(and-hello(world)}. \\\\includegraphics{./world(and)helloworld}). \\\\includegraphics{./world(and)helloworld)}. " `; exports[`toLaTeX: remark specs links-url-nested-parentheses: links-url-nested-parentheses 1`] = ` "\\\\externalLink{Hello}{./world(and)hello(world)}. \\\\externalLink{Hello}{./world(and)hello(world)}. \\\\includegraphics{./world(and)hello(world)}. \\\\includegraphics{./world(and)hello(world)}. " `; exports[`toLaTeX: remark specs links-url-new-line: links-url-new-line 1`] = ` "[Hello](./wo rld.html). \\\\externalLink{Hello}{./wo rld.html}. \\\\includegraphics{undefined}(./wo rld.png). \\\\includegraphics{./wo rld.png}. " `; exports[`toLaTeX: remark specs links-url-unclosed: links-url-unclosed 1`] = ` "[Hello]( [World](< \\\\includegraphics{undefined}( \\\\includegraphics{undefined}(< " `; exports[`toLaTeX: remark specs links-url-white-space: links-url-white-space 1`] = ` "[Hello](./wo rld.html). \\\\externalLink{Hello}{./wo rld.html}. \\\\includegraphics{undefined}(./wo rld.png). \\\\includegraphics{./wo rld.png}. " `; exports[`toLaTeX: remark specs list: list 1`] = ` "\\\\part{List bullets} \\\\begin{itemize} \\\\item\\\\relax One: \\\\begin{itemize} \\\\item\\\\relax Nested one; \\\\item\\\\relax Nested two: \\\\begin{itemize} \\\\item\\\\relax Nested three. \\\\end{itemize} \\\\end{itemize} \\\\item\\\\relax Two; \\\\item\\\\relax Three. \\\\end{itemize} " `; exports[`toLaTeX: remark specs list-after-list: list-after-list 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{enumerate} \\\\horizontalLine \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{enumerate} " `; exports[`toLaTeX: remark specs list-and-code: list-and-code 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax This is a list item \\\\end{itemize} \\\\begin{CodeBlock}{text} This is code \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs list-continuation: list-continuation 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo \\\\end{enumerate} \\\\horizontalLine \\\\begin{enumerate} \\\\item\\\\relax foo \\\\end{enumerate} \\\\begin{CodeBlock}{js} code(); \\\\end{CodeBlock} \\\\begin{enumerate} \\\\item\\\\relax \\\\hyperref[foo]{foo} \\\\end{enumerate} \\\\footnote{\\\\label{foo}\\\\externalLink{http://google.com}{http://google.com}}" `; exports[`toLaTeX: remark specs list-indentation: list-indentation 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Hello 1a World 1a. \\\\item\\\\relax Hello 1b World 1b. \\\\item\\\\relax Hello 2a World 2a. \\\\item\\\\relax Hello 2b World 2b. \\\\item\\\\relax Hello 3a World 3a. \\\\item\\\\relax Hello 3b World 3b. \\\\item\\\\relax Hello 4a World 4a. \\\\item\\\\relax Hello 4b World 4b. \\\\item\\\\relax \\\\begin{CodeBlock}{text} Hello 5a \\\\end{CodeBlock} World 5a. \\\\item\\\\relax \\\\begin{CodeBlock}{text} Hello 5b World 5b. \\\\end{CodeBlock} \\\\end{itemize} " `; exports[`toLaTeX: remark specs list-item-empty: list-item-empty 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax foo \\\\item\\\\relax \\\\item\\\\relax bar \\\\item\\\\relax foo \\\\item\\\\relax \\\\item\\\\relax bar \\\\end{itemize} " `; exports[`toLaTeX: remark specs list-item-empty-with-white-space: list-item-empty-with-white-space 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax \\\\end{itemize} " `; exports[`toLaTeX: remark specs list-item-indent: list-item-indent 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{enumerate} \\\\begin{enumerate} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{enumerate} \\\\begin{itemize} \\\\item\\\\relax foo bar baz. \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax foo bar baz. foo bar baz. \\\\end{itemize} " `; exports[`toLaTeX: remark specs list-item-newline: list-item-newline 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Foo \\\\item\\\\relax Bar \\\\end{itemize} " `; exports[`toLaTeX: remark specs list-item-text: list-item-text 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax item1 \\\\begin{itemize} \\\\item\\\\relax item2 \\\\end{itemize} text \\\\end{itemize} " `; exports[`toLaTeX: remark specs list-ordered: list-ordered 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo; \\\\item\\\\relax bar; \\\\item\\\\relax baz. \\\\end{enumerate} " `; exports[`toLaTeX: remark specs lists-with-code-and-rules: lists-with-code-and-rules 1`] = ` "\\\\chapter{foo} \\\\begin{enumerate} \\\\item\\\\relax bar: \\\\begin{Quotation} \\\\begin{itemize} \\\\item\\\\relax one \\\\begin{itemize} \\\\item\\\\relax two \\\\begin{itemize} \\\\item\\\\relax three \\\\item\\\\relax four \\\\item\\\\relax five \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} \\\\end{Quotation} \\\\item\\\\relax foo: \\\\begin{CodeBlock}{text} line 1 line 2 \\\\end{CodeBlock} \\\\item\\\\relax foo: \\\\begin{enumerate} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{erb} some code here \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{erb} foo --- bar --- foo bar \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{html} --- foo foo --- bar \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\texttt{bar} bar: \\\\begin{CodeBlock}{text} foo --- bar \\\\end{CodeBlock} \\\\item\\\\relax foo \\\\end{enumerate} \\\\end{enumerate} " `; exports[`toLaTeX: remark specs loose-lists: loose-lists 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax hello world how are \\\\item\\\\relax you \\\\end{itemize} better behavior: \\\\begin{itemize} \\\\item\\\\relax hello \\\\begin{itemize} \\\\item\\\\relax world how are you \\\\item\\\\relax today \\\\end{itemize} \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world how \\\\item\\\\relax hi \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax how are \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax hello \\\\item\\\\relax world \\\\item\\\\relax how are \\\\end{itemize} " `; exports[`toLaTeX: remark specs main: main 1`] = ` "\\\\footnote{\\\\label{test}\\\\externalLink{http://google.com/}{http://google.com/}} \\\\part{A heading} Just a note, I've found that I can't test my markdown parser vs others. For example, both markdown.js and showdown code blocks in lists wrong. They're also completely \\\\hyperref[test]{inconsistent} with regards to paragraphs in list items. A link. Not anymore. \\\\begin{itemize} \\\\item\\\\relax List Item 1 \\\\item\\\\relax List Item 2 \\\\begin{itemize} \\\\item\\\\relax New List Item 1 Hi, this is a list item. \\\\item\\\\relax New List Item 2 Another item Code goes here. Lots of it... \\\\item\\\\relax New List Item 3 The last item \\\\end{itemize} \\\\item\\\\relax List Item 3 The final item. \\\\item\\\\relax List Item 4 The real final item. \\\\end{itemize} Paragraph. \\\\begin{Quotation} \\\\begin{itemize} \\\\item\\\\relax bq Item 1 \\\\item\\\\relax bq Item 2 \\\\begin{itemize} \\\\item\\\\relax New bq Item 1 \\\\item\\\\relax New bq Item 2 Text here \\\\end{itemize} \\\\end{itemize} \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} Another blockquote! I really need to get more creative with mockup text.. markdown.js breaks here again \\\\end{Quotation} \\\\chapter{Another Heading} Hello \\\\textit{world}. Here is a \\\\externalLink{link}{//hello}. And an image \\\\includegraphics{src}. And an image with an empty alt attribute \\\\includegraphics{src}. \\\\begin{CodeBlock}{text} Code goes here. Lots of it... \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs markdown-documentation-basics: markdown-documentation-basics 1`] = ` "\\\\part{Markdown: Basics} \\\\chapter{Getting the Gist of Markdown's Formatting Syntax} This page offers a brief overview of what it's like to use Markdown. The \\\\hyperref[s]{syntax page} provides complete, detailed documentation for every feature, but Markdown should be very easy to pick up simply by looking at a few examples of it in action. The examples on this page are written in a before/after style, showing example syntax and the HTML output produced by Markdown. It's also helpful to simply try Markdown out; the \\\\hyperref[d]{Dingus} is a web application that allows you type your own Markdown-formatted text and translate it to XHTML. \\\\textbf{Note:} This document is itself written using Markdown; you can \\\\hyperref[src]{see the source for it by adding '.text' to the URL}. \\\\footnote{\\\\label{s}\\\\externalLink{/projects/markdown/syntax}{/projects/markdown/syntax}} \\\\footnote{\\\\label{d}\\\\externalLink{/projects/markdown/dingus}{/projects/markdown/dingus}} \\\\footnote{\\\\label{src}\\\\externalLink{/projects/markdown/basics.text}{/projects/markdown/basics.text}} \\\\chapter{Paragraphs, Headers, Blockquotes} A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. Markdown offers two styles of headers: \\\\textit{Setext} and \\\\textit{atx}. Setext-style headers for \\\\texttt{

} and \\\\texttt{

} are created by \\"underlining\\" with equal signs (\\\\texttt{=}) and hyphens (\\\\texttt{-}), respectively. To create an atx-style header, you put 1-6 hash marks (\\\\texttt{\\\\#}) at the beginning of the line -- the number of hashes equals the resulting HTML header level. Blockquotes are indicated using email-style '\\\\texttt{>}' angle brackets. Markdown: \\\\begin{CodeBlock}{text} A First Level Header ==================== A Second Level Header --------------------- Now is the time for all good men to come to the aid of their country. This is just a regular paragraph. The quick brown fox jumped over the lazy dog's back. ### Header 3 > This is a blockquote. > > This is the second paragraph in the blockquote. > > ## This is an H2 in a blockquote \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

A First Level Header

A Second Level Header

Now is the time for all good men to come to the aid of their country. This is just a regular paragraph.

The quick brown fox jumped over the lazy dog's back.

Header 3

This is a blockquote.

This is the second paragraph in the blockquote.

This is an H2 in a blockquote

\\\\end{CodeBlock} \\\\section{Phrase Emphasis} Markdown uses asterisks and underscores to indicate spans of emphasis. Markdown: \\\\begin{CodeBlock}{text} Some of these words *are emphasized*. Some of these words _are emphasized also_. Use two asterisks for **strong emphasis**. Or, if you prefer, __use two underscores instead__. \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

Some of these words are emphasized. Some of these words are emphasized also.

Use two asterisks for strong emphasis. Or, if you prefer, use two underscores instead.

\\\\end{CodeBlock} \\\\chapter{Lists} Unordered (bulleted) lists use asterisks, pluses, and hyphens (\\\\texttt{*}, \\\\texttt{+}, and \\\\texttt{-}) as list markers. These three markers are interchangable; this: \\\\begin{CodeBlock}{text} * Candy. * Gum. * Booze. \\\\end{CodeBlock} this: \\\\begin{CodeBlock}{text} + Candy. + Gum. + Booze. \\\\end{CodeBlock} and this: \\\\begin{CodeBlock}{text} - Candy. - Gum. - Booze. \\\\end{CodeBlock} all produce the same output: \\\\begin{CodeBlock}{text}
  • Candy.
  • Gum.
  • Booze.
\\\\end{CodeBlock} Ordered (numbered) lists use regular numbers, followed by periods, as list markers: \\\\begin{CodeBlock}{text} 1. Red 2. Green 3. Blue \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}
  1. Red
  2. Green
  3. Blue
\\\\end{CodeBlock} If you put blank lines between items, you'll get \\\\texttt{

} tags for the list item text. You can create multi-paragraph list items by indenting the paragraphs by 4 spaces or 1 tab: \\\\begin{CodeBlock}{text} * A list item. With multiple paragraphs. * Another item in the list. \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

  • A list item.

    With multiple paragraphs.

  • Another item in the list.

\\\\end{CodeBlock} \\\\section{Links} Markdown supports two styles for creating links: \\\\textit{inline} and \\\\textit{reference}. With both styles, you use square brackets to delimit the text you want to turn into a link. Inline-style links use parentheses immediately after the link text. For example: \\\\begin{CodeBlock}{text} This is an [example link](http://example.com/). \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

This is an example link.

\\\\end{CodeBlock} Optionally, you may include a title attribute in the parentheses: \\\\begin{CodeBlock}{text} This is an [example link](http://example.com/ \\"With a Title\\"). \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

This is an example link.

\\\\end{CodeBlock} Reference-style links allow you to refer to your links by names, which you define elsewhere in your document: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google][1] than from [Yahoo][2] or [MSN][3]. [1]: http://google.com/ \\"Google\\" [2]: http://search.yahoo.com/ \\"Yahoo Search\\" [3]: http://search.msn.com/ \\"MSN Search\\" \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

I get 10 times more traffic from Google than from Yahoo or MSN.

\\\\end{CodeBlock} The title attribute is optional. Link names may contain letters, numbers and spaces, but are \\\\textit{not} case sensitive: \\\\begin{CodeBlock}{text} I start my morning with a cup of coffee and [The New York Times][NY Times]. [ny times]: http://www.nytimes.com/ \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

I start my morning with a cup of coffee and The New York Times.

\\\\end{CodeBlock} \\\\section{Images} Image syntax is very much like link syntax. Inline (titles are optional): \\\\begin{CodeBlock}{text} ![alt text](/path/to/img.jpg \\"Title\\") \\\\end{CodeBlock} Reference-style: \\\\begin{CodeBlock}{text} ![alt text][id] [id]: /path/to/img.jpg \\"Title\\" \\\\end{CodeBlock} Both of the above examples produce the same output: \\\\begin{CodeBlock}{text} \\"alt \\\\end{CodeBlock} \\\\section{Code} In a regular paragraph, you can create code span by wrapping text in backtick quotes. Any ampersands (\\\\texttt{\\\\&}) and angle brackets (\\\\texttt{<} or \\\\texttt{>}) will automatically be translated into HTML entities. This makes it easy to use Markdown to write about HTML example code: \\\\begin{CodeBlock}{text} I strongly recommend against using any \`\` tags. I wish SmartyPants used named entities like \`—\` instead of decimal-encoded entites like \`—\`. \\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

I strongly recommend against using any <blink> tags.

I wish SmartyPants used named entities like &mdash; instead of decimal-encoded entites like &#8212;.

\\\\end{CodeBlock} To specify an entire block of pre-formatted code, indent every line of the block by 4 spaces or 1 tab. Just like with code spans, \\\\texttt{\\\\&}, \\\\texttt{<}, and \\\\texttt{>} characters will be escaped automatically. Markdown: \\\\begin{CodeBlock}{text} If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

For example.

\\\\end{CodeBlock} Output: \\\\begin{CodeBlock}{text}

If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

<blockquote>
    <p>For example.</p>
</blockquote>
\\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs markdown-documentation-syntax: markdown-documentation-syntax 1`] = ` "\\\\part{Markdown: Syntax} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Overview}{\\\\#overview} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Philosophy}{\\\\#philosophy} \\\\item\\\\relax \\\\externalLink{Inline HTML}{\\\\#html} \\\\item\\\\relax \\\\externalLink{Automatic Escaping for Special Characters}{\\\\#autoescape} \\\\end{itemize} \\\\item\\\\relax \\\\externalLink{Block Elements}{\\\\#block} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Paragraphs and Line Breaks}{\\\\#p} \\\\item\\\\relax \\\\externalLink{Headers}{\\\\#header} \\\\item\\\\relax \\\\externalLink{Blockquotes}{\\\\#blockquote} \\\\item\\\\relax \\\\externalLink{Lists}{\\\\#list} \\\\item\\\\relax \\\\externalLink{Code Blocks}{\\\\#precode} \\\\item\\\\relax \\\\externalLink{Horizontal Rules}{\\\\#hr} \\\\end{itemize} \\\\item\\\\relax \\\\externalLink{Span Elements}{\\\\#span} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Links}{\\\\#link} \\\\item\\\\relax \\\\externalLink{Emphasis}{\\\\#em} \\\\item\\\\relax \\\\externalLink{Code}{\\\\#code} \\\\item\\\\relax \\\\externalLink{Images}{\\\\#img} \\\\end{itemize} \\\\item\\\\relax \\\\externalLink{Miscellaneous}{\\\\#misc} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{Backslash Escapes}{\\\\#backslash} \\\\item\\\\relax \\\\externalLink{Automatic Links}{\\\\#autolink} \\\\end{itemize} \\\\end{itemize} \\\\textbf{Note:} This document is itself written using Markdown; you can \\\\hyperref[src]{see the source for it by adding '.text' to the URL}. \\\\footnote{\\\\label{src}\\\\externalLink{/projects/markdown/syntax.text}{/projects/markdown/syntax.text}} \\\\horizontalLine

Overview

Philosophy

Markdown is intended to be as easy-to-read and easy-to-write as is feasible. Readability, however, is emphasized above all else. A Markdown-formatted document should be publishable as-is, as plain text, without looking like it's been marked up with tags or formatting instructions. While Markdown's syntax has been influenced by several existing text-to-HTML filters -- including \\\\hyperref[1]{Setext}, \\\\hyperref[2]{atx}, \\\\hyperref[3]{Textile}, \\\\hyperref[4]{reStructuredText}, \\\\hyperref[5]{Grutatext}, and \\\\hyperref[6]{EtText} -- the single biggest source of inspiration for Markdown's syntax is the format of plain text email. \\\\footnote{\\\\label{1}\\\\externalLink{http://docutils.sourceforge.net/mirror/setext.html}{http://docutils.sourceforge.net/mirror/setext.html}} \\\\footnote{\\\\label{2}\\\\externalLink{http://www.aaronsw.com/2002/atx/}{http://www.aaronsw.com/2002/atx/}} \\\\footnote{\\\\label{3}\\\\externalLink{http://textism.com/tools/textile/}{http://textism.com/tools/textile/}} \\\\footnote{\\\\label{4}\\\\externalLink{http://docutils.sourceforge.net/rst.html}{http://docutils.sourceforge.net/rst.html}} \\\\footnote{\\\\label{5}\\\\externalLink{http://www.triptico.com/software/grutatxt.html}{http://www.triptico.com/software/grutatxt.html}} \\\\footnote{\\\\label{6}\\\\externalLink{http://ettext.taint.org/doc/}{http://ettext.taint.org/doc/}} To this end, Markdown's syntax is comprised entirely of punctuation characters, which punctuation characters have been carefully chosen so as to look like what they mean. E.g., asterisks around a word actually look like *emphasis*. Markdown lists look like, well, lists. Even blockquotes look like quoted passages of text, assuming you've ever used email.

Inline HTML

Markdown's syntax is intended for one purpose: to be used as a format for \\\\textit{writing} for the web. Markdown is not a replacement for HTML, or even close to it. Its syntax is very small, corresponding only to a very small subset of HTML tags. The idea is \\\\textit{not} to create a syntax that makes it easier to insert HTML tags. In my opinion, HTML tags are already easy to insert. The idea for Markdown is to make it easy to read, write, and edit prose. HTML is a \\\\textit{publishing} format; Markdown is a \\\\textit{writing} format. Thus, Markdown's formatting syntax only addresses issues that can be conveyed in plain text. For any markup that is not covered by Markdown's syntax, you simply use HTML itself. There's no need to preface it or delimit it to indicate that you're switching from Markdown to HTML; you just use the tags. The only restrictions are that block-level HTML elements -- e.g. \\\\texttt{
}, \\\\texttt{}, \\\\texttt{
}, \\\\texttt{

}, etc. -- must be separated from surrounding content by blank lines, and the start and end tags of the block should not be indented with tabs or spaces. Markdown is smart enough not to add extra (unwanted) \\\\texttt{

} tags around HTML block-level tags. For example, to add an HTML table to a Markdown article: \\\\begin{CodeBlock}{text} This is a regular paragraph.

Foo
This is another regular paragraph. \\\\end{CodeBlock} Note that Markdown formatting syntax is not processed within block-level HTML tags. E.g., you can't use Markdown-style \\\\texttt{*emphasis*} inside an HTML block. Span-level HTML tags -- e.g. \\\\texttt{}, \\\\texttt{}, or \\\\texttt{} -- can be used anywhere in a Markdown paragraph, list item, or header. If you want, you can even use HTML tags instead of Markdown formatting; e.g. if you'd prefer to use HTML \\\\texttt{} or \\\\texttt{} tags instead of Markdown's link or image syntax, go right ahead. Unlike block-level HTML tags, Markdown syntax \\\\textit{is} processed within span-level tags.

Automatic Escaping for Special Characters

In HTML, there are two characters that demand special treatment: \\\\texttt{<} and \\\\texttt{\\\\&}. Left angle brackets are used to start tags; ampersands are used to denote HTML entities. If you want to use them as literal characters, you must escape them as entities, e.g. \\\\texttt{\\\\<}, and \\\\texttt{\\\\&}. Ampersands in particular are bedeviling for web writers. If you want to write about 'AT\\\\&T', you need to write '\\\\texttt{AT\\\\&T}'. You even need to escape ampersands within URLs. Thus, if you want to link to: \\\\begin{CodeBlock}{text} http://images.google.com/images?num=30&q=larry+bird \\\\end{CodeBlock} you need to encode the URL as: \\\\begin{CodeBlock}{text} http://images.google.com/images?num=30&q=larry+bird \\\\end{CodeBlock} in your anchor tag \\\\texttt{href} attribute. Needless to say, this is easy to forget, and is probably the single most common source of HTML validation errors in otherwise well-marked-up web sites. Markdown allows you to use these characters naturally, taking care of all the necessary escaping for you. If you use an ampersand as part of an HTML entity, it remains unchanged; otherwise it will be translated into \\\\texttt{\\\\&}. So, if you want to include a copyright symbol in your article, you can write: \\\\begin{CodeBlock}{text} © \\\\end{CodeBlock} and Markdown will leave it alone. But if you write: \\\\begin{CodeBlock}{text} AT&T \\\\end{CodeBlock} Markdown will translate it to: \\\\begin{CodeBlock}{text} AT&T \\\\end{CodeBlock} Similarly, because Markdown supports \\\\externalLink{inline HTML}{\\\\#html}, if you use angle brackets as delimiters for HTML tags, Markdown will treat them as such. But if you write: \\\\begin{CodeBlock}{text} 4 < 5 \\\\end{CodeBlock} Markdown will translate it to: \\\\begin{CodeBlock}{text} 4 < 5 \\\\end{CodeBlock} However, inside Markdown code spans and blocks, angle brackets and ampersands are \\\\textit{always} encoded automatically. This makes it easy to use Markdown to write about HTML code. (As opposed to raw HTML, which is a terrible format for writing about HTML syntax, because every single \\\\texttt{<} and \\\\texttt{\\\\&} in your example code needs to be escaped.) \\\\horizontalLine

Block Elements

Paragraphs and Line Breaks

A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. The implication of the \\"one or more consecutive lines of text\\" rule is that Markdown supports \\"hard-wrapped\\" text paragraphs. This differs significantly from most other text-to-HTML formatters (including Movable Type's \\"Convert Line Breaks\\" option) which translate every line break character in a paragraph into a \\\\texttt{
} tag. When you \\\\textit{do} want to insert a \\\\texttt{
} break tag using Markdown, you end a line with two or more spaces, then type return. Yes, this takes a tad more effort to create a \\\\texttt{
}, but a simplistic \\"every line break is a \\\\texttt{
}\\" rule wouldn't work for Markdown. Markdown's email-style \\\\hyperref[bq]{blockquoting} and multi-paragraph \\\\hyperref[l]{list items} work best -- and look better -- when you format them with hard breaks. \\\\footnote{\\\\label{bq}\\\\externalLink{\\\\#blockquote}{\\\\#blockquote}} \\\\footnote{\\\\label{l}\\\\externalLink{\\\\#list}{\\\\#list}}

Headers

Markdown supports two styles of headers, \\\\hyperref[1]{Setext} and \\\\hyperref[2]{atx}. Setext-style headers are \\"underlined\\" using equal signs (for first-level headers) and dashes (for second-level headers). For example: \\\\begin{CodeBlock}{text} This is an H1 ============= This is an H2 ------------- \\\\end{CodeBlock} Any number of underlining \\\\texttt{=}'s or \\\\texttt{-}'s will work. Atx-style headers use 1-6 hash characters at the start of the line, corresponding to header levels 1-6. For example: \\\\begin{CodeBlock}{text} # This is an H1 ## This is an H2 ###### This is an H6 \\\\end{CodeBlock} Optionally, you may \\"close\\" atx-style headers. This is purely cosmetic -- you can use this if you think it looks better. The closing hashes don't even need to match the number of hashes used to open the header. (The number of opening hashes determines the header level.) : \\\\begin{CodeBlock}{text} # This is an H1 # ## This is an H2 ## ### This is an H3 ###### \\\\end{CodeBlock}

Blockquotes

Markdown uses email-style \\\\texttt{>} characters for blockquoting. If you're familiar with quoting passages of text in an email message, then you know how to create a blockquote in Markdown. It looks best if you hard wrap the text and put a \\\\texttt{>} before every line: \\\\begin{CodeBlock}{text} > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse > id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} Markdown allows you to be lazy and only put the \\\\texttt{>} before the first line of a hard-wrapped paragraph: \\\\begin{CodeBlock}{text} > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by adding additional levels of \\\\texttt{>}: \\\\begin{CodeBlock}{text} > This is the first level of quoting. > > > This is nested blockquote. > > Back to the first level. \\\\end{CodeBlock} Blockquotes can contain other Markdown elements, including headers, lists, and code blocks: \\\\begin{CodeBlock}{text} > ## This is a header. > > 1. This is the first list item. > 2. This is the second list item. > > Here's some example code: > > return shell_exec(\\"echo $input | $markdown_script\\"); \\\\end{CodeBlock} Any decent text editor should make email-style quoting easy. For example, with BBEdit, you can make a selection and choose Increase Quote Level from the Text menu.

Lists

Markdown supports ordered (numbered) and unordered (bulleted) lists. Unordered lists use asterisks, pluses, and hyphens -- interchangably -- as list markers: \\\\begin{CodeBlock}{text} * Red * Green * Blue \\\\end{CodeBlock} is equivalent to: \\\\begin{CodeBlock}{text} + Red + Green + Blue \\\\end{CodeBlock} and: \\\\begin{CodeBlock}{text} - Red - Green - Blue \\\\end{CodeBlock} Ordered lists use numbers followed by periods: \\\\begin{CodeBlock}{text} 1. Bird 2. McHale 3. Parish \\\\end{CodeBlock} It's important to note that the actual numbers you use to mark the list have no effect on the HTML output Markdown produces. The HTML Markdown produces from the above list is: \\\\begin{CodeBlock}{text}
  1. Bird
  2. McHale
  3. Parish
\\\\end{CodeBlock} If you instead wrote the list in Markdown like this: \\\\begin{CodeBlock}{text} 1. Bird 1. McHale 1. Parish \\\\end{CodeBlock} or even: \\\\begin{CodeBlock}{text} 3. Bird 1. McHale 8. Parish \\\\end{CodeBlock} you'd get the exact same HTML output. The point is, if you want to, you can use ordinal numbers in your ordered Markdown lists, so that the numbers in your source match the numbers in your published HTML. But if you want to be lazy, you don't have to. If you do use lazy list numbering, however, you should still start the list with the number 1. At some point in the future, Markdown may support starting ordered lists at an arbitrary number. List markers typically start at the left margin, but may be indented by up to three spaces. List markers must be followed by one or more spaces or a tab. To make lists look nice, you can wrap items with hanging indents: \\\\begin{CodeBlock}{text} * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} But if you want to be lazy, you don't have to: \\\\begin{CodeBlock}{text} * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} If list items are separated by blank lines, Markdown will wrap the items in \\\\texttt{

} tags in the HTML output. For example, this input: \\\\begin{CodeBlock}{text} * Bird * Magic \\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}

  • Bird
  • Magic
\\\\end{CodeBlock} But this: \\\\begin{CodeBlock}{text} * Bird * Magic \\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}
  • Bird

  • Magic

\\\\end{CodeBlock} List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be intended by either 4 spaces or one tab: \\\\begin{CodeBlock}{text} 1. This is a list item with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 2. Suspendisse id sem consectetuer libero luctus adipiscing. \\\\end{CodeBlock} It looks nice if you indent every line of the subsequent paragraphs, but here again, Markdown will allow you to be lazy: \\\\begin{CodeBlock}{text} * This is a list item with two paragraphs. This is the second paragraph in the list item. You're only required to indent the first line. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. * Another item in the same list. \\\\end{CodeBlock} To put a blockquote within a list item, the blockquote's \\\\texttt{>} delimiters need to be indented: \\\\begin{CodeBlock}{text} * A list item with a blockquote: > This is a blockquote > inside a list item. \\\\end{CodeBlock} To put a code block within a list item, the code block needs to be indented \\\\textit{twice} -- 8 spaces or two tabs: \\\\begin{CodeBlock}{text} * A list item with a code block: \\\\end{CodeBlock} It's worth noting that it's possible to trigger an ordered list by accident, by writing something like this: \\\\begin{CodeBlock}{text} 1986. What a great season. \\\\end{CodeBlock} In other words, a \\\\textit{number-period-space} sequence at the beginning of a line. To avoid this, you can backslash-escape the period: \\\\begin{CodeBlock}{text} 1986\\\\. What a great season. \\\\end{CodeBlock}

Code Blocks

Pre-formatted code blocks are used for writing about programming or markup source code. Rather than forming normal paragraphs, the lines of a code block are interpreted literally. Markdown wraps a code block in both \\\\texttt{
} and \\\\texttt{} tags.



To produce a code block in Markdown, simply indent every line of the
block by at least 4 spaces or 1 tab. For example, given this input:



\\\\begin{CodeBlock}{text}
This is a normal paragraph:

    This is a code block.
\\\\end{CodeBlock}



Markdown will generate:



\\\\begin{CodeBlock}{text}

This is a normal paragraph:

This is a code block.
\\\\end{CodeBlock} One level of indentation -- 4 spaces or 1 tab -- is removed from each line of the code block. For example, this: \\\\begin{CodeBlock}{text} Here is an example of AppleScript: tell application \\"Foo\\" beep end tell \\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}

Here is an example of AppleScript:

tell application \\"Foo\\"
    beep
end tell
\\\\end{CodeBlock} A code block continues until it reaches a line that is not indented (or the end of the article). Within a code block, ampersands (\\\\texttt{\\\\&}) and angle brackets (\\\\texttt{<} and \\\\texttt{>}) are automatically converted into HTML entities. This makes it very easy to include example HTML source code using Markdown -- just paste it and indent it, and Markdown will handle the hassle of encoding the ampersands and angle brackets. For example, this: \\\\begin{CodeBlock}{text}
© 2004 Foo Corporation
\\\\end{CodeBlock} will turn into: \\\\begin{CodeBlock}{text}
<div class=\\"footer\\">
    &copy; 2004 Foo Corporation
</div>
\\\\end{CodeBlock} Regular Markdown syntax is not processed within code blocks. E.g., asterisks are just literal asterisks within a code block. This means it's also easy to use Markdown to write about Markdown's own syntax.

Horizontal Rules

You can produce a horizontal rule tag (\\\\texttt{
}) by placing three or more hyphens, asterisks, or underscores on a line by themselves. If you wish, you may use spaces between the hyphens or asterisks. Each of the following lines will produce a horizontal rule: \\\\begin{CodeBlock}{text} * * * *** ***** - - - --------------------------------------- _ _ _ \\\\end{CodeBlock} \\\\horizontalLine

Span Elements

Links

Markdown supports two style of links: \\\\textit{inline} and \\\\textit{reference}. In both styles, the link text is delimited by [square brackets]. To create an inline link, use a set of regular parentheses immediately after the link text's closing square bracket. Inside the parentheses, put the URL where you want the link to point, along with an \\\\textit{optional} title for the link, surrounded in quotes. For example: \\\\begin{CodeBlock}{text} This is [an example](http://example.com/ \\"Title\\") inline link. [This link](http://example.net/) has no title attribute. \\\\end{CodeBlock} Will produce: \\\\begin{CodeBlock}{text}

This is an example inline link.

This link has no title attribute.

\\\\end{CodeBlock} If you're referring to a local resource on the same server, you can use relative paths: \\\\begin{CodeBlock}{text} See my [About](/about/) page for details. \\\\end{CodeBlock} Reference-style links use a second set of square brackets, inside which you place a label of your choosing to identify the link: \\\\begin{CodeBlock}{text} This is [an example][id] reference-style link. \\\\end{CodeBlock} You can optionally use a space to separate the sets of brackets: \\\\begin{CodeBlock}{text} This is [an example] [id] reference-style link. \\\\end{CodeBlock} Then, anywhere in the document, you define your link label like this, on a line by itself: \\\\begin{CodeBlock}{text} [id]: http://example.com/ \\"Optional Title Here\\" \\\\end{CodeBlock} That is: \\\\begin{itemize} \\\\item\\\\relax Square brackets containing the link identifier (optionally indented from the left margin using up to three spaces); \\\\item\\\\relax followed by a colon; \\\\item\\\\relax followed by one or more spaces (or tabs); \\\\item\\\\relax followed by the URL for the link; \\\\item\\\\relax optionally followed by a title attribute for the link, enclosed in double or single quotes. \\\\end{itemize} The link URL may, optionally, be surrounded by angle brackets: \\\\begin{CodeBlock}{text} [id]: \\"Optional Title Here\\" \\\\end{CodeBlock} You can put the title attribute on the next line and use extra spaces or tabs for padding, which tends to look better with longer URLs: \\\\begin{CodeBlock}{text} [id]: http://example.com/longish/path/to/resource/here \\"Optional Title Here\\" \\\\end{CodeBlock} Link definitions are only used for creating links during Markdown processing, and are stripped from your document in the HTML output. Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are \\\\textit{not} case sensitive. E.g. these two links: \\\\begin{CodeBlock}{text} [link text][a] [link text][A] \\\\end{CodeBlock} are equivalent. The \\\\textit{implicit link name} shortcut allows you to omit the name of the link, in which case the link text itself is used as the name. Just use an empty set of square brackets -- e.g., to link the word \\"Google\\" to the google.com web site, you could simply write: \\\\begin{CodeBlock}{text} [Google][] \\\\end{CodeBlock} And then define the link: \\\\begin{CodeBlock}{text} [Google]: http://google.com/ \\\\end{CodeBlock} Because link names may contain spaces, this shortcut even works for multiple words in the link text: \\\\begin{CodeBlock}{text} Visit [Daring Fireball][] for more information. \\\\end{CodeBlock} And then define the link: \\\\begin{CodeBlock}{text} [Daring Fireball]: http://daringfireball.net/ \\\\end{CodeBlock} Link definitions can be placed anywhere in your Markdown document. I tend to put them immediately after each paragraph in which they're used, but if you want, you can put them all at the end of your document, sort of like footnotes. Here's an example of reference links in action: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]. [1]: http://google.com/ \\"Google\\" [2]: http://search.yahoo.com/ \\"Yahoo Search\\" [3]: http://search.msn.com/ \\"MSN Search\\" \\\\end{CodeBlock} Using the implicit link name shortcut, you could instead write: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google][] than from [Yahoo][] or [MSN][]. [google]: http://google.com/ \\"Google\\" [yahoo]: http://search.yahoo.com/ \\"Yahoo Search\\" [msn]: http://search.msn.com/ \\"MSN Search\\" \\\\end{CodeBlock} Both of the above examples will produce the following HTML output: \\\\begin{CodeBlock}{text}

I get 10 times more traffic from Google than from Yahoo or MSN.

\\\\end{CodeBlock} For comparison, here is the same paragraph written using Markdown's inline link style: \\\\begin{CodeBlock}{text} I get 10 times more traffic from [Google](http://google.com/ \\"Google\\") than from [Yahoo](http://search.yahoo.com/ \\"Yahoo Search\\") or [MSN](http://search.msn.com/ \\"MSN Search\\"). \\\\end{CodeBlock} The point of reference-style links is not that they're easier to write. The point is that with reference-style links, your document source is vastly more readable. Compare the above examples: using reference-style links, the paragraph itself is only 81 characters long; with inline-style links, it's 176 characters; and as raw HTML, it's 234 characters. In the raw HTML, there's more markup than there is text. With Markdown's reference-style links, a source document much more closely resembles the final output, as rendered in a browser. By allowing you to move the markup-related metadata out of the paragraph, you can add links without interrupting the narrative flow of your prose.

Emphasis

Markdown treats asterisks (\\\\texttt{*}) and underscores (\\\\texttt{\\\\_}) as indicators of emphasis. Text wrapped with one \\\\texttt{*} or \\\\texttt{\\\\_} will be wrapped with an HTML \\\\texttt{} tag; double \\\\texttt{*}'s or \\\\texttt{\\\\_}'s will be wrapped with an HTML \\\\texttt{} tag. E.g., this input: \\\\begin{CodeBlock}{text} *single asterisks* _single underscores_ **double asterisks** __double underscores__ \\\\end{CodeBlock} will produce: \\\\begin{CodeBlock}{text} single asterisks single underscores double asterisks double underscores \\\\end{CodeBlock} You can use whichever style you prefer; the lone restriction is that the same character must be used to open and close an emphasis span. Emphasis can be used in the middle of a word: \\\\begin{CodeBlock}{text} un*fucking*believable \\\\end{CodeBlock} But if you surround an \\\\texttt{*} or \\\\texttt{\\\\_} with spaces, it'll be treated as a literal asterisk or underscore. To produce a literal asterisk or underscore at a position where it would otherwise be used as an emphasis delimiter, you can backslash escape it: \\\\begin{CodeBlock}{text} \\\\*this text is surrounded by literal asterisks\\\\* \\\\end{CodeBlock}

Code

To indicate a span of code, wrap it with backtick quotes (\\\\texttt{\`}). Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example: \\\\begin{CodeBlock}{text} Use the \`printf()\` function. \\\\end{CodeBlock} will produce: \\\\begin{CodeBlock}{text}

Use the printf() function.

\\\\end{CodeBlock} To include a literal backtick character within a code span, you can use multiple backticks as the opening and closing delimiters: \\\\begin{CodeBlock}{text} \`\`There is a literal backtick (\`) here.\`\` \\\\end{CodeBlock} which will produce this: \\\\begin{CodeBlock}{text}

There is a literal backtick (\`) here.

\\\\end{CodeBlock} The backtick delimiters surrounding a code span may include spaces -- one after the opening, one before the closing. This allows you to place literal backtick characters at the beginning or end of a code span: \\\\begin{CodeBlock}{text} A single backtick in a code span: \`\` \` \`\` A backtick-delimited string in a code span: \`\` \`foo\` \`\` \\\\end{CodeBlock} will produce: \\\\begin{CodeBlock}{text}

A single backtick in a code span: \`

A backtick-delimited string in a code span: \`foo\`

\\\\end{CodeBlock} With a code span, ampersands and angle brackets are encoded as HTML entities automatically, which makes it easy to include example HTML tags. Markdown will turn this: \\\\begin{CodeBlock}{text} Please don't use any \`\` tags. \\\\end{CodeBlock} into: \\\\begin{CodeBlock}{text}

Please don't use any <blink> tags.

\\\\end{CodeBlock} You can write this: \\\\begin{CodeBlock}{text} \`—\` is the decimal-encoded equivalent of \`—\`. \\\\end{CodeBlock} to produce: \\\\begin{CodeBlock}{text}

&#8212; is the decimal-encoded equivalent of &mdash;.

\\\\end{CodeBlock}

Images

Admittedly, it's fairly difficult to devise a \\"natural\\" syntax for placing images into a plain text document format. Markdown uses an image syntax that is intended to resemble the syntax for links, allowing for two styles: \\\\textit{inline} and \\\\textit{reference}. Inline image syntax looks like this: \\\\begin{CodeBlock}{text} ![Alt text](/path/to/img.jpg) ![Alt text](/path/to/img.jpg \\"Optional title\\") \\\\end{CodeBlock} That is: \\\\begin{itemize} \\\\item\\\\relax An exclamation mark: \\\\texttt{!}; \\\\item\\\\relax followed by a set of square brackets, containing the \\\\texttt{alt} attribute text for the image; \\\\item\\\\relax followed by a set of parentheses, containing the URL or path to the image, and an optional \\\\texttt{title} attribute enclosed in double or single quotes. \\\\end{itemize} Reference-style image syntax looks like this: \\\\begin{CodeBlock}{text} ![Alt text][id] \\\\end{CodeBlock} Where \\"id\\" is the name of a defined image reference. Image references are defined using syntax identical to link references: \\\\begin{CodeBlock}{text} [id]: url/to/image \\"Optional title attribute\\" \\\\end{CodeBlock} As of this writing, Markdown has no syntax for specifying the dimensions of an image; if this is important to you, you can simply use regular HTML \\\\texttt{} tags. \\\\horizontalLine

Miscellaneous

Automatic Links

Markdown supports a shortcut style for creating \\"automatic\\" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} Markdown will turn this into: \\\\begin{CodeBlock}{text} http://example.com/ \\\\end{CodeBlock} Automatic links for email addresses work similarly, except that Markdown will also perform a bit of randomized decimal and hex entity-encoding to help obscure your address from address-harvesting spambots. For example, Markdown will turn this: \\\\begin{CodeBlock}{text} \\\\end{CodeBlock} into something like this: \\\\begin{CodeBlock}{text} address@exa mple.com \\\\end{CodeBlock} which will render in a browser as a clickable link to \\"\\\\externalLink{address@example.com}{mailto:address@example.com}\\". (This sort of entity-encoding trick will indeed fool many, if not most, address-harvesting bots, but it definitely won't fool all of them. It's better than nothing, but an address published in this way will probably eventually start receiving spam.)

Backslash Escapes

Markdown allows you to use backslash escapes to generate literal characters which would otherwise have special meaning in Markdown's formatting syntax. For example, if you wanted to surround a word with literal asterisks (instead of an HTML \\\\texttt{} tag), you can backslashes before the asterisks, like this: \\\\begin{CodeBlock}{text} \\\\*literal asterisks\\\\* \\\\end{CodeBlock} Markdown provides backslash escapes for the following characters: \\\\begin{CodeBlock}{text} \\\\ backslash \` backtick * asterisk _ underscore {} curly braces [] square brackets () parentheses # hash mark + plus sign - minus sign (hyphen) . dot ! exclamation mark \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs mixed-indentation: mixed-indentation 1`] = ` "\\\\part{Mixed spaces and tabs} \\\\begin{itemize} \\\\item\\\\relax Very long paragraph \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax Very long paragraph \\\\end{enumerate} \\\\begin{itemize} \\\\item\\\\relax Very long paragraph \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax Very long paragraph \\\\end{enumerate} " `; exports[`toLaTeX: remark specs nested-blockquotes: nested-blockquotes 1`] = ` "\\\\begin{Quotation} foo \\\\begin{Quotation} bar \\\\end{Quotation} foo \\\\end{Quotation} " `; exports[`toLaTeX: remark specs nested-code: nested-code 1`] = ` "\\\\texttt{hi ther \`\` ok \`\`\`} \\\\texttt{\`hi ther\`} " `; exports[`toLaTeX: remark specs nested-em: nested-em 1`] = ` "\\\\textit{test \\\\textbf{test} test} \\\\textit{test \\\\textbf{test} test} " `; exports[`toLaTeX: remark specs nested-references: nested-references 1`] = ` "This nested image should work: [\\\\includegraphics{undefined}] This nested link should not work: [[Foo][bar]] " `; exports[`toLaTeX: remark specs nested-square-link: nested-square-link 1`] = ` "[the \`]\` character](/url) [the \\\\texttt{[} character](/url) [the \`\` \\\\externalLink{ \`\`\` character}{/url} \\\\externalLink{the \\\\texttt{\`} character}{/url} " `; exports[`toLaTeX: remark specs no-positionals: no-positionals 1`] = ` "This document tests for the working of \\\\texttt{position: false} as a parse option. \\\\begin{Quotation} Block-quotes \\\\begin{itemize} \\\\item\\\\relax With list items. \\\\end{itemize} \\\\end{Quotation} Another block-quote: \\\\begin{Quotation} \\\\begin{enumerate} \\\\item\\\\relax And another list. \\\\end{enumerate} \\\\end{Quotation} Some \\\\externalLink{deeply \\\\textbf{nested \\\\textit{elements}}}{http://example.com} An entity: ©, and an warning entity: ©. " `; exports[`toLaTeX: remark specs not-a-link: not-a-link 1`] = ` "[test](not a link) " `; exports[`toLaTeX: remark specs ordered-and-unordered-lists: ordered-and-unordered-lists 1`] = ` "\\\\chapter{Unordered} Asterisks tight: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} Asterisks loose: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\horizontalLine Pluses tight: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} Pluses loose: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} \\\\horizontalLine Minuses tight: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} Minuses loose: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} \\\\chapter{Ordered} Tight: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Loose using tabs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and using spaces: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Multiple paragraphs: \\\\begin{enumerate} \\\\item\\\\relax Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. \\\\item\\\\relax Item 2. \\\\item\\\\relax Item 3. \\\\end{enumerate} \\\\chapter{Nested} \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} Here's another: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate} Same thing but with paragraphs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate} This was an error in Markdown 1.0.1: \\\\begin{itemize} \\\\item\\\\relax this \\\\begin{itemize} \\\\item\\\\relax sub \\\\end{itemize} that \\\\end{itemize} " `; exports[`toLaTeX: remark specs ordered-different-types: ordered-different-types 1`] = ` "\\\\begin{enumerate} \\\\item\\\\relax foo \\\\item\\\\relax bar 3) baz \\\\end{enumerate} " `; exports[`toLaTeX: remark specs ordered-with-parentheses: ordered-with-parentheses 1`] = ` "\\\\chapter{Ordered} Tight: 1) First 2) Second 3) Third and: 1) One 2) Two 3) Three Loose using tabs: 1) First 2) Second 3) Third and using spaces: 1) One 2) Two 3) Three Multiple paragraphs: 1) Item 1, graf one. \\\\begin{CodeBlock}{text} Item 2. graf two. The quick brown fox jumped over the lazy dog's back. \\\\end{CodeBlock} 2) Item 2. 3) Item 3. " `; exports[`toLaTeX: remark specs paragraphs-and-indentation: paragraphs-and-indentation 1`] = ` "\\\\part{Without lines.} This is a paragraph and this is further text This is a paragraph and this is further text This is a paragraph with some asterisks \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} This is a paragraph followed by a horizontal rule \\\\horizontalLine \\\\part{With lines.} This is a paragraph \\\\begin{CodeBlock}{text} and this is code \\\\end{CodeBlock} This is a paragraph and this is a new paragraph This is a paragraph with some asterisks in a code block \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} This is a paragraph followed by a horizontal rule \\\\horizontalLine " `; exports[`toLaTeX: remark specs paragraphs-empty: paragraphs-empty 1`] = ` "aaa \\\\part{aaa} bbb ccc " `; exports[`toLaTeX: remark specs ref-paren: ref-paren 1`] = ` "\\\\hyperref[hi]{hi} \\\\footnote{\\\\label{hi}\\\\externalLink{/url}{/url}}" `; exports[`toLaTeX: remark specs reference-image-empty-alt: reference-image-empty-alt 1`] = ` "\\\\includegraphics{/xyz.png} \\\\footnote{\\\\label{1}\\\\externalLink{/xyz.png}{/xyz.png}}" `; exports[`toLaTeX: remark specs reference-link-escape: reference-link-escape 1`] = ` "[b*r*], \\\\hyperref[b\\\\*r*]{b*r*}, \\\\hyperref[b\\\\*r*]{b*r*}. \\\\includegraphics{http://google.com}, \\\\includegraphics{http://google.com}, \\\\includegraphics{http://google.com}. \\\\footnote{\\\\label{b\\\\*r*}\\\\externalLink{http://google.com}{http://google.com}}" `; exports[`toLaTeX: remark specs reference-link-not-closed: reference-link-not-closed 1`] = ` "[bar]bar [bar] [bar] " `; exports[`toLaTeX: remark specs reference-link-with-angle-brackets: reference-link-with-angle-brackets 1`] = ` "\\\\hyperref[foo]{foo} \\\\footnote{\\\\label{foo}\\\\externalLink{./url with spaces}{./url with spaces}}" `; exports[`toLaTeX: remark specs reference-link-with-multiple-definitions: reference-link-with-multiple-definitions 1`] = ` "\\\\hyperref[foo]{foo} \\\\footnote{\\\\label{foo}\\\\externalLink{first}{first}} \\\\footnote{\\\\label{foo-1}\\\\externalLink{second}{second}}" `; exports[`toLaTeX: remark specs same-bullet: same-bullet 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax test \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax test \\\\end{itemize} \\\\begin{itemize} \\\\item\\\\relax test \\\\end{itemize} " `; exports[`toLaTeX: remark specs stringify-escape: stringify-escape 1`] = ` "Characters that should be escaped in general: \\\\textbackslash{} \` * [ Characters that shouldn't: \\\\{\\\\}]()\\\\#+-.!>\\"\\\\$\\\\%',/:;=?@\\\\textasciicircum{}\\\\textasciitilde{} Underscores are \\\\_escaped\\\\_ unless they appear in\\\\_the\\\\_middle\\\\_of\\\\_a\\\\_word. or \\\\textbf{\\\\_here}, or here\\\\_\\\\_ Ampersands are escaped only when they would otherwise start an entity: \\\\begin{itemize} \\\\item\\\\relax \\\\textbackslash{}©cat \\\\textbackslash{}\\\\& \\\\textbackslash{}\\\\& \\\\item\\\\relax \\\\©cat \\\\& \\\\&\\\\#x26 \\\\item\\\\relax But: ©cat; \\\\texttt{\\\\≬} \\\\&foo; \\\\& AT\\\\&T \\\\&c \\\\end{itemize} Open parenthesis should be escaped after a shortcut reference: [ref](text) And after a shortcut reference and a space (for GitHub): [ref] (text) Hyphen should be escaped at the beginning of a line: - not a list item - not a list item + not a list item Same for angle brackets: > not a block quote And hash signs: \\\\# not a heading \\\\#\\\\# not a subheading Text under a shortcut reference should be preserved verbatim: \\\\begin{itemize} \\\\item\\\\relax [two*three] \\\\item\\\\relax [two*three] \\\\item\\\\relax [a\\\\textbackslash{}a] \\\\item\\\\relax [a\\\\textbackslash{}a] \\\\item\\\\relax [a\\\\textbackslash{}\\\\textbackslash{}a] \\\\item\\\\relax [a\\\\_a\\\\_a] \\\\end{itemize} \\\\textbf{GFM:} Colon should be escaped in URLs: \\\\begin{itemize} \\\\item\\\\relax http\\\\textbackslash{}://user:password@host:port/path?key=value\\\\#fragment \\\\item\\\\relax https\\\\textbackslash{}://user:password@host:port/path?key=value\\\\#fragment \\\\item\\\\relax http://user:password@host:port/path?key=value\\\\#fragment \\\\item\\\\relax https://user:password@host:port/path?key=value\\\\#fragment \\\\end{itemize} Double tildes should be \\\\textasciitilde{}\\\\textasciitilde{}escaped\\\\textasciitilde{}\\\\textasciitilde{}. And here: foo\\\\textasciitilde{}\\\\textasciitilde{}. Pipes should not be escaped here: | \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} here & they \\\\\\\\ should & tho|ugh \\\\\\\\ \\\\end{longtblr} And here: | here | they | | ---- | ----- | | should | though | And here: here | they ---- | ------ should | though \\\\textbf{Commonmark:} Open angle bracket should be escaped: \\\\begin{itemize} \\\\item\\\\relax \\\\textbackslash{}
\\\\textbackslash{}
\\\\item\\\\relax \\\\textbackslash{} \\\\item\\\\relax
\\\\item\\\\relax \\\\end{itemize} " `; exports[`toLaTeX: remark specs strong-and-em-together-one: strong-and-em-together-one 1`] = ` "\\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word. \\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word. " `; exports[`toLaTeX: remark specs strong-and-em-together-two: strong-and-em-together-two 1`] = ` "perform\\\\_complicated\\\\_task do\\\\_this\\\\_and\\\\_do\\\\_that\\\\_and\\\\_another\\\\_thing perform\\\\textit{complicated}task do\\\\textit{this}and\\\\textit{do}that\\\\textit{and}another*thing " `; exports[`toLaTeX: remark specs strong-emphasis: strong-emphasis 1`] = ` "Foo \\\\textbf{bar} \\\\textbf{baz}. Foo \\\\textbf{bar} \\\\textbf{baz}. " `; exports[`toLaTeX: remark specs strong-initial-white-space: strong-initial-white-space 1`] = ` "\\\\textbf{ bar }. \\\\textbf{ bar }. " `; exports[`toLaTeX: remark specs table: table 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Heading 1 & \\\\textbf{H}eading 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 & Header 3 & Header 4 \\\\\\\\ Cell 1 & Cell 2 & Cell 3 & Cell 4 \\\\\\\\ Cell 5 & Cell 6 & Cell 7 & Cell 8 \\\\\\\\ \\\\end{longtblr} \\\\begin{CodeBlock}{text} Test code \\\\end{CodeBlock} \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 & Header 3 & Header 4 \\\\\\\\ Cell 1 & Cell 2 & Cell 3 & Cell 4 \\\\\\\\ \\\\textit{Cell 5} & Cell 6 & Cell 7 & Cell 8 \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-empty-initial-cell: table-empty-initial-cell 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} & a & c \\\\\\\\ a & b & c \\\\\\\\ a & b & c \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-escaped-pipes: table-escaped-pipes 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} First & Second & third \\\\\\\\ first & second & third \\\\\\\\ first & second | second & third | \\\\\\\\ first & second \\\\textbackslash{} & third \\\\textbackslash{} \\\\\\\\ first & second \\\\textbackslash{}| second & third \\\\textbackslash{}| \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-in-list: table-in-list 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax Unordered: \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr} \\\\item\\\\relax Ordered: \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr} \\\\end{itemize} " `; exports[`toLaTeX: remark specs table-invalid-alignment: table-invalid-alignment 1`] = ` "Missing alignment characters: | a | b | c | | |---|---| | d | e | f | \\\\horizontalLine | a | b | c | |---|---| | | d | e | f | Invalid characters: | a | b | c | |---|-*-|---| | d | e | f | " `; exports[`toLaTeX: remark specs table-loose: table-loose 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-no-body: table-no-body 1`] = ` "\\\\part{Foo} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Name & GitHub & Twitter \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-no-end-of-line: table-no-end-of-line 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} foo & bar \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-one-column: table-one-column 1`] = ` "This is a table: \\\\begin{longtblr}{colspec={X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} a \\\\\\\\ b \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-one-row: table-one-row 1`] = ` "This is a table: \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} a & b & c \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-padded: table-padded 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-pipes-in-code: table-pipes-in-code 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} abc & head2 \\\\\\\\ x & \` & & & \` \\\\\\\\ x & \` \\\\\\\\ x & \` & \` \\\\\\\\ x & \\\\texttt{f} \\\\\\\\ x & \`\`\`\` \\\\\\\\ x & \`\\\\texttt{f} \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} abc & head2 \\\\\\\\ x & \` \\\\\\\\ x & \` & \` \\\\\\\\ x & \\\\texttt{f} \\\\\\\\ x & \`\`\`\` \\\\\\\\ x & \`\\\\texttt{f} \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-spaced: table-spaced 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Header 1 & Header 2 \\\\\\\\ Cell 1 & Cell 2 \\\\\\\\ Cell 3 & Cell 4 \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs table-with-image: table-with-image 1`] = ` "Someone wanted to do this, let's implement it! \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} c1 & c2 \\\\\\\\ c3 & \\\\includegraphics{https://zestedesavoir.com/media/galleries/426/56dc4a1e-416b-4a9d-830d-95b45d58a17a.png} \\\\\\\\ \\\\end{longtblr} " `; exports[`toLaTeX: remark specs tabs: tabs 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax this is a list item indented with tabs \\\\item\\\\relax this is a list item indented with spaces \\\\end{itemize} Code: \\\\begin{CodeBlock}{text} this code block is indented by one tab \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} this code block is indented by two tabs \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} + this is an example list item indented with tabs + this is an example list item indented with spaces \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs tabs-and-spaces: tabs-and-spaces 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax this is a list item indented with tabs \\\\item\\\\relax this is a list item indented with spaces \\\\end{itemize} Code: \\\\begin{CodeBlock}{text} this code block is indented by one tab \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} this code block is indented by two tabs \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} + this is an example list item indented with tabs + this is an example list item indented with spaces \\\\end{CodeBlock} " `; exports[`toLaTeX: remark specs task-list: task-list 1`] = ` "\\\\part{Empty items} \\\\begin{itemize} \\\\item\\\\relax [ ] \\\\item\\\\relax [ ] \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [x] \\\\item\\\\relax [X] \\\\end{enumerate} \\\\part{Single space} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\item[$\\\\square$]\\\\relax \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\item[$\\\\boxtimes$]\\\\relax \\\\end{enumerate} \\\\part{Tab} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\item[$\\\\square$]\\\\relax \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\item[$\\\\boxtimes$]\\\\relax \\\\end{enumerate} \\\\part{No white space with content} \\\\begin{itemize} \\\\item\\\\relax [ ]Hello; \\\\item\\\\relax [ ]World; \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [x]Foo. \\\\item\\\\relax [X]Bar \\\\end{enumerate} \\\\part{Single space with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Hello; \\\\item[$\\\\square$]\\\\relax World; \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax World :D \\\\end{enumerate} \\\\part{Single tab with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Hello; \\\\item[$\\\\square$]\\\\relax World; \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax Hello. \\\\end{enumerate} \\\\part{Multiple spaces with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} World; \\\\end{CodeBlock} \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Foo. \\\\item[$\\\\boxtimes$]\\\\relax Bar. \\\\end{enumerate} \\\\part{Multiple tabs with content} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} World; \\\\end{CodeBlock} \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\begin{CodeBlock}{text} Foo. \\\\end{CodeBlock} \\\\item[$\\\\boxtimes$]\\\\relax \\\\begin{CodeBlock}{text} Bar. \\\\end{CodeBlock} \\\\end{enumerate} \\\\part{Mixed tabs and spaces} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax \\\\begin{CodeBlock}{text} World; \\\\end{CodeBlock} \\\\end{enumerate} \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax \\\\begin{CodeBlock}{text} Hello; \\\\end{CodeBlock} \\\\item[$\\\\square$]\\\\relax World. \\\\end{itemize} \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Bar. \\\\end{enumerate} \\\\part{Line breaks} \\\\begin{itemize} \\\\item\\\\relax [ ] Hello; \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [ ] Hello; \\\\end{enumerate} \\\\part{Multiple unfinished characters} \\\\begin{itemize} \\\\item\\\\relax [ ] Hello; \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax [ ] World; \\\\item\\\\relax [ ] Hello; \\\\item\\\\relax [ ] World. \\\\end{enumerate} " `; exports[`toLaTeX: remark specs task-list-ordered: task-list-ordered 1`] = ` "\\\\begin{enumerate} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{enumerate} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{enumerate} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{enumerate} " `; exports[`toLaTeX: remark specs task-list-unordered-asterisk: task-list-unordered-asterisk 1`] = ` "\\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{itemize} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{itemize} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{itemize} " `; exports[`toLaTeX: remark specs task-list-unordered-dash: task-list-unordered-dash 1`] = ` "\\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{itemize} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{itemize} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{itemize} " `; exports[`toLaTeX: remark specs task-list-unordered-plus: task-list-unordered-plus 1`] = ` "\\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax Mercury; \\\\item\\\\relax [] Venus (this one’s invalid); \\\\item[$\\\\boxtimes$]\\\\relax Earth: \\\\begin{itemize} \\\\item[$\\\\boxtimes$]\\\\relax Moon. \\\\end{itemize} \\\\item[$\\\\square$]\\\\relax Mars; \\\\item\\\\relax [] Neptune (this one’s also invalid). \\\\end{itemize} " `; exports[`toLaTeX: remark specs tidyness: tidyness 1`] = ` "\\\\begin{Quotation} A list within a blockquote: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\end{Quotation} " `; exports[`toLaTeX: remark specs title-attributes: title-attributes 1`] = ` "\\\\part{Links} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Implementation & Characters & Nested & Mismatched & Escaped & Named Entities & Numbered Entities \\\\\\\\ Markdown.pl & \\\\texttt{\\"} & Yes & Yes & No & Yes & Yes \\\\\\\\ GitHub & \\\\texttt{\\"} & Yes & Yes & No & No & No \\\\\\\\ CommonMark & \\\\texttt{\\"} & No & No & Yes & Yes & Yes \\\\\\\\ Markdown.pl & \\\\texttt{'} & Yes & Yes & No & Yes & Yes \\\\\\\\ GitHub & \\\\texttt{'} & Yes & Yes & No & No & No \\\\\\\\ CommonMark & \\\\texttt{'} & No & No & Yes & Yes & Yes \\\\\\\\ Markdown.pl & \\\\texttt{()} & - & - & - & - & - \\\\\\\\ GitHub & \\\\texttt{()} & - & - & - & - & - \\\\\\\\ CommonMark & \\\\texttt{()} & No & Yes & Yes & Yes & Yes \\\\\\\\ \\\\end{longtblr} \\\\chapter{Double quotes} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\chapter{Single quotes} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\externalLink{Hello}{./world.html} \\\\part{Images} \\\\chapter{Double quotes} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\chapter{Single quotes} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} \\\\includegraphics{./world.png} " `; exports[`toLaTeX: remark specs toplevel-paragraphs: toplevel-paragraphs 1`] = ` "hello world how are you how are you hello world \\\\begin{CodeBlock}{text} how are you \\\\end{CodeBlock} hello world \\\\horizontalLine hello world \\\\part{how are you} hello world \\\\part{how are you} hello world \\\\begin{Quotation} how are you \\\\end{Quotation} hello world \\\\begin{itemize} \\\\item\\\\relax how are you \\\\end{itemize} hello world
how are you
hello world how are you hello \\\\hyperref[how]{world} \\\\footnote{\\\\label{how}\\\\externalLink{/are/you}{/are/you}}
hello
hello " `; exports[`toLaTeX: remark specs tricky-list: tricky-list 1`] = ` "\\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax hello world \\\\end{itemize} \\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax hello world \\\\end{itemize} \\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax Hello world \\\\end{itemize} \\\\textbf{hello} \\\\textit{world} \\\\begin{itemize} \\\\item\\\\relax hello world \\\\end{itemize} " `; ================================================ FILE: packages/rebber/__tests__/fixtures/amps-and-angles-encoding.text ================================================ AT&T has an ampersand in their name. AT&T is another way to write it. This & that. 4 < 5. 6 > 5. Here's a [link] [1] with an ampersand in the URL. Here's a link with an amersand in the link text: [AT&T] [2]. Here's an inline [link](/script?foo=1&bar=2). Here's an inline [link](). [1]: http://example.com/?foo=1&bar=2 [2]: http://att.com/ "AT&T" ================================================ FILE: packages/rebber/__tests__/fixtures/auto-link-invalid.text ================================================ hello world ================================================ FILE: packages/rebber/__tests__/fixtures/auto-link-output.output.text ================================================ Link: . Link to an email: . Link without protocol, which should not render as an auto-link because they are easily mistaken for HTML: [google.com](google.com). ================================================ FILE: packages/rebber/__tests__/fixtures/auto-link-url-invalid.text ================================================ http://. Link to an email: . Link to an email: . With an ampersand: * In a list? * * It should. > Blockquoted: Auto-links should not occur here: `` or here: ================================================ FILE: packages/rebber/__tests__/fixtures/backslash-escapes.text ================================================ These should all get escaped: Backslash: \\ Backtick: \` Asterisk: \* Underscore: \_ Left brace: \{ Right brace: \} Left bracket: \[ Right bracket: \] Left paren: \( Right paren: \) Greater-than: \> Hash: \# Period: \. Bang: \! Plus: \+ Minus: \- **GFM:** Pipe: \| Tilde: \~ **Commonmark:** Quote: \" Dollar: \$ Percentage: \% Ampersand: \& Single quote: \' Comma: \, Forward slash: \/ Colon: \: Semicolon: \; Less-than: \< Equals: \= Question mark: \? At-sign: \@ Caret: \^ New line: \ only works in paragraphs. These should not, because they occur within a code block: Backslash: \\ Backtick: \` Asterisk: \* Underscore: \_ Left brace: \{ Right brace: \} Left bracket: \[ Right bracket: \] Left paren: \( Right paren: \) Greater-than: \> Hash: \# Period: \. Bang: \! Plus: \+ Minus: \- **GFM:** Pipe: \| Tilde: \~ **Commonmark:** Quote: \" Dollar: \$ Percentage: \% Ampersand: \& Single quote: \' Comma: \, Forward slash: \/ Colon: \: Semicolon: \; Less-than: \< Equals: \= Question mark: \? At-sign: \@ Caret: \^ New line: \ only works in paragraphs. Nor should these, which occur in code spans: Backslash: `\\` Backtick: `` \` `` Asterisk: `\*` Underscore: `\_` Left brace: `\{` Right brace: `\}` Left bracket: `\[` Right bracket: `\]` Left paren: `\(` Right paren: `\)` Greater-than: `\>` Hash: `\#` Period: `\.` Bang: `\!` Plus: `\+` Minus: `\-` **GFM:** Pipe: `\|` Tilde: `\~` **Commonmark:** Quote: `\"` Dollar: `\$` Percentage: `\%` Ampersand: `\&` Single quote: `\'` Comma: `\,` Forward slash: `\/` Colon: `\:` Semicolon: `\;` Less-than: `\<` Equals: `\=` Question mark: `\?` At-sign: `\@` Caret: `\^` New line: `\ ` only works in paragraphs. These should get escaped, even though they're matching pairs for other Markdown constructs: \*asterisks\* \_underscores\_ \`backticks\` This is a code span with a literal backslash-backtick sequence: `` \` `` This is a tag with unescaped backticks bar. This is a tag with backslashes bar. ================================================ FILE: packages/rebber/__tests__/fixtures/block-elements.text ================================================ * Different lists should receive two newline characters between them. * This is another list. > * The same goes for lists in block quotes. > > > * This is another list. * And for lists in lists: 1. First sublist. 1. Second sublist. And for lists followed by indented code blocks: * This is a paragraph in a list And this is code(); ================================================ FILE: packages/rebber/__tests__/fixtures/blockquote-indented.text ================================================ > bar > baz ================================================ FILE: packages/rebber/__tests__/fixtures/blockquote-lazy-code.text ================================================ > foo bar ================================================ FILE: packages/rebber/__tests__/fixtures/blockquote-lazy-fence.text ================================================ > ``` > aNormalCodeBlockInABlockqoute(); > ``` A paragraph. > ``` thisIsAlsoSomeCodeInABlockquote(); > ``` A paragraph. > ``` aNonTerminatedCodeBlockInABlockquote(); ``` aNewCodeBlockFollowingTheBlockQuote(); ``` A paragraph. > Something in a blockquote. ``` aNewCodeBlock(); ``` ================================================ FILE: packages/rebber/__tests__/fixtures/blockquote-lazy-list.text ================================================ > This is a blockquote. - And in normal mode this is an internal list, but in commonmark this is a top level list. ================================================ FILE: packages/rebber/__tests__/fixtures/blockquote-lazy-rule.text ================================================ > This is a blockquote. Followed by a rule. * * * ================================================ FILE: packages/rebber/__tests__/fixtures/blockquote-list-item.text ================================================ This fails in markdown.pl and upskirt: * hello > world ================================================ FILE: packages/rebber/__tests__/fixtures/blockquotes-empty-lines.output.text ================================================ > Note there is no space on the following line. > > Note there is no space on the preceding line. ================================================ FILE: packages/rebber/__tests__/fixtures/blockquotes-with-code-blocks.text ================================================ > Example: > > sub status { > print "working"; > } > > Or: > > sub status { > return "working"; > } ================================================ FILE: packages/rebber/__tests__/fixtures/blockquotes.text ================================================ > This is a blockquote. > This is, in commonmark mode, another blockquote. ================================================ FILE: packages/rebber/__tests__/fixtures/bom.text ================================================ # Hello from a BOM Be careful when editing this file! ================================================ FILE: packages/rebber/__tests__/fixtures/breaks-hard.text ================================================ These are not breaks: Look at the pretty line breaks. These are breaks: Look at the pretty line breaks. In `commonmark: true` mode, an escaped newline character is exposed as a `break` node: Look at the\ pretty line\ breaks. ================================================ FILE: packages/rebber/__tests__/fixtures/case-insensitive-refs.text ================================================ [hi] [HI]: /url ================================================ FILE: packages/rebber/__tests__/fixtures/code-block-escape.text ================================================ A little flaw: ```python \end{CodeBlock} \end {CodeBlock} ``` An ingenuous flaw: ``` \end\end{CodeBlock}{CodeBlock} \input{/etc/passwd} \begin{CodeBlock}{text} ``` ================================================ FILE: packages/rebber/__tests__/fixtures/code-block-indentation.nooutput.text ================================================ Fenced code blocks are normally not exdented, however, when the initial fence is indented by spaces, the value of the code is exdented by up to that amount of spaces. ``` This is a code block... ...which is not exdented. ``` But... ``` This one... ...is. ``` And... ``` So is this... ...one. ``` ================================================ FILE: packages/rebber/__tests__/fixtures/code-block-nesting-bug.nooutput.text ================================================ GitHub, thus RedCarpet, has a bug where “nested” fenced code blocks, even with shorter fences, can exit their actual “parent” block. Note that this bug does not occur on indented code-blocks. ````foo ```bar baz ``` ```` Even with a different fence marker: ````foo ~~~bar baz ~~~ ```` And reversed: ~~~~foo ~~~bar baz ~~~ ~~~~ ~~~~foo ```bar baz ``` ~~~~ ================================================ FILE: packages/rebber/__tests__/fixtures/code-block.output.fence=`.text ================================================ Ticks: ```javascript alert('Hello World!'); ``` ================================================ FILE: packages/rebber/__tests__/fixtures/code-block.output.fence=~.text ================================================ Tildes: ~~~javascript alert('Hello World!'); ~~~ ================================================ FILE: packages/rebber/__tests__/fixtures/code-blocks.output.fences.text ================================================ A code block using fences: ``` alert('Hello World!'); ``` ================================================ FILE: packages/rebber/__tests__/fixtures/code-blocks.output.text ================================================ A code block NOT using fences: alert('Hello World!'); ================================================ FILE: packages/rebber/__tests__/fixtures/code-blocks.text ================================================ code block on the first line Regular text. code block indented by spaces Regular text. the lines in this block all contain trailing spaces Regular Text. code block on the last line ================================================ FILE: packages/rebber/__tests__/fixtures/code-spans.text ================================================ `` Fix for backticks within HTML tag: like this Here's how you put `` `backticks` `` in a code span. Additionally, empty code spans are NOT supported: ``. Here’s an example, `` foo ` bar ``. And here, ` `` `. `` // this is also inline code `` So is this `foo bar baz`. And this `foo `` bar` And `this\`but this is text`. ================================================ FILE: packages/rebber/__tests__/fixtures/def-blocks.text ================================================ > hello > [1]: hello * * * > hello [2]: hello * hello * [3]: hello * hello [4]: hello > foo > bar [1]: foo > bar ================================================ FILE: packages/rebber/__tests__/fixtures/definition-newline.text ================================================ [baz]: /url ( ) [foo]: /url " " [bar]: /url ' ' [baz]: /url (foo bar) [baz]: /url "foo bar" [baz]: /url 'foo bar' [baz]: /url 'foo ================================================ FILE: packages/rebber/__tests__/fixtures/definition-unclosed-attribute.text ================================================ [baz]: /url (there [foo]: /url "there [bar]: /url 'there [baz]: url ( [foo]: url " [bar]: /url ' [baz]: ( [foo]: " [bar]: ' ================================================ FILE: packages/rebber/__tests__/fixtures/definition-unclosed.text ================================================ [foo]: [bar]: Already linked: http://example.com/.

Already linked: [http://example.com/](http://example.com/). Already linked: **http://example.com/**. ================================================ FILE: packages/rebber/__tests__/fixtures/emphasis-empty.text ================================================ Hello ** ** world. Hello __ __ world. Hello * * world. Hello _ _ world. ================================================ FILE: packages/rebber/__tests__/fixtures/emphasis-escaped-final-marker.text ================================================ *bar\* **bar\** _bar\_ __bar\__ ================================================ FILE: packages/rebber/__tests__/fixtures/emphasis-internal.text ================================================ These words should_not_be_emphasized. ================================================ FILE: packages/rebber/__tests__/fixtures/emphasis.output.emphasis=-asterisk-.strong=_.text ================================================ *emphasis*. __strong__. ================================================ FILE: packages/rebber/__tests__/fixtures/emphasis.output.emphasis=_.strong=-asterisk-.text ================================================ _emphasis_. **strong**. ================================================ FILE: packages/rebber/__tests__/fixtures/empty.text ================================================ ================================================ FILE: packages/rebber/__tests__/fixtures/entities-advanced.text ================================================ > However, &MadeUpEntities; are kept in the document. > Entities even work in the language flag of fenced code blocks: > ```some©language > alert('Hello'); > ``` > And in an auto-link: > Foo and bar and http://example©xample.com and baz. > Or in [l©nks]( > ~/some©file "in some pl©ce") > Or in [l©lnks]( > <~/some©file> > "in some pl©ce") > Or in ![ > l©nks > ]( > ~/some©file "in some pl©ce") *** > Or in ![l©lnks]( > <~/some©file> > "in some pl©ce") > Or in ![ > l©nks > ][12] > [1]: http://example©xample.com "in some > pl©ce" > [ > 1 > ]: http://example©xample.com "in some > pl©ce" *** > But, entities are not interpreted in `inline cöde`, or in > code blocks: > CÖDE block. ================================================ FILE: packages/rebber/__tests__/fixtures/entities.output.entities.text ================================================ # Entities Plain text: AT&T with entity, AT&T with numeric entity, AT&T without entity. Fenced code language flags: ```AT&T Something in the AT&T language ``` ```AT&T Something in the AT&T language ``` Automatic links: , , and . Link `href`: [With entity](http://at&t.com), [numeric entity](http://at&t.com), [without entity](http://at&t.com). Link `title`: [With entity](http://att.com "AT&T"), [numeric entity](http://att.com "AT&T"), [without entity](http://example.com "AT&T"). Image `src`: ![With entity](http://at&t.com/fav.ico), ![numeric entity](http://at&t.com/fav.ico), ![without entity](http://at&t.com/fav.ico). Image `alt`: ![AT&T with entity](http://att.com/fav.ico), ![AT&T with numeric entity](http://att.com/fav.ico), ![AT&T without entity](http://att.com/fav.ico). Image `title`: ![With entity](http://att.com/fav.ico "AT&T"), ![numeric entity](http://att.com/fav.ico "AT&T"), ![without entity](http://att.com/fav.ico "AT&T"). Image Reference `alt`: ![AT&T with entity][favicon], ![AT&T with numeric entity][favicon], ![AT&T without entity][favicon]. [favicon]: http://att.com/fav.ico "ATT favicon" Shortcut and collapsed references: [foo&bar], [foo&bar][], [foo&bar], [foo&bar][]. [foo&bar]: http://example.com [foo&bar]: http://example.com ================================================ FILE: packages/rebber/__tests__/fixtures/entities.output.entities=escape.text ================================================ # Entities Plain text: AT&T with entity, AT&T with numeric entity, AT&T without entity. Fenced code language flags: ```AT&T Something in the AT&T language ``` ```AT&T Something in the AT&T language ``` Automatic links: , , and . Link `href`: [With entity](http://at&t.com), [numeric entity](http://at&t.com), [without entity](http://at&t.com). Link `title`: [With entity](http://att.com "AT&T"), [numeric entity](http://att.com "AT&T"), [without entity](http://example.com "AT&T"). Image `src`: ![With entity](http://at&t.com/fav.ico), ![numeric entity](http://at&t.com/fav.ico), ![without entity](http://at&t.com/fav.ico). Image `alt`: ![AT&T with entity](http://att.com/fav.ico), ![AT&T with numeric entity](http://att.com/fav.ico), ![AT&T without entity](http://att.com/fav.ico). Image `title`: ![With entity](http://att.com/fav.ico "AT&T"), ![numeric entity](http://att.com/fav.ico "AT&T"), ![without entity](http://att.com/fav.ico "AT&T"). Image Reference `alt`: ![AT&T with entity][favicon], ![AT&T with numeric entity][favicon], ![AT&T without entity][favicon]. [favicon]: http://att.com/fav.ico "ATT favicon" Shortcut and collapsed references: [foo&bar], [foo&bar][], [foo&bar], [foo&bar][]. [foo&bar]: http://example.com [foo&bar]: http://example.com ================================================ FILE: packages/rebber/__tests__/fixtures/entities.output.entities=numbers.text ================================================ # Entities Plain text: AT&T with entity, AT&T with numeric entity, AT&T without entity. Fenced code language flags: ```AT&T Something in the AT&T language ``` ```AT&T Something in the AT&T language ``` Automatic links: , , and . Link `href`: [With entity](http://at&t.com), [numeric entity](http://at&t.com), [without entity](http://at&t.com). Link `title`: [With entity](http://att.com "AT&T"), [numeric entity](http://att.com "AT&T"), [without entity](http://example.com "AT&T"). Image `src`: ![With entity](http://at&t.com/fav.ico), ![numeric entity](http://at&t.com/fav.ico), ![without entity](http://at&t.com/fav.ico). Image `alt`: ![AT&T with entity](http://att.com/fav.ico), ![AT&T with numeric entity](http://att.com/fav.ico), ![AT&T without entity](http://att.com/fav.ico). Image `title`: ![With entity](http://att.com/fav.ico "AT&T"), ![numeric entity](http://att.com/fav.ico "AT&T"), ![without entity](http://att.com/fav.ico "AT&T"). Image Reference `alt`: ![AT&T with entity][favicon], ![AT&T with numeric entity][favicon], ![AT&T without entity][favicon]. [favicon]: http://att.com/fav.ico "ATT favicon" Shortcut and collapsed references: [foo&bar], [foo&bar][], [foo&bar], [foo&bar][]. [foo&bar]: http://example.com [foo&bar]: http://example.com ================================================ FILE: packages/rebber/__tests__/fixtures/entities.output.noentities.text ================================================ # Entities Plain text: AT&T with entity, AT&T with numeric entity, AT&T without entity. Fenced code language flags: ```AT&T Something in the AT&T language ``` ```AT&T Something in the AT&T language ``` Automatic links: , , and . Link `href`: [With entity](http://at&t.com), [numeric entity](http://at&t.com), [without entity](http://at&t.com). Link `title`: [With entity](http://att.com "AT&T"), [numeric entity](http://att.com "AT&T"), [without entity](http://example.com "AT&T"). Image `src`: ![With entity](http://at&t.com/fav.ico), ![numeric entity](http://at&t.com/fav.ico), ![without entity](http://at&t.com/fav.ico). Image `alt`: ![AT&T with entity](http://att.com/fav.ico), ![AT&T with numeric entity](http://att.com/fav.ico), ![AT&T without entity](http://att.com/fav.ico). Image `title`: ![With entity](http://att.com/fav.ico "AT&T"), ![numeric entity](http://att.com/fav.ico "AT&T"), ![without entity](http://att.com/fav.ico "AT&T"). Image Reference `alt`: ![AT&T with entity][favicon], ![AT&T with numeric entity][favicon], ![AT&T without entity][favicon]. [favicon]: http://att.com/fav.ico "ATT favicon" Shortcut and collapsed references: [foo&bar], [foo&bar][], [foo&bar], [foo&bar][]. [foo&bar]: http://example.com [foo&bar]: http://example.com ================================================ FILE: packages/rebber/__tests__/fixtures/entities.text ================================================ Lots of entities are supported in mdast:  , &, ©, Æ, Ď, ¾, ℋ, ⅆ, ∲, &c. Even some entities with a missing terminal semicolon are parsed correctly (as per the HTML5 spec): ÿ, á, ©, and &. However, &MadeUpEntities; are kept in the document. Entities even work in the language flag of fenced code blocks: ```some—language alert('Hello'); ``` Or in [línks](~/some—file "in some plæce") Or in ![ímages](~/an–image.png "© Someone") But, entities are not interpreted in `inline cöde`, or in code blocks: CÖDE block. ================================================ FILE: packages/rebber/__tests__/fixtures/escaped-angles.text ================================================ \> ================================================ FILE: packages/rebber/__tests__/fixtures/fenced-code-empty.text ================================================ Normal with language tag: ```js ``` With white space: ``` bash ``` With very long fences: `````` `````` With nothing: ``` ``` ================================================ FILE: packages/rebber/__tests__/fixtures/fenced-code-trailing-characters-2.nooutput.text ================================================ ``` ``` aaa ``` ================================================ FILE: packages/rebber/__tests__/fixtures/fenced-code-trailing-characters.nooutput.text ================================================ ```js foo(); ```bash ``` ================================================ FILE: packages/rebber/__tests__/fixtures/fenced-code-white-space-after-flag.text ================================================ ``` js foo(); ``` ~~~~~ bash echo "hello, ${WORLD}" ~~~~~ ================================================ FILE: packages/rebber/__tests__/fixtures/fenced-code.text ================================================ ``` js var a = 'hello'; console.log(a + ' world'); ``` ~~~bash echo "hello, ${WORLD}" ~~~ ```````longfence Q: What do you call a tall person who sells stolen goods? ``````` ~~~~~~~~~~ ManyTildes A longfence! ~~~~~~~~~~ ================================================ FILE: packages/rebber/__tests__/fixtures/hard-wrapped-paragraphs-with-list-like-lines.text ================================================ In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the 123. middle of a paragraph looked like a list item. Here's one with a bullet. * criminey. Non-GFM does not create a list for either. GFM does not create a list for `8.`, but does for `*`. CommonMark creates a list for both. All versions create lists for the following. * Here's one with a bullet. * criminey. ...and the following: 1. In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. ================================================ FILE: packages/rebber/__tests__/fixtures/heading-atx-closed-trailing-white-space.text ================================================ # Foo # ## Bar ## ================================================ FILE: packages/rebber/__tests__/fixtures/heading-atx-empty.text ================================================ # # ## ### ### #### ##### ##### ###### ================================================ FILE: packages/rebber/__tests__/fixtures/heading-in-blockquote.text ================================================ > A blockquote with some more text. A normal paragraph. > A blockquote followed by a horizontal rule (in CommonMark). --- > A heading in a blockquote > --- ================================================ FILE: packages/rebber/__tests__/fixtures/heading-in-paragraph.text ================================================ Hello World === ================================================ FILE: packages/rebber/__tests__/fixtures/heading-not-atx.text ================================================ #This is not a heading, per CommonMark: Kramdown (GitHub) neither supports unspaced ATX-headings. ######## h7? ######### h8? ########## h9? More than six # characters is not a heading: ================================================ FILE: packages/rebber/__tests__/fixtures/heading-setext-with-initial-spacing.text ================================================ Heading 1 ========= Heading 2 --------- Both these headings caused positional problems in on commit daa344c and before. ================================================ FILE: packages/rebber/__tests__/fixtures/heading.output.close-atx.text ================================================ # Heading 1 # ## Heading 2 ## ### Heading 4 ### #### Heading 4 #### ##### Heading 5 ##### ###### Heading 6 ###### ================================================ FILE: packages/rebber/__tests__/fixtures/heading.output.setext.text ================================================ Heading 1 ========= Heading 2 --------- ### Heading 4 #### Heading 4 ##### Heading 5 ###### Heading 6 ================================================ FILE: packages/rebber/__tests__/fixtures/horizontal-rules-adjacent.text ================================================ *** --- ___ The three asterisks are not a Setext header. This is a paragraph. *** This is another paragraph. ___ But this is a secondary heading. --- --- ================================================ FILE: packages/rebber/__tests__/fixtures/horizontal-rules.text ================================================ Dashes: --- --- --- --- --- - - - - - - - - - - - - - - - Asterisks: *** *** *** *** *** * * * * * * * * * * * * * * * Underscores: ___ ___ ___ ___ ___ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ================================================ FILE: packages/rebber/__tests__/fixtures/hr-list-break.text ================================================ * hello world * how are * * * you today? The above asterisks do split the list, but the below ones do not. - hello world - how are - * * * you today? + Neither do these + how are + * * you today? - But these do - how are - - - - you today? ================================================ FILE: packages/rebber/__tests__/fixtures/hr.output.norule-spaces.text ================================================ *** ================================================ FILE: packages/rebber/__tests__/fixtures/hr.output.rule-repetition=5.text ================================================ * * * * * ================================================ FILE: packages/rebber/__tests__/fixtures/hr.output.rule=-.text ================================================ - - - ================================================ FILE: packages/rebber/__tests__/fixtures/hr.output.rule=-asterisk-.text ================================================ * * * ================================================ FILE: packages/rebber/__tests__/fixtures/hr.output.rule=_.text ================================================ _ _ _ ================================================ FILE: packages/rebber/__tests__/fixtures/html-advanced.text ================================================ Simple block on one line:
foo
And nested without indentation:
foo
bar
================================================ FILE: packages/rebber/__tests__/fixtures/html-attributes.text ================================================ # Block-level
foo ================================================ FILE: packages/rebber/__tests__/fixtures/html-comments.text ================================================ Paragraph one. What follows is not an HTML comment because it contains two consecutive dashes: . But this is fine (in commonmark): And, this is wrong (in commonmark): --> The end. ================================================ FILE: packages/rebber/__tests__/fixtures/html-declaration.text ================================================ foo ================================================ FILE: packages/rebber/__tests__/fixtures/html-indented.text ================================================
*hello*
*hello* alpha ================================================ FILE: packages/rebber/__tests__/fixtures/html-processing-instruction.text ================================================ '; ?> ================================================ FILE: packages/rebber/__tests__/fixtures/html-simple.text ================================================ Here's a simple block:
foo
This should be a code block, though:
foo
As should this:
foo
Now, nested:
foo
This should just be an HTML comment: Multiline: Code block: Just plain comment, with trailing spaces on the line: Code:
Hr's:








================================================ FILE: packages/rebber/__tests__/fixtures/html-tags.text ================================================ # Block
<-article>
). ![Hello [world]!](./hello-world.html). ![Hello [world]!](<./hello-world.html>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-text-empty.text ================================================ [](./hello-world.html). [](<./hello-world.html>). ![](./hello-world.html). ![](<./hello-world.html>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-text-entity-delimiters.text ================================================ [Hello [world]!](./hello-world.html). [Hello [world]!](<./hello-world.html>). ![Hello [world]!](./hello-world.html). ![Hello [world]!](<./hello-world.html>). [Hello [world]!](./hello-world.html). [Hello [world]!](<./hello-world.html>). ![Hello [world]!](./hello-world.html). ![Hello [world]!](<./hello-world.html>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-text-escaped-delimiters.text ================================================ [Hello \[world\]!](./hello-world.html). [Hello \[world\]!](<./hello-world.html>). ![Hello \[world\]!](./hello-world.html). ![Hello \[world\]!](<./hello-world.html>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-text-mismatched-delimiters.text ================================================ [Hello [world!](./hello-world.html). [Hello [world!](<./hello-world.html>). ![Hello [world!](./hello-world.html). ![Hello [world!](<./hello-world.html>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-double-quotes-delimiters.text ================================================ [Hello](./world.html "Hello "World" Hello!"). [Hello](<./world.html> "Hello "World" Hello!"). ![Hello](./world.html "Hello "World" Hello!"). ![Hello](<./world.html> "Hello "World" Hello!"). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-double-quotes-entity-delimiters.text ================================================ [Hello](./world.html "Hello "World" Hello!"). [Hello](<./world.html> "Hello "World" Hello!"). ![Hello](./world.html "Hello "World" Hello!"). ![Hello](<./world.html> "Hello "World" Hello!"). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-double-quotes-escaped-delimiters.text ================================================ [Hello](./world.html "Hello \"World\" Hello!"). [Hello](<./world.html> "Hello \"World\" Hello!"). ![Hello](./world.html "Hello \"World\" Hello!"). ![Hello](<./world.html> "Hello \"World\" Hello!"). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-double-quotes-mismatched-delimiters.text ================================================ [Hello](./world.html "Hello "World Hello!"). [Hello](<./world.html> "Hello "World Hello!"). ![Hello](./world.html "Hello "World Hello!"). ![Hello](<./world.html> "Hello "World Hello!"). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-double-quotes.text ================================================ [Hello](./world.html "Hello World!"). [Hello](<./world.html> "Hello World!"). ![Hello](./world.html "Hello World!"). ![Hello](<./world.html> "Hello World!"). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-empty-double-quotes.text ================================================ [Hello](./world.html ""). [Hello](<./world.html> ""). ![Hello](./world.html ""). ![Hello](<./world.html> ""). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-empty-parentheses.text ================================================ [Hello](./world.html ()). [Hello](<./world.html> ()). ![Hello](./world.html ()). ![Hello](<./world.html> ()). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-empty-single-quotes.text ================================================ [Hello](./world.html ''). [Hello](<./world.html> ''). ![Hello](./world.html ''). ![Hello](<./world.html> ''). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-parentheses.text ================================================ [Hello](./world.html (Hello World!)). [Hello](<./world.html> (Hello World!)). ![Hello](./world.html (Hello World!)). ![Hello](<./world.html> (Hello World!)). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-single-quotes-delimiters.text ================================================ [Hello](./world.html 'Hello 'World' Hello!'). [Hello](<./world.html> 'Hello 'World' Hello!'). ![Hello](./world.html 'Hello 'World' Hello!'). ![Hello](<./world.html> 'Hello 'World' Hello!'). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-single-quotes-entity-delimiters.text ================================================ [Hello](./world.html "Hello 'World' Hello!"). [Hello](<./world.html> "Hello 'World' Hello!"). ![Hello](./world.html "Hello 'World' Hello!"). ![Hello](<./world.html> "Hello 'World' Hello!"). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-single-quotes-escaped-delimiters.text ================================================ [Hello](./world.html 'Hello \'World\' Hello!'). [Hello](<./world.html> 'Hello \'World\' Hello!'). ![Hello](./world.html 'Hello \'World\' Hello!'). ![Hello](<./world.html> 'Hello \'World\' Hello!'). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-single-quotes-mismatched-delimiters.text ================================================ [Hello](./world.html 'Hello 'World Hello!'). [Hello](<./world.html> 'Hello 'World Hello!'). ![Hello](./world.html 'Hello 'World Hello!'). ![Hello](<./world.html> 'Hello 'World Hello!'). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-single-quotes.text ================================================ [Hello](./world.html 'Hello World!'). [Hello](<./world.html> 'Hello World!'). ![Hello](./world.html 'Hello World!'). ![Hello](<./world.html> 'Hello World!'). ================================================ FILE: packages/rebber/__tests__/fixtures/links-title-unclosed.text ================================================ [Hello](./world.html 'Hello [Hello](<./world.html> 'Hello ![Hello](./world.html 'Hello ![Hello](<./world.html> 'Hello [Hello](./world.html "Hello [Hello](<./world.html> "Hello ![Hello](./world.html "Hello ![Hello](<./world.html> "Hello [Hello](./world.html (Hello [Hello](<./world.html> (Hello ![Hello](./world.html (Hello ![Hello](<./world.html> (Hello ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-empty-title-double-quotes.text ================================================ [Hello]("World!"). [Hello]( "World!"). [World](<> "World!"). ![Hello]("World!"). ![Hello]( "World!"). ![World](<> "World!"). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-empty-title-parentheses.text ================================================ [Hello]((World!)). [Hello]( (World!)). [World](<> (World!)). ![Hello]((World!)). ![Hello]( (World!)). ![World](<> (World!)). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-empty-title-single-quotes.text ================================================ [Hello]('World!'). [Hello]( 'World!'). [World](<> 'World!'). ![Hello]('World!'). ![Hello]( 'World!'). ![World](<> 'World!'). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-empty.text ================================================ [Hello](). [World](<>). ![Hello](). ![World](<>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-entity-parentheses.text ================================================ [Hello](./world(and-hello(world)). [Hello](<./world(and-hello(world)>). [Hello](./world(and)helloworld)). [Hello](<./world(and)helloworld)>). [Hello](./world(and-hello(world)). [Hello](<./world(and-hello(world)>). [Hello](./world(and)helloworld)). [Hello](<./world(and)helloworld)>). ![Hello](./world(and-hello(world)). ![Hello](<./world(and-hello(world)>). ![Hello](./world(and)helloworld)). ![Hello](<./world(and)helloworld)>). ![Hello](./world(and-hello(world)). ![Hello](<./world(and-hello(world)>). ![Hello](./world(and)helloworld)). ![Hello](<./world(and)helloworld)>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-escaped-parentheses.text ================================================ [Hello](./world\(and-hello\(world\)). [Hello](<./world\(and-hello\(world\)>). [Hello](./world\(and\)helloworld\)). [Hello](<./world\(and\)helloworld\)>). ![Hello](./world\(and-hello\(world\)). ![Hello](<./world\(and-hello\(world\)>). ![Hello](./world\(and\)helloworld\)). ![Hello](<./world\(and\)helloworld\)>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-mismatched-parentheses.text ================================================ [Hello](./world(and-hello(world)). [Hello](<./world(and-hello(world)>). [Hello](./world(and)helloworld)). [Hello](<./world(and)helloworld)>). ![Hello](./world(and-hello(world)). ![Hello](<./world(and-hello(world)>). ![Hello](./world(and)helloworld)). ![Hello](<./world(and)helloworld)>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-nested-parentheses.text ================================================ [Hello](./world(and)hello(world)). [Hello](<./world(and)hello(world)>). ![Hello](./world(and)hello(world)). ![Hello](<./world(and)hello(world)>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-new-line.text ================================================ [Hello](./wo rld.html). [Hello](<./wo rld.html>). ![Hello](./wo rld.png). ![Hello](<./wo rld.png>). ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-unclosed.text ================================================ [Hello]( [World](< ![Hello]( ![World](< ================================================ FILE: packages/rebber/__tests__/fixtures/links-url-white-space.text ================================================ [Hello](./wo rld.html). [Hello](<./wo rld.html>). ![Hello](./wo rld.png). ![Hello](<./wo rld.png>). ================================================ FILE: packages/rebber/__tests__/fixtures/links.output.noreference-links.text ================================================ Lorem ipsum dolor sit [amet](http://amet.com "Amet"), consectetur adipiscing elit. Praesent dictum purus ullamcorper ligula semper pellentesque. Nulla [finibus](http://finibus.com "Finibus") neque et diam rhoncus convallis. Nam dictum sapien nec sem ultrices fermentum. Nulla [facilisi](http://facilisi.com "Facilisi"). In et feugiat massa. Donec sed sodales metus, ut aliquet quam. Suspendisse nec ipsum risus. Interdum et malesuada fames ac ante ipsum primis in [faucibus](http://faucibus.com "Faucibus"). ================================================ FILE: packages/rebber/__tests__/fixtures/list-after-list.text ================================================ - item - item - item 1. item 1. item 1. item --- - item - item - item 1. item 1. item 1. item ================================================ FILE: packages/rebber/__tests__/fixtures/list-and-code.text ================================================ * This is a list item This is code ================================================ FILE: packages/rebber/__tests__/fixtures/list-continuation.text ================================================ 1. foo --- 1. foo ```js code(); ``` 1. [foo][] [foo]: http://google.com ================================================ FILE: packages/rebber/__tests__/fixtures/list-indentation.nooutput.text ================================================ - Hello 1a World 1a. - Hello 1b World 1b. - Hello 2a World 2a. - Hello 2b World 2b. - Hello 3a World 3a. - Hello 3b World 3b. - Hello 4a World 4a. - Hello 4b World 4b. - Hello 5a World 5a. - Hello 5b World 5b. ================================================ FILE: packages/rebber/__tests__/fixtures/list-item-empty-with-white-space.text ================================================ - ================================================ FILE: packages/rebber/__tests__/fixtures/list-item-empty.text ================================================ - foo - - bar - foo - - bar ================================================ FILE: packages/rebber/__tests__/fixtures/list-item-indent.list-item-indent=1.output.text ================================================ 1. foo bar baz. 99. foo bar baz. 999. foo bar baz. 1. foo bar baz. foo bar baz. 99. foo bar baz. foo bar baz. 999. foo bar baz. foo bar baz. - foo bar baz. - foo bar baz. foo bar baz. ================================================ FILE: packages/rebber/__tests__/fixtures/list-item-indent.list-item-indent=mixed.output.text ================================================ 1. foo bar baz. 99. foo bar baz. 999. foo bar baz. 1. foo bar baz. foo bar baz. 99. foo bar baz. foo bar baz. 999. foo bar baz. foo bar baz. - foo bar baz. - foo bar baz. foo bar baz. ================================================ FILE: packages/rebber/__tests__/fixtures/list-item-indent.list-item-indent=tab.output.text ================================================ 1. foo bar baz. 99. foo bar baz. 999. foo bar baz. 1. foo bar baz. foo bar baz. 99. foo bar baz. foo bar baz. 999. foo bar baz. foo bar baz. - foo bar baz. - foo bar baz. foo bar baz. ================================================ FILE: packages/rebber/__tests__/fixtures/list-item-newline.nooutput.text ================================================ - Foo - Bar ================================================ FILE: packages/rebber/__tests__/fixtures/list-item-text.text ================================================ * item1 * item2 text ================================================ FILE: packages/rebber/__tests__/fixtures/list-ordered.increment-list-marker.output.text ================================================ 2. foo; 3. bar; 4. baz. ================================================ FILE: packages/rebber/__tests__/fixtures/list-ordered.noincrement-list-marker.output.text ================================================ 2. foo; 2. bar; 2. baz. ================================================ FILE: packages/rebber/__tests__/fixtures/list.output.bullet=+.text ================================================ # List bullets + One: + Nested one; + Nested two: + Nested three. + Two; + Three. ================================================ FILE: packages/rebber/__tests__/fixtures/list.output.bullet=-.text ================================================ # List bullets - One: - Nested one; - Nested two: - Nested three. - Two; - Three. ================================================ FILE: packages/rebber/__tests__/fixtures/list.output.bullet=-asterisk-.text ================================================ # List bullets * One: * Nested one; * Nested two: * Nested three. * Two; * Three. ================================================ FILE: packages/rebber/__tests__/fixtures/lists-with-code-and-rules.text ================================================ ## foo 1. bar: > - one - two - three - four - five 1. foo: ``` line 1 line 2 ``` 1. foo: 1. foo `bar` bar: ``` erb some code here ``` 2. foo `bar` bar: ``` erb foo --- bar --- foo bar ``` 3. foo `bar` bar: ``` html --- foo foo --- bar ``` 4. foo `bar` bar: foo --- bar 5. foo ================================================ FILE: packages/rebber/__tests__/fixtures/loose-lists.text ================================================ * hello world how are * you better behavior: * hello * world how are you * today * hi * hello * world * hi * hello * world * hi * hello * world how * hi * hello * world * how are * hello * world * how are ================================================ FILE: packages/rebber/__tests__/fixtures/main.text ================================================ [test]: http://google.com/ "Google" # A heading Just a note, I've found that I can't test my markdown parser vs others. For example, both markdown.js and showdown code blocks in lists wrong. They're also completely [inconsistent][test] with regards to paragraphs in list items. A link. Not anymore. * List Item 1 * List Item 2 * New List Item 1 Hi, this is a list item. * New List Item 2 Another item Code goes here. Lots of it... * New List Item 3 The last item * List Item 3 The final item. * List Item 4 The real final item. Paragraph. > * bq Item 1 > * bq Item 2 > * New bq Item 1 > * New bq Item 2 > Text here * * * > Another blockquote! > I really need to get > more creative with > mockup text.. > markdown.js breaks here again Another Heading ------------- Hello *world*. Here is a [link](//hello). And an image ![alt](src "a title"). And an image with an empty alt attribute ![](src). Code goes here. Lots of it... ================================================ FILE: packages/rebber/__tests__/fixtures/markdown-documentation-basics.text ================================================ Markdown: Basics ================ Getting the Gist of Markdown's Formatting Syntax ------------------------------------------------ This page offers a brief overview of what it's like to use Markdown. The [syntax page] [s] provides complete, detailed documentation for every feature, but Markdown should be very easy to pick up simply by looking at a few examples of it in action. The examples on this page are written in a before/after style, showing example syntax and the HTML output produced by Markdown. It's also helpful to simply try Markdown out; the [Dingus] [d] is a web application that allows you type your own Markdown-formatted text and translate it to XHTML. **Note:** This document is itself written using Markdown; you can [see the source for it by adding '.text' to the URL] [src]. [s]: /projects/markdown/syntax "Markdown Syntax" [d]: /projects/markdown/dingus "Markdown Dingus" [src]: /projects/markdown/basics.text ## Paragraphs, Headers, Blockquotes ## A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. Markdown offers two styles of headers: *Setext* and *atx*. Setext-style headers for `

` and `

` are created by "underlining" with equal signs (`=`) and hyphens (`-`), respectively. To create an atx-style header, you put 1-6 hash marks (`#`) at the beginning of the line -- the number of hashes equals the resulting HTML header level. Blockquotes are indicated using email-style '`>`' angle brackets. Markdown: A First Level Header ==================== A Second Level Header --------------------- Now is the time for all good men to come to the aid of their country. This is just a regular paragraph. The quick brown fox jumped over the lazy dog's back. ### Header 3 > This is a blockquote. > > This is the second paragraph in the blockquote. > > ## This is an H2 in a blockquote Output:

A First Level Header

A Second Level Header

Now is the time for all good men to come to the aid of their country. This is just a regular paragraph.

The quick brown fox jumped over the lazy dog's back.

Header 3

This is a blockquote.

This is the second paragraph in the blockquote.

This is an H2 in a blockquote

### Phrase Emphasis ### Markdown uses asterisks and underscores to indicate spans of emphasis. Markdown: Some of these words *are emphasized*. Some of these words _are emphasized also_. Use two asterisks for **strong emphasis**. Or, if you prefer, __use two underscores instead__. Output:

Some of these words are emphasized. Some of these words are emphasized also.

Use two asterisks for strong emphasis. Or, if you prefer, use two underscores instead.

## Lists ## Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`, `+`, and `-`) as list markers. These three markers are interchangable; this: * Candy. * Gum. * Booze. this: + Candy. + Gum. + Booze. and this: - Candy. - Gum. - Booze. all produce the same output:
  • Candy.
  • Gum.
  • Booze.
Ordered (numbered) lists use regular numbers, followed by periods, as list markers: 1. Red 2. Green 3. Blue Output:
  1. Red
  2. Green
  3. Blue
If you put blank lines between items, you'll get `

` tags for the list item text. You can create multi-paragraph list items by indenting the paragraphs by 4 spaces or 1 tab: * A list item. With multiple paragraphs. * Another item in the list. Output:

  • A list item.

    With multiple paragraphs.

  • Another item in the list.

### Links ### Markdown supports two styles for creating links: *inline* and *reference*. With both styles, you use square brackets to delimit the text you want to turn into a link. Inline-style links use parentheses immediately after the link text. For example: This is an [example link](http://example.com/). Output:

This is an example link.

Optionally, you may include a title attribute in the parentheses: This is an [example link](http://example.com/ "With a Title"). Output:

This is an example link.

Reference-style links allow you to refer to your links by names, which you define elsewhere in your document: I get 10 times more traffic from [Google][1] than from [Yahoo][2] or [MSN][3]. [1]: http://google.com/ "Google" [2]: http://search.yahoo.com/ "Yahoo Search" [3]: http://search.msn.com/ "MSN Search" Output:

I get 10 times more traffic from Google than from Yahoo or MSN.

The title attribute is optional. Link names may contain letters, numbers and spaces, but are *not* case sensitive: I start my morning with a cup of coffee and [The New York Times][NY Times]. [ny times]: http://www.nytimes.com/ Output:

I start my morning with a cup of coffee and The New York Times.

### Images ### Image syntax is very much like link syntax. Inline (titles are optional): ![alt text](/path/to/img.jpg "Title") Reference-style: ![alt text][id] [id]: /path/to/img.jpg "Title" Both of the above examples produce the same output: alt text ### Code ### In a regular paragraph, you can create code span by wrapping text in backtick quotes. Any ampersands (`&`) and angle brackets (`<` or `>`) will automatically be translated into HTML entities. This makes it easy to use Markdown to write about HTML example code: I strongly recommend against using any `` tags. I wish SmartyPants used named entities like `—` instead of decimal-encoded entites like `—`. Output:

I strongly recommend against using any <blink> tags.

I wish SmartyPants used named entities like &mdash; instead of decimal-encoded entites like &#8212;.

To specify an entire block of pre-formatted code, indent every line of the block by 4 spaces or 1 tab. Just like with code spans, `&`, `<`, and `>` characters will be escaped automatically. Markdown: If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

For example.

Output:

If you want your page to validate under XHTML 1.0 Strict, you've got to put paragraph tags in your blockquotes:

<blockquote>
        <p>For example.</p>
    </blockquote>
    
================================================ FILE: packages/rebber/__tests__/fixtures/markdown-documentation-syntax.text ================================================ Markdown: Syntax ================ * [Overview](#overview) * [Philosophy](#philosophy) * [Inline HTML](#html) * [Automatic Escaping for Special Characters](#autoescape) * [Block Elements](#block) * [Paragraphs and Line Breaks](#p) * [Headers](#header) * [Blockquotes](#blockquote) * [Lists](#list) * [Code Blocks](#precode) * [Horizontal Rules](#hr) * [Span Elements](#span) * [Links](#link) * [Emphasis](#em) * [Code](#code) * [Images](#img) * [Miscellaneous](#misc) * [Backslash Escapes](#backslash) * [Automatic Links](#autolink) **Note:** This document is itself written using Markdown; you can [see the source for it by adding '.text' to the URL][src]. [src]: /projects/markdown/syntax.text * * *

Overview

Philosophy

Markdown is intended to be as easy-to-read and easy-to-write as is feasible. Readability, however, is emphasized above all else. A Markdown-formatted document should be publishable as-is, as plain text, without looking like it's been marked up with tags or formatting instructions. While Markdown's syntax has been influenced by several existing text-to-HTML filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4], [Grutatext] [5], and [EtText] [6] -- the single biggest source of inspiration for Markdown's syntax is the format of plain text email. [1]: http://docutils.sourceforge.net/mirror/setext.html [2]: http://www.aaronsw.com/2002/atx/ [3]: http://textism.com/tools/textile/ [4]: http://docutils.sourceforge.net/rst.html [5]: http://www.triptico.com/software/grutatxt.html [6]: http://ettext.taint.org/doc/ To this end, Markdown's syntax is comprised entirely of punctuation characters, which punctuation characters have been carefully chosen so as to look like what they mean. E.g., asterisks around a word actually look like \*emphasis\*. Markdown lists look like, well, lists. Even blockquotes look like quoted passages of text, assuming you've ever used email.

Inline HTML

Markdown's syntax is intended for one purpose: to be used as a format for *writing* for the web. Markdown is not a replacement for HTML, or even close to it. Its syntax is very small, corresponding only to a very small subset of HTML tags. The idea is *not* to create a syntax that makes it easier to insert HTML tags. In my opinion, HTML tags are already easy to insert. The idea for Markdown is to make it easy to read, write, and edit prose. HTML is a *publishing* format; Markdown is a *writing* format. Thus, Markdown's formatting syntax only addresses issues that can be conveyed in plain text. For any markup that is not covered by Markdown's syntax, you simply use HTML itself. There's no need to preface it or delimit it to indicate that you're switching from Markdown to HTML; you just use the tags. The only restrictions are that block-level HTML elements -- e.g. `
`, ``, `
`, `

`, etc. -- must be separated from surrounding content by blank lines, and the start and end tags of the block should not be indented with tabs or spaces. Markdown is smart enough not to add extra (unwanted) `

` tags around HTML block-level tags. For example, to add an HTML table to a Markdown article: This is a regular paragraph.

Foo
This is another regular paragraph. Note that Markdown formatting syntax is not processed within block-level HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an HTML block. Span-level HTML tags -- e.g. ``, ``, or `` -- can be used anywhere in a Markdown paragraph, list item, or header. If you want, you can even use HTML tags instead of Markdown formatting; e.g. if you'd prefer to use HTML `` or `` tags instead of Markdown's link or image syntax, go right ahead. Unlike block-level HTML tags, Markdown syntax *is* processed within span-level tags.

Automatic Escaping for Special Characters

In HTML, there are two characters that demand special treatment: `<` and `&`. Left angle brackets are used to start tags; ampersands are used to denote HTML entities. If you want to use them as literal characters, you must escape them as entities, e.g. `<`, and `&`. Ampersands in particular are bedeviling for web writers. If you want to write about 'AT&T', you need to write '`AT&T`'. You even need to escape ampersands within URLs. Thus, if you want to link to: http://images.google.com/images?num=30&q=larry+bird you need to encode the URL as: http://images.google.com/images?num=30&q=larry+bird in your anchor tag `href` attribute. Needless to say, this is easy to forget, and is probably the single most common source of HTML validation errors in otherwise well-marked-up web sites. Markdown allows you to use these characters naturally, taking care of all the necessary escaping for you. If you use an ampersand as part of an HTML entity, it remains unchanged; otherwise it will be translated into `&`. So, if you want to include a copyright symbol in your article, you can write: © and Markdown will leave it alone. But if you write: AT&T Markdown will translate it to: AT&T Similarly, because Markdown supports [inline HTML](#html), if you use angle brackets as delimiters for HTML tags, Markdown will treat them as such. But if you write: 4 < 5 Markdown will translate it to: 4 < 5 However, inside Markdown code spans and blocks, angle brackets and ampersands are *always* encoded automatically. This makes it easy to use Markdown to write about HTML code. (As opposed to raw HTML, which is a terrible format for writing about HTML syntax, because every single `<` and `&` in your example code needs to be escaped.) * * *

Block Elements

Paragraphs and Line Breaks

A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be intended with spaces or tabs. The implication of the "one or more consecutive lines of text" rule is that Markdown supports "hard-wrapped" text paragraphs. This differs significantly from most other text-to-HTML formatters (including Movable Type's "Convert Line Breaks" option) which translate every line break character in a paragraph into a `
` tag. When you *do* want to insert a `
` break tag using Markdown, you end a line with two or more spaces, then type return. Yes, this takes a tad more effort to create a `
`, but a simplistic "every line break is a `
`" rule wouldn't work for Markdown. Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l] work best -- and look better -- when you format them with hard breaks. [bq]: #blockquote [l]: #list Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. Setext-style headers are "underlined" using equal signs (for first-level headers) and dashes (for second-level headers). For example: This is an H1 ============= This is an H2 ------------- Any number of underlining `=`'s or `-`'s will work. Atx-style headers use 1-6 hash characters at the start of the line, corresponding to header levels 1-6. For example: # This is an H1 ## This is an H2 ###### This is an H6 Optionally, you may "close" atx-style headers. This is purely cosmetic -- you can use this if you think it looks better. The closing hashes don't even need to match the number of hashes used to open the header. (The number of opening hashes determines the header level.) : # This is an H1 # ## This is an H2 ## ### This is an H3 ######

Blockquotes

Markdown uses email-style `>` characters for blockquoting. If you're familiar with quoting passages of text in an email message, then you know how to create a blockquote in Markdown. It looks best if you hard wrap the text and put a `>` before every line: > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse > id sem consectetuer libero luctus adipiscing. Markdown allows you to be lazy and only put the `>` before the first line of a hard-wrapped paragraph: > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by adding additional levels of `>`: > This is the first level of quoting. > > > This is nested blockquote. > > Back to the first level. Blockquotes can contain other Markdown elements, including headers, lists, and code blocks: > ## This is a header. > > 1. This is the first list item. > 2. This is the second list item. > > Here's some example code: > > return shell_exec("echo $input | $markdown_script"); Any decent text editor should make email-style quoting easy. For example, with BBEdit, you can make a selection and choose Increase Quote Level from the Text menu.

Lists

Markdown supports ordered (numbered) and unordered (bulleted) lists. Unordered lists use asterisks, pluses, and hyphens -- interchangably -- as list markers: * Red * Green * Blue is equivalent to: + Red + Green + Blue and: - Red - Green - Blue Ordered lists use numbers followed by periods: 1. Bird 2. McHale 3. Parish It's important to note that the actual numbers you use to mark the list have no effect on the HTML output Markdown produces. The HTML Markdown produces from the above list is:
  1. Bird
  2. McHale
  3. Parish
If you instead wrote the list in Markdown like this: 1. Bird 1. McHale 1. Parish or even: 3. Bird 1. McHale 8. Parish you'd get the exact same HTML output. The point is, if you want to, you can use ordinal numbers in your ordered Markdown lists, so that the numbers in your source match the numbers in your published HTML. But if you want to be lazy, you don't have to. If you do use lazy list numbering, however, you should still start the list with the number 1. At some point in the future, Markdown may support starting ordered lists at an arbitrary number. List markers typically start at the left margin, but may be indented by up to three spaces. List markers must be followed by one or more spaces or a tab. To make lists look nice, you can wrap items with hanging indents: * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. But if you want to be lazy, you don't have to: * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse id sem consectetuer libero luctus adipiscing. If list items are separated by blank lines, Markdown will wrap the items in `

` tags in the HTML output. For example, this input: * Bird * Magic will turn into:

  • Bird
  • Magic
But this: * Bird * Magic will turn into:
  • Bird

  • Magic

List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be intended by either 4 spaces or one tab: 1. This is a list item with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 2. Suspendisse id sem consectetuer libero luctus adipiscing. It looks nice if you indent every line of the subsequent paragraphs, but here again, Markdown will allow you to be lazy: * This is a list item with two paragraphs. This is the second paragraph in the list item. You're only required to indent the first line. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. * Another item in the same list. To put a blockquote within a list item, the blockquote's `>` delimiters need to be indented: * A list item with a blockquote: > This is a blockquote > inside a list item. To put a code block within a list item, the code block needs to be indented *twice* -- 8 spaces or two tabs: * A list item with a code block: It's worth noting that it's possible to trigger an ordered list by accident, by writing something like this: 1986. What a great season. In other words, a *number-period-space* sequence at the beginning of a line. To avoid this, you can backslash-escape the period: 1986\. What a great season.

Code Blocks

Pre-formatted code blocks are used for writing about programming or markup source code. Rather than forming normal paragraphs, the lines of a code block are interpreted literally. Markdown wraps a code block in both `
` and `` tags.

To produce a code block in Markdown, simply indent every line of the
block by at least 4 spaces or 1 tab. For example, given this input:

    This is a normal paragraph:

        This is a code block.

Markdown will generate:

    

This is a normal paragraph:

This is a code block.
    
One level of indentation -- 4 spaces or 1 tab -- is removed from each line of the code block. For example, this: Here is an example of AppleScript: tell application "Foo" beep end tell will turn into:

Here is an example of AppleScript:

tell application "Foo"
        beep
    end tell
    
A code block continues until it reaches a line that is not indented (or the end of the article). Within a code block, ampersands (`&`) and angle brackets (`<` and `>`) are automatically converted into HTML entities. This makes it very easy to include example HTML source code using Markdown -- just paste it and indent it, and Markdown will handle the hassle of encoding the ampersands and angle brackets. For example, this: will turn into:
<div class="footer">
        &copy; 2004 Foo Corporation
    </div>
    
Regular Markdown syntax is not processed within code blocks. E.g., asterisks are just literal asterisks within a code block. This means it's also easy to use Markdown to write about Markdown's own syntax.

Horizontal Rules

You can produce a horizontal rule tag (`
`) by placing three or more hyphens, asterisks, or underscores on a line by themselves. If you wish, you may use spaces between the hyphens or asterisks. Each of the following lines will produce a horizontal rule: * * * *** ***** - - - --------------------------------------- _ _ _ * * *

Span Elements

Markdown supports two style of links: *inline* and *reference*. In both styles, the link text is delimited by [square brackets]. To create an inline link, use a set of regular parentheses immediately after the link text's closing square bracket. Inside the parentheses, put the URL where you want the link to point, along with an *optional* title for the link, surrounded in quotes. For example: This is [an example](http://example.com/ "Title") inline link. [This link](http://example.net/) has no title attribute. Will produce:

This is an example inline link.

This link has no title attribute.

If you're referring to a local resource on the same server, you can use relative paths: See my [About](/about/) page for details. Reference-style links use a second set of square brackets, inside which you place a label of your choosing to identify the link: This is [an example][id] reference-style link. You can optionally use a space to separate the sets of brackets: This is [an example] [id] reference-style link. Then, anywhere in the document, you define your link label like this, on a line by itself: [id]: http://example.com/ "Optional Title Here" That is: * Square brackets containing the link identifier (optionally indented from the left margin using up to three spaces); * followed by a colon; * followed by one or more spaces (or tabs); * followed by the URL for the link; * optionally followed by a title attribute for the link, enclosed in double or single quotes. The link URL may, optionally, be surrounded by angle brackets: [id]: "Optional Title Here" You can put the title attribute on the next line and use extra spaces or tabs for padding, which tends to look better with longer URLs: [id]: http://example.com/longish/path/to/resource/here "Optional Title Here" Link definitions are only used for creating links during Markdown processing, and are stripped from your document in the HTML output. Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are *not* case sensitive. E.g. these two links: [link text][a] [link text][A] are equivalent. The *implicit link name* shortcut allows you to omit the name of the link, in which case the link text itself is used as the name. Just use an empty set of square brackets -- e.g., to link the word "Google" to the google.com web site, you could simply write: [Google][] And then define the link: [Google]: http://google.com/ Because link names may contain spaces, this shortcut even works for multiple words in the link text: Visit [Daring Fireball][] for more information. And then define the link: [Daring Fireball]: http://daringfireball.net/ Link definitions can be placed anywhere in your Markdown document. I tend to put them immediately after each paragraph in which they're used, but if you want, you can put them all at the end of your document, sort of like footnotes. Here's an example of reference links in action: I get 10 times more traffic from [Google] [1] than from [Yahoo] [2] or [MSN] [3]. [1]: http://google.com/ "Google" [2]: http://search.yahoo.com/ "Yahoo Search" [3]: http://search.msn.com/ "MSN Search" Using the implicit link name shortcut, you could instead write: I get 10 times more traffic from [Google][] than from [Yahoo][] or [MSN][]. [google]: http://google.com/ "Google" [yahoo]: http://search.yahoo.com/ "Yahoo Search" [msn]: http://search.msn.com/ "MSN Search" Both of the above examples will produce the following HTML output:

I get 10 times more traffic from Google than from Yahoo or MSN.

For comparison, here is the same paragraph written using Markdown's inline link style: I get 10 times more traffic from [Google](http://google.com/ "Google") than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or [MSN](http://search.msn.com/ "MSN Search"). The point of reference-style links is not that they're easier to write. The point is that with reference-style links, your document source is vastly more readable. Compare the above examples: using reference-style links, the paragraph itself is only 81 characters long; with inline-style links, it's 176 characters; and as raw HTML, it's 234 characters. In the raw HTML, there's more markup than there is text. With Markdown's reference-style links, a source document much more closely resembles the final output, as rendered in a browser. By allowing you to move the markup-related metadata out of the paragraph, you can add links without interrupting the narrative flow of your prose.

Emphasis

Markdown treats asterisks (`*`) and underscores (`_`) as indicators of emphasis. Text wrapped with one `*` or `_` will be wrapped with an HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML `` tag. E.g., this input: *single asterisks* _single underscores_ **double asterisks** __double underscores__ will produce: single asterisks single underscores double asterisks double underscores You can use whichever style you prefer; the lone restriction is that the same character must be used to open and close an emphasis span. Emphasis can be used in the middle of a word: un*fucking*believable But if you surround an `*` or `_` with spaces, it'll be treated as a literal asterisk or underscore. To produce a literal asterisk or underscore at a position where it would otherwise be used as an emphasis delimiter, you can backslash escape it: \*this text is surrounded by literal asterisks\*

Code

To indicate a span of code, wrap it with backtick quotes (`` ` ``). Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example: Use the `printf()` function. will produce:

Use the printf() function.

To include a literal backtick character within a code span, you can use multiple backticks as the opening and closing delimiters: ``There is a literal backtick (`) here.`` which will produce this:

There is a literal backtick (`) here.

The backtick delimiters surrounding a code span may include spaces -- one after the opening, one before the closing. This allows you to place literal backtick characters at the beginning or end of a code span: A single backtick in a code span: `` ` `` A backtick-delimited string in a code span: `` `foo` `` will produce:

A single backtick in a code span: `

A backtick-delimited string in a code span: `foo`

With a code span, ampersands and angle brackets are encoded as HTML entities automatically, which makes it easy to include example HTML tags. Markdown will turn this: Please don't use any `` tags. into:

Please don't use any <blink> tags.

You can write this: `—` is the decimal-encoded equivalent of `—`. to produce:

&#8212; is the decimal-encoded equivalent of &mdash;.

Images

Admittedly, it's fairly difficult to devise a "natural" syntax for placing images into a plain text document format. Markdown uses an image syntax that is intended to resemble the syntax for links, allowing for two styles: *inline* and *reference*. Inline image syntax looks like this: ![Alt text](/path/to/img.jpg) ![Alt text](/path/to/img.jpg "Optional title") That is: * An exclamation mark: `!`; * followed by a set of square brackets, containing the `alt` attribute text for the image; * followed by a set of parentheses, containing the URL or path to the image, and an optional `title` attribute enclosed in double or single quotes. Reference-style image syntax looks like this: ![Alt text][id] Where "id" is the name of a defined image reference. Image references are defined using syntax identical to link references: [id]: url/to/image "Optional title attribute" As of this writing, Markdown has no syntax for specifying the dimensions of an image; if this is important to you, you can simply use regular HTML `` tags. * * *

Miscellaneous

Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: Markdown will turn this into: http://example.com/ Automatic links for email addresses work similarly, except that Markdown will also perform a bit of randomized decimal and hex entity-encoding to help obscure your address from address-harvesting spambots. For example, Markdown will turn this: into something like this: address@exa mple.com which will render in a browser as a clickable link to "address@example.com". (This sort of entity-encoding trick will indeed fool many, if not most, address-harvesting bots, but it definitely won't fool all of them. It's better than nothing, but an address published in this way will probably eventually start receiving spam.)

Backslash Escapes

Markdown allows you to use backslash escapes to generate literal characters which would otherwise have special meaning in Markdown's formatting syntax. For example, if you wanted to surround a word with literal asterisks (instead of an HTML `` tag), you can backslashes before the asterisks, like this: \*literal asterisks\* Markdown provides backslash escapes for the following characters: \ backslash ` backtick * asterisk _ underscore {} curly braces [] square brackets () parentheses # hash mark + plus sign - minus sign (hyphen) . dot ! exclamation mark ================================================ FILE: packages/rebber/__tests__/fixtures/mixed-indentation.text ================================================ # Mixed spaces and tabs - Very long paragraph 1. Very long paragraph - Very long paragraph 1. Very long paragraph ================================================ FILE: packages/rebber/__tests__/fixtures/nested-blockquotes.text ================================================ > foo > > > bar > > foo ================================================ FILE: packages/rebber/__tests__/fixtures/nested-code.text ================================================ ````` hi ther `` ok ``` ````` `` `hi ther` `` ================================================ FILE: packages/rebber/__tests__/fixtures/nested-em.nooutput.text ================================================ *test **test** test* _test __test__ test_ ================================================ FILE: packages/rebber/__tests__/fixtures/nested-references.text ================================================ This nested image should work: [![Foo][bar]][baz] This nested link should not work: [[Foo][bar]][baz] ================================================ FILE: packages/rebber/__tests__/fixtures/nested-square-link.text ================================================ [the `]` character](/url) [the `` [ `` character](/url) [the `` [ ``` character](/url) [the `` ` `` character](/url) ================================================ FILE: packages/rebber/__tests__/fixtures/no-positionals.nooutput.text ================================================ This document tests for the working of `position: false` as a parse option. > Block-quotes > > * With list items. Another block-quote: > 1. And another list. Some [deeply **nested _elements_**](http://example.com) An entity: ©, and an warning entity: ©. ================================================ FILE: packages/rebber/__tests__/fixtures/not-a-link.text ================================================ \[test](not a link) ================================================ FILE: packages/rebber/__tests__/fixtures/ordered-and-unordered-lists.text ================================================ ## Unordered Asterisks tight: * asterisk 1 * asterisk 2 * asterisk 3 Asterisks loose: * asterisk 1 * asterisk 2 * asterisk 3 * * * Pluses tight: + Plus 1 + Plus 2 + Plus 3 Pluses loose: + Plus 1 + Plus 2 + Plus 3 * * * Minuses tight: - Minus 1 - Minus 2 - Minus 3 Minuses loose: - Minus 1 - Minus 2 - Minus 3 ## Ordered Tight: 1. First 2. Second 3. Third and: 1. One 2. Two 3. Three Loose using tabs: 1. First 2. Second 3. Third and using spaces: 1. One 2. Two 3. Three Multiple paragraphs: 1. Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. 2. Item 2. 3. Item 3. ## Nested * Tab * Tab * Tab Here's another: 1. First 2. Second: * Fee * Fie * Foe 3. Third Same thing but with paragraphs: 1. First 2. Second: * Fee * Fie * Foe 3. Third This was an error in Markdown 1.0.1: * this * sub that ================================================ FILE: packages/rebber/__tests__/fixtures/ordered-different-types.text ================================================ 1. foo 2. bar 3) baz ================================================ FILE: packages/rebber/__tests__/fixtures/ordered-with-parentheses.text ================================================ ## Ordered Tight: 1) First 2) Second 3) Third and: 1) One 2) Two 3) Three Loose using tabs: 1) First 2) Second 3) Third and using spaces: 1) One 2) Two 3) Three Multiple paragraphs: 1) Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. 2) Item 2. 3) Item 3. ================================================ FILE: packages/rebber/__tests__/fixtures/paragraphs-and-indentation.text ================================================ # Without lines. This is a paragraph and this is further text This is a paragraph and this is further text This is a paragraph with some asterisks *** This is a paragraph followed by a horizontal rule *** # With lines. This is a paragraph and this is code This is a paragraph and this is a new paragraph This is a paragraph with some asterisks in a code block *** This is a paragraph followed by a horizontal rule *** ================================================ FILE: packages/rebber/__tests__/fixtures/paragraphs-empty.text ================================================ aaa # aaa bbb ccc ================================================ FILE: packages/rebber/__tests__/fixtures/ref-paren.text ================================================ [hi] [hi]: /url (there) ================================================ FILE: packages/rebber/__tests__/fixtures/reference-image-empty-alt.text ================================================ ![][1] [1]: /xyz.png ================================================ FILE: packages/rebber/__tests__/fixtures/reference-link-escape.nooutput.text ================================================ [b\*r*][b\-r], [b\*r*][], [b\*r*]. ![foo][b\*r*], ![b\*r*][], ![b\*r*]. [b\*r*]: http://google.com ================================================ FILE: packages/rebber/__tests__/fixtures/reference-link-not-closed.text ================================================ [bar][bar [bar][ [bar] ================================================ FILE: packages/rebber/__tests__/fixtures/reference-link-with-angle-brackets.text ================================================ [foo] [foo]: <./url with spaces> ================================================ FILE: packages/rebber/__tests__/fixtures/reference-link-with-multiple-definitions.text ================================================ [foo] [foo]: first [foo]: second ================================================ FILE: packages/rebber/__tests__/fixtures/same-bullet.text ================================================ * test + test - test ================================================ FILE: packages/rebber/__tests__/fixtures/stringify-escape.output.commonmark.text ================================================ Characters that should be escaped in general: \\ \` \* \[ Characters that shouldn't: {}]()#+-.!>"$%',/:;=?@^~ Underscores are \_escaped\_ unless they appear in_the_middle_of_a_word. Ampersands are escaped only when they would otherwise start an entity: - \©cat \& \& - But: ©cat; `≬` &foo; & AT&T &c Open parenthesis should be escaped after a shortcut reference: [ref]\(text) And after a shortcut reference and a space (for GitHub): [ref] \(text) Hyphen should be escaped at the beginning of a line: \- not a list item \- not a list item \+ not a list item Same for angle brackets: \> not a block quote And hash signs: \# not a heading \## not a subheading Text under a shortcut reference should be preserved verbatim: - [two*three] - [two\*three] - [a\a] - [a\\a] - [a\\\a] - [a_a\_a] **GFM:** Colon should be escaped in URLs: - http\://user:password@host:port/path?key=value#fragment - https\://user:password@host:port/path?key=value#fragment Double tildes should be \~~escaped\~~. And here: foo\~~. Pipes should not be escaped here: | | here | they | | ------ | -------- | | should | tho\|ugh | And here: | here | they | \| ---- \| ----- \| | should | though | And here: here | they \---- \| ------ should | though **Commonmark:** Open angle bracket should be escaped: - \
\
- \ ================================================ FILE: packages/rebber/__tests__/fixtures/stringify-escape.output.nogfm.commonmark.text ================================================ Characters that should be escaped in general: \\ \` \* \[ Characters that shouldn't: {}]()#+-.!>"$%',/:;=?@^~ Underscores are \_escaped\_ unless they appear in_the_middle_of_a_word. Ampersands are escaped only when they would otherwise start an entity: - \©cat \& \& - But: ©cat; `≬` &foo; & AT&T &c Open parenthesis should be escaped after a shortcut reference: [ref]\(text) And after a shortcut reference and a space (for GitHub): [ref] \(text) Hyphen should be escaped at the beginning of a line: \- not a list item \- not a list item \+ not a list item Same for angle brackets: \> not a block quote And hash signs: \# not a heading \## not a subheading Text under a shortcut reference should be preserved verbatim: - [two*three] - [two\*three] - [a\a] - [a\\a] - [a\\\a] - [a_a\_a] **GFM:** Colon should not be escaped in URLs: - http://user:password@host:port/path?key=value#fragment - https://user:password@host:port/path?key=value#fragment Double tildes should not be ~~escaped~~. Nor here: foo~~. Pipes should not be escaped here: | | here | they | | ------ | -------- | | should | nei|ther | Nor here: | here | they | | ------ | ------ | | should | though | Nor here: here | they \----- | ------ should | though **Commonmark:** Open angle bracket should be escaped: - \
\
- \ ================================================ FILE: packages/rebber/__tests__/fixtures/stringify-escape.output.nogfm.text ================================================ Characters that should be escaped in general: \\ \` \* \[ Characters that shouldn't: {}]()#+-.!>"$%',/:;=?@^~ Underscores are \_escaped\_ unless they appear in_the_middle_of_a_word. Ampersands are escaped only when they would otherwise start an entity: - &copycat &amp; &#x26 - But: ©cat; `≬` &foo; & AT&T &c Open parenthesis should be escaped after a shortcut reference: [ref]\(text) And after a shortcut reference and a space (for GitHub): [ref] \(text) Hyphen should be escaped at the beginning of a line: \- not a list item \- not a list item \+ not a list item Same for angle brackets: \> not a block quote And hash signs: \# not a heading \## not a subheading Text under a shortcut reference should be preserved verbatim: - [two*three] - [two\*three] - [a\a] - [a\\a] - [a\\\a] - [a_a\_a] **GFM:** Colon should not be escaped in URLs: - http://user:password@host:port/path?key=value#fragment - https://user:password@host:port/path?key=value#fragment Double tildes should not be ~~escaped~~. Nor here: foo~~. Pipes should not be escaped here: | | here | they | | ------ | -------- | | should | nei|ther | Nor here: | here | they | | ------ | ------ | | should | though | Nor here: here | they \----- | ------ should | though **Commonmark:** Open angle bracket should be escaped: - <div></div> - <http:google.com> ================================================ FILE: packages/rebber/__tests__/fixtures/stringify-escape.output.noposition.pedantic.text ================================================ Characters that should be escaped in general: \\ \` \* \[ \_ Characters that shouldn't: {}]()#+-.!>"$%',/:;=?@^~ Underscores are always \_escaped\_, even when they appear in\_the\_middle\_of\_a\_word. Ampersands are escaped only when they would otherwise start an entity: - &copycat &amp; &#x26 - But: ©cat; `≬` &foo; & AT&T &c Open parenthesis should be escaped after a shortcut reference: [ref]\(text) And after a shortcut reference and a space (for GitHub): [ref] \(text) Hyphen should be escaped at the beginning of a line: \- not a list item \- not a list item \+ not a list item Same for angle brackets: \> not a block quote And hash signs: \# not a heading \## not a subheading Text under a shortcut reference should be preserved verbatim: - [two*three] - [two\*three] - [a\a] - [a\\a] - [a\\\a] - [a_a\_a] **GFM:** Colon should be escaped in URLs: - http://user:password@host:port/path?key=value#fragment - https://user:password@host:port/path?key=value#fragment Double tildes should be \~~escaped\~~. And here: foo\~~. Pipes should not be escaped here: | | here | they | | ------ | -------- | | should | tho\|ugh | And here: | here | they | \| ---- \| ----- \| | should | though | And here: here | they \---- \| ------ should | though **Commonmark:** Open angle bracket should be escaped: - <div></div> - <http:google.com> ================================================ FILE: packages/rebber/__tests__/fixtures/stringify-escape.output.pedantic.text ================================================ Characters that should be escaped in general: \\ \` \* \[ \_ Characters that shouldn't: {}]()#+-.!>"$%',/:;=?@^~ Underscores are always \_escaped\_, even when they appear in\_the\_middle\_of\_a\_word. Ampersands are escaped only when they would otherwise start an entity: - &copycat &amp; &#x26 - But: ©cat; `≬` &foo; & AT&T &c Open parenthesis should be escaped after a shortcut reference: [ref]\(text) And after a shortcut reference and a space (for GitHub): [ref] \(text) Hyphen should be escaped at the beginning of a line: \- not a list item \- not a list item \+ not a list item Same for angle brackets: \> not a block quote And hash signs: \# not a heading \## not a subheading Text under a shortcut reference should be preserved verbatim: - [two*three] - [two\*three] - [a\a] - [a\\a] - [a\\\a] - [a_a\_a] **GFM:** Colon should be escaped in URLs: - http://user:password@host:port/path?key=value#fragment - https://user:password@host:port/path?key=value#fragment Double tildes should be \~~escaped\~~. And here: foo\~~. Pipes should not be escaped here: | | here | they | | ------ | -------- | | should | tho\|ugh | And here: | here | they | \| ---- \| ----- \| | should | though | And here: here | they \---- \| ------ should | though **Commonmark:** Open angle bracket should be escaped: - <div></div> - <http:google.com> ================================================ FILE: packages/rebber/__tests__/fixtures/stringify-escape.output.text ================================================ Characters that should be escaped in general: \\ \` \* \[ Characters that shouldn't: {}]()#+-.!>"$%',/:;=?@^~ Underscores are \_escaped\_ unless they appear in_the_middle_of_a_word. Ampersands are escaped only when they would otherwise start an entity: - &copycat &amp; &#x26 - But: ©cat; `≬` &foo; & AT&T &c Open parenthesis should be escaped after a shortcut reference: [ref]\(text) And after a shortcut reference and a space (for GitHub): [ref] \(text) Hyphen should be escaped at the beginning of a line: \- not a list item \- not a list item \+ not a list item Same for angle brackets: \> not a block quote And hash signs: \# not a heading \## not a subheading Text under a shortcut reference should be preserved verbatim: - [two*three] - [two\*three] - [a\a] - [a\\a] - [a\\\a] - [a_a\_a] **GFM:** Colon should be escaped in URLs: - http://user:password@host:port/path?key=value#fragment - https://user:password@host:port/path?key=value#fragment Double tildes should be \~~escaped\~~. And here: foo\~~. Pipes should not be escaped here: | | here | they | | ------ | -------- | | should | tho\|ugh | And here: | here | they | \| ---- \| ----- \| | should | though | And here: here | they \---- \| ------ should | though **Commonmark:** Open angle bracket should be escaped: - <div></div> - <http:google.com> ================================================ FILE: packages/rebber/__tests__/fixtures/stringify-escape.text ================================================ Characters that should be escaped in general: \\ \` \* \[ Characters that shouldn't: {}]()#+-.!>"$%',/:;=?@^~ Underscores are \_escaped\_ unless they appear in_the_middle_of_a_word. or **_here**, or here__ Ampersands are escaped only when they would otherwise start an entity: - \©cat \& \& - &copycat &amp; &#x26 - But: ©cat; `≬` &foo; & AT&T &c Open parenthesis should be escaped after a shortcut reference: [ref]\(text) And after a shortcut reference and a space (for GitHub): [ref] \(text) Hyphen should be escaped at the beginning of a line: \- not a list item \- not a list item \+ not a list item Same for angle brackets: \> not a block quote And hash signs: \# not a heading \## not a subheading Text under a shortcut reference should be preserved verbatim: - [two*three] - [two\*three] - [a\a] - [a\\a] - [a\\\a] - [a_a\_a] **GFM:** Colon should be escaped in URLs: - http\://user:password@host:port/path?key=value#fragment - https\://user:password@host:port/path?key=value#fragment - http://user:password@host:port/path?key=value#fragment - https://user:password@host:port/path?key=value#fragment Double tildes should be \~~escaped\~~. And here: foo~~. Pipes should not be escaped here: | | here | they | | ------ | -------- | | should | tho\|ugh | And here: | here | they | \| ---- \| ----- \| | should | though | And here: here | they \---- \| ------ should | though **Commonmark:** Open angle bracket should be escaped: - \
\
- \ - <div></div> - <http:google.com> ================================================ FILE: packages/rebber/__tests__/fixtures/strong-and-em-together-one.text ================================================ ***This is strong and em.*** So is ***this*** word. ___This is strong and em.___ So is ___this___ word. ================================================ FILE: packages/rebber/__tests__/fixtures/strong-and-em-together-two.nooutput.text ================================================ perform_complicated_task do_this_and_do_that_and_another_thing perform*complicated*task do*this*and*do*that*and*another*thing ================================================ FILE: packages/rebber/__tests__/fixtures/strong-emphasis.text ================================================ Foo **bar** __baz__. Foo __bar__ **baz**. ================================================ FILE: packages/rebber/__tests__/fixtures/strong-initial-white-space.text ================================================ ** bar **. __ bar __. ================================================ FILE: packages/rebber/__tests__/fixtures/table-empty-initial-cell.text ================================================ | | a|c| |--|:----:|:---| |a|b|c| |a|b|c| ================================================ FILE: packages/rebber/__tests__/fixtures/table-escaped-pipes.nooutput.text ================================================ | First | Second | third | | ----- | ------ | ----- | | first | second | third | | first | second \| second | third \| | first | second \\| third \\| | first | second \\\| second | third \\\| ================================================ FILE: packages/rebber/__tests__/fixtures/table-in-list.text ================================================ * Unordered: | A | B | | - | - | | 1 | 2 | * Ordered: | A | B | | - | - | | 1 | 2 | ================================================ FILE: packages/rebber/__tests__/fixtures/table-invalid-alignment.text ================================================ Missing alignment characters: | a | b | c | | |---|---| | d | e | f | * * * | a | b | c | |---|---| | | d | e | f | Invalid characters: | a | b | c | |---|-*-|---| | d | e | f | ================================================ FILE: packages/rebber/__tests__/fixtures/table-loose.output.loose-table.text ================================================ Header 1 | Header 2 -------- | -------- Cell 1 | Cell 2 Cell 3 | Cell 4 ================================================ FILE: packages/rebber/__tests__/fixtures/table-loose.output.text ================================================ | Header 1 | Header 2 | | -------- | -------- | | Cell 1 | Cell 2 | | Cell 3 | Cell 4 | ================================================ FILE: packages/rebber/__tests__/fixtures/table-no-body.text ================================================ # Foo | Name | GitHub | Twitter | | ---- | ------ | ------- | ================================================ FILE: packages/rebber/__tests__/fixtures/table-no-end-of-line.text ================================================ |foo|bar| |-|-| |1|2| ================================================ FILE: packages/rebber/__tests__/fixtures/table-one-column.text ================================================ This is a table: | a | | - | | b | ================================================ FILE: packages/rebber/__tests__/fixtures/table-one-row.text ================================================ This is a table: | a | b | c | | - | - | - | ================================================ FILE: packages/rebber/__tests__/fixtures/table-padded.output.nopadded-table.text ================================================ | Header 1 | Header 2 | | -------- | -------- | | Cell 1 | Cell 2 | | Cell 3 | Cell 4 | ================================================ FILE: packages/rebber/__tests__/fixtures/table-padded.output.text ================================================ | Header 1 | Header 2 | | -------- | -------- | | Cell 1 | Cell 2 | | Cell 3 | Cell 4 | ================================================ FILE: packages/rebber/__tests__/fixtures/table-pipes-in-code.text ================================================ | abc | head2 | | --- | ------: | | x | `|||` | | x | ` | | x | `|` | | x | `` f `` | | x | ```` | | x | ``f` | | abc | head2 | --- | ------: | x | ` | x | `|` | x | `` f `` | x | ```` | x | ``f` ================================================ FILE: packages/rebber/__tests__/fixtures/table-spaced.output.nospaced-table.text ================================================ |Header 1|Header 2| |--------|-------:| |Cell 1 | Cell 2| |Cell 3 | Cell 4| ================================================ FILE: packages/rebber/__tests__/fixtures/table-spaced.output.text ================================================ | Header 1 | Header 2 | | -------- | -------: | | Cell 1 | Cell 2 | | Cell 3 | Cell 4 | ================================================ FILE: packages/rebber/__tests__/fixtures/table-with-image.text ================================================ Someone wanted to do this, let's implement it! c1 | c2 ---|-- c3 | ![image](https://zestedesavoir.com/media/galleries/426/56dc4a1e-416b-4a9d-830d-95b45d58a17a.png) ================================================ FILE: packages/rebber/__tests__/fixtures/table.text ================================================ | Heading 1 | **H**eading 2 | --------- | --------- | Cell 1 | Cell 2 | Cell 3 | Cell 4 | Header 1 | Header 2 | Header 3 | Header 4 | | :------: | -------: | :------- | -------- | | Cell 1 | Cell 2 | Cell 3 | Cell 4 | | Cell 5 | Cell 6 | Cell 7 | Cell 8 | Test code Header 1 | Header 2 -------- | -------- Cell 1 | Cell 2 Cell 3 | Cell 4 Header 1|Header 2|Header 3|Header 4 :-------|:------:|-------:|-------- Cell 1 |Cell 2 |Cell 3 |Cell 4 *Cell 5*|Cell 6 |Cell 7 |Cell 8 ================================================ FILE: packages/rebber/__tests__/fixtures/tabs-and-spaces.text ================================================ + this is a list item indented with tabs + this is a list item indented with spaces Code: this code block is indented by one tab And: this code block is indented by two tabs And: + this is an example list item indented with tabs + this is an example list item indented with spaces ================================================ FILE: packages/rebber/__tests__/fixtures/tabs.text ================================================ + this is a list item indented with tabs + this is a list item indented with spaces Code: this code block is indented by one tab And: this code block is indented by two tabs And: + this is an example list item indented with tabs + this is an example list item indented with spaces ================================================ FILE: packages/rebber/__tests__/fixtures/task-list-ordered.text ================================================ 1. [ ] Mercury; 2. [] Venus (this one’s invalid); 3. [x] Earth: 1. [x] Moon. 4. [ ] Mars; 8. [] Neptune (this one’s also invalid). ================================================ FILE: packages/rebber/__tests__/fixtures/task-list-unordered-asterisk.text ================================================ * [ ] Mercury; * [] Venus (this one’s invalid); * [x] Earth: * [x] Moon. * [ ] Mars; * [] Neptune (this one’s also invalid). ================================================ FILE: packages/rebber/__tests__/fixtures/task-list-unordered-dash.text ================================================ - [ ] Mercury; - [] Venus (this one’s invalid); - [x] Earth: - [x] Moon. - [ ] Mars; - [] Neptune (this one’s also invalid). ================================================ FILE: packages/rebber/__tests__/fixtures/task-list-unordered-plus.text ================================================ + [ ] Mercury; + [] Venus (this one’s invalid); + [x] Earth: + [x] Moon. + [ ] Mars; + [] Neptune (this one’s also invalid). ================================================ FILE: packages/rebber/__tests__/fixtures/task-list.text ================================================ Empty items =========== * [ ] * [ ] 1. [x] 2. [X] Single space ============ * [ ] * [ ] 1. [x] 2. [X] Tab === * [ ] * [ ] 1. [x] 2. [X] No white space with content =========================== * [ ]Hello; * [ ]World; 1. [x]Foo. 2. [X]Bar Single space with content ========================= * [ ] Hello; * [ ] World; 1. [x] Foo. 2. [X] World :D Single tab with content ======================= * [ ] Hello; * [ ] World; 1. [x] Foo. 2. [X] Hello. Multiple spaces with content ============================ * [ ] Hello; * [ ] World; 1. [x] Foo. 3. [X] Bar. Multiple tabs with content ========================== * [ ] Hello; * [ ] World; 1. [x] Foo. 2. [X] Bar. Mixed tabs and spaces ===================== * [ ] Hello; 1. [x] World; * [ ] Hello; * [ ] World. 2. [X] Bar. Line breaks =========== * [ ] Hello; 1. [ ] Hello; Multiple unfinished characters ============================== * [ ] Hello; 1. [ ] World; 2. [ ] Hello; 3. [ ] World. ================================================ FILE: packages/rebber/__tests__/fixtures/tidyness.text ================================================ > A list within a blockquote: > > * asterisk 1 > * asterisk 2 > * asterisk 3 ================================================ FILE: packages/rebber/__tests__/fixtures/title-attributes.text ================================================ # Links | Implementation | Characters | Nested | Mismatched | Escaped | Named Entities | Numbered Entities | | -------------- | ---------- |------- | ---------- | ------- | -------------- | ----------------- | | Markdown.pl | `"` | Yes | Yes | No | Yes | Yes | | GitHub | `"` | Yes | Yes | No | No | No | | CommonMark | `"` | No | No | Yes | Yes | Yes | | Markdown.pl | `'` | Yes | Yes | No | Yes | Yes | | GitHub | `'` | Yes | Yes | No | No | No | | CommonMark | `'` | No | No | Yes | Yes | Yes | | Markdown.pl | `()` | - | - | - | - | - | | GitHub | `()` | - | - | - | - | - | | CommonMark | `()` | No | Yes | Yes | Yes | Yes | ## Double quotes [Hello](./world.html "and text") [Hello](./world.html "and "matching delimiters"") [Hello](./world.html "and "mismatched delimiters") [Hello](./world.html "and \"escapes\"") [Hello](./world.html "and "named entities"") [Hello](./world.html "and "numbered entities"") ## Single quotes [Hello](./world.html 'and text') [Hello](./world.html 'and 'matching delimiters'') [Hello](./world.html 'and 'mismatched delimiters') [Hello](./world.html 'and \'escapes\'') [Hello](./world.html 'and 'named entities'') [Hello](./world.html 'and 'numbered entities'') # Images ## Double quotes ![Hello](./world.png "and text") ![Hello](./world.png "and "matching delimiters"") ![Hello](./world.png "and "mismatched delimiters") ![Hello](./world.png "and \"escapes\"") ![Hello](./world.png "and "named entities"") ![Hello](./world.png "and "numbered entities"") ## Single quotes ![Hello](./world.png 'and text') ![Hello](./world.png 'and 'matching delimiters'') ![Hello](./world.png 'and 'mismatched delimiters') ![Hello](./world.png 'and \'escapes\'') ![Hello](./world.png 'and 'named entities'') ![Hello](./world.png 'and 'numbered entities'') ================================================ FILE: packages/rebber/__tests__/fixtures/toplevel-paragraphs.text ================================================ hello world how are you how are you hello world ``` how are you ``` hello world * * * hello world # how are you hello world how are you =========== hello world > how are you hello world * how are you hello world
how are you
hello world how are you hello [world][how] [how]: /are/you
hello
hello ================================================ FILE: packages/rebber/__tests__/fixtures/tricky-list.text ================================================ **hello** _world_ * hello world **hello** _world_ * hello world **hello** _world_ * Hello world **hello** _world_ * hello world ================================================ FILE: packages/rebber/__tests__/mdast.tests.js ================================================ import {readdirSync as directory, readFileSync as file, lstatSync as stat} from 'fs' import {join} from 'path' import unified from 'unified' import reParse from 'remark-parse' import footnotes from 'remark-footnotes' import rebber from '../src' const rebberConfig = { blockquote: (text) => `[blockquote(${text})]`, break: () => `[break(---)]`, code: (text, lang) => `[code${lang ? lang[0].toUpperCase() + lang.slice(1) : ''}(${text})]`, // comment: () => // `break(`, definition: (ctx, identifier, url, title) => `[definition(identifier=${identifier}, url=${url}, title=${title})]`, // delete: (text) => // `delete(${text})`, // emphasis: (text) => // `emphasis(${text})`, headings: [ (text) => `heading1(${text})`, (text) => `heading2(${text})`, (text) => `heading3(${text})`, (text) => `heading4(${text})`, (text) => `heading5(${text})`, (text) => `heading6(${text})`, (text) => `heading7(${text})`, ], // html: (text) => // `html(${text})`, image: (node) => `[image(${node.url})]`, // inlinecode: (text) => // `inlinecode(${text})`, link: (displayText, url, title) => `[link(displayText=${displayText}, url=${url}, title=${title})]`, linkReference: (reference, content) => `[linkReference(reference=${reference}, content=${content})]`, list: (content, isOrdered) => `[${isOrdered ? '' : 'un'}orderedList(${content})]`, listItem: (content) => `[listItem(${content})]`, // paragraph: (text) => // `paragraph(${text})`, // raw: (text) => // `raw(${text})`, // strong: (text) => // `strong(${text})`, // table: (ctx, node) => { // return node.children.map(n => `row(${one(n)})`).join('\n') // }, // tableCell: (ctx, node) => // `tableCell(${one(node)})`, // tableRow: (ctx, node) => // `tableRow(${one(node)})`, text: (text) => `[text(${text})]`, thematicBreak: () => `[thematicBreak(---)]`, } const specs = hydrateFixtures() describe('rebber: remark specs', () => { Object.keys(specs).filter(Boolean).forEach(name => { const spec = specs[name] test(name, () => { const {contents} = unified() .use(reParse) .use(footnotes, {inlineNotes: true}) .use(rebber) .processSync(spec) expect(contents.trim()).toMatchSnapshot(name) }) }) }) describe('rebber: remark specs with config: custom macros', () => { Object.keys(specs).filter(Boolean).forEach(name => { const spec = specs[name] test(name, () => { const {contents} = unified() .use(reParse) .use(footnotes, {inlineNotes: true}) .use(rebber, rebberConfig) .processSync(spec) expect(contents.trim()).toMatchSnapshot() }) }) }) describe('toLaTeX: remark specs', () => { Object.keys(specs).filter(Boolean).forEach(name => { const spec = specs[name] test(name, () => { const mdast = unified() .use(reParse) .use(footnotes, {inlineNotes: true}) .parse(spec) const latex = rebber.toLaTeX(mdast) expect(latex).toMatchSnapshot(name) }) }) }) /* helpers */ function hydrateFixtures () { const base = join(__dirname, `fixtures/`) return directory(base) .reduce((tests, filename) => { const parts = filename.split('.') if (stat(join(base, filename)).isFile()) { tests[parts[0]] = file(join(base, filename), 'utf-8').trim() } return tests }, {}) } ================================================ FILE: packages/rebber/dist/all.js ================================================ "use strict"; /* Dependencies. */ const one = require('./one'); /* Expose. */ module.exports = all; /* Stringify all children of `parent`. */ function all(ctx, parent) { const children = parent && parent.children; if (!children) return ''; return children.map((child, index) => one(ctx, child, index, parent)).join(''); } ================================================ FILE: packages/rebber/dist/escaper.js ================================================ "use strict"; /* Dependencies. */ const has = require('has'); const xtend = require('xtend'); /* Expose. */ module.exports = encode; encode.escape = escape; /* List of enforced escapes. */ const defaultEscapes = { '#': '\\#', $: '\\$', '%': '\\%', '&': '\\&', '\\': '\\textbackslash{}', '^': '\\textasciicircum{}', _: '\\_', '{': '\\{', '}': '\\}', '~': '\\textasciitilde{}' }; /* Encode special characters in `value`. */ function encode(value, opts = {}) { const escapes = xtend(defaultEscapes, opts); const set = toExpression(Object.keys(escapes)); value = value.replace(set, function (char, pos, val) { return one(char, val.charAt(pos + 1), escapes); }); return value; } /* Encode `char` according to `options`. */ function one(char, next, escapes) { if (has(escapes, char)) { return escapes[char]; } return char; } /* Create an expression for `characters`. */ function toExpression(characters) { const pattern = characters.map(escapeRegExp).join('|'); return new RegExp(`[${pattern}]`, 'g'); } function escapeRegExp(str) { return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); } ================================================ FILE: packages/rebber/dist/index.js ================================================ "use strict"; /* Dependencies. */ const xtend = require('xtend'); const definitions = require('mdast-util-definitions'); const one = require('./one'); const preprocess = require('./preprocessors'); /* Expose. */ module.exports = stringify; module.exports.toLaTeX = toLaTeX; function toLaTeX(tree, options = {}) { /* Stringify the given MDAST node. */ preprocess(options, tree); // resolve definition after preprocess because this step can create new identifiers options.definitions = definitions(tree, options); return one(options, tree, undefined, undefined); } /* Compile MDAST tree using toLaTeX */ function stringify(config) { const settings = xtend(config, this.data('settings')); this.Compiler = compiler; function compiler(tree) { return toLaTeX(tree, settings, tree); } } ================================================ FILE: packages/rebber/dist/one.js ================================================ "use strict"; /* Dependencies. */ const has = require('has'); const xtend = require('xtend'); /* Expose. */ module.exports = one; /* Handlers. */ const handlers = {}; handlers.blockquote = require('./types/blockquote'); handlers.break = require('./types/break'); handlers.code = require('./types/code'); handlers.definition = require('./types/definition'); handlers.delete = require('./types/delete'); handlers.emphasis = require('./types/emphasis'); handlers.heading = require('./types/heading'); handlers.html = require('./types/html'); handlers.image = require('./types/image'); handlers.inlineCode = require('./types/inlinecode'); handlers.link = require('./types/link'); handlers.linkReference = require('./types/linkReference'); handlers.list = require('./types/list'); handlers.listItem = require('./types/listItem'); handlers.paragraph = require('./types/paragraph'); handlers.root = require('./types/root'); handlers.strong = require('./types/strong'); handlers.table = require('./types/table'); handlers.tableCell = require('./types/tableCell'); handlers.tableRow = require('./types/tableRow'); handlers.text = require('./types/text'); handlers.thematicBreak = require('./types/thematic-break'); /* Stringify `node`. */ function one(ctx, node, index, parent) { const handlersOverrides = has(ctx, 'overrides') ? ctx.overrides : {}; const h = xtend(handlers, handlersOverrides); const type = node && node.type; if (!type) { throw new Error(`Received node '${node}' does not have a type.`); } if (!has(h, type) || typeof h[type] !== 'function') { throw new Error(`Cannot compile unknown node \`${type}\``); } return h[type](ctx, node, index, parent); } ================================================ FILE: packages/rebber/dist/preprocessors/index.js ================================================ "use strict"; const xtend = require('xtend'); const visit = require('unist-util-visit'); const referenceVisitors = require('./referenceVisitors'); module.exports = preprocess; function preprocess(ctx, tree) { const { definitionVisitor, imageReferenceVisitor } = referenceVisitors(); const defaultVisitors = { definition: [definitionVisitor], imageReference: [imageReferenceVisitor] }; const visitors = xtend(defaultVisitors, ctx.preprocessors || {}); Object.keys(visitors).forEach(nodeType => { if (Array.isArray(visitors[nodeType])) { visitors[nodeType].forEach(visitor => visit(tree, nodeType, visitor(ctx, tree))); } else { visit(tree, nodeType, visitors[nodeType](ctx, tree)); } }); } ================================================ FILE: packages/rebber/dist/preprocessors/referenceVisitors.js ================================================ "use strict"; module.exports = () => { const state = {}; return { definitionVisitor() { return (node, index, parent) => { let identifier = node.identifier; while (Object.keys(state).includes(identifier)) { identifier += '-1'; } state[identifier] = node.url; node.identifier = identifier; // force to remove twice so that latex compiles if (node.referenceType === 'shortcut') { // remark for abbr parent.children.splice(index, 1); } }; }, imageReferenceVisitor() { return node => { node.type = 'image'; node.title = ''; node.url = state[node.identifier]; }; }, addIdentifier(identifier, content) { state[identifier] = content; } }; }; ================================================ FILE: packages/rebber/dist/types/blockquote.js ================================================ "use strict"; /* Expose. */ module.exports = blockquote; const defaultMacro = innerText => `\\begin{Quotation}\n${innerText}\n\\end{Quotation}\n\n`; /* Stringify a Blockquote `node`. */ function blockquote(ctx, node) { const macro = ctx.blockquote || defaultMacro; const innerText = require('../all')(ctx, node); return macro(innerText.trim()); } ================================================ FILE: packages/rebber/dist/types/break.js ================================================ "use strict"; /* Expose. */ module.exports = br; const defaultMacro = () => ' \\\\\n'; /* Stringify a break `node`. */ function br(ctx, node) { const macro = ctx.break ? ctx.break : defaultMacro; return macro(node); } ================================================ FILE: packages/rebber/dist/types/code.js ================================================ "use strict"; /* Expose. */ module.exports = code; const defaultMacro = (content, lang) => { // Escape CodeBlocks let escaped = content; const escapeRegex = /\\end\s*{CodeBlock}/g; while (escapeRegex.test(escaped)) { escaped = escaped.replace(escapeRegex, ''); } // Default language is "text" if (!lang) lang = 'text'; return `\\begin{CodeBlock}{${lang}}\n${escaped}\n\\end{CodeBlock}\n\n`; }; /* Stringify a code `node`. */ function code(ctx, node) { const macro = ctx.code || defaultMacro; return `${macro(node.value, node.lang, node.meta)}`; } code.macro = defaultMacro; ================================================ FILE: packages/rebber/dist/types/definition.js ================================================ "use strict"; module.exports = definition; const defaultMacro = (ctx, identifier, url, title) => { const node = { children: [{ type: 'link', title, url, children: [{ type: 'text', value: url }] }] }; const link = require('../all')(ctx, node); return `\\footnote{\\label{${identifier}}${link}}`; }; function definition(ctx, node) { const macro = ctx.definition ? ctx.definition : defaultMacro; return macro(ctx, node.identifier, node.url, node.title); } ================================================ FILE: packages/rebber/dist/types/delete.js ================================================ "use strict"; // TODO: make it customizable /* Expose. */ module.exports = deleteNode; /* Stringify a delete `node`. */ function deleteNode(ctx, node, index, parent) { const contents = require('../all')(ctx, node); return `\\sout{${contents}}`; } ================================================ FILE: packages/rebber/dist/types/emphasis.js ================================================ "use strict"; // TODO: make it customizable /* Expose. */ module.exports = emphasis; /* Stringify an emphasis `node`. */ function emphasis(ctx, node, index, parent) { const contents = require('../all')(ctx, node); return `\\textit{${contents}}`; } ================================================ FILE: packages/rebber/dist/types/heading.js ================================================ "use strict"; /* Expose. */ module.exports = heading; const defaultHeadings = [val => `\\part{${val}}\n`, val => `\\chapter{${val}}\n`, val => `\\section{${val}}\n`, val => `\\subsection{${val}}\n`, val => `\\subsubsection{${val}}\n`, val => `\\paragraph{${val}}\n`, val => `\\subparagaph{${val}}\n`]; /* Stringify a heading `node`. */ function heading(ctx, node) { const depth = node.depth; const content = require('../all')(ctx, node); const headings = ctx.headings || defaultHeadings; const fn = headings[node.depth - 1]; if (typeof fn !== 'function') { throw new Error(`Cannot compile heading of depth ${depth}: not a function`); } return fn(content); } ================================================ FILE: packages/rebber/dist/types/html.js ================================================ "use strict"; // TODO: make it customizable /* Expose. */ module.exports = html; /* Stringify a html `node`. */ function html(ctx, node, index, parent) { return node.value; } ================================================ FILE: packages/rebber/dist/types/image.js ================================================ "use strict"; /* Dependencies. */ const path = require('path'); /* Expose. */ module.exports = image; const defaultInlineMatcher = (node, parent) => { return parent.type === 'paragraph' && parent.children.length - 1 || parent.type === 'heading'; }; const defaultMacro = node => { /* Note that MDAST `Image` nodes don't have a `width` property. You might still want to specify a width since \includegraphics handles it. */ const width = node.width ? `[width=${node.width}]` : ''; return `\\includegraphics${width}{${node.url}}`; }; const defaultInline = defaultMacro; function image(ctx, node, _, parent) { const options = ctx.image || {}; if (node.url) { // Avoid a security flaw: trying to escape image paths node.url = node.url.replace(/}/g, ''); try { const { root, dir, base, ext, name } = path.parse(node.url); // \includegraphics crashes with filenames that contain more than one `.`, // the workaround is \includegraphics{/path/to/{image.foo}.jpg} if (base.includes('.')) { const safeName = name.includes('.') ? `{${name}}${ext}` : `${name}${ext}`; node.url = `${path.format({ root, dir })}${safeName}`; } } catch (e) { node.url = ''; } } let macro = options.image ? options.image : defaultMacro; const inlineMatcher = options.inlineMatcher ? options.inlineMatcher : defaultInlineMatcher; if (inlineMatcher(node, parent)) { macro = options.inlineImage ? options.inlineImage : defaultInline; } return macro(node); } ================================================ FILE: packages/rebber/dist/types/inlinecode.js ================================================ "use strict"; const collapse = require('collapse-white-space'); const escape = require('../escaper'); module.exports = function inlineCode(ctx, node) { const finalCode = escape(collapse(node.value)); return `\\texttt{${finalCode}}`; }; ================================================ FILE: packages/rebber/dist/types/link.js ================================================ "use strict"; /* Dependencies. */ const has = require('has'); const escape = require('../escaper'); /* Expose. */ module.exports = link; const defaultMacro = (displayText, url, title) => `\\externalLink{${displayText}}{${url}}`; /* Stringify a link `node`. */ function link(ctx, node) { if (!node.url) return ''; const config = ctx.link || {}; const macro = has(config, 'macro') ? config.macro : defaultMacro; const prefix = has(config, 'prefix') ? config.prefix : ''; const url = escape(node.url.startsWith('/') ? prefix + node.url : node.url); return macro(require('../all')(ctx, node), url, node.title); } ================================================ FILE: packages/rebber/dist/types/linkReference.js ================================================ "use strict"; module.exports = linkReference; const defaultMacro = (reference, inner) => `\\hyperref[${reference}]{${inner}}`; function linkReference(ctx, node) { const macro = ctx.linkReference ? ctx.linkReference : defaultMacro; const innerText = require('../all')(ctx, node); if (!ctx.definitions(node.identifier)) return `[${innerText}]`; return macro(node.identifier, innerText); } ================================================ FILE: packages/rebber/dist/types/list.js ================================================ "use strict"; const has = require('has'); module.exports = list; const defaultMacro = (innerText, isOrdered) => { if (isOrdered) { return `\\begin{enumerate}\n${innerText}\\end{enumerate}\n`; } else { return `\\begin{itemize}\n${innerText}\\end{itemize}\n`; } }; function list(ctx, node) { const rebberList = has(ctx, 'list') ? ctx.list : defaultMacro; const itemized = require('../all')(ctx, node); return rebberList(itemized, node.ordered); } ================================================ FILE: packages/rebber/dist/types/listItem.js ================================================ "use strict"; const has = require('has'); const defaultMacro = innerText => `\\item\\relax ${innerText}\n`; const defaultCheckedMacro = innerText => `\\item[$\\boxtimes$]\\relax ${innerText}\n`; const defaultUncheckedMacro = innerText => `\\item[$\\square$]\\relax ${innerText}\n`; module.exports = listItem; function listItem(ctx, node) { let rebberListItem = has(ctx, 'listItem') ? ctx.listItem : defaultMacro; if (node.checked === true) { rebberListItem = has(ctx, 'checkedListItem') ? ctx.checkedListItem : defaultCheckedMacro; } else if (node.checked === false) { rebberListItem = has(ctx, 'uncheckedListItem') ? ctx.uncheckedListItem : defaultUncheckedMacro; } return rebberListItem(require('../all')(ctx, node).trim()); } ================================================ FILE: packages/rebber/dist/types/paragraph.js ================================================ "use strict"; /* Expose. */ module.exports = paragraph; /* Stringify a paragraph `node`. */ function paragraph(ctx, node) { const contents = require('../all')(ctx, node); return `${contents.trim()}\n\n`; } ================================================ FILE: packages/rebber/dist/types/raw.js ================================================ "use strict"; // TODO: make it customizable /* Dependencies. */ const text = require('./text'); /* Expose. */ module.exports = raw; /* Stringify `raw`. */ function raw(ctx, node) { return ctx.dangerous ? node.value : text(ctx, node); } ================================================ FILE: packages/rebber/dist/types/root.js ================================================ "use strict"; /* Dependencies. */ const one = require('../one'); /* Expose. */ module.exports = root; /* Stringify a text `node`. */ function root(ctx, node, _, parent) { const children = node.children; if (!children) return ''; let previous; return children.reduce((output, child, index) => { if (previous) { if (child.type === previous.type && previous.type === 'list') { output += previous.ordered === child.ordered ? '\n\n\n' : '\n\n'; } else if (previous.type === 'list' && child.type === 'code' && !child.lang) { output += '\n\n\n'; } else { output += '\n\n'; } } output += one(ctx, child, index, node, node); previous = child; return output; }, ''); } ================================================ FILE: packages/rebber/dist/types/strong.js ================================================ "use strict"; // TODO: make it customizable /* Expose. */ module.exports = strong; /* Stringify a strong `node`. */ function strong(ctx, node, index, parent) { const contents = require('../all')(ctx, node); return `\\textbf{${contents}}`; } ================================================ FILE: packages/rebber/dist/types/table.js ================================================ "use strict"; const clone = require('clone'); const one = require('../one'); /* Expose. */ module.exports = table; const defaultHeaderParse = rows => { const columns = Math.max(...rows.map(l => l.split('&').length)); return ' X[-1]'.repeat(columns).substring(1); }; // Retrocompatibility: first row is always header on default tables const defaultheaderCounter = () => { return 1; }; const defaultMacro = (ctx, node) => { const headerParse = ctx.headerParse ? ctx.headerParse : defaultHeaderParse; const headerCounter = ctx.headerCounter ? ctx.headerCounter : defaultheaderCounter; const parsed = node.children.map((n, index) => one(ctx, n, index, node)); const headerCount = headerCounter(node); const colHeader = headerParse(parsed); const envName = typeof ctx.tableEnvName === 'string' ? ctx.tableEnvName : 'longtblr'; const caption = node.caption ? `\n\\captionof{table}{${node.caption}}\n` : ''; // eslint-disable-next-line max-len const headerProperties = typeof ctx.headerProperties === 'string' ? ctx.headerProperties : 'font=\\bfseries'; let extraProps = ''; if (headerCount && headerCount > 0) { const tableHeaderEnum = new Array(headerCount).fill(0).map((_, i) => i + 1).join(','); extraProps += `,rowhead=${headerCount},row{${tableHeaderEnum}}={${headerProperties}}`; } // eslint-disable-next-line max-len return `\\begin{${envName}}{colspec={${colHeader}}${extraProps}}\n${parsed.join('')}\\end{${envName}}${caption}\n`; }; /* Stringify a table `node`. */ function table(ctx, node) { const macro = ctx.table || defaultMacro; const overriddenCtx = clone(ctx); overriddenCtx.image = overriddenCtx.image ? overriddenCtx.image : {}; overriddenCtx.image.inlineMatcher = () => true; return macro(overriddenCtx, node); } ================================================ FILE: packages/rebber/dist/types/tableCell.js ================================================ "use strict"; /* Expose. */ module.exports = tableCell; const defaultMacro = (ctx, node) => { return require('../all')(ctx, node); }; /* Stringify a tableCell `node`. */ function tableCell(ctx, node) { const macro = ctx.tableCell || defaultMacro; return macro(ctx, node); } ================================================ FILE: packages/rebber/dist/types/tableRow.js ================================================ "use strict"; /* Expose. */ const one = require('../one'); module.exports = tableRow; const defaultMacro = (ctx, node) => { const parsed = []; node.children.map((n, index) => parsed.push(one(ctx, n, index, node))); const line = parsed.join(' & '); return `${line} \\\\\n`; }; /* Stringify a tableRow `node`. */ function tableRow(ctx, node, index) { const macro = ctx.tableRow || defaultMacro; return macro(ctx, node); } ================================================ FILE: packages/rebber/dist/types/text.js ================================================ "use strict"; /* Dependencies. */ const trimLines = require('trim-lines'); const escaper = require('../escaper'); /* Stringify a text `node`. */ module.exports = function text(ctx, node, index, parent) { const value = trimLines(node.value); return isLiteral(parent) ? value : escaper(value, ctx.escapes); }; // TODO: `tagName` isn't part of MDAST! /* Check if content of `node` should not be escaped. */ function isLiteral(node) { return node && (node.tagName === 'script' || node.tagName === 'style'); } ================================================ FILE: packages/rebber/dist/types/thematic-break.js ================================================ "use strict"; /* Expose. */ module.exports = thematicBreak; const defaultMacro = () => '\\horizontalLine\n\n'; /* Stringify a delete `node`. */ function thematicBreak(ctx, node, index, parent) { const macro = ctx.thematicBreak || defaultMacro; return macro(node); } ================================================ FILE: packages/rebber/package.json ================================================ { "name": "rebber", "version": "5.5.0", "description": "Stringifies MDAST to LaTeX", "repository": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rebber", "author": "Victor Felder (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "mdast", "latex" ], "license": "MIT", "dependencies": { "clone": "^2.1.2", "collapse-white-space": "^1.0.6", "has": "^1.0.3", "mdast-util-definitions": "^3.0.1", "trim-lines": "^1.1.3", "xtend": "^4.0.2" } } ================================================ FILE: packages/rebber/src/all.js ================================================ /* Dependencies. */ const one = require('./one') /* Expose. */ module.exports = all /* Stringify all children of `parent`. */ function all (ctx, parent) { const children = parent && parent.children if (!children) return '' return children .map((child, index) => one(ctx, child, index, parent)) .join('') } ================================================ FILE: packages/rebber/src/escaper.js ================================================ /* Dependencies. */ const has = require('has') const xtend = require('xtend') /* Expose. */ module.exports = encode encode.escape = escape /* List of enforced escapes. */ const defaultEscapes = { '#': '\\#', $: '\\$', '%': '\\%', '&': '\\&', '\\': '\\textbackslash{}', '^': '\\textasciicircum{}', _: '\\_', '{': '\\{', '}': '\\}', '~': '\\textasciitilde{}' } /* Encode special characters in `value`. */ function encode (value, opts = {}) { const escapes = xtend(defaultEscapes, opts) const set = toExpression(Object.keys(escapes)) value = value.replace(set, function (char, pos, val) { return one(char, val.charAt(pos + 1), escapes) }) return value } /* Encode `char` according to `options`. */ function one (char, next, escapes) { if (has(escapes, char)) { return escapes[char] } return char } /* Create an expression for `characters`. */ function toExpression (characters) { const pattern = characters.map(escapeRegExp).join('|') return new RegExp(`[${pattern}]`, 'g') } function escapeRegExp (str) { return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') } ================================================ FILE: packages/rebber/src/index.js ================================================ /* Dependencies. */ const xtend = require('xtend') const definitions = require('mdast-util-definitions') const one = require('./one') const preprocess = require('./preprocessors') /* Expose. */ module.exports = stringify module.exports.toLaTeX = toLaTeX function toLaTeX (tree, options = {}) { /* Stringify the given MDAST node. */ preprocess(options, tree) // resolve definition after preprocess because this step can create new identifiers options.definitions = definitions(tree, options) return one(options, tree, undefined, undefined) } /* Compile MDAST tree using toLaTeX */ function stringify (config) { const settings = xtend(config, this.data('settings')) this.Compiler = compiler function compiler (tree) { return toLaTeX(tree, settings, tree) } } ================================================ FILE: packages/rebber/src/one.js ================================================ /* Dependencies. */ const has = require('has') const xtend = require('xtend') /* Expose. */ module.exports = one /* Handlers. */ const handlers = {} handlers.blockquote = require('./types/blockquote') handlers.break = require('./types/break') handlers.code = require('./types/code') handlers.definition = require('./types/definition') handlers.delete = require('./types/delete') handlers.emphasis = require('./types/emphasis') handlers.heading = require('./types/heading') handlers.html = require('./types/html') handlers.image = require('./types/image') handlers.inlineCode = require('./types/inlinecode') handlers.link = require('./types/link') handlers.linkReference = require('./types/linkReference') handlers.list = require('./types/list') handlers.listItem = require('./types/listItem') handlers.paragraph = require('./types/paragraph') handlers.root = require('./types/root') handlers.strong = require('./types/strong') handlers.table = require('./types/table') handlers.tableCell = require('./types/tableCell') handlers.tableRow = require('./types/tableRow') handlers.text = require('./types/text') handlers.thematicBreak = require('./types/thematic-break') /* Stringify `node`. */ function one (ctx, node, index, parent) { const handlersOverrides = has(ctx, 'overrides') ? ctx.overrides : {} const h = xtend(handlers, handlersOverrides) const type = node && node.type if (!type) { throw new Error(`Received node '${node}' does not have a type.`) } if (!has(h, type) || typeof h[type] !== 'function') { throw new Error(`Cannot compile unknown node \`${type}\``) } return h[type](ctx, node, index, parent) } ================================================ FILE: packages/rebber/src/preprocessors/index.js ================================================ const xtend = require('xtend') const visit = require('unist-util-visit') const referenceVisitors = require('./referenceVisitors') module.exports = preprocess function preprocess (ctx, tree) { const { definitionVisitor, imageReferenceVisitor } = referenceVisitors() const defaultVisitors = { definition: [definitionVisitor], imageReference: [imageReferenceVisitor] } const visitors = xtend(defaultVisitors, ctx.preprocessors || {}) Object.keys(visitors).forEach((nodeType) => { if (Array.isArray(visitors[nodeType])) { visitors[nodeType].forEach(visitor => visit(tree, nodeType, visitor(ctx, tree))) } else { visit(tree, nodeType, visitors[nodeType](ctx, tree)) } }) } ================================================ FILE: packages/rebber/src/preprocessors/referenceVisitors.js ================================================ module.exports = () => { const state = {} return { definitionVisitor () { return (node, index, parent) => { let identifier = node.identifier while (Object.keys(state).includes(identifier)) { identifier += '-1' } state[identifier] = node.url node.identifier = identifier // force to remove twice so that latex compiles if (node.referenceType === 'shortcut') { // remark for abbr parent.children.splice(index, 1) } } }, imageReferenceVisitor () { return (node) => { node.type = 'image' node.title = '' node.url = state[node.identifier] } }, addIdentifier (identifier, content) { state[identifier] = content } } } ================================================ FILE: packages/rebber/src/types/blockquote.js ================================================ /* Expose. */ module.exports = blockquote const defaultMacro = (innerText) => `\\begin{Quotation}\n${innerText}\n\\end{Quotation}\n\n` /* Stringify a Blockquote `node`. */ function blockquote (ctx, node) { const macro = ctx.blockquote || defaultMacro const innerText = require('../all')(ctx, node) return macro(innerText.trim()) } ================================================ FILE: packages/rebber/src/types/break.js ================================================ /* Expose. */ module.exports = br const defaultMacro = () => ' \\\\\n' /* Stringify a break `node`. */ function br (ctx, node) { const macro = ctx.break ? ctx.break : defaultMacro return macro(node) } ================================================ FILE: packages/rebber/src/types/code.js ================================================ /* Expose. */ module.exports = code const defaultMacro = (content, lang) => { // Escape CodeBlocks let escaped = content const escapeRegex = /\\end\s*{CodeBlock}/g while (escapeRegex.test(escaped)) { escaped = escaped.replace(escapeRegex, '') } // Default language is "text" if (!lang) lang = 'text' return `\\begin{CodeBlock}{${lang}}\n${escaped}\n\\end{CodeBlock}\n\n` } /* Stringify a code `node`. */ function code (ctx, node) { const macro = ctx.code || defaultMacro return `${macro(node.value, node.lang, node.meta)}` } code.macro = defaultMacro ================================================ FILE: packages/rebber/src/types/definition.js ================================================ module.exports = definition const defaultMacro = (ctx, identifier, url, title) => { const node = { children: [{ type: 'link', title, url, children: [{ type: 'text', value: url }] }] } const link = require('../all')(ctx, node) return `\\footnote{\\label{${identifier}}${link}}` } function definition (ctx, node) { const macro = ctx.definition ? ctx.definition : defaultMacro return macro(ctx, node.identifier, node.url, node.title) } ================================================ FILE: packages/rebber/src/types/delete.js ================================================ // TODO: make it customizable /* Expose. */ module.exports = deleteNode /* Stringify a delete `node`. */ function deleteNode (ctx, node, index, parent) { const contents = require('../all')(ctx, node) return `\\sout{${contents}}` } ================================================ FILE: packages/rebber/src/types/emphasis.js ================================================ // TODO: make it customizable /* Expose. */ module.exports = emphasis /* Stringify an emphasis `node`. */ function emphasis (ctx, node, index, parent) { const contents = require('../all')(ctx, node) return `\\textit{${contents}}` } ================================================ FILE: packages/rebber/src/types/heading.js ================================================ /* Expose. */ module.exports = heading const defaultHeadings = [ (val) => `\\part{${val}}\n`, (val) => `\\chapter{${val}}\n`, (val) => `\\section{${val}}\n`, (val) => `\\subsection{${val}}\n`, (val) => `\\subsubsection{${val}}\n`, (val) => `\\paragraph{${val}}\n`, (val) => `\\subparagaph{${val}}\n` ] /* Stringify a heading `node`. */ function heading (ctx, node) { const depth = node.depth const content = require('../all')(ctx, node) const headings = ctx.headings || defaultHeadings const fn = headings[node.depth - 1] if (typeof fn !== 'function') { throw new Error(`Cannot compile heading of depth ${depth}: not a function`) } return fn(content) } ================================================ FILE: packages/rebber/src/types/html.js ================================================ // TODO: make it customizable /* Expose. */ module.exports = html /* Stringify a html `node`. */ function html (ctx, node, index, parent) { return node.value } ================================================ FILE: packages/rebber/src/types/image.js ================================================ /* Dependencies. */ const path = require('path') /* Expose. */ module.exports = image const defaultInlineMatcher = (node, parent) => { return (parent.type === 'paragraph' && parent.children.length - 1) || parent.type === 'heading' } const defaultMacro = (node) => { /* Note that MDAST `Image` nodes don't have a `width` property. You might still want to specify a width since \includegraphics handles it. */ const width = node.width ? `[width=${node.width}]` : '' return `\\includegraphics${width}{${node.url}}` } const defaultInline = defaultMacro function image (ctx, node, _, parent) { const options = ctx.image || {} if (node.url) { // Avoid a security flaw: trying to escape image paths node.url = node.url.replace(/}/g, '') try { const { root, dir, base, ext, name } = path.parse(node.url) // \includegraphics crashes with filenames that contain more than one `.`, // the workaround is \includegraphics{/path/to/{image.foo}.jpg} if (base.includes('.')) { const safeName = name.includes('.') ? `{${name}}${ext}` : `${name}${ext}` node.url = `${path.format({ root, dir })}${safeName}` } } catch (e) { node.url = '' } } let macro = options.image ? options.image : defaultMacro const inlineMatcher = options.inlineMatcher ? options.inlineMatcher : defaultInlineMatcher if (inlineMatcher(node, parent)) { macro = options.inlineImage ? options.inlineImage : defaultInline } return macro(node) } ================================================ FILE: packages/rebber/src/types/inlinecode.js ================================================ const collapse = require('collapse-white-space') const escape = require('../escaper') module.exports = function inlineCode (ctx, node) { const finalCode = escape(collapse(node.value)) return `\\texttt{${finalCode}}` } ================================================ FILE: packages/rebber/src/types/link.js ================================================ /* Dependencies. */ const has = require('has') const escape = require('../escaper') /* Expose. */ module.exports = link const defaultMacro = (displayText, url, title) => `\\externalLink{${displayText}}{${url}}` /* Stringify a link `node`. */ function link (ctx, node) { if (!node.url) return '' const config = ctx.link || {} const macro = has(config, 'macro') ? config.macro : defaultMacro const prefix = has(config, 'prefix') ? config.prefix : '' const url = escape(node.url.startsWith('/') ? prefix + node.url : node.url) return macro(require('../all')(ctx, node), url, node.title) } ================================================ FILE: packages/rebber/src/types/linkReference.js ================================================ module.exports = linkReference const defaultMacro = (reference, inner) => `\\hyperref[${reference}]{${inner}}` function linkReference (ctx, node) { const macro = ctx.linkReference ? ctx.linkReference : defaultMacro const innerText = require('../all')(ctx, node) if (!ctx.definitions(node.identifier)) return `[${innerText}]` return macro(node.identifier, innerText) } ================================================ FILE: packages/rebber/src/types/list.js ================================================ const has = require('has') module.exports = list const defaultMacro = (innerText, isOrdered) => { if (isOrdered) { return `\\begin{enumerate}\n${innerText}\\end{enumerate}\n` } else { return `\\begin{itemize}\n${innerText}\\end{itemize}\n` } } function list (ctx, node) { const rebberList = has(ctx, 'list') ? ctx.list : defaultMacro const itemized = require('../all')(ctx, node) return rebberList(itemized, node.ordered) } ================================================ FILE: packages/rebber/src/types/listItem.js ================================================ const has = require('has') const defaultMacro = (innerText) => `\\item\\relax ${innerText}\n` const defaultCheckedMacro = (innerText) => `\\item[$\\boxtimes$]\\relax ${innerText}\n` const defaultUncheckedMacro = (innerText) => `\\item[$\\square$]\\relax ${innerText}\n` module.exports = listItem function listItem (ctx, node) { let rebberListItem = has(ctx, 'listItem') ? ctx.listItem : defaultMacro if (node.checked === true) { rebberListItem = has(ctx, 'checkedListItem') ? ctx.checkedListItem : defaultCheckedMacro } else if (node.checked === false) { rebberListItem = has(ctx, 'uncheckedListItem') ? ctx.uncheckedListItem : defaultUncheckedMacro } return rebberListItem(require('../all')(ctx, node).trim()) } ================================================ FILE: packages/rebber/src/types/paragraph.js ================================================ /* Expose. */ module.exports = paragraph /* Stringify a paragraph `node`. */ function paragraph (ctx, node) { const contents = require('../all')(ctx, node) return `${contents.trim()}\n\n` } ================================================ FILE: packages/rebber/src/types/raw.js ================================================ // TODO: make it customizable /* Dependencies. */ const text = require('./text') /* Expose. */ module.exports = raw /* Stringify `raw`. */ function raw (ctx, node) { return ctx.dangerous ? node.value : text(ctx, node) } ================================================ FILE: packages/rebber/src/types/root.js ================================================ /* Dependencies. */ const one = require('../one') /* Expose. */ module.exports = root /* Stringify a text `node`. */ function root (ctx, node, _, parent) { const children = node.children if (!children) return '' let previous return children.reduce((output, child, index) => { if (previous) { if (child.type === previous.type && previous.type === 'list') { output += previous.ordered === child.ordered ? '\n\n\n' : '\n\n' } else if (previous.type === 'list' && child.type === 'code' && !child.lang) { output += '\n\n\n' } else { output += '\n\n' } } output += one(ctx, child, index, node, node) previous = child return output }, '') } ================================================ FILE: packages/rebber/src/types/strong.js ================================================ // TODO: make it customizable /* Expose. */ module.exports = strong /* Stringify a strong `node`. */ function strong (ctx, node, index, parent) { const contents = require('../all')(ctx, node) return `\\textbf{${contents}}` } ================================================ FILE: packages/rebber/src/types/table.js ================================================ const clone = require('clone') const one = require('../one') /* Expose. */ module.exports = table const defaultHeaderParse = rows => { const columns = Math.max(...rows.map(l => l.split('&').length)) return ' X[-1]'.repeat(columns).substring(1) } // Retrocompatibility: first row is always header on default tables const defaultheaderCounter = () => { return 1 } const defaultMacro = (ctx, node) => { const headerParse = ctx.headerParse ? ctx.headerParse : defaultHeaderParse const headerCounter = ctx.headerCounter ? ctx.headerCounter : defaultheaderCounter const parsed = node.children.map((n, index) => one(ctx, n, index, node)) const headerCount = headerCounter(node) const colHeader = headerParse(parsed) const envName = typeof ctx.tableEnvName === 'string' ? ctx.tableEnvName : 'longtblr' const caption = node.caption ? `\n\\captionof{table}{${node.caption}}\n` : '' // eslint-disable-next-line max-len const headerProperties = typeof ctx.headerProperties === 'string' ? ctx.headerProperties : 'font=\\bfseries' let extraProps = '' if (headerCount && headerCount > 0) { const tableHeaderEnum = new Array(headerCount) .fill(0) .map((_, i) => i + 1) .join(',') extraProps += `,rowhead=${headerCount},row{${tableHeaderEnum}}={${headerProperties}}` } // eslint-disable-next-line max-len return `\\begin{${envName}}{colspec={${colHeader}}${extraProps}}\n${parsed.join('')}\\end{${envName}}${caption}\n` } /* Stringify a table `node`. */ function table (ctx, node) { const macro = ctx.table || defaultMacro const overriddenCtx = clone(ctx) overriddenCtx.image = overriddenCtx.image ? overriddenCtx.image : {} overriddenCtx.image.inlineMatcher = () => true return macro(overriddenCtx, node) } ================================================ FILE: packages/rebber/src/types/tableCell.js ================================================ /* Expose. */ module.exports = tableCell const defaultMacro = (ctx, node) => { return require('../all')(ctx, node) } /* Stringify a tableCell `node`. */ function tableCell (ctx, node) { const macro = ctx.tableCell || defaultMacro return macro(ctx, node) } ================================================ FILE: packages/rebber/src/types/tableRow.js ================================================ /* Expose. */ const one = require('../one') module.exports = tableRow const defaultMacro = (ctx, node) => { const parsed = [] node.children.map((n, index) => parsed.push(one(ctx, n, index, node))) const line = parsed.join(' & ') return `${line} \\\\\n` } /* Stringify a tableRow `node`. */ function tableRow (ctx, node, index) { const macro = ctx.tableRow || defaultMacro return macro(ctx, node) } ================================================ FILE: packages/rebber/src/types/text.js ================================================ /* Dependencies. */ const trimLines = require('trim-lines') const escaper = require('../escaper') /* Stringify a text `node`. */ module.exports = function text (ctx, node, index, parent) { const value = trimLines(node.value) return isLiteral(parent) ? value : escaper(value, ctx.escapes) } // TODO: `tagName` isn't part of MDAST! /* Check if content of `node` should not be escaped. */ function isLiteral (node) { return node && (node.tagName === 'script' || node.tagName === 'style') } ================================================ FILE: packages/rebber/src/types/thematic-break.js ================================================ /* Expose. */ module.exports = thematicBreak const defaultMacro = () => '\\horizontalLine\n\n' /* Stringify a delete `node`. */ function thematicBreak (ctx, node, index, parent) { const macro = ctx.thematicBreak || defaultMacro return macro(node) } ================================================ FILE: packages/rebber-plugins/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/rebber-plugins/README.md ================================================ # rebber-plugins [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] **rebber-plugins** is a collection of LaTeX stringifiers for custom [mdast][] nodes. These plugins are intended to be used with [rebber][]. It currently supports: * [remark-abbr][] * [remark-align][] * [remark-custom-blocks][] * [remark-emoticons][] * [remark-footnotes][] * [remark-grid-tables][] * [remark-iframes][] * [remark-kbd][] * [remark-ping][] * [remark-sub-super][] ## Installation [npm][]: ```bash npm install rebber-plugins ``` ## Usage ```javascript const unified = require('unified') const remarkParser = require('remark-parse') const rebber = require('rebber') const {contents} = unified() .use(remarkParser, remarkConfig) .use(rebber, rebberConfig) .processSync('### foo') console.log(contents); ``` ## Supported remark plugins ###### [remark-abbr][] * `remarkConfig` needs to be configured for `remark-abbr` * `rebberConfig.overrides.abbr = require('rebber-plugins/dist/type/abbr')` * `rebberConfig.abbr = (displayText, definition) => ''` ###### [remark-align][] * `remarkConfig` needs to be configured for `remark-align` * `rebberConfig.overrides.centerAligned = require('rebber-plugins/dist/type/align')` * `rebberConfig.overrides.leftAligned = require('rebber-plugins/dist/type/align')` * `rebberConfig.overrides.rightAligned = require('rebber-plugins/dist/type/align')` * `rebberConfig.leftAligned = (innerText) => ''` * `rebberConfig.centerAligned = (innerText) => ''` * `rebberConfig.rightAligned = (innerText) => ''` * `rebberConfig.defaultType = (innerText, type) => ''` ###### [remark-custom-blocks][] * `remarkConfig` needs to be configured for `remark-custom-blocks` * `rebberConfig.overrides.errorCustomBlock = require('rebber-plugins/dist/type/customBlocks')` * `rebberConfig.errorCustomBlock = (innerText, environmentName) => ''` ###### [remark-emoticons][] * `remarkConfig` needs to be configured for `remark-emoticons` * `rebberConfig.overrides.emoticon = require('rebber-plugins/dist/type/emoticon')` * `rebberConfig.emoticons = remarkConfig.emoticons` ###### [remark-grid-tables][] * `remarkConfig` needs to be configured for `remark-grid-tables` * `rebberConfig.overrides.gridTable = require('rebber-plugins/dist/type/gridTable')` Proper handling of fenced code blocks in grid tables being hard to achieve in LaTeX, you can use the following preprocessor to automatically move the code blocks to an appendix section and replace the original location with a reference to the appendix section: ```js .use(rebber, { preprocessors: { iframe: require('rebber-plugins/dist/preprocessors/iframe') } }) ``` ###### [remark-iframes][] * `remarkConfig` needs to be configured for `remark-iframes` `iframe` nodes require some preprocessing before getting compiled to LaTeX: ```javascript const unified = require('unified') const remarkParser = require('remark-parse') const rebber = require('rebber') const {contents} = unified() .use(remarkParser, { // see config options in the remark-iframes package iframes: { 'www.dailymotion.com': { tag: 'iframe', width: 480, height: 270, disabled: false, replace: [ ['video/', 'embed/video/'], ], thumbnail: { format: 'http://www.dailymotion.com/thumbnail/video/{id}', id: '.+/(.+)$' } }, } }) .use(rebber, { preprocessors: { iframe: require('rebber-plugins/dist/preprocessors/iframe') } }) .processSync('some markdown') console.log(contents); ``` ###### [remark-kbd][] * `remarkConfig` needs to be configured for `remark-kbd` * `rebberConfig.overrides.kbd = require('rebber-plugins/dist/type/kbd')` ###### [remark-ping][] * `remarkConfig` needs to be configured for `remark-ping` * `rebberConfig.overrides.ping = require('rebber-plugins/dist/type/ping')` ###### [remark-sub-super][] * `remarkConfig` needs to be configured for `remark-sub-super` * `rebberConfig.overrides.sub = require('rebber-plugins/dist/type/sub')` * `rebberConfig.overrides.sup = require('rebber-plugins/dist/type/sup')` ## License [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/rebber-plugins/LICENSE-MIT [rebber]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/rebber [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/rebber-plugins [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark-abbr]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-abbr#remark-abbr-- [remark-align]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-align#remark-align-- [remark-custom-blocks]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-custom-blocks#remark-custom-blocks-- [remark-emoticons]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-emoticons#remark-emoticons-- [remark-footnotes]: https://github.com/remarkjs/remark-footnotes [remark-grid-tables]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-grid-tables#remark-grid-tables-- [remark-iframes]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-iframes#remark-iframes-- [remark-kbd]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-kbd#remark-kbd-- [remark-ping]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-ping#remark-ping-- [remark-sub-super]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-sub-super#remark-sub-super-- ================================================ FILE: packages/rebber-plugins/__tests__/__snapshots__/rebber.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`abbr 1`] = ` "HELO to you Can I INJECT something in \\\\textbackslash{}LaTeX ? *[HELO]: In SMTP, the client initiates the dialog with a HELO command identifying itself *[INJECT]: discard\\\\}\\\\textbackslash{}LaTeX\\\\textbackslash{}abbr\\\\{INJECTED\\\\}\\\\{discard " `; exports[`abbr 2`] = ` "\\\\abbr{FOO}{bar} " `; exports[`abbr with custom config 1`] = ` "->FOO<- " `; exports[`blockquote 1`] = ` "\\\\begin{Quotation} a quote \\\\end{Quotation} \\\\begin{Quotation} a multiline quote \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} a quote \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} a multiline quote \\\\end{Quotation}" `; exports[`blockquote 2`] = ` "\\\\begin{Quotation} a quote \\\\end{Quotation} \\\\begin{Quotation} a multiline quote \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} a quote \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} a multiline quote \\\\end{Quotation}" `; exports[`blockquote with custom config 1`] = ` "\\\\begin{Foo} a quote \\\\end{Foo} \\\\begin{Foo} a multiline quote \\\\end{Foo} \\\\horizontalLine \\\\begin{Foo} a quote \\\\end{Foo} \\\\horizontalLine \\\\begin{Foo} a multiline quote \\\\end{Foo}" `; exports[`code 1`] = ` "\\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\begin{CodeBlock}{text} a code without lang \\\\end{CodeBlock}" `; exports[`code+caption 1`] = ` "\\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\captionof{listing}{With Source}" `; exports[`comments blocks 1`] = ` "Foo<--COMMENTS I am a comment COMMENTS-->bar " `; exports[`custom-blocks 1`] = ` "\\\\begin{Information} info \\\\end{Information} \\\\begin{Warning} warning \\\\end{Warning} \\\\begin{FooBar} secret \\\\end{FooBar} \\\\begin{FooBar} imbricated spoilers and another and that's it \\\\end{FooBar} \\\\begin{FooBar} imbricated spoilers second level second level too \\\\end{FooBar} \\\\begin{FooBar} imbricated spoilers second level with text in between second level too \\\\end{FooBar} \\\\begin{FooBar} imbricated spoilers \\\\begin{CodeBlock}{markdown} and here is some code \\\\end{CodeBlock} second level \\\\end{FooBar} \\\\begin{FooBar} do not over-flattenize expected to be flattened first level \\\\begin{Question} this remains a question \\\\end{Question} \\\\end{FooBar} \\\\begin{FooBar} flattenize children of children \\\\begin{Question} this remains a question but the content in it is flattened to the question \\\\end{Question} \\\\end{FooBar} \\\\begin{Question} question \\\\texttt{coded} \\\\end{Question} \\\\begin{Error} \\\\textbf{error} foo bar \\\\\\\\ baz \\\\end{Error} \\\\begin{Error}[{{a bad error}}] \\\\textbf{error} foo bar \\\\\\\\ baz \\\\end{Error} \\\\begin{Neutre}[{{\\\\textbf{title}}}] foo \\\\end{Neutre}" `; exports[`emoticon 1`] = ` "foo :p bar \\\\smiley{/path/to/smile.png} \\\\smiley{/path/to/wink.png}" `; exports[`figure+caption 1`] = ` "\\\\begin{Quotation}[With Source] a multiline quote \\\\end{Quotation} \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Nom & Age \\\\\\\\ Fred & 39 \\\\\\\\ Sam & 38 \\\\\\\\ Alice & 35 \\\\\\\\ Mathilde & 35 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Nom & Age \\\\\\\\ Fred & 39 \\\\\\\\ Sam & 38 \\\\\\\\ Alice & 35 \\\\\\\\ Mathilde & 35 \\\\\\\\ \\\\end{longtblr} \\\\captionof{table}{Coucou}" `; exports[`footnotes 1`] = ` "\\\\part{International Radiotelephony Spelling Alphabet\\\\textsuperscript{\\\\protect\\\\footnotemark[wiki]}} Here's the NATO phonetic alphabet\\\\textsuperscript{\\\\footnotemark[wiki]}: Alfa, Bravo, Charlie, Delta, Echo, Foxtrot, Golf, Hotel, India, Juliet, Kilo, Lima, Mike, November, Oscar, Papa, Quebec, Romeo, Sierra, Tango, Uniform, Victor\\\\textsuperscript{\\\\footnotemark[name]}\\\\textsuperscript{\\\\footnotemark[consecutive]}, Whiskey, X-ray, Yankee, and Zulu. And here's some more text. \\\\footnotetext[wiki]{Read more about it here.} \\\\footnotetext[wiki]{And here.} \\\\footnotetext[wiki2]{Here's another good article on the subject.} \\\\footnotetext[name]{A great first name.} \\\\footnotetext[consecutive]{I know.} The NATO phonetic alphabet\\\\textsuperscript{\\\\footnotemark[wi\\\\-ki]}. \\\\footnotetext[wi\\\\-ki]{Read more about it somewhere else.} This example checks that \\\\footnote[l17c26o614]{the generated} IDs do not overwrite the user's IDs\\\\textsuperscript{\\\\footnotemark[1]}. \\\\footnotetext[1]{Old behavior would, for \\"generated\\", generate a footnote with an ID set to \\\\texttt{1}, thus overwriting this footnote.} The NATO phonetic alphabet\\\\textsuperscript{\\\\footnotemark[wiki3]}. \\\\footnotetext[wiki3]{Read more about it somewhere else.} This is an example of an inline footnote.\\\\footnote[l25c42o918]{This is the \\\\textit{actual} footnote.} This one isn't even [defined]. \\\\textsuperscript{\\\\footnotemark[both]}[invalid], \\\\footnote[l29c19o1015]{this too}[]. \\\\begin{enumerate} \\\\item\\\\relax \\\\hyperref[bar]{foo} \\\\item\\\\relax \\\\textsuperscript{\\\\footnotemark[foo]}\\\\hyperref[bar]{bar} \\\\item\\\\relax [foo] \\\\item\\\\relax \\\\textsuperscript{\\\\footnotemark[foo]}\\\\textsuperscript{\\\\footnotemark[bar]} \\\\end{enumerate} A footnote\\\\textsuperscript{\\\\footnotemark[2]}. \\\\footnotetext[2]{Including \\\\footnote[l38c17o1125]{another \\\\textbf{footnote}}} A footnote\\\\textsuperscript{\\\\footnotemark[tostring]} and \\\\textsuperscript{\\\\footnotemark[__proto__]} and \\\\textsuperscript{\\\\footnotemark[constructor]}. \\\\footnotetext[tostring]{See \\\\texttt{Object.prototype.toString()}.} \\\\footnotetext[constructor]{See \\\\texttt{Object.prototype.valueOf()}.} \\\\footnotetext[__proto__]{See \\\\texttt{Object.prototype.\\\\_\\\\_proto\\\\_\\\\_()}.} foo\\\\textsuperscript{\\\\footnotemark[abc]} bar. foo\\\\textsuperscript{\\\\footnotemark[xyz]} bar \\\\footnotetext[abc]{Baz baz} \\\\footnotetext[xyz]{Baz} Lorem ipsum dolor sit amet\\\\textsuperscript{\\\\footnotemark[3]}. Nulla finibus\\\\textsuperscript{\\\\footnotemark[4]} neque et diam rhoncus convallis. \\\\footnotetext[3]{Consectetur \\\\textbf{adipiscing} elit. Praesent dictum purus ullamcorper ligula semper pellentesque\\\\textsuperscript{\\\\footnotemark[3]}. \\\\begin{itemize} \\\\item\\\\relax Containing a list. \\\\end{itemize}} \\\\footnotetext[4]{Nam dictum sapien nec sem ultrices fermentum. Nulla \\\\textbf{facilisi}. In et feugiat massa.} \\\\footnotetext[5]{Nunc dapibus ipsum ut mi \\\\textit{ultrices}, non euismod velit pretium.} Here is some text containing a footnote\\\\textsuperscript{\\\\footnotemark[somesamplefootnote]}. You can then continue your thought... \\\\footnotetext[somesamplefootnote]{Here is the text of the footnote itself.} Even go to a new \\\\hyperref[paragraph]{paragraph} and the footnotes will go to the bottom of the document\\\\textsuperscript{\\\\footnotemark[documentdetails]}. \\\\footnotetext[documentdetails]{Depending on the \\\\textbf{final} form of your document, of course. See the documentation and experiment. This footnote has a second \\\\hyperref[paragraph]{paragraph}.} \\\\footnote{\\\\label{paragraph}\\\\externalLink{http://example.com}{http://example.com}} \\\\part{my heading\\\\protect\\\\footnote[l76c13o2284]{ref def}} or \\\\part{my heading\\\\textsuperscript{\\\\protect\\\\footnotemark[ref]}} \\\\footnotetext[ref]{def} First\\\\footnote[l84c6o2338]{the generated} and then a manual numbered def\\\\textsuperscript{\\\\footnotemark[def]}. \\\\footnotetext[def]{hello} \\\\begin{itemize} \\\\item\\\\relax one\\\\footnote[l89c6o2415]{the first} \\\\item\\\\relax two\\\\textsuperscript{\\\\footnotemark[2nd]} \\\\item\\\\relax three\\\\textsuperscript{\\\\footnotemark[3rd]} \\\\item\\\\relax four\\\\footnote[l92c7o2460]{the last} \\\\end{itemize} \\\\footnotetext[2nd]{second} \\\\footnotetext[3rd]{third} This nested footnote would not work: \\\\hyperref[baz]{\\\\textsuperscript{\\\\footnotemark[foo2]}} \\\\footnote{\\\\label{bar}\\\\externalLink{https://bar.com}{https://bar.com}} \\\\footnote{\\\\label{baz}\\\\externalLink{https://baz.com}{https://baz.com}} \\\\footnotetext[foo2]{A footnote.} \\\\chapter{New list continuation} \\\\begin{enumerate} \\\\item\\\\relax \\\\textsuperscript{\\\\footnotemark[foo]} \\\\end{enumerate} \\\\footnotetext[foo]{bar baz.} \\\\part{mytitle A\\\\textsuperscript{\\\\protect\\\\footnotemark[footnoteref]}} \\\\footnotetext[footnoteref]{reference in title} \\\\part{mytitle B\\\\protect\\\\footnote[l117c12o2768]{footnoterawhead inner}} \\\\part{myti\\\\textit{tle C\\\\protect\\\\footnote[l119c13o2806]{foo inner}}} a paragraph\\\\footnote[l121c12o2832]{footnoteRawPar inner} \\\\inlineImage{undefined}(an image) Figure: with an \\\\footnote[l124c17o2894]{inline footnote} in caption " `; exports[`gridTable 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Sub & Headings & ABBR \\\\\\\\ \\\\SetCell[r=2]{l} cell \\\\endgraf spans \\\\endgraf rows & \\\\SetCell[c=2]{l} ABBR spanning & \\\\\\\\ & normal & cell \\\\\\\\ multi \\\\endgraf line \\\\endgraf \\\\endgraf cells \\\\endgraf too & \\\\SetCell[c=2]{l} cells can be \\\\endgraf \\\\textit{formatted} \\\\endgraf \\\\textbf{paragraphs} & \\\\\\\\ \\\\end{longtblr} \\\\captionof{table}{The new table ABBR [\\\\textasciicircum{}foot] with ||CTRL|| + ||S||} *[ABBR]: abbreviation [\\\\textasciicircum{}foot]: a foot \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} title & image \\\\\\\\ space & \\\\inlineImage{https://i.ytimg.com/vi/lt0WQ8JzLz4/maxresdefault.jpg} \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} title & code \\\\\\\\ inline & \\\\texttt{inline} br \\\\endgraf \\\\texttt{inline} \\\\\\\\ block & \\\\hyperref[appendix-1]{Code appendice 1} \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B & C \\\\\\\\ \\\\SetCell[r=2]{l} D & \\\\SetCell[c=2]{l} E & \\\\\\\\ & F & G \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B & C & D \\\\\\\\ \\\\SetCell[r=2]{l} D & \\\\SetCell[c=2]{l} E & & \\\\\\\\ & F & \\\\SetCell[c=2]{l} G & \\\\\\\\ a & b & \\\\SetCell[r=2]{l} c d \\\\endgraf \\\\endgraf g & \\\\\\\\ e & f & & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=2,row{1,2}={font=\\\\bfseries}} \\\\SetCell[c=2]{l} Table Headings & & Here \\\\\\\\ Sub & Headings & Too \\\\\\\\ \\\\SetCell[r=2]{l} cell \\\\endgraf spans \\\\endgraf rows & \\\\SetCell[c=2]{l} column[\\\\textasciicircum{}foot] & \\\\\\\\ & normal & cell \\\\\\\\ multi \\\\endgraf line \\\\endgraf \\\\endgraf cells \\\\endgraf too & \\\\SetCell[c=2]{l} cells can be \\\\endgraf \\\\textit{formatted} \\\\endgraf \\\\textbf{paragraphs} & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B & C \\\\\\\\ \\\\SetCell[r=2]{l} D & \\\\SetCell[c=2]{l} E & \\\\\\\\ & F & G \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[r=3]{l} A & \\\\SetCell[c=2]{l} B & \\\\\\\\ & C & D \\\\\\\\ & \\\\SetCell[c=2]{l} E & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} A & B & C & D \\\\\\\\ \\\\SetCell[c=2]{l} E & & \\\\SetCell[c=2]{l} F & \\\\\\\\ \\\\SetCell[c=4]{l} G & & & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[c=4]{l} A & & & \\\\\\\\ \\\\SetCell[c=2]{l} B & & \\\\SetCell[c=2]{l} C & \\\\\\\\ D & E & F & G \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} H & \\\\SetCell[c=16]{l} & & & & & & & & & & & & & & & & He \\\\\\\\ Li & Be & \\\\SetCell[r=2]{l} & & & & & & & & & & B & C & N & O & F & Ne \\\\\\\\ Na & Mg & & & & & & & & & & & Al & Si & P & S & Cl & Ar \\\\\\\\ K & Ca & Sc & Ti & V & Cr & Mn & Fe & Co & Ni & Cu & Zn & Ga & Ge & As & Se & Br & Kr \\\\\\\\ Rb & Sr & Y & Zr & Nb & Mo & Tc & Ru & Rh & Pd & Ag & Cd & In & Sn & Sb & Te & I & Xe \\\\\\\\ Cs & Ba & LAN & Hf & Ta & W & Re & Os & Ir & Pt & Au & Hg & Tl & Pb & Bi & Po & At & Rn \\\\\\\\ Fr & Ra & ACT & \\\\SetCell[c=15]{l} & & & & & & & & & & & & & & \\\\\\\\ \\\\SetCell[c=18]{l} & & & & & & & & & & & & & & & & & \\\\\\\\ \\\\SetCell[c=3]{l} Lanthanide & & & La & Ce & Pr & Nd & Pm & Sm & Eu & Gd & Tb & Dy & Ho & Er & Tm & Yb & Lu \\\\\\\\ \\\\SetCell[c=3]{l} Actinide & & & Ac & Th & Pa & U & Np & Pu & Am & Cm & Bk & Cf & Es & Fm & Md & No & Lw \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1]}} A \\\\\\\\ \\\\end{longtblr} Text at the end \\\\begin{longtblr}{colspec={X[-1]}} A \\\\\\\\ \\\\end{longtblr} Text at the \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]}} a & \\\\SetCell[r=2]{l} & & \\\\\\\\ b & & & c \\\\\\\\ \\\\end{longtblr} \\\\captionof{table}{multirow} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} \\\\SetCell[c=2]{l} span & & header & \\\\SetCell[c=2]{l} span & \\\\\\\\ elem & \\\\SetCell[c=2]{l} span middle & & \\\\SetCell[c=2]{l} & \\\\\\\\ \\\\end{longtblr} \\\\begin{appendices} \\\\part{\\\\label{appendix-1}Annexes 1} \\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\end{appendices}" `; exports[`heading 1`] = ` "\\\\part{first level title} \\\\chapter{second level title} \\\\section{third level title} \\\\subsection{fourth level title} \\\\subsubsection{fifth level title} \\\\paragraph{sixth level title} \\\\part{a \\\\textbackslash{} b} \\\\part{a \\\\} b} \\\\part{a \\\\} \\\\textbackslash{} b} \\\\part{a \\\\} b} \\\\part{\\\\includegraphics{https://www.johnfdoherty.com/wp-content/uploads/2011/07/Titles1.jpg}} \\\\part{a title with \\\\includegraphics{https://www.johnfdoherty.com/wp-content/uploads/2011/07/Titles1.jpg} and text} " `; exports[`heading with custom config 1`] = ` "\\\\LevelOneTitle{first level title} \\\\LevelTwoTitle{second level title} \\\\LevelThreeTitle{third level title} \\\\LevelFourTitle{fourth level title} \\\\LevelFiveTitle{fifth level title} \\\\LevelSixTitle{sixth level title} \\\\LevelOneTitle{a \\\\textbackslash{} b} \\\\LevelOneTitle{a \\\\} b} \\\\LevelOneTitle{a \\\\} \\\\textbackslash{} b} \\\\LevelOneTitle{a \\\\} b} \\\\LevelOneTitle{\\\\inlineImage{https://www.johnfdoherty.com/wp-content/uploads/2011/07/Titles1.jpg}} \\\\LevelOneTitle{a title with \\\\inlineImage{https://www.johnfdoherty.com/wp-content/uploads/2011/07/Titles1.jpg} and text} " `; exports[`html nodes 1`] = ` "\\\\part{foo} \\\\textbf{something else} " `; exports[`inline-code 1`] = ` "a paragraph \\\\texttt{with inline code}. \\\\texttt{a multiline inlinecode} \\\\texttt{a code with \\\\textbackslash{}} \\\\texttt{a \\\\textbackslash{}text in \\\\textbackslash{}LaTeX \\\\textbackslash{}\\\\textbackslash{}}" `; exports[`link 1`] = ` "\\\\externalLink{an external link}{https://wikipedia.com} \\\\externalLink{an internal link}{/forums} \\\\externalLink{an \\\\texttt{external} link}{https://wikipedia.com}" `; exports[`link with special characters 1`] = `"\\\\externalLink{foo}{http://example.com?a=b\\\\%c\\\\textasciicircum{}\\\\{\\\\}\\\\#foo}"`; exports[`link-prepend 1`] = ` "\\\\externalLink{an external link}{https://wikipedia.com} \\\\externalLink{an internal link}{http://zestedesavoir.com/forums} \\\\externalLink{an \\\\texttt{external} link}{https://wikipedia.com}" `; exports[`list 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax an \\\\item\\\\relax \\\\textbf{unordered} \\\\item\\\\relax list \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax an \\\\item\\\\relax \\\\texttt{ordered} \\\\item\\\\relax list \\\\end{enumerate}" `; exports[`math 1`] = ` "A sentence ($S$) with \\\\textit{italic} and inline math ($C_L$) and $b$ another. \\\\[ L = \\\\frac{1}{2} \\\\rho v^2 S C_L \\\\] hehe We also want escaping like so: $$ or like so: $\\\\frac{1}{2}$ " `; exports[`math-extra-command 1`] = ` "\\\\[ \\\\nabla^2 E(\\\\vec x) + [n(\\\\vec{x})\\\\,k]^2\\\\,E(\\\\vec x) = -s(\\\\vec x), \\\\] " `; exports[`math-left-right 1`] = ` "\\\\[ \\\\boxed{\\\\nabla^2 E(\\\\vec x) + \\\\left\\\\{[n(\\\\vec{x})\\\\,k]^2-i\\\\,k\\\\,\\\\sigma(\\\\vec x)\\\\right\\\\}\\\\,E(\\\\vec x)} \\\\] " `; exports[`mix-1 1`] = ` "(for convenience, are replaced with simple single spaces in the tests) \\\\part{first 1} foo \\\\\\\\ bar Disrupt \\\\sout{asymmetrical} unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue \\\\textbf{bottle craft} beer hexagon artisan. Chia gochujang crucifix, \\\\textsuperscript{ready} \\\\textsuperscript{made} hammock \\\\textit{blog succulents} sriracha raw denim scenester cray typewriter fashion axe \\\\textsubscript{art} \\\\textit{party}. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street \\\\textsubscript{art} typewriter. \\\\chapter{\\\\textit{second} 1} \\\\textbf{Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street \\\\textsubscript{art} synth. Godard synth truffaut selfies, vape fanny \\\\sout{pack} subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial \\\\textit{YOLO}. Williamsburg salvia photo booth \\\\textsuperscript{readymade} listicle man braid.} \\\\horizontalLine \\\\begin{Quotation}[Guru] Marfa pickled helvetica put a bird on it hot chicken williamsburg. Edison bulb asymmetrical \\\\texttt{keffiyeh} schlitz iceland put a bird on it hoodie affogato. Tofu tote bag distillery viral knausgaard, health goth asymmetrical. \\\\end{Quotation} Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR\\\\&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. \\\\section{\\\\sout{third} 1} Art party keytar godard iceland neutra cronut. Austin \\\\textsuperscript{readymade} semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. \\\\texttt{foo bar} \\\\begin{Quotation} \\\\texttt{fo\\\\textbackslash{}o bar} \\\\end{Quotation} Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. \\\\section{third 2} \\\\subsection{\\\\sout{fo}u\\\\textbf{r}t\\\\textit{h} \\\\textit{1}} Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. \\\\subsubsection{fifth 1} Banjo bespoke subway tile, street \\\\textsubscript{a\\\\textit{r}}\\\\textsuperscript{t} drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR\\\\&B, \\\\textsubscript{art} party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. \\\\section{third 3} Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR\\\\&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. \\\\subsection{fourth 2} Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. \\\\subsection{fourth 3} Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. \\\\subsubsection{fifth 2} Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. \\\\part{first 2} \\\\chapter{second 2} \\\\subsubsection{fifth 3} Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR\\\\&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh." `; exports[`mix-2 1`] = ` "(for convenience, are replaced with simple single spaces in the tests) \\\\part{first 1} \\\\begin{Quotation}[Quotation Source] Code inside quote \\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\captionof{listing}{code source} \\\\end{Quotation} \\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\captionof{listing}{First \\\\keys{CTRL}+\\\\keys{S}} Code: Second \\\\begin{CodeBlock}{python} print('code without caption') \\\\end{CodeBlock}" `; exports[`mix-3 1`] = ` "\\\\part{right-aligned list} {\\\\raggedleft \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} } \\\\part{centered list} {\\\\centering \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} } \\\\part{left list} \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize}" `; exports[`mix-4 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Sub & Headings & \\\\abbr{ABBR}{abbreviation} \\\\\\\\ \\\\SetCell[r=2]{l} cell \\\\endgraf spans \\\\endgraf rows & \\\\SetCell[c=2]{l} column spanning & \\\\\\\\ & normal & cell \\\\\\\\ multi \\\\endgraf line \\\\endgraf \\\\endgraf cells \\\\endgraf too & \\\\SetCell[c=2]{l} cells can be \\\\endgraf \\\\textit{formatted} \\\\endgraf \\\\textbf{paragraphs} & \\\\\\\\ \\\\end{longtblr} \\\\captionof{table}{The new table \\\\abbr{ABBR}{abbreviation} \\\\textsuperscript{\\\\protect\\\\footnotemark[foot]} with \\\\keys{CTRL} + \\\\keys{S}} \\\\footnotetext[foot]{a foot} \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} title & image \\\\\\\\ space & \\\\inlineImage{https://i.ytimg.com/vi/lt0WQ8JzLz4/maxresdefault.jpg} \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} title & code \\\\\\\\ inline & \\\\texttt{inline} br \\\\endgraf \\\\texttt{inline} \\\\\\\\ block & \\\\hyperref[appendix-1]{Code appendice 1} \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B & C \\\\\\\\ \\\\SetCell[r=2]{l} D & \\\\SetCell[c=2]{l} E & \\\\\\\\ & F & G \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B & C & D \\\\\\\\ \\\\SetCell[r=2]{l} D & \\\\SetCell[c=2]{l} E & & \\\\\\\\ & F & \\\\SetCell[c=2]{l} G & \\\\\\\\ a & b & \\\\SetCell[r=2]{l} c d \\\\endgraf \\\\endgraf g & \\\\\\\\ e & f & & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=2,row{1,2}={font=\\\\bfseries}} \\\\SetCell[c=2]{l} Table Headings & & Here \\\\\\\\ Sub & Headings & Too \\\\\\\\ \\\\SetCell[r=2]{l} cell \\\\endgraf spans \\\\endgraf rows & \\\\SetCell[c=2]{l} column spanning & \\\\\\\\ & normal & cell \\\\\\\\ multi \\\\endgraf line \\\\endgraf \\\\endgraf cells \\\\endgraf too & \\\\SetCell[c=2]{l} cells can be \\\\endgraf \\\\textit{formatted} \\\\endgraf \\\\textbf{paragraphs} & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B & C \\\\\\\\ \\\\SetCell[r=2]{l} D & \\\\SetCell[c=2]{l} E & \\\\\\\\ & F & G \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[r=3]{l} A & \\\\SetCell[c=2]{l} B & \\\\\\\\ & C & D \\\\\\\\ & \\\\SetCell[c=2]{l} E & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} A & B & C & D \\\\\\\\ \\\\SetCell[c=2]{l} E & & \\\\SetCell[c=2]{l} F & \\\\\\\\ \\\\SetCell[c=4]{l} G & & & \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[c=4]{l} A & & & \\\\\\\\ \\\\SetCell[c=2]{l} B & & \\\\SetCell[c=2]{l} C & \\\\\\\\ D & E & F & G \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} H & \\\\SetCell[c=16]{l} & & & & & & & & & & & & & & & & He \\\\\\\\ Li & Be & \\\\SetCell[r=2]{l} & & & & & & & & & & B & C & N & O & F & Ne \\\\\\\\ Na & Mg & & & & & & & & & & & Al & Si & P & S & Cl & Ar \\\\\\\\ K & Ca & Sc & Ti & V & Cr & Mn & Fe & Co & Ni & Cu & Zn & Ga & Ge & As & Se & Br & Kr \\\\\\\\ Rb & Sr & Y & Zr & Nb & Mo & Tc & Ru & Rh & Pd & Ag & Cd & In & Sn & Sb & Te & I & Xe \\\\\\\\ Cs & Ba & LAN & Hf & Ta & W & Re & Os & Ir & Pt & Au & Hg & Tl & Pb & Bi & Po & At & Rn \\\\\\\\ Fr & Ra & ACT & \\\\SetCell[c=15]{l} & & & & & & & & & & & & & & \\\\\\\\ \\\\SetCell[c=18]{l} & & & & & & & & & & & & & & & & & \\\\\\\\ \\\\SetCell[c=3]{l} Lanthanide & & & La & Ce & Pr & Nd & Pm & Sm & Eu & Gd & Tb & Dy & Ho & Er & Tm & Yb & Lu \\\\\\\\ \\\\SetCell[c=3]{l} Actinide & & & Ac & Th & Pa & U & Np & Pu & Am & Cm & Bk & Cf & Es & Fm & Md & No & Lw \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1]}} A \\\\\\\\ \\\\end{longtblr} Text at the end \\\\begin{longtblr}{colspec={X[-1]}} A \\\\\\\\ \\\\end{longtblr} Text at the \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]}} a & \\\\SetCell[r=2]{l} & & \\\\\\\\ b & & & c \\\\\\\\ \\\\end{longtblr} \\\\captionof{table}{multirow} \\\\begin{appendices} \\\\part{\\\\label{appendix-1}Annexes 1} \\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\end{appendices}" `; exports[`mix-5 1`] = ` "\\\\image{http://www.numerama.com/content/uploads/2016/07/espace.jpg}[espace\\\\textsuperscript{\\\\protect\\\\footnotemark[node]}] \\\\footnotetext[node]{Two things are infinite: the universe and human stupidity.}" `; exports[`mix-6 1`] = ` "\\\\image{http://img.youtube.com/vi/8TQIvdFl4aU/0.jpg}[\\\\externalLink{https://www.youtube.com/embed/8TQIvdFl4aU}{https://www.youtube.com/embed/8TQIvdFl4aU}] Video: a caption \\\\image{http://img.youtube.com/vi/8TQIvdFl4aU/0.jpg}[\\\\externalLink{https://www.youtube.com/embed/8TQIvdFl4aU}{https://www.youtube.com/embed/8TQIvdFl4aU}] no caption" `; exports[`mix-7 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]}} cell & \\\\texttt{code with line break} \\\\\\\\ \\\\SetCell[c=2]{l} \\\\hyperref[appendix-1]{Code appendice 1} & \\\\\\\\ \\\\end{longtblr} \\\\begin{appendices} \\\\part{\\\\label{appendix-1}Annexes 1} \\\\begin{CodeBlock}{text} line 1 line2 \\\\end{CodeBlock} \\\\end{appendices}" `; exports[`mix-math-escape 1`] = ` "$\\\\Delta \\\\varphi = 2 \\\\pi f \\\\Delta t_{orange/bleu} = -\\\\frac{\\\\pi}{4}$ \\\\[ \\\\begin{array}{l r} & 45 \\\\\\\\ + & 218 \\\\\\\\ + & 800 \\\\\\\\ \\\\hline = & 1063 \\\\end{array} \\\\] \\\\[ \\\\left( \\\\frac{(4 + 5)\\\\times(2 + 3) + 6}{8 + 7} \\\\right) ^9 \\\\] $42\\\\%$ This should be escaped: $\\\\pink{ {I love \\\\LaTeX} {ls / > outputrce} {outputrce}}$" `; exports[`paragraph 1`] = ` "\\\\part{first 1} Disrupt asymmetrical unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue bottle craft beer hexagon artisan. Chia gochujang crucifix, readymade hammock blog succulents sriracha raw denim scenester cray typewriter fashion axe art party. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street art typewriter. \\\\chapter{second 1} Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street art synth. Godard synth truffaut selfies, vape fanny pack subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial YOLO. Williamsburg salvia photo booth readymade listicle man braid. Marfa pickled helvetica put a bird on it hot chicken williamsburg. Edison bulb asymmetrical keffiyeh schlitz iceland put a bird on it hoodie affogato. Tofu tote bag distillery viral knausgaard, health goth asymmetrical. Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR\\\\&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. \\\\section{third 1} Art party keytar godard iceland neutra cronut. Austin readymade semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. \\\\section{third 2} \\\\subsection{fourth 1} Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. \\\\subsubsection{fifth 1} Banjo bespoke subway tile, street art drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR\\\\&B, art party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. \\\\section{third 3} Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR\\\\&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. \\\\subsection{fourth 2} Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. \\\\subsection{fourth 3} Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. \\\\subsubsection{fifth 2} Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. \\\\part{first 2} \\\\chapter{second 2} \\\\subsubsection{fifth 3} Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR\\\\&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh." `; exports[`ping 1`] = ` "Hello \\\\ping{you} and \\\\ping{you_too}, and \\\\ping{also you} " `; exports[`regression: code block without language 1`] = ` "\\\\begin{CodeBlock}{text} a \\\\end{CodeBlock} " `; exports[`table 1`] = ` "\\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} 1 & 2 \\\\\\\\ 1 & 2 \\\\\\\\ 1 & 2 \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{longtblr} \\\\begin{longtblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} 1 & 2 & 3 \\\\\\\\ 1 & 2 \\\\\\\\ 1 & 2  & 3 \\\\\\\\ 1 & 2  & 3 \\\\\\\\ \\\\end{longtblr} Another test for inline image with overridden configuration: \\\\begin{longtblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} c1 & c2 \\\\\\\\ c3 & \\\\inlineImage{https://zestedesavoir.com/media/galleries/426/56dc4a1e-416b-4a9d-830d-95b45d58a17a.png} \\\\\\\\ \\\\end{longtblr}" `; ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/abbr.fixture.md ================================================ HELO to you Can I INJECT something in \LaTeX ? *[HELO]: In SMTP, the client initiates the dialog with a HELO command identifying itself *[INJECT]: discard}\LaTeX\abbr{INJECTED}{discard ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/blockquote.fixture.md ================================================ > a quote > a > multiline > quote --- > a quote --- > a > multiline > quote ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/blocks.fixture.md ================================================ [[i]] | info [[a]] | warning [[s]] | secret [[s]] | imbricated spoilers | [[s]] | | and another | | [[s]] | | | and that's it [[s]] | imbricated spoilers | [[s]] | | second level | [[s]] | | second level too [[s]] | imbricated spoilers | [[s]] | | second level | | with text in between | | [[s]] | | second level too [[s]] | imbricated spoilers | | ```markdown | and here is some code | ``` | | [[s]] | | second level [[s]] | do not over-flattenize | [[s]] | | expected to be flattened | | first level | | [[q]] | | this remains a question [[s]] | flattenize children of children | [[q]] | | this remains a question | | [[s]] | | | but the content in it is flattened to the question [[q]] | question `coded` [[e]] | **error** | foo bar·· | baz [[e | a bad error]] | **error** | foo bar·· | baz [[neutre | **title**]] | foo ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/code.fixture.md ================================================ ```python print('bla') ``` ``` a code without lang ``` ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/emoticon.fixture.md ================================================ foo :p bar :) ;) ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/figure-code.fixture.md ================================================ ```python print('bla') ``` Code: With Source ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/figure.fixture.md ================================================ > a > multiline > quote Source: With Source Nom | Age ------|----- Fred | 39 Sam | 38 Alice | 35 Mathilde | 35 Nom | Age ------|----- Fred | 39 Sam | 38 Alice | 35 Mathilde | 35 Table: Coucou ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/footnote.fixture.md ================================================ # International Radiotelephony Spelling Alphabet[^wiki] Here's the NATO phonetic alphabet[^wiki]: Alfa, Bravo, Charlie, Delta, Echo, Foxtrot, Golf, Hotel, India, Juliet, Kilo, Lima, Mike, November, Oscar, Papa, Quebec, Romeo, Sierra, Tango, Uniform, Victor[^name][^consecutive], Whiskey, X-ray, Yankee, and Zulu. And here's some more text. [^wiki]: Read more about it here. [^wiki]: And here. [^wiki2]: Here's another good article on the subject. [^name]: A great first name. [^consecutive]: I know. The NATO phonetic alphabet[^wi\-ki]. [^wi\-ki]: Read more about it somewhere else. This example checks that ^[the generated] IDs do not overwrite the user's IDs[^1]. [^1]: Old behavior would, for "generated", generate a footnote with an ID set to `1`, thus overwriting this footnote. The NATO phonetic alphabet[^wiki3]. [^wiki3]: Read more about it somewhere else. This is an example of an inline footnote.^[This is the _actual_ footnote.] This one isn't even [defined][^foofoofoo]. [^both][invalid], ^[this too][]. 1. [foo][bar] 2. [^foo][bar] 3. [foo][^bar] 4. [^foo][^bar] A footnote[^2]. [^2]: Including ^[another **footnote**] A footnote[^toString] and [^__proto__] and [^constructor]. [^toString]: See `Object.prototype.toString()`. [^constructor]: See `Object.prototype.valueOf()`. [^__proto__]: See `Object.prototype.__proto__()`. foo[^abc] bar. foo[^xyz] bar [^abc]: Baz baz [^xyz]: Baz Lorem ipsum dolor sit amet[^3]. Nulla finibus[^4] neque et diam rhoncus convallis. [^3]: Consectetur **adipiscing** elit. Praesent dictum purus ullamcorper ligula semper pellentesque[^3]. - Containing a list. [^4]: Nam dictum sapien nec sem ultrices fermentum. Nulla **facilisi**. In et feugiat massa. [^5]: Nunc dapibus ipsum ut mi _ultrices_, non euismod velit pretium. Here is some text containing a footnote[^somesamplefootnote]. You can then continue your thought... [^somesamplefootnote]: Here is the text of the footnote itself. Even go to a new [paragraph] and the footnotes will go to the bottom of the document[^documentdetails]. [^documentdetails]: Depending on the **final** form of your document, of course. See the documentation and experiment. This footnote has a second [paragraph]. [paragraph]: http://example.com # my heading^[ref def] or # my heading[^ref] [^ref]: def First^[the generated] and then a manual numbered def[^def]. [^def]: hello * one^[the first] * two[^2nd] * three[^3rd] * four^[the last] [^2nd]: second [^3rd]: third This nested footnote would not work: [[^foo2]][baz] [bar]: https://bar.com "bar" [baz]: https://baz.com "baz" [^foo2]: A footnote. ## New list continuation 1. [^foo] [^foo]: bar baz. # mytitle A[^footnoteRef] [^footnoteRef]: reference in title # mytitle B^[footnoterawhead inner] # myti*tle C^[foo inner]* a paragraph^[footnoteRawPar inner] ![img.png](an image) Figure: with an ^[inline footnote] in caption ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/gridTable.fixture.md ================================================ +-------+----------+------+ | Sub | Headings | ABBR | +=======+==========+======+ | cell | ABBR spanning | | spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ Table: The new table ABBR [^foot] with ||CTRL|| + ||S|| *[ABBR]: abbreviation [^foot]: a foot +-----------+---------------------------------------------------------------+ | title | image | +===========+===============================================================+ | space | ![space](https://i.ytimg.com/vi/lt0WQ8JzLz4/maxresdefault.jpg)| +-----------+---------------------------------------------------------------+ +-----------+--------------------+ | title | code | +===========+====================+ | inline | `inline` br | | | `inline` | +-----------+--------------------+ | block | ```python | | | print('bla') | | | ``` | +-----------+--------------------+ +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ +---+---+---+---+ | A | B | C | D | +===+===+===+===+ | D | E | | | +---+---+---+ | | F | G | +---+---+---+---+ | a | b | c d | +---+---+ | | e | f | g | +---+---+---+---+ +-------+----------+------+ | Table Headings | Here | +-------+----------+------+ | Sub | Headings | Too | +=======+==========+======+ | cell | column[^foot] | + spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ +---+-------+ | A | B | | +---+---+ | | C | D | | +---+---+ | | E | +---+-------+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | E | F | +-------+-------+ | G | +---------------+ +---------------+ | A | +-------+-------+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+---------------------------------------------------------------+---+ | H | |He | +---+---+---------------------------------------+---+---+---+---+---+---+ |Li |Be | | B | C | N | O | F |Ne | +---+---+ +---+---+---+---+---+---+ |Na |Mg | |Al |Si | P | S |Cl |Ar | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | K |Ca |Sc |Ti | V |Cr |Mn |Fe |Co |Ni |Cu |Zn |Ga |Ge |As |Se |Br |Kr | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Rb |Sr | Y |Zr |Nb |Mo |Tc |Ru |Rh |Pd |Ag |Cd |In |Sn |Sb |Te | I |Xe | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Cs |Ba |LAN|Hf |Ta | W |Re |Os |Ir |Pt |Au |Hg |Tl |Pb |Bi |Po |At |Rn | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Fr |Ra |ACT| | +---+---+---+-----------------------------------------------------------+ | | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Lanthanide |La |Ce |Pr |Nd |Pm |Sm |Eu |Gd |Tb |Dy |Ho |Er |Tm |Yb |Lu | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Actinide |Ac |Th |Pa | U |Np |Pu |Am |Cm |Bk |Cf |Es |Fm |Md |No |Lw | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---------+ | A | +---------+ Text at the end +---------+ | A | +---------+ Text at the +----+------+------+------+ | a | | | +----+ +------+ | b | | c | +----+-------------+------+ Table: multirow +------+--------+------+------+------+ |span |header| span | +======+========+======+======+======+ |elem |span middle | | +------+--------+------+------+------+ ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/heading.fixture.md ================================================ # first level title ## second level title ### third level title #### fourth level title ##### fifth level title ###### sixth level title # a \ b # a } b # a } \ b # a \} b # ![searching for bugs](https://www.johnfdoherty.com/wp-content/uploads/2011/07/Titles1.jpg) # a title with ![image](https://www.johnfdoherty.com/wp-content/uploads/2011/07/Titles1.jpg) and text ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/inline-code.fixture.md ================================================ a paragraph `with inline code`. `a multiline inlinecode` `a code with \` `a \text in \LaTeX \\` ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/link-prepend.fixture.md ================================================ [an external link](https://wikipedia.com) [an internal link](/forums) [an `external` link](https://wikipedia.com) ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/link.fixture.md ================================================ [an external link](https://wikipedia.com) [an internal link](/forums) [an `external` link](https://wikipedia.com) ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/list.fixture.md ================================================ - an - **unordered** - list 1. an 1. `ordered` 1. list ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-1.fixture.md ================================================ (for convenience, · are replaced with simple single spaces in the tests) # first 1 foo·· bar Disrupt ~~asymmetrical~~ unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue **bottle craft** beer hexagon artisan. Chia gochujang crucifix, ^ready^ ^made^ hammock _blog succulents_ sriracha raw denim scenester cray typewriter fashion axe ~art~ *party*. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street ~art~ typewriter. ## *second* 1 **Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street ~art~ synth. Godard synth truffaut selfies, vape fanny ~~pack~~ subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial _YOLO_. Williamsburg salvia photo booth ^readymade^ listicle man braid.** --- > Marfa pickled helvetica put a bird on it hot chicken williamsburg. > Edison bulb asymmetrical `keffiyeh` schlitz iceland put a bird on it hoodie affogato. > > Tofu tote bag distillery viral knausgaard, health goth asymmetrical. Source: Guru Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. ### ~~third~~ 1 Art party keytar godard iceland neutra cronut. Austin ^readymade^ semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. `foo bar` > `fo\o > bar` Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. ### third 2 #### ~~fo~~u**r**t*h* _1_ Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. ##### fifth 1 Banjo bespoke subway tile, street ~a*r*~^t^ drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR&B, ~art~ party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. ### third 3 Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. #### fourth 2 Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. #### fourth 3 Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. ##### fifth 2 Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. # first 2 ## second 2 ##### fifth 3 Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh. ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-2.fixture.md ================================================ (for convenience, · are replaced with simple single spaces in the tests) # first 1 > Code inside quote > ```python > print('bla') > ``` > Code: code source Source: Quotation Source ```python print('bla') ``` Code: First ||CTRL||+||S|| Code: Second ```python print('code without caption') ``` ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-3.fixture.md ================================================ # right-aligned list -> - item - item -> # centered list -> - item - item - item <- # left list <- - item - item - item <- ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-4.fixture.md ================================================ +-------+----------+------+ | Sub | Headings | ABBR | +=======+==========+======+ | cell | column spanning | | spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ Table: The new table ABBR [^foot] with ||CTRL|| + ||S|| *[ABBR]: abbreviation [^foot]: a foot +-----------+---------------------------------------------------------------+ | title | image | +===========+===============================================================+ | space | ![space](https://i.ytimg.com/vi/lt0WQ8JzLz4/maxresdefault.jpg)| +-----------+---------------------------------------------------------------+ +-----------+--------------------+ | title | code | +===========+====================+ | inline | `inline` br | | | `inline` | +-----------+--------------------+ | block | ```python | | | print('bla') | | | ``` | +-----------+--------------------+ +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ +---+---+---+---+ | A | B | C | D | +===+===+===+===+ | D | E | | | +---+---+---+ | | F | G | +---+---+---+---+ | a | b | c d | +---+---+ | | e | f | g | +---+---+---+---+ +-------+----------+------+ | Table Headings | Here | +-------+----------+------+ | Sub | Headings | Too | +=======+==========+======+ | cell | column spanning | + spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ +---+-------+ | A | B | | +---+---+ | | C | D | | +---+---+ | | E | +---+-------+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | E | F | +-------+-------+ | G | +---------------+ +---------------+ | A | +-------+-------+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+---------------------------------------------------------------+---+ | H | |He | +---+---+---------------------------------------+---+---+---+---+---+---+ |Li |Be | | B | C | N | O | F |Ne | +---+---+ +---+---+---+---+---+---+ |Na |Mg | |Al |Si | P | S |Cl |Ar | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | K |Ca |Sc |Ti | V |Cr |Mn |Fe |Co |Ni |Cu |Zn |Ga |Ge |As |Se |Br |Kr | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Rb |Sr | Y |Zr |Nb |Mo |Tc |Ru |Rh |Pd |Ag |Cd |In |Sn |Sb |Te | I |Xe | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Cs |Ba |LAN|Hf |Ta | W |Re |Os |Ir |Pt |Au |Hg |Tl |Pb |Bi |Po |At |Rn | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Fr |Ra |ACT| | +---+---+---+-----------------------------------------------------------+ | | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Lanthanide |La |Ce |Pr |Nd |Pm |Sm |Eu |Gd |Tb |Dy |Ho |Er |Tm |Yb |Lu | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Actinide |Ac |Th |Pa | U |Np |Pu |Am |Cm |Bk |Cf |Es |Fm |Md |No |Lw | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---------+ | A | +---------+ Text at the end +---------+ | A | +---------+ Text at the +----+------+------+------+ | a | | | +----+ +------+ | b | | c | +----+-------------+------+ Table: multirow ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-5.fixture.md ================================================ ![](http://www.numerama.com/content/uploads/2016/07/espace.jpg) Figure: espace[^node] [^node]: Two things are infinite: the universe and human stupidity. ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-6.fixture.md ================================================ !(https://www.youtube.com/watch?v=8TQIvdFl4aU) Video: a caption !(https://www.youtube.com/watch?v=8TQIvdFl4aU) no caption ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-7.fixture.md ================================================ +------+----------------------+ | cell | `code | | | with line break` | +------+----------------------+ | ```text | | line 1 | | line2 | |``` | +------+----------------------+ ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/mix-math-escape.fixture.md ================================================ $$\Delta \varphi = 2 \pi f \Delta t_{orange/bleu} = -\frac{\pi}{4}$$ $$ \begin{array}{l r} & 45 \\ + & 218 \\ + & 800 \\ \hline = & 1063 \end{array} $$ $$ \left( \frac{(4 + 5)\times(2 + 3) + 6}{8 + 7} \right) ^9 $$ $$42\%$$ This should be escaped: $\pink{ \ext{I love \LaTeX} \immediate\write18{ls / > outputrce} \input{outputrce}}$ ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/paragraph.fixture.md ================================================ # first 1 Disrupt asymmetrical unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue bottle craft beer hexagon artisan. Chia gochujang crucifix, readymade hammock blog succulents sriracha raw denim scenester cray typewriter fashion axe art party. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street art typewriter. ## second 1 Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street art synth. Godard synth truffaut selfies, vape fanny pack subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial YOLO. Williamsburg salvia photo booth readymade listicle man braid. Marfa pickled helvetica put a bird on it hot chicken williamsburg. Edison bulb asymmetrical keffiyeh schlitz iceland put a bird on it hoodie affogato. Tofu tote bag distillery viral knausgaard, health goth asymmetrical. Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. ### third 1 Art party keytar godard iceland neutra cronut. Austin readymade semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. ### third 2 #### fourth 1 Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. ##### fifth 1 Banjo bespoke subway tile, street art drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR&B, art party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. ### third 3 Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. #### fourth 2 Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. #### fourth 3 Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. ##### fifth 2 Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. # first 2 ## second 2 ##### fifth 3 Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh. ================================================ FILE: packages/rebber-plugins/__tests__/fixtures/table.fixture.md ================================================ 1 | 2 --|-- 1|2 1|2 1|2 1 | 2 | 3 --|---|-- 1 | 2 1 | 2 | 3 1 | 2 | 3 Another test for inline image with overridden configuration: c1 | c2 ---|-- c3 | ![image](https://zestedesavoir.com/media/galleries/426/56dc4a1e-416b-4a9d-830d-95b45d58a17a.png) ================================================ FILE: packages/rebber-plugins/__tests__/rebber.test.js ================================================ import {readdirSync as directory, readFileSync as file, lstatSync as stat} from 'fs' import {join} from 'path' import unified from 'unified' import reParse from 'remark-parse' import footnotes from 'remark-footnotes' import remarkMath from 'remark-math' import remarkPing from 'remark-ping' import rebber from 'rebber' import dedent from 'dedent' const base = join(__dirname, 'fixtures') const fixtures = directory(base).reduce((tests, contents) => { const parts = contents.split('.') if (!tests[parts[0]]) { tests[parts[0]] = {} } if (stat(join(base, contents)).isFile()) { tests[parts[0]] = file(join(base, contents), 'utf-8') } return tests }, {}) const emoticonsConfig = { emoticons: { ':)': '/path/to/smile.png', ';)': '/path/to/wink.png', }, } const mathEscape = require('../src/preprocessors/mathEscape') const footnoteProtect = require('../src/preprocessors/footnoteProtect') const integrationConfig = { preprocessors: { tableCell: require('../src/preprocessors/codeVisitor'), iframe: require('../src/preprocessors/iframe'), spoilerFlatten: require('../src/preprocessors/spoilerFlatten')([ 'sCustomBlock', 'secretCustomBlock', ]), heading: footnoteProtect, figcaption: footnoteProtect, inlineMath: mathEscape, math: mathEscape, }, overrides: { abbr: require('../src/type/abbr'), centerAligned: require('../src/type/align'), emoticon: require('../src/type/emoticon'), errorCustomBlock: require('../src/type/customBlocks'), figure: require('../src/type/figure'), footnote: require('../src/type/footnote'), footnoteDefinition: require('../src/type/footnoteDefinition'), footnoteReference: require('../src/type/footnoteReference'), gridTable: require('../src/type/gridTable'), informationCustomBlock: require('../src/type/customBlocks'), inlineMath: require('../src/type/math'), kbd: require('../src/type/kbd'), leftAligned: require('../src/type/align'), math: require('../src/type/math'), neutreCustomBlock: require('../src/type/customBlocks'), ping: require('../src/type/ping'), questionCustomBlock: require('../src/type/customBlocks'), rightAligned: require('../src/type/align'), secretCustomBlock: require('../src/type/customBlocks'), sub: require('../src/type/sub'), sup: require('../src/type/sup'), tableHeader: require('../src/type/tableHeader'), warningCustomBlock: require('../src/type/customBlocks'), }, emoticons: emoticonsConfig, codeAppendiceTitle: 'Annexes', customBlocks: { map: { secret: 'FooBar', }, }, image: { inlineImage: (node) => `\\inlineImage{${node.url}}`, image: (node) => `\\image{${node.url}}`, }, figure: { image: (_1, _2, caption, extra) => `\\image{${extra.url}}${caption ? `[${caption}]` : ''}\n`, }, } integrationConfig.overrides.eCustomBlock = (ctx, node) => { node.type = 'errorCustomBlock' return integrationConfig.overrides.warningCustomBlock(ctx, node) } integrationConfig.overrides.iCustomBlock = (ctx, node) => { node.type = 'informationCustomBlock' return integrationConfig.overrides.informationCustomBlock(ctx, node) } integrationConfig.overrides.qCustomBlock = (ctx, node) => { node.type = 'questionCustomBlock' return integrationConfig.overrides.questionCustomBlock(ctx, node) } integrationConfig.overrides.sCustomBlock = (ctx, node) => { node.type = 'secretCustomBlock' return integrationConfig.overrides.secretCustomBlock(ctx, node) } integrationConfig.overrides.aCustomBlock = (ctx, node) => { node.type = 'warningCustomBlock' return integrationConfig.overrides.warningCustomBlock(ctx, node) } test('abbr', () => { const fixture = fixtures['abbr'] const {contents} = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(contents).toMatchSnapshot() }) test('heading', () => { const fixture = fixtures['heading'] const {contents} = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(contents).toMatchSnapshot() }) test('html nodes', () => { const {contents} = unified() .use(reParse) .use(rebber) .processSync(dedent` # foo **something else** `) expect(contents).toMatchSnapshot() }) test('heading with custom config', () => { const fixture = fixtures['heading'] const {contents} = unified() .use(reParse) .use(rebber, { headings: [ (val) => `\\LevelOneTitle{${val}}\n`, (val) => `\\LevelTwoTitle{${val}}\n`, (val) => `\\LevelThreeTitle{${val}}\n`, (val) => `\\LevelFourTitle{${val}}\n`, (val) => `\\LevelFiveTitle{${val}}\n`, (val) => `\\LevelSixTitle{${val}}\n`, (val) => `\\LevelSevenTitle{${val}}\n`, ], image: { inlineImage: (node) => `\\inlineImage{${node.url}}`, image: (node) => `\\image{${node.url}}`, }, }) .processSync(fixture) expect(contents).toMatchSnapshot() }) test('paragraph', () => { const fixture = fixtures['paragraph'] const {contents} = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('inline-code', () => { const fixture = fixtures['inline-code'] const {contents} = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('emoticon', () => { const fixture = fixtures['emoticon'] const {contents} = unified() .use(reParse) .use(require('remark-emoticons/src'), emoticonsConfig) .use(rebber, { overrides: { emoticon: require('../src/type/emoticon'), }, emoticons: emoticonsConfig, }) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('table', () => { const fixture = fixtures['table'] const {contents} = unified() .use(reParse) .use(rebber, { image: { inlineImage: (node) => `\\inlineImage{${node.url}}`, image: (node) => `\\image{${node.url}}`, }, }) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('gridTable', () => { const fixture = fixtures['gridTable'] const {contents} = unified() .use(reParse) .use(require('remark-captions/src'), {external: {gridTable: 'Table:', math: 'Equation'}}) .use(require('remark-grid-tables/src')) .use(rebber, integrationConfig) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() expect(contents.trim()).not.toContain('\\number-of-column') }) test('blockquote', () => { const fixture = fixtures['blockquote'] let compiled = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(compiled.contents.trim()).toMatchSnapshot() compiled = unified() .use(reParse) .use(rebber, { blockquote: undefined, }) .processSync(fixture) expect(compiled.contents.trim()).toMatchSnapshot() }) test('blockquote with custom config', () => { const fixture = fixtures['blockquote'] const {contents} = unified() .use(reParse) .use(rebber, { blockquote: (val) => `\\begin{Foo}\n${val}\n\\end{Foo}\n\n`, }) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('figure+caption', () => { const fixture = fixtures['figure'] const {contents} = unified() .use(reParse) .use(require('remark-captions/src')) .use(rebber, { overrides: { figure: require('../src/type/figure'), }, }) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('code', () => { const fixture = fixtures['code'] const {contents} = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('code+caption', () => { const fixture = fixtures['figure-code'] const {contents} = unified() .use(reParse) .use(require('remark-captions/src')) .use(rebber, { overrides: { figure: require('../src/type/figure'), }, }) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('list', () => { const fixture = fixtures['list'] const {contents} = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('link', () => { const fixture = fixtures['link'] const {contents} = unified() .use(reParse) .use(rebber) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) test('link with special characters', () => { const {contents} = unified() .use(reParse) .use(rebber) .processSync(dedent` [foo](http://example.com?a=b%c^{}#foo) `) expect(contents.trim()).toMatchSnapshot() }) test('link-prepend', () => { const fixture = fixtures['link-prepend'] const {contents} = unified() .use(reParse) .use(rebber, { link: { prefix: 'http://zestedesavoir.com', }, }) .processSync(fixture) expect(contents.trim()).toMatchSnapshot() }) Object.keys(fixtures).filter(Boolean).filter(name => name.startsWith('mix-')).forEach(name => { const fixture = fixtures[name] test(name, () => { const {contents} = unified() .use(reParse) .use(footnotes, {inlineNotes: true}) .use(require('remark-emoticons/src'), emoticonsConfig) .use(require('remark-captions/src'), { external: {gridTable: 'Table:', math: 'Equation'}, internal: {iframe: 'Video:'}, }) .use(require('remark-grid-tables/src')) .use(require('remark-sub-super/src')) .use(require('remark-iframes/src'), { 'www.youtube.com': { tag: 'iframe', width: 560, height: 315, disabled: false, replace: [ ['watch?v=', 'embed/'], ['http://', 'https://'], ], thumbnail: { format: 'http://img.youtube.com/vi/{id}/0.jpg', id: '.+/(.+)$', }, removeAfter: '&', }, }) .use(require('remark-kbd/src')) .use(require('remark-abbr/src')) .use(require('remark-align/src'), { right: 'custom-right', center: 'custom-center', }) .use(remarkMath) .use(rebber, integrationConfig) .processSync(fixture.replace(/·/g, ' ')) expect(contents.trim()).toMatchSnapshot() }) }) test('footnotes', () => { const fixture = fixtures['footnote'] const {contents} = unified() .use(reParse) .use(footnotes, {inlineNotes: true}) .use(rebber, integrationConfig) .processSync(fixture) expect(contents).toMatchSnapshot() }) test('abbr', () => { const {contents} = unified() .use(reParse) .use(require('remark-abbr/src')) .use(rebber, { overrides: { abbr: require('../src/type/abbr'), }, }) .processSync(dedent` FOO *[FOO]: bar `) expect(contents).toMatchSnapshot() }) test('abbr with custom config', () => { const {contents} = unified() .use(reParse) .use(require('remark-abbr/src')) .use(rebber, { overrides: { abbr: require('../src/type/abbr'), }, abbr: (x) => `->${x}<-`, }) .processSync(dedent` FOO *[FOO]: bar `) expect(contents).toMatchSnapshot() }) test('math', () => { const {contents} = unified() .use(reParse) .use(remarkMath) .use(rebber, integrationConfig) .processSync(dedent` A sentence ($S$) with *italic* and inline math ($C_L$) and $$b$$ another. $$ L = \frac{1}{2} \rho v^2 S C_L $$ hehe We also want escaping like so: $\alphb$ or like so: $$\ve\frac{1}{2}$$ `) expect(contents).toMatchSnapshot() }) test('math-extra-command', () => { const {contents} = unified() .use(reParse) .use(remarkMath) .use(rebber, integrationConfig) .processSync(dedent` $$ xnabla^2 E(\vec x) + [n(\vec{x})\,k]^2\,E(\vec x) = -s(\vec x), $$ `.replace('xn', '\\n')) expect(contents).toMatchSnapshot() }) test('math-left-right', () => { const {contents} = unified() .use(reParse) .use(remarkMath) .use(rebber, integrationConfig) .processSync(dedent` $$ \boxed{xnabla^2 E(\vec x) + \left\{[n(\vec{x})\,k]^2-i\,k\,\sigma(\vec x)\right\}\,E(\vec x)} $$ `.replace('xn', '\\n')) expect(contents).toMatchSnapshot() }) test('ping', () => { const {contents} = unified() .use(reParse) .use(remarkPing, { pingUsername: username => true, userURL: username => `/membres/voir/${username}/`, }) .use(rebber, integrationConfig) .processSync(dedent` Hello @you and @you_too, and @**also you** `) expect(contents).toMatchSnapshot() }) test('custom-blocks', () => { const fixture = fixtures['blocks'] const {contents} = unified() .use(reParse) .use(require('remark-custom-blocks'), { secret: { classes: 'spoiler', }, s: { classes: 'spoiler', }, information: { classes: 'information ico-after', }, i: { classes: 'information ico-after', }, question: { classes: 'question ico-after', }, q: { classes: 'question ico-after', }, attention: { classes: 'warning ico-after', }, a: { classes: 'warning ico-after', }, erreur: { classes: 'error ico-after', }, e: { classes: 'error ico-after', title: 'optional', }, neutre: { classes: 'custom-block-neutre', title: 'required', }, }, true) .use(rebber, integrationConfig) .processSync(fixture.replace(/·/g, ' ')) expect(contents.trim()).toMatchSnapshot() }) test('regression: code block without language', () => { const {contents} = unified() .use(reParse) .use(rebber, integrationConfig) .processSync(dedent` \`\`\` a \`\`\` `) expect(contents).toMatchSnapshot() }) // TODO: fix this? test.skip('comments blocks', () => { const {contents} = unified() .use(reParse) .use(rebber, integrationConfig) .processSync(dedent` Foo<--COMMENTS I am a comment COMMENTS-->bar `) expect(contents).toMatch('begin{comment}') }) ================================================ FILE: packages/rebber-plugins/dist/preprocessors/codeVisitor.js ================================================ "use strict"; const visit = require('unist-util-visit'); const appendix = require('../type/appendix'); const defaultReferenceGenerator = appendixIndex => `Code appendice ${appendixIndex}`; module.exports = (ctx, tree) => { ctx.overrides.appendix = appendix; return node => { let appendix = tree.children[tree.children.length - 1]; if (!appendix || appendix.type !== 'appendix') { appendix = { type: 'appendix', children: [] }; tree.children.push(appendix); } let appendixIndex = appendix.children.length + 1; visit(node, 'code', (innerNode, index, _parent) => { appendix.children.push({ type: 'paragraph', children: [{ type: 'heading', children: [{ type: 'definition', identifier: `appendix-${appendixIndex}`, url: `${ctx.codeAppendiceTitle || 'Appendix'} ${appendixIndex}`, referenceType: 'full' }], depth: 1 }] }); appendix.children.push(innerNode); const generator = ctx.appendiceReferenceGenerator || defaultReferenceGenerator; const referenceNode = { type: 'linkReference', identifier: `appendix-${appendixIndex}`, children: [{ type: 'text', value: generator(appendixIndex) }] }; _parent.children.splice(index, 1, referenceNode); appendixIndex++; }); }; }; ================================================ FILE: packages/rebber-plugins/dist/preprocessors/footnoteProtect.js ================================================ "use strict"; module.exports = plugin; /* LaTeX requires special handlings of footnotes placed in headings such as \section{} We therefore mark each footnote placed in heading for later handling. */ const nodeTypes = ['footnote', 'footnoteDefinition', 'footnoteReference']; function plugin() { return function footnoteProtect(node, index, parent) { if (nodeTypes.includes(node.type) && node.commandProtect !== true) { node.commandProtect = true; } if (node.children) { node.children.forEach((n, i) => footnoteProtect(n, i, node)); } }; } ================================================ FILE: packages/rebber-plugins/dist/preprocessors/iframe.js ================================================ "use strict"; module.exports = () => (node, index, parent) => { const linkNode = { type: 'link', url: node.data.hProperties.src, children: [{ type: 'text', value: node.data.hProperties.src }] }; const thumbnailNode = { type: 'image', url: node.data.thumbnail }; if (parent.type !== 'figure') { const figureNode = { type: 'figure', children: [thumbnailNode, { type: 'figcaption', children: [linkNode] }] }; parent.children[index] = figureNode; } else { parent.children[index] = thumbnailNode; parent.children[parent.children.length - 1].children.push(linkNode); } }; ================================================ FILE: packages/rebber-plugins/dist/preprocessors/mathEscape.js ================================================ "use strict"; const katexConstants = require('../../src/preprocessors/katexConstants.json'); const endOfCommandChars = ['\\', '[', ']', '{', '}', ' ', '(', ')', '\t', '\n', '^', '_', ',']; function isEndOfCommand(nodeLine, index) { if (index >= nodeLine.length) { return true; } return endOfCommandChars.includes(nodeLine.charAt(index)); } module.exports = () => node => { let commandStart = node.value.indexOf('\\'); while (commandStart !== -1) { // Eat leading backslashes (at most two) let leadSlashes = 1; let isSlash = node.value.charAt(commandStart + leadSlashes) === '\\'; while (isSlash && leadSlashes < 2) { isSlash = node.value.charAt(commandStart + leadSlashes) === '\\'; leadSlashes++; } let currentCommand = ''; // Find end of command let potentialEnd = leadSlashes + commandStart; while (!isEndOfCommand(node.value, potentialEnd)) { currentCommand += node.value.charAt(potentialEnd); potentialEnd++; } // if we just had a \{ or \, for example if (currentCommand === '' && potentialEnd < node.value.length && katexConstants.includes(`\\${node.value.charAt(potentialEnd)}`)) { currentCommand = node.value.charAt(potentialEnd); } // Check for unknown commands const slashedCommand = '\\'.repeat(leadSlashes).concat(currentCommand); const commandLength = slashedCommand.length; if (!katexConstants.includes(slashedCommand)) { const beforeCommand = node.value.substring(0, commandStart); const afterCommand = node.value.substring(commandStart + commandLength, node.value.length); node.value = `${beforeCommand} ${afterCommand}`; // As we changed the command we need to restart from the beginning potentialEnd = 1; } commandStart = node.value.indexOf('\\', potentialEnd); } // Check count of brackets const openingBracesCount = (node.value.match(/(? 0) { node.value = '{'.repeat(bracesDelta) + node.value; } }; ================================================ FILE: packages/rebber-plugins/dist/preprocessors/prepareQuizz.js ================================================ "use strict"; const clone = require('clone'); const visit = require('unist-util-visit'); module.exports = processQuizzFactory; function processQuizzFactory(ctx) { const correctionTitle = ctx.correctionTitle || 'Correction'; return (node, index, parent) => { if (node.children.length < 2 || !node.children[0].children || !node.children[1].children) { return; } node.type = 'neutralCustomBlock'; const correction = clone(node); correction.type = 'sCustomBlock'; correction.children[0].children[0].value = correctionTitle; parent.children.splice(index + 1, 0, correction); const bodyChildren = node.children[1].children; while (bodyChildren.length > 0 && bodyChildren[bodyChildren.length - 1].type !== 'list') { bodyChildren.splice(bodyChildren.length - 1, 1); } visit(node, 'listItem', itemNode => { itemNode.checked = null; }); }; } ================================================ FILE: packages/rebber-plugins/dist/preprocessors/spoilerFlatten.js ================================================ "use strict"; const visit = require('unist-util-visit'); module.exports = elemNames => (_, tree) => { // This should contain something like ['sCustomBlockBody', 'secretCustomBlockBody'] const elemNamesBody = elemNames.map(v => v.concat('Body')); // Iterate over the elements to be flattened for (const elemNameBody of elemNamesBody) { visit(tree, elemNameBody, node => { // Recursive function that flattens all matching elements and keeps the others function flattenTree(blockTree) { return blockTree.map(v => { if (elemNames.includes(v.type)) { // Get sCustomBlock > sCustomBlockBody > * const newTree = v.children.filter(v => elemNamesBody.includes(v.type)) // This is sub-optimal, but flatMap doesn't have good support by now .reduce((acc, e) => acc.concat(e.children), []); // First level of recursion: on direct descendents return flattenTree(newTree); } else { // Second level of recursion: on indirect descendents if (v.children) { v.children = flattenTree(v.children); } return v; } }).reduce((acc, e) => acc.concat(e), []); } // First round on direct children node.children = flattenTree(node.children); }); } }; ================================================ FILE: packages/rebber-plugins/dist/type/abbr.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); const escape = require('rebber/dist/escaper'); /* Expose. */ module.exports = abbrPlugin; function abbrPlugin(ctx, node) { const abbr = all(ctx, node); const reference = escape(node.reference); if (ctx.abbr && typeof ctx.abbr === 'function') { return ctx.abbr(abbr, reference); } return `\\abbr{${abbr}}{${reference}}`; } ================================================ FILE: packages/rebber-plugins/dist/type/align.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = align; const defaultMacros = { leftAligned: innerText => `\n\n${innerText}\n\n`, centerAligned: innerText => `\n{\\centering ${innerText}}\n`, rightAligned: innerText => `\n{\\raggedleft\n${innerText}}\n`, defaultType: (innerText, type) => `\n\\begin{${type}}\n${innerText}\n\\end{${type}}\n` }; function align(ctx, node) { const macro = ctx[node.type] || defaultMacros[node.type] || defaultMacros.defaultType; const innerText = all(ctx, node); return macro(innerText, node.type); } ================================================ FILE: packages/rebber-plugins/dist/type/appendix.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); const clone = require('clone'); /* Expose. */ module.exports = appendixPlugin; function definitionMacro(ctx, identifier, url) { return `\\label{${identifier}}${url}`; } function appendixPlugin(ctx, node) { if (node.children.length === 0) { return ''; } const overriddenCtx = clone(ctx); overriddenCtx.definition = definitionMacro; const innerText = all(overriddenCtx, node); return `\\begin{appendices}\n${innerText.trimEnd()}\n\\end{appendices}`; } ================================================ FILE: packages/rebber-plugins/dist/type/comments.js ================================================ "use strict"; /* Expose. */ module.exports = comments; /* Stringify a comments `node`. */ function comments(ctx, node) { return `\\begin{comment}\n${node.data.comment}\n\\end{comment}\n`; } ================================================ FILE: packages/rebber-plugins/dist/type/conclusion.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = conclusion; const conclusionMacros = [content => `\\begin{LevelOneConclusion}\n${content}\n\\end{LevelOneConclusion}`, content => `\\begin{LevelTwoConclusion}\n${content}\n\\end{LevelTwoConclusion}`, content => `\\begin{LevelThreeConclusion}\n${content}\n\\end{LevelThreeConclusion}`]; /* Stringify an conclusion `node`. */ function conclusion(ctx, node) { const level = node.data.level || 0; const macro = ctx[node.type] || conclusionMacros[level]; const innerText = all(ctx, node); return `${macro(innerText.trim())}\n\n`; } ================================================ FILE: packages/rebber-plugins/dist/type/customBlocks.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = customBlock; const defaultMacros = { defaultBlock: (environmentName, blockTitle, blockContent) => { return `\\begin{${environmentName}}${blockTitle ? `[{{${blockTitle}}}]` : ''}` + `\n${blockContent}` + `\n\\end{${environmentName}}\n`; } }; function customBlock(ctx, node) { const blockMacro = ctx[node.type] || defaultMacros[node.type] || defaultMacros.defaultBlock; let blockTitle = ''; if (node.children && node.children.length) { if (node.children[0].type.endsWith('CustomBlockHeading')) { const titleNode = node.children.splice(0, 1)[0]; blockTitle = all(ctx, titleNode).trim(); } } node.children[0].type = 'paragraph'; const blockContent = all(ctx, node).trim(); const options = ctx.customBlocks || {}; let environmentName; const type = node.type.replace('CustomBlock', ''); if (options.map && options.map[type]) { environmentName = options.map[type]; } else { environmentName = type[0].toUpperCase() + type.substring(1); } return blockMacro(environmentName, blockTitle, blockContent); } ================================================ FILE: packages/rebber-plugins/dist/type/emoticon.js ================================================ "use strict"; /* Dependencies. */ const has = require('has'); /* Expose. */ module.exports = emoticon; /* Stringify an emoticon `node`. */ function emoticon(ctx, node) { const code = node.value; if (!ctx.emoticons.emoticons || !has(ctx.emoticons.emoticons, code)) return; return `\\smiley{${ctx.emoticons.emoticons[code]}}`; } ================================================ FILE: packages/rebber-plugins/dist/type/figure.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); const one = require('rebber/dist/one'); const defaultCodeStringifier = require('rebber/dist/types/code').macro; const has = require('has'); /* Expose. */ module.exports = figure; const defaultMacros = { blockquote: (_, innerText, caption = 'Anonymous') => `\\begin{Quotation}[${caption}]\n${innerText}\n\\end{Quotation}\n\n`, code: (ctx, code, caption, extra) => { const codeStringifier = has(ctx, 'code') && ctx.code || defaultCodeStringifier; // Remove the two last line feed const rebberCode = codeStringifier(code, extra.language, extra.others).slice(0, -2); return `${rebberCode}\n\\captionof{listing}{${caption}}\n\n`; }, image: (_1, _2, caption, extra) => '\\begin{center}\n' + `\\includegraphics${extra.width ? `[${extra.width}]` : ''}{${extra.url}}\n` + `\\captionof{figure}{${caption}}\n` + '\\end{center}\n' }; const makeExtra = { blockquote: node => {}, code: node => { return { language: node.lang || 'text', others: node.meta }; }, image: node => ({ url: node.url, width: '\\linewidth' }) }; /* Stringify a Figure `node`. */ function figure(ctx, node, index, parent) { const type = node.children[0].type; const macro = has(ctx, 'figure') && has(ctx.figure, type) && ctx.figure[type] || has(defaultMacros, type) && defaultMacros[type]; let caption = ''; if (node.children.length) { caption = node.children.filter(captionNode => captionNode.type === 'figcaption').map(captionNode => all(ctx, captionNode)).join(''); } node.caption = caption; // allows to add caption to the default processing if (!macro) { node.children[0].caption = caption; return one(ctx, node.children[0], 0, node); } const wrappedNode = node.children[0]; wrappedNode.caption = node.caption; node.children = node.children.filter(node => node.type !== 'figcaption'); if (node.children.length === 1) { node.children = node.children[0].children; } const extra = has(makeExtra, type) ? makeExtra[type](wrappedNode) : undefined; const innerText = all(ctx, node) || node.value || ''; return macro(ctx, innerText.trim(), caption, extra); } ================================================ FILE: packages/rebber-plugins/dist/type/footnote.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = notes; const defaultMacro = (identifier, text, protect) => { const footnote = `${protect ? '\\protect' : ''}\\footnote[${identifier}]{${text}}`; return footnote; }; function autoId(node) { const { line, column, offset } = node.position.start; return `l${line}c${column}o${offset}`; } /* Stringify a footnote `node`. */ function notes(ctx, node) { const macro = ctx.footnote || defaultMacro; const protect = Boolean(node.commandProtect); const identifier = autoId(node); return macro(identifier, all(ctx, node).trim(), protect); } ================================================ FILE: packages/rebber-plugins/dist/type/footnoteDefinition.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = notes; const defaultMacro = (identifier, text, protect) => `${protect ? '\\protect' : ''}\\footnotetext[${identifier}]{${text}}`; function notes(ctx, node) { const macro = ctx.footnoteDefinition || defaultMacro; const protect = Boolean(node.commandProtect); return macro(node.identifier, all(ctx, node).trim(), protect); } ================================================ FILE: packages/rebber-plugins/dist/type/footnoteReference.js ================================================ "use strict"; /* Expose. */ module.exports = notes; const defaultMacro = (identifier, protect) => `\\textsuperscript{${protect ? '\\protect' : ''}\\footnotemark[${identifier}]}`; function notes(ctx, node) { const macro = ctx.footnoteReference || defaultMacro; const protect = Boolean(node.commandProtect); return macro(node.identifier, protect); } ================================================ FILE: packages/rebber-plugins/dist/type/gridTable.js ================================================ "use strict"; /* Dependencies. */ const clone = require('clone'); const tableCell = require('rebber/dist/types/tableCell'); const tableRow = require('rebber/dist/types/tableRow'); const table = require('rebber/dist/types/table'); const text = require('rebber/dist/types/text'); const paragraph = require('rebber/dist/types/paragraph'); /* Expose. */ module.exports = gridTable; class MultiRowLine { constructor(startRow, endRow, startCell, endCell, colSpan, endOfLine) { this.multilineCounter = endRow - startRow; this.startCell = startCell; this.endCell = endCell; this.colSpan = colSpan; this.endOfLine = endOfLine; } getCLine() { let startCLine = 1; let endCLine = this.startCell - 1; // case where the multi row line is at the start of the table if (this.startCell === 1) { startCLine = this.startCell + this.colSpan; endCLine = this.endOfLine; } else if (this.startCell > 1 && this.startCell + this.colSpan < this.endOfLine) { // case where the multi row line is in the middle of the table const clineBefore = `\\cline{1-${this.startCell - 1}}`; const clineAfter = `\\cline{${this.startCell + this.colSpan}-${this.endOfLine}}`; return `${clineBefore} ${clineAfter}`; } return `\\cline{${startCLine}-${endCLine}}`; } } class GridTableStringifier { constructor() { this.lastMultiRowLine = null; this.currentSpan = 0; this.rowIndex = 0; this.colIndex = 0; this.multiLineCellIndex = 0; this.colSpan = 1; this.nbOfColumns = 0; } gridTableCell(ctx, node) { const overriddenCtx = clone(ctx); this.colIndex++; overriddenCtx.tableCell = undefined; // we have to replace \n by \endgraf only in text node, not in other // see #352 overriddenCtx.overrides.text = (c, n, index, parent) => text(c, n, index, parent).replace(/\n/g, ' \\endgraf '); overriddenCtx.overrides.paragraph = (c, n) => `${paragraph(c, n).trim()} \\endgraf \\endgraf `; let baseText = tableCell(overriddenCtx, node).trim(); while (baseText.substring(baseText.length - '\\endgraf'.length) === '\\endgraf') { baseText = baseText.substring(0, baseText.length - '\\endgraf'.length).trim(); } if (node.data && node.data.hProperties.rowSpan > 1) { this.currentSpan = node.data.hProperties.rowSpan; this.multiLineCellIndex = this.colIndex; baseText = `\\SetCell[r=${this.currentSpan}]{l} ${baseText}`; this.colSpan = node.data.hProperties.colSpan > 1 ? node.data.hProperties.colSpan : 1; } else if (node.data && node.data.hProperties.colSpan > 1) { const colSpan = node.data.hProperties.colSpan; baseText = `\\SetCell[c=${colSpan}]{l} ${baseText}`; } if (node.data && node.data.hProperties.colSpan > 1) { this.colIndex -= 1; this.colIndex += node.data.hProperties.colSpan; } return baseText; } gridTableRow(ctx, node, index) { const overriddenCtx = clone(ctx); overriddenCtx.tableRow = undefined; this.rowIndex++; const extraCell = { type: 'tableCell', children: [{ type: 'paragraph', children: [{ type: 'text', value: ' ' }] }] }; // Duplicate cells with colSpan greater than one for (let i = 0; i < node.children.length; i++) { if (!node.children[i].data) continue; const colSpan = node.children[i].data.hProperties.colSpan; if (!colSpan || colSpan <= 1) continue; for (let j = 0; j < colSpan - 1; j++) { node.children.splice(i + 1, 0, extraCell); } } if (this.previousRowWasMulti()) { const lastMultiRowline = this.flushMultiRowLineIfNeeded(); for (let i = 0; i < lastMultiRowline.colSpan; i++) { node.children.splice(lastMultiRowline.startCell - 1, 0, extraCell); } this.colIndex = 0; let rowStr = tableRow(overriddenCtx, node, index); if (lastMultiRowline.multilineCounter > 0) { rowStr = rowStr.replace(/\\hline/, lastMultiRowline.getCLine()); } this.colIndex = 0; return rowStr; } let rowText = tableRow(overriddenCtx, node, index); if (this.currentSpan !== 0) { this.lastMultiRowLine = new MultiRowLine(this.rowIndex, this.rowIndex + this.currentSpan + -1, this.multiLineCellIndex, this.colIndex + this.colSpan, this.colSpan, this.colIndex); rowText = rowText.replace(/\\hline/, this.lastMultiRowLine.getCLine()); } this.currentSpan = 0; if (this.colIndex >= this.nbOfColumns) { this.nbOfColumns = this.colIndex; } this.colIndex = 0; return rowText; } flushMultiRowLineIfNeeded() { if (!this.lastMultiRowLine) { return null; } const row = this.lastMultiRowLine; if (row.multilineCounter >= 1) { row.multilineCounter--; } if (row.multilineCounter === 0) { this.lastMultiRowLine = null; } return row; } gridTableheaderCounter(node) { const tableHeaders = node.children.filter(n => n.data && n.data.hName === 'thead'); return tableHeaders.length >= 1 ? tableHeaders[0].children.length : 0; } gridTableHeaderParse() { return ' X[-1]'.repeat(this.nbOfColumns).substring(1); } previousRowWasMulti() { return this.lastMultiRowLine !== null; } } function gridTable(ctx, node) { const overriddenCtx = clone(ctx); const stringifier = new GridTableStringifier(); // Inside tables, `\\\\` won't work overriddenCtx.break = () => ' \\endgraf'; overriddenCtx.tableCell = stringifier.gridTableCell.bind(stringifier); overriddenCtx.tableRow = stringifier.gridTableRow.bind(stringifier); overriddenCtx.headerCounter = stringifier.gridTableheaderCounter.bind(stringifier); overriddenCtx.headerParse = stringifier.gridTableHeaderParse.bind(stringifier); overriddenCtx.image = overriddenCtx.image ? overriddenCtx.image : {}; overriddenCtx.image.inlineMatcher = () => true; return table(overriddenCtx, node).replace(/\\number-of-column/gm, stringifier.nbOfColumns); } ================================================ FILE: packages/rebber-plugins/dist/type/introduction.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = introduction; const introductionMacros = [content => `\\begin{LevelOneIntroduction}\n${content}\n\\end{LevelOneIntroduction}`, content => `\\begin{LevelTwoIntroduction}\n${content}\n\\end{LevelTwoIntroduction}`, content => `\\begin{LevelThreeIntroduction}\n${content}\n\\end{LevelThreeIntroduction}`]; /* Stringify an introduction `node`. */ function introduction(ctx, node) { const level = node.data.level || 0; const macro = ctx[node.type] || introductionMacros[level]; const innerText = all(ctx, node); return `${macro(innerText.trim())}\n\n`; } ================================================ FILE: packages/rebber-plugins/dist/type/kbd.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = kbd; /* Stringify a kbd `node`. */ function kbd(ctx, node) { const contents = all(ctx, node); return `\\keys{${contents}}`; } ================================================ FILE: packages/rebber-plugins/dist/type/math.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); const has = require('has'); /* Expose. */ module.exports = math; const defaultMacros = { inlineMath: content => `$${content}$`, inlineMathDouble: content => `$$${content}$$`, math: content => `\\[ ${content} \\]\n\n` }; /* Stringify a Figure `node`. */ function math(ctx, node, index, parent) { let type = 'math'; if (node.type === 'inlineMath') { try { const classes = node.data.hProperties.className; type = classes.includes('math-display') ? 'inlineMathDouble' : 'inlineMath'; } catch (e) { console.error(e, 'This rebber math plugin is only compatible with remark-math.'); } } const macro = has(ctx, 'math') && has(ctx.math, type) && ctx.math[type] || has(defaultMacros, type) && defaultMacros[type]; const content = all(ctx, node) || node.value || ''; return macro(content.trim()); } ================================================ FILE: packages/rebber-plugins/dist/type/ping.js ================================================ "use strict"; /* Expose. */ module.exports = ping; /* Stringify a `ping` node. */ function ping(_, node) { return `\\ping{${node.username}}`; } ================================================ FILE: packages/rebber-plugins/dist/type/sub.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = sub; /* Stringify a sub `node`. */ function sub(ctx, node, index, parent) { const contents = all(ctx, node); return `\\textsubscript{${contents}}`; } ================================================ FILE: packages/rebber-plugins/dist/type/sup.js ================================================ "use strict"; /* Dependencies. */ const all = require('rebber/dist/all'); /* Expose. */ module.exports = sup; /* Stringify a sup `node`. */ function sup(ctx, node, index, parent) { const contents = all(ctx, node); return `\\textsuperscript{${contents}}`; } ================================================ FILE: packages/rebber-plugins/dist/type/tableHeader.js ================================================ "use strict"; module.exports = (ctx, node, index, parent) => { const one = require('rebber/dist/one'); if (ctx.tableHeader) { return ctx.tableHeader(node.value); } if (index === 0 && parent.children.length > 1) { // if we are on first header row we do not want to switch back to // font of normal serie return node.children.map((n, childIndex) => one(ctx, n, childIndex === 0 ? 0 : 2, node)).join(''); } const parsed = node.children.map((n, childIndex) => one(ctx, n, index + childIndex, node)); return parsed.join(''); }; ================================================ FILE: packages/rebber-plugins/package.json ================================================ { "name": "rebber-plugins", "version": "4.4.0", "description": "Stringifies custom MDAST nodes to LaTeX", "repository": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rebber-plugins", "author": "Victor Felder (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "mdast", "latex" ], "license": "MIT", "dependencies": { "has": "^1.0.3", "xtend": "^4.0.2" }, "devDependencies": { "rebber": "file:../rebber", "remark-abbr": "file:../remark-abbr", "remark-align": "file:../remark-align", "remark-captions": "file:../remark-captions", "remark-emoticons": "file:../remark-emoticons", "remark-grid-tables": "file:../remark-grid-tables", "remark-numbered-footnotes": "file:../remark-numbered-footnotes", "remark-sub-super": "file:../remark-sub-super" } } ================================================ FILE: packages/rebber-plugins/src/preprocessors/codeVisitor.js ================================================ const visit = require('unist-util-visit') const appendix = require('../type/appendix') const defaultReferenceGenerator = (appendixIndex) => `Code appendice ${appendixIndex}` module.exports = (ctx, tree) => { ctx.overrides.appendix = appendix return (node) => { let appendix = tree.children[tree.children.length - 1] if (!appendix || appendix.type !== 'appendix') { appendix = { type: 'appendix', children: [] } tree.children.push(appendix) } let appendixIndex = appendix.children.length + 1 visit(node, 'code', (innerNode, index, _parent) => { appendix.children.push({ type: 'paragraph', children: [ { type: 'heading', children: [ { type: 'definition', identifier: `appendix-${appendixIndex}`, url: `${ctx.codeAppendiceTitle || 'Appendix'} ${appendixIndex}`, referenceType: 'full' } ], depth: 1 } ] }) appendix.children.push(innerNode) const generator = ctx.appendiceReferenceGenerator || defaultReferenceGenerator const referenceNode = { type: 'linkReference', identifier: `appendix-${appendixIndex}`, children: [ { type: 'text', value: generator(appendixIndex) } ] } _parent.children.splice(index, 1, referenceNode) appendixIndex++ }) } } ================================================ FILE: packages/rebber-plugins/src/preprocessors/footnoteProtect.js ================================================ module.exports = plugin /* LaTeX requires special handlings of footnotes placed in headings such as \section{} We therefore mark each footnote placed in heading for later handling. */ const nodeTypes = ['footnote', 'footnoteDefinition', 'footnoteReference'] function plugin () { return function footnoteProtect (node, index, parent) { if (nodeTypes.includes(node.type) && node.commandProtect !== true) { node.commandProtect = true } if (node.children) { node.children.forEach((n, i) => footnoteProtect(n, i, node)) } } } ================================================ FILE: packages/rebber-plugins/src/preprocessors/iframe.js ================================================ module.exports = () => (node, index, parent) => { const linkNode = { type: 'link', url: node.data.hProperties.src, children: [ { type: 'text', value: node.data.hProperties.src } ] } const thumbnailNode = { type: 'image', url: node.data.thumbnail } if (parent.type !== 'figure') { const figureNode = { type: 'figure', children: [thumbnailNode, { type: 'figcaption', children: [linkNode] }] } parent.children[index] = figureNode } else { parent.children[index] = thumbnailNode parent.children[parent.children.length - 1].children.push(linkNode) } } ================================================ FILE: packages/rebber-plugins/src/preprocessors/katexConstants.json ================================================ ["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring","\\widecheck","\\widehat","\\widetilde","\\overrightarrow","\\overleftarrow","\\Overrightarrow","\\overleftrightarrow","\\overgroup","\\overlinesegment","\\overleftharpoon","\\overrightharpoon","\\'","\\`","\\^","\\~","\\=","\\u","\\.","\\\"","\\c","\\r","\\H","\\v","\\textcircled","\\underleftarrow","\\underrightarrow","\\underleftrightarrow","\\undergroup","\\underlinesegment","\\utilde","\\xleftarrow","\\xrightarrow","\\xLeftarrow","\\xRightarrow","\\xleftrightarrow","\\xLeftrightarrow","\\xhookleftarrow","\\xhookrightarrow","\\xmapsto","\\xrightharpoondown","\\xrightharpoonup","\\xleftharpoondown","\\xleftharpoonup","\\xrightleftharpoons","\\xleftrightharpoons","\\xlongequal","\\xtwoheadrightarrow","\\xtwoheadleftarrow","\\xtofrom","\\xrightleftarrows","\\xrightequilibrium","\\xleftequilibrium","\\\\cdrightarrow","\\\\cdleftarrow","\\\\cdlongequal","\\\\cdleft","\\\\cdright","\\\\cdparent","\\@char","\\textcolor","\\color","\\\\","\\global","\\long","\\\\globallong","\\def","\\gdef","\\edef","\\xdef","\\let","\\\\globallet","\\futurelet","\\\\globalfuture","\\bigl","\\Bigl","\\biggl","\\Biggl","\\bigr","\\Bigr","\\biggr","\\Biggr","\\bigm","\\Bigm","\\biggm","\\Biggm","\\big","\\Big","\\bigg","\\Bigg","\\right","\\right.","\\left","\\left.","\\middle","\\colorbox","\\fcolorbox","\\fbox","\\cancel","\\bcancel","\\xcancel","\\sout","\\phase","\\angl","\\hline","\\hdashline","\\begin","\\end","\\mathord","\\mathbin","\\mathrel","\\mathopen","\\mathclose","\\mathpunct","\\mathinner","\\@binrel","\\stackrel","\\overset","\\underset","\\mathrm","\\mathit","\\mathbf","\\mathnormal","\\mathbb","\\mathcal","\\mathfrak","\\mathscr","\\mathsf","\\mathtt","\\Bbb","\\bold","\\frak","\\boldsymbol","\\bm","\\rm","\\sf","\\tt","\\bf","\\it","\\cal","\\dfrac","\\frac","\\tfrac","\\dbinom","\\binom","\\tbinom","\\\\atopfrac","\\\\bracefrac","\\\\brackfrac","\\cfrac","\\over","\\choose","\\atop","\\brace","\\brack","\\genfrac","\\above","\\\\abovefrac","\\overbrace","\\underbrace","\\href","\\url","\\hbox","\\htmlClass","\\htmlId","\\htmlStyle","\\htmlData","\\html@mathml","\\includegraphics","\\kern","\\mkern","\\hskip","\\mskip","\\mathllap","\\mathrlap","\\mathclap","\\(","\\)","\\]","\\mathchoice","\\coprod","\\bigvee","\\bigwedge","\\biguplus","\\bigcap","\\bigcup","\\intop","\\prod","\\sum","\\bigotimes","\\bigoplus","\\bigodot","\\bigsqcup","\\smallint","\\mathop","\\arcsin","\\arccos","\\arctan","\\arctg","\\arcctg","\\arg","\\ch","\\cos","\\cosec","\\cosh","\\cot","\\cotg","\\coth","\\csc","\\ctg","\\cth","\\deg","\\dim","\\exp","\\hom","\\ker","\\lg","\\ln","\\log","\\sec","\\sin","\\sinh","\\sh","\\tan","\\tanh","\\tg","\\th","\\det","\\gcd","\\inf","\\lim","\\max","\\min","\\Pr","\\sup","\\int","\\iint","\\iiint","\\oint","\\oiint","\\oiiint","\\operatorname@","\\operatornamewithlimits","\\overline","\\phantom","\\hphantom","\\vphantom","\\raisebox","\\rule","\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge","\\smash","\\sqrt","\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle","\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup","\\underline","\\vcenter","\\verb","\\equiv","\\prec","\\succ","\\sim","\\perp","\\preceq","\\succeq","\\simeq","\\mid","\\ll","\\gg","\\asymp","\\parallel","\\bowtie","\\smile","\\sqsubseteq","\\sqsupseteq","\\doteq","\\frown","\\ni","\\propto","\\vdash","\\dashv","\\owns","\\ldotp","\\cdotp","\\#","\\&","\\aleph","\\forall","\\hbar","\\exists","\\nabla","\\flat","\\ell","\\natural","\\clubsuit","\\wp","\\sharp","\\diamondsuit","\\Re","\\heartsuit","\\Im","\\spadesuit","\\S","\\P","\\dag","\\ddag","\\rmoustache","\\lmoustache","\\rgroup","\\lgroup","\\mp","\\ominus","\\uplus","\\sqcap","\\ast","\\sqcup","\\bigcirc","\\bullet","\\ddagger","\\wr","\\amalg","\\And","\\longleftarrow","\\Leftarrow","\\Longleftarrow","\\longrightarrow","\\Rightarrow","\\Longrightarrow","\\leftrightarrow","\\longleftrightarrow","\\Leftrightarrow","\\Longleftrightarrow","\\mapsto","\\longmapsto","\\nearrow","\\hookleftarrow","\\hookrightarrow","\\searrow","\\leftharpoonup","\\rightharpoonup","\\swarrow","\\leftharpoondown","\\rightharpoondown","\\nwarrow","\\rightleftharpoons","\\nless","\\@nleqslant","\\@nleqq","\\lneq","\\lneqq","\\@lvertneqq","\\lnsim","\\lnapprox","\\nprec","\\npreceq","\\precnsim","\\precnapprox","\\nsim","\\@nshortmid","\\nmid","\\nvdash","\\nvDash","\\ntriangleleft","\\ntrianglelefteq","\\subsetneq","\\@varsubsetneq","\\subsetneqq","\\@varsubsetneqq","\\ngtr","\\@ngeqslant","\\@ngeqq","\\gneq","\\gneqq","\\@gvertneqq","\\gnsim","\\gnapprox","\\nsucc","\\nsucceq","\\succnsim","\\succnapprox","\\ncong","\\@nshortparallel","\\nparallel","\\nVDash","\\ntriangleright","\\ntrianglerighteq","\\@nsupseteqq","\\supsetneq","\\@varsupsetneq","\\supsetneqq","\\@varsupsetneqq","\\nVdash","\\precneqq","\\succneqq","\\@nsubseteqq","\\unlhd","\\unrhd","\\nleftarrow","\\nrightarrow","\\nLeftarrow","\\nRightarrow","\\nleftrightarrow","\\nLeftrightarrow","\\vartriangle","\\hslash","\\triangledown","\\lozenge","\\circledS","\\circledR","\\measuredangle","\\nexists","\\mho","\\Finv","\\Game","\\backprime","\\blacktriangle","\\blacktriangledown","\\blacksquare","\\blacklozenge","\\bigstar","\\sphericalangle","\\complement","\\eth","\\diagup","\\diagdown","\\square","\\Box","\\Diamond","\\yen","\\checkmark","\\beth","\\daleth","\\gimel","\\digamma","\\varkappa","\\@ulcorner","\\@urcorner","\\@llcorner","\\@lrcorner","\\leqq","\\leqslant","\\eqslantless","\\lesssim","\\lessapprox","\\approxeq","\\lessdot","\\lll","\\lessgtr","\\lesseqgtr","\\lesseqqgtr","\\doteqdot","\\risingdotseq","\\fallingdotseq","\\backsim","\\backsimeq","\\subseteqq","\\Subset","\\sqsubset","\\preccurlyeq","\\curlyeqprec","\\precsim","\\precapprox","\\vartriangleleft","\\trianglelefteq","\\vDash","\\Vvdash","\\smallsmile","\\smallfrown","\\bumpeq","\\Bumpeq","\\geqq","\\geqslant","\\eqslantgtr","\\gtrsim","\\gtrapprox","\\gtrdot","\\ggg","\\gtrless","\\gtreqless","\\gtreqqless","\\eqcirc","\\circeq","\\triangleq","\\thicksim","\\thickapprox","\\supseteqq","\\Supset","\\sqsupset","\\succcurlyeq","\\curlyeqsucc","\\succsim","\\succapprox","\\vartriangleright","\\trianglerighteq","\\Vdash","\\shortmid","\\shortparallel","\\between","\\pitchfork","\\varpropto","\\blacktriangleleft","\\therefore","\\backepsilon","\\blacktriangleright","\\because","\\llless","\\gggtr","\\lhd","\\rhd","\\eqsim","\\Join","\\Doteq","\\dotplus","\\smallsetminus","\\Cap","\\Cup","\\doublebarwedge","\\boxminus","\\boxplus","\\divideontimes","\\ltimes","\\rtimes","\\leftthreetimes","\\rightthreetimes","\\curlywedge","\\curlyvee","\\circleddash","\\circledast","\\centerdot","\\intercal","\\doublecap","\\doublecup","\\boxtimes","\\dashrightarrow","\\dashleftarrow","\\leftleftarrows","\\leftrightarrows","\\Lleftarrow","\\twoheadleftarrow","\\leftarrowtail","\\looparrowleft","\\leftrightharpoons","\\curvearrowleft","\\circlearrowleft","\\Lsh","\\upuparrows","\\upharpoonleft","\\downharpoonleft","\\origof","\\imageof","\\multimap","\\leftrightsquigarrow","\\rightrightarrows","\\rightleftarrows","\\twoheadrightarrow","\\rightarrowtail","\\looparrowright","\\curvearrowright","\\circlearrowright","\\Rsh","\\downdownarrows","\\upharpoonright","\\downharpoonright","\\rightsquigarrow","\\leadsto","\\Rrightarrow","\\restriction","\\$","\\%","\\_","\\angle","\\infty","\\prime","\\triangle","\\Gamma","\\Delta","\\Theta","\\Lambda","\\Xi","\\Pi","\\Sigma","\\Upsilon","\\Phi","\\Psi","\\Omega","\\neg","\\lnot","\\top","\\bot","\\emptyset","\\varnothing","\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta","\\eta","\\theta","\\iota","\\kappa","\\lambda","\\mu","\\nu","\\xi","\\omicron","\\pi","\\rho","\\sigma","\\tau","\\upsilon","\\phi","\\chi","\\psi","\\omega","\\varepsilon","\\vartheta","\\varpi","\\varrho","\\varsigma","\\varphi","\\cdot","\\circ","\\div","\\pm","\\times","\\cap","\\cup","\\setminus","\\land","\\lor","\\wedge","\\vee","\\surd","\\langle","\\lvert","\\lVert","\\rangle","\\rvert","\\rVert","\\approx","\\cong","\\ge","\\geq","\\gets","\\gt","\\in","\\@not","\\subset","\\supset","\\subseteq","\\supseteq","\\nsubseteq","\\nsupseteq","\\models","\\leftarrow","\\le","\\leq","\\lt","\\rightarrow","\\to","\\ngeq","\\nleq","\\ ","\\space","\\nobreakspace","\\nobreak","\\allowbreak","\\barwedge","\\veebar","\\odot","\\oplus","\\otimes","\\partial","\\oslash","\\circledcirc","\\boxdot","\\bigtriangleup","\\bigtriangledown","\\dagger","\\diamond","\\star","\\triangleleft","\\triangleright","\\{","\\}","\\lbrace","\\rbrace","\\lbrack","\\rbrack","\\lparen","\\rparen","\\lfloor","\\rfloor","\\lceil","\\rceil","\\backslash","\\vert","\\|","\\Vert","\\uparrow","\\Uparrow","\\downarrow","\\Downarrow","\\updownarrow","\\Updownarrow","\\mathellipsis","\\ldots","\\@cdots","\\ddots","\\varvdots","\\@imath","\\@jmath","\\degree","\\pounds","\\mathsterling","\\maltese","\\textdagger","\\textdaggerdbl","\\textdollar","\\textunderscore","\\textbraceleft","\\textbraceright","\\textless","\\textgreater","\\textbar","\\textbardbl","\\textasciitilde","\\textbackslash","\\textasciicircum","\\textellipsis","\\i","\\j","\\ss","\\ae","\\oe","\\o","\\AE","\\OE","\\O","\\textendash","\\textemdash","\\textquoteleft","\\textquoteright","\\textquotedblleft","\\textquotedblright","\\textdegree","\\textsterling","\\operatorname","\\noexpand","\\expandafter","\\@firstoftwo","\\@secondoftwo","\\@ifnextchar","\\@ifstar","\\TextOrMath","\\char","\\newcommand","\\renewcommand","\\providecommand","\\message","\\errmessage","\\show","\\bgroup","\\egroup","\\lq","\\rq","\\aa","\\AA","\\textcopyright","\\copyright","\\textregistered","\\Bbbk","\\llap","\\rlap","\\clap","\\mathstrut","\\underbar","\\not","\\neq","\\ne","\\notin","\\ulcorner","\\urcorner","\\llcorner","\\lrcorner","\\vdots","\\varGamma","\\varDelta","\\varTheta","\\varLambda","\\varXi","\\varPi","\\varSigma","\\varUpsilon","\\varPhi","\\varPsi","\\varOmega","\\substack","\\colon","\\boxed","\\iff","\\implies","\\impliedby","\\dots","\\dotso","\\dotsc","\\cdots","\\dotsb","\\dotsm","\\dotsi","\\dotsx","\\DOTSI","\\DOTSB","\\DOTSX","\\tmspace","\\,","\\thinspace","\\>","\\:","\\medspace","\\;","\\thickspace","\\!","\\negthinspace","\\negmedspace","\\negthickspace","\\enspace","\\enskip","\\quad","\\qquad","\\tag","\\tag@paren","\\tag@literal","\\bmod","\\pod","\\pmod","\\mod","\\pmb","\\newline","\\TeX","\\LaTeX","\\KaTeX","\\hspace","\\@hspace","\\@hspacer","\\ordinarycolon","\\vcentcolon","\\dblcolon","\\coloneqq","\\Coloneqq","\\coloneq","\\Coloneq","\\eqqcolon","\\Eqqcolon","\\eqcolon","\\Eqcolon","\\colonapprox","\\Colonapprox","\\colonsim","\\Colonsim","\\ratio","\\coloncolon","\\colonequals","\\coloncolonequals","\\equalscolon","\\equalscoloncolon","\\colonminus","\\coloncolonminus","\\minuscolon","\\minuscoloncolon","\\coloncolonapprox","\\coloncolonsim","\\simcolon","\\simcoloncolon","\\approxcolon","\\approxcoloncolon","\\notni","\\limsup","\\liminf","\\injlim","\\projlim","\\varlimsup","\\varliminf","\\varinjlim","\\varprojlim","\\gvertneqq","\\lvertneqq","\\ngeqq","\\ngeqslant","\\nleqq","\\nleqslant","\\nshortmid","\\nshortparallel","\\nsubseteqq","\\nsupseteqq","\\varsubsetneq","\\varsubsetneqq","\\varsupsetneq","\\varsupsetneqq","\\imath","\\jmath","\\llbracket","\\rrbracket","\\lBrace","\\rBrace","\\minuso","\\darr","\\dArr","\\Darr","\\lang","\\rang","\\uarr","\\uArr","\\Uarr","\\N","\\R","\\Z","\\alef","\\alefsym","\\Alpha","\\Beta","\\bull","\\Chi","\\clubs","\\cnums","\\Complex","\\Dagger","\\diamonds","\\empty","\\Epsilon","\\Eta","\\exist","\\harr","\\hArr","\\Harr","\\hearts","\\image","\\infin","\\Iota","\\isin","\\Kappa","\\larr","\\lArr","\\Larr","\\lrarr","\\lrArr","\\Lrarr","\\Mu","\\natnums","\\Nu","\\Omicron","\\plusmn","\\rarr","\\rArr","\\Rarr","\\real","\\reals","\\Reals","\\Rho","\\sdot","\\sect","\\spades","\\sub","\\sube","\\supe","\\Tau","\\thetasym","\\weierp","\\Zeta","\\argmin","\\argmax","\\plim","\\bra","\\ket","\\braket","\\Bra","\\Ket","\\angln","\\blue","\\orange","\\pink","\\red","\\green","\\gray","\\purple","\\blueA","\\blueB","\\blueC","\\blueD","\\blueE","\\tealA","\\tealB","\\tealC","\\tealD","\\tealE","\\greenA","\\greenB","\\greenC","\\greenD","\\greenE","\\goldA","\\goldB","\\goldC","\\goldD","\\goldE","\\redA","\\redB","\\redC","\\redD","\\redE","\\maroonA","\\maroonB","\\maroonC","\\maroonD","\\maroonE","\\purpleA","\\purpleB","\\purpleC","\\purpleD","\\purpleE","\\mintA","\\mintB","\\mintC","\\grayA","\\grayB","\\grayC","\\grayD","\\grayE","\\grayF","\\grayG","\\grayH","\\grayI","\\kaBlue","\\kaGreen","\\ce"] ================================================ FILE: packages/rebber-plugins/src/preprocessors/mathEscape.js ================================================ const katexConstants = require('../../src/preprocessors/katexConstants.json') const endOfCommandChars = ['\\', '[', ']', '{', '}', ' ', '(', ')', '\t', '\n', '^', '_', ','] function isEndOfCommand (nodeLine, index) { if (index >= nodeLine.length) { return true } return endOfCommandChars.includes(nodeLine.charAt(index)) } module.exports = () => node => { let commandStart = node.value.indexOf('\\') while (commandStart !== -1) { // Eat leading backslashes (at most two) let leadSlashes = 1 let isSlash = (node.value.charAt(commandStart + leadSlashes) === '\\') while (isSlash && leadSlashes < 2) { isSlash = (node.value.charAt(commandStart + leadSlashes) === '\\') leadSlashes++ } let currentCommand = '' // Find end of command let potentialEnd = leadSlashes + commandStart while (!isEndOfCommand(node.value, potentialEnd)) { currentCommand += node.value.charAt(potentialEnd) potentialEnd++ } // if we just had a \{ or \, for example if (currentCommand === '' && potentialEnd < node.value.length && katexConstants.includes(`\\${node.value.charAt(potentialEnd)}`)) { currentCommand = node.value.charAt(potentialEnd) } // Check for unknown commands const slashedCommand = '\\'.repeat(leadSlashes).concat(currentCommand) const commandLength = slashedCommand.length if (!katexConstants.includes(slashedCommand)) { const beforeCommand = node.value.substring(0, commandStart) const afterCommand = node.value.substring(commandStart + commandLength, node.value.length) node.value = `${beforeCommand} ${afterCommand}` // As we changed the command we need to restart from the beginning potentialEnd = 1 } commandStart = node.value.indexOf('\\', potentialEnd) } // Check count of brackets const openingBracesCount = (node.value.match(/(? 0) { node.value = '{'.repeat(bracesDelta) + node.value } } ================================================ FILE: packages/rebber-plugins/src/preprocessors/prepareQuizz.js ================================================ const clone = require('clone') const visit = require('unist-util-visit') module.exports = processQuizzFactory function processQuizzFactory (ctx) { const correctionTitle = ctx.correctionTitle || 'Correction' return (node, index, parent) => { if (node.children.length < 2 || !node.children[0].children || !node.children[1].children) { return } node.type = 'neutralCustomBlock' const correction = clone(node) correction.type = 'sCustomBlock' correction.children[0].children[0].value = correctionTitle parent.children.splice(index + 1, 0, correction) const bodyChildren = node.children[1].children while (bodyChildren.length > 0 && bodyChildren[bodyChildren.length - 1].type !== 'list') { bodyChildren.splice(bodyChildren.length - 1, 1) } visit(node, 'listItem', (itemNode) => { itemNode.checked = null }) } } ================================================ FILE: packages/rebber-plugins/src/preprocessors/spoilerFlatten.js ================================================ const visit = require('unist-util-visit') module.exports = (elemNames) => (_, tree) => { // This should contain something like ['sCustomBlockBody', 'secretCustomBlockBody'] const elemNamesBody = elemNames.map(v => v.concat('Body')) // Iterate over the elements to be flattened for (const elemNameBody of elemNamesBody) { visit(tree, elemNameBody, node => { // Recursive function that flattens all matching elements and keeps the others function flattenTree (blockTree) { return blockTree.map(v => { if (elemNames.includes(v.type)) { // Get sCustomBlock > sCustomBlockBody > * const newTree = v.children .filter(v => elemNamesBody.includes(v.type)) // This is sub-optimal, but flatMap doesn't have good support by now .reduce((acc, e) => acc.concat(e.children), []) // First level of recursion: on direct descendents return flattenTree(newTree) } else { // Second level of recursion: on indirect descendents if (v.children) { v.children = flattenTree(v.children) } return v } }).reduce((acc, e) => acc.concat(e), []) } // First round on direct children node.children = flattenTree(node.children) }) } } ================================================ FILE: packages/rebber-plugins/src/type/abbr.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') const escape = require('rebber/dist/escaper') /* Expose. */ module.exports = abbrPlugin function abbrPlugin (ctx, node) { const abbr = all(ctx, node) const reference = escape(node.reference) if (ctx.abbr && typeof ctx.abbr === 'function') { return ctx.abbr(abbr, reference) } return `\\abbr{${abbr}}{${reference}}` } ================================================ FILE: packages/rebber-plugins/src/type/align.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = align const defaultMacros = { leftAligned: (innerText) => `\n\n${innerText}\n\n`, centerAligned: (innerText) => `\n{\\centering ${innerText}}\n`, rightAligned: (innerText) => `\n{\\raggedleft\n${innerText}}\n`, defaultType: (innerText, type) => `\n\\begin{${type}}\n${innerText}\n\\end{${type}}\n` } function align (ctx, node) { const macro = ctx[node.type] || defaultMacros[node.type] || defaultMacros.defaultType const innerText = all(ctx, node) return macro(innerText, node.type) } ================================================ FILE: packages/rebber-plugins/src/type/appendix.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') const clone = require('clone') /* Expose. */ module.exports = appendixPlugin function definitionMacro (ctx, identifier, url) { return `\\label{${identifier}}${url}` } function appendixPlugin (ctx, node) { if (node.children.length === 0) { return '' } const overriddenCtx = clone(ctx) overriddenCtx.definition = definitionMacro const innerText = all(overriddenCtx, node) return `\\begin{appendices}\n${innerText.trimEnd()}\n\\end{appendices}` } ================================================ FILE: packages/rebber-plugins/src/type/comments.js ================================================ /* Expose. */ module.exports = comments /* Stringify a comments `node`. */ function comments (ctx, node) { return `\\begin{comment}\n${node.data.comment}\n\\end{comment}\n` } ================================================ FILE: packages/rebber-plugins/src/type/conclusion.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = conclusion const conclusionMacros = [ content => `\\begin{LevelOneConclusion}\n${content}\n\\end{LevelOneConclusion}`, content => `\\begin{LevelTwoConclusion}\n${content}\n\\end{LevelTwoConclusion}`, content => `\\begin{LevelThreeConclusion}\n${content}\n\\end{LevelThreeConclusion}` ] /* Stringify an conclusion `node`. */ function conclusion (ctx, node) { const level = node.data.level || 0 const macro = ctx[node.type] || conclusionMacros[level] const innerText = all(ctx, node) return `${macro(innerText.trim())}\n\n` } ================================================ FILE: packages/rebber-plugins/src/type/customBlocks.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = customBlock const defaultMacros = { defaultBlock: (environmentName, blockTitle, blockContent) => { return `\\begin{${environmentName}}${blockTitle ? `[{{${blockTitle}}}]` : ''}` + `\n${blockContent}` + `\n\\end{${environmentName}}\n` } } function customBlock (ctx, node) { const blockMacro = ctx[node.type] || defaultMacros[node.type] || defaultMacros.defaultBlock let blockTitle = '' if (node.children && node.children.length) { if (node.children[0].type.endsWith('CustomBlockHeading')) { const titleNode = node.children.splice(0, 1)[0] blockTitle = all(ctx, titleNode).trim() } } node.children[0].type = 'paragraph' const blockContent = all(ctx, node).trim() const options = ctx.customBlocks || {} let environmentName const type = node.type.replace('CustomBlock', '') if (options.map && options.map[type]) { environmentName = options.map[type] } else { environmentName = type[0].toUpperCase() + type.substring(1) } return blockMacro(environmentName, blockTitle, blockContent) } ================================================ FILE: packages/rebber-plugins/src/type/emoticon.js ================================================ /* Dependencies. */ const has = require('has') /* Expose. */ module.exports = emoticon /* Stringify an emoticon `node`. */ function emoticon (ctx, node) { const code = node.value if (!ctx.emoticons.emoticons || !has(ctx.emoticons.emoticons, code)) return return `\\smiley{${ctx.emoticons.emoticons[code]}}` } ================================================ FILE: packages/rebber-plugins/src/type/figure.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') const one = require('rebber/dist/one') const defaultCodeStringifier = require('rebber/dist/types/code').macro const has = require('has') /* Expose. */ module.exports = figure const defaultMacros = { blockquote: (_, innerText, caption = 'Anonymous') => `\\begin{Quotation}[${caption}]\n${innerText}\n\\end{Quotation}\n\n`, code: (ctx, code, caption, extra) => { const codeStringifier = (has(ctx, 'code') && ctx.code) || defaultCodeStringifier // Remove the two last line feed const rebberCode = codeStringifier(code, extra.language, extra.others).slice(0, -2) return `${rebberCode}\n\\captionof{listing}{${caption}}\n\n` }, image: (_1, _2, caption, extra) => '\\begin{center}\n' + `\\includegraphics${extra.width ? `[${extra.width}]` : ''}{${extra.url}}\n` + `\\captionof{figure}{${caption}}\n` + '\\end{center}\n' } const makeExtra = { blockquote: node => {}, code: (node) => { return { language: node.lang || 'text', others: node.meta } }, image: node => ({ url: node.url, width: '\\linewidth' }) } /* Stringify a Figure `node`. */ function figure (ctx, node, index, parent) { const type = node.children[0].type const macro = (has(ctx, 'figure') && has(ctx.figure, type) && ctx.figure[type]) || (has(defaultMacros, type) && defaultMacros[type]) let caption = '' if (node.children.length) { caption = node.children .filter(captionNode => captionNode.type === 'figcaption') .map(captionNode => all(ctx, captionNode)) .join('') } node.caption = caption // allows to add caption to the default processing if (!macro) { node.children[0].caption = caption return one(ctx, node.children[0], 0, node) } const wrappedNode = node.children[0] wrappedNode.caption = node.caption node.children = node.children.filter(node => node.type !== 'figcaption') if (node.children.length === 1) { node.children = node.children[0].children } const extra = has(makeExtra, type) ? makeExtra[type](wrappedNode) : undefined const innerText = all(ctx, node) || node.value || '' return macro(ctx, innerText.trim(), caption, extra) } ================================================ FILE: packages/rebber-plugins/src/type/footnote.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = notes const defaultMacro = (identifier, text, protect) => { const footnote = `${protect ? '\\protect' : ''}\\footnote[${identifier}]{${text}}` return footnote } function autoId (node) { const { line, column, offset } = node.position.start return `l${line}c${column}o${offset}` } /* Stringify a footnote `node`. */ function notes (ctx, node) { const macro = ctx.footnote || defaultMacro const protect = Boolean(node.commandProtect) const identifier = autoId(node) return macro(identifier, all(ctx, node).trim(), protect) } ================================================ FILE: packages/rebber-plugins/src/type/footnoteDefinition.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = notes const defaultMacro = (identifier, text, protect) => `${protect ? '\\protect' : ''}\\footnotetext[${identifier}]{${text}}` function notes (ctx, node) { const macro = ctx.footnoteDefinition || defaultMacro const protect = Boolean(node.commandProtect) return macro(node.identifier, all(ctx, node).trim(), protect) } ================================================ FILE: packages/rebber-plugins/src/type/footnoteReference.js ================================================ /* Expose. */ module.exports = notes const defaultMacro = (identifier, protect) => `\\textsuperscript{${protect ? '\\protect' : ''}\\footnotemark[${identifier}]}` function notes (ctx, node) { const macro = ctx.footnoteReference || defaultMacro const protect = Boolean(node.commandProtect) return macro(node.identifier, protect) } ================================================ FILE: packages/rebber-plugins/src/type/gridTable.js ================================================ /* Dependencies. */ const clone = require('clone') const tableCell = require('rebber/dist/types/tableCell') const tableRow = require('rebber/dist/types/tableRow') const table = require('rebber/dist/types/table') const text = require('rebber/dist/types/text') const paragraph = require('rebber/dist/types/paragraph') /* Expose. */ module.exports = gridTable class MultiRowLine { constructor (startRow, endRow, startCell, endCell, colSpan, endOfLine) { this.multilineCounter = endRow - startRow this.startCell = startCell this.endCell = endCell this.colSpan = colSpan this.endOfLine = endOfLine } getCLine () { let startCLine = 1 let endCLine = this.startCell - 1 // case where the multi row line is at the start of the table if (this.startCell === 1) { startCLine = this.startCell + this.colSpan endCLine = this.endOfLine } else if (this.startCell > 1 && (this.startCell + this.colSpan) < this.endOfLine) { // case where the multi row line is in the middle of the table const clineBefore = `\\cline{1-${this.startCell - 1}}` const clineAfter = `\\cline{${this.startCell + this.colSpan}-${this.endOfLine}}` return `${clineBefore} ${clineAfter}` } return `\\cline{${startCLine}-${endCLine}}` } } class GridTableStringifier { constructor () { this.lastMultiRowLine = null this.currentSpan = 0 this.rowIndex = 0 this.colIndex = 0 this.multiLineCellIndex = 0 this.colSpan = 1 this.nbOfColumns = 0 } gridTableCell (ctx, node) { const overriddenCtx = clone(ctx) this.colIndex++ overriddenCtx.tableCell = undefined // we have to replace \n by \endgraf only in text node, not in other // see #352 overriddenCtx.overrides.text = (c, n, index, parent) => text(c, n, index, parent) .replace(/\n/g, ' \\endgraf ') overriddenCtx.overrides.paragraph = (c, n) => `${paragraph(c, n).trim()} \\endgraf \\endgraf ` let baseText = tableCell(overriddenCtx, node).trim() while (baseText.substring(baseText.length - '\\endgraf'.length) === '\\endgraf') { baseText = baseText.substring(0, baseText.length - '\\endgraf'.length).trim() } if (node.data && node.data.hProperties.rowSpan > 1) { this.currentSpan = node.data.hProperties.rowSpan this.multiLineCellIndex = this.colIndex baseText = `\\SetCell[r=${this.currentSpan}]{l} ${baseText}` this.colSpan = node.data.hProperties.colSpan > 1 ? node.data.hProperties.colSpan : 1 } else if (node.data && node.data.hProperties.colSpan > 1) { const colSpan = node.data.hProperties.colSpan baseText = `\\SetCell[c=${colSpan}]{l} ${baseText}` } if (node.data && node.data.hProperties.colSpan > 1) { this.colIndex -= 1 this.colIndex += node.data.hProperties.colSpan } return baseText } gridTableRow (ctx, node, index) { const overriddenCtx = clone(ctx) overriddenCtx.tableRow = undefined this.rowIndex++ const extraCell = { type: 'tableCell', children: [{ type: 'paragraph', children: [{ type: 'text', value: ' ' }] }] } // Duplicate cells with colSpan greater than one for (let i = 0; i < node.children.length; i++) { if (!node.children[i].data) continue const colSpan = node.children[i].data.hProperties.colSpan if (!colSpan || colSpan <= 1) continue for (let j = 0; j < colSpan - 1; j++) { node.children.splice(i + 1, 0, extraCell) } } if (this.previousRowWasMulti()) { const lastMultiRowline = this.flushMultiRowLineIfNeeded() for (let i = 0; i < lastMultiRowline.colSpan; i++) { node.children.splice(lastMultiRowline.startCell - 1, 0, extraCell) } this.colIndex = 0 let rowStr = tableRow(overriddenCtx, node, index) if (lastMultiRowline.multilineCounter > 0) { rowStr = rowStr.replace(/\\hline/, lastMultiRowline.getCLine()) } this.colIndex = 0 return rowStr } let rowText = tableRow(overriddenCtx, node, index) if (this.currentSpan !== 0) { this.lastMultiRowLine = new MultiRowLine( this.rowIndex, this.rowIndex + this.currentSpan + (-1), this.multiLineCellIndex, this.colIndex + this.colSpan, this.colSpan, this.colIndex ) rowText = rowText.replace(/\\hline/, this.lastMultiRowLine.getCLine()) } this.currentSpan = 0 if (this.colIndex >= this.nbOfColumns) { this.nbOfColumns = this.colIndex } this.colIndex = 0 return rowText } flushMultiRowLineIfNeeded () { if (!this.lastMultiRowLine) { return null } const row = this.lastMultiRowLine if (row.multilineCounter >= 1) { row.multilineCounter-- } if (row.multilineCounter === 0) { this.lastMultiRowLine = null } return row } gridTableheaderCounter (node) { const tableHeaders = node.children .filter(n => n.data && n.data.hName === 'thead') return tableHeaders.length >= 1 ? tableHeaders[0].children.length : 0 } gridTableHeaderParse () { return ' X[-1]'.repeat(this.nbOfColumns).substring(1) } previousRowWasMulti () { return this.lastMultiRowLine !== null } } function gridTable (ctx, node) { const overriddenCtx = clone(ctx) const stringifier = new GridTableStringifier() // Inside tables, `\\\\` won't work overriddenCtx.break = () => ' \\endgraf' overriddenCtx.tableCell = stringifier.gridTableCell.bind(stringifier) overriddenCtx.tableRow = stringifier.gridTableRow.bind(stringifier) overriddenCtx.headerCounter = stringifier.gridTableheaderCounter.bind(stringifier) overriddenCtx.headerParse = stringifier.gridTableHeaderParse.bind(stringifier) overriddenCtx.image = overriddenCtx.image ? overriddenCtx.image : {} overriddenCtx.image.inlineMatcher = () => true return table(overriddenCtx, node).replace(/\\number-of-column/gm, stringifier.nbOfColumns) } ================================================ FILE: packages/rebber-plugins/src/type/introduction.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = introduction const introductionMacros = [ content => `\\begin{LevelOneIntroduction}\n${content}\n\\end{LevelOneIntroduction}`, content => `\\begin{LevelTwoIntroduction}\n${content}\n\\end{LevelTwoIntroduction}`, content => `\\begin{LevelThreeIntroduction}\n${content}\n\\end{LevelThreeIntroduction}` ] /* Stringify an introduction `node`. */ function introduction (ctx, node) { const level = node.data.level || 0 const macro = ctx[node.type] || introductionMacros[level] const innerText = all(ctx, node) return `${macro(innerText.trim())}\n\n` } ================================================ FILE: packages/rebber-plugins/src/type/kbd.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = kbd /* Stringify a kbd `node`. */ function kbd (ctx, node) { const contents = all(ctx, node) return `\\keys{${contents}}` } ================================================ FILE: packages/rebber-plugins/src/type/math.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') const has = require('has') /* Expose. */ module.exports = math const defaultMacros = { inlineMath: (content) => `$${content}$`, inlineMathDouble: (content) => `$$${content}$$`, math: (content) => `\\[ ${content} \\]\n\n` } /* Stringify a Figure `node`. */ function math (ctx, node, index, parent) { let type = 'math' if (node.type === 'inlineMath') { try { const classes = node.data.hProperties.className type = classes.includes('math-display') ? 'inlineMathDouble' : 'inlineMath' } catch (e) { console.error(e, 'This rebber math plugin is only compatible with remark-math.') } } const macro = (has(ctx, 'math') && has(ctx.math, type) && ctx.math[type]) || (has(defaultMacros, type) && defaultMacros[type]) const content = all(ctx, node) || node.value || '' return macro(content.trim()) } ================================================ FILE: packages/rebber-plugins/src/type/ping.js ================================================ /* Expose. */ module.exports = ping /* Stringify a `ping` node. */ function ping (_, node) { return `\\ping{${node.username}}` } ================================================ FILE: packages/rebber-plugins/src/type/sub.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = sub /* Stringify a sub `node`. */ function sub (ctx, node, index, parent) { const contents = all(ctx, node) return `\\textsubscript{${contents}}` } ================================================ FILE: packages/rebber-plugins/src/type/sup.js ================================================ /* Dependencies. */ const all = require('rebber/dist/all') /* Expose. */ module.exports = sup /* Stringify a sup `node`. */ function sup (ctx, node, index, parent) { const contents = all(ctx, node) return `\\textsuperscript{${contents}}` } ================================================ FILE: packages/rebber-plugins/src/type/tableHeader.js ================================================ module.exports = (ctx, node, index, parent) => { const one = require('rebber/dist/one') if (ctx.tableHeader) { return ctx.tableHeader(node.value) } if (index === 0 && parent.children.length > 1) { // if we are on first header row we do not want to switch back to // font of normal serie return node.children.map((n, childIndex) => one(ctx, n, childIndex === 0 ? 0 : 2, node)) .join('') } const parsed = node.children.map((n, childIndex) => one(ctx, n, index + childIndex, node)) return parsed.join('') } ================================================ FILE: packages/rehype-footnotes-title/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/rehype-footnotes-title/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/rehype-footnotes-title/README.md ================================================ This plugin adds a `title` attribute to the footnote links, mainly for accessibility purpose. ```diff ``` Usage: * `.use(footnotesTitles, 'Jump to reference')` * `.use(footnotesTitles, 'Going back to footnote with id $id')` * If `$id` is found in the string, it gets replaced with the footnote identifier. ================================================ FILE: packages/rehype-footnotes-title/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly with first config 1`] = ` "

This is the body with a footnote1 or two2 or more3 4 5.

Also a reference that does not exist6.

Second line of first paragraph. And then third...


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of the footnote : nice!

" `; exports[`renders correctly with second config 1`] = ` "

This is the body with a footnote1 or two2 or more3 4 5.

Also a reference that does not exist6.

Second line of first paragraph. And then third...


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of the footnote : nice!

" `; exports[`renders correctly without config 1`] = ` "

This is the body with a footnote1 or two2 or more3 4 5.

Also a reference that does not exist6.

Second line of first paragraph. And then third...


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of the footnote : nice!

" `; ================================================ FILE: packages/rehype-footnotes-title/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import footnotes from 'remark-footnotes' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import footnotesTitle from '../src/' const render = config => unified() .use(reParse) .use(remark2rehype) .use(footnotes, {inlineNotes: true}) .use(footnotesTitle, config) .use(stringify) .processSync(dedent` This is the body with a footnote[^1] or two[^2] or more[^3] [^4] [^5]. Also a reference that does not exist[^6]. [^1]: Footnote that ends with a list: * item 1 * item 2 [^2]: > This footnote is a blockquote. [^3]: A simple oneliner. [^4]: A footnote with multiple paragraphs. Paragraph two. [^5]: First line of first paragraph. Second line of the footnote : nice! Second line of first paragraph. And then third... `) it('renders correctly with first config', () => { const {contents} = render('Foo bar $id') expect(contents).toMatchSnapshot() }) it('renders correctly with second config', () => { const {contents} = render('Baz $id qux?') expect(contents).toMatchSnapshot() }) it('renders correctly without config', () => { const {contents} = render() expect(contents).toMatchSnapshot() }) ================================================ FILE: packages/rehype-footnotes-title/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); function findLastTag(node, tag = 'p') { if (!node.children || !node.children.length) return; const links = node.children.filter(e => e.tagName === tag); if (!links.length) return; return links[links.length - 1]; } function findLastLink(node, className) { if (!node.children || !node.children.length) return; const links = node.children.filter(e => e.tagName === 'a'); if (!links.length) return; const aTag = links[links.length - 1]; if (!aTag.properties || !aTag.properties.className || !aTag.properties.className.includes(className)) return; return aTag; } function plugin(title = '') { function transformer(tree) { visit(tree, 'element', visitor); } function visitor(node, index, parent) { if (node.tagName === 'li' && node.properties.id) { if (!node.children || !node.children.length) return; let aTag = findLastLink(node, 'footnote-backref'); if (!aTag) { const pTag = findLastTag(node, 'p'); aTag = findLastLink(pTag, 'footnote-backref'); } if (!aTag) return; const identifier = node.properties.id.slice(3); const placeholderIndex = title.indexOf('$id'); let thisTitle; if (placeholderIndex !== -1) { thisTitle = title.split(''); thisTitle.splice(placeholderIndex, 3, identifier); thisTitle = thisTitle.join(''); } if (!thisTitle) thisTitle = identifier; aTag.properties.title = thisTitle; } } return transformer; } module.exports = plugin; ================================================ FILE: packages/rehype-footnotes-title/package.json ================================================ { "name": "rehype-footnotes-title", "version": "2.0.1", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rehype-footnotes-title", "type": "git" }, "author": "Victor Felder (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/rehype-footnotes-title/src/index.js ================================================ const visit = require('unist-util-visit') function findLastTag (node, tag = 'p') { if (!node.children || !node.children.length) return const links = node.children.filter(e => e.tagName === tag) if (!links.length) return return links[links.length - 1] } function findLastLink (node, className) { if (!node.children || !node.children.length) return const links = node.children.filter(e => e.tagName === 'a') if (!links.length) return const aTag = links[links.length - 1] if (!aTag.properties || !aTag.properties.className || !aTag.properties.className.includes(className)) return return aTag } function plugin (title = '') { function transformer (tree) { visit(tree, 'element', visitor) } function visitor (node, index, parent) { if (node.tagName === 'li' && node.properties.id) { if (!node.children || !node.children.length) return let aTag = findLastLink(node, 'footnote-backref') if (!aTag) { const pTag = findLastTag(node, 'p') aTag = findLastLink(pTag, 'footnote-backref') } if (!aTag) return const identifier = node.properties.id.slice(3) const placeholderIndex = title.indexOf('$id') let thisTitle if (placeholderIndex !== -1) { thisTitle = title.split('') thisTitle.splice(placeholderIndex, 3, identifier) thisTitle = thisTitle.join('') } if (!thisTitle) thisTitle = identifier aTag.properties.title = thisTitle } } return transformer } module.exports = plugin ================================================ FILE: packages/rehype-html-blocks/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/rehype-html-blocks/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/rehype-html-blocks/README.md ================================================ This plugin wraps (multi-line) raw HTML in `p`. See fixtures for example behavior. ================================================ FILE: packages/rehype-html-blocks/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should process inline html with rehype-html-blocks 1`] = ` "

Here's a simple block:

<div> foo </div>

This should be a code block, though:

<div> foo </div>

As should this:

<div>foo</div>

Now, nested:

<div> <div> <div> foo </div> </div> </div>

This should just be an HTML comment:

<!-- Comment -->

Multiline:

<!-- Blah Blah -->

Code block:

<!-- Comment -->

Just plain comment, with trailing spaces on the line:

<!-- foo -->

Code:

<hr />

Hr's:

<hr>

<hr/>

<hr />

<hr>

<hr/>

<hr />

<hr class=\\"foo\\" id=\\"bar\\" />

<hr class=\\"foo\\" id=\\"bar\\"/>

<hr class=\\"foo\\" id=\\"bar\\" >

<some weird stuff>

" `; exports[`should process inline html without rehype-html-blocks 1`] = ` "

Here's a simple block:

<div> foo </div>

This should be a code block, though:

<div> foo </div>

As should this:

<div>foo</div>

Now, nested:

<div> <div> <div> foo </div> </div> </div>

This should just be an HTML comment:

<!-- Comment -->

Multiline:

<!-- Blah Blah -->

Code block:

<!-- Comment -->

Just plain comment, with trailing spaces on the line:

<!-- foo -->

Code:

<hr />

Hr's:

<hr> <hr/> <hr /> <hr> <hr/> <hr /> <hr class=\\"foo\\" id=\\"bar\\" /> <hr class=\\"foo\\" id=\\"bar\\"/> <hr class=\\"foo\\" id=\\"bar\\" >

<some weird stuff>

" `; exports[`should process multi-line tags with rehype-html-blocks 1`] = ` "

<div>

asdf asdfasd

</div>

<div>

foo bar

</div> No blank line.

" `; exports[`should process multi-line tags without rehype-html-blocks 1`] = ` "<div>

asdf asdfasd

</div> <div>

foo bar

</div> No blank line." `; exports[`should process simple div with rehype-html-blocks 1`] = ` "

<div id=\\"sidebar\\">

foo

</div>

And now in uppercase:

<DIV> foo </DIV>

" `; exports[`should process simple div without rehype-html-blocks 1`] = ` "<div id=\\"sidebar\\">

foo

</div>

And now in uppercase:

<DIV> foo </DIV>" `; exports[`should render the same with rehype-html-blocks 1`] = ` "

<DIV> foo </DIV>

" `; exports[`should render the same without rehype-html-blocks 1`] = ` "

<DIV> foo </DIV>

" `; ================================================ FILE: packages/rehype-html-blocks/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import rehypeStringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import htmlBlocks from '../src/' const render = (use, text) => unified() .use(reParse, { gfm: true, commonmark: false, footnotes: true, /* sets list of known blocks to nothing, otherwise

hey

would become <h3>hey</h3> instead of

<h3>hey</h3>

*/ blocks: [], }) .use(remark2rehype, {allowDangerousHtml: true}) .use(use ? htmlBlocks : () => {}) .use(rehypeStringify) .processSync(text) Array.from([[true, 'with'], [false, 'without']]).forEach(([use, str]) => { it(`should process simple div ${str} rehype-html-blocks`, () => { const {contents} = render(use, dedent` And now in uppercase:
foo
`) expect(contents).toMatchSnapshot() }) it(`should process inline html ${str} rehype-html-blocks`, () => { const {contents} = render(use, dedent` Here's a simple block:
foo
This should be a code block, though:
foo
As should this:
foo
Now, nested:
foo
This should just be an HTML comment: Multiline: Code block: Just plain comment, with trailing spaces on the line: Code:
Hr's:








`) expect(contents).toMatchSnapshot() }) it(`should process multi-line tags ${str} rehype-html-blocks`, () => { const {contents} = render(use, dedent`
asdf asdfasd
foo bar
No blank line. `) expect(contents).toMatchSnapshot() }) it(`should render the same ${str} rehype-html-blocks`, () => { const {contents} = render(use, dedent` **
foo
** `) expect(contents).toMatchSnapshot() }) }) ================================================ FILE: packages/rehype-html-blocks/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); const inline = ['a', 'b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'time', 'var', 'bdo', 'br', 'img', 'map', 'object', 'p', 'q', 'script', 'span', 'sub', 'sup', 'button', 'input', 'label', 'select', 'textarea']; function plugin() { return transformer; } function transformer(tree) { visit(tree, 'raw', visitor); } function visitor(node, index, parent) { let replacement = { type: 'text', value: node.value }; if (!inline.includes(parent.tagName)) { replacement = { type: 'element', tagName: 'p', properties: {}, children: [{ type: 'text', value: node.value }] }; } parent.children[index] = replacement; } module.exports = plugin; ================================================ FILE: packages/rehype-html-blocks/package.json ================================================ { "name": "rehype-html-blocks", "version": "0.1.2", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rehype-html-blocks", "type": "git" }, "author": "Victor Felder (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "rehype" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/rehype-html-blocks/src/index.js ================================================ const visit = require('unist-util-visit') const inline = [ 'a', 'b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'time', 'var', 'bdo', 'br', 'img', 'map', 'object', 'p', 'q', 'script', 'span', 'sub', 'sup', 'button', 'input', 'label', 'select', 'textarea' ] function plugin () { return transformer } function transformer (tree) { visit(tree, 'raw', visitor) } function visitor (node, index, parent) { let replacement = { type: 'text', value: node.value } if (!inline.includes(parent.tagName)) { replacement = { type: 'element', tagName: 'p', properties: {}, children: [{ type: 'text', value: node.value }] } } parent.children[index] = replacement } module.exports = plugin ================================================ FILE: packages/rehype-postfix-footnote-anchors/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/rehype-postfix-footnote-anchors/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/rehype-postfix-footnote-anchors/README.md ================================================ # rehype-postfix-footnote-anchors [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This [rehype][rehype] plugin appends a custom postfix to footnotes, or changes the anchors/IDs to/from footnotes. When you render several pieces of Markdown to HTML in a same webpage you might want to make sure that footnotes will not conflict between each piece of rendered HTML. For instance: * ```md foo[^foo] [^foo]: Footnote :) ``` * ```md bar[^foo] [^foo]: Conflict? ``` Rendering both of these will have the note next to `bar` link to `Footnote :)` instead of `Conflict?`, and `Conflict?` will have a link to go back to `foo` instead of `bar`. This plugin plays well with [remark-numbered-footnotes](https://www.npmjs.com/package/remark-numbered-footnotes). Using `remark-numbered-footnotes` increases the risks of conflicts, hence the interest of postfixing footnote anchors. ## Installation [npm][npm]: ```bash npm install rehype-postfix-footnote-anchors ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const rehypePostfixFoonotes = require('rehype-postfix-footnote-anchors') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') ``` Usage: ```javascript unified() .use(reParse, {footnotes: true}) .use(remark2rehype) .use(rehypePostfixFoonotes, postfix) .use(stringify) ``` ## Configuration In the above **Usage** example, postfix can be one of two things: * a string: `postfix = '-my-postfix'` `postfix` will be appended to the existing footnotes identifiers * a function: `postfix = (identifier: string): string => 'foo' + identifier + 'bar'` `postfix` will be called with the footnote identifier and should return a string ## License [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/rehype-postfix-footnote-anchors/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/rehype-postfix-footnote-anchors [rehype]: https://github.com/rehypejs/rehype ================================================ FILE: packages/rehype-postfix-footnote-anchors/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`{"gfm":false,"commonmark":false} footnote-split 1`] = ` "

afirstb1cthird


  1. first def
  2. second def
  3. third def
" `; exports[`{"gfm":false,"commonmark":false} footnotes 1`] = ` "

This is the body with a footnotefoo or twobar or more baz qux fiji. One again: foo

Also a reference that does not existnope.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":false} given postfix 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":false} numbered footnotes 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":false} postfixing function 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":false} regression-1 1`] = ` "

afoo

afoofoo

afoofoofoo

afoofoofoofoo

afoofoofoofoofoo

afoofoofoofoofoofoo


  1. MyNote
" `; exports[`{"gfm":false,"commonmark":false} regression-2 1`] = ` "

1 1 2 2 3 3 4 w 4 w 5 4


  1. alpha bravo one
  2. alpha bravo two
  3. alpha bravo third
  4. foo
  5. alpha bravo fourth
" `; exports[`{"gfm":false,"commonmark":true} footnote-split 1`] = ` "

afirstb1cthird


  1. first def
  2. second def
  3. third def
" `; exports[`{"gfm":false,"commonmark":true} footnotes 1`] = ` "

This is the body with a footnotefoo or twobar or more baz qux fiji. One again: foo

Also a reference that does not existnope.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":true} given postfix 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":true} numbered footnotes 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":true} postfixing function 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":false,"commonmark":true} regression-1 1`] = ` "

afoo

afoofoo

afoofoofoo

afoofoofoofoo

afoofoofoofoofoo

afoofoofoofoofoofoo


  1. MyNote
" `; exports[`{"gfm":false,"commonmark":true} regression-2 1`] = ` "

1 1 2 2 3 3 4 w 4 w 5 4


  1. alpha bravo one
  2. alpha bravo two
  3. alpha bravo third
  4. foo
  5. alpha bravo fourth
" `; exports[`{"gfm":true,"commonmark":false} footnote-split 1`] = ` "

afirstb1cthird


  1. first def
  2. second def
  3. third def
" `; exports[`{"gfm":true,"commonmark":false} footnotes 1`] = ` "

This is the body with a footnotefoo or twobar or more baz qux fiji. One again: foo

Also a reference that does not existnope.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":false} given postfix 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":false} numbered footnotes 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":false} postfixing function 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":false} regression-1 1`] = ` "

afoo

afoofoo

afoofoofoo

afoofoofoofoo

afoofoofoofoofoo

afoofoofoofoofoofoo


  1. MyNote
" `; exports[`{"gfm":true,"commonmark":false} regression-2 1`] = ` "

1 1 2 2 3 3 4 w 4 w 5 4


  1. alpha bravo one
  2. alpha bravo two
  3. alpha bravo third
  4. foo
  5. alpha bravo fourth
" `; exports[`{"gfm":true,"commonmark":true} footnote-split 1`] = ` "

afirstb1cthird


  1. first def
  2. second def
  3. third def
" `; exports[`{"gfm":true,"commonmark":true} footnotes 1`] = ` "

This is the body with a footnotefoo or twobar or more baz qux fiji. One again: foo

Also a reference that does not existnope.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":true} given postfix 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":true} numbered footnotes 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":true} postfixing function 1`] = ` "

This is the body with a footnote1 or two2 or more 3 4 5. One again: 1

Also a reference that does not exist6.


  1. Footnote that ends with a list:

    • item 1
    • item 2

  2. This footnote is a blockquote.

  3. A simple oneliner.

  4. A footnote with multiple paragraphs.

    Paragraph two.

  5. First line of first paragraph. Second line of first paragraph is not intended. Nor is third...

" `; exports[`{"gfm":true,"commonmark":true} regression-1 1`] = ` "

afoo

afoofoo

afoofoofoo

afoofoofoofoo

afoofoofoofoofoo

afoofoofoofoofoofoo


  1. MyNote
" `; exports[`{"gfm":true,"commonmark":true} regression-2 1`] = ` "

1 1 2 2 3 3 4 w 4 w 5 4


  1. alpha bravo one
  2. alpha bravo two
  3. alpha bravo third
  4. foo
  5. alpha bravo fourth
" `; ================================================ FILE: packages/rehype-postfix-footnote-anchors/__tests__/fixtures/footnote-split.fixture.md ================================================ a[^first]b^[second def]c[^third] [^first]: first def [^third]: third def ================================================ FILE: packages/rehype-postfix-footnote-anchors/__tests__/fixtures/footnotes.fixture.md ================================================ This is the body with a footnote[^foo] or two[^bar] or more [^baz] [^qux] [^fiji]. One again: [^foo] Also a reference that does not exist[^nope]. [^foo]: Footnote that ends with a list: * item 1 * item 2 [^bar]: > This footnote is a blockquote. [^baz]: A simple oneliner. [^qux]: A footnote with multiple paragraphs. Paragraph two. [^fiji]: First line of first paragraph. Second line of first paragraph is not intended. Nor is third... ================================================ FILE: packages/rehype-postfix-footnote-anchors/__tests__/fixtures/regression-1.fixture.md ================================================ a[^foo] a[^foo][^foo] a[^foo][^foo][^foo] a[^foo][^foo][^foo][^foo] a[^foo][^foo][^foo][^foo][^foo] a[^foo][^foo][^foo][^foo][^foo][^foo] [^foo]: MyNote ================================================ FILE: packages/rehype-postfix-footnote-anchors/__tests__/fixtures/regression-2.fixture.md ================================================ 1 ^[alpha bravo one] 2 ^[alpha bravo two] 3 ^[alpha bravo third] 4 [^w] 4 [^w] 5 ^[alpha bravo fourth] [^w]: foo ================================================ FILE: packages/rehype-postfix-footnote-anchors/__tests__/index.js ================================================ import {readdirSync as directory, readFileSync as file} from 'fs' import {join} from 'path' import unified from 'unified' import reParse from 'remark-parse' import footnotes from 'remark-footnotes' import remarkNumberedFootnotes from 'remark-numbered-footnotes' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' const base = join(__dirname, 'fixtures') const specs = directory(base).reduce((tests, contents) => { const parts = contents.split('.') if (!tests[parts[0]]) { tests[parts[0]] = {} } tests[parts[0]][parts[1]] = file(join(base, contents), 'utf-8') return tests }, {}) const configs = [ { gfm: true, commonmark: false, }, { gfm: false, commonmark: false, }, { gfm: false, commonmark: true, }, { gfm: true, commonmark: true, }, ] configs.forEach(config => { describe(JSON.stringify(config), () => { test('footnotes', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(remark2rehype) .use(require('../src')) .use(stringify) .processSync(specs['footnotes'].fixture) expect(contents).toMatchSnapshot() }) test('numbered footnotes', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(remarkNumberedFootnotes) .use(remark2rehype) .use(require('../src')) .use(stringify) .processSync(specs['footnotes'].fixture) expect(contents).toMatchSnapshot() }) test('given postfix', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(remarkNumberedFootnotes) .use(remark2rehype) .use(require('../src'), '-bar') .use(stringify) .processSync(specs['footnotes'].fixture) expect(contents).toMatchSnapshot() }) test('postfixing function', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(remarkNumberedFootnotes) .use(remark2rehype) .use(require('../src'), (identifier) => `foo--${identifier}--bar`) .use(stringify) .processSync(specs['footnotes'].fixture) expect(contents).toMatchSnapshot() }) test('regression-1', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(remark2rehype) .use(require('../src')) .use(stringify) .processSync(specs['regression-1'].fixture) expect(contents).toMatchSnapshot() }) test('regression-2', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(remark2rehype) .use(require('../src')) .use(stringify) .processSync(specs['regression-2'].fixture) expect(contents).toMatchSnapshot() }) test('footnote-split', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(remark2rehype) .use(require('../src')) .use(stringify) .processSync(specs['footnote-split'].fixture) expect(contents).toMatchSnapshot() }) }) }) ================================================ FILE: packages/rehype-postfix-footnote-anchors/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); function findLastTag(node, tag = 'p') { if (!node.children || !node.children.length) return; const links = node.children.filter(e => e.tagName === tag); if (!links.length) return; return links[links.length - 1]; } function findLastLink(node, className) { if (!node.children || !node.children.length) return; const links = node.children.filter(e => e.tagName === 'a'); if (!links.length) return; const aTag = links[links.length - 1]; if (!aTag.properties || !aTag.properties.className || !aTag.properties.className.includes(className)) return; return aTag; } function setPostfix(node, aTag, postfix) { if (typeof postfix === 'function') { const id = node.properties.id; node.properties.id = postfix(id); const link = aTag.properties.href; aTag.properties.href = `#${postfix(link.substr(1))}`; } else { node.properties.id += postfix; aTag.properties.href += postfix; } } function plugin(postfix = '-postfix') { return tree => { visit(tree, 'element', (node, index, parent) => { if (!['li', 'sup'].includes(node.tagName)) return; if (node.tagName === 'li') { if (!node.properties || !node.properties.id) return; if (!node.properties.id.startsWith('fn-')) return; if (!node.children.length) return; let aTag = findLastLink(node, 'footnote-backref'); if (!aTag) { const pTag = findLastTag(node, 'p'); aTag = findLastLink(pTag, 'footnote-backref'); } if (!aTag) return; setPostfix(node, aTag, postfix); } if (node.tagName === 'sup') { if (!node.properties || !node.properties.id) return; if (!node.properties.id.startsWith('fnref-')) return; if (!node.children.length || node.children[0].tagName !== 'a') return; const aTag = node.children[0]; if (!aTag.properties || !aTag.properties.className || !aTag.properties.className.includes('footnote-ref')) return; setPostfix(node, aTag, postfix); } }); }; } module.exports = plugin; ================================================ FILE: packages/rehype-postfix-footnote-anchors/package.json ================================================ { "name": "rehype-postfix-footnote-anchors", "version": "2.0.3", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rehype-postfix-footnote-anchors", "type": "git" }, "author": "Victor Felder (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "rehype", "footnotes" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" }, "devDependencies": { "remark-numbered-footnotes": "file:../remark-numbered-footnotes" } } ================================================ FILE: packages/rehype-postfix-footnote-anchors/src/index.js ================================================ const visit = require('unist-util-visit') function findLastTag (node, tag = 'p') { if (!node.children || !node.children.length) return const links = node.children.filter(e => e.tagName === tag) if (!links.length) return return links[links.length - 1] } function findLastLink (node, className) { if (!node.children || !node.children.length) return const links = node.children.filter(e => e.tagName === 'a') if (!links.length) return const aTag = links[links.length - 1] if (!aTag.properties || !aTag.properties.className || !aTag.properties.className.includes(className)) return return aTag } function setPostfix (node, aTag, postfix) { if (typeof postfix === 'function') { const id = node.properties.id node.properties.id = postfix(id) const link = aTag.properties.href aTag.properties.href = `#${postfix(link.substr(1))}` } else { node.properties.id += postfix aTag.properties.href += postfix } } function plugin (postfix = '-postfix') { return (tree) => { visit(tree, 'element', (node, index, parent) => { if (!['li', 'sup'].includes(node.tagName)) return if (node.tagName === 'li') { if (!node.properties || !node.properties.id) return if (!node.properties.id.startsWith('fn-')) return if (!node.children.length) return let aTag = findLastLink(node, 'footnote-backref') if (!aTag) { const pTag = findLastTag(node, 'p') aTag = findLastLink(pTag, 'footnote-backref') } if (!aTag) return setPostfix(node, aTag, postfix) } if (node.tagName === 'sup') { if (!node.properties || !node.properties.id) return if (!node.properties.id.startsWith('fnref-')) return if (!node.children.length || node.children[0].tagName !== 'a') return const aTag = node.children[0] if (!aTag.properties || !aTag.properties.className || !aTag.properties.className.includes('footnote-ref')) return setPostfix(node, aTag, postfix) } }) } } module.exports = plugin ================================================ FILE: packages/remark-abbr/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-abbr/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-abbr/README.md ================================================ # remark-abbr [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This [remark][remark] plugin parses custom Markdown syntax to produce (HTML) abbreviations. It introduces a new [MDAST][mdast] node type: "abbr". ```javascript interface abbr <: Node { type: "abbr"; abbr: string; reference: string; data: { hName: "abbr"; hProperties: { title: string; } } } ``` ## Syntax Abbreviations are defined a bit like footnotes: ```markdown This plugin works on MDAST, a Markdown AST implemented by [remark](https://github.com/remarkjs/remark) *[MDAST]: Markdown Abstract Syntax Tree. *[AST]: Abstract syntax tree ``` This would compile to the following HTML: ```html

This plugin works on MDAST, a Markdown AST implemented by remark

``` ## Installation [npm][npm]: ```bash npm install remark-abbr ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const remarkAbbr = require('remark-abbr') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkAbbr) .use(remark2rehype) .use(stringify) ``` ## Options ### options.expandFirst Expand the first occurrence of each abbreviation in place to introduce the definition and it's definition. Further occurrences are parsed into "abbr" [MDAST][mdast] nodes as the plugin would normally do. **example** ```javascript .use(remarkAbbr, { expandFirst: true }) ``` **given** ```markdown This plugin works on MDAST. More stuff about MDAST. *[MDAST]: Markdown Abstract Syntax Tree ``` **produces** ```html

This plugin works on Markdown Abstract Syntax Tree (MDAST).

More stuff about MDAST.

" ``` ## License [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-abbr/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-abbr [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark ================================================ FILE: packages/remark-abbr/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`compiles to markdown 1`] = ` "_abbr_ HTML > HTML inside quote *[abbr]: abbreviation *[HTML]: HyperText Markup Language" `; exports[`compiles to markdown 2`] = ` "_abbr_ HTML > HTML inside quote *[abbr]: abbreviation *[HTML]: HyperText Markup Language" `; exports[`compiles to markdown 3`] = ` "_abbr_ HTML > HTML inside quote *[abbr]: abbreviation *[HTML]: HyperText Markup Language" `; exports[`empty object does not break with references in their own paragraphs 1`] = `"

Here is a test featuring abc and def

"`; exports[`empty object no reference 1`] = `"

No reference!

"`; exports[`empty object passes the first regression test 1`] = ` "

The HTML specification is maintained by the W3C:link, this line had an abbr before link.

A line with a link before an abbr: HTML.

" `; exports[`empty object passes the retro test 1`] = ` "

An ABBR: \\"REF\\", ref and REFERENCE should be ignored.

The HTML specification is maintained by the W3C.

" `; exports[`empty object passes the retro test 2`] = ` "An ABBR: \\"REF\\", ref and REFERENCE should be ignored. The HTML specification is maintained by the W3C. *[ABBR]: Abbreviation *[REF]: Reference *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium" `; exports[`empty object passes the second regression test 1`] = ` "

The HTML specification is maintained by the W3C:link, this line had an abbr before link HTML.

A line with a link before an abbr: HTML.

" `; exports[`empty object renders references 1`] = ` "

This is an abbreviation: REF. ref and REFERENCE should be ignored.

Here is another one in a link: FOO.

Here is the first one in a link: REF.

" `; exports[`expandFirst does not break with references in their own paragraphs 1`] = `"

Here is a test featuring A B C (abc) and D E F (def)

"`; exports[`expandFirst no reference 1`] = `"

No reference!

"`; exports[`expandFirst passes the first regression test 1`] = ` "

The Hyper Text Markup Language (HTML) specification is maintained by the World Wide Web Consortium (W3C):link, this line had an abbr before link.

A line with a link before an abbr: HTML.

" `; exports[`expandFirst passes the retro test 1`] = ` "

An ABBR: \\"REF\\", ref and REFERENCE should be ignored.

The HTML specification is maintained by the W3C.

" `; exports[`expandFirst passes the retro test 2`] = ` "An ABBR: \\"REF\\", ref and REFERENCE should be ignored. The HTML specification is maintained by the W3C. *[ABBR]: Abbreviation *[REF]: Reference *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium" `; exports[`expandFirst passes the second regression test 1`] = ` "

The Hyper Text Markup Language (HTML) specification is maintained by the World Wide Web Consortium (W3C):link, this line had an abbr before link HTML.

A line with a link before an abbr: HTML.

" `; exports[`expandFirst renders references 1`] = ` "

This is an abbreviation: Reference (REF). ref and REFERENCE should be ignored.

Here is another one in a link: Reference (FOO).

Here is the first one in a link: REF.

" `; exports[`no-config does not break with references in their own paragraphs 1`] = `"

Here is a test featuring abc and def

"`; exports[`no-config no reference 1`] = `"

No reference!

"`; exports[`no-config passes the first regression test 1`] = ` "

The HTML specification is maintained by the W3C:link, this line had an abbr before link.

A line with a link before an abbr: HTML.

" `; exports[`no-config passes the retro test 1`] = ` "

An ABBR: \\"REF\\", ref and REFERENCE should be ignored.

The HTML specification is maintained by the W3C.

" `; exports[`no-config passes the retro test 2`] = ` "An ABBR: \\"REF\\", ref and REFERENCE should be ignored. The HTML specification is maintained by the W3C. *[ABBR]: Abbreviation *[REF]: Reference *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium" `; exports[`no-config passes the second regression test 1`] = ` "

The HTML specification is maintained by the W3C:link, this line had an abbr before link HTML.

A line with a link before an abbr: HTML.

" `; exports[`no-config renders references 1`] = ` "

This is an abbreviation: REF. ref and REFERENCE should be ignored.

Here is another one in a link: FOO.

Here is the first one in a link: REF.

" `; ================================================ FILE: packages/remark-abbr/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import remarkStringify from 'remark-stringify' import remarkAbbr from '../src/' const render = (text, config) => unified() .use(reParse) .use(remarkAbbr, config) .use(remark2rehype) .use(stringify) .processSync(text) const renderToMarkdown = (text, config) => unified() .use(reParse) .use(remarkStringify) .use(remarkAbbr, config) .processSync(text) const configToTest = { 'no-config': undefined, 'empty object': {}, expandFirst: {expandFirst: true}, } for (const [configName, config] of Object.entries(configToTest)) { it(`${configName} renders references`, () => { const {contents} = render(dedent` This is an abbreviation: REF. ref and REFERENCE should be ignored. Here is another one in a link: [FOO](http://example.com). Here is the first one in a link: [REF](http://example.com). *[REF]: Reference *[FOO]: Reference `, config) expect(contents).toMatchSnapshot() }) it(`${configName} passes the first regression test`, () => { const {contents} = render(dedent` The HTML specification is maintained by the W3C:\ [link](https://w3c.github.io/html/), this line had an abbr before link. A line with [a link](http://example.com) before an abbr: HTML. *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium `, config) expect(contents).toMatchSnapshot() }) it(`${configName} passes the second regression test`, () => { const {contents} = render(dedent` The HTML specification is maintained by the W3C:\ [link](https://w3c.github.io/html/), this line had an abbr before **link** HTML. A line with [a link](http://example.com) before an abbr: HTML. *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium `, config) expect(contents).toMatchSnapshot() }) it(`${configName} passes the retro test`, () => { const input = dedent` An ABBR: "REF", ref and REFERENCE should be ignored. The HTML specification is maintained by the W3C. *[REF]: Reference *[ABBR]: This gets overridden by the next one. *[ABBR]: Abbreviation *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium ` const {contents: html} = render(input) expect(html).toMatchSnapshot() const {contents: markdown} = renderToMarkdown(input) expect(markdown).toMatchSnapshot() }) it(`${configName} no reference`, () => { const {contents} = render(dedent` No reference! `, config) expect(contents).toMatchSnapshot() }) test('compiles to markdown', () => { const md = dedent` *abbr* HTML > HTML inside quote *[abbr]: abbreviation *[noabbr]: explanation that does not match *[HTML]: HyperText Markup Language ` const {contents} = renderToMarkdown(md) expect(contents).toMatchSnapshot() const contents1 = renderToMarkdown(md).contents const contents2 = renderToMarkdown(contents1).contents expect(contents1).toBe(contents2) }) it(`${configName} handles abbreviations ending with a period`, () => { const {contents} = render(dedent` A.B.C. and C-D%F. foo *[A.B.C.]: ref1 *[C-D%F.]: ref2 `, config) expect(contents).toContain(`A.B.C.`) expect(contents).toContain(`C-D%F.`) }) it(`${configName} does not parse words starting with abbr`, () => { const {contents} = render(dedent` ABC ABC ABC *[AB]: ref1 `, config) expect(contents).not.toContain(' { const {contents} = render(dedent` ABC ABC ABC *[BC]: ref1 `, config) expect(contents).not.toContain(' { const {contents} = render(dedent` ABC ABC ABC *[B]: ref1 `, config) expect(contents).not.toContain(' { const {contents} = render(dedent` Here is a test featuring abc and def *[abc]: A B C *[def]: D E F `, config) expect(contents).toMatchSnapshot() }) } ================================================ FILE: packages/remark-abbr/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); function plugin(options) { const opts = options || {}; const expandFirst = opts.expandFirst; function locator(value, fromIndex) { return value.indexOf('*[', fromIndex); } function inlineTokenizer(eat, value, silent) { const regex = /[*]\[([^\]]*)\]:\s*(.+)\n*/; const keep = regex.exec(value); /* istanbul ignore if - never used (yet) */ if (silent) return silent; if (!keep || keep.index !== 0) return; const [matched, abbr, reference] = keep; return eat(matched)({ type: 'abbr', abbr, reference, children: [{ type: 'text', value: abbr }], data: { hName: 'abbr', hProperties: { title: reference } } }); } function transformer(tree) { const abbrs = {}; const emptyParagraphsToRemove = new Map(); visit(tree, 'paragraph', find(abbrs, emptyParagraphsToRemove)); emptyParagraphsToRemove.forEach((indices, key) => { indices.reverse(); indices.forEach(index => { key.children.splice(index, 1); }); }); visit(tree, replace(abbrs)); } function find(abbrs, emptyParagraphsToRemove) { return function one(node, index, parent) { for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; if (child.type !== 'abbr') continue; // Store abbr node for later use abbrs[child.abbr] = child; node.children.splice(i, 1); i -= 1; } // Keep track of empty paragraphs to remove if (node.children.length === 0) { const indices = emptyParagraphsToRemove.get(parent) || []; indices.push(index); emptyParagraphsToRemove.set(parent, indices); } }; } function replace(abbrs) { function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); // eslint-disable-line no-useless-escape } const pattern = Object.keys(abbrs).map(escapeRegExp).join('|'); const regex = new RegExp(`(\\b|\\W)(${pattern})(\\b|\\W)`); const expanded = {}; function one(node, index, parent) { if (Object.keys(abbrs).length === 0) return; if (!node.children) return; // If a text node is present in child nodes, check if an abbreviation is present for (let c = 0; c < node.children.length; c++) { const child = node.children[c]; if (node.type === 'abbr' || child.type !== 'text') continue; if (!regex.test(child.value)) continue; // Transform node const newTexts = child.value.split(regex); // Remove old text node node.children.splice(c, 1); // Replace abbreviations for (let i = 0; i < newTexts.length; i++) { const content = newTexts[i]; if (Object.prototype.hasOwnProperty.call(abbrs, content)) { const abbr = abbrs[content]; if (expandFirst && !expanded[content]) { node.children.splice(c + i, 0, { type: 'text', value: `${abbr.reference} (${abbr.abbr})` }); expanded[content] = true; } else { node.children.splice(c + i, 0, abbr); } } else { node.children.splice(c + i, 0, { type: 'text', value: content }); } } } } return one; } inlineTokenizer.locator = locator; const Parser = this.Parser; // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers; const inlineMethods = Parser.prototype.inlineMethods; inlineTokenizers.abbr = inlineTokenizer; inlineMethods.splice(0, 0, 'abbr'); const Compiler = this.Compiler; if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; const abbrMap = {}; visitors.abbr = node => { if (!abbrMap[node.abbr]) { abbrMap[node.abbr] = `*[${node.abbr}]: ${node.reference}`; } return `${node.abbr}`; }; const originalRootCompiler = visitors.root; visitors.root = function (node) { return `${originalRootCompiler.apply(this, arguments)}\n${Object.values(abbrMap).join('\n')}`; }; } return transformer; } module.exports = plugin; ================================================ FILE: packages/remark-abbr/package.json ================================================ { "name": "remark-abbr", "version": "1.4.2", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-abbr", "type": "git" }, "author": "Sébastien (AmarOk) Blin ", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/remark-abbr/src/index.js ================================================ const visit = require('unist-util-visit') function plugin (options) { const opts = options || {} const expandFirst = opts.expandFirst function locator (value, fromIndex) { return value.indexOf('*[', fromIndex) } function inlineTokenizer (eat, value, silent) { const regex = /[*]\[([^\]]*)\]:\s*(.+)\n*/ const keep = regex.exec(value) /* istanbul ignore if - never used (yet) */ if (silent) return silent if (!keep || keep.index !== 0) return const [matched, abbr, reference] = keep return eat(matched)({ type: 'abbr', abbr, reference, children: [ { type: 'text', value: abbr } ], data: { hName: 'abbr', hProperties: { title: reference } } }) } function transformer (tree) { const abbrs = {} const emptyParagraphsToRemove = new Map() visit(tree, 'paragraph', find(abbrs, emptyParagraphsToRemove)) emptyParagraphsToRemove.forEach((indices, key) => { indices.reverse() indices.forEach((index) => { key.children.splice(index, 1) }) }) visit(tree, replace(abbrs)) } function find (abbrs, emptyParagraphsToRemove) { return function one (node, index, parent) { for (let i = 0; i < node.children.length; i++) { const child = node.children[i] if (child.type !== 'abbr') continue // Store abbr node for later use abbrs[child.abbr] = child node.children.splice(i, 1) i -= 1 } // Keep track of empty paragraphs to remove if (node.children.length === 0) { const indices = emptyParagraphsToRemove.get(parent) || [] indices.push(index) emptyParagraphsToRemove.set(parent, indices) } } } function replace (abbrs) { function escapeRegExp (str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') // eslint-disable-line no-useless-escape } const pattern = Object.keys(abbrs).map(escapeRegExp).join('|') const regex = new RegExp(`(\\b|\\W)(${pattern})(\\b|\\W)`) const expanded = {} function one (node, index, parent) { if (Object.keys(abbrs).length === 0) return if (!node.children) return // If a text node is present in child nodes, check if an abbreviation is present for (let c = 0; c < node.children.length; c++) { const child = node.children[c] if (node.type === 'abbr' || child.type !== 'text') continue if (!regex.test(child.value)) continue // Transform node const newTexts = child.value.split(regex) // Remove old text node node.children.splice(c, 1) // Replace abbreviations for (let i = 0; i < newTexts.length; i++) { const content = newTexts[i] if (Object.prototype.hasOwnProperty.call(abbrs, content)) { const abbr = abbrs[content] if (expandFirst && !expanded[content]) { node.children.splice(c + i, 0, { type: 'text', value: `${abbr.reference} (${abbr.abbr})` }) expanded[content] = true } else { node.children.splice(c + i, 0, abbr) } } else { node.children.splice(c + i, 0, { type: 'text', value: content }) } } } } return one } inlineTokenizer.locator = locator const Parser = this.Parser // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers const inlineMethods = Parser.prototype.inlineMethods inlineTokenizers.abbr = inlineTokenizer inlineMethods.splice(0, 0, 'abbr') const Compiler = this.Compiler if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return const abbrMap = {} visitors.abbr = (node) => { if (!abbrMap[node.abbr]) { abbrMap[node.abbr] = `*[${node.abbr}]: ${node.reference}` } return `${node.abbr}` } const originalRootCompiler = visitors.root visitors.root = function (node) { return `${originalRootCompiler.apply(this, arguments)}\n${Object.values(abbrMap).join('\n')}` } } return transformer } module.exports = plugin ================================================ FILE: packages/remark-align/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-align/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-align/README.md ================================================ # remark-align [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax to center- or right-align elements. ## AST node (see [mdast][mdast] specification) It adds three new node types, described below, to the [mdast][mdast] produced by [remark][remark]: ### `LeftAligned` ```javascript interface LeftAligned <: Parent { type: "leftAligned"; data: { hName: "div"; hProperties: { class: string; } } } ``` ### `CenterAligned` ```javascript interface CenterAligned <: Parent { type: "centerAligned"; data: { hName: "div"; hProperties: { class: string; } } } ``` ### `RightAligned` ```javascript interface RightAligned <: Parent { type: "rightAligned"; data: { hName: "div"; hProperties: { class: string; } } } ``` If you are using [rehype][rehype], the stringified HTML result will be `div`s with configurable CSS classes. It is up to you to have CSS rules producing the desired result for these three classes. ## Syntax Alignment is done by wrapping something in arrows indicating the alignment: ```markdown ->paragraph<- ->paragraph-> ``` produces: ```html

paragraph

paragraph

``` ## Installation [npm][npm]: ```bash npm install remark-align ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkAlign = require('remark-align') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkAlign, { left: 'align-left', center: 'align-center', right: 'align-right', }) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-align/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-align [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark [rehype]: https://github.com/rehypejs/rehype ================================================ FILE: packages/remark-align/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`align 1`] = ` "

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A centered paragraph.

Containing two paragraph

an other simple paragraph

A right aligned paragraph.

Containing two paragraph

an other simple paragraph

A centered paragraph.

An other centered paragraph.

left aligned

a simple paragraph

->A started block without end.

" `; exports[`align-custom-config 1`] = ` "

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A centered paragraph.

Containing two paragraph

an other simple paragraph

A right aligned paragraph.

Containing two paragraph

an other simple paragraph

A centered paragraph.

An other centered paragraph.

left aligned

a simple paragraph

->A started block without end.

" `; exports[`align-custom-config 2`] = ` "

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A centered paragraph.

Containing two paragraph

an other simple paragraph

A right aligned paragraph.

Containing two paragraph

an other simple paragraph

A centered paragraph.

An other centered paragraph.

left aligned

a simple paragraph

->A started block without end.

" `; exports[`block-wrap 1`] = ` "

wraps blocks e.g. title:

foo

title

foo

" `; exports[`center-no-start 1`] = ` "

title

foo <-

title

" `; exports[`compiles to markdown 1`] = ` "# title <- foo <- # title -> **foo** <- -> ![img](src) -> # wraps blocks e.g. title: -> foo # title foo -> " `; exports[`escapable 1`] = ` "

title

foo ->escaped-in

->escaped-out

center

" `; exports[`left align 1`] = ` "

title

foo

title

" `; exports[`list-block 1`] = ` "

title

  • a
  • b
  • c
  • d
" `; exports[`no content 1`] = ` "
" `; exports[`right-no-end 1`] = ` "

title

-> foo

title

" `; exports[`right-no-start 1`] = ` "

title

foo ->

title

" `; exports[`should not crash on invalid align 1`] = ` "<- foo -> " `; ================================================ FILE: packages/remark-align/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import remarkStringify from 'remark-stringify' import remarkAlign from '../src/' const render = (text, config) => unified() .use(reParse) .use(remarkAlign, config) .use(remark2rehype) .use(stringify) .processSync(text) const renderToMarkdown = (text, config) => unified() .use(reParse) .use(remarkStringify) .use(remarkAlign, config) .processSync(text) const alignFixture = dedent` A simple paragraph ->A centered paragraph<- a simple paragraph ->A right aligned paragraph-> an other simple paragraph A simple paragraph ->A centered paragraph<- a simple paragraph ->A right aligned paragraph-> an other simple paragraph ->A centered paragraph. Containing two paragraph<- an other simple paragraph ->A right aligned paragraph. Containing two paragraph<- an other simple paragraph ->A centered paragraph.<- ->An other centered paragraph.<- <-left aligned<- a simple paragraph ->A started block without end. ` test('align', () => { const {contents} = render(alignFixture) expect(contents).toMatchSnapshot() }) test('align-custom-config', () => { const {contents} = render(alignFixture, { right: 'custom-right', center: 'custom-center', }) expect(contents).toMatchSnapshot() }) test('align-custom-config', () => { const {contents} = render(alignFixture, { left: 'custom-left', right: 'custom-right', center: 'custom-center', }) expect(contents).toMatchSnapshot() }) test('block-wrap', () => { const {contents} = render(dedent` # wraps blocks e.g. title: -> foo # title foo -> `) expect(contents).toMatchSnapshot() }) test('center-no-start', () => { const {contents} = render(dedent` # title foo <- # title `) expect(contents).toMatchSnapshot() }) test('right-no-end', () => { const {contents} = render(dedent` # title -> foo # title `) expect(contents).toMatchSnapshot() }) test('right-no-start', () => { const {contents} = render(dedent` # title foo -> # title `) expect(contents).toMatchSnapshot() }) test('list-block', () => { const {contents} = render(dedent` # title -> - a - b -> -> - c - d <- `) expect(contents).toMatchSnapshot() }) test.skip('should not break blocks such as lists', () => { const {contents} = render(dedent` # title -> - list item - list -> item - sublist - -> - c - d <- `) expect(contents).toBe(dedent`

title

  • list item
  • list -> item
  • sublist
  • ->
  • c
  • d
`) }) test('escapable', () => { const {contents} = render(dedent` # title -> foo \->escaped-in <- \->escaped-out ->center<- `) expect(contents).toMatchSnapshot() }) test('left align', () => { const {contents} = render(dedent` # title <- foo <- # title `) expect(contents).toMatchSnapshot() }) test('no content', () => { const md = dedent` <- <- <- <- -> <- -><- -> <- ->-> -> -> -> -> -> -> ` const {contents} = render(md) expect(contents).toMatchSnapshot() const contents1 = renderToMarkdown(md).contents const contents2 = renderToMarkdown(contents1).contents expect(contents1).toBe(contents2) }) test('compiles to markdown', () => { const md = dedent` # title <- foo <- # title -> **foo** <- -> ![img](src) -> # wraps blocks e.g. title: -> foo # title foo -> ` const {contents} = renderToMarkdown(md) expect(contents).toMatchSnapshot() const contents1 = renderToMarkdown(md).contents const contents2 = renderToMarkdown(contents1).contents expect(contents1).toBe(contents2) }) test('should not crash on invalid align', () => { const md = dedent` <- foo -> ` const {contents} = renderToMarkdown(md) expect(contents).toMatchSnapshot() const contents1 = renderToMarkdown(md).contents const contents2 = renderToMarkdown(contents1).contents expect(contents1).toBe(contents2) }) ================================================ FILE: packages/remark-align/dist/index.js ================================================ "use strict"; const spaceSeparated = require('space-separated-tokens'); const C_NEWLINE = '\n'; const C_NEWPARAGRAPH = '\n\n'; module.exports = function plugin(classNames = {}) { const locateMarker = /[^\\]?(->|<-)/; const endMarkers = ['->', '<-']; function alignTokenizer(eat, value, silent) { const keep = value.match(locateMarker); if (!keep || keep.index !== 0) return; const now = eat.now(); const [, startMarker] = keep; /* istanbul ignore if - never used (yet) */ if (silent) return true; let index = 0; let linesToEat = []; const finishedBlocks = []; let endMarker = ''; let canEatLine = true; let blockStartIndex = 0; while (canEatLine) { const nextIndex = value.indexOf(C_NEWLINE, index + 1); const lineToEat = nextIndex !== -1 ? value.slice(index, nextIndex) : value.slice(index); linesToEat.push(lineToEat); const endIndex = endMarkers.indexOf(lineToEat.slice(-2)); // If nextIndex = (blockStartIndex + 2), it's the first marker of the block. if ((nextIndex > blockStartIndex + 2 || nextIndex === -1) && lineToEat.length >= 2 && endIndex !== -1) { if (endMarker === '') endMarker = lineToEat.slice(-2); finishedBlocks.push(linesToEat.join(C_NEWLINE)); // Check if another block is following if (value.indexOf('->', nextIndex) !== nextIndex + 1) break; linesToEat = []; blockStartIndex = nextIndex + 1; } index = nextIndex + 1; canEatLine = nextIndex !== -1; } let elementType = ''; let classes = ''; if (startMarker === '<-' && endMarker === '<-') { elementType = 'leftAligned'; classes = classNames.left ? classNames.left : 'align-left'; } if (startMarker === '->') { if (endMarker === '<-') { elementType = 'centerAligned'; classes = classNames.center ? classNames.center : 'align-center'; } if (endMarker === '->') { elementType = 'rightAligned'; classes = classNames.right ? classNames.right : 'align-right'; } } if (!elementType) return; if (finishedBlocks.length === 0) return; let stringToEat = ''; const marker = finishedBlocks[0].substring(finishedBlocks[0].length - 2, finishedBlocks[0].length); const toEat = []; for (let i = 0; i < finishedBlocks.length; ++i) { const block = finishedBlocks[i]; if (marker !== block.substring(block.length - 2, block.length)) break; toEat.push(block); stringToEat += block.slice(2, -2) + C_NEWPARAGRAPH; } const add = eat(toEat.join(C_NEWLINE)); const exit = this.enterBlock(); const values = this.tokenizeBlock(stringToEat, now); exit(); return add({ type: elementType, children: values, data: { hName: 'div', hProperties: { class: spaceSeparated.parse(classes) } } }); } const Parser = this.Parser; // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers; const blockMethods = Parser.prototype.blockMethods; blockTokenizers.alignBlocks = alignTokenizer; blockMethods.splice(blockMethods.indexOf('list') + 1, 0, 'alignBlocks'); const Compiler = this.Compiler; // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; const alignCompiler = function (node) { const innerContent = this.all(node); const markers = { left: ['<-', '<-'], right: ['->', '->'], center: ['->', '<-'] }; const alignType = node.type.slice(0, -7); if (!markers[alignType]) return innerContent.join('\n\n'); const [start, end] = markers[alignType]; if (innerContent.length < 2) return `${start} ${innerContent.join('\n').trim()} ${end}`; return `${start}\n${innerContent.join('\n\n').trim()}\n${end}`; }; visitors.leftAligned = alignCompiler; visitors.rightAligned = alignCompiler; visitors.centerAligned = alignCompiler; } }; ================================================ FILE: packages/remark-align/package.json ================================================ { "name": "remark-align", "version": "1.2.15", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-align", "type": "git" }, "author": "Sébastien (AmarOk) Blin ", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "space-separated-tokens": "^1.1.5" } } ================================================ FILE: packages/remark-align/src/index.js ================================================ const spaceSeparated = require('space-separated-tokens') const C_NEWLINE = '\n' const C_NEWPARAGRAPH = '\n\n' module.exports = function plugin (classNames = {}) { const locateMarker = /[^\\]?(->|<-)/ const endMarkers = ['->', '<-'] function alignTokenizer (eat, value, silent) { const keep = value.match(locateMarker) if (!keep || keep.index !== 0) return const now = eat.now() const [, startMarker] = keep /* istanbul ignore if - never used (yet) */ if (silent) return true let index = 0 let linesToEat = [] const finishedBlocks = [] let endMarker = '' let canEatLine = true let blockStartIndex = 0 while (canEatLine) { const nextIndex = value.indexOf(C_NEWLINE, index + 1) const lineToEat = nextIndex !== -1 ? value.slice(index, nextIndex) : value.slice(index) linesToEat.push(lineToEat) const endIndex = endMarkers.indexOf(lineToEat.slice(-2)) // If nextIndex = (blockStartIndex + 2), it's the first marker of the block. if ((nextIndex > (blockStartIndex + 2) || nextIndex === -1) && lineToEat.length >= 2 && endIndex !== -1 ) { if (endMarker === '') endMarker = lineToEat.slice(-2) finishedBlocks.push(linesToEat.join(C_NEWLINE)) // Check if another block is following if (value.indexOf('->', nextIndex) !== (nextIndex + 1)) break linesToEat = [] blockStartIndex = nextIndex + 1 } index = nextIndex + 1 canEatLine = nextIndex !== -1 } let elementType = '' let classes = '' if (startMarker === '<-' && endMarker === '<-') { elementType = 'leftAligned' classes = classNames.left ? classNames.left : 'align-left' } if (startMarker === '->') { if (endMarker === '<-') { elementType = 'centerAligned' classes = classNames.center ? classNames.center : 'align-center' } if (endMarker === '->') { elementType = 'rightAligned' classes = classNames.right ? classNames.right : 'align-right' } } if (!elementType) return if (finishedBlocks.length === 0) return let stringToEat = '' const marker = finishedBlocks[0].substring( finishedBlocks[0].length - 2, finishedBlocks[0].length ) const toEat = [] for (let i = 0; i < finishedBlocks.length; ++i) { const block = finishedBlocks[i] if (marker !== block.substring(block.length - 2, block.length)) break toEat.push(block) stringToEat += block.slice(2, -2) + C_NEWPARAGRAPH } const add = eat(toEat.join(C_NEWLINE)) const exit = this.enterBlock() const values = this.tokenizeBlock(stringToEat, now) exit() return add({ type: elementType, children: values, data: { hName: 'div', hProperties: { class: spaceSeparated.parse(classes) } } }) } const Parser = this.Parser // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers const blockMethods = Parser.prototype.blockMethods blockTokenizers.alignBlocks = alignTokenizer blockMethods.splice(blockMethods.indexOf('list') + 1, 0, 'alignBlocks') const Compiler = this.Compiler // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return const alignCompiler = function (node) { const innerContent = this.all(node) const markers = { left: ['<-', '<-'], right: ['->', '->'], center: ['->', '<-'] } const alignType = node.type.slice(0, -7) if (!markers[alignType]) return innerContent.join('\n\n') const [start, end] = markers[alignType] if (innerContent.length < 2) return `${start} ${innerContent.join('\n').trim()} ${end}` return `${start}\n${innerContent.join('\n\n').trim()}\n${end}` } visitors.leftAligned = alignCompiler visitors.rightAligned = alignCompiler visitors.centerAligned = alignCompiler } } ================================================ FILE: packages/remark-captions/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-captions/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-captions/README.md ================================================ # remark-captions [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This [remark][remark] plugin adds custom syntax to add a caption to elements which might benefit from a legend. It wraps the said element in a `figure` node with `figcaption` node as last child. It is particularly interesting for use with quotes, images, tables, code blocks. It follows a "whitelist" approach: for each [mdast][mdast] node type for which you want to allow captioning you'll have to add a configuration property mapping a node type to its caption "trigger". ## Syntax ```markdown > Do it or do it not, there is no try Source: A little green man, with a saber larger than himself ``` This takes what follows `Source: ` until the end of the block containing `Source: ` and puts this inside a `figcaption` mdast node. What precedes it becomes children of a `figure` node, the last child of this `figure` node being `figcaption`. Used with `rehype`, it generates the corresponding HTML elements. ```javascript interface figure <: Parent { type: 'figure' data: { hName: 'figure', } } ``` ```javascript interface figcaption <: Parent { type: 'figcaption' data: { hName: 'figcaption', } } ``` This plugin handles two different types of caption/legend nodes : - `internalLegend`: when the caption, after being parsed by `remark`, is inside the captioned element or inside its wrapping paragraph: - blockquote - image - inlineMath - iframe - ... - `externalLegend`: when the caption, after being parsed by `remark`, is outside the captioned element or after its wrapping paragraph: - table - code - math - ... ## Installation [npm][npm]: ```bash npm install remark-captions ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkCaptions = require('remark-captions') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkCaptions, { external: { table: 'Table:', code: 'Code:', math: 'Equation:', }, internal: { image: 'Figure:', } }) .use(remark2rehype) .use(stringify) ``` By default, it features : ```javascript external = { table: 'Table:', code: 'Code:', } internal = { blockquote: 'Source:', image: 'Figure:', } ``` ## Other examples This enables you to deal with such a code: ```python a_highlighted_code('blah') ``` Code: My code *caption* will yield ```javascript { type: 'figure', data: { hName: 'figure' }, children: [ { type: 'code', language: 'python', value: '\na_highlighted_code(\'blah\')\n' }, { type: 'figcaption', data: { hName: 'figcaption' } children: [ { type: 'text', value: 'My code ' }, { type: 'em', children: [ { type: 'text', value: 'caption' } ] } ] } ] } ``` Tables are also supported, example: ```markdown head1| head2 -----|------ bla|bla Table: figcapt1 ``` Associated with `remark-rehype` this generates a HTML tree encapsulated inside a `
` tag ```html
head1 head2
bla bla
figcapt1
``` [MIT][license] © [Zeste de Savoir][zds] [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-captions/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-captions [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark ================================================ FILE: packages/remark-captions/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`#101 1`] = ` "

code followed by non text par

foo

" `; exports[`caption without block 1`] = ` "
print('bla')
print('bla')
print('bla')
print('bla')
a code without lang
" `; exports[`captions should break lists 1`] = ` "
  • Bar
fc2
  • Baz
    * Baz
    
fc3
" `; exports[`code 1`] = ` "

Code

Normal code

print('bla')

With Legend

print('bla')
figcapt1
print('bla')
figcapt1

break

print('bla')
figcapt1 em strong code end

bla

print('bla2')
figcapt1

Code: bis

" `; exports[`compiles to markdown when at least 1 block caption 1`] = ` "foo ![](img) Figure: 3 this is a legend ![](img) Figure: 4 this is a legend, remainder of the paragraph goes into the legend Figure: 5 this is a text with and image !\\\\[]( img) in the middle Figure: 6 displayed as text ![](img) Figure: 7 is a legend ![](img) Figure: 8 is a legend. Figure: 9 this is a text. > My citation > Source: first capt Source: last capt·· 2nd line noop > foo > bar > baz > qux > Source: **first** > b_a_r Source: This is **the real** \`source\` noop > foo > bar > baz > qux > Source: **first** > b_a_r Source: This is **the real** \`source\` " `; exports[`compiles to markdown when at least 1 innerLegend caption 1`] = ` "## Code Normal code \`\`\`python print('bla') \`\`\` With Legend \`\`\`python print('bla') \`\`\` Code: figcapt1 \`\`\`python print('bla') \`\`\` Code: figcapt1 break \`\`\`python print('bla') \`\`\` Code: figcapt1 _em_ **strong \`code\`** end bla \`\`\`python print('bla2') \`\`\` Code: figcapt1 Code: bis " `; exports[`compiles to markdown when no caption 1`] = ` "foo ![](img) Figure: 1 this is parsed as legend baz ![](img) aFigure: 2 this is displayed as text ![alt 2b](https://zestedesavoir.com/static/images/home-clem.4a2f792744c9.png) this is displayed as text " `; exports[`custom-table 1`] = ` "<h2>Table</h2> <p>Normal table</p> <table> <thead> <tr> <th>head1</th> <th>head2</th> </tr> </thead> <tbody> <tr> <td>bla</td> <td>bla</td> </tr> </tbody> </table> <p>With Legend</p> <figure><table> <thead> <tr> <th>head1</th> <th>head2</th> </tr> </thead> <tbody> <tr> <td>bla</td> <td>bla</td> </tr> </tbody> </table><figcaption>figcapt1</figcaption></figure> <figure><table> <thead> <tr> <th>head1</th> <th>head2</th> </tr> </thead> <tbody> <tr> <td>bla</td> <td>bla</td> </tr> </tbody> </table><figcaption>figcapt1</figcaption></figure> <p>CustomTable: bis</p>" `; exports[`external legend: two legends 1`] = ` "<figure><table> <thead> <tr> <th>head1</th> <th>head2</th> </tr> </thead> <tbody> <tr> <td>bla</td> <td>bla</td> </tr> </tbody> </table><figcaption>figcapt1</figcaption></figure> <p>Table: bis</p>" `; exports[`gridtables 1`] = `"<figure><table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>a</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>b</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>c</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>1</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>2</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>3</p></td></tr></tbody></table><figcaption>bla bla</figcaption></figure>"`; exports[`internal legend: two legends 1`] = ` "<p>Should only keep the 1st</p> <figure><blockquote> <p>My citation Source: first capt</p> </blockquote><figcaption>last capt<br> 2nd line</figcaption></figure> <p>noop</p> <figure><blockquote> <p>foo bar baz qux Source: <strong>first</strong> b<em>a</em>r</p> </blockquote><figcaption>This is <strong>the real</strong> <code>source</code></figcaption></figure> <p>noop</p>" `; exports[`legend in paragraph 1`] = ` "<p>foo</p> <figure><img src=\\"\\"><figcaption>1 this is parsed as legend</figcaption></figure> <p>baz</p> <p><img src=\\"\\"> aFigure: 2 this is displayed as text</p> <p><img src=\\"https://zestedesavoir.com/static/images/home-clem.4a2f792744c9.png\\" alt=\\"alt 2b\\"> this is displayed as text</p> <p>foo</p> <figure><img src=\\"\\"><figcaption>3 this is a legend</figcaption></figure> <figure><img src=\\"\\"><figcaption>4 this is a legend, remainder of the paragraph goes into the legend</figcaption></figure> <p>Figure: 5 this is a text with and image <img src=\\"\\"> in the middle</p> <p>Figure: 6 displayed as text</p> <figure><img src=\\"\\"><figcaption>7 is a legend</figcaption></figure> <figure><img src=\\"\\"><figcaption>8 is a legend.</figcaption></figure> <p>Figure: 9 this is a text.</p>" `; exports[`quotation 1`] = ` "<p>(for convenience, are replaced with simple single spaces in the tests)</p> <h2>Blockquote</h2> <p>Empty blockquote</p> <blockquote> </blockquote> <p>Normal blockquote</p> <blockquote> <p>My citation</p> </blockquote> <p>With Legend</p> <figure><blockquote> <p>My citation</p> </blockquote><figcaption>figcapt1</figcaption></figure> <p>With Legend without Source:</p> <blockquote> <p>My citation : figcapt2</p> </blockquote> <p>haha</p> <figure><blockquote> <figure><blockquote> <figure><blockquote> <p>Foo<br> Foo</p> </blockquote><figcaption>fc1</figcaption></figure> <p>Bar<br> Bar<br> </p> </blockquote><figcaption>fc2</figcaption></figure> <p>Baz Baz</p> </blockquote><figcaption>fc3</figcaption></figure>" `; exports[`table 1`] = ` "<h2>Table</h2> <p>Normal table</p> <table> <thead> <tr> <th>head1</th> <th>head2</th> </tr> </thead> <tbody> <tr> <td>bla</td> <td>bla</td> </tr> </tbody> </table> <p>With Legend</p> <figure><table> <thead> <tr> <th>head1</th> <th>head2</th> </tr> </thead> <tbody> <tr> <td>bla</td> <td>bla</td> </tr> </tbody> </table><figcaption>figcapt1</figcaption></figure>" `; ================================================ FILE: packages/remark-captions/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import remarkStringify from 'remark-stringify' import remarkCaptions from '../src/' import remarkGridTables from '../../remark-grid-tables/src' const render = (text, config) => unified() .use(reParse, { gfm: true, commonmark: false, footnotes: true, /* sets list of known blocks to nothing, otherwise <h3>hey</h3> would become <h3>hey</h3> instead of <p><h3>hey</h3></p> */ blocks: [], }) .use(remarkGridTables) .use(remarkCaptions, config) .use(remark2rehype) .use(stringify) .processSync(text) const renderToMarkdown = (text, config) => unified() .use(reParse) .use(remarkStringify) .use(remarkCaptions, config) .processSync(text) test('code', () => { const {contents} = render(dedent` ## Code Normal code \`\`\`python print('bla') \`\`\` With Legend \`\`\`python print('bla') \`\`\` Code: figcapt1 \`\`\`python print('bla') \`\`\` Code: figcapt1 break \`\`\`python print('bla') \`\`\` Code: figcapt1 *em* **strong \`code\`** end bla \`\`\`python print('bla2') \`\`\` Code: figcapt1 Code: bis `) expect(contents).toMatchSnapshot() }) test('#101', () => { const {contents} = render(dedent` ## code followed by non text par \`\`\` foo \`\`\` [](#)`, {external: {code: 'Code:'}}) expect(contents).toMatchSnapshot() }) test('custom-table', () => { const {contents} = render(dedent` ## Table Normal table head1| head2 -----|------ bla|bla With Legend head1| head2 -----|------ bla|bla CustomTable: figcapt1 head1| head2 -----|------ bla|bla CustomTable: figcapt1 CustomTable: bis `, {external: {table: 'CustomTable:'}} ) expect(contents).toMatchSnapshot() }) test('gridtables', () => { const {contents} = render(dedent` +----+----+----+ | a | b | c | +====+====+====+ | 1 |2 |3 | +----+----+----+ Table: bla bla`, {external: {gridTable: 'Table:'}} ) expect(contents).toMatchSnapshot() }) test('caption without block', () => { const {contents} = render(dedent` \`\`\`python print('bla') \`\`\` \`\`\`python hl_lines=1,2 print('bla') print('bla') print('bla') \`\`\` \`\`\` a code without lang \`\`\` `, {external: {gridTable: 'Table:'}} ) expect(contents).toMatchSnapshot() }) test('quotation', () => { const {contents} = render(dedent` (for convenience, · are replaced with simple single spaces in the tests) ## Blockquote Empty blockquote > Normal blockquote > My citation With Legend > My citation Source: figcapt1 With Legend without Source: > My citation : figcapt2 haha > > > Foo·· > > > Foo > > Source: fc1 > > > > Bar·· > > Bar·· > Source: fc2 > > Baz > Baz Source: fc3 `.replace(/·/g, ' ')) expect(contents).toMatchSnapshot() }) test('captions should break lists', () => { const {contents} = render(dedent` > * Bar Source: fc2 > * Baz > * Baz Source: fc3 `.replace(/·/g, ' ')) expect(contents).toMatchSnapshot() }) test('table', () => { const {contents} = render(dedent` ## Table Normal table head1| head2 -----|------ bla|bla With Legend head1| head2 -----|------ bla|bla Table: figcapt1 `) expect(contents).toMatchSnapshot() }) test('external legend: two legends', () => { const {contents} = render(dedent` head1| head2 -----|------ bla|bla Table: figcapt1 Table: bis `) expect(contents).toMatchSnapshot() }) test('internal legend: two legends', () => { const {contents} = render(dedent` Should only keep the 1st > My citation Source: first capt Source: last capt·· 2nd line noop > foo > bar > baz > qux Source: **first** b*a*r Source: This is **the real** \`source\` noop `.replace(/·/g, ' ')) expect(contents).toMatchSnapshot() }) test('legend in paragraph', () => { const {contents} = render(dedent` foo ![]() Figure: 1 this is parsed as legend baz ![]() aFigure: 2 this is displayed as text ![alt 2b](https://zestedesavoir.com/static/images/home-clem.4a2f792744c9.png) this is displayed as text foo ![]() Figure: 3 this is a legend ![]() Figure: 4 this is a legend, remainder of the paragraph goes into the legend Figure: 5 this is a text with and image ![]() in the middle Figure: 6 displayed as text ![]() Figure: 7 is a legend ![]() Figure: 8 is a legend. Figure: 9 this is a text. `.replace(/·/g, ' ')) expect(contents).toMatchSnapshot() }) test('compiles to markdown when no caption', () => { const md = dedent` foo ![](img) Figure: 1 this is parsed as legend baz ![](img) aFigure: 2 this is displayed as text ![alt 2b](https://zestedesavoir.com/static/images/home-clem.4a2f792744c9.png) this is displayed as text ` const {contents} = renderToMarkdown(md) expect(contents).toMatchSnapshot() const contents1 = renderToMarkdown(md).contents const contents2 = renderToMarkdown(contents1).contents expect(contents1).toBe(contents2) }) test('compiles to markdown when at least 1 block caption', () => { const md = dedent` foo ![](img) Figure: 3 this is a legend ![](img) Figure: 4 this is a legend, remainder of the paragraph goes into the legend Figure: 5 this is a text with and image ![](<title> img) in the middle Figure: 6 displayed as text ![](img) Figure: 7 is a legend ![](img) Figure: 8 is a legend. Figure: 9 this is a text. > My citation Source: first capt Source: last capt·· 2nd line noop > foo > bar > baz > qux Source: **first** b*a*r Source: This is **the real** \`source\` noop > foo > bar > baz > qux Source: **first** b*a*r Source: This is **the real** \`source\` ` const {contents} = renderToMarkdown(md) expect(contents).toMatchSnapshot() const contents1 = renderToMarkdown(md).contents const contents2 = renderToMarkdown(contents1).contents expect(contents1).toBe(contents2) }) test('compiles to markdown when at least 1 innerLegend caption', () => { const md = dedent` ## Code Normal code \`\`\`python print('bla') \`\`\` With Legend \`\`\`python print('bla') \`\`\` Code: figcapt1 \`\`\`python print('bla') \`\`\` Code: figcapt1 break \`\`\`python print('bla') \`\`\` Code: figcapt1 *em* **strong \`code\`** end bla \`\`\`python print('bla2') \`\`\` Code: figcapt1 Code: bis ` const {contents} = renderToMarkdown(md) expect(contents).toMatchSnapshot() const contents1 = renderToMarkdown(md).contents const contents2 = renderToMarkdown(contents1).contents expect(contents1).toBe(contents2) }) ================================================ FILE: packages/remark-captions/dist/index.js ================================================ "use strict"; const clone = require('clone'); const visit = require('unist-util-visit'); const xtend = require('xtend'); const legendBlock = { table: 'Table:', code: 'Code:' }; const internLegendBlock = { blockquote: 'Source:', image: 'Figure:' }; function plugin(opts) { const externalBlocks = xtend(legendBlock, opts && opts.external || {}); const internalBlocks = xtend(internLegendBlock, opts && opts.internal || {}); const Compiler = this.Compiler; if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; visitors.figure = function (node) { const captionedNode = node.children[0]; const captionNode = node.children[1]; const captionedMarkdown = this.visit(captionedNode); // compile without taking care of the "figcaption" wrapper node const captionMarkdown = this.all(captionNode).join(''); if (!(captionedNode.type in externalBlocks || captionedNode.type in internalBlocks)) { return captionedMarkdown; } let prefix = ''; if (captionedNode.type in externalBlocks) { prefix = externalBlocks[captionedNode.type]; } else if (captionedNode.type in internalBlocks) { prefix = internalBlocks[captionedNode.type]; } return `${captionedMarkdown}\n${prefix} ${captionMarkdown}`; }; } return function transformer(tree) { Object.keys(internalBlocks).forEach(nodeType => visit(tree, nodeType, internLegendVisitor(internalBlocks))); Object.keys(externalBlocks).forEach(nodeType => visit(tree, nodeType, externLegendVisitorCreator(externalBlocks))); visit(tree, 'figure', (figure, index, parent) => { if (parent.type === 'paragraph') { if (index === 0) { parent.type = figure.type; parent.data = figure.data; parent.children = figure.children; return; } parent.type = 'tempWrapper'; } }); visit(tree, 'tempWrapper', (wrapper, index, parent) => { const newChildren = []; wrapper.children.forEach((node, i) => { const child = clone(node); if (child.type === 'figure') { newChildren.push(child); return; } if (child.type === 'text' && !child.value.trim()) { return; } else if (child.type === 'text') { child.value = child.value.trim(); } wrapper.children[i].type = 'paragraph'; wrapper.children[i].children = [child]; newChildren.push(wrapper.children[i]); }); parent.children.splice(index, 1, ...newChildren); }); }; } function internLegendVisitor(internalBlocks) { return function (node, index, parent) { // if already wrapped in figure, skip if (parent && parent.type === 'figure') return; // if the current node has some children, the legend is the last child. // if not, the legend is the last child of the parent node. const lastP = node.children ? getLastParagraph(node.children) : parent; // legend can only be in a paragraph. if (!lastP || node.children && lastP.type !== 'paragraph' || !node.children && parent.type !== 'paragraph') { return; } // find which child contains the last legend let legendChildIndex = -1; lastP.children.forEach((child, index) => { if (child.type === 'text' && (child.value.startsWith(internalBlocks[node.type]) || child.value.includes(`\n${internalBlocks[node.type]}`))) { legendChildIndex = index; } }); if (legendChildIndex === -1 || !node.children && legendChildIndex < index) { return; } // split the text node containing the last legend and find the line containing it const potentialLegendLines = lastP.children[legendChildIndex].value.split('\n'); let lastLegendIndex = -1; potentialLegendLines.forEach((line, index) => { if (line.startsWith(internalBlocks[node.type])) { lastLegendIndex = index; } }); // the child containing the last legend is split in two: head contains text until // legend, tail contains legend text const tail = clone(lastP.children[legendChildIndex]); const headText = potentialLegendLines.slice(0, lastLegendIndex).join('\n'); // replace existing node 'head' content with text until legend lastP.children[legendChildIndex].value = headText; // legend text is put into the cloned node… const legendText = potentialLegendLines.slice(lastLegendIndex).join('\n').slice(internalBlocks[node.type].length).trimLeft(); tail.value = legendText; // … and 'tail', the cloned node is inserted after 'head' lastP.children.splice(legendChildIndex + 1, 0, tail); // gather all nodes that should be inside the legend const legendNodes = lastP.children.slice(legendChildIndex + 1); // remove them from the parent paragraph lastP.children = lastP.children.slice(0, legendChildIndex + 1); const figcaption = { type: 'figcaption', children: legendNodes, data: { hName: 'figcaption' } }; const figure = { type: 'figure', children: [clone(node), figcaption], data: { hName: 'figure' } }; node.type = figure.type; node.children = figure.children; node.data = figure.data; }; } function externLegendVisitorCreator(blocks) { return function (node, index, parent) { if (index >= parent.children.length - 1) return; if (parent.children[index + 1].type !== 'paragraph') return; const legendNode = parent.children[index + 1]; const firstChild = legendNode.children[0]; if (firstChild.type !== 'text' || !firstChild.value.startsWith(blocks[node.type])) return; const legendNodes = []; const followingNodes = []; const firstTextLine = firstChild.value.replace(blocks[node.type], '').split('\n')[0]; if (firstChild.value.includes('\n')) { followingNodes.push({ type: 'text', value: firstChild.value.replace(blocks[node.type], '').split('\n')[1] }); } legendNodes.push({ type: 'text', value: firstTextLine.trimLeft() // remove the " " after the {prefix}: }); legendNode.children.forEach((node, index) => { if (index === 0) return; if (node.type === 'text') { const keepInLegend = node.value.split('\n')[0]; if (node.value.includes('\n')) { node.value = node.value.split('\n')[1]; followingNodes.push(node); } legendNodes.push({ type: 'text', value: keepInLegend }); } else { legendNodes.push(clone(node)); } }); const figcaption = { type: 'figcaption', children: legendNodes, data: { hName: 'figcaption' } }; const figure = { type: 'figure', children: [clone(node), figcaption], data: { hName: 'figure' } }; node.type = figure.type; node.children = figure.children; node.data = figure.data; if (followingNodes.length) { parent.children.splice(index + 1, 1, { type: 'paragraph', children: followingNodes }); } else { parent.children.splice(index + 1, 1); } }; } function getLastParagraph(xs, lastParagraph) { const len = xs.length; if (!len) return; const last = xs[len - 1]; if (last.type === 'text') return lastParagraph; if (!last.children || !last.children.length) return lastParagraph; if (last.type === 'paragraph') return getLastParagraph(last.children, last); return getLastParagraph(last.children, lastParagraph); } module.exports = plugin; ================================================ FILE: packages/remark-captions/package.json ================================================ { "name": "remark-captions", "version": "2.2.4", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-captions", "type": "git" }, "author": "François (artragis) Dambrine <perso@francoisdambrine.me>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "clone": "^2.1.2", "unist-util-visit": "^2.0.3", "xtend": "^4.0.2" } } ================================================ FILE: packages/remark-captions/src/index.js ================================================ const clone = require('clone') const visit = require('unist-util-visit') const xtend = require('xtend') const legendBlock = { table: 'Table:', code: 'Code:' } const internLegendBlock = { blockquote: 'Source:', image: 'Figure:' } function plugin (opts) { const externalBlocks = xtend(legendBlock, (opts && opts.external) || {}) const internalBlocks = xtend(internLegendBlock, (opts && opts.internal) || {}) const Compiler = this.Compiler if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return visitors.figure = function (node) { const captionedNode = node.children[0] const captionNode = node.children[1] const captionedMarkdown = this.visit(captionedNode) // compile without taking care of the "figcaption" wrapper node const captionMarkdown = this.all(captionNode).join('') if (!(captionedNode.type in externalBlocks || captionedNode.type in internalBlocks)) { return captionedMarkdown } let prefix = '' if (captionedNode.type in externalBlocks) { prefix = externalBlocks[captionedNode.type] } else if (captionedNode.type in internalBlocks) { prefix = internalBlocks[captionedNode.type] } return `${captionedMarkdown}\n${prefix} ${captionMarkdown}` } } return function transformer (tree) { Object.keys(internalBlocks).forEach((nodeType) => visit(tree, nodeType, internLegendVisitor(internalBlocks))) Object.keys(externalBlocks).forEach(nodeType => visit(tree, nodeType, externLegendVisitorCreator(externalBlocks))) visit(tree, 'figure', (figure, index, parent) => { if (parent.type === 'paragraph') { if (index === 0) { parent.type = figure.type parent.data = figure.data parent.children = figure.children return } parent.type = 'tempWrapper' } }) visit(tree, 'tempWrapper', (wrapper, index, parent) => { const newChildren = [] wrapper.children.forEach((node, i) => { const child = clone(node) if (child.type === 'figure') { newChildren.push(child) return } if (child.type === 'text' && !child.value.trim()) { return } else if (child.type === 'text') { child.value = child.value.trim() } wrapper.children[i].type = 'paragraph' wrapper.children[i].children = [child] newChildren.push(wrapper.children[i]) }) parent.children.splice(index, 1, ...newChildren) }) } } function internLegendVisitor (internalBlocks) { return function (node, index, parent) { // if already wrapped in figure, skip if (parent && parent.type === 'figure') return // if the current node has some children, the legend is the last child. // if not, the legend is the last child of the parent node. const lastP = node.children ? getLastParagraph(node.children) : parent // legend can only be in a paragraph. if (!lastP || (node.children && lastP.type !== 'paragraph') || (!node.children && parent.type !== 'paragraph')) { return } // find which child contains the last legend let legendChildIndex = -1 lastP.children.forEach((child, index) => { if (child.type === 'text' && (child.value.startsWith(internalBlocks[node.type]) || child.value.includes(`\n${internalBlocks[node.type]}`)) ) { legendChildIndex = index } }) if (legendChildIndex === -1 || (!node.children && legendChildIndex < index) ) { return } // split the text node containing the last legend and find the line containing it const potentialLegendLines = lastP.children[legendChildIndex].value.split('\n') let lastLegendIndex = -1 potentialLegendLines.forEach((line, index) => { if (line.startsWith(internalBlocks[node.type])) { lastLegendIndex = index } }) // the child containing the last legend is split in two: head contains text until // legend, tail contains legend text const tail = clone(lastP.children[legendChildIndex]) const headText = potentialLegendLines.slice(0, lastLegendIndex).join('\n') // replace existing node 'head' content with text until legend lastP.children[legendChildIndex].value = headText // legend text is put into the cloned node… const legendText = potentialLegendLines .slice(lastLegendIndex) .join('\n') .slice(internalBlocks[node.type].length) .trimLeft() tail.value = legendText // … and 'tail', the cloned node is inserted after 'head' lastP.children.splice(legendChildIndex + 1, 0, tail) // gather all nodes that should be inside the legend const legendNodes = lastP.children.slice(legendChildIndex + 1) // remove them from the parent paragraph lastP.children = lastP.children.slice(0, legendChildIndex + 1) const figcaption = { type: 'figcaption', children: legendNodes, data: { hName: 'figcaption' } } const figure = { type: 'figure', children: [ clone(node), figcaption ], data: { hName: 'figure' } } node.type = figure.type node.children = figure.children node.data = figure.data } } function externLegendVisitorCreator (blocks) { return function (node, index, parent) { if (index >= parent.children.length - 1) return if (parent.children[index + 1].type !== 'paragraph') return const legendNode = parent.children[index + 1] const firstChild = legendNode.children[0] if (firstChild.type !== 'text' || !firstChild.value.startsWith(blocks[node.type])) return const legendNodes = [] const followingNodes = [] const firstTextLine = firstChild.value.replace(blocks[node.type], '').split('\n')[0] if (firstChild.value.includes('\n')) { followingNodes.push({ type: 'text', value: firstChild.value.replace(blocks[node.type], '').split('\n')[1] }) } legendNodes.push({ type: 'text', value: firstTextLine.trimLeft() // remove the " " after the {prefix}: }) legendNode.children.forEach((node, index) => { if (index === 0) return if (node.type === 'text') { const keepInLegend = node.value.split('\n')[0] if (node.value.includes('\n')) { node.value = node.value.split('\n')[1] followingNodes.push(node) } legendNodes.push({ type: 'text', value: keepInLegend }) } else { legendNodes.push(clone(node)) } }) const figcaption = { type: 'figcaption', children: legendNodes, data: { hName: 'figcaption' } } const figure = { type: 'figure', children: [ clone(node), figcaption ], data: { hName: 'figure' } } node.type = figure.type node.children = figure.children node.data = figure.data if (followingNodes.length) { parent.children.splice(index + 1, 1, { type: 'paragraph', children: followingNodes }) } else { parent.children.splice(index + 1, 1) } } } function getLastParagraph (xs, lastParagraph) { const len = xs.length if (!len) return const last = xs[len - 1] if (last.type === 'text') return lastParagraph if (!last.children || !last.children.length) return lastParagraph if (last.type === 'paragraph') return getLastParagraph(last.children, last) return getLastParagraph(last.children, lastParagraph) } module.exports = plugin ================================================ FILE: packages/remark-comments/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-comments/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-comments/README.md ================================================ # remark-comments [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax for Markdown source comments. ## Syntax You can insert comments in the Markdown source this way: ```markdown Foo<--COMMENTS I am a comment COMMENTS-->bar ``` Everything between `<--COMMENTS` and `COMMENTS-->` will be absent from the HTML output. Compiling to Markdown will preserve all comments. ## AST node (see [mdast][mdast] specification) The plugin will product the following node and add it to the MDAST syntax tree: ```javascript interface Comments <: Node { type: "comments"; data: { comment: string; } } ``` ## Installation [npm][npm]: ```bash npm install remark-comments ``` ## Configuration Two options can be passed, as a single argument object: {beginMarker = 'COMMENTS', endMarker = 'COMMENTS'} Therefore, invoking this plugin this way: ```js .use(remarkComments, { beginMarker: 'foo', endMarker: 'bar' }) ``` will make this plugin remove what's put between `<--foo` and `bar-->`. ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkComments = require('remark-comments') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkComments) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-comments/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-comments ================================================ FILE: packages/remark-comments/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`comments 1`] = ` "<p>Foobar</p> <pre><code>Foo<--COMMENTS I will not get removed because I am in a code block DEF COMMENTS-->bar </code></pre> <p><--COMMENTS Unfinished block won't get parsed either GHI</p>" `; exports[`comments custom different markers 1`] = ` "<p>Foobar</p> <pre><code>Foo<--foo I will not get removed because I am in a code block bAR-->bar </code></pre> <p><--foo Unfinished block won't get parsed either</p>" `; exports[`comments custom same markers 1`] = ` "<p>Foobar</p> <pre><code>Foo<--foo I will not get removed because I am in a code block foo-->bar </code></pre> <p><--foo Unfinished block won't get parsed either</p>" `; exports[`compiles to markdown 1`] = ` "Foo<--COMMENTS I will be gone ABC COMMENTS-->bar Foo<--COMMENTS I will not get removed because I am in a code block DEF COMMENTS-->bar <--COMMENTS Unfinished block won't get parsed either GHI " `; ================================================ FILE: packages/remark-comments/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import remark2rehype from 'remark-rehype' import remarkStringify from 'remark-stringify' import rehypeStringify from 'rehype-stringify' import plugin from '../src/' const renderToMarkdown = text => unified() .use(reParse) .use(remarkStringify) .use(plugin) .processSync(text) test('comments', () => { const render = text => unified() .use(reParse) .use(plugin) .use(remark2rehype) .use(rehypeStringify) .processSync(text) const {contents} = render(dedent` Foo<--COMMENTS I will be gone ABC COMMENTS-->bar \`\`\` Foo<--COMMENTS I will not get removed because I am in a code block DEF COMMENTS-->bar \`\`\` <--COMMENTS Unfinished block won't get parsed either GHI `) expect(contents).toMatchSnapshot() expect(contents).not.toContain('ABC') expect(contents).toContain('DEF') expect(contents).toContain('GHI') }) test('compiles to markdown', () => { const {contents} = renderToMarkdown(dedent` Foo<--COMMENTS I will be gone ABC COMMENTS-->bar \`\`\` Foo<--COMMENTS I will not get removed because I am in a code block DEF COMMENTS-->bar \`\`\` <--COMMENTS Unfinished block won't get parsed either GHI `) expect(contents).toMatchSnapshot() expect(contents).toContain('Foo<--COMMENTS I will be gone ABC COMMENTS-->bar') }) test('comments custom different markers', () => { const render = text => unified() .use(reParse) .use(plugin, { beginMarker: 'foo', endMarker: 'bAR', }) .use(remark2rehype) .use(rehypeStringify) .processSync(text) const {contents} = render(dedent` Foo<--foo I will be gone bAR-->bar \`\`\` Foo<--foo I will not get removed because I am in a code block bAR-->bar \`\`\` <--foo Unfinished block won't get parsed either `) expect(contents).toMatchSnapshot() }) test('comments custom same markers', () => { const render = text => unified() .use(reParse) .use(plugin, { beginMarker: 'foo', endMarker: 'foo', }) .use(remark2rehype) .use(rehypeStringify) .processSync(text) const {contents} = render(dedent` Foo<--foo I will be gone foo-->bar \`\`\` Foo<--foo I will not get removed because I am in a code block foo-->bar \`\`\` <--foo Unfinished block won't get parsed either `) expect(contents).toMatchSnapshot() }) ================================================ FILE: packages/remark-comments/dist/index.js ================================================ "use strict"; const beginMarkerFactory = (marker = 'COMMENTS') => `<--${marker} `; const endMarkerFactory = (marker = 'COMMENTS') => ` ${marker}-->`; function plugin({ beginMarker = 'COMMENTS', endMarker = 'COMMENTS' } = {}) { beginMarker = beginMarkerFactory(beginMarker); endMarker = endMarkerFactory(endMarker); function locator(value, fromIndex) { return value.indexOf(beginMarker, fromIndex); } function inlineTokenizer(eat, value, silent) { const keepBegin = value.indexOf(beginMarker); const keepEnd = value.indexOf(endMarker); if (keepBegin !== 0 || keepEnd === -1) return; /* istanbul ignore if - never used (yet) */ if (silent) return true; const comment = value.substring(beginMarker.length, keepEnd); return eat(beginMarker + comment + endMarker)({ type: 'comments', value: '', data: { comment } }); } inlineTokenizer.locator = locator; const Parser = this.Parser; // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers; const inlineMethods = Parser.prototype.inlineMethods; inlineTokenizers.comments = inlineTokenizer; inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'comments'); const Compiler = this.Compiler; if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; visitors.comments = node => { return beginMarker + node.data.comment + endMarker; }; } } module.exports = plugin; ================================================ FILE: packages/remark-comments/package.json ================================================ { "name": "remark-comments", "version": "1.2.10", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-comments", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT" } ================================================ FILE: packages/remark-comments/src/index.js ================================================ const beginMarkerFactory = (marker = 'COMMENTS') => `<--${marker} ` const endMarkerFactory = (marker = 'COMMENTS') => ` ${marker}-->` function plugin ({ beginMarker = 'COMMENTS', endMarker = 'COMMENTS' } = {}) { beginMarker = beginMarkerFactory(beginMarker) endMarker = endMarkerFactory(endMarker) function locator (value, fromIndex) { return value.indexOf(beginMarker, fromIndex) } function inlineTokenizer (eat, value, silent) { const keepBegin = value.indexOf(beginMarker) const keepEnd = value.indexOf(endMarker) if (keepBegin !== 0 || keepEnd === -1) return /* istanbul ignore if - never used (yet) */ if (silent) return true const comment = value.substring(beginMarker.length, keepEnd) return eat(beginMarker + comment + endMarker)({ type: 'comments', value: '', data: { comment } }) } inlineTokenizer.locator = locator const Parser = this.Parser // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers const inlineMethods = Parser.prototype.inlineMethods inlineTokenizers.comments = inlineTokenizer inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'comments') const Compiler = this.Compiler if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return visitors.comments = (node) => { return beginMarker + node.data.comment + endMarker } } } module.exports = plugin ================================================ FILE: packages/remark-custom-blocks/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-custom-blocks/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-custom-blocks/README.md ================================================ # remark-custom-blocks [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax to create new custom blocks. It adds new nodes types to the [mdast][mdast] produced by [remark][remark]: * `{yourType}CustomBlock` If you are using [rehype][rehype], the stringified HTML result will be `div`s with configurable CSS classes. It is up to you to have CSS rules producing the desired result for these classes. The goal is to let you create blocks or panels somewhat similar to [these](http://docdock.netlify.com/shortcodes/panel/). Each custom block can specify CSS classes and whether users are allowed or required to add a custom title to the block. Only inline Markdown will be parsed in titles. ## AST nodes (see [mdast][mdast] specification) By default, the plugin will produce the following two nodes, where `whatnot` is the name of you block: ```javascript interface whatnotCustomBlock <: Parent { type: "whatnotCustomBlock"; data: { hName: "div" or "details"; hProperties: { className: [string]; } } } ``` ```javascript interface whatnotCustomBlockBody <: Parent { type: "whatnotCustomBlockBody"; data: { hName: "div"; hProperties: { className: [string]; } } } ``` If your block has a heading, the following node will also be produced: ```javascript interface whatnotCustomBlockHeading <: Parent { type: "whatnotCustomBlockHeading"; data: { hName: "div" or "summary"; hProperties: { className: [string]; } } } ``` ## Installation [npm][npm]: ```bash npm install remark-custom-blocks ``` ## Usage, Configuration, Syntax #### Configuration: The configuration object follows this pattern: ``` trigger: { classes: String, space-separated classes, optional, default: '' title: String, 'optional' | 'required', optional, default: custom titles not allowed containerElement: String, optional, default: 'div' titleElement: String, optional, default: 'div' contentsElement: String, optional, default: 'div' details: Boolean, optional, default: false } ``` `containerElement`, `titleElement`, `contentsElement` allow you to customize the html elements that will be generated by `remark-rehype` stringifier. The generated HTML structure will be ```html <containerElement> <titleElement> block title </titleElement> <contentsElement> block content </contentsElement> </containerElement> ``` you can see `details: true` as a shortcut to ```javascript { containerElement: 'details', titleElement: 'summary', contentsElement: 'div', } ``` Those specific parameters are here to help you build semantic HTML structure. #### Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkCustomBlocks = require('remark-custom-blocks') ``` #### Usage: ```javascript unified() .use(remarkParse) .use(remarkCustomBlocks, { foo: { classes: 'a-class another-class' }, bar: { classes: 'something', title: 'optional' }, qux: { classes: 'qux-block', title: 'required' }, spoiler: { classes: 'spoiler-block', title: 'optional', details: true }, }) .use(remark2rehype) .use(stringify) ``` The sample configuration provided above would have the following effect: 1. Allows you to use the following Markdown syntax to create blocks: ```markdown [[foo]] | content [[bar]] | content [[bar | my **title**]] | content [[qux | my title]] | content [[spoiler | my title]] | content ``` * Block `foo` cannot have a title, `[[foo | title]]` will not result in a block. * Block `bar` can have a title but does not need to. * Block `qux` requires a title, `[[qux]]` will not result in a block. 1. This Remark plugin would create [mdast][mdast] nodes for these two blocks, these nodes would be of type: * `fooCustomBlock`, content will be in `fooCustomBlockBody` * `barCustomBlock`, content in `barCustomBlockBody`, optional title in `barCustomBlockHeading` * `quxCustomBlock`, content in `quxCustomBlockBody`, required title in `quxCustomBlockHeading` 1. If you're using [rehype][rehype], you will end up with these 4 `div`s and 1 `details`: ```html <div class="custom-block a-class another-class"> <div class="custom-block-body"><p>content</p></div> </div> <div class="custom-block something"> <div class="custom-block-body"><p>content</p></div> </div> <div class="custom-block something"> <div class="custom-block-heading">my <strong>title</strong></div> <div class="custom-block-body"><p>content</p></div> </div> <div class="custom-block qux-block"> <div class="custom-block-heading">my title</div> <div class="custom-block-body"><p>content</p></div> </div> <details class="custom-block spoiler-block"> <summary class="custom-block-heading">my title</summary> <div class="custom-block-body"><p>content</p></div> </details> ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-custom-blocks/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-custom-blocks [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark [rehype]: https://github.com/rehypejs/rehype ================================================ FILE: packages/remark-custom-blocks/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Common 1`] = ` "<div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>Secret Block</p></div></div> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>Secret Block</p></div></div> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>another</p></div></div> <blockquote> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><blockquote> <p>Blockquote in secret block in blockquote</p> </blockquote></div></div> </blockquote> <div class=\\"custom-block information ico-after\\"><div class=\\"custom-block-body\\"><p>Information Block</p></div></div> <div class=\\"custom-block information ico-after\\"><div class=\\"custom-block-body\\"><p>another</p></div></div> <div class=\\"custom-block question ico-after\\"><div class=\\"custom-block-body\\"><p>Question Block</p></div></div> <div class=\\"custom-block question ico-after\\"><div class=\\"custom-block-body\\"><p>another</p></div></div> <div class=\\"custom-block warning ico-after\\"><div class=\\"custom-block-body\\"><p>Attention Block</p></div></div> <div class=\\"custom-block warning ico-after\\"><div class=\\"custom-block-body\\"><p>another</p></div></div> <div class=\\"custom-block error ico-after\\"><div class=\\"custom-block-body\\"><p>Erreur Block</p></div></div> <div class=\\"custom-block error ico-after\\"><div class=\\"custom-block-body\\"><p>another</p></div></div> <p>[[se]] | not a block</p> <p>[[secretsecret]] | not a block</p> <p>[[SECRET]] | not a block</p> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>Multiline block</p><blockquote> <p>with blockquote !</p> </blockquote></div></div> <p>| Not a block</p> <p>[[attention | title]] | not parsed</p>" `; exports[`compile fixture to markdown 1`] = ` "[[s]] | Secret Block [[s]] | Secret Block [[secret]] | another > [[s]] > | > Blockquote in secret block in blockquote [[i]] | Information Block [[information]] | another [[q]] | Question Block [[question]] | another [[a]] | Attention Block [[attention]] | another [[e]] | Erreur Block [[erreur]] | another \\\\[[se]] | not a block \\\\[[secretsecret]] | not a block \\\\[[SECRET]] | not a block [[s]] | Multiline block | | > with blockquote ! | Not a block \\\\[[attention | title]] | not parsed " `; exports[`compile multiline block to markdown 1`] = ` "[[information]] | content | | > blockquote | | simple paragraph " `; exports[`compile regression1 to markdown 1`] = ` "content before [[s]] | Block with content after " `; exports[`compile titled block to markdown 1`] = ` "[[details | **my** title]] | content " `; exports[`regression 1 1`] = ` "<p>content before</p> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>Block</p></div></div> <p>with content after</p>" `; exports[`regression 2 1`] = ` "<p>[[information]][titre] | test</p>" `; exports[`title is optional 1`] = ` "<div class=\\"custom-block neutral foo\\"><div class=\\"custom-block-body\\"><p>yes</p></div></div> <div class=\\"custom-block neutral foo\\"><div class=\\"custom-block-heading\\">my tïtle</div><div class=\\"custom-block-body\\"><p>yes</p></div></div>" `; exports[`title is required 1`] = ` "<p>[[neutre]] | no</p> <div class=\\"custom-block neutral foo\\"><div class=\\"custom-block-heading\\">my title</div><div class=\\"custom-block-body\\"><p>yes</p></div></div> <div class=\\"custom-block neutral foo\\"><div class=\\"custom-block-heading\\">my <strong>title</strong></div><div class=\\"custom-block-body\\"><p>yes</p></div></div>" `; exports[`with customized container, contents and title element types 1`] = ` "<div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>Secret Block</p></div></div> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>Secret Block</p></div></div> <details class=\\"custom-block\\"><div class=\\"custom-block-body\\"><p>another</p></div></details> <blockquote> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><blockquote> <p>Blockquote in secret block in blockquote</p> </blockquote></div></div> </blockquote> <div class=\\"custom-block information ico-after\\"><div class=\\"custom-block-body\\"><p>Information Block</p></div></div> <div class=\\"custom-block information ico-after\\"><fieldset class=\\"custom-block-body\\"><p>another</p></fieldset></div> <div class=\\"custom-block question ico-after\\"><div class=\\"custom-block-body\\"><p>Question Block</p></div></div> <div class=\\"custom-block question ico-after\\"><div class=\\"custom-block-body\\"><p>another</p></div></div> <div class=\\"custom-block warning ico-after\\"><div class=\\"custom-block-body\\"><p>Attention Block</p></div></div> <article class=\\"custom-block warning ico-after\\"><section class=\\"custom-block-body\\"><p>another</p></section></article> <div class=\\"custom-block error ico-after\\"><div class=\\"custom-block-body\\"><p>Erreur Block</p></div></div> <div class=\\"custom-block error ico-after\\"><div class=\\"custom-block-body\\"><p>another</p></div></div> <p>[[se]] | not a block</p> <p>[[secretsecret]] | not a block</p> <p>[[SECRET]] | not a block</p> <div class=\\"custom-block spoiler\\"><div class=\\"custom-block-body\\"><p>Multiline block</p><blockquote> <p>with blockquote !</p> </blockquote></div></div> <p>| Not a block</p> <article class=\\"custom-block warning ico-after\\"><header class=\\"custom-block-heading\\">title</header><section class=\\"custom-block-body\\"><p>not parsed</p></section></article>" `; ================================================ FILE: packages/remark-custom-blocks/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remarkStringify from 'remark-stringify' import remark2rehype from 'remark-rehype' import xtend from 'xtend' import plugin from '../src/' const defaultConfig = { secret: { classes: 'spoiler', }, s: { classes: 'spoiler', }, information: { classes: 'information ico-after', }, i: { classes: 'information ico-after', }, question: { classes: 'question ico-after', }, q: { classes: 'question ico-after', }, attention: { classes: 'warning ico-after', }, a: { classes: 'warning ico-after', }, erreur: { classes: 'error ico-after', }, e: { classes: 'error ico-after', }, neutre: { classes: 'neutral foo', title: 'required', }, customizableBlock: { classes: 'neutral foo', title: 'optional', }, details: { classes: 'spoiler', title: 'optional', details: true, }, defaultTitle: { classes: 'defaultTitle', title: 'optional', defaultTitle: 'Anything you want', }, } const render = (text, allowTitle, config) => { const realConfig = xtend(Object.assign({}, defaultConfig), config) return unified() .use(reParse) .use(remark2rehype) .use(plugin, realConfig, allowTitle) .use(stringify) .processSync(text) } const renderToMarkdown = (text) => unified() .use(reParse) .use(remarkStringify) .use(plugin, Object.assign({}, defaultConfig)) .processSync(text) const fixture = dedent` [[s]] | Secret Block [[s]] |Secret Block [[secret]] | another > [[s]] > | > Blockquote in secret block in blockquote [[i]] | Information Block [[information]] | another [[q]] | Question Block [[question]] | another [[a]] | Attention Block [[attention]] | another [[e]] | Erreur Block [[erreur]] | another [[se]] | not a block [[secretsecret]] | not a block [[SECRET]] | not a block [[s]] | Multiline block | | > with blockquote ! | Not a block [[attention | title]] | not parsed ` test('Common', () => { const {contents} = render(fixture) expect(contents).toMatchSnapshot() }) test('title is required', () => { const {contents} = render(dedent` [[neutre]] | no [[neutre|my title]] | yes [[neutre | my **title**]] | yes `) expect(contents).toMatchSnapshot() }) test('title is optional', () => { const {contents} = render(dedent` [[customizableBlock]] | yes [[customizableBlock | my <i>tïtle</i>]] | yes `) expect(contents).toMatchSnapshot() }) test('details', () => { const {contents} = render(dedent` [[details| my title]] | content `) expect(contents).toBe(dedent` <details class="custom-block spoiler">\ <summary class="custom-block-heading">my title</summary>\ <div class="custom-block-body"><p>content</p></div>\ </details>`) }) test('Errors without config', () => { const fail = () => unified() .use(reParse) .use(remark2rehype) .use(plugin) .use(stringify) .processSync('') expect(fail).toThrowError(Error) }) test('regression 1', () => { const {contents} = render(dedent` content before [[s]] |Block with content after `) expect(contents).toMatchSnapshot() }) test('regression 2', () => { const {contents} = render(dedent` [[information]][titre] | test `) expect(contents).toMatchSnapshot() }) test('compile fixture to markdown', () => { const {contents} = renderToMarkdown(fixture) expect(contents).toMatchSnapshot() const result = renderToMarkdown(contents) expect(result.contents).toBe(contents) }) test('compile regression1 to markdown', () => { const {contents} = renderToMarkdown(dedent` content before [[s]] |Block with content after `) expect(contents).toMatchSnapshot() const result = renderToMarkdown(contents) expect(result.contents).toBe(contents) }) test('compile titled block to markdown', () => { const {contents} = renderToMarkdown(dedent` [[details| **my** title]] | content `) expect(contents).toMatchSnapshot() const result = renderToMarkdown(contents) expect(result.contents).toBe(contents) }) test('compile multiline block to markdown', () => { const fixture = dedent` [[information]] | content | > blockquote | | simple paragraph ` const {contents} = renderToMarkdown(fixture) expect(contents).toMatchSnapshot() const result = renderToMarkdown(contents) expect(result.contents).toBe(contents) expect(renderToMarkdown(result.contents).contents).toBe(result.contents) }) test('compile multiline block to markdown, with multiline paragraph', () => { const fixture = dedent` [[information]] | content | | > blockquote | | a long | multiline | paragraph ` let {contents} = renderToMarkdown(fixture) contents = renderToMarkdown(contents).contents contents = renderToMarkdown(contents).contents.trim() expect(contents).toBe(fixture) }) test('with customized container, contents and title element types', () => { const {contents} = render(fixture, true, { attention: { title: 'optional', titleElement: 'header', contentsElement: 'section', containerElement: 'article', classes: 'warning ico-after', }, information: { classes: 'information ico-after', contentsElement: 'fieldset', }, secret: { title: 'optional', containerElement: 'details', titleElement: 'summary', }, }) expect(contents).toMatchSnapshot() }) test('weirdly named blocks', () => { // a block name would usually be something like "info" or "spoiler" // here we test that using regex special characters still works as // block names since they are correctly escaped const blockNames = [ '[a]', '(b|)', '[a})', '[a-Z]', '.*', '??}', '^$', '|||', ']', '[]]', ] const config = blockNames.reduce((acc, blockName, idx) => { acc[blockName] = { classes: `class${idx}`, } return acc }, {}) const render = (text, allowTitle) => unified() .use(reParse) .use(remark2rehype) .use(plugin, config, allowTitle) .use(stringify) .processSync(text) const makeFixture = (blockName) => dedent` [[${blockName}]] | [[${blockName}]] | | this ` blockNames.forEach((blockName, idx) => { const {contents} = render(makeFixture(blockName)) expect(contents).toContain(`class="custom-block class${idx}"`) expect(contents).toContain(`<pre><code>[[${blockName}]]`) }) }) ================================================ FILE: packages/remark-custom-blocks/dist/index.js ================================================ "use strict"; const spaceSeparated = require('space-separated-tokens'); function escapeRegExp(str) { return str.replace(/[-[\]{}()*+?.\\^$|/]/g, '\\$&'); } const C_NEWLINE = '\n'; const C_FENCE = '|'; function compilerFactory(nodeType) { let text; let title; return { blockHeading(node) { title = this.all(node).join(''); return ''; }, blockBody(node) { text = this.all(node).map(s => s.replace(/\n/g, '\n| ')).join('\n|\n| '); return text; }, block(node) { text = ''; title = ''; this.all(node); if (title) { return `[[${nodeType} | ${title}]]\n| ${text}`; } else { return `[[${nodeType}]]\n| ${text}`; } } }; } module.exports = function blockPlugin(availableBlocks = {}) { const pattern = Object.keys(availableBlocks).map(escapeRegExp).join('|'); if (!pattern) { throw new Error('remark-custom-blocks needs to be passed a configuration object as option'); } const regex = new RegExp(`\\[\\[(${pattern})(?: *\\| *(.*))?\\]\\]\n`); function blockTokenizer(eat, value, silent) { const now = eat.now(); const keep = regex.exec(value); if (!keep) return; if (keep.index !== 0) return; const [eaten, blockType] = keep; const potentialBlock = availableBlocks[blockType]; const blockTitle = keep[2] || potentialBlock.defaultTitle; /* istanbul ignore if - never used (yet) */ if (silent) return true; const linesToEat = []; const content = []; let idx = 0; while ((idx = value.indexOf(C_NEWLINE)) !== -1) { const next = value.indexOf(C_NEWLINE, idx + 1); // either slice until next NEWLINE or slice until end of string const lineToEat = next !== -1 ? value.slice(idx + 1, next) : value.slice(idx + 1); if (lineToEat[0] !== C_FENCE) break; // remove leading `FENCE ` or leading `FENCE` const line = lineToEat.slice(lineToEat.startsWith(`${C_FENCE} `) ? 2 : 1); linesToEat.push(lineToEat); content.push(line); value = value.slice(idx + 1); } const contentString = content.join(C_NEWLINE); const stringToEat = eaten + linesToEat.join(C_NEWLINE); const titleAllowed = potentialBlock.title && ['optional', 'required'].includes(potentialBlock.title); const titleRequired = potentialBlock.title && potentialBlock.title === 'required'; if (titleRequired && !blockTitle) return; if (!titleAllowed && blockTitle) return; const add = eat(stringToEat); if (potentialBlock.details) { potentialBlock.containerElement = 'details'; potentialBlock.titleElement = 'summary'; } const exit = this.enterBlock(); const contents = { type: `${blockType}CustomBlockBody`, data: { hName: potentialBlock.contentsElement ? potentialBlock.contentsElement : 'div', hProperties: { className: 'custom-block-body' } }, children: this.tokenizeBlock(contentString, now) }; exit(); const blockChildren = [contents]; if (titleAllowed && blockTitle) { const titleElement = potentialBlock.titleElement ? potentialBlock.titleElement : 'div'; const titleNode = { type: `${blockType}CustomBlockHeading`, data: { hName: titleElement, hProperties: { className: 'custom-block-heading' } }, children: this.tokenizeInline(blockTitle, now) }; blockChildren.unshift(titleNode); } const classList = spaceSeparated.parse(potentialBlock.classes || ''); return add({ type: `${blockType}CustomBlock`, children: blockChildren, data: { hName: potentialBlock.containerElement ? potentialBlock.containerElement : 'div', hProperties: { className: ['custom-block', ...classList] } } }); } const Parser = this.Parser; // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers; const blockMethods = Parser.prototype.blockMethods; blockTokenizers.customBlocks = blockTokenizer; blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'customBlocks'); const Compiler = this.Compiler; if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; Object.keys(availableBlocks).forEach(key => { const compiler = compilerFactory(key); visitors[`${key}CustomBlock`] = compiler.block; visitors[`${key}CustomBlockHeading`] = compiler.blockHeading; visitors[`${key}CustomBlockBody`] = compiler.blockBody; }); } // Inject into interrupt rules const interruptParagraph = Parser.prototype.interruptParagraph; const interruptList = Parser.prototype.interruptList; const interruptBlockquote = Parser.prototype.interruptBlockquote; interruptParagraph.splice(interruptParagraph.indexOf('fencedCode') + 1, 0, ['customBlocks']); interruptList.splice(interruptList.indexOf('fencedCode') + 1, 0, ['customBlocks']); interruptBlockquote.splice(interruptBlockquote.indexOf('fencedCode') + 1, 0, ['customBlocks']); }; ================================================ FILE: packages/remark-custom-blocks/package.json ================================================ { "name": "remark-custom-blocks", "version": "2.6.1", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-custom-blocks", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "space-separated-tokens": "^1.1.5" } } ================================================ FILE: packages/remark-custom-blocks/src/index.js ================================================ const spaceSeparated = require('space-separated-tokens') function escapeRegExp (str) { return str.replace(/[-[\]{}()*+?.\\^$|/]/g, '\\$&') } const C_NEWLINE = '\n' const C_FENCE = '|' function compilerFactory (nodeType) { let text let title return { blockHeading (node) { title = this.all(node).join('') return '' }, blockBody (node) { text = this.all(node).map(s => s.replace(/\n/g, '\n| ')).join('\n|\n| ') return text }, block (node) { text = '' title = '' this.all(node) if (title) { return `[[${nodeType} | ${title}]]\n| ${text}` } else { return `[[${nodeType}]]\n| ${text}` } } } } module.exports = function blockPlugin (availableBlocks = {}) { const pattern = Object .keys(availableBlocks) .map(escapeRegExp) .join('|') if (!pattern) { throw new Error('remark-custom-blocks needs to be passed a configuration object as option') } const regex = new RegExp(`\\[\\[(${pattern})(?: *\\| *(.*))?\\]\\]\n`) function blockTokenizer (eat, value, silent) { const now = eat.now() const keep = regex.exec(value) if (!keep) return if (keep.index !== 0) return const [eaten, blockType] = keep const potentialBlock = availableBlocks[blockType] const blockTitle = keep[2] || potentialBlock.defaultTitle /* istanbul ignore if - never used (yet) */ if (silent) return true const linesToEat = [] const content = [] let idx = 0 while ((idx = value.indexOf(C_NEWLINE)) !== -1) { const next = value.indexOf(C_NEWLINE, idx + 1) // either slice until next NEWLINE or slice until end of string const lineToEat = next !== -1 ? value.slice(idx + 1, next) : value.slice(idx + 1) if (lineToEat[0] !== C_FENCE) break // remove leading `FENCE ` or leading `FENCE` const line = lineToEat.slice(lineToEat.startsWith(`${C_FENCE} `) ? 2 : 1) linesToEat.push(lineToEat) content.push(line) value = value.slice(idx + 1) } const contentString = content.join(C_NEWLINE) const stringToEat = eaten + linesToEat.join(C_NEWLINE) const titleAllowed = potentialBlock.title && ['optional', 'required'].includes(potentialBlock.title) const titleRequired = potentialBlock.title && potentialBlock.title === 'required' if (titleRequired && !blockTitle) return if (!titleAllowed && blockTitle) return const add = eat(stringToEat) if (potentialBlock.details) { potentialBlock.containerElement = 'details' potentialBlock.titleElement = 'summary' } const exit = this.enterBlock() const contents = { type: `${blockType}CustomBlockBody`, data: { hName: potentialBlock.contentsElement ? potentialBlock.contentsElement : 'div', hProperties: { className: 'custom-block-body' } }, children: this.tokenizeBlock(contentString, now) } exit() const blockChildren = [contents] if (titleAllowed && blockTitle) { const titleElement = potentialBlock.titleElement ? potentialBlock.titleElement : 'div' const titleNode = { type: `${blockType}CustomBlockHeading`, data: { hName: titleElement, hProperties: { className: 'custom-block-heading' } }, children: this.tokenizeInline(blockTitle, now) } blockChildren.unshift(titleNode) } const classList = spaceSeparated.parse(potentialBlock.classes || '') return add({ type: `${blockType}CustomBlock`, children: blockChildren, data: { hName: potentialBlock.containerElement ? potentialBlock.containerElement : 'div', hProperties: { className: ['custom-block', ...classList] } } }) } const Parser = this.Parser // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers const blockMethods = Parser.prototype.blockMethods blockTokenizers.customBlocks = blockTokenizer blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'customBlocks') const Compiler = this.Compiler if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return Object.keys(availableBlocks).forEach(key => { const compiler = compilerFactory(key) visitors[`${key}CustomBlock`] = compiler.block visitors[`${key}CustomBlockHeading`] = compiler.blockHeading visitors[`${key}CustomBlockBody`] = compiler.blockBody }) } // Inject into interrupt rules const interruptParagraph = Parser.prototype.interruptParagraph const interruptList = Parser.prototype.interruptList const interruptBlockquote = Parser.prototype.interruptBlockquote interruptParagraph.splice(interruptParagraph.indexOf('fencedCode') + 1, 0, ['customBlocks']) interruptList.splice(interruptList.indexOf('fencedCode') + 1, 0, ['customBlocks']) interruptBlockquote.splice(interruptBlockquote.indexOf('fencedCode') + 1, 0, ['customBlocks']) } ================================================ FILE: packages/remark-disable-tokenizers/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-disable-tokenizers/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-disable-tokenizers/README.md ================================================ # remark-disable-tokenizers [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This [remark][remark] plugin can disable any or all remark `blockTokenizers` and `inlineTokenizers`. It can not only disable the ones provided by remark core, but also any other tokenizer that has been added to the remark parser whether through plugins or not. Remark default tokenizers that can be disabled are listed [here][remark-doc]: * [blockTokenizers][blockTokenizers] * [inlineTokenizers][inlineTokenizers] ## Configuration Two options can be passed, as a single argument object: {block = [], inline = []} Each of these can contain both tokenizer names as strings or arrays `['tokenizerName', 'error message']`. * A string `name`: this tokenizer will be disabled * An array `[name, message]`: this tokenizer, if used, will throw an `Error` with the message `message` ## Motivation In some situations it might be interesting to only parse inline Markdown syntax. We created it for the purpose of parsing/rendering forum signatures -- short textual content people can use to sign their messages on web forums. In this context it made no sense to allow elements that would eat up a lot of vertical space. ## Installation [npm][npm]: ```bash npm install remark-disable-tokenizers ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkDisableBlocks = require('remark-disable-tokenizers') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkDisableTokenizers, { block: [ 'indentedCode', 'fencedCode', // I'd like to ignore a bunch of blockTokenizers but specifically // I want blockquotes to throw this `Error` if used in the input Markdown ['blockquote', 'Blockquote are not allowed!'], 'atxHeading', 'setextHeading', 'footnote', 'table', 'custom_blocks' ], inline: [ 'emphasis' // emphasis is the only inlineTokenizer I'm disallowing ] }) .use(remark2rehype) .use(stringify) ``` ## Caveats * `autoLink` is not working correctly -- in order to disable auto-linking, you have to pass `url` to the array of disabled inline tokenizers. ```javascript unified() .use(remarkParse) .use(remarkDisableTokenizers, { inline: ['url'] }) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-disable-tokenizers/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-disable-tokenizers [remark]: https://github.com/remarkjs/remark [remark-doc]: https://github.com/remarkjs/remark/tree/master/packages/remark-parse#parserblocktokenizers [blockTokenizers]: https://github.com/remarkjs/remark/tree/master/packages/remark-parse#parserblockmethods [inlineTokenizers]: https://github.com/remarkjs/remark/tree/master/packages/remark-parse#parserinlinemethods ================================================ FILE: packages/remark-disable-tokenizers/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`block + inline 1`] = ` "<p>*a*</p> <p>> *a*</p> <p>foo <code>bar</code> baz</p>" `; exports[`block 1`] = ` "<p><em>a</em></p> <p>> <em>a</em></p> <p>foo <code>bar</code> baz</p>" `; exports[`block throws + inline 1`] = `"Blockquote are not allowed!"`; exports[`block throws + inline throws 1`] = `"Blockquote are not allowed!"`; exports[`block throws 1`] = `"Blockquote are not allowed!"`; exports[`does nothing 1`] = ` "<p><em>a</em></p> <blockquote> <p><em>a</em></p> </blockquote> <p>foo <code>bar</code> baz</p>" `; exports[`inline 1`] = ` "<p>*a*</p> <blockquote> <p>*a*</p> </blockquote> <p>foo <code>bar</code> baz</p>" `; exports[`inline throws 1`] = `"nope"`; exports[`regression: actually turn off the tokenizer #412 1`] = ` "<p>hello world</p>" `; exports[`unknown tokenizer 1`] = ` "<p><em>a</em></p> <blockquote> <p><em>a</em></p> </blockquote> <p>foo <code>bar</code> baz</p>" `; ================================================ FILE: packages/remark-disable-tokenizers/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import plugin from '../src/' test('block throws + inline', () => { function t () { unified() .use(reParse) .use(plugin, { block: [ ['blockquote', 'Blockquote are not allowed!'], ], inline: [ 'emphasis', ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) } expect(t).toThrowErrorMatchingSnapshot() }) test('block + inline', () => { const {contents} = unified() .use(reParse) .use(plugin, { block: [ 'blockquote', ], inline: [ 'emphasis', ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) expect(contents).toMatchSnapshot() }) test('block', () => { const {contents} = unified() .use(reParse) .use(plugin, { block: [ 'blockquote', ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) expect(contents).toMatchSnapshot() }) test('inline', () => { const {contents} = unified() .use(reParse) .use(plugin, { inline: [ 'emphasis', ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) expect(contents).toMatchSnapshot() }) test('inline throws', () => { function t () { unified() .use(reParse) .use(plugin, { inline: [ ['emphasis', 'nope'], ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) } expect(t).toThrowErrorMatchingSnapshot() }) test('block throws', () => { function t () { unified() .use(reParse) .use(plugin, { block: [ ['blockquote', 'Blockquote are not allowed!'], ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) } expect(t).toThrowErrorMatchingSnapshot() }) test('block throws + inline throws', () => { function t () { unified() .use(reParse) .use(plugin, { block: [ ['blockquote', 'Blockquote are not allowed!'], ], inline: [ ['emphasis', 'Nope.'], ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) } expect(t).toThrowErrorMatchingSnapshot() }) test('does nothing', () => { const {contents} = unified() .use(reParse) .use(plugin) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) expect(contents).toMatchSnapshot() }) test('unknown tokenizer', () => { const {contents} = unified() .use(reParse) .use(plugin, { inline: [ 'foo bar', ], }) .use(remark2rehype) .use(stringify) .processSync(dedent` *a* > *a* foo \`bar\` baz `) expect(contents).toMatchSnapshot() }) test('regression: actually turn off the tokenizer #412', () => { const {contents} = unified() .use(reParse) .use(plugin, { block: [ ['html'], ], inline: [ ['html'], ], }) .use(remark2rehype) .use(stringify) .processSync(`hello\nworld`) expect(contents).toMatchSnapshot() }) ================================================ FILE: packages/remark-disable-tokenizers/dist/index.js ================================================ "use strict"; const clone = require('clone'); const noop = () => false; const noopLocator = () => -1; const throwing = msg => () => { throw new Error(msg); }; function plugin({ block = [], inline = [] } = {}) { if (block.length) { block.filter(key => { if (Array.isArray(key)) return block.map(xs => xs[0]).includes(key[0]); return block.includes(key); }).forEach(key => { if (Array.isArray(key) && key.length === 2) { this.Parser.prototype.blockTokenizers[key[0]] = throwing(key[1]); } else { this.Parser.prototype.blockTokenizers[key] = noop; } }); } if (inline.length) { inline.filter(key => { if (Array.isArray(key)) return inline.map(xs => xs[0]).includes(key[0]); return inline.includes(key); }).forEach(key => { let tokenizerName; let replacer; if (Array.isArray(key) && key.length === 2) { tokenizerName = key[0]; replacer = throwing(key[1]); } else { tokenizerName = key; replacer = clone(noop); } if (this.Parser.prototype.inlineTokenizers[tokenizerName]) { Object.keys(this.Parser.prototype.inlineTokenizers[tokenizerName]).forEach(prop => { replacer[prop] = this.Parser.prototype.inlineTokenizers[tokenizerName][prop]; }); } this.Parser.prototype.inlineTokenizers[tokenizerName] = replacer; this.Parser.prototype.inlineTokenizers[tokenizerName].locator = noopLocator; }); } } module.exports = plugin; ================================================ FILE: packages/remark-disable-tokenizers/package.json ================================================ { "name": "remark-disable-tokenizers", "version": "1.1.1", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-disable-tokenizers", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "clone": "^2.1.2" } } ================================================ FILE: packages/remark-disable-tokenizers/src/index.js ================================================ const clone = require('clone') const noop = () => false const noopLocator = () => -1 const throwing = (msg) => () => { throw new Error(msg) } function plugin ({ block = [], inline = [] } = {}) { if (block.length) { block .filter((key) => { if (Array.isArray(key)) return block.map(xs => xs[0]).includes(key[0]) return block.includes(key) }) .forEach((key) => { if (Array.isArray(key) && key.length === 2) { this.Parser.prototype.blockTokenizers[key[0]] = throwing(key[1]) } else { this.Parser.prototype.blockTokenizers[key] = noop } }) } if (inline.length) { inline .filter((key) => { if (Array.isArray(key)) return inline.map(xs => xs[0]).includes(key[0]) return inline.includes(key) }) .forEach((key) => { let tokenizerName let replacer if (Array.isArray(key) && key.length === 2) { tokenizerName = key[0] replacer = throwing(key[1]) } else { tokenizerName = key replacer = clone(noop) } if (this.Parser.prototype.inlineTokenizers[tokenizerName]) { Object .keys(this.Parser.prototype.inlineTokenizers[tokenizerName]) .forEach((prop) => { replacer[prop] = this.Parser.prototype.inlineTokenizers[tokenizerName][prop] }) } this.Parser.prototype.inlineTokenizers[tokenizerName] = replacer this.Parser.prototype.inlineTokenizers[tokenizerName].locator = noopLocator }) } } module.exports = plugin ================================================ FILE: packages/remark-emoticons/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-emoticons/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-emoticons/README.md ================================================ # remark-emoticons [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This [remark][remark] plugin parses ASCII emoticons such as `:D` or shortcodes such as `:smiling-cat:`. It can be configured to parse any shortcode/emoticon you wish. It introduces a new [MDAST][mdast] node type: "emoticon". This plugin is compatible with [rehype][rehype], turning the emoticons or shortcodes into HTML `<img>` tags with customizable classes. ## AST node (see [mdast][mdast] specification) ```javascript interface emoticon <: Node { type: "emoticon"; value: string; data: { hName: "img"; hProperties: { src: string; alt: string; className: string; } } } ``` ## Syntax The following Markdown: ```markdown :D ``` if you configured the plugin with: ```javascript { emoticons: { ':D': '/static/smileys/happy.png', }, classes: 'some-smiley foo', } ``` will produce this AST node: ```javascript { type: "emoticon", value: ":D", data: { hName: "img"; hProperties: { src: '/static/smileys/happy.png'; alt: ":D"; className: "some-smiley foo"; } } } ``` If you use [rehype][rehype] stringifier it will output: ```html <img src="/static/smileys/happy.png" alt=":D" class="some-smiley foo"> ``` ## Installation [npm][npm]: ```bash npm install remark-emoticons ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkEmoticons = require('remark-emoticons') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkEmoticons, { emoticons: { ':D': '/static/smileys/happy.png', }, classes: 'some-class' }) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-emoticons/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-emoticons [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark [rehype]: https://github.com/rehypejs/rehype ================================================ FILE: packages/remark-emoticons/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`emoticons 1`] = ` "<p>Hello <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\" class=\\"foo bar\\"> Hey <img src=\\"/static/smileys/heureux.png\\" alt=\\":D\\" class=\\"foo bar\\"></p> <p><img src=\\"/static/smileys/smile.png\\" alt=\\":)\\" class=\\"foo bar\\"></p> <blockquote> <p>Citation</p> </blockquote> <p><img src=\\"/static/smileys/heureux.png\\" alt=\\":D\\" class=\\"foo bar\\"> This is not a caption</p> <p><img src=\\"https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png\\" alt=\\"toto\\"></p> <p><img src=\\"/static/smileys/heureux.png\\" alt=\\":D\\" class=\\"foo bar\\"> This is not a caption</p> <p>This is not a smiley:)</p> <p>Not :)a smiley either.</p> <p>Smiley after another node: <a href=\\"#foo\\">link</a> <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\" class=\\"foo bar\\"></p> <p>Smiley after another node w/2 spaces: <a href=\\"#foo\\">link</a> <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\" class=\\"foo bar\\"></p> <p>Smiley after another node w/3 spaces: <a href=\\"#foo\\">link</a> <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\" class=\\"foo bar\\"></p>" `; exports[`emoticons without class 1`] = ` "<p>Hello <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\"> Hey <img src=\\"/static/smileys/heureux.png\\" alt=\\":D\\"></p> <p><img src=\\"/static/smileys/smile.png\\" alt=\\":)\\"></p> <blockquote> <p>Citation</p> </blockquote> <p><img src=\\"/static/smileys/heureux.png\\" alt=\\":D\\"> This is not a caption</p> <p><img src=\\"https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png\\" alt=\\"toto\\"></p> <p><img src=\\"/static/smileys/heureux.png\\" alt=\\":D\\"> This is not a caption</p> <p>This is not a smiley:)</p> <p>Not :)a smiley either.</p> <p>Smiley after another node: <a href=\\"#foo\\">link</a> <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\"></p> <p>Smiley after another node w/2 spaces: <a href=\\"#foo\\">link</a> <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\"></p> <p>Smiley after another node w/3 spaces: <a href=\\"#foo\\">link</a> <img src=\\"/static/smileys/smile.png\\" alt=\\":)\\"></p>" `; exports[`renders to markdown 1`] = ` "Hello :) Hey :D :) > Citation :D This is not a caption ![toto](https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png) :D This is not a caption This is not a smiley:) Not :)a smiley either. Smiley after another node: [link](#foo) :) Smiley after another node w/2 spaces: [link](#foo) :) Smiley after another node w/3 spaces: [link](#foo) :) " `; exports[`unicode emoticons 1`] = `"<p><img src=\\"/static/smileys/svg/1f9d0.svg\\" alt=\\"🧐\\" class=\\"foo bar\\"></p>"`; ================================================ FILE: packages/remark-emoticons/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import visit from 'unist-util-visit' import remark2rehype from 'remark-rehype' import remarkStringify from 'remark-stringify' import remarkCaption from '../../remark-captions/src' import plugin from '../src/' const config = { emoticons: { ':ange:': '/static/smileys/ange.png', ':colere:': '/static/smileys/angry.gif', 'o_O': '/static/smileys/blink.gif', ';)': '/static/smileys/clin.png', ':diable:': '/static/smileys/diable.png', ':D': '/static/smileys/heureux.png', '^^': '/static/smileys/hihi.png', ':o': '/static/smileys/huh.png', ':p': '/static/smileys/langue.png', ':magICIen:': '/static/smileys/magicien.png', ':colere2:': '/static/smileys/mechant.png', ':ninja:': '/static/smileys/ninja.png', 'x(': '/static/smileys/pinch.png', ':pirate:': '/static/smileys/pirate.png', ":'(": '/static/smileys/pleure.png', ':lol:': '/static/smileys/rire.gif', ':honte:': '/static/smileys/rouge.png', ':-°': '/static/smileys/siffle.png', ':)': '/static/smileys/smile.png', ':soleil:': '/static/smileys/soleil.png', ':(': '/static/smileys/triste.png', ':euh:': '/static/smileys/unsure.gif', ':waw:': '/static/smileys/waw.png', ':zorro:': '/static/smileys/zorro.png', '🧐': '/static/smileys/svg/1f9d0.svg', }, classes: 'foo bar', } const render = text => unified() .use(reParse) .use(plugin, config) .use(remark2rehype) .use(stringify) .processSync(text) const renderToMarkdown = text => unified() .use(reParse) .use(remarkStringify) .use(plugin, config) .use(remarkCaption) .processSync(text) test('emoticons', () => { const {contents} = render(dedent` Hello :) Hey :D :) > Citation :D This is not a caption ![toto](https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png) :D This is not a caption This is not a smiley:) Not :)a smiley either. Smiley after another node: [link](#foo) :) Smiley after another node w/2 spaces: [link](#foo) :) Smiley after another node w/3 spaces: [link](#foo) :) `) expect(contents).toMatchSnapshot() }) test('does not eat spaces leading to a smiley', () => { const {contents} = render(dedent` *hey* :) [ho](#) :D let's go `) expect(contents).not.toContain('</em><img') expect(contents).not.toContain('</a><img') }) test('confusable locators', () => { const {contents} = render(dedent` :) o_O :) `) expect((contents.match(/<img/g) || []).length).toBe(3) }) test('multiple chars no match', () => { const {contents} = render(dedent` this contains an o_O `) expect((contents.match(/<img/g) || []).length).toBe(1) }) test('unicode emoticons', () => { const {contents} = render(dedent` 🧐 `) expect(contents).toContain('<img') expect(contents).toMatchSnapshot() }) test('emoticons without class', () => { const render = text => unified() .use(reParse) .use(plugin, config) .use(remark2rehype) .use(stringify) .processSync(text) delete config.classes const {contents} = render(dedent` Hello :) Hey :D :) > Citation :D This is not a caption ![toto](https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png) :D This is not a caption This is not a smiley:) Not :)a smiley either. Smiley after another node: [link](#foo) :) Smiley after another node w/2 spaces: [link](#foo) :) Smiley after another node w/3 spaces: [link](#foo) :) `) expect(contents).toMatchSnapshot() }) test('errors without config', () => { const fail = () => unified() .use(reParse) .use(remark2rehype) .use(plugin) .use(stringify) .processSync('') expect(fail).toThrowError(Error) }) test('renders to markdown', () => { const {contents} = renderToMarkdown(dedent` Hello :) Hey :D :) > Citation :D This is not a caption ![toto](https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png) :D This is not a caption This is not a smiley:) Not :)a smiley either. Smiley after another node: [link](#foo) :) Smiley after another node w/2 spaces: [link](#foo) :) Smiley after another node w/3 spaces: [link](#foo) :) `) expect(contents).toMatchSnapshot() const recompiled = renderToMarkdown(contents).contents expect(recompiled).toBe(contents) }) test('emoticons are case insensitive', () => { const render = text => unified() .use(reParse) .use(plugin, config) .use(() => (tree) => { visit(tree, 'emoticon', (node) => { node.data.hProperties.alt = '' }) }) .use(remark2rehype) .use(stringify) .processSync(text) expect(render(`:p`).contents).toBe(render(`:P`).contents) expect(render(`:d`).contents).toBe(render(`:D`).contents) expect(render(`:magicien:`).contents).toBe(render(`:MAGICIEN:`).contents) expect(render(`:magicien:`).contents).toBe(render(`:MagICIEn:`).contents) expect(render(`:magICIen:`).contents).toBe(render(`:MagICIEn:`).contents) }) ================================================ FILE: packages/remark-emoticons/dist/index.js ================================================ "use strict"; const whitespace = require('is-whitespace-character'); function escapeRegExp(str) { return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); } module.exports = function inlinePlugin(ctx) { const emoticonClasses = ctx && ctx.classes; const emoticonsRaw = ctx && ctx.emoticons; if (!emoticonsRaw) { throw new Error('remark-emoticons needs to be passed a configuration object as option'); } // Convert emoticons to lowercase const emoticons = Object.keys(emoticonsRaw).reduce((acc, key) => { acc[key.toLowerCase()] = emoticonsRaw[key]; return acc; }, {}); // Create a list composed of the first character of each emoticon const firstChars = Object.keys(emoticons).reduce((acc, key) => { const firstChar = key.charAt(0); if (acc.indexOf(firstChar) === -1) acc.push(firstChar); return acc; }, []); const pattern = Object.keys(emoticons).map(escapeRegExp).join('|'); const regex = new RegExp(`(?:\\s|^)(${pattern})(?:\\s|$)`, 'i'); function locator(value, fromIndex) { let lowestMatch = -1; // Iterate on chars for (let c = 0; c < firstChars.length; c++) { const char = firstChars[c]; let offset = 0; let match = -1; // Oftentimes, the first occurence is not good, so loop on all possible ones while (match === -1 && offset < value.length - 1) { match = value.indexOf(char, fromIndex + offset); // Also match uppercase if (match === -1 && char !== char.toUpperCase()) { match = value.indexOf(char.toUpperCase(), fromIndex + offset); } if (match === -1) { break; // A smiley should be precedeed by at least one whitespace } else if (!whitespace(value[match - 1])) { offset = match; match = -1; } } if (match !== -1 && (lowestMatch === -1 || match < lowestMatch)) { lowestMatch = match; } } return lowestMatch; } function inlineTokenizer(eat, value, silent) { const keep = regex.exec(value); if (!keep || keep.index !== 0) return true; if (!keep[0].startsWith(keep[1])) return true; /* istanbul ignore if - never used (yet) */ if (silent) return true; const toEat = keep[1]; const emoticon = toEat.trim(); const src = emoticons[emoticon.toLowerCase()]; const emoticonNode = { type: 'emoticon', value: emoticon, data: { hName: 'img', hProperties: { src, alt: emoticon } } }; if (emoticonClasses) { emoticonNode.data.hProperties.class = emoticonClasses; } eat(toEat)(emoticonNode); } inlineTokenizer.locator = locator; const Parser = this.Parser; // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers; const inlineMethods = Parser.prototype.inlineMethods; inlineTokenizers.emoticons = inlineTokenizer; inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'emoticons'); const Compiler = this.Compiler; if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; visitors.emoticon = node => node.value; } }; ================================================ FILE: packages/remark-emoticons/package.json ================================================ { "name": "remark-emoticons", "version": "2.3.2", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-emoticons", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "dependencies": { "is-whitespace-character": "^1.0.4" }, "license": "MIT" } ================================================ FILE: packages/remark-emoticons/src/index.js ================================================ const whitespace = require('is-whitespace-character') function escapeRegExp (str) { return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') } module.exports = function inlinePlugin (ctx) { const emoticonClasses = ctx && ctx.classes const emoticonsRaw = ctx && ctx.emoticons if (!emoticonsRaw) { throw new Error('remark-emoticons needs to be passed a configuration object as option') } // Convert emoticons to lowercase const emoticons = Object.keys(emoticonsRaw).reduce((acc, key) => { acc[key.toLowerCase()] = emoticonsRaw[key] return acc }, {}) // Create a list composed of the first character of each emoticon const firstChars = Object.keys(emoticons).reduce((acc, key) => { const firstChar = key.charAt(0) if (acc.indexOf(firstChar) === -1) acc.push(firstChar) return acc }, []) const pattern = Object.keys(emoticons).map(escapeRegExp).join('|') const regex = new RegExp(`(?:\\s|^)(${pattern})(?:\\s|$)`, 'i') function locator (value, fromIndex) { let lowestMatch = -1 // Iterate on chars for (let c = 0; c < firstChars.length; c++) { const char = firstChars[c] let offset = 0 let match = -1 // Oftentimes, the first occurence is not good, so loop on all possible ones while (match === -1 && offset < (value.length - 1)) { match = value.indexOf(char, fromIndex + offset) // Also match uppercase if (match === -1 && char !== char.toUpperCase()) { match = value.indexOf(char.toUpperCase(), fromIndex + offset) } if (match === -1) { break // A smiley should be precedeed by at least one whitespace } else if (!whitespace(value[match - 1])) { offset = match match = -1 } } if (match !== -1 && (lowestMatch === -1 || match < lowestMatch)) { lowestMatch = match } } return lowestMatch } function inlineTokenizer (eat, value, silent) { const keep = regex.exec(value) if (!keep || keep.index !== 0) return true if (!keep[0].startsWith(keep[1])) return true /* istanbul ignore if - never used (yet) */ if (silent) return true const toEat = keep[1] const emoticon = toEat.trim() const src = emoticons[emoticon.toLowerCase()] const emoticonNode = { type: 'emoticon', value: emoticon, data: { hName: 'img', hProperties: { src, alt: emoticon } } } if (emoticonClasses) { emoticonNode.data.hProperties.class = emoticonClasses } eat(toEat)(emoticonNode) } inlineTokenizer.locator = locator const Parser = this.Parser // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers const inlineMethods = Parser.prototype.inlineMethods inlineTokenizers.emoticons = inlineTokenizer inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'emoticons') const Compiler = this.Compiler if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return visitors.emoticon = (node) => node.value } } ================================================ FILE: packages/remark-escape-escaped/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-escape-escaped/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-escape-escaped/README.md ================================================ This plugin escapes HTML entities from Markdown input. This is the default behavior though, it's configurable. * `&` -> `&` * `&` -> `&` * `é` -> `é` this plugin does * `&` -> `&` * `&` -> `&amp;` * `é` -> `&eacute;` ================================================ FILE: packages/remark-escape-escaped/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`with 1`] = ` "<p>this plugin does</p> <ul> <li>& -> &#x26;</li> <li>&amp; -> &#x26;amp;</li> <li>&eacute; -> &#x26;eacute;</li> </ul>" `; exports[`without 1`] = ` "<p>remark does</p> <ul> <li>& -> &#x26;</li> <li>&amp; -> &#x26;</li> <li>&eacute; -> é</li> </ul>" `; ================================================ FILE: packages/remark-escape-escaped/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import plugin from '../src/' const render = (text, config) => unified() .use(reParse) .use(remark2rehype) .use(plugin, config) .use(stringify) .processSync(text) test('with', () => { const {contents} = render(dedent` this plugin does * & -> & * & -> &amp; * é -> &eacute; `) expect(contents).toMatchSnapshot() }) test('without', () => { const {contents} = render(dedent` remark does * & -> & * & -> & * é -> é `) expect(contents).toMatchSnapshot() }) test('Errors with invalid config: []', () => { const fail = () => render('', []) expect(fail).toThrowError(Error) }) test('Errors with invalid config: 1', () => { const fail = () => render('', 1) expect(fail).toThrowError(Error) }) ================================================ FILE: packages/remark-escape-escaped/dist/index.js ================================================ "use strict"; function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); // eslint-disable-line no-useless-escape } module.exports = function escapeEscaped(entitiesToKeep = ['&']) { if (!Array.isArray(entitiesToKeep) || !entitiesToKeep.length) { throw new Error('remark-escape-escaped needs to be passed a configuration array as option'); } const pattern = entitiesToKeep.map(escapeRegExp).join('|'); const regex = new RegExp(`(${pattern})`); function locator(value, fromIndex) { const indices = entitiesToKeep.map(entity => value.indexOf(entity, fromIndex)); return Math.min(...indices); } function inlineTokenizer(eat, value, silent) { const keep = regex.exec(value); if (keep) { if (keep.index !== 0) return true; /* istanbul ignore if - never used (yet) */ if (silent) return true; eat(keep[0])({ type: 'text', value: keep[0] }); } } inlineTokenizer.locator = locator; const Parser = this.Parser; // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers; const inlineMethods = Parser.prototype.inlineMethods; inlineTokenizers.keep_entities = inlineTokenizer; inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'keep_entities'); }; ================================================ FILE: packages/remark-escape-escaped/package.json ================================================ { "name": "remark-escape-escaped", "version": "0.0.35", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-escape-escaped", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT" } ================================================ FILE: packages/remark-escape-escaped/src/index.js ================================================ function escapeRegExp (str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') // eslint-disable-line no-useless-escape } module.exports = function escapeEscaped (entitiesToKeep = ['&']) { if (!Array.isArray(entitiesToKeep) || !entitiesToKeep.length) { throw new Error('remark-escape-escaped needs to be passed a configuration array as option') } const pattern = entitiesToKeep.map(escapeRegExp).join('|') const regex = new RegExp(`(${pattern})`) function locator (value, fromIndex) { const indices = entitiesToKeep.map(entity => value.indexOf(entity, fromIndex)) return Math.min(...indices) } function inlineTokenizer (eat, value, silent) { const keep = regex.exec(value) if (keep) { if (keep.index !== 0) return true /* istanbul ignore if - never used (yet) */ if (silent) return true eat(keep[0])({ type: 'text', value: keep[0] }) } } inlineTokenizer.locator = locator const Parser = this.Parser // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers const inlineMethods = Parser.prototype.inlineMethods inlineTokenizers.keep_entities = inlineTokenizer inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'keep_entities') } ================================================ FILE: packages/remark-fix-guillemets/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-fix-guillemets/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-fix-guillemets/README.md ================================================ # remark-fix-guillemets [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin fixes `typographic-plugin` when used together with `remark-parse`. ## Motivation When `<<a>>` is parsed by `remark-parse` the resulting tree is: ``` root[1] (1:1-1:6, 0-5) └─ paragraph[3] (1:1-1:6, 0-5) ├─ text: "<" (1:1-1:2, 0-1) ├─ html: "<a>" (1:2-1:5, 1-4) └─ text: ">" (1:5-1:6, 0-1) ``` As you see here `<<` got split into a text node `<` and an HTML node. Since [remark-textr][remark-textr] only gets applied to 'text' nodes, `<<` is not replaced by `«`. This plugin replaces the previous tree with: ``` root[1] (1:1-1:6, 0-5) └─ paragraph[1] (1:1-1:6, 0-5) └─ text: "<<a>>" (1:1-1:6, 0-5) ``` ## Install [npm][npm]: ```sh npm install --save remark-fix-guillemets ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkFixGuillemets = require('remark-fix-guillemets') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkFixGuillemets) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-fix-guillemets/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-fix-guillemets [remark-textr]: https://github.com/remarkjs/remark-textr ================================================ FILE: packages/remark-fix-guillemets/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`do-not-replace 1`] = `"<p>></p>"`; exports[`issue-80 1`] = ` "<p>« html » « html1 » « html2 » « html3 » « html4 » « <strong>bold</strong> »</p> <p>« html » « html1 » <foobar> « html3 » « html4 » « <strong>bold</strong> »</p>" `; exports[`no-html-block 1`] = `"<p>« 1 »</p>"`; ================================================ FILE: packages/remark-fix-guillemets/__tests__/index.js ================================================ import dedent from 'dedent' import remark2rehype from 'remark-rehype' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import textr from 'textr' import textrGuillemets from 'typographic-guillemets' import unified from 'unified' import visit from 'unist-util-visit' import remarkFixGuillemets from '../src/' function remarkTextr ({plugins = [], options = {}} = {}) { let fn return function transformer (tree) { fn = plugins.reduce( (processor, p) => processor.use(typeof p === 'string' ? require(p) : p), textr(options) ) visit(tree, 'text', visitor) } function visitor (node) { node.value = fn(node.value) } } const render = (text, config) => unified() .use(reParse, { gfm: true, commonmark: false, footnotes: true, /* sets list of known blocks to nothing, otherwise <h3>hey</h3> would become <h3>hey</h3> instead of <p><h3>hey</h3></p> */ blocks: [], }) .use(remarkFixGuillemets) .use(remark2rehype) .use(stringify) .use(remarkTextr, { plugins: [ textrGuillemets, ], options: { locale: 'fr', }, }) .processSync(text) test('issue-80', () => { const {contents} = render(dedent` <<html>> <<html1>> <<html2>> <<html3>> <<html4>> <<**bold**>> <<html>> <<html1>> <foo<html2>bar> <<html3>> <<html4>> <<**bold**>> `) expect(contents).toMatchSnapshot() }) test('no-html-block', () => { const {contents} = render(dedent` << 1 >> `) expect(contents).toMatchSnapshot() }) test('do-not-replace', () => { const {contents} = render(dedent` <a>> `) expect(contents).toMatchSnapshot() }) ================================================ FILE: packages/remark-fix-guillemets/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); function plugin() { return transformer; } function transformer(tree) { visit(tree, 'paragraph', visitor); } function visitor(node, index, parent) { for (let c = 1; c < node.children.length - 1; c++) { const child = node.children[c]; if (child.type === 'html') { const previousNode = node.children[c - 1]; const nextNode = node.children[c + 1]; if (previousNode.type === 'text' && previousNode.value.slice(-1) === '<' && nextNode.type === 'text' && nextNode.value[0] === '>') { previousNode.value += child.value; previousNode.value += nextNode.value; node.children.splice(c, 2); c -= 1; } } } } module.exports = plugin; ================================================ FILE: packages/remark-fix-guillemets/package.json ================================================ { "name": "remark-fix-guillemets", "version": "1.1.3", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-fix-guillemets", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" }, "devDependencies": { "typographic-guillemets": "file:../typographic-guillemets" } } ================================================ FILE: packages/remark-fix-guillemets/src/index.js ================================================ const visit = require('unist-util-visit') function plugin () { return transformer } function transformer (tree) { visit(tree, 'paragraph', visitor) } function visitor (node, index, parent) { for (let c = 1; c < node.children.length - 1; c++) { const child = node.children[c] if (child.type === 'html') { const previousNode = node.children[c - 1] const nextNode = node.children[c + 1] if (previousNode.type === 'text' && previousNode.value.slice(-1) === '<' && nextNode.type === 'text' && nextNode.value[0] === '>') { previousNode.value += child.value previousNode.value += nextNode.value node.children.splice(c, 2) c -= 1 } } } } module.exports = plugin ================================================ FILE: packages/remark-grid-tables/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-grid-tables/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-grid-tables/README.md ================================================ # remark-grid-tables [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax to describe tables. It was inspired by [this syntax](https://github.com/smartboyathome/Markdown-GridTables/blob/b4d16d5d254bed4336713d27eb8a37dc0e5f4273/mdx_grid_tables.py). ## AST nodes It adds a new node type to the [mdast][mdast] produced by [remark][remark]: `gridTable`. If you are using [rehype][rehype], the stringified HTML result will be a `table`. It is up to you to have CSS rules producing the desired result for these `table`. ```javascript interface GridTable <: Parent { type: "gridTable"; data: { hName: "table"; } } ``` A `gridTable` mdast node can contain the following mdast node types: ### `tableHeader` ```javascript interface TableHeader <: Parent { type: "tableHeader"; data: { hName: "thead" or "tbody"; } } ``` Where `hName` can be either `thead` or `tbody`. ### `tableRow` ```javascript interface TableRow <: Parent { type: "tableRow"; data: { hName: "tr"; } } ``` ### `tableCell` ```javascript interface TableCell <: Parent { type: "tableCell"; data: { hName: "td"; hProperties: { colspan: number >= 1; rowspan: number >= 1; } } } ``` ## Syntax For example: ```markdown # Grid table ## Basic example +-------+----------+------+ | Table Headings | Here | +-------+----------+------+ | Sub | Headings | Too | +=======+==========+======+ | cell | column spanning | + spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ ``` produces: ```html <h1>Grid table</h1> <h2>Basic example</h2> <table> <thead> <tr> <th colspan="2" rowspan="1"><p>Table Headings</p></th> <th colspan="1" rowspan="1"><p>Here</p></th> </tr> <tr> <th colspan="1" rowspan="1"><p>Sub</p></th> <th colspan="1" rowspan="1"><p>Headings</p></th> <th colspan="1" rowspan="1"><p>Too</p></th> </tr> </thead> <tbody> <tr> <td colspan="1" rowspan="2"><p>cell spans rows</p></td> <td colspan="2" rowspan="1"><p>column spanning</p></td> </tr> <tr> <td colspan="1" rowspan="1"><p>normal</p></td> <td colspan="1" rowspan="1"><p>cell</p></td> </tr> <tr> <td colspan="1" rowspan="1"><p>multi line</p><p>cells too</p></td> <td colspan="2" rowspan="1"><p>cells can be <em>formatted</em> <strong>paragraphs</strong></p></td> </tr> </tbody> </table> ``` Note: the top of a cell must be indicated by `+-` followed by some `-` or `+` and finished by `-+`. So, this is not a correct cell: ```md +--+ |a | +--+ ``` But, this is a correct cell: ```md +---+ | a | +---+ ``` ## Installation [npm][npm]: ```bash npm install remark-grid-tables ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkGridTables = require('remark-grid-tables') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkGridTables) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-grid-tables/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-grid-tables [remark]: https://github.com/remarkjs/remark [mdast]: https://github.com/wooorm/mdast [rehype]: https://github.com/rehypejs/rehype ================================================ FILE: packages/remark-grid-tables/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`grid-table 1`] = ` "<h1>Grid table</h1> <h2>Basic example</h2> <table><thead><tr><th colspan=\\"2\\" rowspan=\\"1\\"><p>Table Headings</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>Here</p></th></tr><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>Sub</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>Headings</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>Too</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>cell spans rows</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>column spanning</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>normal</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>cell</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>multi line</p><p>cells too</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>cells can be <em>formatted</em> <strong>paragraphs</strong></p></td></tr></tbody></table> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>B</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>D</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>E</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"3\\"><p>A</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>B</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>E</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"3\\" rowspan=\\"1\\"><p>A</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>B</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>D</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"4\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>D</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>G</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>H</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>I</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>B</p></td><td colspan=\\"1\\" rowspan=\\"4\\"><p>C</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>F</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>B</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>E</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>F</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>A</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>B</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>C</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"3\\"><p>A</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>B</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>E</p></td><td colspan=\\"1\\" rowspan=\\"3\\"><p>F</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>G</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>H</p></td></tr><tr><td colspan=\\"6\\" rowspan=\\"1\\"><p>I</p></td></tr></tbody></table> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></th><th colspan=\\"6\\" rowspan=\\"1\\"><p>B</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"4\\"><p>C</p></td><td colspan=\\"6\\" rowspan=\\"4\\"><table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>H</p></td></tr></tbody></table></td></tr><tr></tr><tr></tr><tr></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>H</p></td><td colspan=\\"16\\" rowspan=\\"1\\"></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>He</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Li</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Be</p></td><td colspan=\\"10\\" rowspan=\\"2\\"></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>B</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>N</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>O</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ne</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Na</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Mg</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Al</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Si</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>P</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>S</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Cl</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ar</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>K</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ca</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Sc</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ti</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>V</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Cr</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Mn</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Fe</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Co</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ni</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Cu</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Zn</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ga</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ge</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>As</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Se</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Br</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Kr</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Rb</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Sr</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Y</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Zr</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Nb</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Mo</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Tc</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ru</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Rh</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Pd</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ag</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Cd</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>In</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Sn</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Sb</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Te</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>I</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Xe</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Cs</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ba</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>LAN</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Hf</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ta</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>W</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Re</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Os</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ir</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Pt</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Au</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Hg</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Tl</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Pb</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Bi</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Po</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>At</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Rn</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Fr</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ra</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ACT</p></td><td colspan=\\"15\\" rowspan=\\"1\\"></td></tr><tr><td colspan=\\"18\\" rowspan=\\"1\\"></td></tr><tr><td colspan=\\"3\\" rowspan=\\"1\\"><p>Lanthanide</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>La</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ce</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Pr</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Nd</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Pm</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Sm</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Eu</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Gd</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Tb</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Dy</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ho</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Er</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Tm</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Yb</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Lu</p></td></tr><tr><td colspan=\\"3\\" rowspan=\\"1\\"><p>Actinide</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ac</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Th</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Pa</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>U</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Np</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Pu</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Am</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Cm</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Bk</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Cf</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Es</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Fm</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Md</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>No</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Lw</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></td></tr></tbody></table> <p>Text at the end</p> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></td></tr></tbody></table> <p>Text at the</p> <h2>specific tests</h2> <p>In this examples, the second row should always be a full-cell</p> <table><tbody><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>A</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>B | C</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></td><td colspan=\\"3\\" rowspan=\\"1\\"></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>B | C</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>D E</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>B</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>B | C</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>A</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>B</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>C</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>B | C</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>D</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>E</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>F</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>G</p></td></tr></tbody></table> <h2>Failing example</h2> <p>+--- A ---+</p> <p>+---------+ +---------+</p> <p>+---------+ | A | | |</p> <p>+---------+ | A | +=========+ | B | +=========+</p> <p>+--- A ---+ | |</p> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"></td></tr></tbody></table> <p>Bug #107</p> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"></th><th colspan=\\"2\\" rowspan=\\"1\\"><p>case1</p></th><th colspan=\\"2\\" rowspan=\\"1\\"><p>case2</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>case3</p></th></tr><tr><th colspan=\\"1\\" rowspan=\\"1\\"></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>case4</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>case5</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>case6</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>case7</p></th><th colspan=\\"1\\" rowspan=\\"1\\"></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>X</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>X</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>X</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>X</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>X</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>X</p></td></tr></tbody></table>" `; exports[`grid-table double 1`] = ` "<h1>Grid table</h1> <h2>Basic example</h2> <table><thead><tr><th colspan=\\"2\\" rowspan=\\"1\\"><p>テーブル表題</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>表題</p></th></tr><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>副題</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>見出し</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>セル</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>行の 結合 列</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>列の結合行</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>標準</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>セル</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>複数 行の</p><p>セル 結合</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p><em>書式つきの</em> <strong>段落</strong> セル</p></td></tr></tbody></table> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>い</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>え</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>お</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"3\\"><p>あ</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>い</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>お</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"3\\" rowspan=\\"1\\"><p>あ</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>い</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>え</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"4\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>き</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>く</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>け</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>い</p></td><td colspan=\\"1\\" rowspan=\\"4\\"><p>う</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>か</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>い</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>か</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>あ</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>い</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>う</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"3\\"><p>あ</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>い</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"2\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"3\\"><p>か</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>き</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>く</p></td></tr><tr><td colspan=\\"6\\" rowspan=\\"1\\"><p>け</p></td></tr></tbody></table> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></th><th colspan=\\"6\\" rowspan=\\"1\\"><p>い</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"4\\"><p>う</p></td><td colspan=\\"6\\" rowspan=\\"4\\"><table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>く</p></td></tr></tbody></table></td></tr><tr></tr><tr></tr><tr></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td></tr></tbody></table> <p>Text at the end</p> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td></tr></tbody></table> <p>Text at the</p> <h2>specific tests</h2> <p>In this examples, the second row should always be a full-cell</p> <table><tbody><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>あ</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>い | う</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td><td colspan=\\"3\\" rowspan=\\"1\\"></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>い | う</p></td></tr><tr><td colspan=\\"2\\" rowspan=\\"1\\"><p>え お</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>い</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>い | う</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>い</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td></tr><tr><td colspan=\\"4\\" rowspan=\\"1\\"><p>い | う</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>き</p></td></tr></tbody></table> <h2>Failing example</h2> <p>+--- あ ---+</p> <p>+---------+ +---------+</p> <p>+---------+ | あ | | |</p> <p>+---------+ | あ | +=========+ | い | +=========+</p> <p>+--- あ ---+ | |</p> <table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"></td></tr></tbody></table> <p>Bug #107</p> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"></th><th colspan=\\"2\\" rowspan=\\"1\\"><p>例1</p></th><th colspan=\\"2\\" rowspan=\\"1\\"><p>例2</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>例3</p></th></tr><tr><th colspan=\\"1\\" rowspan=\\"1\\"></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>例4</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>例5</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>例6</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>例7</p></th><th colspan=\\"1\\" rowspan=\\"1\\"></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>あ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>い</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>う</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>え</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>お</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>か</p></td></tr></tbody></table> <h2>Emoji</h2> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>xy</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>🐶</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>🍣</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>✌</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>👏 🌵</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>🦄</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>👨‍👨‍👧‍👦</p></td></tr></tbody></table> <h2>Emoji and Ambiguous Width</h2> <table><thead><tr><th colspan=\\"1\\" rowspan=\\"1\\"><p>xy</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>🐶</p></th><th colspan=\\"1\\" rowspan=\\"1\\"><p>é</p></th></tr></thead><tbody><tr><td colspan=\\"1\\" rowspan=\\"2\\"><p>✌</p></td><td colspan=\\"2\\" rowspan=\\"1\\"><p>👏 🌵</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>è</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>👨‍👨‍👧‍👦</p></td></tr></tbody></table>" `; exports[`handles Cyrillic script 1`] = `"<table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>А</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Б</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>В</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Г</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Д</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Е</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ё</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ж</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>З</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>И</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Й</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>К</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Л</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>М</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Н</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>О</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>П</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Р</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>С</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Т</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>У</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ф</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Х</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ц</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ч</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ш</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Щ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ъ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ы</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ь</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Э</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ю</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Я</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>а</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>б</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>в</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>г</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>д</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>е</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ё</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ж</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>з</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>и</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>й</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>к</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>л</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>м</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>н</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>о</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>п</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>р</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>с</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>т</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>у</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ф</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>х</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ц</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ч</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ш</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>щ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ъ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ы</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ь</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>э</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ю</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>я</p></td></tr></tbody></table>"`; exports[`handles Cyrillic script 2`] = `"<table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>abc</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>ѥръ</p></td></tr></tbody></table>"`; exports[`handles Cyrillic script 3`] = `"<table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ӂ</p></td></tr></tbody></table>"`; exports[`handles Cyrillic script 4`] = `"<table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>z</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ӽ</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ӽ</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Ў</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>ў</p></td></tr></tbody></table>"`; exports[`indentation in code blocks - negative indent 1`] = ` "<table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Code</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Block</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><pre><code class=\\"language-python\\"> def echo(str): print(str) </code></pre></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Another thing here maybe?</p></td></tr></tbody></table>" `; exports[`indentation in code blocks - simple example 1`] = ` "<table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>Code</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Block</p></td></tr><tr><td colspan=\\"1\\" rowspan=\\"1\\"><pre><code class=\\"language-python\\">def echo(str): print(str) </code></pre></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>Another thing here maybe?</p></td></tr></tbody></table>" `; exports[`regression: grid table in fenced code block 1`] = ` "<pre><code>+---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ </code></pre>" `; exports[`regression: grid table in non-fenced code block 1`] = ` "<pre><code>+---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ </code></pre>" `; exports[`regression: should not crash with two spaces on the next line 1`] = `"<table><tbody><tr><td colspan=\\"1\\" rowspan=\\"1\\"><p>:)</p></td><td colspan=\\"1\\" rowspan=\\"1\\"><p>:)</p></td></tr></tbody></table>"`; ================================================ FILE: packages/remark-grid-tables/__tests__/grid-tables.double.md ================================================ # Grid table ## Basic example +------+--------+------+ | テーブル表題 | 表題 | +------+--------+------+ | 副題 | 見出し | セル | +======+========+======+ | 行の | 列の結合行 | + 結合 +--------+------+ | 列 | 標準 | セル | +------+--------+------+ | 複数 | *書式つきの* | | 行の | **段落** | | | セル | | セル | | | 結合 | | +------+---------------+ +----+----+----+ | あ | い | う | +====+====+====+ | え | お | | +----+----+ | | か | き | +----+----+----+ +----+---------+ | あ | い | | +----+----+ | | う | え | | +----+----+ | | お | +----+---------+ +--------------+ | あ | +----+----+----+ | い | う | え | | +----+ | | | お | | +----+----+----+ +----+----+----+ | う | え | お | | | +----+ | | | か | | +----+----+ | | き | く | | | +----+ | | | け | +----+----+----+ +----+----+----+ | あ | い | う | +----+ | | | え | | | +----+----+ | | お | か | | +----+ | | | き | | | +----+----+----+ +----+----+----+----+ | あ | い | う | え | +----+----+----+----+ | お | か | +---------+---------+ | き | +-------------------+ +-------------------+ | あ | +---------+---------+ | い | う | +----+----+----+----+ | え | お | か | き | +----+----+----+----+ +----+----+----+----+----+----+ | あ | い | う | え | お | か | | | +----+----+ | | | | | き | | | | +----+---------+----+ | | | く | | +----+-------------------+----+ | け | +-----------------------------+ +----+-----------------------+ | あ | い | +====+=======================+ | う | | | | +----+----+----+----+ | | | | え | お | か | き | | | | +----+----+----+----+ | | | | く | | | | +-------------------+ | | | | +----+-----------------------+ +----------+ | あ | +----------+ Text at the end +----------+ | あ | +----------+ Text at the ## specific tests In this examples, the second row should always be a full-cell +-------------------+ | あ | +-------------------+ | い | う | +----+----+----+----+ | え | お | か | き | +----+----+----+----+ +----+--------------+ | あ | | +----+--------------+ | い | う | +---------+----+----+ | え お | か | き | +---------+----+----+ +----+----+----+----+ | あ | い | う | え | +----+----+----+----+ | い | う | | | +----+----+----+----+ | え | お | か | き | +----+----+----+----+ +----+----+----+----+ | あ | い | う | え | +----+----+----+----+ | い | う | +----+----+----+----+ | え | お | か | き | +----+----+----+----+ ## Failing example +--- あ ---+ +---------+ +---------+ +---------+ | あ | | | +---------+ | あ | +=========+ | い | +=========+ +--- あ ---+ | | +----------+ | | +----------+ Bug #107 +----+-----+-----+-----+-----+-----+ | | 例1 | 例2 | 例3 | +----+-----+-----+-----+-----+-----+ | | 例4 | 例5 | 例6 | 例7 | | +====+=====+=====+=====+=====+=====+ | あ | い | う | え | お | か | +----+-----+-----+-----+-----+-----+ ## Emoji +----+----+----+ | xy | 🐶 | 🍣 | +====+====+====+ | ✌ | 👏 🌵 | | +----+----+ | | 🦄 | 👨‍👨‍👧‍👦 | +----+----+----+ ## Emoji and Ambiguous Width +----+----+----+ | xy | 🐶 | é | +====+====+====+ | ✌ | 👏 🌵 | | +----+----+ | | è | 👨‍👨‍👧‍👦 | +----+----+----+ ================================================ FILE: packages/remark-grid-tables/__tests__/grid-tables.md ================================================ # Grid table ## Basic example +-------+----------+------+ | Table Headings | Here | +-------+----------+------+ | Sub | Headings | Too | +=======+==========+======+ | cell | column spanning | + spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ +---+-------+ | A | B | | +---+---+ | | C | D | | +---+---+ | | E | +---+-------+ +-----------+ | A | +---+---+---+ | B | C | D | | +---+ | | | E | | +---+---+---+ +---+---+---+ | C | D | E | | | +---+ | | | F | | +---+---+ | | G | H | | | +---+ | | | I | +---+---+---+ +---+---+---+ | A | B | C | +---+ | | | D | | | +---+---+ | | E | F | | +---+ | | | G | | | +---+---+---+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | E | F | +-------+-------+ | G | +---------------+ +---------------+ | A | +-------+-------+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+---+---+---+---+---+ | A | B | C | D | E | F | | | +---+---+ | | | | | G | | | | +---+-------+---+ | | | H | | +---+---------------+---+ | I | +-----------------------+ +---+-------------------+ | A | B | +===+===================+ | C | | | | +---+---+---+---+ | | | | D | E | F | G | | | | +---+---+---+---+ | | | | H | | | | +---------------+ | | | | +---+-------------------+ +---+---------------------------------------------------------------+---+ | H | |He | +---+---+---------------------------------------+---+---+---+---+---+---+ |Li |Be | | B | C | N | O | F |Ne | +---+---+ +---+---+---+---+---+---+ |Na |Mg | |Al |Si | P | S |Cl |Ar | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | K |Ca |Sc |Ti | V |Cr |Mn |Fe |Co |Ni |Cu |Zn |Ga |Ge |As |Se |Br |Kr | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Rb |Sr | Y |Zr |Nb |Mo |Tc |Ru |Rh |Pd |Ag |Cd |In |Sn |Sb |Te | I |Xe | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Cs |Ba |LAN|Hf |Ta | W |Re |Os |Ir |Pt |Au |Hg |Tl |Pb |Bi |Po |At |Rn | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Fr |Ra |ACT| | +---+---+---+-----------------------------------------------------------+ | | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Lanthanide |La |Ce |Pr |Nd |Pm |Sm |Eu |Gd |Tb |Dy |Ho |Er |Tm |Yb |Lu | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Actinide |Ac |Th |Pa | U |Np |Pu |Am |Cm |Bk |Cf |Es |Fm |Md |No |Lw | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---------+ | A | +---------+ Text at the end +---------+ | A | +---------+ Text at the ## specific tests In this examples, the second row should always be a full-cell +---------------+ | A | +---------------+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+-----------+ | A | | +---+-----------+ | B | C | +-------+---+---+ | D E | F | G | +-------+---+---+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | B | C | | | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ ## Failing example +--- A ---+ +---------+ +---------+ +---------+ | A | | | +---------+ | A | +=========+ | B | +=========+ +--- A ---+ | | +---------+ | | +---------+ Bug #107 +-----+-------+-------+-------+-------+-------+ | | case1 | case2 | case3 | +-----+-------+-------+-------+-------+-------+ | | case4 | case5 | case6 | case7 | | +=====+=======+=======+=======+=======+=======+ | X | X | X | X | X | X | +-----+-------+-------+-------+-------+-------+ ================================================ FILE: packages/remark-grid-tables/__tests__/index.js ================================================ /* eslint-disable max-len */ import {readFileSync as file} from 'fs' import {join} from 'path' import unified from 'unified' import dedent from 'dedent' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import stringifyRemark from 'remark-stringify' import plugin from '../src/' const render = text => unified() .use(reParse) .use(plugin) .use(remark2rehype) .use(stringify) .processSync(text) const compiler = text => unified() .use(reParse) .use(stringifyRemark) .use(plugin) .processSync(text) test('grid-table', () => { const {contents} = render(file(join(__dirname, 'grid-tables.md'))) expect(contents).toMatchSnapshot() }) test('grid-table double', () => { const {contents} = render(file(join(__dirname, 'grid-tables.double.md'))) expect(contents).toMatchSnapshot() }) test('regression: grid table in fenced code block', () => { const {contents} = render(` \`\`\` +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ \`\`\` `) expect(contents).toMatchSnapshot() }) test('regression: grid table in non-fenced code block', () => { const {contents} = render(` +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ `) expect(contents).toMatchSnapshot() }) test('regression: should not crash with two spaces on the next line', () => { const {contents} = render(dedent` +----+----+ + :) | :) + +----+----+ ·· `.replace(/·/g, ' ')) expect(contents).toMatchSnapshot() }) test('regression: should be parsed with two spaces on last line', () => { const {contents: base} = render(dedent` +----+----+ + :) | :) + +----+----+ hello `) const {contents} = render(dedent` +----+----+ + :) | :) + +----+----+·· hello `.replace(/·/g, ' ')) expect(contents).toBe(base) }) test('regression: should not crash with leading space', () => { const {contents: base} = render(dedent` a +---+ | b | +---+ hello `) const {contents} = render(dedent` a· +---+ | b | +---+ hello `.replace(/·/g, ' ')) expect(contents).toBe(base) }) test('regression: should not crash when followed by "sth<space>"', () => { const {contents: base} = render(dedent` +---+ | A | +===+ | B | +---+ <- bug `) const {contents} = render(dedent` +---+ | A | +===+ | B | +---+ <-· bug `.replace(/·/g, ' ')) expect(contents).toBe(base) }) test('regression: should ignore spaces at the right of the table', () => { const {contents: base} = render(dedent` +---+ | A | +===+ | B | +---+ `) const {contents} = render(dedent` +---+ | A | +===+··· | B |· +---+ `.replace(/·/g, ' ')) expect(contents).toBe(base) }) test('regression: handles east asian ambiguous width', () => { const {contents: base} = render(dedent` +---+ | ï | +---+ `) const {contents: test1} = render(dedent` +---+ | é | +---+ `) const {contents: test2} = render(dedent` +---+ | Ê | +---+ `) const {contents: test3} = render(dedent` +---+ | fl | +---+ `) const {contents: test4} = render(dedent` +---+ | ¯ | +---+ `) expect(test1).toBe(base.replace('ï', 'é')) expect(test2).toBe(base.replace('ï', 'Ê')) expect(test3).toBe(base.replace('ï', 'fl')) expect(test4).toBe(base.replace('ï', '¯')) }) test('distinguish between tables of the same width', () => { const {contents: test1} = render(dedent` +---+---+---+ | 1 | 2 | 3 | +---+---+---+ | | a | +---+---+---+ | b | | +-------+---+ `) const {contents: test2} = render(dedent` +---+---+---+ | 1 | 2 | 3 | +---+---+---+ | | a | +---+-------+ | b | | +---+-------+ `) expect(test1).not.toEqual(test2) }) test('indentation in code blocks - simple example', () => { const {contents} = render(dedent` +----------------+---------+ | Code | Block | +----------------+---------+ | \`\`\`python | Another | | def echo(str): | thing | | print(str) | here | | \`\`\` | maybe? | +----------------+---------+ `) expect(contents).toMatchSnapshot() }) test('indentation in code blocks - negative indent', () => { const {contents} = render(dedent` +-----------------+---------+ | Code | Block | +-----------------+---------+ | \`\`\`python | Another | | def echo(str): | thing | | print(str) | here | | \`\`\` | maybe? | +-----------------+---------+ `) expect(contents).toMatchSnapshot() }) test('handles Cyrillic script', () => { const {contents: test1} = render(dedent` +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | z | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | А | Б | В | Г | Д | Е | Ё | Ж | З | И | Й | К | Л | М | Н | О | П | Р | С | Т | У | Ф | Х | Ц | Ч | Ш | Щ | Ъ | Ы | Ь | Э | Ю | Я | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | а | б | в | г | д | е | ё | ж | з | и | й | к | л | м | н | о | п | р | с | т | у | ф | х | ц | ч | ш | щ | ъ | ы | ь | э | ю | я | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ `) const {contents: test2} = render(dedent` +-----+ | abc | +-----+ | ѥръ | +-----+ `) const {contents: test3} = render(dedent` +---+ | Ӂ | +---+ `) const {contents: test4} = render(dedent` +---+---+ | z | z | +---+---+ | Ӽ | ӽ | +---+---+ | Ў | ў | +---+---+ `) expect(test1).toMatchSnapshot() expect(test2).toMatchSnapshot() expect(test3).toMatchSnapshot() expect(test4).toMatchSnapshot() }) test('stringify', () => { const fileExample = file(join(__dirname, 'grid-tables.md')) const {contents} = render(fileExample) const contents2 = render(compiler(fileExample)).contents expect(contents).toBe(contents2) }) ================================================ FILE: packages/remark-grid-tables/dist/index.js ================================================ "use strict"; const trimEnd = require('lodash.trimend'); const visit = require('unist-util-visit'); const stringWidth = require('string-width'); const splitter = new (require('grapheme-splitter'))(); const mainLineRegex = /((\+)|(\|)).+((\|)|(\+))/; const totalMainLineRegex = /^((\+)|(\|)).+((\|)|(\+))$/; const headerLineRegex = /^\+=[=+]+=\+$/; const partLineRegex = /\+-[-+]+-\+/; const separationLineRegex = /^\+-[-+]+-\+$/; module.exports = plugin; // A small class helping table generation class Table { constructor(linesInfos) { this._parts = []; this._linesInfos = linesInfos; this.addPart(); } lastPart() { return this._parts[this._parts.length - 1]; } addPart() { this._parts.push(new TablePart(this._linesInfos)); } } class TablePart { constructor(linesInfos) { this._rows = []; this._linesInfos = linesInfos; this.addRow(); } addRow() { this._rows.push(new TableRow(this._linesInfos)); } removeLastRow() { this._rows.pop(); } lastRow() { return this._rows[this._rows.length - 1]; } updateWithMainLine(line, isEndLine) { // Update last row according to a line. const mergeChars = isEndLine ? '+|' : '|'; const newCells = [this.lastRow()._cells[0]]; for (let c = 1; c < this.lastRow()._cells.length; c++) { const cell = this.lastRow()._cells[c]; // Only cells with rowSpan equals can be merged // Test if the char does not compose a character // or the char before the cell is a separation character if (cell._rowSpan === newCells[newCells.length - 1]._rowSpan && (!isCodePointPosition(line, cell._startPosition - 1) || !mergeChars.includes(substringLine(line, cell._startPosition - 1)))) { newCells[newCells.length - 1].mergeWith(cell); } else { newCells.push(cell); } } this.lastRow()._cells = newCells; } updateWithPartLine(line) { // Get cells not finished const remainingCells = []; for (let c = 0; c < this.lastRow()._cells.length; c++) { const cell = this.lastRow()._cells[c]; const partLine = substringLine(line, cell._startPosition - 1, cell._endPosition + 1); if (!isSeparationLine(partLine)) { cell._lines.push(substringLine(line, cell._startPosition, cell._endPosition)); cell._rowSpan += 1; remainingCells.push(cell); } } // Generate new row this.addRow(); const newCells = []; for (let c = 0; c < remainingCells.length; c++) { const remainingCell = remainingCells[c]; for (let cc = 0; cc < this.lastRow()._cells.length; cc++) { const cell = this.lastRow()._cells[cc]; if (cell._endPosition < remainingCell._startPosition && !newCells.includes(cell)) { newCells.push(cell); } } newCells.push(remainingCell); for (let cc = 0; cc < this.lastRow()._cells.length; cc++) { const cell = this.lastRow()._cells[cc]; if (cell._startPosition > remainingCell._endPosition && !newCells.includes(cell)) { newCells.push(cell); } } } // Remove duplicates for (let nc = 0; nc < newCells.length; nc++) { let newCell = newCells[nc]; for (let ncc = 0; ncc < newCells.length; ncc++) { if (nc !== ncc) { const other = newCells[ncc]; if (other._startPosition >= newCell._startPosition && other._endPosition <= newCell._endPosition) { if (other._lines.length === 0) { newCells.splice(ncc, 1); ncc -= 1; if (nc > ncc) { nc -= 1; newCell = newCells[nc]; } } } } } } this.lastRow()._cells = newCells; } } class TableRow { constructor(linesInfos) { this._linesInfos = linesInfos; this._cells = []; for (let i = 0; i < linesInfos.length - 1; i++) { this._cells.push(new TableCell(linesInfos[i] + 1, linesInfos[i + 1])); } } updateContent(line) { for (let c = 0; c < this._cells.length; c++) { const cell = this._cells[c]; cell._lines.push(substringLine(line, cell._startPosition, cell._endPosition)); } } } class TableCell { constructor(startPosition, endPosition) { this._startPosition = startPosition; this._endPosition = endPosition; this._colSpan = 1; this._rowSpan = 1; this._lines = []; } mergeWith(other) { this._endPosition = other._endPosition; this._colSpan += other._colSpan; const newLines = []; for (let l = 0; l < this._lines.length; l++) { newLines.push(`${this._lines[l]}|${other._lines[l]}`); } this._lines = newLines; } } function merge(beforeTable, gridTable, afterTable) { // get the eaten text let total = beforeTable.join('\n'); if (total.length) { total += '\n'; } total += gridTable.join('\n'); if (afterTable.join('\n').length) { total += '\n'; } total += afterTable.join('\n'); return total; } function isSeparationLine(line) { return separationLineRegex.exec(line); } function isHeaderLine(line) { return headerLineRegex.exec(line); } function isPartLine(line) { return partLineRegex.exec(line); } function findAll(str, characters) { let current = 0; const pos = []; const content = splitter.splitGraphemes(str); for (let i = 0; i < content.length; i++) { const char = content[i]; if (characters.includes(char)) { pos.push(current); } current += stringWidth(char); } return pos; } function computePlainLineColumnsStartingPositions(line) { return findAll(line, '+|'); } function mergeColumnsStartingPositions(allPos) { // Get all starting positions, allPos is an array of array of positions const positions = []; allPos.forEach(posRow => posRow.forEach(pos => { if (!positions.includes(pos)) { positions.push(pos); } })); return positions.sort((a, b) => a - b); } function computeColumnStartingPositions(lines) { const linesInfo = []; lines.forEach(line => { if (isHeaderLine(line) || isPartLine(line)) { linesInfo.push(computePlainLineColumnsStartingPositions(line)); } }); return mergeColumnsStartingPositions(linesInfo); } function isCodePointPosition(line, pos) { const content = splitter.splitGraphemes(line); let offset = 0; for (let i = 0; i < content.length; i++) { // The pos points character position if (pos === offset) { return true; } // The pos points non-character position if (pos < offset) { return false; } offset += stringWidth(content[i]); } // Reaching end means character position return true; } function substringLine(line, start, end) { end = end || start + 1; const content = splitter.splitGraphemes(line); let offset = 0; let str = ''; for (let i = 0; i < content.length; i++) { if (offset >= start) { str += content[i]; } offset += stringWidth(content[i]); if (offset >= end) { break; } } return str; } function extractTable(value, eat, tokenizer) { // Extract lines before the grid table const markdownLines = value.split('\n'); let i = 0; const before = []; for (; i < markdownLines.length; i++) { const line = markdownLines[i]; if (isSeparationLine(line)) break; if (stringWidth(line) === 0) break; before.push(line); } const possibleGridTable = markdownLines.map(line => trimEnd(line)); // Extract table if (!possibleGridTable[i + 1]) return [null, null, null, null]; const gridTable = []; const realGridTable = []; let hasHeader = false; for (; i < possibleGridTable.length; i++) { const line = possibleGridTable[i]; const realLine = markdownLines[i]; // line is in table if (totalMainLineRegex.exec(line)) { const isHeaderLine = headerLineRegex.exec(line); if (isHeaderLine && !hasHeader) hasHeader = true; // A table can't have 2 headers else if (isHeaderLine && hasHeader) { break; } realGridTable.push(realLine); gridTable.push(line); } else { // this line is not in the grid table. break; } } // if the last line is not a plain line if (!separationLineRegex.exec(gridTable[gridTable.length - 1])) { // Remove lines not in the table for (let j = gridTable.length - 1; j >= 0; j--) { const isSeparation = separationLineRegex.exec(gridTable[j]); if (isSeparation) break; gridTable.pop(); i -= 1; } } // Extract lines after table const after = []; for (; i < possibleGridTable.length; i++) { const line = possibleGridTable[i]; if (stringWidth(line) === 0) break; after.push(markdownLines[i]); } return [before, gridTable, realGridTable, after, hasHeader]; } function extractTableContent(lines, linesInfos, hasHeader) { const table = new Table(linesInfos); for (let l = 0; l < lines.length; l++) { const line = lines[l]; // Get if the line separate the head of the table from the body const matchHeader = hasHeader & isHeaderLine(line) !== null; // Get if the line close some cells const isEndLine = matchHeader | isPartLine(line) !== null; if (isEndLine) { // It is a header, a plain line or a line with plain line part. // First, update the last row table.lastPart().updateWithMainLine(line, isEndLine); // Create the new row if (l !== 0) { if (matchHeader) { table.addPart(); } else if (isSeparationLine(line)) { table.lastPart().addRow(); } else { table.lastPart().updateWithPartLine(line); } } // update the last row table.lastPart().updateWithMainLine(line, isEndLine); } else { // it's a plain line table.lastPart().updateWithMainLine(line, isEndLine); table.lastPart().lastRow().updateContent(line); } } // Because the last line is a separation, the last row is always empty table.lastPart().removeLastRow(); return table; } function processCellLines(lines) { const trimmedLines = []; let inCodeBlock = false; let leadingWhitespace = 0; let leadingWhitespaceRegex = null; for (let i = 0; i < lines.length; i++) { let line = lines[i]; // Trim the end of the line line = line.trimEnd(); // Check if we're entering or exiting a code block if (line.trim().startsWith('```')) { inCodeBlock = !inCodeBlock; // If we're entering a code block, remember the amount of leading whitespace if (inCodeBlock) { // Set how much whitespace to remoev from lines // inside the code-block leadingWhitespace = line.match(/^ */)[0].length; leadingWhitespaceRegex = new RegExp(`^ {0,${leadingWhitespace}}`); } // Remove leading whitespace from the opening/closing code-block // statement as well line = line.replace(leadingWhitespaceRegex, ''); } else if (inCodeBlock) { // If we're *already* in a code block, trim the start of the line // by the amount of leading whitespace line = line.replace(leadingWhitespaceRegex, ''); } else { // We're not in a code block, trim the start of the line line = line.trimStart(); } // Replace the line in the array trimmedLines.push(line); } return trimmedLines; } function generateTable(tableContent, now, tokenizer) { // Generate the gridTable node to insert in the AST const tableElt = { type: 'gridTable', children: [], data: { hName: 'table' } }; const hasHeader = tableContent._parts.length > 1; for (let p = 0; p < tableContent._parts.length; p++) { const part = tableContent._parts[p]; const partElt = { type: 'tableHeader', children: [], data: { hName: hasHeader && p === 0 ? 'thead' : 'tbody' } }; for (let r = 0; r < part._rows.length; r++) { const row = part._rows[r]; const rowElt = { type: 'tableRow', children: [], data: { hName: 'tr' } }; for (let c = 0; c < row._cells.length; c++) { const cell = row._cells[c]; const trimmedLines = processCellLines(cell._lines); const tokenizedContent = tokenizer.tokenizeBlock(trimmedLines.join('\n'), now); const cellElt = { type: 'tableCell', children: tokenizedContent, data: { hName: hasHeader && p === 0 ? 'th' : 'td', hProperties: { colSpan: cell._colSpan, rowSpan: cell._rowSpan } } }; const endLine = r + cell._rowSpan; if (cell._rowSpan > 1 && endLine - 1 < part._rows.length) { for (let rs = 1; rs < cell._rowSpan; rs++) { for (let cc = 0; cc < part._rows[r + rs]._cells.length; cc++) { const other = part._rows[r + rs]._cells[cc]; if (cell._startPosition === other._startPosition && cell._endPosition === other._endPosition && cell._colSpan === other._colSpan && cell._rowSpan === other._rowSpan && cell._lines === other._lines) { part._rows[r + rs]._cells.splice(cc, 1); } } } } rowElt.children.push(cellElt); } partElt.children.push(rowElt); } tableElt.children.push(partElt); } return tableElt; } function gridTableTokenizer(eat, value, silent) { let index = 0; const length = value.length; let character; while (index < length) { character = value.charAt(index); if (character !== ' ' && character !== '\t') { break; } index++; } if (value.charAt(index) !== '+') { return; } if (value.charAt(index + 1) !== '-') { return; } const keep = mainLineRegex.test(value); if (!keep) return; const [before, gridTable, realGridTable, after, hasHeader] = extractTable(value, eat, this); if (!gridTable || gridTable.length < 3) return; const now = eat.now(); const linesInfos = computeColumnStartingPositions(gridTable); const tableContent = extractTableContent(gridTable, linesInfos, hasHeader); const tableElt = generateTable(tableContent, now, this); const merged = merge(before, realGridTable, after); // Because we can't add multiples blocs in one eat, I use a temp block const wrapperBlock = { type: 'element', tagName: 'WrapperBlock', children: [] }; if (before.length) { const tokensBefore = this.tokenizeBlock(before.join('\n'), now)[0]; wrapperBlock.children.push(tokensBefore); } wrapperBlock.children.push(tableElt); if (after.length) { const tokensAfter = this.tokenizeBlock(after.join('\n'), now); if (tokensAfter.length) { wrapperBlock.children.push(tokensAfter[0]); } } return eat(merged)(wrapperBlock); } function deleteWrapperBlock() { function one(node, index, parent) { if (!node.children) return; const newChildren = []; let replace = false; for (let c = 0; c < node.children.length; c++) { const child = node.children[c]; if (child.tagName === 'WrapperBlock' && child.type === 'element') { replace = true; for (let cc = 0; cc < child.children.length; cc++) { newChildren.push(child.children[cc]); } } else { newChildren.push(child); } } if (replace) { node.children = newChildren; } } return one; } function transformer(tree) { // Remove the temporary block in which we previously wrapped the table parts visit(tree, deleteWrapperBlock()); } function createGrid(nbRows, nbCols) { const grid = []; for (let i = 0; i < nbRows; i++) { grid.push([]); for (let j = 0; j < nbCols; j++) { grid[i].push({ height: -1, width: -1, hasBottom: true, hasRigth: true }); } } return grid; } function setWidth(grid, i, j, cols) { /* To do it, we put enougth space to write the text. * For multi-cell, we divid it among the cells. */ let tmpWidth = Math.max(...Array.from(grid[i][j].value).map(x => x.length)) + 2; grid[i].forEach((_, c) => { if (c < cols) { // To divid const localWidth = Math.ceil(tmpWidth / (cols - c)); // cols - c will be 1 for the last cell tmpWidth -= localWidth; grid[i][j + c].width = localWidth; } }); } function setHeight(grid, i, j, values) { // To do it, we count the line. Extra length to cell with a pipe // in the value of the last line, to not be confuse with a border. grid[i][j].height = values.length; // Extra line if (values[values.length - 1].indexOf('|') > 0) { grid[i][j].height += 1; } } function extractAST(gridNode, grid) { let i = 0; /* Fill the grid with value, height and width from the ast */ gridNode.children.forEach(th => { th.children.forEach(row => { row.children.forEach((cell, j) => { let X = 0; // x taking colSpan and rowSpan into account while (grid[i][j + X].evaluated) X++; grid[i][j + X].value = this.all(cell).join('\n\n').split('\n'); setHeight(grid, i, j + X, grid[i][j + X].value); setWidth(grid, i, j + X, cell.data.hProperties.colSpan); // If it's empty, we fill it up with a useless space // Otherwise, it will not be parsed. if (!grid[0][0].value.join('\n')) { grid[0][0].value = [' ']; grid[0][0].width = 3; } // Define the border of each cell for (let x = 0; x < cell.data.hProperties.rowSpan; x++) { for (let y = 0; y < cell.data.hProperties.colSpan; y++) { // b attribute is for bottom grid[i + x][j + X + y].hasBottom = x + 1 === cell.data.hProperties.rowSpan; // r attribute is for right grid[i + x][j + X + y].hasRigth = y + 1 === cell.data.hProperties.colSpan; // set v if a cell has ever been define grid[i + x][j + X + y].evaluated = ' '; } } }); i++; }); }); // If they is 2 differents tableHeader, so the first one is a header and // should be underlined if (gridNode.children.length > 1) { grid[gridNode.children[0].children.length - 1][0].isHeader = true; } } function setSize(grid) { // The idea is the max win // Set the height of each column grid.forEach(row => { // Find the max const maxHeight = Math.max(...row.map(cell => cell.height)); // Set it to each cell row.forEach(cell => { cell.height = maxHeight; }); }); // Set the width of each row grid[0].forEach((_, j) => { // Find the max const maxWidth = Math.max(...grid.map(row => row[j].width)); // Set it to each cell grid.forEach(row => { row[j].width = maxWidth; }); }); } function generateBorders(grid, nbRows, nbCols, gridString) { /** **** Create the borders *******/ // Create the first line /* * We have to create the first line manually because * we process the borders from the attributes bottom * and right of each cell. For the first line, their * is no bottom nor right cell. * * We only need the right attribute of the first row's * cells */ let first = '+'; grid[0].forEach((cell, i) => { first += '-'.repeat(cell.width); first += cell.hasRigth || i === nbCols - 1 ? '+' : '-'; }); gridString.push(first); grid.forEach((row, i) => { let line = ''; // Cells lines // The inner of the cell line = '|'; row.forEach(cell => { cell.y = gridString.length; cell.x = line.length + 1; line += ' '.repeat(cell.width); line += cell.hasRigth ? '|' : ' '; }); // Add it until the text can fit for (let t = 0; t < row[0].height; t++) { gridString.push(line); } // "End" line // It's the last line of the cell. Actually the border. line = row[0].hasBottom ? '+' : '|'; row.forEach((cell, j) => { let char = ' '; if (cell.hasBottom) { if (row[0].isHeader) { char = '='; } else { char = '-'; } } line += char.repeat(cell.width); if (cell.hasBottom || j + 1 < nbCols && grid[i][j + 1].hasBottom) { if (cell.hasRigth || i + 1 < nbRows && grid[i + 1][j].hasRigth) { line += '+'; } else { line += row[0].isHeader ? '=' : '-'; } } else if (cell.hasRigth || i + 1 < nbRows && grid[i + 1][j].hasRigth) { line += '|'; } else { line += ' '; } }); gridString.push(line); }); } function writeText(grid, gridString) { grid.forEach(row => { row.forEach(cell => { if (cell.value && cell.value[0]) { for (let tmpCount = 0; tmpCount < cell.value.length; tmpCount++) { const tmpLine = cell.y + tmpCount; const line = cell.value[tmpCount]; const lineEdit = gridString[tmpLine]; gridString[tmpLine] = lineEdit.substr(0, cell.x); gridString[tmpLine] += line; gridString[tmpLine] += lineEdit.substr(cell.x + line.length); } } }); }); } function stringifyGridTables(gridNode) { const gridString = []; const nbRows = gridNode.children.map(th => th.children.length).reduce((a, b) => a + b); const nbCols = gridNode.children[0].children[0].children.map(c => c.data.hProperties.colSpan).reduce((a, b) => a + b); const grid = createGrid(nbRows, nbCols); /* First, we extract the information * then, we set the size(2) of the border * and create it(3). * Finaly we fill it up. */ extractAST.bind(this)(gridNode, grid); setSize(grid); generateBorders(grid, nbRows, nbCols, gridString); writeText(grid, gridString); return gridString.join('\n'); } function plugin() { const Parser = this.Parser; // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers; const blockMethods = Parser.prototype.blockMethods; blockTokenizers.gridTable = gridTableTokenizer; blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'gridTable'); const Compiler = this.Compiler; // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; visitors.gridTable = stringifyGridTables; } return transformer; } ================================================ FILE: packages/remark-grid-tables/package.json ================================================ { "name": "remark-grid-tables", "version": "2.2.2", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-grid-tables", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "grapheme-splitter": "^1.0.4", "lodash.trimend": "^4.5.1", "string-width": "^4.2.0", "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/remark-grid-tables/src/index.js ================================================ const trimEnd = require('lodash.trimend') const visit = require('unist-util-visit') const stringWidth = require('string-width') const splitter = new (require('grapheme-splitter'))() const mainLineRegex = /((\+)|(\|)).+((\|)|(\+))/ const totalMainLineRegex = /^((\+)|(\|)).+((\|)|(\+))$/ const headerLineRegex = /^\+=[=+]+=\+$/ const partLineRegex = /\+-[-+]+-\+/ const separationLineRegex = /^\+-[-+]+-\+$/ module.exports = plugin // A small class helping table generation class Table { constructor (linesInfos) { this._parts = [] this._linesInfos = linesInfos this.addPart() } lastPart () { return this._parts[this._parts.length - 1] } addPart () { this._parts.push(new TablePart(this._linesInfos)) } } class TablePart { constructor (linesInfos) { this._rows = [] this._linesInfos = linesInfos this.addRow() } addRow () { this._rows.push(new TableRow(this._linesInfos)) } removeLastRow () { this._rows.pop() } lastRow () { return this._rows[this._rows.length - 1] } updateWithMainLine (line, isEndLine) { // Update last row according to a line. const mergeChars = isEndLine ? '+|' : '|' const newCells = [this.lastRow()._cells[0]] for (let c = 1; c < this.lastRow()._cells.length; c++) { const cell = this.lastRow()._cells[c] // Only cells with rowSpan equals can be merged // Test if the char does not compose a character // or the char before the cell is a separation character if (cell._rowSpan === newCells[newCells.length - 1]._rowSpan && ( !isCodePointPosition(line, cell._startPosition - 1) || !mergeChars.includes(substringLine(line, cell._startPosition - 1)) )) { newCells[newCells.length - 1].mergeWith(cell) } else { newCells.push(cell) } } this.lastRow()._cells = newCells } updateWithPartLine (line) { // Get cells not finished const remainingCells = [] for (let c = 0; c < this.lastRow()._cells.length; c++) { const cell = this.lastRow()._cells[c] const partLine = substringLine(line, cell._startPosition - 1, cell._endPosition + 1) if (!isSeparationLine(partLine)) { cell._lines.push(substringLine(line, cell._startPosition, cell._endPosition)) cell._rowSpan += 1 remainingCells.push(cell) } } // Generate new row this.addRow() const newCells = [] for (let c = 0; c < remainingCells.length; c++) { const remainingCell = remainingCells[c] for (let cc = 0; cc < this.lastRow()._cells.length; cc++) { const cell = this.lastRow()._cells[cc] if (cell._endPosition < remainingCell._startPosition && !newCells.includes(cell)) { newCells.push(cell) } } newCells.push(remainingCell) for (let cc = 0; cc < this.lastRow()._cells.length; cc++) { const cell = this.lastRow()._cells[cc] if (cell._startPosition > remainingCell._endPosition && !newCells.includes(cell)) { newCells.push(cell) } } } // Remove duplicates for (let nc = 0; nc < newCells.length; nc++) { let newCell = newCells[nc] for (let ncc = 0; ncc < newCells.length; ncc++) { if (nc !== ncc) { const other = newCells[ncc] if (other._startPosition >= newCell._startPosition && other._endPosition <= newCell._endPosition) { if (other._lines.length === 0) { newCells.splice(ncc, 1) ncc -= 1 if (nc > ncc) { nc -= 1 newCell = newCells[nc] } } } } } } this.lastRow()._cells = newCells } } class TableRow { constructor (linesInfos) { this._linesInfos = linesInfos this._cells = [] for (let i = 0; i < linesInfos.length - 1; i++) { this._cells.push(new TableCell(linesInfos[i] + 1, linesInfos[i + 1])) } } updateContent (line) { for (let c = 0; c < this._cells.length; c++) { const cell = this._cells[c] cell._lines.push(substringLine(line, cell._startPosition, cell._endPosition)) } } } class TableCell { constructor (startPosition, endPosition) { this._startPosition = startPosition this._endPosition = endPosition this._colSpan = 1 this._rowSpan = 1 this._lines = [] } mergeWith (other) { this._endPosition = other._endPosition this._colSpan += other._colSpan const newLines = [] for (let l = 0; l < this._lines.length; l++) { newLines.push(`${this._lines[l]}|${other._lines[l]}`) } this._lines = newLines } } function merge (beforeTable, gridTable, afterTable) { // get the eaten text let total = beforeTable.join('\n') if (total.length) { total += '\n' } total += gridTable.join('\n') if (afterTable.join('\n').length) { total += '\n' } total += afterTable.join('\n') return total } function isSeparationLine (line) { return separationLineRegex.exec(line) } function isHeaderLine (line) { return headerLineRegex.exec(line) } function isPartLine (line) { return partLineRegex.exec(line) } function findAll (str, characters) { let current = 0 const pos = [] const content = splitter.splitGraphemes(str) for (let i = 0; i < content.length; i++) { const char = content[i] if (characters.includes(char)) { pos.push(current) } current += stringWidth(char) } return pos } function computePlainLineColumnsStartingPositions (line) { return findAll(line, '+|') } function mergeColumnsStartingPositions (allPos) { // Get all starting positions, allPos is an array of array of positions const positions = [] allPos.forEach((posRow) => posRow.forEach((pos) => { if (!positions.includes(pos)) { positions.push(pos) } })) return positions.sort((a, b) => a - b) } function computeColumnStartingPositions (lines) { const linesInfo = [] lines.forEach((line) => { if (isHeaderLine(line) || isPartLine(line)) { linesInfo.push(computePlainLineColumnsStartingPositions(line)) } }) return mergeColumnsStartingPositions(linesInfo) } function isCodePointPosition (line, pos) { const content = splitter.splitGraphemes(line) let offset = 0 for (let i = 0; i < content.length; i++) { // The pos points character position if (pos === offset) { return true } // The pos points non-character position if (pos < offset) { return false } offset += stringWidth(content[i]) } // Reaching end means character position return true } function substringLine (line, start, end) { end = end || start + 1 const content = splitter.splitGraphemes(line) let offset = 0 let str = '' for (let i = 0; i < content.length; i++) { if (offset >= start) { str += content[i] } offset += stringWidth(content[i]) if (offset >= end) { break } } return str } function extractTable (value, eat, tokenizer) { // Extract lines before the grid table const markdownLines = value .split('\n') let i = 0 const before = [] for (; i < markdownLines.length; i++) { const line = markdownLines[i] if (isSeparationLine(line)) break if (stringWidth(line) === 0) break before.push(line) } const possibleGridTable = markdownLines .map(line => trimEnd(line)) // Extract table if (!possibleGridTable[i + 1]) return [null, null, null, null] const gridTable = [] const realGridTable = [] let hasHeader = false for (; i < possibleGridTable.length; i++) { const line = possibleGridTable[i] const realLine = markdownLines[i] // line is in table if (totalMainLineRegex.exec(line)) { const isHeaderLine = headerLineRegex.exec(line) if (isHeaderLine && !hasHeader) hasHeader = true // A table can't have 2 headers else if (isHeaderLine && hasHeader) { break } realGridTable.push(realLine) gridTable.push(line) } else { // this line is not in the grid table. break } } // if the last line is not a plain line if (!separationLineRegex.exec(gridTable[gridTable.length - 1])) { // Remove lines not in the table for (let j = gridTable.length - 1; j >= 0; j--) { const isSeparation = separationLineRegex.exec(gridTable[j]) if (isSeparation) break gridTable.pop() i -= 1 } } // Extract lines after table const after = [] for (; i < possibleGridTable.length; i++) { const line = possibleGridTable[i] if (stringWidth(line) === 0) break after.push(markdownLines[i]) } return [before, gridTable, realGridTable, after, hasHeader] } function extractTableContent (lines, linesInfos, hasHeader) { const table = new Table(linesInfos) for (let l = 0; l < lines.length; l++) { const line = lines[l] // Get if the line separate the head of the table from the body const matchHeader = hasHeader & isHeaderLine(line) !== null // Get if the line close some cells const isEndLine = matchHeader | isPartLine(line) !== null if (isEndLine) { // It is a header, a plain line or a line with plain line part. // First, update the last row table.lastPart().updateWithMainLine(line, isEndLine) // Create the new row if (l !== 0) { if (matchHeader) { table.addPart() } else if (isSeparationLine(line)) { table.lastPart().addRow() } else { table.lastPart().updateWithPartLine(line) } } // update the last row table.lastPart().updateWithMainLine(line, isEndLine) } else { // it's a plain line table.lastPart().updateWithMainLine(line, isEndLine) table.lastPart().lastRow().updateContent(line) } } // Because the last line is a separation, the last row is always empty table.lastPart().removeLastRow() return table } function processCellLines (lines) { const trimmedLines = [] let inCodeBlock = false let leadingWhitespace = 0 let leadingWhitespaceRegex = null for (let i = 0; i < lines.length; i++) { let line = lines[i] // Trim the end of the line line = line.trimEnd() // Check if we're entering or exiting a code block if (line.trim().startsWith('```')) { inCodeBlock = !inCodeBlock // If we're entering a code block, remember the amount of leading whitespace if (inCodeBlock) { // Set how much whitespace to remoev from lines // inside the code-block leadingWhitespace = line.match(/^ */)[0].length leadingWhitespaceRegex = new RegExp(`^ {0,${leadingWhitespace}}`) } // Remove leading whitespace from the opening/closing code-block // statement as well line = line.replace(leadingWhitespaceRegex, '') } else if (inCodeBlock) { // If we're *already* in a code block, trim the start of the line // by the amount of leading whitespace line = line.replace(leadingWhitespaceRegex, '') } else { // We're not in a code block, trim the start of the line line = line.trimStart() } // Replace the line in the array trimmedLines.push(line) } return trimmedLines } function generateTable (tableContent, now, tokenizer) { // Generate the gridTable node to insert in the AST const tableElt = { type: 'gridTable', children: [], data: { hName: 'table' } } const hasHeader = tableContent._parts.length > 1 for (let p = 0; p < tableContent._parts.length; p++) { const part = tableContent._parts[p] const partElt = { type: 'tableHeader', children: [], data: { hName: (hasHeader && p === 0) ? 'thead' : 'tbody' } } for (let r = 0; r < part._rows.length; r++) { const row = part._rows[r] const rowElt = { type: 'tableRow', children: [], data: { hName: 'tr' } } for (let c = 0; c < row._cells.length; c++) { const cell = row._cells[c] const trimmedLines = processCellLines(cell._lines) const tokenizedContent = tokenizer.tokenizeBlock( trimmedLines.join('\n'), now ) const cellElt = { type: 'tableCell', children: tokenizedContent, data: { hName: (hasHeader && p === 0) ? 'th' : 'td', hProperties: { colSpan: cell._colSpan, rowSpan: cell._rowSpan } } } const endLine = r + cell._rowSpan if (cell._rowSpan > 1 && endLine - 1 < part._rows.length) { for (let rs = 1; rs < cell._rowSpan; rs++) { for (let cc = 0; cc < part._rows[r + rs]._cells.length; cc++) { const other = part._rows[r + rs]._cells[cc] if (cell._startPosition === other._startPosition && cell._endPosition === other._endPosition && cell._colSpan === other._colSpan && cell._rowSpan === other._rowSpan && cell._lines === other._lines) { part._rows[r + rs]._cells.splice(cc, 1) } } } } rowElt.children.push(cellElt) } partElt.children.push(rowElt) } tableElt.children.push(partElt) } return tableElt } function gridTableTokenizer (eat, value, silent) { let index = 0 const length = value.length let character while (index < length) { character = value.charAt(index) if (character !== ' ' && character !== '\t') { break } index++ } if (value.charAt(index) !== '+') { return } if (value.charAt(index + 1) !== '-') { return } const keep = mainLineRegex.test(value) if (!keep) return const [before, gridTable, realGridTable, after, hasHeader] = extractTable(value, eat, this) if (!gridTable || gridTable.length < 3) return const now = eat.now() const linesInfos = computeColumnStartingPositions(gridTable) const tableContent = extractTableContent(gridTable, linesInfos, hasHeader) const tableElt = generateTable(tableContent, now, this) const merged = merge(before, realGridTable, after) // Because we can't add multiples blocs in one eat, I use a temp block const wrapperBlock = { type: 'element', tagName: 'WrapperBlock', children: [] } if (before.length) { const tokensBefore = this.tokenizeBlock(before.join('\n'), now)[0] wrapperBlock.children.push(tokensBefore) } wrapperBlock.children.push(tableElt) if (after.length) { const tokensAfter = this.tokenizeBlock(after.join('\n'), now) if (tokensAfter.length) { wrapperBlock.children.push(tokensAfter[0]) } } return eat(merged)(wrapperBlock) } function deleteWrapperBlock () { function one (node, index, parent) { if (!node.children) return const newChildren = [] let replace = false for (let c = 0; c < node.children.length; c++) { const child = node.children[c] if (child.tagName === 'WrapperBlock' && child.type === 'element') { replace = true for (let cc = 0; cc < child.children.length; cc++) { newChildren.push(child.children[cc]) } } else { newChildren.push(child) } } if (replace) { node.children = newChildren } } return one } function transformer (tree) { // Remove the temporary block in which we previously wrapped the table parts visit(tree, deleteWrapperBlock()) } function createGrid (nbRows, nbCols) { const grid = [] for (let i = 0; i < nbRows; i++) { grid.push([]) for (let j = 0; j < nbCols; j++) { grid[i].push({ height: -1, width: -1, hasBottom: true, hasRigth: true }) } } return grid } function setWidth (grid, i, j, cols) { /* To do it, we put enougth space to write the text. * For multi-cell, we divid it among the cells. */ let tmpWidth = Math.max(...Array.from(grid[i][j].value).map(x => x.length)) + 2 grid[i].forEach((_, c) => { if (c < cols) { // To divid const localWidth = Math.ceil(tmpWidth / (cols - c)) // cols - c will be 1 for the last cell tmpWidth -= localWidth grid[i][j + c].width = localWidth } }) } function setHeight (grid, i, j, values) { // To do it, we count the line. Extra length to cell with a pipe // in the value of the last line, to not be confuse with a border. grid[i][j].height = values.length // Extra line if (values[values.length - 1].indexOf('|') > 0) { grid[i][j].height += 1 } } function extractAST (gridNode, grid) { let i = 0 /* Fill the grid with value, height and width from the ast */ gridNode.children.forEach(th => { th.children.forEach(row => { row.children.forEach((cell, j) => { let X = 0 // x taking colSpan and rowSpan into account while (grid[i][j + X].evaluated) X++ grid[i][j + X].value = this.all(cell).join('\n\n').split('\n') setHeight(grid, i, j + X, grid[i][j + X].value) setWidth(grid, i, j + X, cell.data.hProperties.colSpan) // If it's empty, we fill it up with a useless space // Otherwise, it will not be parsed. if (!grid[0][0].value.join('\n')) { grid[0][0].value = [' '] grid[0][0].width = 3 } // Define the border of each cell for (let x = 0; x < cell.data.hProperties.rowSpan; x++) { for (let y = 0; y < cell.data.hProperties.colSpan; y++) { // b attribute is for bottom grid[i + x][j + X + y].hasBottom = x + 1 === cell.data.hProperties.rowSpan // r attribute is for right grid[i + x][j + X + y].hasRigth = y + 1 === cell.data.hProperties.colSpan // set v if a cell has ever been define grid[i + x][j + X + y].evaluated = ' ' } } }) i++ }) }) // If they is 2 differents tableHeader, so the first one is a header and // should be underlined if (gridNode.children.length > 1) { grid[gridNode.children[0].children.length - 1][0].isHeader = true } } function setSize (grid) { // The idea is the max win // Set the height of each column grid.forEach(row => { // Find the max const maxHeight = Math.max(...row.map(cell => cell.height)) // Set it to each cell row.forEach(cell => { cell.height = maxHeight }) }) // Set the width of each row grid[0].forEach((_, j) => { // Find the max const maxWidth = Math.max(...grid.map(row => row[j].width)) // Set it to each cell grid.forEach(row => { row[j].width = maxWidth }) }) } function generateBorders (grid, nbRows, nbCols, gridString) { /** **** Create the borders *******/ // Create the first line /* * We have to create the first line manually because * we process the borders from the attributes bottom * and right of each cell. For the first line, their * is no bottom nor right cell. * * We only need the right attribute of the first row's * cells */ let first = '+' grid[0].forEach((cell, i) => { first += '-'.repeat(cell.width) first += cell.hasRigth || i === nbCols - 1 ? '+' : '-' }) gridString.push(first) grid.forEach((row, i) => { let line = '' // Cells lines // The inner of the cell line = '|' row.forEach(cell => { cell.y = gridString.length cell.x = line.length + 1 line += ' '.repeat(cell.width) line += cell.hasRigth ? '|' : ' ' }) // Add it until the text can fit for (let t = 0; t < row[0].height; t++) { gridString.push(line) } // "End" line // It's the last line of the cell. Actually the border. line = row[0].hasBottom ? '+' : '|' row.forEach((cell, j) => { let char = ' ' if (cell.hasBottom) { if (row[0].isHeader) { char = '=' } else { char = '-' } } line += char.repeat(cell.width) if (cell.hasBottom || (j + 1 < nbCols && grid[i][j + 1].hasBottom)) { if (cell.hasRigth || (i + 1 < nbRows && grid[i + 1][j].hasRigth)) { line += '+' } else { line += (row[0].isHeader ? '=' : '-') } } else if (cell.hasRigth || (i + 1 < nbRows && grid[i + 1][j].hasRigth)) { line += '|' } else { line += ' ' } }) gridString.push(line) }) } function writeText (grid, gridString) { grid.forEach(row => { row.forEach(cell => { if (cell.value && cell.value[0]) { for (let tmpCount = 0; tmpCount < cell.value.length; tmpCount++) { const tmpLine = cell.y + tmpCount const line = cell.value[tmpCount] const lineEdit = gridString[tmpLine] gridString[tmpLine] = lineEdit.substr(0, cell.x) gridString[tmpLine] += line gridString[tmpLine] += lineEdit.substr(cell.x + line.length) } } }) }) } function stringifyGridTables (gridNode) { const gridString = [] const nbRows = gridNode.children.map(th => th.children.length).reduce((a, b) => a + b) const nbCols = gridNode.children[0] .children[0] .children.map(c => c.data.hProperties.colSpan) .reduce((a, b) => a + b) const grid = createGrid(nbRows, nbCols) /* First, we extract the information * then, we set the size(2) of the border * and create it(3). * Finaly we fill it up. */ extractAST.bind(this)(gridNode, grid) setSize(grid) generateBorders(grid, nbRows, nbCols, gridString) writeText(grid, gridString) return gridString.join('\n') } function plugin () { const Parser = this.Parser // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers const blockMethods = Parser.prototype.blockMethods blockTokenizers.gridTable = gridTableTokenizer blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'gridTable') const Compiler = this.Compiler // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return visitors.gridTable = stringifyGridTables } return transformer } ================================================ FILE: packages/remark-heading-shift/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-heading-shift/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-heading-shift/README.md ================================================ # remark-heading-shift [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin increments headings by `shift`. It guards against shifting too far backwards or forwards. `shift` defaults to `1`, it can be a positive or negative value, it will never shift a heading past `6` (because `h6` is the smallest HTML heading) in case of a positive `shift`, or lower than `1` (`h0` is not a valid HTML heading) in case of a negative `shift`. For example, the following markdown: ## foo #### bar ##### baz Yields: (shift=1) ### foo ##### bar ###### baz (shift=3) ##### foo ###### bar ###### baz (shift=-2) # foo ## bar ### baz ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-heading-shift/LICENSE-MIT [zds]: https://zestedesavoir.com ================================================ FILE: packages/remark-heading-shift/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`shift -1 1`] = ` "# remark-heading-shift # Example # API ## foo # Contributing " `; exports[`shift 0 1`] = ` "# remark-heading-shift ## Example ## API ### foo ## Contributing " `; exports[`shift 1 1`] = ` "## remark-heading-shift ### Example ### API #### foo ### Contributing " `; exports[`shift 3 1`] = ` "#### remark-heading-shift ##### Example ##### API ###### foo ##### Contributing " `; exports[`shift null 1`] = ` "# remark-heading-shift ## Example ## API ### foo ## Contributing " `; ================================================ FILE: packages/remark-heading-shift/__tests__/index.js ================================================ import dedent from 'dedent' import remark from 'remark' import plugin from '../src/' Array.from([null, 0, 1, -1, 3]).forEach(shift => { test(`shift ${shift}`, () => { const {contents} = remark() .use(plugin, shift) .processSync(dedent` # remark-heading-shift ## Example ## API ### foo ## Contributing `) expect(contents).toMatchSnapshot() }) }) ================================================ FILE: packages/remark-heading-shift/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); function shifter(shift = 1) { return tree => { visit(tree, 'heading', function (node) { if (!shift) return; if (node.depth + shift <= 1) { node.depth = 1; return; } if (node.depth + shift >= 6) { node.depth = 6; return; } node.depth += shift; }); }; } module.exports = shifter; ================================================ FILE: packages/remark-heading-shift/package.json ================================================ { "name": "remark-heading-shift", "version": "1.1.2", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-heading-shift", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/remark-heading-shift/src/index.js ================================================ const visit = require('unist-util-visit') function shifter (shift = 1) { return (tree) => { visit(tree, 'heading', function (node) { if (!shift) return if ((node.depth + shift) <= 1) { node.depth = 1 return } if ((node.depth + shift) >= 6) { node.depth = 6 return } node.depth += shift }) } } module.exports = shifter ================================================ FILE: packages/remark-heading-trailing-spaces/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-heading-trailing-spaces/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-heading-trailing-spaces/README.md ================================================ This plugin removes trailing spaces from Markdown headers. Examples, here `·` represents a space ` ` ``` Header ------·· ### H3··· H1·· = H2 --·· ``` Result: ``` <h2>Header</h2> <h3>H3</h3> <h1>H1</h1> <h2>H2</h2> ``` ================================================ FILE: packages/remark-heading-trailing-spaces/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`regression 1 1`] = ` "<p>word</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <table> <thead> <tr> <th>a</th> <th>b</th> </tr> </thead> <tbody> <tr> <td>c</td> <td>d</td> </tr> </tbody> </table>" `; exports[`with plugin 1`] = ` "<h2>Header</h2> <h3>H3</h3> <h1>H1</h1> <p>H2 -- </p>" `; exports[`without 1`] = ` "<p>Header ------··</p> <h3>H3···</h3> <h1>H1··</h1> <p>H2 --··</p>" `; ================================================ FILE: packages/remark-heading-trailing-spaces/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import rehypeStringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import plugin from '../src/' const render = text => unified() .use(reParse) .use(remark2rehype) .use(plugin) .use(rehypeStringify) .processSync(text.replace(/·/g, ' ')) /** * (for convenience, · are replaced with * simple single spaces in the tests) */ const fixture = dedent` Header ------·· ### H3··· H1·· = H2 --·· ` test('with plugin', () => { const {contents} = render(fixture) expect(contents).toMatchSnapshot() }) test('without', () => { const {contents} = unified() .use(reParse) .use(remark2rehype) .use(rehypeStringify) .processSync(fixture) expect(contents).toMatchSnapshot() }) test('regression 1', () => { const {contents} = render(dedent` word -·item·1 -·item·2 a|b ---|--- c|d `) expect(contents).toMatchSnapshot() }) ================================================ FILE: packages/remark-heading-trailing-spaces/dist/index.js ================================================ "use strict"; module.exports = function alignPlugin() { function headingTokenizer(eat, value, silent) { /* istanbul ignore if - never used (yet) */ if (silent) return true; const lines = value.match(/.*\n/g) || []; // Check if first line is not empty, // here, we don't use \s because a line with a tab is not empty if (/^$| +/.test(lines[0])) return; // and if the second line is a heading with trailing spaces if (!/^(-+|=+)\s+\n?$/.test(lines[1])) return; const now = eat.now(); const head = lines[0] + lines[1]; const add = eat(head); const exit = this.enterBlock(); exit(); return add({ type: 'heading', depth: lines[1][0] === '=' ? 1 : 2, children: this.tokenizeInline(lines[0].slice(0, -1), now) }); } const Parser = this.Parser; // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers; const blockMethods = Parser.prototype.blockMethods; blockTokenizers.heading_blocks = headingTokenizer; blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'heading_blocks'); }; ================================================ FILE: packages/remark-heading-trailing-spaces/package.json ================================================ { "name": "remark-heading-trailing-spaces", "version": "0.0.32", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-heading-trailing-spaces", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT" } ================================================ FILE: packages/remark-heading-trailing-spaces/src/index.js ================================================ module.exports = function alignPlugin () { function headingTokenizer (eat, value, silent) { /* istanbul ignore if - never used (yet) */ if (silent) return true const lines = value.match(/.*\n/g) || [] // Check if first line is not empty, // here, we don't use \s because a line with a tab is not empty if (/^$| +/.test(lines[0])) return // and if the second line is a heading with trailing spaces if (!/^(-+|=+)\s+\n?$/.test(lines[1])) return const now = eat.now() const head = lines[0] + lines[1] const add = eat(head) const exit = this.enterBlock() exit() return add({ type: 'heading', depth: lines[1][0] === '=' ? 1 : 2, children: this.tokenizeInline(lines[0].slice(0, -1), now) }) } const Parser = this.Parser // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers const blockMethods = Parser.prototype.blockMethods blockTokenizers.heading_blocks = headingTokenizer blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'heading_blocks') } ================================================ FILE: packages/remark-iframes/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-iframes/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-iframes/README.md ================================================ # remark-iframes [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax to create iframes. This creates a new MDAST element called "iframe" If you are using [rehype][rehype], the stringified HTML result will be a tag you can configure. Most of time you want `iframe`. ## iframe node type ```javascript interface iframe <: Node { type: "iframe"; url: string; provider: string; data: { hName: "iframe"; hProperties: { src: string; width: 0 <= uint32; height: 0 <= uint32; allowfullscreen: boolean; frameborder: string; } thumbnail: string?; } } ``` `provider` variable refers to the provider as configured in plugin options. ## Syntax ```markdown !(https://www.youtube.com/watch?v=8TQIvdFl4aU) ``` ## Installation [npm][npm]: ```bash npm install remark-iframes ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkIframe = require('remark-iframes') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkIframe, { // this key corresponds to the hostname: !(http://hostname/foo) // the config associated to this hostname will apply to any iframe // with a matching hostname 'www.youtube.com': { tag: 'iframe', width: 560, height: 315, disabled: false, replace: [ ['watch?v=', 'embed/'], ['http://', 'https://'], ], thumbnail: { format: 'http://img.youtube.com/vi/{id}/0.jpg', id: '.+/(.+)$' }, removeAfter: '&' } }) .use(remark2rehype) .use(stringify) ``` ## Configuration fields: - `tag`: HTML tag to use in rehype output, you most probably want `iframe`. - `width` and `height`: iframe size, set as `width="" height=""` HTML attributes. - `disabled`: Can be used to disable this provider. This is useful when you want to deal with multiple configurations from a common set of plugins. - `replace`: Rules passed to `String.prototype.replace` with the `input_url`. It's a list `[[from, to]]`, rules are applied sequentially on the output of the previous rule. Each rule only replaces the first occurrence. - `removeAfter`: Truncates the URL after the first occurrence of char. For example `http://dailymotion.com/video/?time=1&bla=2` will result in `http://dailymotion.com/video/?time=1` if `removeAfter` is set to `&`. - `append`: Any string you want to append to the URL, for example an API key. - `removeFileName`: If set to `true`, removes the filename (i.e last fragment before query string) from URL. - `match`: a regular expression passed to `String.prototype.test`, used to validate the URL. - `thumbnail`: a way to retrieve a thumbnail. This param is an object with a `format` key of this type: `'http://url/{param1}/{param2}'` you must then provide patterns `param: 'pattern'` to extract the value which will replace the corresponding `{param}` in the `format` URL. - `droppedQueryParameters`: a list of query parameters to remove from the iframe source URL. - `oembed`: an URL to the oEmbed API of the website you want to embed; - `lazyLoad`: tell browsers to lazy load the iframe whenever possible, using the HTML `loading` attribute. ### oEmbed usage When using the `oembed` configuration parameter, the other parameters are discarded, excepted for `disabled`, which can be used freely; you may use `width` and `height` if really needed, altough it is not recommended by the oEmbed specification. The thumbnail is constructed from the oEmbed `thumbnail_url` response, so there is no need for providing any URL, and any configuration will not be taken into account. ### Thumbnail construction when you configure the `thumbnail` as part of a provider, the URL of the thumbnail is computed following this algorithm: ``` thumbnail_url_template = provider.thumbnail.format for each property of provider.thumbnail if property is not "format": regexp_for_current_property = provider.thumbnail[property] extracted_value = video_url.search(regexp_for_current_property)[1] thumbnail_url_template = thumbnail_url_template.replace('{' + property + '}', extracted_value) ``` ## Example ### Config: ```javascript { // Youtube RegEx example 'www.youtube.com': { tag: 'iframe', width: 560, height: 315, disabled: false, replace: [ ['watch?v=', 'embed/'], ['http://', 'https://'], ], thumbnail: { format: 'http://img.youtube.com/vi/{id}/0.jpg', id: '.+/(.+)$' }, removeAfter: '&' }, // Youtube oEmbed example 'youtu.be': { width: 560, height: 315, disabled: false, oembed: 'https://www.youtube.com/oembed' } } ``` ### Input: ```markdown !(https://www.youtube.com/watch?v=8TQIvdFl4aU) ``` ### Resulting Node ```javascript { type: 'iframe', provider: 'www.youtube.com', data: { hName: 'iframe', hProperties: { src: 'https://www.youtube.com/embed/8TQIvdFl4aU', width: 560, height: 315, allowfullscreen: true, frameborder: '0' } thumbnail: 'https://image.youtube.com/8TQIvdFl4aU/0.jpg' } } ``` ### Resulting HTML ```html <iframe src="https://www.youtube.com/embed/8TQIvdFl4aU" width="560" height="315"></iframe> ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-iframes/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-iframes [rehype]: https://github.com/rehypejs/rehype ================================================ FILE: packages/remark-iframes/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Compiles to Markdown 1`] = ` "A [link with **bold**](http://example.com) !(https://www.youtube.com/watch?v=FdltlrKFr1w) These ones should not be allowed by config: !(http://jsfiddle.net/Sandhose/BcKhe/1/) !(http://jsfiddle.net/zgjhjv9j/) !(http://jsfiddle.net/zgjhjv9j/1/) !(http://jsfiddle.net/Sandhose/BcKhe/) Foo !(this is a parenthesis) bar " `; exports[`Compiles to Markdown 2`] = ` "A [link with **bold**](http://example.com) !(https://www.youtube.com/watch?v=FdltlrKFr1w) These ones should not be allowed by config: !(http://jsfiddle.net/Sandhose/BcKhe/1/) !(http://jsfiddle.net/zgjhjv9j/) !(http://jsfiddle.net/zgjhjv9j/1/) !(http://jsfiddle.net/Sandhose/BcKhe/) Foo !(this is a parenthesis) bar " `; exports[`does not parse without markers 1`] = ` "<iframe src=\\"https://www.youtube.com/embed/FdltlrKFr1w\\" width=\\"560\\" height=\\"315\\" allowfullscreen frameborder=\\"0\\"></iframe> <p><a href=\\"https://www.youtube.com/watch?v=FdltlrKFr1w\\">https://www.youtube.com/watch?v=FdltlrKFr1w</a></p>" `; exports[`oembed falls back 1`] = `"<a href=\\"https://www.youtube.com/watch?v=FdltlrKFr1w\\">https://www.youtube.com/watch?v=FdltlrKFr1w</a>"`; exports[`video 1`] = ` "<iframe src=\\"https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed\\" width=\\"560\\" height=\\"315\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://www.dailymotion.com/embed/video/x2y6lhm\\" width=\\"480\\" height=\\"270\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://player.vimeo.com/video/133693532\\" width=\\"500\\" height=\\"281\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://screen.yahoo.com/weatherman-gives-forecast-using-taylor-191821481.html?format=embed&player_autoplay=false\\" width=\\"624\\" height=\\"351\\" allowfullscreen frameborder=\\"0\\"></iframe> <p>A <a href=\\"http://example.com\\">link with <strong>bold</strong></a></p> <iframe src=\\"https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed\\" width=\\"560\\" height=\\"315\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed\\" width=\\"560\\" height=\\"315\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://jsfiddle.net/Sandhose/BcKhe/1/embedded/result,js,html,css/\\" width=\\"560\\" height=\\"560\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://jsfiddle.net/zgjhjv9j/embedded/result,js,html,css/\\" width=\\"560\\" height=\\"560\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://jsfiddle.net/zgjhjv9j/1/embedded/result,js,html,css/\\" width=\\"560\\" height=\\"560\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"https://www.youtube.com/embed/1Bh4DZ2xGmw?feature=oembed\\" width=\\"560\\" height=\\"315\\" allowfullscreen frameborder=\\"0\\"></iframe> <iframe src=\\"http://player.ina.fr/player/embed/MAN9062216517/1/1b0bd203fbcd702f9bc9b10ac3d0fc21/560/315/1/148db8\\" width=\\"620\\" height=\\"349\\" allowfullscreen frameborder=\\"0\\"></iframe> <p>Not parsed:</p> <p>!(http://jsfiddle.net/Sandhose/BcKhe/)</p> <iframe src=\\"https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed\\" width=\\"560\\" height=\\"315\\" allowfullscreen frameborder=\\"0\\"></iframe> <p>with text after</p>" `; ================================================ FILE: packages/remark-iframes/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import remarkStringify from 'remark-stringify' import plugin from '../src/' const render = async (text, config) => unified() .use(reParse) .use(plugin, config) .use(remark2rehype) .use(stringify) .process(text) const renderMarkdown = (text, config) => unified() .use(reParse) .use(remarkStringify) .use(plugin, config) .processSync(text) const config = { video: { 'www.dailymotion.com': { tag: 'iframe', width: 480, height: 270, disabled: false, replace: [ ['video/', 'embed/video/'], ], }, 'www.vimeo.com': { tag: 'iframe', width: 500, height: 281, disabled: false, replace: [ ['http://', 'https://'], ['www.', ''], ['vimeo.com/', 'player.vimeo.com/video/'], ], }, 'vimeo.com': { tag: 'iframe', width: 500, height: 281, disabled: false, replace: [ ['http://', 'https://'], ['www.', ''], ['vimeo.com/', 'player.vimeo.com/video/'], ], }, 'www.youtube.com': { width: 560, height: 315, disabled: false, oembed: 'https://www.youtube.com/oembed', }, 'youtube.com': { width: 560, height: 315, disabled: false, oembed: 'https://www.youtube.com/oembed', }, 'youtu.be': { width: 560, height: 315, disabled: false, oembed: 'https://www.youtube.com/oembed', }, 'screen.yahoo.com': { tag: 'iframe', width: 624, height: 351, disabled: false, append: '?format=embed&player_autoplay=false', }, 'www.ina.fr': { tag: 'iframe', width: 620, height: 349, disabled: false, replace: [ ['www.', 'player.'], ['/video/', '/player/embed/'], ], append: '/1/1b0bd203fbcd702f9bc9b10ac3d0fc21/560/315/1/148db8', removeFileName: true, }, 'www.jsfiddle.net': { tag: 'iframe', width: 560, height: 560, disabled: false, replace: [ ['http://', 'https://'], ], append: 'embedded/result,js,html,css/', match: /https?:\/\/(www\.)?jsfiddle\.net\/([\w\d]+\/[\w\d]+\/\d+\/?|[\w\d]+\/\d+\/?|[\w\d]+\/?)$/, }, 'jsfiddle.net': { tag: 'iframe', width: 560, height: 560, disabled: false, replace: [ ['http://', 'https://'], ], append: 'embedded/result,js,html,css/', match: /https?:\/\/(www\.)?jsfiddle\.net\/([\w\d]+\/[\w\d]+\/\d+\/?|[\w\d]+\/\d+\/?|[\w\d]+\/?)$/, }, }, extra: { 'www.youtube.com': { tag: 'iframe', width: 560, height: 315, disabled: false, replace: [ ['watch?v=', 'embed/'], ['http://', 'https://'], ], droppedQueryParameters: ['feature'], removeAfter: '&', }, 'jsfiddle.net': { tag: 'iframe', width: 560, height: 560, disabled: true, replace: [ ['http://', 'https://'], ], append: 'embedded/result,js,html,css/', }, }, toMd: { 'www.youtube.com': { tag: 'iframe', width: 560, height: 315, disabled: false, replace: [ ['watch?v=', 'embed/'], ['http://', 'https://'], ], removeAfter: '&', }, 'jsfiddle.net': { tag: 'iframe', width: 560, height: 560, disabled: true, replace: [ ['http://', 'https://'], ], append: 'embedded/result,js,html,css/', match: /https?:\/\/(www\.)?jsfiddle\.net\/([\w\d]+\/[\w\d]+\/\d+\/?|[\w\d]+\/\d+\/?|[\w\d]+\/?)$/, thumbnail: { format: 'http://www.unixstickers.com/image/data/stickers' + '/jsfiddle/JSfiddle-blue-w-type.sh.png', }, }, }, } test('video', async () => { const {contents} = await render(dedent` !(https://www.youtube.com/watch?v=FdltlrKFr1w) !(https://www.dailymotion.com/video/x2y6lhm) !(http://vimeo.com/133693532) !(https://screen.yahoo.com/weatherman-gives-forecast-using-taylor-191821481.html) A [link with **bold**](http://example.com) !(https://youtu.be/FdltlrKFr1w) !(http://youtube.com/watch?v=FdltlrKFr1w) !(http://jsfiddle.net/Sandhose/BcKhe/1/) !(http://jsfiddle.net/zgjhjv9j/) !(http://jsfiddle.net/zgjhjv9j/1/) !(https://www.youtube.com/watch?v=1Bh4DZ2xGmw&ab_channel=DestinationPr%C3%A9pa) !(http://www.ina.fr/video/MAN9062216517/) Not parsed: !(http://jsfiddle.net/Sandhose/BcKhe/) !(https://www.youtube.com/watch?v=FdltlrKFr1w) with text after `, config.video) expect(contents).toMatchSnapshot() }) test('oembed falls back', async () => { const config = { 'www.youtube.com': { width: 560, height: 315, disabled: false, oembed: 'http://example.com:7777/oembed', }, } const result = await render(dedent` !(https://www.youtube.com/watch?v=FdltlrKFr1w) `, config) expect(result.messages[0].message).toContain('timeout') expect(result.contents).toMatchSnapshot() }) test('extra', async () => { const {contents: parsed} = await render(dedent` !(https://www.youtube.com/watch?v=FdltlrKFr1w) !(https://www.youtube.com/watch?feature=embedded&v=FdltlrKFr1w) `, config.extra) expect(parsed).toMatch(/iframe.*iframe/) const {contents: notParsed} = await render(dedent` !(http://jsfiddle.net/Sandhose/BcKhe/1/) !(http://jsfiddle.net/zgjhjv9j/) !(http://jsfiddle.net/zgjhjv9j/1/) !(http://jsfiddle.net/Sandhose/BcKhe/) `, config.extra) expect(notParsed).not.toMatch('iframe') }) test('does not parse without markers', async () => { const config = { 'www.youtube.com': { tag: 'iframe', width: 560, height: 315, disabled: false, replace: [ ['watch?v=', 'embed/'], ['http://', 'https://'], ], droppedQueryParameters: ['feature'], removeAfter: '&', }, } const {contents} = await render(dedent` !(https://www.youtube.com/watch?v=FdltlrKFr1w) https://www.youtube.com/watch?v=FdltlrKFr1w `, config) expect(contents).toMatchSnapshot() }) test('allows lazy loading', async () => { const config = { 'youtube.com': { width: 560, height: 315, disabled: false, lazyLoad: true, oembed: 'https://www.youtube.com/oembed', }, } const {contents} = await render('!(http://youtube.com/watch?v=FdltlrKFr1w)', config) expect(contents).toContain('loading="lazy"') }) test('Errors without config', async () => { expect(render('')).rejects.toThrowError(Error) }) test('Errors with empty config', async () => { expect(render('', {})).rejects.toThrowError(Error) }) test('Errors with invalid config', async () => { expect(render('', '')).rejects.toThrowError(Error) }) test('Compiles to Markdown', () => { const txt = dedent` A [link with **bold**](http://example.com) !(https://www.youtube.com/watch?v=FdltlrKFr1w) These ones should not be allowed by config: !(http://jsfiddle.net/Sandhose/BcKhe/1/) !(http://jsfiddle.net/zgjhjv9j/) !(http://jsfiddle.net/zgjhjv9j/1/) !(http://jsfiddle.net/Sandhose/BcKhe/) Foo !(this is a parenthesis) bar ` const {contents} = renderMarkdown(txt, config.toMd) expect(contents).toMatchSnapshot() const recompiled = renderMarkdown(contents.replace(/:/g, ':'), config.toMd).contents expect(recompiled).toBe(contents) config.toMd['jsfiddle.net'].disabled = false const withJsFiddleActivated = renderMarkdown(txt, config.toMd).contents expect(withJsFiddleActivated).toMatchSnapshot() const recompiledWithJsFiddleActivated = renderMarkdown(withJsFiddleActivated.replace(/:/g, ':'), config.toMd).contents expect(recompiledWithJsFiddleActivated).toBe(withJsFiddleActivated) }) ================================================ FILE: packages/remark-iframes/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); const fetch = require('node-fetch'); module.exports = function plugin(opts) { if (typeof opts !== 'object' || !Object.keys(opts).length) { throw new Error('remark-iframes needs to be passed a configuration object as option'); } function detectProvider(url) { const hostname = new URL(url).hostname; return opts[hostname]; } function blockTokenizer(eat, value, silent) { if (!value.startsWith('!(http')) return; let eatenValue = ''; let url = ''; const specialChars = ['!', '(', ')']; for (let i = 0; i < value.length && value[i - 1] !== ')'; i++) { eatenValue += value[i]; if (!specialChars.includes(value[i])) { url += value[i]; } } /* istanbul ignore if - never used (yet) */ if (silent) return true; const provider = detectProvider(url); if (!provider || provider.disabled === true || provider.match && provider.match instanceof RegExp && !provider.match.test(url)) { return eat(eatenValue)({ type: 'paragraph', children: [{ type: 'text', value: eatenValue }] }); } let finalUrl, thumbnail; const data = { hName: provider.tag || 'iframe', hProperties: { src: 'tmp', width: provider.width, height: provider.height, allowfullscreen: true, frameborder: '0' } }; if (provider.lazyLoad) data.hProperties.loading = 'lazy'; if (provider.oembed) { Object.assign(data, { oembed: { provider, url: `${provider.oembed}?format=json&url=${encodeURIComponent(url)}`, fallback: { type: 'link', url, children: [{ type: 'text', value: url }] } } }); } else { finalUrl = computeFinalUrl(provider, url); thumbnail = computeThumbnail(provider, finalUrl); Object.assign(data, { hProperties: { src: finalUrl, width: provider.width, height: provider.height, allowfullscreen: true, frameborder: '0' }, thumbnail }); } eat(eatenValue)({ type: 'iframe', src: url, data }); } const Parser = this.Parser; // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers; const blockMethods = Parser.prototype.blockMethods; blockTokenizers.iframes = blockTokenizer; blockMethods.splice(blockMethods.indexOf('blockquote') + 1, 0, 'iframes'); const Compiler = this.Compiler; if (Compiler) { const visitors = Compiler.prototype.visitors; if (!visitors) return; visitors.iframe = node => `!(${node.src})`; } return async function transform(tree, vfile, next) { let toVisit = 0; visit(tree, 'iframe', async node => { toVisit++; }); function nextVisitOrBail() { if (toVisit === 0) next(); } nextVisitOrBail(); visit(tree, 'iframe', async node => { if (!node.data.oembed) { toVisit--; nextVisitOrBail(); return; } const data = node.data; const oembed = data.oembed; const provider = data.oembed.provider; const fallback = data.oembed.fallback; try { const { url, thumbnail, height, width } = await fetchEmbed(oembed.url); node.thumbnail = thumbnail; Object.assign(data.hProperties, { src: url, width: provider.width || width, height: provider.height || height, allowfullscreen: true, frameborder: '0' }); } catch (err) { let message = err.message; if (err.name === 'FetchError') { message = `oEmbed URL timeout: ${oembed.url}`; } vfile.message(message, node.position, oembed.url); node.data = {}; Object.assign(node, fallback); } delete data.oembed; toVisit--; nextVisitOrBail(); }); }; }; function computeFinalUrl(provider, url) { let finalUrl = url; let parsed = new URL(finalUrl); if (provider.droppedQueryParameters && parsed.search) { const search = new URLSearchParams(parsed.search); provider.droppedQueryParameters.forEach(ignored => search.delete(ignored)); parsed.search = search.toString(); finalUrl = parsed.toString(); } if (provider.replace && provider.replace.length) { provider.replace.forEach(rule => { const [from, to] = rule; if (from && to) finalUrl = finalUrl.replace(from, to); parsed = new URL(finalUrl); }); finalUrl = parsed.toString(); } if (provider.removeFileName) { parsed.pathname = parsed.pathname.substring(0, parsed.pathname.lastIndexOf('/')); finalUrl = parsed.toString(); } if (provider.removeAfter && finalUrl.includes(provider.removeAfter)) { finalUrl = finalUrl.substring(0, finalUrl.indexOf(provider.removeAfter)); } if (provider.append) { finalUrl += provider.append; } return finalUrl; } function computeThumbnail(provider, url) { let thumbnailURL = ''; const thumbnailConfig = provider.thumbnail; if (thumbnailConfig && thumbnailConfig.format) { thumbnailURL = thumbnailConfig.format; Object.keys(thumbnailConfig).filter(key => key !== 'format').forEach(key => { const search = new RegExp(`{${key}}`, 'g'); const replace = new RegExp(thumbnailConfig[key]).exec(url); if (replace) thumbnailURL = thumbnailURL.replace(search, replace[1]); }); } return thumbnailURL; } async function fetchEmbed(url) { return fetch(url, { timeout: 1500 }).then(res => res.json()).then(oembedRes => { const oembedUrl = oembedRes.html.match(/src="(.+?)"/)[1]; const oembedThumbnail = oembedRes.thumbnail_url; return { url: oembedUrl, thumbnail: oembedThumbnail, width: oembedRes.width, height: oembedRes.height }; }); } ================================================ FILE: packages/remark-iframes/package.json ================================================ { "name": "remark-iframes", "version": "4.1.1", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-iframes", "type": "git" }, "author": "François (artragis) Dambrine <perso@francoisdambrine.me>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "node-fetch": "^2.6.0" } } ================================================ FILE: packages/remark-iframes/src/index.js ================================================ const visit = require('unist-util-visit') const fetch = require('node-fetch') module.exports = function plugin (opts) { if (typeof opts !== 'object' || !Object.keys(opts).length) { throw new Error('remark-iframes needs to be passed a configuration object as option') } function detectProvider (url) { const hostname = new URL(url).hostname return opts[hostname] } function blockTokenizer (eat, value, silent) { if (!value.startsWith('!(http')) return let eatenValue = '' let url = '' const specialChars = ['!', '(', ')'] for (let i = 0; i < value.length && value[i - 1] !== ')'; i++) { eatenValue += value[i] if (!specialChars.includes(value[i])) { url += value[i] } } /* istanbul ignore if - never used (yet) */ if (silent) return true const provider = detectProvider(url) if ( (!provider || provider.disabled === true) || (provider.match && provider.match instanceof RegExp && !provider.match.test(url)) ) { return eat(eatenValue)({ type: 'paragraph', children: [{ type: 'text', value: eatenValue }] }) } let finalUrl, thumbnail const data = { hName: provider.tag || 'iframe', hProperties: { src: 'tmp', width: provider.width, height: provider.height, allowfullscreen: true, frameborder: '0' } } if (provider.lazyLoad) data.hProperties.loading = 'lazy' if (provider.oembed) { Object.assign(data, { oembed: { provider, url: `${provider.oembed}?format=json&url=${encodeURIComponent(url)}`, fallback: { type: 'link', url, children: [{ type: 'text', value: url }] } } }) } else { finalUrl = computeFinalUrl(provider, url) thumbnail = computeThumbnail(provider, finalUrl) Object.assign(data, { hProperties: { src: finalUrl, width: provider.width, height: provider.height, allowfullscreen: true, frameborder: '0' }, thumbnail }) } eat(eatenValue)({ type: 'iframe', src: url, data }) } const Parser = this.Parser // Inject blockTokenizer const blockTokenizers = Parser.prototype.blockTokenizers const blockMethods = Parser.prototype.blockMethods blockTokenizers.iframes = blockTokenizer blockMethods.splice(blockMethods.indexOf('blockquote') + 1, 0, 'iframes') const Compiler = this.Compiler if (Compiler) { const visitors = Compiler.prototype.visitors if (!visitors) return visitors.iframe = (node) => `!(${node.src})` } return async function transform (tree, vfile, next) { let toVisit = 0 visit(tree, 'iframe', async (node) => { toVisit++ }) function nextVisitOrBail () { if (toVisit === 0) next() } nextVisitOrBail() visit(tree, 'iframe', async (node) => { if (!node.data.oembed) { toVisit-- nextVisitOrBail() return } const data = node.data const oembed = data.oembed const provider = data.oembed.provider const fallback = data.oembed.fallback try { const { url, thumbnail, height, width } = await fetchEmbed(oembed.url) node.thumbnail = thumbnail Object.assign(data.hProperties, { src: url, width: provider.width || width, height: provider.height || height, allowfullscreen: true, frameborder: '0' }) } catch (err) { let message = err.message if (err.name === 'FetchError') { message = `oEmbed URL timeout: ${oembed.url}` } vfile.message(message, node.position, oembed.url) node.data = {} Object.assign(node, fallback) } delete data.oembed toVisit-- nextVisitOrBail() }) } } function computeFinalUrl (provider, url) { let finalUrl = url let parsed = new URL(finalUrl) if (provider.droppedQueryParameters && parsed.search) { const search = new URLSearchParams(parsed.search) provider.droppedQueryParameters.forEach(ignored => search.delete(ignored)) parsed.search = search.toString() finalUrl = parsed.toString() } if (provider.replace && provider.replace.length) { provider.replace.forEach((rule) => { const [from, to] = rule if (from && to) finalUrl = finalUrl.replace(from, to) parsed = new URL(finalUrl) }) finalUrl = parsed.toString() } if (provider.removeFileName) { parsed.pathname = parsed.pathname.substring(0, parsed.pathname.lastIndexOf('/')) finalUrl = parsed.toString() } if (provider.removeAfter && finalUrl.includes(provider.removeAfter)) { finalUrl = finalUrl.substring(0, finalUrl.indexOf(provider.removeAfter)) } if (provider.append) { finalUrl += provider.append } return finalUrl } function computeThumbnail (provider, url) { let thumbnailURL = '' const thumbnailConfig = provider.thumbnail if (thumbnailConfig && thumbnailConfig.format) { thumbnailURL = thumbnailConfig.format Object .keys(thumbnailConfig) .filter((key) => key !== 'format') .forEach((key) => { const search = new RegExp(`{${key}}`, 'g') const replace = new RegExp(thumbnailConfig[key]).exec(url) if (replace) thumbnailURL = thumbnailURL.replace(search, replace[1]) }) } return thumbnailURL } async function fetchEmbed (url) { return fetch(url, { timeout: 1500 }) .then((res) => res.json()) .then((oembedRes) => { const oembedUrl = oembedRes.html.match(/src="(.+?)"/)[1] const oembedThumbnail = oembedRes.thumbnail_url return { url: oembedUrl, thumbnail: oembedThumbnail, width: oembedRes.width, height: oembedRes.height } }) } ================================================ FILE: packages/remark-images-download/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-images-download/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-images-download/README.md ================================================ # remark-images-download [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin downloads images to a custom directory, replacing images URLs with the path to the downloaded file. ## Installation [npm][npm]: ```bash npm install remark-images-download ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkImagesDownload = require('remark-images-download') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkIframe, .use(remarkImagesDownload, { disabled: true, downloadDestination: './img/', defaultImagePath: 'black.png', defaultOn: { statusCode: true, mimeType: false, fileTooBig: false, }, maxlength: 1000000, dirSizeLimit: 10000000, localUrlToLocalPath: (localUrl) => localPath }) .use(remark2rehype) .use(stringify) ``` ## Configuration options: All options are optional. - `disabled`: bool, default: `false` If `true`, disables the plugin. - `downloadDestination`: string, default: `/tmp` Parent destination folder for downloads. - `defaultImagePath`: string or boolean, default: `false` Image path to fallback to for images that couldn't be found. Set to `false` or keep default value to disable. - `defaultOn`: object, with properties, Cases when the default image should be used. - `statusCode`: boolean, default `false` The status code is different than 200. - `mimeType`: boolean, default `false` The MIME type does not match an image. - `fileTooBig`: boolean, default `false` The file size exceed the `maxFileSize` limit. - `maxFileLength`: number, default: `1000000` Any file with a bigger size than this number (in bytes) will be skipped. - `dirSizeLimit`: number, default: `10000000` Download directory size limit (in bytes). When reached, subsequent images are skipped. - `localUrlToLocalPath`: `(localUrl: string): string => localPath` or `[from: string, to: string]`, default: `<none>` (skip local images) If provided, local images referenced in Markdown source (such as `![](/img/example.png)`) will be copied to `downloadDestination` after applying this function to the path to obtain the local location of `example.png`, e.g. `localUrlToLocalPath('/img/example.png') === '/opt/assets/example.png'`. It will get renamed to a shortId just like any downloaded image. In case a two-element array is provided, the string `from` will get replaced by `to` using the following RegExp: ```js '/img/example.png'.replace(new RegExp(`^${from}`), to) ``` If not provided, local images will not end up in `downloadDestination`. ## Example ```markdown Two small images: ![](https://example.com/example.png) ![](https://example.com/example2.png) And an image of 1Tb! ![](https://example.com/example_1Tb.png) ``` with the previous configuration `remark-images-download` will download the two first images in `img/UUID/otherUUID.png` and `img/UUID/yetAnotherUUID.png` where `UUID` is a random string and it does not download `example_1Tb.png` because the file is too large. `vfile.data.imageDir` will be set to the path to the folder where images were downloaded. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-images-download/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-images-download ================================================ FILE: packages/remark-images-download/__mock__/files/empty ================================================ ================================================ FILE: packages/remark-images-download/__mock__/files/world ================================================ hello ================================================ FILE: packages/remark-images-download/__mock__/files/wrong-mime.txt ================================================ coucou! ================================================ FILE: packages/remark-images-download/__mock__/server.js ================================================ const path = require('path') const express = require('express') const fs = require('fs') const app = express() let pngHeader const fd = fs.createReadStream(path.join(__dirname, 'files', 'ok.png')) fd.on('data', (data) => { if (!pngHeader) { pngHeader = data } }) app.use((req, res, next) => { /* Some websites (like Wikimedia) may refuse requests without User-Agent * header, so mimic their behaviour: */ if (!req.get('User-Agent')) { res.writeHead(403) res.end() } next() }) const bloat = Buffer.alloc(10 * 1024) app.get('/stream-bomb', (req, res) => { res.header('content-type', 'image/png') res.header('transfer-encoding', 'chunked') res.writeHead(200) res.write(pngHeader) // Send garbage until the client closes the connection const sendBloat = () => { while (res.write(bloat)); } sendBloat() res.socket.on('drain', sendBloat) }) app.get('/empty', (req, res) => { res.header('content-type', 'image/png') res.header('transfer-encoding', 'chunked') res.writeHead(200) res.end() }) app.use('/', express.static(path.join(__dirname, 'files'))) const server = app.listen(27273, () => {}) module.exports = server ================================================ FILE: packages/remark-images-download/__tests__/index.js ================================================ /* global beforeAll, afterAll */ import clone from 'clone' import fs from 'fs' import dedent from 'dedent' import path from 'path' import rimraf from 'rimraf' import unified from 'unified' import reParse from 'remark-parse' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import plugin from '../src/' import server from '../__mock__/server' const DOWNLOAD_DESTINATION = '/tmp' const downloadDestination = path.join(DOWNLOAD_DESTINATION, 'remark-image-download-tests') const testFilesDir = `${__dirname.replace('__tests__', '__mock__')}/files` const firstMsg = (vfile) => vfile.messages[0].message const renderFactory = (opts = {}) => (text) => unified() .use(reParse) .use(plugin, Object.assign(clone(opts), {downloadDestination})) .use(remark2rehype) .use(stringify) .process(text) beforeAll((done) => { fs.mkdir(downloadDestination, done) }) afterAll((done) => { server.close() rimraf(downloadDestination, done) }) const r = (html) => html.replace( new RegExp( path.join(downloadDestination, '([a-zA-Z0-9_-]{7,14})', '([a-zA-Z0-9_-]{7,14})'), 'g'), 'foo/bar') const noDownloads = async asyncFunction => { const prevDownloadDirCount = fs.readdirSync(downloadDestination).length await asyncFunction() const downloadDirCount = fs.readdirSync(downloadDestination).length expect(downloadDirCount).toBe(prevDownloadDirCount) } describe('mock server tests', () => { test('rejects invalid URLs', () => { const file = '![foo](https://example.com:demo)' const render = renderFactory() return render(file).then(vfile => { expect(firstMsg(vfile)).toBe('Invalid URL: https://example.com:demo') }) }) test('downloads image ok', () => { const file = dedent` ![](http://localhost:27273/test.svg) ![](http://localhost:27273/ok.png) ![](http://localhost:27273/ok.png?foo) ` const html = dedent` <p><img src="foo/bar.svg"></p> <p><img src="foo/bar.png"></p> <p><img src="foo/bar.png"></p> ` const render = renderFactory() return render(file).then(vfile => { expect(r(vfile.contents)).toBe(html) const downloadedFiles = fs.readdirSync(vfile.data.imageDir) expect(downloadedFiles.length).toBe(3) }) }) test('does not crash without images', () => { const file = `foo` const html = `<p>foo</p>` const render = renderFactory() return render(file).then(vfile => { expect(vfile.contents).toBe(html) }) }) test('does not create empty dest folder', async () => { const file = `foo` const render = renderFactory() return noDownloads(() => render(file).then(vfile => { expect(vfile.data.imageDir).toBe(undefined) }), ) }) test('skips bigger images and reports', () => { const file = `![](http://localhost:27273/ok.png)` const html = `<p><img src="http://localhost:27273/ok.png"></p>` const error = 'File at http://localhost:27273/ok.png weighs 516, max size is 400' const render = renderFactory({ maxFileSize: 400, }) return noDownloads(() => render(file).then(vfile => { expect(firstMsg(vfile)).toBe(error) expect(vfile.contents).toBe(html) expect(vfile.data.imageDir).toBe(undefined) }), ) }) test('skips empty images', () => { const md = `![]()` const html = `<p><img src=""></p>` const render = renderFactory() return render(md).then(vfile => { expect(firstMsg(vfile)).toBe('URL is empty') expect(vfile.contents).toBe(html) }) }) test('default big images', () => { const defaultImagePath = 'default.png' const file = `![](http://localhost:27273/ok.png)` const html = `<p><img src="${downloadDestination}/${defaultImagePath}"></p>` const render = renderFactory({ maxFileSize: 400, defaultImagePath, defaultOn: { fileTooBig: true, }, }) return render(file).then(vfile => { expect(vfile.contents).toBe(html) }) }) test('reports bad SVG', () => { const file = `![](http://localhost:27273/bad.svg)` const html = `<p><img src="http://localhost:27273/bad.svg"></p>` const render = renderFactory() return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( 'Could not detect http://localhost:27273/bad.svg mime type, not SVG either') expect(vfile.contents).toBe(html) expect(vfile.data.imageDir).toBe(undefined) }) }) test('skips wrong mimes', () => { const file = `![](http://localhost:27273/wrong-mime.txt)` const html = `<p><img src="http://localhost:27273/wrong-mime.txt"></p>` const render = renderFactory() return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( 'Content-Type of http://localhost:27273/wrong-mime.txt is not an image/ type') expect(vfile.contents).toBe(html) }) }) test('default wrong mime', () => { const defaultImagePath = 'default.png' const file = `![](http://localhost:27273/wrong-mime.txt)` const html = `<p><img src="${downloadDestination}/${defaultImagePath}"></p>` const render = renderFactory({ defaultImagePath, defaultOn: { mimeType: true, }, }) return render(file).then(vfile => { expect(vfile.contents).toBe(html) }) }) test('reports bad mime headers', () => { const file = `![](http://localhost:27273/wrong-mime.txt)` const render = renderFactory() return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( 'Content-Type of http://localhost:27273/wrong-mime.txt is not an image/ type') }) }) test('reports bad mime content', () => { const file = `![](http://localhost:27273/wrong-mime.png)` const render = renderFactory() return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( 'Could not detect http://localhost:27273/wrong-mime.png mime type, not SVG either') }) }) test('reports protocol not whitelisted', () => { const file = `![](xmpp://localhost:27273/wrong-mime.png)` const error = "Protocol 'xmpp:' not allowed." const render = renderFactory() return render(file).then(vfile => { expect(firstMsg(vfile)).toBe(error) }) }) test('skips when directory reaches size limit', () => { const file = dedent` ![](http://localhost:27273/ok.png) ![](http://localhost:27273/ok.png?foo) ![](http://localhost:27273/ok.png?bar) ![](http://localhost:27273/ok.png?moo) ` // query strings are required in order to make URLs unique (one single // file is downloaded otherwise). // each file being roughly 30% of directory size limit, the 4th one would exceed this // limit. It shouldn't get downloaded & replaced: const html = dedent` <p><img src="foo/bar.png"> <img src="foo/bar.png"> <img src="foo/bar.png"> <img src="http://localhost:27273/ok.png?moo"></p> ` const error = 'Cannot download http://localhost:27273/ok.png?moo because ' + 'destination directory reached size limit' const render = renderFactory({ maxFileSize: 516, dirSizeLimit: (516 * 3) + 50, }) return render(file) .then(vfile => { expect(firstMsg(vfile)).toBe(error) expect(r(vfile.contents)).toBe(html) }) }) test('does not download when disabled', () => { const file = dedent` ![](http://localhost:27273/ok.png) ![](http://localhost:27273/ok.png) ![](http://localhost:27273/ok.png) ![](http://localhost:27273/ok.png) ` // images will not be downloaded const html = dedent` <p><img src="http://localhost:27273/ok.png"> <img src="http://localhost:27273/ok.png"> <img src="http://localhost:27273/ok.png"> <img src="http://localhost:27273/ok.png"></p> ` const render = renderFactory({ disabled: true, }) return noDownloads(async () => { const vfile = await render(file) expect(vfile.contents).toBe(html) }) }) test('skips local images', () => { const file = `![](local.png)` const html = `<p><img src="local.png"></p>` const render = renderFactory() expect(render(file).then(vfile => vfile.contents)).resolves.toBe(html) }) test('reports local magic number errors', () => { const file = `![](/foobar/wrong-mime.txt)` const render = renderFactory({ localUrlToLocalPath: (localUrl) => { const localPath = __dirname.replace('__tests__', '__mock__') return `${localPath}/files${localUrl.slice(7)}` }, }) return render(file).then(vfile => { expect(firstMsg(vfile)).toMatch('mime type, not SVG either') }) }) test('copies local images with function', () => { const file = `![](/foobar/ok.png)` const html = `<p><img src="foo/bar.png"></p>` const render = renderFactory({ localUrlToLocalPath: (localUrl) => { const localPath = __dirname.replace('__tests__', '__mock__') return `${localPath}/files${localUrl.slice(7)}` }, }) return render(file).then(vfile => { expect(vfile.messages).toEqual([]) expect(r(vfile.contents)).toBe(html) }) }) test('copies local images with replacement array', () => { const file = `![](/foobar/ok.png)` const html = `<p><img src="foo/bar.png"></p>` const render = renderFactory({ localUrlToLocalPath: [ '/foobar', testFilesDir, ], }) return render(file).then(vfile => { expect(vfile.messages).toEqual([]) expect(r(vfile.contents)).toBe(html) }) }) test('copies local SVG with replacement array', () => { const file = `![](/foobar/test.svg)` const html = `<p><img src="foo/bar.svg"></p>` const render = renderFactory({ localUrlToLocalPath: [ '/foobar', testFilesDir, ], }) return render(file).then(vfile => { expect(vfile.messages).toEqual([]) expect(r(vfile.contents)).toBe(html) }) }) test('copies local images with replacement array', () => { const file = `![](/foobar/ok.png)` const html = `<p><img src="foo/bar.png"></p>` const render = renderFactory({ localUrlToLocalPath: [ '/foobar', testFilesDir, ], }) return render(file).then(vfile => { expect(vfile.messages).toEqual([]) expect(r(vfile.contents)).toBe(html) }) }) test('does not copy bad local SVG', () => { const file = `![](/foobar/bad.svg)` const html = `<p><img src="/foobar/bad.svg"></p>` const render = renderFactory({ localUrlToLocalPath: [ '/foobar', testFilesDir, ], }) return render(file).then(vfile => { expect(firstMsg(vfile)).toMatch('mime type, not SVG either') expect(r(vfile.contents)).toBe(html) }) }) test('ignores nonexistent local files', () => { const file = `![](/does-not-exist)` const html = `<p><img src="/does-not-exist"></p>` const render = renderFactory({ localUrlToLocalPath: ['', `${__dirname}`], }) return render(file).then(vfile => { // XXX probably okay but a bit dirty expect(firstMsg(vfile)).toMatch('ENOENT: no such file or directory, open ') expect(r(vfile.contents)).toBe(html) }) }) test('ignores empty local files', () => { const file = `![](/empty)` const html = `<p><img src="/empty"></p>` const render = renderFactory({ localUrlToLocalPath: ['', testFilesDir], }) return render(file).then(vfile => { expect(firstMsg(vfile)).toMatch('Empty file: ') expect(r(vfile.contents)).toBe(html) }) }) test('does not copy non-image files', () => { const file = `![](/foobar/wrong-mime.zip)` const html = `<p><img src="/foobar/wrong-mime.zip"></p>` const render = renderFactory({ localUrlToLocalPath: [ '/foobar', testFilesDir, ], }) return render(file).then(vfile => { expect(firstMsg(vfile)).toMatch('is not an image/ type') expect(r(vfile.contents)).toBe(html) }) }) test('does not copy dangerous local absolute URLs', () => { const file = `![](/../../../etc/shadow)` const html = `<p><img src="/../../../etc/shadow"></p>` const render = renderFactory({ localUrlToLocalPath: (localUrl) => { const localPath = __dirname.replace('__tests__', '__mock__') return `${localPath}/files${localUrl.slice(7)}` }, }) return render(file).then(vfile => { expect(firstMsg(vfile)).toMatch('Dangerous absolute image URL detected') expect(r(vfile.contents)).toBe(html) }) }) test('default nonexisting images', () => { const defaultImagePath = 'default.png' const file = `![](/home/img.png)` const html = `<p><img src="${downloadDestination}/${defaultImagePath}"></p>` const render = renderFactory({ defaultImagePath, localUrlToLocalPath: ['/', '/tmp/'], defaultOn: { invalidPath: true, }, }) return render(file).then(vfile => { expect(vfile.contents).toBe(html) }) }) test('reports 404', () => { const file = `![](http://localhost:27273/404/notfound)` const html = `<p><img src="http://localhost:27273/404/notfound"></p>` const render = renderFactory() return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( 'Received HTTP404 for: http://localhost:27273/404/notfound') expect(vfile.contents).toBe(html) }) }) test('default bad status code', () => { const defaultImagePath = 'default.png' const file = `![](http://localhost:27273/noimage.png)` const html = `<p><img src="${downloadDestination}/${defaultImagePath}"></p>` const render = renderFactory({ defaultImagePath, defaultOn: { statusCode: true, }, }) return render(file).then(vfile => { expect(vfile.contents).toBe(html) }) }) test('reports DNS resolution issues', () => { const file = `![](http://doesnotexist-kqwtkk78.com)` const html = `<p><img src="http://doesnotexist-kqwtkk78.com"></p>` const render = renderFactory({httpRequestTimeout: 500}) return render(file).then(vfile => { expect(firstMsg(vfile)).toContain( 'getaddrinfo ENOTFOUND doesnotexist-kqwtkk78.com') expect(vfile.contents).toBe(html) }) }) test('report timeout errors', () => { const blackholeAddr = '192.0.2.1' const file = `![](http://${blackholeAddr}:32764/bad)` const html = `<p><img src="http://${blackholeAddr}:32764/bad"></p>` const render = renderFactory({httpRequestTimeout: 500}) return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( `Request for http://${blackholeAddr}:32764/bad timed out`) expect(vfile.contents).toBe(html) }) }) test('report timeout errors - IPv6 unreachable', () => { const file = `![](http://example.com:32764/bad)` const html = `<p><img src="http://example.com:32764/bad"></p>` const render = renderFactory({httpRequestTimeout: 500}) return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( 'Request for http://example.com:32764/bad timed out') expect(vfile.contents).toBe(html) }) }) test('does not download large chunked streams', async () => { const file = `![](http://localhost:27273/stream-bomb)` const html = `<p><img src="http://localhost:27273/stream-bomb"></p>` const render = renderFactory() const error = 'File at http://localhost:27273/stream-bomb weighs more than 1000000' await noDownloads(async () => { const vfile = await render(file) expect(vfile.data.imageDir).toBe(undefined) expect(vfile.contents).toBe(html) expect(vfile.messages.length).toBe(1) expect(firstMsg(vfile)).toBe(error) }) }) test('rejects empty files', async () => { const file = `![](http://localhost:27273/empty)` const html = `<p><img src="http://localhost:27273/empty"></p>` const render = renderFactory() const error = 'File at http://localhost:27273/empty is empty' await noDownloads(async () => { const vfile = await render(file) expect(vfile.data.imageDir).toBe(undefined) expect(vfile.contents).toBe(html) expect(vfile.messages.length).toBe(1) expect(firstMsg(vfile)).toBe(error) }) }) test('does not download many times the same image', async () => { const file = dedent` ![](http://localhost:27273/ok.png) ![](http://localhost:27273/ok.png) ` const html = new RegExp(dedent` <p><img src="([^"]+)"> <img src="([^"]+)"></p> `) const render = renderFactory() return render(file) .then(vfile => { expect(vfile.messages.length).toBe(0) const match = vfile.contents.match(html) expect(match).toBeTruthy() expect(match[1]).toBe(match[2]) const downloadedFiles = fs.readdirSync(vfile.data.imageDir) expect(downloadedFiles.length).toBe(1) }) }) test('prevents local hosts', () => { const file = `![](http://192.168.2.3:27273/ok.png)` const html = `<p><img src="http://192.168.2.3:27273/ok.png"></p>` const render = renderFactory({ disallowLocal: true, }) return render(file).then(vfile => { expect(firstMsg(vfile)).toBe( 'IP resolved in a forbidden range') expect(vfile.contents).toBe(html) }) }) }) ================================================ FILE: packages/remark-images-download/dist/index.js ================================================ "use strict"; const dns = require('dns'); const fs = require('fs'); const http = require('http'); const https = require('https'); const path = require('path'); const { promisify } = require('util'); const { Transform } = require('stream'); const { URL } = require('url'); const { Address4, Address6 } = require('ip-address'); const FileType = require('file-type'); const isSvg = require('is-svg'); const shortid = require('shortid'); const visit = require('unist-util-visit'); const rimraf = require('rimraf'); const isImage = headers => { return headers['content-type'].substring(0, 6) === 'image/'; }; const getSize = (headers = {}) => { const size = parseInt(headers['content-length'], 10); if (Number.isNaN(size)) { return 0; } return size; }; const mkdir = path => new Promise((resolve, reject) => { fs.mkdir(path, err => { if (err) reject(new Error(`Failed to create dir ${path}`)); resolve(); }); }); const checkFileType = async (name, data) => { if (!data.length) { throw new Error(`Empty file: ${name}`); } return FileType.fromBuffer(data).catch(() => {}).then((type = { mime: '' }) => { if (!type.mime || type.mime === 'application/xml') { if (!isSvg(data.toString())) { return Promise.reject(new Error(`Could not detect ${name} mime type, not SVG either`)); } } else if (type.mime.slice(0, 6) !== 'image/') { return Promise.reject(new Error(`Detected mime of local file '${name}' is not an image/ type`)); } return Promise.resolve(); }); }; // Creates a Transform stream which raises an error if the file type // is wrong or if the file is not a image. const makeValidatorStream = (fileName, maxSize) => { let firstChunk = true; let totalSize = 0; return new Transform({ flush(cb) { if (totalSize === 0) { cb(new Error(`File at ${fileName} is empty`)); return; } cb(null); }, transform(chunk, encoding, cb) { totalSize += chunk.length; if (maxSize && maxSize < totalSize) { cb(new Error(`File at ${fileName} weighs more than ${maxSize}`)); return; } if (firstChunk) { checkFileType(fileName, chunk).then(() => { firstChunk = false; cb(null, chunk); }).catch(error => { cb(error); }); } else { cb(null, chunk); } } }); }; const FORBIDDEN_IPV4 = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '192.18.0.0/15'].map(a => new Address4(a)); const FORBIDDEN_IPV6 = ['fc0::/7', 'fe80::/10'].map(a => new Address6(a)); const checkHost = async rawUrl => { const url = new URL(rawUrl); const unbracketedHost = url.hostname.replace(/^\[/, '').replace(/\]$/, ''); // Check for IP in hostname let ipv4 = Address4.isValid(url.hostname) && url.hostname; let ipv6 = Address6.isValid(unbracketedHost) && unbracketedHost; // Try to resolve hostname if (!ipv4 && !ipv6) { const { address, family } = await promisify(dns.lookup)(url.hostname); if (family === 4) { ipv4 = address; } else { ipv6 = address; } } const ipv4Resolved = Boolean(ipv4); const ipv6Resolved = Boolean(ipv6); // Match forbidden ranges if (ipv4Resolved) { ipv4 = new Address4(ipv4); ipv4 = FORBIDDEN_IPV4.reduce((acc, cur) => acc && !ipv4.isInSubnet(cur), true) && ipv4; } if (ipv6Resolved) { ipv6 = new Address6(ipv6); // IPv6to4 addresses are handled separately if (ipv6.is6to4()) { const ipv6to4 = new Address4(ipv6.to4()); ipv6 = FORBIDDEN_IPV4.reduce((acc, cur) => acc && !ipv6to4.isInSubnet(cur), true) && ipv6; } else { ipv6 = FORBIDDEN_IPV6.reduce((acc, cur) => acc && !ipv6.isInSubnet(cur), true) && ipv6; } } if (ipv4Resolved && !ipv4 || ipv6Resolved && !ipv6) { throw new Error('IP resolved in a forbidden range'); } return rawUrl; }; function plugin({ disabled = false, maxFileSize = 1000000, dirSizeLimit = 10000000, downloadDestination = '/tmp', defaultImagePath = false, defaultOn = { statusCode: false, mimeType: false, fileTooBig: false, invalidPath: false }, localUrlToLocalPath, httpRequestTimeout = 5000 // in milliseconds } = {}) { // Sends an HTTP request, checks headers and resolves a readable stream // if headers are valid. // Rejects with an error if headers are invalid. const initDownload = url => new Promise((resolve, reject) => { const packageInfo = require('../package.json'); const parsedUrl = new URL(url); const proto = parsedUrl.protocol === 'https:' ? https : http; const reqOptions = { timeout: httpRequestTimeout, // Websites may refuse connection if there is no User-Agent // (see for instance https://meta.wikimedia.org/wiki/User-Agent_policy) headers: { 'User-Agent': `${packageInfo.name} bot/${packageInfo.version} (${packageInfo.repository.url})` } }; const req = proto.get(parsedUrl, reqOptions, res => { const { headers, statusCode } = res; let error; const fileSize = getSize(headers); if (statusCode !== 200) { error = new Error(`Received HTTP${statusCode} for: ${url}`); error.replaceWithDefault = defaultOn && defaultOn.statusCode; } else if (!isImage(headers)) { error = new Error(`Content-Type of ${url} is not an image/ type`); error.replaceWithDefault = defaultOn && defaultOn.mimeType; } else if (maxFileSize && fileSize > maxFileSize) { error = new Error(`File at ${url} weighs ${headers['content-length']}, ` + `max size is ${maxFileSize}`); error.replaceWithDefault = defaultOn && defaultOn.fileTooBig; } if (error) { req.destroy(); res.resume(); reject(error); return; } resolve(res); }); req.on('timeout', () => { req.destroy(); reject(new Error(`Request for ${url} timed out`)); }); req.on('error', err => { if (err.errors) { const errorCodes = err.errors.map(e => e.code); // Node > 18 issues two requests: IPv4 and IPv6, timeout is // not handled by default if one of them throws unreachable if (errorCodes.length === 2 && errorCodes.includes('ETIMEDOUT') && errorCodes.includes('ENETUNREACH')) { req.emit('timeout'); } } reject(err); }); }); const checkAndCopy = async (from, to) => { const data = await promisify(fs.readFile)(from).catch(e => { e.replaceWithDefault = defaultOn && defaultOn.invalidPath; throw e; }); await checkFileType(from, data); try { await promisify(fs.copyFile)(from, to); } catch (err) { throw new Error(`Failed to copy ${from} to ${to}`); } }; const downloadAndSave = (node, sourceUrl, httpResponse, destinationPath) => new Promise((resolve, reject) => httpResponse.on('error', function (error) { reject(error); httpResponse.destroy(error); }).pipe(makeValidatorStream(sourceUrl, maxFileSize)).on('error', function (error) { reject(error); httpResponse.destroy(error); }).pipe(fs.createWriteStream(destinationPath)).on('error', function (error) { reject(error); httpResponse.destroy(error); }).on('close', e => { resolve(); })); const doDownloadTasks = async tasks => { await Promise.all(tasks.map(task => checkHost(task.url).then(initDownload).then(res => { task.res = res; }, error => { task.error = error; }))); if (dirSizeLimit) { let totalSize = 0; for (const task of tasks) { if (task.error) { continue; } const fileSize = getSize(task.res.headers); if (totalSize + fileSize >= dirSizeLimit) { const e = new Error(`Cannot download ${task.url} because destination ` + 'directory reached size limit'); task.error = e; task.res.destroy(e); } else { totalSize += fileSize; } } } await Promise.all(tasks.map(task => { if (!task.error) { return downloadAndSave(task.node, task.url, task.res, task.destination).catch(error => { task.error = error; }); } else { return null; } })); }; const doLocalCopyTasks = tasks => Promise.all(tasks.map(task => { if (task.localSourcePath.includes('../')) { task.error = new Error(`Dangerous absolute image URL detected: ${task.localSourcePath}`); task.error.replaceWithDefault = defaultOn && defaultOn.invalidPath; // eslint-disable-next-line array-callback-return return; } return checkAndCopy(task.localSourcePath, task.destination).catch(error => { task.error = error; }); })); return async function transform(tree, vfile) { if (disabled) return; // images are downloaded to destinationPath const destinationPath = path.join(downloadDestination, shortid.generate()); // allow to fallback when image is not found const defaultImageDestination = defaultImagePath ? path.join(downloadDestination, defaultImagePath) : false; let downloadTasks = []; let localCopyTasks = []; visit(tree, 'image', async node => { const { url, position } = node; // Empty URL make nasty error messages, so ignore them if (!url) { vfile.message('URL is empty', position); return; } let parsedURI; try { parsedURI = new URL(url); } catch (error) { try { // If the URL was invalid, it might be a local file parsedURI = new URL(url, 'file://'); } catch (_) { vfile.message(`Invalid URL: ${url}`, position, url); return; } } const extension = path.extname(parsedURI.pathname); const filename = `${shortid.generate()}${extension}`; const destination = path.join(destinationPath, filename); if (!parsedURI.host) { let localPath; if (typeof localUrlToLocalPath === 'function') { localPath = localUrlToLocalPath(url); } else if (Array.isArray(localUrlToLocalPath) && localUrlToLocalPath.length === 2) { const [from, to] = localUrlToLocalPath; localPath = url.replace(new RegExp(`^${from}`), to); } else { return; } localCopyTasks.push({ node, url, destination, localSourcePath: localPath }); return; } if (!['http:', 'https:'].includes(parsedURI.protocol)) { vfile.message(`Protocol '${parsedURI.protocol}' not allowed.`, position, url); return; } downloadTasks.push({ node, url, destination }); }); // Group by URL in order to download each file only once const groupTasksByUrl = tasks => { const map = new Map(); for (const task of tasks) { const otherTasks = map.get(task.url) || []; map.set(task.url, otherTasks.concat([task])); } return Array.from(map.values()).map(taskGroup => Object.assign({}, taskGroup[0], { nodes: taskGroup.map(t => t.node) })); }; downloadTasks = groupTasksByUrl(downloadTasks); localCopyTasks = groupTasksByUrl(localCopyTasks); const tasks = downloadTasks.concat(localCopyTasks); if (!tasks.length) { return tree; } let successfulTasks = []; await mkdir(destinationPath); try { await Promise.all([doDownloadTasks(downloadTasks), doLocalCopyTasks(localCopyTasks)]); const failedTasks = tasks.filter(t => t.error); successfulTasks = tasks.filter(t => !t.error); for (const task of failedTasks) { for (const node of task.nodes) { // mutates the AST even in case of error if requested if (defaultImageDestination && task.error.replaceWithDefault) { node.url = defaultImageDestination; } vfile.message(task.error, node.position, task.url); } } for (const task of successfulTasks) { for (const node of task.nodes) { // mutates the AST! node.url = task.destination; } } } catch (err) { vfile.message(err); await promisify(rimraf)(destinationPath); } if (successfulTasks.length) { vfile.data.imageDir = destinationPath; } else { await promisify(rimraf)(destinationPath); } return tree; }; } module.exports = plugin; ================================================ FILE: packages/remark-images-download/package.json ================================================ { "name": "remark-images-download", "version": "3.0.5", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-images-download", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "clone": "^2.1.2", "file-type": "^14.6.2", "ip-address": "^8.1.0", "is-svg": "~4.2.1", "read-chunk": "^3.2.0", "rimraf": "^3.0.2", "shortid": "^2.2.15", "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/remark-images-download/src/index.js ================================================ const dns = require('dns') const fs = require('fs') const http = require('http') const https = require('https') const path = require('path') const { promisify } = require('util') const { Transform } = require('stream') const { URL } = require('url') const { Address4, Address6 } = require('ip-address') const FileType = require('file-type') const isSvg = require('is-svg') const shortid = require('shortid') const visit = require('unist-util-visit') const rimraf = require('rimraf') const isImage = (headers) => { return (headers['content-type'].substring(0, 6) === 'image/') } const getSize = (headers = {}) => { const size = parseInt(headers['content-length'], 10) if (Number.isNaN(size)) { return 0 } return size } const mkdir = (path) => new Promise((resolve, reject) => { fs.mkdir(path, (err) => { if (err) reject(new Error(`Failed to create dir ${path}`)) resolve() }) }) const checkFileType = async (name, data) => { if (!data.length) { throw new Error(`Empty file: ${name}`) } return FileType.fromBuffer(data) .catch(() => {}) .then((type = { mime: '' }) => { if (!type.mime || type.mime === 'application/xml') { if (!isSvg(data.toString())) { return Promise.reject(new Error(`Could not detect ${name} mime type, not SVG either`)) } } else if (type.mime.slice(0, 6) !== 'image/') { return Promise.reject(new Error( `Detected mime of local file '${name}' is not an image/ type`)) } return Promise.resolve() }) } // Creates a Transform stream which raises an error if the file type // is wrong or if the file is not a image. const makeValidatorStream = (fileName, maxSize) => { let firstChunk = true let totalSize = 0 return new Transform({ flush (cb) { if (totalSize === 0) { cb(new Error(`File at ${fileName} is empty`)) return } cb(null) }, transform (chunk, encoding, cb) { totalSize += chunk.length if (maxSize && maxSize < totalSize) { cb(new Error(`File at ${fileName} weighs more than ${maxSize}`)) return } if (firstChunk) { checkFileType(fileName, chunk) .then(() => { firstChunk = false cb(null, chunk) }) .catch((error) => { cb(error) }) } else { cb(null, chunk) } } }) } const FORBIDDEN_IPV4 = [ '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '192.18.0.0/15' ].map(a => new Address4(a)) const FORBIDDEN_IPV6 = [ 'fc0::/7', 'fe80::/10' ].map(a => new Address6(a)) const checkHost = async rawUrl => { const url = new URL(rawUrl) const unbracketedHost = url.hostname.replace(/^\[/, '').replace(/\]$/, '') // Check for IP in hostname let ipv4 = Address4.isValid(url.hostname) && url.hostname let ipv6 = Address6.isValid(unbracketedHost) && unbracketedHost // Try to resolve hostname if (!ipv4 && !ipv6) { const { address, family } = await promisify(dns.lookup)(url.hostname) if (family === 4) { ipv4 = address } else { ipv6 = address } } const ipv4Resolved = Boolean(ipv4) const ipv6Resolved = Boolean(ipv6) // Match forbidden ranges if (ipv4Resolved) { ipv4 = new Address4(ipv4) ipv4 = FORBIDDEN_IPV4.reduce((acc, cur) => acc && !ipv4.isInSubnet(cur), true) && ipv4 } if (ipv6Resolved) { ipv6 = new Address6(ipv6) // IPv6to4 addresses are handled separately if (ipv6.is6to4()) { const ipv6to4 = new Address4(ipv6.to4()) ipv6 = FORBIDDEN_IPV4.reduce((acc, cur) => acc && !ipv6to4.isInSubnet(cur), true) && ipv6 } else { ipv6 = FORBIDDEN_IPV6.reduce((acc, cur) => acc && !ipv6.isInSubnet(cur), true) && ipv6 } } if ((ipv4Resolved && !ipv4) || (ipv6Resolved && !ipv6)) { throw new Error('IP resolved in a forbidden range') } return rawUrl } function plugin ({ disabled = false, maxFileSize = 1000000, dirSizeLimit = 10000000, downloadDestination = '/tmp', defaultImagePath = false, defaultOn = { statusCode: false, mimeType: false, fileTooBig: false, invalidPath: false }, localUrlToLocalPath, httpRequestTimeout = 5000 // in milliseconds } = {}) { // Sends an HTTP request, checks headers and resolves a readable stream // if headers are valid. // Rejects with an error if headers are invalid. const initDownload = url => new Promise((resolve, reject) => { const packageInfo = require('../package.json') const parsedUrl = new URL(url) const proto = parsedUrl.protocol === 'https:' ? https : http const reqOptions = { timeout: httpRequestTimeout, // Websites may refuse connection if there is no User-Agent // (see for instance https://meta.wikimedia.org/wiki/User-Agent_policy) headers: { 'User-Agent': `${packageInfo.name} bot/${packageInfo.version} (${ packageInfo.repository.url})` } } const req = proto.get(parsedUrl, reqOptions, res => { const { headers, statusCode } = res let error const fileSize = getSize(headers) if (statusCode !== 200) { error = new Error(`Received HTTP${statusCode} for: ${url}`) error.replaceWithDefault = defaultOn && defaultOn.statusCode } else if (!isImage(headers)) { error = new Error(`Content-Type of ${url} is not an image/ type`) error.replaceWithDefault = defaultOn && defaultOn.mimeType } else if (maxFileSize && fileSize > maxFileSize) { error = new Error( `File at ${url} weighs ${headers['content-length']}, ` + `max size is ${maxFileSize}` ) error.replaceWithDefault = defaultOn && defaultOn.fileTooBig } if (error) { req.destroy() res.resume() reject(error) return } resolve(res) }) req.on('timeout', () => { req.destroy() reject(new Error(`Request for ${url} timed out`)) }) req.on('error', err => { if (err.errors) { const errorCodes = err.errors.map(e => e.code) // Node > 18 issues two requests: IPv4 and IPv6, timeout is // not handled by default if one of them throws unreachable if (errorCodes.length === 2 && errorCodes.includes('ETIMEDOUT') && errorCodes.includes('ENETUNREACH')) { req.emit('timeout') } } reject(err) }) }) const checkAndCopy = async (from, to) => { const data = await promisify(fs.readFile)(from) .catch(e => { e.replaceWithDefault = defaultOn && defaultOn.invalidPath throw e }) await checkFileType(from, data) try { await promisify(fs.copyFile)(from, to) } catch (err) { throw new Error(`Failed to copy ${from} to ${to}`) } } const downloadAndSave = (node, sourceUrl, httpResponse, destinationPath) => new Promise((resolve, reject) => httpResponse .on('error', function (error) { reject(error) httpResponse.destroy(error) }) .pipe(makeValidatorStream(sourceUrl, maxFileSize)) .on('error', function (error) { reject(error) httpResponse.destroy(error) }) .pipe(fs.createWriteStream(destinationPath)) .on('error', function (error) { reject(error) httpResponse.destroy(error) }) .on('close', e => { resolve() }) ) const doDownloadTasks = async tasks => { await Promise.all(tasks.map(task => checkHost(task.url) .then(initDownload) .then( res => { task.res = res }, error => { task.error = error } ) )) if (dirSizeLimit) { let totalSize = 0 for (const task of tasks) { if (task.error) { continue } const fileSize = getSize(task.res.headers) if ((totalSize + fileSize) >= dirSizeLimit) { const e = new Error(`Cannot download ${task.url} because destination ` + 'directory reached size limit') task.error = e task.res.destroy(e) } else { totalSize += fileSize } } } await Promise.all(tasks.map(task => { if (!task.error) { return downloadAndSave(task.node, task.url, task.res, task.destination) .catch(error => { task.error = error }) } else { return null } })) } const doLocalCopyTasks = tasks => Promise.all(tasks.map(task => { if (task.localSourcePath.includes('../')) { task.error = new Error(`Dangerous absolute image URL detected: ${task.localSourcePath}`) task.error.replaceWithDefault = defaultOn && defaultOn.invalidPath // eslint-disable-next-line array-callback-return return } return checkAndCopy(task.localSourcePath, task.destination) .catch(error => { task.error = error }) })) return async function transform (tree, vfile) { if (disabled) return // images are downloaded to destinationPath const destinationPath = path.join(downloadDestination, shortid.generate()) // allow to fallback when image is not found const defaultImageDestination = defaultImagePath ? path.join(downloadDestination, defaultImagePath) : false let downloadTasks = [] let localCopyTasks = [] visit(tree, 'image', async node => { const { url, position } = node // Empty URL make nasty error messages, so ignore them if (!url) { vfile.message('URL is empty', position) return } let parsedURI try { parsedURI = new URL(url) } catch (error) { try { // If the URL was invalid, it might be a local file parsedURI = new URL(url, 'file://') } catch (_) { vfile.message(`Invalid URL: ${url}`, position, url) return } } const extension = path.extname(parsedURI.pathname) const filename = `${shortid.generate()}${extension}` const destination = path.join(destinationPath, filename) if (!parsedURI.host) { let localPath if (typeof localUrlToLocalPath === 'function') { localPath = localUrlToLocalPath(url) } else if (Array.isArray(localUrlToLocalPath) && localUrlToLocalPath.length === 2) { const [from, to] = localUrlToLocalPath localPath = url.replace(new RegExp(`^${from}`), to) } else { return } localCopyTasks.push({ node, url, destination, localSourcePath: localPath }) return } if (!['http:', 'https:'].includes(parsedURI.protocol)) { vfile.message(`Protocol '${parsedURI.protocol}' not allowed.`, position, url) return } downloadTasks.push({ node, url, destination }) }) // Group by URL in order to download each file only once const groupTasksByUrl = tasks => { const map = new Map() for (const task of tasks) { const otherTasks = map.get(task.url) || [] map.set(task.url, otherTasks.concat([task])) } return Array.from(map.values()) .map(taskGroup => Object.assign( {}, taskGroup[0], { nodes: taskGroup.map(t => t.node) } ) ) } downloadTasks = groupTasksByUrl(downloadTasks) localCopyTasks = groupTasksByUrl(localCopyTasks) const tasks = downloadTasks.concat(localCopyTasks) if (!tasks.length) { return tree } let successfulTasks = [] await mkdir(destinationPath) try { await Promise.all([ doDownloadTasks(downloadTasks), doLocalCopyTasks(localCopyTasks) ]) const failedTasks = tasks.filter(t => t.error) successfulTasks = tasks.filter(t => !t.error) for (const task of failedTasks) { for (const node of task.nodes) { // mutates the AST even in case of error if requested if (defaultImageDestination && task.error.replaceWithDefault) { node.url = defaultImageDestination } vfile.message(task.error, node.position, task.url) } } for (const task of successfulTasks) { for (const node of task.nodes) { // mutates the AST! node.url = task.destination } } } catch (err) { vfile.message(err) await promisify(rimraf)(destinationPath) } if (successfulTasks.length) { vfile.data.imageDir = destinationPath } else { await promisify(rimraf)(destinationPath) } return tree } } module.exports = plugin ================================================ FILE: packages/remark-kbd/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-kbd/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-kbd/README.md ================================================ # remark-kbd [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax to handle keyboard keys. It adds a new node type to the [mdast][mdast] produced by [remark][remark]: `kbd` If you are using [rehype][rehype], the stringified HTML result will be `<kbd>`. ## Syntax ```markdown Hit ||enter|| twice to create a new paragraph. ``` ## AST (see [mdast][mdast] specification) `Kbd` ([`Parent`][parent]) represents a reference to a user. ```javascript interface Kbd <: Parent { type: "kbd"; } ``` For example, the following markdown: `||enter||` Yields: ```javascript { type: 'kbd', children: [{ type: 'text', value: 'enter' }] } ``` ## Rehype This plugin is compatible with [rehype][rehype]. `Kbd` mdast nodes will become `<kbd>contents</kbd>`. ## Installation [npm][npm]: ```bash npm install remark-kbd ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkKbd = require('remark-kbd') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkKbd) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-kbd/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-kbd [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark [rehype]: https://github.com/rehypejs/rehype [parent]: https://github.com/syntax-tree/unist#parent ================================================ FILE: packages/remark-kbd/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`allow different left-right characters 1`] = `"<p><kbd>CTRL</kbd>+<kbd>ALT</kbd>+<kbd>SUPPR</kbd></p>"`; exports[`allow non-pipe characters 1`] = `"<p><kbd>CTRL</kbd>, +<kbd>D</kbd></p>"`; exports[`parses kbd parses a big fixture 1`] = ` "<p>Blabla <kbd>ok</kbd> kxcvj <kbd>ok foo</kbd> sdff</p> <p>sdf |||| df</p> <p>sfdgs | | dfg || dgsg | qs</p> <p>With two pipes: ||key|| you'll get <kbd>key</kbd>.</p> <p>It can contain inline markdown:</p> <ul> <li><kbd>hell<a href=\\"#he\\"><del>o</del></a>?</kbd></li> </ul> <p>It cannot contain blocks:</p> <ul> <li><kbd>hello: [[secret]]?</kbd></li> </ul>" `; exports[`to markdown 1`] = ` "Blabla ||ok|| kxcvj ||ok foo|| sdff sdf |||| df sfdgs | | dfg || dgsg | qs With two pipes: \\\\||key|| you'll get ||key||. It can contain inline markdown: - ||hell[~~o~~](#he)?|| It cannot contain blocks: - ||hello: \\\\[[secret]]?|| " `; ================================================ FILE: packages/remark-kbd/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import remarkStringify from 'remark-stringify' import rehypeStringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import remarkCustomBlocks from '../../remark-custom-blocks' import plugin from '../src/' const render = text => unified() .use(reParse, { footnotes: true, }) .use(remarkCustomBlocks, { secret: 'spoiler', }) .use(plugin) .use(remark2rehype) .use(rehypeStringify) .processSync(text) const fixture = dedent` Blabla ||ok|| kxcvj ||ok foo|| sdff sdf |||| df sfdgs | | dfg || dgsg | qs With two pipes: \||key|| you'll get ||key||. It can contain inline markdown: * ||hell[~~o~~](#he)?|| It cannot contain blocks: * ||hello: [[secret]]?|| ` describe('parses kbd', () => { it('parses a big fixture', () => { const {contents} = render(fixture) expect(contents).toMatchSnapshot() }) it('escapes the start marker', () => { const {contents} = render(dedent` ||one|| \||escaped|| ||three|| \|||four|| ||five|| `) expect(contents).toContain('||escaped||') expect(contents).toContain('|<kbd>four</kbd>') }) }) test('allow non-pipe characters', () => { const {contents} = unified() .use(reParse) .use(plugin, {charLeft: '+', charRight: '+'}) .use(remark2rehype) .use(rehypeStringify) .processSync('++CTRL++, \\+++D++') expect(contents).toMatchSnapshot() }) test('allow different left-right characters', () => { const {contents} = unified() .use(reParse) .use(plugin, {charLeft: '[', charRight: ']'}) .use(remark2rehype) .use(rehypeStringify) .processSync('[[CTRL]]+[[ALT]]+[[SUPPR]]') expect(contents).toMatchSnapshot() }) test('to markdown', () => { const {contents} = unified() .use(reParse) .use(remarkStringify) .use(plugin) .processSync(fixture) expect(contents).toMatchSnapshot() }) ================================================ FILE: packages/remark-kbd/dist/index.js ================================================ "use strict"; const whitespace = require('is-whitespace-character'); const DEFAULT_LEFT = '|'; const DEFAULT_RIGHT = '|'; function plugin(config) { const CHAR_LEFT = config && config.charLeft || DEFAULT_LEFT; const CHAR_RIGHT = config && config.charRight || DEFAULT_RIGHT; const DOUBLE_LEFT = CHAR_LEFT + CHAR_LEFT; const DOUBLE_RIGHT = CHAR_RIGHT + CHAR_RIGHT; function locator(value, fromIndex) { const index = value.indexOf(DOUBLE_LEFT, fromIndex); return index; } function inlineTokenizer(eat, value, silent) { if (!this.options.gfm || value.substr(0, 2) !== DOUBLE_LEFT || value.substr(0, 4) === DOUBLE_LEFT + DOUBLE_RIGHT || whitespace(value.charAt(2))) { return; } let character = ''; let previous = ''; let preceding = ''; let subvalue = ''; let index = 1; const length = value.length; const now = eat.now(); now.column += 2; now.offset += 2; while (++index < length) { character = value.charAt(index); if (character === CHAR_RIGHT && previous === CHAR_RIGHT && (!preceding || !whitespace(preceding))) { /* istanbul ignore if - never used (yet) */ if (silent) return true; return eat(DOUBLE_LEFT + subvalue + DOUBLE_RIGHT)({ type: 'kbd', children: this.tokenizeInline(subvalue, now), data: { hName: 'kbd' } }); } subvalue += previous; preceding = previous; previous = character; } } inlineTokenizer.locator = locator; const Parser = this.Parser; // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers; const inlineMethods = Parser.prototype.inlineMethods; inlineTokenizers.kbd = inlineTokenizer; inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'kbd'); const Compiler = this.Compiler; // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors; visitors.kbd = function (node) { return `${DOUBLE_LEFT}${this.all(node).join('')}${DOUBLE_RIGHT}`; }; } } module.exports = plugin; ================================================ FILE: packages/remark-kbd/package.json ================================================ { "name": "remark-kbd", "version": "1.1.1", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-kbd", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "is-whitespace-character": "^1.0.4" } } ================================================ FILE: packages/remark-kbd/src/index.js ================================================ const whitespace = require('is-whitespace-character') const DEFAULT_LEFT = '|' const DEFAULT_RIGHT = '|' function plugin (config) { const CHAR_LEFT = (config && config.charLeft) || DEFAULT_LEFT const CHAR_RIGHT = (config && config.charRight) || DEFAULT_RIGHT const DOUBLE_LEFT = CHAR_LEFT + CHAR_LEFT const DOUBLE_RIGHT = CHAR_RIGHT + CHAR_RIGHT function locator (value, fromIndex) { const index = value.indexOf(DOUBLE_LEFT, fromIndex) return index } function inlineTokenizer (eat, value, silent) { if ( !this.options.gfm || (value.substr(0, 2) !== DOUBLE_LEFT) || (value.substr(0, 4) === (DOUBLE_LEFT + DOUBLE_RIGHT)) || whitespace(value.charAt(2)) ) { return } let character = '' let previous = '' let preceding = '' let subvalue = '' let index = 1 const length = value.length const now = eat.now() now.column += 2 now.offset += 2 while (++index < length) { character = value.charAt(index) if ( character === CHAR_RIGHT && previous === CHAR_RIGHT && (!preceding || !whitespace(preceding)) ) { /* istanbul ignore if - never used (yet) */ if (silent) return true return eat(DOUBLE_LEFT + subvalue + DOUBLE_RIGHT)({ type: 'kbd', children: this.tokenizeInline(subvalue, now), data: { hName: 'kbd' } }) } subvalue += previous preceding = previous previous = character } } inlineTokenizer.locator = locator const Parser = this.Parser // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers const inlineMethods = Parser.prototype.inlineMethods inlineTokenizers.kbd = inlineTokenizer inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'kbd') const Compiler = this.Compiler // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors visitors.kbd = function (node) { return `${DOUBLE_LEFT}${this.all(node).join('')}${DOUBLE_RIGHT}` } } } module.exports = plugin ================================================ FILE: packages/remark-numbered-footnotes/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-numbered-footnotes/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-numbered-footnotes/README.md ================================================ This plugin replaces the footnote references with a number sequence (starting from 1) in the same order as the footnote definitions (**not** the footnote references). Reordering the definitions (usually put at the end of the document in the Markdown source) will therefore let you reorder the sequence. This is useful if you want your footnotes to be superscript numbers without having to manually enter them while keeping the benefit of using strings that make sense to you in your Markdown source. # Warning If you are using this plugin, your project is most certainly relying on [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast). Run `npm ls mdast-util-to-hast` if you're unsure. Starting from `mdast-util-to-hast@6.0.0`, the [footnote order changed](https://github.com/syntax-tree/mdast-util-to-hast/commit/fd38c45421bbec497f56e5c624eb8652d3a3bba4). Before `6.0.0`, footnotes were following the order in which they were defined, starting from `6.0.0` they follow the order of the references. ```md a[^first_footnote_reference] b[^`b` second footnote reference but first footnote definition] c[^last_footnote_reference] [^last_footnote_reference]: `c` last footnote reference but second footnote definition [^first_footnote_reference]: `a` first footnote reference but last footnote definition ``` Before `6.0.0`: ![image](https://user-images.githubusercontent.com/2022803/73589529-7207e980-44d7-11ea-8c67-cd8a20d961fe.png) After `6.0.0`: ![image](https://user-images.githubusercontent.com/2022803/73589535-7cc27e80-44d7-11ea-90e0-3e0e0dbac87c.png) In the HTML ordered list, the list items 1/2/3 don't match the footnote references numbers anymore. To avoid this issue you can either use older versions of your dependencies (not recommended) or visit the HAST tree after `mdast-util-to-hast` to fix the footnote orders (recommended). [Here is an example](https://github.com/zestedesavoir/zmarkdown/blob/7edd73057aba4eba52600106c6f8511619f045bd/packages/zmarkdown/common.js#L175-L206). This way the above markdown will always generate HTML with matching footnote list items and footnote numbered references as can be seen below: ![image](https://user-images.githubusercontent.com/2022803/73589529-7207e980-44d7-11ea-8c67-cd8a20d961fe.png) ================================================ FILE: packages/remark-numbered-footnotes/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`{"gfm":false,"commonmark":false} footnote-split 1`] = ` "<p>a<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup>b<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup>c<sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-2\\">first def<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-1\\">second def<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">third def<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":false} footnotes 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">6</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":false} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":false} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":false} regression-1 1`] = ` "<p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">MyNote<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":false} regression-2 1`] = ` "<p>1 <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> 2 <sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> 3 <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 5 <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">alpha bravo one<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-2\\">alpha bravo two<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">alpha bravo third<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-5\\">foo<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-4\\">alpha bravo fourth<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":true} footnote-split 1`] = ` "<p>a<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup>b<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup>c<sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-2\\">first def<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-1\\">second def<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">third def<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":true} footnotes 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">6</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":true} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":true} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":true} regression-1 1`] = ` "<p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">MyNote<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":false,"commonmark":true} regression-2 1`] = ` "<p>1 <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> 2 <sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> 3 <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 5 <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">alpha bravo one<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-2\\">alpha bravo two<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">alpha bravo third<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-5\\">foo<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-4\\">alpha bravo fourth<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":false} footnote-split 1`] = ` "<p>a<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup>b<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup>c<sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-2\\">first def<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-1\\">second def<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">third def<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":false} footnotes 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">6</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":false} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":false} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":false} regression-1 1`] = ` "<p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">MyNote<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":false} regression-2 1`] = ` "<p>1 <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> 2 <sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> 3 <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 5 <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">alpha bravo one<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-2\\">alpha bravo two<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">alpha bravo third<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-5\\">foo<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-4\\">alpha bravo fourth<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":true} footnote-split 1`] = ` "<p>a<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup>b<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup>c<sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-2\\">first def<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-1\\">second def<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">third def<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":true} footnotes 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">6</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":true} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":true} footnotes with customized labelTemplate 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup> or two<sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">[2]</a></sup> or more <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">[3]</a></sup> <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">[4]</a></sup> <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">[5]</a></sup>. One again: <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">[1]</a></sup></p> <p>Also a reference that does not exist<sup id=\\"fnref-6\\"><a href=\\"#fn-6\\" class=\\"footnote-ref\\">[6]</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-2\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-3\\"> <p>A simple oneliner.<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-4\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></p> </li> <li id=\\"fn-5\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></p> </li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":true} regression-1 1`] = ` "<p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <p>a<sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup><sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">MyNote<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; exports[`{"gfm":true,"commonmark":true} regression-2 1`] = ` "<p>1 <sup id=\\"fnref-1\\"><a href=\\"#fn-1\\" class=\\"footnote-ref\\">1</a></sup> 2 <sup id=\\"fnref-2\\"><a href=\\"#fn-2\\" class=\\"footnote-ref\\">2</a></sup> 3 <sup id=\\"fnref-3\\"><a href=\\"#fn-3\\" class=\\"footnote-ref\\">3</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 4 <sup id=\\"fnref-5\\"><a href=\\"#fn-5\\" class=\\"footnote-ref\\">5</a></sup> 5 <sup id=\\"fnref-4\\"><a href=\\"#fn-4\\" class=\\"footnote-ref\\">4</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1\\">alpha bravo one<a href=\\"#fnref-1\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-2\\">alpha bravo two<a href=\\"#fnref-2\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-3\\">alpha bravo third<a href=\\"#fnref-3\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-5\\">foo<a href=\\"#fnref-5\\" class=\\"footnote-backref\\">↩</a></li> <li id=\\"fn-4\\">alpha bravo fourth<a href=\\"#fnref-4\\" class=\\"footnote-backref\\">↩</a></li> </ol> </div>" `; ================================================ FILE: packages/remark-numbered-footnotes/__tests__/fixtures/footnote-split.fixture.md ================================================ a[^first]b^[second def]c[^third] [^first]: first def [^third]: third def ================================================ FILE: packages/remark-numbered-footnotes/__tests__/fixtures/footnotes.fixture.md ================================================ This is the body with a footnote[^foo] or two[^bar] or more [^baz] [^qux] [^fiji]. One again: [^foo] Also a reference that does not exist[^nope]. [^foo]: Footnote that ends with a list: * item 1 * item 2 [^bar]: > This footnote is a blockquote. [^baz]: A simple oneliner. [^qux]: A footnote with multiple paragraphs. Paragraph two. [^fiji]: First line of first paragraph. Second line of first paragraph is not intended. Nor is third... ================================================ FILE: packages/remark-numbered-footnotes/__tests__/fixtures/regression-1.fixture.md ================================================ a[^foo] a[^foo][^foo] a[^foo][^foo][^foo] a[^foo][^foo][^foo][^foo] a[^foo][^foo][^foo][^foo][^foo] a[^foo][^foo][^foo][^foo][^foo][^foo] [^foo]: MyNote ================================================ FILE: packages/remark-numbered-footnotes/__tests__/fixtures/regression-2.fixture.md ================================================ 1 ^[alpha bravo one] 2 ^[alpha bravo two] 3 ^[alpha bravo third] 4 [^w] 4 [^w] 5 ^[alpha bravo fourth] [^w]: foo ================================================ FILE: packages/remark-numbered-footnotes/__tests__/index.js ================================================ import {readdirSync as directory, readFileSync as file} from 'fs' import {join} from 'path' import unified from 'unified' import reParse from 'remark-parse' import footnotes from 'remark-footnotes' import stringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' const base = join(__dirname, 'fixtures') const specs = directory(base).reduce((tests, contents) => { const parts = contents.split('.') if (!tests[parts[0]]) { tests[parts[0]] = {} } tests[parts[0]][parts[1]] = file(join(base, contents), 'utf-8') return tests }, {}) const configs = [ { gfm: true, commonmark: false, }, { gfm: false, commonmark: false, }, { gfm: false, commonmark: true, }, { gfm: true, commonmark: true, }, ] configs.forEach(config => { describe(JSON.stringify(config), () => { test('footnotes', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(require('../src')) .use(remark2rehype) .use(stringify) .processSync(specs['footnotes'].fixture) expect(contents).toMatchSnapshot() }) test('regression-1', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(require('../src')) .use(remark2rehype) .use(stringify) .processSync(specs['regression-1'].fixture) expect(contents).toMatchSnapshot() }) test('regression-2', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(require('../src')) .use(remark2rehype) .use(stringify) .processSync(specs['regression-2'].fixture) expect(contents).toMatchSnapshot() }) test('footnote-split', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(require('../src')) .use(remark2rehype) .use(stringify) .processSync(specs['footnote-split'].fixture) expect(contents).toMatchSnapshot() }) test('footnotes with customized labelTemplate', () => { const {contents} = unified() .use(reParse, config) .use(footnotes, {inlineNotes: true}) .use(require('../src'), {labelPrefix: '[', labelSuffix: ']'}) .use(remark2rehype) .use(stringify) .processSync(specs['footnotes'].fixture) expect(contents).toMatchSnapshot() }) }) }) ================================================ FILE: packages/remark-numbered-footnotes/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); function plugin({ labelPrefix = '', labelSuffix = '' } = {}) { function transformer(tree) { const footnotes = {}; visit(tree, 'footnote', convert); visit(tree, 'footnoteDefinition', createIds(footnotes)); visit(tree, 'footnoteReference', replaceIds(footnotes)); } function createIds(footnotes) { return (node, index, parent) => { const identifier = node.identifier; if (!Object.prototype.hasOwnProperty.call(footnotes, identifier)) { footnotes[identifier] = Object.keys(footnotes).length + 1; } node.identifier = String(footnotes[identifier]); node.label = `${labelPrefix}${footnotes[identifier]}${labelSuffix}`; }; } function replaceIds(footnotes) { return (node, index, parent) => { const identifier = node.identifier; if (!Object.prototype.hasOwnProperty.call(footnotes, identifier)) { footnotes[identifier] = Object.keys(footnotes).length + 1; } node.identifier = String(footnotes[identifier]); node.label = `${labelPrefix}${footnotes[identifier]}${labelSuffix}`; }; } return transformer; } function convert(node, index, parent) { const id = autoId(node.position.start); const footnoteDefinition = { type: 'footnoteDefinition', identifier: id, children: [{ type: 'paragraph', children: node.children }] }; const footnoteReference = { type: 'footnoteReference', identifier: id }; parent.children.splice(index, 1, footnoteReference, footnoteDefinition); } function autoId(node) { const { line, column, offset } = node; return `l${line}c${column}o${offset}`; } module.exports = plugin; ================================================ FILE: packages/remark-numbered-footnotes/package.json ================================================ { "name": "remark-numbered-footnotes", "version": "3.1.1", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-numbered-footnotes", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/remark-numbered-footnotes/src/index.js ================================================ const visit = require('unist-util-visit') function plugin ({ labelPrefix = '', labelSuffix = '' } = {}) { function transformer (tree) { const footnotes = {} visit(tree, 'footnote', convert) visit(tree, 'footnoteDefinition', createIds(footnotes)) visit(tree, 'footnoteReference', replaceIds(footnotes)) } function createIds (footnotes) { return (node, index, parent) => { const identifier = node.identifier if (!Object.prototype.hasOwnProperty.call(footnotes, identifier)) { footnotes[identifier] = Object.keys(footnotes).length + 1 } node.identifier = String(footnotes[identifier]) node.label = `${labelPrefix}${footnotes[identifier]}${labelSuffix}` } } function replaceIds (footnotes) { return (node, index, parent) => { const identifier = node.identifier if (!Object.prototype.hasOwnProperty.call(footnotes, identifier)) { footnotes[identifier] = Object.keys(footnotes).length + 1 } node.identifier = String(footnotes[identifier]) node.label = `${labelPrefix}${footnotes[identifier]}${labelSuffix}` } } return transformer } function convert (node, index, parent) { const id = autoId(node.position.start) const footnoteDefinition = { type: 'footnoteDefinition', identifier: id, children: [{ type: 'paragraph', children: node.children }] } const footnoteReference = { type: 'footnoteReference', identifier: id } parent.children.splice(index, 1, footnoteReference, footnoteDefinition) } function autoId (node) { const { line, column, offset } = node return `l${line}c${column}o${offset}` } module.exports = plugin ================================================ FILE: packages/remark-ping/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-ping/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-ping/README.md ================================================ # remark-ping [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax such as `@someone` or `@**nick with spaces**` to create links such as `/member/profile/someone` to the corresponding user page if this user exists in your system. ## Default Syntax ```markdown @username @**nick with spaces** ``` ## AST (see [mdast][mdast] specification) `Ping` ([`Parent`][parent]) represents a reference to a user. ```javascript interface Ping <: Parent { type: "ping"; url: "member profile url"; username: "username"; } ``` ## rehype This plugin is compatible with [rehype][rehype]. `Ping` mdast nodes will become HTML links pointing to a customizable target, usually used to link to a user profile. ```md @foo ``` gives: ```html <a href="/custom/link/foo/" rel="nofollow" class="ping ping-link"> @<span class="ping-username">foo</span> </a> ``` Pings are handled a bit differently if they are already inside of a link: ```md [@foo](http://example.com) ``` gives: ```html <a href="http://example.com"> <span class="ping ping-in-link"> @<span class="ping-username">foo</span> </span> </a> ``` ## Installation [npm][npm]: ```bash npm install remark-ping ``` ## Usage ### Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkPing = require('remark-ping') ``` ### Usage: ```javascript unified() .use(remarkParse) .use(remarkPing, { pingUsername: (username) => true, userURL: (username) => `https://your.website.com/path/to/${username}` }) .use(remark2rehype) .use(stringify) ``` as you can see, `remark-ping` takes two mandatory options : - `pingUsername` is a function taking `username` as parameter and returning `true` if the user exists or should be pinged - If you want to parse any username without checking whether they exist or (like GitHub does), use a function always returning `true` (`() => true`) - When `pingUsername(username)` doesn't return `true`, the ping syntax is simply ignored and no AST `Ping` node gets created for this username - `userUrl` is a function taking `username` as parameter and returning a path or URL to this user profile or member page You can override the default parsing regexp, for example if you don't want to include `@**username with space**` by setting up the `usernameRegex` option: ```js .use(remarkPing, { pingUsername: (username) => true, userURL: (username) => `https://your.website.com/path/to/${username}`, usernameRegex: /[\s'"(,:<]?@(\w+)/, }) ``` ### Retrieving the usernames to ping Once the Markdown has been processed by this plugin, the output `vfile` contains a `ping` array in the `data` property. This array contains every username that should be ping, should you want your backend to generate notifications for these. ```js unified() .use(reParse) .use(plugin, {pingUsername, userURL}) .use(remark2rehype) .use(rehypeStringify) .process('@foo @bar') .then((vfile) => { console.log(vfile.data.ping.length === 2) // true console.log(vfile.data.ping[0] === 'foo') // true console.log(vfile.data.ping[1] === 'bar') // true return vfile }) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-ping/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-ping [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [rehype]: https://github.com/rehypejs/rehype [parent]: https://github.com/syntax-tree/unist#parent ================================================ FILE: packages/remark-ping/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`compiles to Markdown 1`] = ` "remark-ping: expected configuration to be passed: { pingUsername: (username) => bool, userURL: (username) => string }" `; exports[`fixture suite 0 compiles to HTML: h0 1`] = ` "<p>ping @Clem</p> <p>ping @<strong>FOO BAR</strong></p> <p>no ping @quxjhdshqjkhfyhefezhjzjhdsjlfjlsqjdfjhsd</p> <p>ping <a href=\\"http://example.com\\"><span class=\\"ping ping-in-link\\">@<span class=\\"ping-username\\">I AM CLEM</span></span></a></p> <p><a href=\\"/membres/voir/baz baz/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">baz baz</span></a></p>" `; exports[`fixture suite 0 compiles to Markdown: m0 1`] = ` "ping @Clem ping @**FOO BAR** no ping @quxjhdshqjkhfyhefezhjzjhdsjlfjlsqjdfjhsd ping [@**I AM CLEM**](http://example.com) @**baz baz** " `; exports[`fixture suite 0 parses: f0 1`] = ` Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 11, "line": 1, "offset": 10, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "text", "value": "ping @Clem", }, ], "position": Position { "end": Object { "column": 11, "line": 1, "offset": 10, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 7, "line": 3, "offset": 18, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 12, }, }, "type": "text", "value": "ping @", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 16, "line": 3, "offset": 27, }, "indent": Array [], "start": Object { "column": 9, "line": 3, "offset": 20, }, }, "type": "text", "value": "FOO BAR", }, ], "position": Position { "end": Object { "column": 18, "line": 3, "offset": 29, }, "indent": Array [], "start": Object { "column": 7, "line": 3, "offset": 18, }, }, "type": "strong", }, ], "position": Position { "end": Object { "column": 18, "line": 3, "offset": 29, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 12, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 50, "line": 5, "offset": 80, }, "indent": Array [], "start": Object { "column": 1, "line": 5, "offset": 31, }, }, "type": "text", "value": "no ping @quxjhdshqjkhfyhefezhjzjhdsjlfjlsqjdfjhsd", }, ], "position": Position { "end": Object { "column": 50, "line": 5, "offset": 80, }, "indent": Array [], "start": Object { "column": 1, "line": 5, "offset": 31, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 6, "line": 7, "offset": 87, }, "indent": Array [], "start": Object { "column": 1, "line": 7, "offset": 82, }, }, "type": "text", "value": "ping ", }, Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "I AM CLEM", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/I AM CLEM/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 21, "line": 7, "offset": 102, }, "indent": Array [], "start": Object { "column": 7, "line": 7, "offset": 88, }, }, "type": "ping", "url": "/membres/voir/I AM CLEM/", "username": "I AM CLEM", }, ], "position": Position { "end": Object { "column": 42, "line": 7, "offset": 123, }, "indent": Array [], "start": Object { "column": 6, "line": 7, "offset": 87, }, }, "title": null, "type": "link", "url": "http://example.com", }, ], "position": Position { "end": Object { "column": 42, "line": 7, "offset": 123, }, "indent": Array [], "start": Object { "column": 1, "line": 7, "offset": 82, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "baz baz", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/baz baz/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 13, "line": 9, "offset": 137, }, "indent": Array [], "start": Object { "column": 1, "line": 9, "offset": 125, }, }, "type": "ping", "url": "/membres/voir/baz baz/", "username": "baz baz", }, ], "position": Position { "end": Object { "column": 13, "line": 9, "offset": 137, }, "indent": Array [], "start": Object { "column": 1, "line": 9, "offset": 125, }, }, "type": "paragraph", }, ], "position": Object { "end": Object { "column": 13, "line": 9, "offset": 137, }, "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "root", } `; exports[`fixture suite 1 compiles to HTML: h1 1`] = ` "<h2>Test ping <a href=\\"/membres/voir/I AM CLEM/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">I AM CLEM</span></a></h2> <blockquote> <blockquote> <p>no metadata output <a href=\\"/membres/voir/I AM CLEM/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">I AM CLEM</span></a></p> </blockquote> </blockquote> <blockquote> <p>no metadata output <a href=\\"/membres/voir/I AM CLEM/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">I AM CLEM</span></a></p> </blockquote> <p>ping <a href=\\"/membres/voir/I AM CLEM/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">I AM CLEM</span></a></p> <p>ping <em><a href=\\"/membres/voir/I AM CLEM/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">I AM CLEM</span></a></em></p> <blockquote> <p>no metadata output <a href=\\"/membres/voir/I AM CLEM/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">I AM CLEM</span></a></p> </blockquote>" `; exports[`fixture suite 1 compiles to Markdown: m1 1`] = ` "## Test ping @**I AM CLEM** > > no metadata output @**I AM CLEM** > no metadata output @**I AM CLEM** ping @**I AM CLEM** ping _@**I AM CLEM**_ > no metadata output @**I AM CLEM** " `; exports[`fixture suite 1 parses: f1 1`] = ` Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 14, "line": 1, "offset": 13, }, "indent": Array [], "start": Object { "column": 4, "line": 1, "offset": 3, }, }, "type": "text", "value": "Test ping ", }, Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "I AM CLEM", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/I AM CLEM/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 28, "line": 1, "offset": 27, }, "indent": Array [], "start": Object { "column": 14, "line": 1, "offset": 13, }, }, "type": "ping", "url": "/membres/voir/I AM CLEM/", "username": "I AM CLEM", }, ], "depth": 2, "position": Position { "end": Object { "column": 28, "line": 1, "offset": 27, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "heading", }, Object { "children": Array [ Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 24, "line": 3, "offset": 52, }, "indent": Array [], "start": Object { "column": 5, "line": 3, "offset": 33, }, }, "type": "text", "value": "no metadata output ", }, Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "I AM CLEM", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/I AM CLEM/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 38, "line": 3, "offset": 66, }, "indent": Array [], "start": Object { "column": 24, "line": 3, "offset": 52, }, }, "type": "ping", "url": "/membres/voir/I AM CLEM/", "username": "I AM CLEM", }, ], "position": Position { "end": Object { "column": 38, "line": 3, "offset": 66, }, "indent": Array [], "start": Object { "column": 5, "line": 3, "offset": 33, }, }, "type": "paragraph", }, ], "position": Position { "end": Object { "column": 38, "line": 3, "offset": 66, }, "indent": Array [], "start": Object { "column": 3, "line": 3, "offset": 31, }, }, "type": "blockquote", }, ], "position": Position { "end": Object { "column": 38, "line": 3, "offset": 66, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 29, }, }, "type": "blockquote", }, Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 22, "line": 5, "offset": 89, }, "indent": Array [], "start": Object { "column": 3, "line": 5, "offset": 70, }, }, "type": "text", "value": "no metadata output ", }, Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "I AM CLEM", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/I AM CLEM/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 36, "line": 5, "offset": 103, }, "indent": Array [], "start": Object { "column": 22, "line": 5, "offset": 89, }, }, "type": "ping", "url": "/membres/voir/I AM CLEM/", "username": "I AM CLEM", }, ], "position": Position { "end": Object { "column": 36, "line": 5, "offset": 103, }, "indent": Array [], "start": Object { "column": 3, "line": 5, "offset": 70, }, }, "type": "paragraph", }, ], "position": Position { "end": Object { "column": 36, "line": 5, "offset": 103, }, "indent": Array [], "start": Object { "column": 1, "line": 5, "offset": 68, }, }, "type": "blockquote", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 6, "line": 7, "offset": 110, }, "indent": Array [], "start": Object { "column": 1, "line": 7, "offset": 105, }, }, "type": "text", "value": "ping ", }, Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "I AM CLEM", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/I AM CLEM/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 20, "line": 7, "offset": 124, }, "indent": Array [], "start": Object { "column": 6, "line": 7, "offset": 110, }, }, "type": "ping", "url": "/membres/voir/I AM CLEM/", "username": "I AM CLEM", }, ], "position": Position { "end": Object { "column": 20, "line": 7, "offset": 124, }, "indent": Array [], "start": Object { "column": 1, "line": 7, "offset": 105, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 6, "line": 9, "offset": 131, }, "indent": Array [], "start": Object { "column": 1, "line": 9, "offset": 126, }, }, "type": "text", "value": "ping ", }, Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "I AM CLEM", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/I AM CLEM/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 21, "line": 9, "offset": 146, }, "indent": Array [], "start": Object { "column": 7, "line": 9, "offset": 132, }, }, "type": "ping", "url": "/membres/voir/I AM CLEM/", "username": "I AM CLEM", }, ], "position": Position { "end": Object { "column": 22, "line": 9, "offset": 147, }, "indent": Array [], "start": Object { "column": 6, "line": 9, "offset": 131, }, }, "type": "emphasis", }, ], "position": Position { "end": Object { "column": 22, "line": 9, "offset": 147, }, "indent": Array [], "start": Object { "column": 1, "line": 9, "offset": 126, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 22, "line": 11, "offset": 170, }, "indent": Array [], "start": Object { "column": 3, "line": 11, "offset": 151, }, }, "type": "text", "value": "no metadata output ", }, Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "I AM CLEM", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/I AM CLEM/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 36, "line": 11, "offset": 184, }, "indent": Array [], "start": Object { "column": 22, "line": 11, "offset": 170, }, }, "type": "ping", "url": "/membres/voir/I AM CLEM/", "username": "I AM CLEM", }, ], "position": Position { "end": Object { "column": 36, "line": 11, "offset": 184, }, "indent": Array [], "start": Object { "column": 3, "line": 11, "offset": 151, }, }, "type": "paragraph", }, ], "position": Position { "end": Object { "column": 36, "line": 11, "offset": 184, }, "indent": Array [], "start": Object { "column": 1, "line": 11, "offset": 149, }, }, "type": "blockquote", }, ], "position": Object { "end": Object { "column": 36, "line": 11, "offset": 184, }, "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "root", } `; exports[`fixture suite 2 compiles to HTML: h2 1`] = ` "<p><a href=\\"/membres/voir/foo/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">foo</span></a> <a href=\\"/membres/voir/bar/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">bar</span></a></p> <p>@baz baz</p> <blockquote> <p><a href=\\"/membres/voir/baz baz/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">baz baz</span></a></p> </blockquote>" `; exports[`fixture suite 2 compiles to Markdown: m2 1`] = ` "@foo @bar @baz baz > @**baz baz** " `; exports[`fixture suite 2 parses: f2 1`] = ` Object { "children": Array [ Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "foo", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/foo/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 5, "line": 1, "offset": 4, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "ping", "url": "/membres/voir/foo/", "username": "foo", }, Object { "position": Position { "end": Object { "column": 6, "line": 1, "offset": 5, }, "indent": Array [], "start": Object { "column": 5, "line": 1, "offset": 4, }, }, "type": "text", "value": " ", }, Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "bar", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/bar/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 10, "line": 1, "offset": 9, }, "indent": Array [], "start": Object { "column": 6, "line": 1, "offset": 5, }, }, "type": "ping", "url": "/membres/voir/bar/", "username": "bar", }, ], "position": Position { "end": Object { "column": 10, "line": 1, "offset": 9, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 9, "line": 3, "offset": 19, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 11, }, }, "type": "text", "value": "@baz baz", }, ], "position": Position { "end": Object { "column": 9, "line": 3, "offset": 19, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 11, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "baz baz", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/baz baz/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 15, "line": 5, "offset": 35, }, "indent": Array [], "start": Object { "column": 3, "line": 5, "offset": 23, }, }, "type": "ping", "url": "/membres/voir/baz baz/", "username": "baz baz", }, ], "position": Position { "end": Object { "column": 15, "line": 5, "offset": 35, }, "indent": Array [], "start": Object { "column": 3, "line": 5, "offset": 23, }, }, "type": "paragraph", }, ], "position": Position { "end": Object { "column": 15, "line": 5, "offset": 35, }, "indent": Array [], "start": Object { "column": 1, "line": 5, "offset": 21, }, }, "type": "blockquote", }, ], "position": Object { "end": Object { "column": 15, "line": 5, "offset": 35, }, "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "root", } `; exports[`fixture suite 3 compiles to HTML: h3 1`] = ` "<p><a href=\\"/membres/voir/Moté/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">Moté</span></a> @Phigger</p> <p><a href=\\"/membres/voir/Phigger Moté/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">Phigger Moté</span></a></p> <p>@Digitals@m <a href=\\"/membres/voir/Digitals@m/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">Digitals@m</span></a></p> <p><a href=\\"/membres/voir/empty/\\" rel=\\"nofollow\\" class=\\"ping ping-link\\">@<span class=\\"ping-username\\">empty</span></a> @</p>" `; exports[`fixture suite 3 compiles to Markdown: m3 1`] = ` "@Moté @Phigger @**Phigger Moté** @Digitals@m @**Digitals@m** @empty @ " `; exports[`fixture suite 3 parses: f3 1`] = ` Object { "children": Array [ Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "Moté", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/Moté/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 6, "line": 1, "offset": 5, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "ping", "url": "/membres/voir/Moté/", "username": "Moté", }, Object { "position": Position { "end": Object { "column": 15, "line": 1, "offset": 14, }, "indent": Array [], "start": Object { "column": 6, "line": 1, "offset": 5, }, }, "type": "text", "value": " @Phigger", }, ], "position": Position { "end": Object { "column": 15, "line": 1, "offset": 14, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "Phigger Moté", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/Phigger Moté/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 18, "line": 3, "offset": 33, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 16, }, }, "type": "ping", "url": "/membres/voir/Phigger Moté/", "username": "Phigger Moté", }, ], "position": Position { "end": Object { "column": 18, "line": 3, "offset": 33, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 16, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 13, "line": 5, "offset": 47, }, "indent": Array [], "start": Object { "column": 1, "line": 5, "offset": 35, }, }, "type": "text", "value": "@Digitals@m ", }, Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "Digitals@m", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/Digitals@m/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 28, "line": 5, "offset": 62, }, "indent": Array [], "start": Object { "column": 13, "line": 5, "offset": 47, }, }, "type": "ping", "url": "/membres/voir/Digitals@m/", "username": "Digitals@m", }, ], "position": Position { "end": Object { "column": 28, "line": 5, "offset": 62, }, "indent": Array [], "start": Object { "column": 1, "line": 5, "offset": 35, }, }, "type": "paragraph", }, Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "empty", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/membres/voir/empty/", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 7, "line": 7, "offset": 70, }, "indent": Array [], "start": Object { "column": 1, "line": 7, "offset": 64, }, }, "type": "ping", "url": "/membres/voir/empty/", "username": "empty", }, Object { "position": Position { "end": Object { "column": 9, "line": 7, "offset": 72, }, "indent": Array [], "start": Object { "column": 7, "line": 7, "offset": 70, }, }, "type": "text", "value": " @", }, ], "position": Position { "end": Object { "column": 9, "line": 7, "offset": 72, }, "indent": Array [], "start": Object { "column": 1, "line": 7, "offset": 64, }, }, "type": "paragraph", }, ], "position": Object { "end": Object { "column": 9, "line": 7, "offset": 72, }, "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "root", } `; ================================================ FILE: packages/remark-ping/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import remarkStringify from 'remark-stringify' import remark2rehype from 'remark-rehype' import rehypeStringify from 'rehype-stringify' import plugin from '../src/' const mockUsernames = [ 'I AM CLEM', 'qux', 'foo', 'bar', 'baz baz', 'Moté', 'Phigger Moté', 'Digitals@m', 'empty', ] function pingUsername (username) { return mockUsernames.includes(username) } function userURL (username) { return `/membres/voir/${username}/` } const remark = text => unified() .use(reParse) .use(plugin, {pingUsername, userURL}) .parse(text) const toHTML = text => unified() .use(reParse) .use(plugin, {pingUsername, userURL}) .use(remark2rehype) .use(rehypeStringify) .process(text) const toMarkdown = text => unified() .use(reParse) .use(remarkStringify) .use(plugin, {pingUsername, userURL}) .processSync(text) .toString() const fixtures = [ dedent` ping @Clem ping @**FOO BAR** no ping @quxjhdshqjkhfyhefezhjzjhdsjlfjlsqjdfjhsd ping [@**I AM CLEM**](http://example.com) @**baz baz** `, dedent` ## Test ping @**I AM CLEM** > > no metadata output @**I AM CLEM** > no metadata output @**I AM CLEM** ping @**I AM CLEM** ping _@**I AM CLEM**_ > no metadata output @**I AM CLEM** `, dedent` @foo @bar @baz baz > @**baz baz** `, dedent` @Moté @Phigger @**Phigger Moté** @Digitals@m @**Digitals@m** @empty @ `, ] const pings = [ ['I AM CLEM', 'baz baz'], ['I AM CLEM', 'I AM CLEM', 'I AM CLEM'], ['foo', 'bar'], ['Moté', 'Phigger Moté', 'Digitals@m', 'empty'], ] fixtures.forEach((fixture, i) => { describe(`fixture suite ${i}`, () => { test('parses', () => { expect(remark(fixture)).toMatchSnapshot(`f${i}`) }) test('sets ping data on vfile', () => { return expect( toHTML(fixture).then(vfile => vfile.data.ping) ).resolves.toEqual(pings[i]) }) test('compiles to HTML', () => { return expect( toHTML(fixture).then(vfile => vfile.contents) ).resolves.toMatchSnapshot(`h${i}`) }) test('compiles to Markdown', () => { expect(toMarkdown(fixture)).toMatchSnapshot(`m${i}`) }) }) }) test('compiles to Markdown', () => { const toMarkdown = text => unified() .use(reParse) .use(remarkStringify) .use(plugin, { pingUsername: 12, userURL, }) .processSync(text) .toString() expect(() => toMarkdown(dedent` # foo @**I AM CLEM** `)).toThrowErrorMatchingSnapshot() }) test('do not create ping links in links', () => { return expect( toHTML(dedent` [foo @**I AM CLEM** bar](http://example.com) `).then(vfile => vfile.contents) ).resolves.toBe(dedent` <p><a href="http://example.com">foo <span class="ping ping-in-link">\ @<span class="ping-username">I AM CLEM</span></span> bar</a></p>`) }) ================================================ FILE: packages/remark-ping/dist/index.js ================================================ "use strict"; const visit = require('unist-util-visit'); const interruptPunctuation = [require('@unicode/unicode-13.0.0/Binary_Property/White_Space/code-points'), require('@unicode/unicode-13.0.0/General_Category/Close_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Final_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Initial_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Open_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Other_Punctuation/code-points')].flat(); const isInterrupt = c => interruptPunctuation.includes(c.charCodeAt(0)); const containsInterrupt = str => { for (let c = 0; c < str.length; c++) { const char = str.charAt(c); if (isInterrupt(char)) return true; } return false; }; const helpMsg = `remark-ping: expected configuration to be passed: { pingUsername: (username) => bool,\n userURL: (username) => string\n}`; module.exports = function plugin({ pingUsername, userURL, pingCharacter = '@', fencedStartSequence = '**', fencedEndSequence = '**' }) { if (typeof pingUsername !== 'function' || typeof userURL !== 'function') { throw new Error(helpMsg); } function inlineTokenizer(eat, value, silent) { let isFenced = false; let eaten = pingCharacter; let username = ''; let c = 1; /* istanbul ignore if - never used (yet) */ if (silent) return silent; if (value.charAt(0) !== pingCharacter) return; // Check if we have a fenced sequence if (value.substring(1).startsWith(fencedStartSequence)) { eaten += fencedStartSequence; isFenced = true; c += 2; } // Iterate until: // - end of string; // - interrupt character for unfenced pings; // - trailing sequence for fenced pings. while (value.charAt(c)) { if (!isFenced && isInterrupt(value.charAt(c))) break; if (isFenced && value.substring(c - 2).startsWith(fencedEndSequence) && isInterrupt(value.charAt(c))) break; username += value.charAt(c++); } eaten += username; // Remove trailing sequence if (isFenced) { if (!username.endsWith(fencedEndSequence)) return; username = username.slice(0, -fencedEndSequence.length); } if (pingUsername(username) === true && username.trim() !== '') { const url = userURL(username); return eat(eaten)({ type: 'ping', username, url, data: { hName: 'a', hProperties: { href: url, rel: 'nofollow', class: 'ping ping-link' } }, children: [{ type: 'text', value: '@' }, { type: 'emphasis', data: { hName: 'span', hProperties: { class: 'ping-username' } }, children: [{ type: 'text', value: username }] }] }); } else { return eat(eaten.charAt(0))({ type: 'text', value: eaten.charAt(0) }); } } function locator(value, fromIndex) { return value.indexOf('@', fromIndex); } inlineTokenizer.locator = locator; const Parser = this.Parser; // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers; const inlineMethods = Parser.prototype.inlineMethods; inlineTokenizers.ping = inlineTokenizer; inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'ping'); const Compiler = this.Compiler; // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors; visitors.ping = node => { if (!containsInterrupt(node.username)) { return pingCharacter + node.username; } return pingCharacter + fencedStartSequence + node.username + fencedEndSequence; }; } return (tree, file) => { // mark pings in blockquotes, later on we'll need that info to avoid pinging from quotes visit(tree, 'blockquote', markInBlockquotes); // remove ping links from pings already in links visit(tree, 'link', node => { visit(node, 'ping', (ping, index) => { ping.data.hName = 'span'; ping.data.hProperties = { class: 'ping ping-in-link' }; }); }); visit(tree, 'ping', node => { if (!node.__inBlockquote) { if (!file.data[node.type]) { file.data[node.type] = []; } // collect usernames to ping, they will be made available on the vfile // for some backend to act on file.data[node.type].push(node.username); } }); }; }; function markInBlockquotes(node) { mark(node); if (node.children) { node.children.map((n, i) => markInBlockquotes(n)); } } function mark(node) { if (node.type === 'ping') node.__inBlockquote = true; } ================================================ FILE: packages/remark-ping/package.json ================================================ { "name": "remark-ping", "version": "2.3.2", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/remark-ping", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)", "Titouan (Stalone) S. <talone@boxph.one>" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT", "dependencies": { "@unicode/unicode-13.0.0": "^1.1.0", "unist-util-visit": "^2.0.3" } } ================================================ FILE: packages/remark-ping/src/index.js ================================================ const visit = require('unist-util-visit') const interruptPunctuation = [ require('@unicode/unicode-13.0.0/Binary_Property/White_Space/code-points'), require('@unicode/unicode-13.0.0/General_Category/Close_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Final_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Initial_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Open_Punctuation/code-points'), require('@unicode/unicode-13.0.0/General_Category/Other_Punctuation/code-points') ].flat() const isInterrupt = c => interruptPunctuation.includes(c.charCodeAt(0)) const containsInterrupt = str => { for (let c = 0; c < str.length; c++) { const char = str.charAt(c) if (isInterrupt(char)) return true } return false } const helpMsg = `remark-ping: expected configuration to be passed: { pingUsername: (username) => bool,\n userURL: (username) => string\n}` module.exports = function plugin ({ pingUsername, userURL, pingCharacter = '@', fencedStartSequence = '**', fencedEndSequence = '**' }) { if (typeof pingUsername !== 'function' || typeof userURL !== 'function') { throw new Error(helpMsg) } function inlineTokenizer (eat, value, silent) { let isFenced = false let eaten = pingCharacter let username = '' let c = 1 /* istanbul ignore if - never used (yet) */ if (silent) return silent if (value.charAt(0) !== pingCharacter) return // Check if we have a fenced sequence if (value.substring(1).startsWith(fencedStartSequence)) { eaten += fencedStartSequence isFenced = true c += 2 } // Iterate until: // - end of string; // - interrupt character for unfenced pings; // - trailing sequence for fenced pings. while (value.charAt(c)) { if (!isFenced && isInterrupt(value.charAt(c))) break if (isFenced && value.substring(c - 2).startsWith(fencedEndSequence) && isInterrupt(value.charAt(c))) break username += value.charAt(c++) } eaten += username // Remove trailing sequence if (isFenced) { if (!username.endsWith(fencedEndSequence)) return username = username.slice(0, -fencedEndSequence.length) } if (pingUsername(username) === true && username.trim() !== '') { const url = userURL(username) return eat(eaten)({ type: 'ping', username, url, data: { hName: 'a', hProperties: { href: url, rel: 'nofollow', class: 'ping ping-link' } }, children: [{ type: 'text', value: '@' }, { type: 'emphasis', data: { hName: 'span', hProperties: { class: 'ping-username' } }, children: [{ type: 'text', value: username }] }] }) } else { return eat(eaten.charAt(0))({ type: 'text', value: eaten.charAt(0) }) } } function locator (value, fromIndex) { return value.indexOf('@', fromIndex) } inlineTokenizer.locator = locator const Parser = this.Parser // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers const inlineMethods = Parser.prototype.inlineMethods inlineTokenizers.ping = inlineTokenizer inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'ping') const Compiler = this.Compiler // Stringify if (Compiler) { const visitors = Compiler.prototype.visitors visitors.ping = (node) => { if (!containsInterrupt(node.username)) { return pingCharacter + node.username } return pingCharacter + fencedStartSequence + node.username + fencedEndSequence } } return (tree, file) => { // mark pings in blockquotes, later on we'll need that info to avoid pinging from quotes visit(tree, 'blockquote', markInBlockquotes) // remove ping links from pings already in links visit(tree, 'link', (node) => { visit(node, 'ping', (ping, index) => { ping.data.hName = 'span' ping.data.hProperties = { class: 'ping ping-in-link' } }) }) visit(tree, 'ping', (node) => { if (!node.__inBlockquote) { if (!file.data[node.type]) { file.data[node.type] = [] } // collect usernames to ping, they will be made available on the vfile // for some backend to act on file.data[node.type].push(node.username) } }) } } function markInBlockquotes (node) { mark(node) if (node.children) { node.children.map((n, i) => markInBlockquotes(n)) } } function mark (node) { if (node.type === 'ping') node.__inBlockquote = true } ================================================ FILE: packages/remark-sub-super/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/remark-sub-super/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/remark-sub-super/README.md ================================================ # remark-sub-super [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] This plugin parses custom Markdown syntax to handle subscript and superscript. It adds new nodes types to the [mdast][mdast] produced by [remark][remark]: * `sub` * `sup` If you are using [rehype][rehype], the stringified HTML result will be `sub` or `sup`. ## Syntax ```markdown ~subscript~, e.g. a~i~ ^superscript^, e.g. e^x^ ``` ## AST (see [mdast][mdast] specification) `Sub` ([`Parent`][parent]) represents a subscript text. ```javascript interface Sub <: Parent { type: "sub"; } ``` `Sup` ([`Parent`][parent]) represents a superscript text. ```javascript interface Sup <: Parent { type: "sup"; } ``` For example, the following markdown: ```markdown a^x^ x~i~ ``` Yields: ```javascript { type: 'paragraph', children: [{ type: 'text', value: 'a', children: [{ type: 'sup', children: [{ type: 'text', value: 'x' }] }] }] }, { type: 'paragraph', children: [{ type: 'text', value: 'x', children: [{ type: 'sub', children: [{ type: 'text', value: 'i' }] }] }] } ``` ## Rehype This plugin is compatible with [rehype][rehype]. `Sub` mdast nodes will become `<sub>contents</sub>`, `Sup` mdast nodes will become `<sup>contents</sup>`. ## Installation [npm][npm]: ```bash npm install remark-sub-super ``` ## Usage Dependencies: ```javascript const unified = require('unified') const remarkParse = require('remark-parse') const stringify = require('rehype-stringify') const remark2rehype = require('remark-rehype') const remarkSubSuper = require('remark-sub-super') ``` Usage: ```javascript unified() .use(remarkParse) .use(remarkSubSuper) .use(remark2rehype) .use(stringify) ``` ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/remark-sub-super/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/remark-sub-super [mdast]: https://github.com/syntax-tree/mdast/blob/master/readme.md [remark]: https://github.com/remarkjs/remark [rehype]: https://github.com/rehypejs/rehype [parent]: https://github.com/syntax-tree/unist#parent ================================================ FILE: packages/remark-sub-super/__tests__/__snapshots__/index.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`disallow empty tags 1`] = `"<p>^<sup>foo</sup>^</p>"`; exports[`regression 1 1`] = ` "<p>a<sup>b</sup> a<sup>b</sup> a<sup>b</sup> a<sup>b</sup> a<sup>b</sup></p> <p>a<sub>b</sub> a<sub>b</sub> a<sub>b</sub> a<sub>b</sub> a<sub>b</sub></p> <p>a<sub>b</sub> a<sup>b</sup> a<sub>b</sub> a<sup>b</sup> a<sub>b</sub> a<sup>b</sup></p> <p>a<sup>b</sup> a<sub>b</sub> a<sup>b</sup> a<sub>b</sub> a<sup>b</sup> a<sub>b</sub></p>" `; exports[`regression 2 1`] = ` "<p>Literally s<sup>e</sup>lfies tbh lo-fi. Actually health go retro polaroidsriracha. Kogi live-edge <sup>mixtape</sup> marfa street <sub>art</sub> synth. Godardsynth truffaut selfies, vape fanny subway tile. Stumptown af pabst,try-hard fam ethical actually four dollar toast. Microdosing <sup>kogi</sup>brooklyn, locavore jianbing etsy sartorial <em>YOLO</em>. Williamsburg salviaphoto<sup>a</sup> booth <sup>readymade</sup> listicle man braid. s<sup>e</sup>lfies</p> <p>Literally s<sup>e</sup>lfies tbh lo-fi. Actually health goa retro polaroid sriracha.Kogi live-edge <sup>mixtape</sup> marfa street <sub>art</sub> synth. Godard synth truffautselfies, vape fanny subway tile. Stumptown af pabst, try-hard fam ethicalactually four dollar toast. Microdosing <sup>kogi</sup> brooklyn, locavore jianbingetsy sartorial <em>YOLO</em>. Williamsburg salvia photo<sup>a</sup> booth <sup>readymade</sup>listicle man braid. s<sup>e</sup>lfies</p>" `; exports[`subscript 1`] = ` "<p>Foo <sub>sup</sub> kxcvj <sub>sup <em>string</em></sub> bar</p> <p>not ~ here</p> <p>neither ~ here ~ because it's escaped</p> <p>foo ^<sup>a</sup>^ bar</p>" `; exports[`superscript 1`] = ` "<p>Foo <sup>sup</sup> kxcvj <sup>sup <em>string</em></sup> bar</p> <p>not ^ here</p> <p>neither ^ here ^ because it's escaped</p>" `; ================================================ FILE: packages/remark-sub-super/__tests__/index.js ================================================ import dedent from 'dedent' import unified from 'unified' import reParse from 'remark-parse' import rehypeStringify from 'rehype-stringify' import remark2rehype from 'remark-rehype' import plugin from '../src/' const render = text => unified() .use(reParse) .use(remark2rehype) .use(plugin) .use(rehypeStringify) .processSync(text) test('superscript', () => { const {contents} = render(dedent` Foo ^sup^ kxcvj ^sup *string*^ bar not ^ here neither \^ here ^ because it's escaped `) expect(contents).toMatchSnapshot() }) test('subscript', () => { const {contents} = render(dedent` Foo ~sup~ kxcvj ~sup *string*~ bar not ~ here neither \~ here ~ because it's escaped foo ^^a^^ bar `) expect(contents).toMatchSnapshot() }) test('regression 1', () => { const {contents} = render(dedent` a^b^ a^b^ a^b^ a^b^ a^b^ a~b~ a~b~ a~b~ a~b~ a~b~ a~b~ a^b^ a~b~ a^b^ a~b~ a^b^ a^b^ a~b~ a^b^ a~b~ a^b^ a~b~ `) expect(contents).toMatchSnapshot() }) test('regression 2', () => { const {contents} = render(dedent` Literally s^e^lfies tbh lo-fi. Actually health go retro polaroid\ sriracha. Kogi live-edge ^mixtape^ marfa street ~art~ synth. Godard\ synth truffaut selfies, vape fanny subway tile. Stumptown af pabst,\ try-hard fam ethical actually four dollar toast. Microdosing ^kogi^\ brooklyn, locavore jianbing etsy sartorial _YOLO_. Williamsburg salvia\ photo^a^ booth ^readymade^ listicle man braid. s^e^lfies Literally s^e^lfies tbh lo-fi. Actually health goa retro polaroid sriracha.\ Kogi live-edge ^mixtape^ marfa street ~art~ synth. Godard synth truffaut\ selfies, vape fanny subway tile. Stumptown af pabst, try-hard fam ethical\ actually four dollar toast. Microdosing ^kogi^ brooklyn, locavore jianbing\ etsy sartorial _YOLO_. Williamsburg salvia photo^a^ booth ^readymade^\ listicle man braid. s^e^lfies `) expect(contents).toMatchSnapshot() }) test('disallow empty tags', () => { const {contents} = render(dedent` ^^foo^^ `) expect(contents).toMatchSnapshot() }) ================================================ FILE: packages/remark-sub-super/dist/index.js ================================================ "use strict"; const SPACE = ' '; const markers = { '~': 'sub', '^': 'sup' }; function locator(value, fromIndex) { let index = -1; const found = []; for (const marker of Object.keys(markers)) { index = value.indexOf(marker, fromIndex); if (index !== -1) { found.push(index); continue; } } if (found.length) { found.sort((a, b) => a - b); return found[0]; } return -1; } function inlinePlugin() { function inlineTokenizer(eat, value, silent) { // allow escaping of all markers for (const marker of Object.keys(markers)) { if (!this.escape.includes(marker)) this.escape.push(marker); } const marker = value[0]; const now = eat.now(); now.column += 1; now.offset += 1; if (Object.prototype.hasOwnProperty.call(markers, marker) && !value.startsWith(marker + SPACE) && !value.startsWith(marker + marker)) { let endMarkerIndex = 1; for (; value[endMarkerIndex] !== marker && endMarkerIndex < value.length; endMarkerIndex++); // if it's actually empty, don't tokenize (disallows e.g. <sup></sup>) if (endMarkerIndex === value.length) return; /* istanbul ignore if - never used (yet) */ if (silent) return true; eat(value.substring(0, endMarkerIndex + 1))({ type: markers[marker], children: this.tokenizeInline(value.substring(1, endMarkerIndex), now), data: { hName: markers[marker] } }); } } inlineTokenizer.locator = locator; const Parser = this.Parser; // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers; const inlineMethods = Parser.prototype.inlineMethods; inlineTokenizers.sub_super = inlineTokenizer; inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'sub_super'); } module.exports = inlinePlugin; ================================================ FILE: packages/remark-sub-super/package.json ================================================ { "name": "remark-sub-super", "version": "1.0.21", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/sub-super", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "remark" ], "license": "MIT" } ================================================ FILE: packages/remark-sub-super/src/index.js ================================================ const SPACE = ' ' const markers = { '~': 'sub', '^': 'sup' } function locator (value, fromIndex) { let index = -1 const found = [] for (const marker of Object.keys(markers)) { index = value.indexOf(marker, fromIndex) if (index !== -1) { found.push(index) continue } } if (found.length) { found.sort((a, b) => a - b) return found[0] } return -1 } function inlinePlugin () { function inlineTokenizer (eat, value, silent) { // allow escaping of all markers for (const marker of Object.keys(markers)) { if (!this.escape.includes(marker)) this.escape.push(marker) } const marker = value[0] const now = eat.now() now.column += 1 now.offset += 1 if (Object.prototype.hasOwnProperty.call(markers, marker) && !value.startsWith(marker + SPACE) && !value.startsWith(marker + marker) ) { let endMarkerIndex = 1 for (; value[endMarkerIndex] !== marker && endMarkerIndex < value.length; endMarkerIndex++); // if it's actually empty, don't tokenize (disallows e.g. <sup></sup>) if (endMarkerIndex === value.length) return /* istanbul ignore if - never used (yet) */ if (silent) return true eat(value.substring(0, endMarkerIndex + 1))({ type: markers[marker], children: this.tokenizeInline(value.substring(1, endMarkerIndex), now), data: { hName: markers[marker] } }) } } inlineTokenizer.locator = locator const Parser = this.Parser // Inject inlineTokenizer const inlineTokenizers = Parser.prototype.inlineTokenizers const inlineMethods = Parser.prototype.inlineMethods inlineTokenizers.sub_super = inlineTokenizer inlineMethods.splice(inlineMethods.indexOf('text'), 0, 'sub_super') } module.exports = inlinePlugin ================================================ FILE: packages/typographic-colon/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-colon/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-colon/README.md ================================================ # typographic-colon [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. This is mainly meant for French typography, it replaces the space preceding a colon by a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)). Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-colon ``` ## Usage ```js var colon = require('typographic-colon') colon(`Exemple : voici.`, { locale: 'fr' }) // this char ^ will be replaced by a narrow no-break space ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-colon/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-colon [textr]: https://github.com/A/textr ================================================ FILE: packages/typographic-colon/__tests__/index.js ================================================ /* eslint-disable no-irregular-whitespace */ const chars = { 'NARROW NO-BREAK SPACE': '\u202F', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} const colon = require('../src') test('should do nothing with no param at all', () => expect(colon()).toEqual('')) test('should do nothing if locale is undefined', () => expect(colon(`foo : bar`)).toEqual(`foo : bar`)) test('should ignore locale not in DB', () => expect(colon(`foo : bar`, american)).toEqual(`foo : bar`)) test('should handle fr[-*]', () => { expect(colon(`foo : bar`, fr)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}: bar`) expect(colon(`foo : bar`, frCH)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}: bar`) }) ================================================ FILE: packages/typographic-colon/dist/db.js ================================================ "use strict"; const chars = { 'NARROW NO-BREAK SPACE': '\u202F' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] }; ================================================ FILE: packages/typographic-colon/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input; const beforeColon = db[locale]; const pattern = / :(\s|$)/gim; const handleColon = (withColon, afterColon) => `${beforeColon}:${afterColon}`; return input.replace(pattern, handleColon); }; ================================================ FILE: packages/typographic-colon/package.json ================================================ { "name": "typographic-colon", "version": "1.0.18", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-colon", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-colon/src/db.js ================================================ const chars = { 'NARROW NO-BREAK SPACE': '\u202F' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] } ================================================ FILE: packages/typographic-colon/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input const beforeColon = db[locale] const pattern = / :(\s|$)/gim const handleColon = (withColon, afterColon) => `${beforeColon}:${afterColon}` return input.replace(pattern, handleColon) } ================================================ FILE: packages/typographic-em-dash/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-em-dash/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-em-dash/README.md ================================================ # typographic-em-dash [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. This is mainly meant for French typography, it replaces _--_ by an _em dash_ ([en](http://www.fileformat.info/info/unicode/char/2014/index.htm), [fr](https://fr.wikipedia.org/wiki/Cadratin)). If the locale is French and we have a pair like _— foo —_, the first space after the first em dash and the space preceding the second dash will be a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)). Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-em-dash ``` ## Usage ```js var dash = require('typographic-em-dash') dash(`--foo--`, { locale: 'fr' }) // will be replaced by —foo— dash(`-- foo --`, { locale: 'fr' }) // will be replaced by —NNBSfooNNBs— where NNBS is a narrow no-break space dash(`--foo--`, { locale: 'en' }) // will be replaced by —foo— dash(`-- foo --`, { locale: 'en' }) // will be replaced by — foo — ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-em-dash/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-em-dash [textr]: https://github.com/A/text ================================================ FILE: packages/typographic-em-dash/__tests__/index.js ================================================ import emDash from '../src/' const chars = { 'NARROW NO-BREAK SPACE': '\u202F', 'EM DASH': '\u2014', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} const nnbs = chars['NARROW NO-BREAK SPACE'] const dashChar = chars['EM DASH'] test('should do nothing with no param at all', () => expect(emDash()).toEqual('')) test('should only replace em dash if the locale is not in db', () => { expect(emDash(`--foo bar--`)).toEqual(`${dashChar}foo bar${dashChar}`) expect(emDash(`--foo bar--`, american)).toEqual(`${dashChar}foo bar${dashChar}`) }) test('should alse replace space for fr[-*]', () => { expect(emDash(`-- foo`, fr)).toEqual(`${dashChar}${nnbs}foo`) expect(emDash(` -- foo -- bar -- foo bar --`, fr)).toEqual( ` ${dashChar}${nnbs}foo${nnbs}${dashChar} bar ${dashChar}${nnbs}foo bar${nnbs}${dashChar}` ) expect(emDash(` -- foo -- bar -- foo bar --`, frCH)).toEqual( ` ${dashChar}${nnbs}foo${nnbs}${dashChar} bar ${dashChar}${nnbs}foo bar${nnbs}${dashChar}` ) }) ================================================ FILE: packages/typographic-em-dash/dist/db.js ================================================ "use strict"; const chars = { 'NARROW NO-BREAK SPACE': '\u202F' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] }; ================================================ FILE: packages/typographic-em-dash/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { // Replace -- by \u2013 for all locales const dashChar = '\u2014'; const dashPattern = /--/gm; let result = input.replace(dashPattern, `${dashChar}`); // nbsp inside em dash pairs // (foo -- bar -- baz. -> foo1—2bar2—1baz. where 1 is and 2 is nbsp if (Object.keys(db).includes(locale)) { const separation = new RegExp(`(^|\\s)(${dashChar})(\\s|$)`); const nnbs = db[locale]; let temp = result; let isOpening = true; let startPosition = separation.exec(temp); result = ''; while (startPosition) { result += temp.substring(0, startPosition.index); const replacement = isOpening ? `$1$2${nnbs}` : `${nnbs}$2$3`; result += startPosition[0].replace(separation, replacement); temp = temp.substring(startPosition.index + startPosition[0].length, temp.length); startPosition = separation.exec(temp); isOpening = !isOpening; } result += temp; } return result; }; ================================================ FILE: packages/typographic-em-dash/package.json ================================================ { "name": "typographic-em-dash", "version": "1.0.17", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-em-dash", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-em-dash/src/db.js ================================================ const chars = { 'NARROW NO-BREAK SPACE': '\u202F' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] } ================================================ FILE: packages/typographic-em-dash/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { // Replace -- by \u2013 for all locales const dashChar = '\u2014' const dashPattern = /--/gm let result = input.replace(dashPattern, `${dashChar}`) // nbsp inside em dash pairs // (foo -- bar -- baz. -> foo1—2bar2—1baz. where 1 is and 2 is nbsp if (Object.keys(db).includes(locale)) { const separation = new RegExp(`(^|\\s)(${dashChar})(\\s|$)`) const nnbs = db[locale] let temp = result let isOpening = true let startPosition = separation.exec(temp) result = '' while (startPosition) { result += temp.substring(0, startPosition.index) const replacement = isOpening ? `$1$2${nnbs}` : `${nnbs}$2$3` result += startPosition[0].replace(separation, replacement) temp = temp.substring(startPosition.index + startPosition[0].length, temp.length) startPosition = separation.exec(temp) isOpening = !isOpening } result += temp } return result } ================================================ FILE: packages/typographic-exclamation-mark/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-exclamation-mark/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-exclamation-mark/README.md ================================================ # typographic-exclamation-mark [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. This is mainly meant for French typography, it replaces the space preceding an exclamation point by a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)). Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-exclamation-mark ``` ## Usage ```js var exclamation = require('typographic-exclamation-mark') exclamation(`Exemple ! voici.`, { locale: 'fr' }) // this char ^ will be replaced by a narrow no-break space ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-exclamation-mark/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-exclamation-mark [textr]: https://github.com/A/textr ================================================ FILE: packages/typographic-exclamation-mark/__tests__/index.js ================================================ import exclamationMark from '../src/' const chars = { 'NARROW NO-BREAK SPACE': '\u202F', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} test('should do nothing with no param at all', () => expect(exclamationMark()).toEqual('')) test('should do nothing if locale is undefined', () => expect(exclamationMark(`foo ! bar`)).toEqual(`foo ! bar`)) test('should ignore locale not in DB', () => expect(exclamationMark(`foo ! bar`, american)).toEqual(`foo ! bar`)) test('should handle fr[-*]', () => { expect(exclamationMark(`foo ! bar`, fr)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}! bar`) expect(exclamationMark(`foo ! bar`, frCH)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}! bar`) }) ================================================ FILE: packages/typographic-exclamation-mark/dist/db.js ================================================ "use strict"; const chars = { 'NARROW NO-BREAK SPACE': '\u202F' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] }; ================================================ FILE: packages/typographic-exclamation-mark/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input; const beforeSemiColon = db[locale]; const pattern = / !(\s|$)/gim; const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon}!${afterSemiColon}`; return input.replace(pattern, handleSemiColon); }; ================================================ FILE: packages/typographic-exclamation-mark/package.json ================================================ { "name": "typographic-exclamation-mark", "version": "1.0.17", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-exclamation-mark", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-exclamation-mark/src/db.js ================================================ const chars = { 'NARROW NO-BREAK SPACE': '\u202F' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] } ================================================ FILE: packages/typographic-exclamation-mark/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input const beforeSemiColon = db[locale] const pattern = / !(\s|$)/gim const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon}!${afterSemiColon}` return input.replace(pattern, handleSemiColon) } ================================================ FILE: packages/typographic-guillemets/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-guillemets/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-guillemets/README.md ================================================ # typographic-guillemets [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. This is mainly meant for French typography, where respectively << and >> should be replaced by a _left-pointing double angle quotation mark_ ([en](http://www.fileformat.info/info/unicode/char/00AB/index.htm), [en](https://en.wikipedia.org/wiki/Guillemet)) and a _right-pointing double angle quotation mark_([en](www.fileformat.info/info/unicode/char/00BB/index.htm)). The left-pointing mark should be followed by a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)) and the right-pointing mark should be preceded by a _narrow no-break space_. Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-guillemets ``` ## Usage ```js var guillemets = require('typographic-guillemets') guillemets(`<< here >>`, { locale: 'fr' }) // will be replaced by «NNBShereNNBS» where NNBS is a narrow no-break space. ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-guillemets/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-guillemets [textr]: https://github.com/A/textr ================================================ FILE: packages/typographic-guillemets/__tests__/index.js ================================================ import guillemet from '../src/' const chars = { 'NARROW NO-BREAK SPACE': '\u202F', 'LEFT-POINTING ANGLE QUOTATION MARK': '\u00AB', 'RIGHT-POINTING ANGLE QUOTATION MARK': '\u00BB', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} test('should do nothing with no param at all', () => expect(guillemet()).toEqual('')) test('should do nothing if locale is undefined', () => expect(guillemet(`<< a >>`)).toEqual(`<< a >>`)) test('should ignore locale not in DB', () => expect(guillemet(`<< a >>`, american)).toEqual(`<< a >>`)) test('should handle fr[-*]', () => { const before = `${chars['LEFT-POINTING ANGLE QUOTATION MARK']}${chars['NARROW NO-BREAK SPACE']}` const after = `${chars['NARROW NO-BREAK SPACE']}${chars['RIGHT-POINTING ANGLE QUOTATION MARK']}` expect(guillemet(`<< a >>`, fr)).toEqual(`${before}a${after}`) expect(guillemet(`<< a >>`, frCH)).toEqual(`${before}a${after}`) }) ================================================ FILE: packages/typographic-guillemets/dist/db.js ================================================ "use strict"; const charsFr = { 'NARROW NO-BREAK SPACE': '\u202F', 'LEFT-POINTING ANGLE QUOTATION MARK': '\u00AB', 'RIGHT-POINTING ANGLE QUOTATION MARK': '\u00BB' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: charsFr, 'fr-sw': charsFr }; ================================================ FILE: packages/typographic-guillemets/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input; const chars = db[locale]; const leftMark = chars['LEFT-POINTING ANGLE QUOTATION MARK']; const rightMark = chars['RIGHT-POINTING ANGLE QUOTATION MARK']; const spaceChar = chars['NARROW NO-BREAK SPACE']; const leftAnglePattern = /<<\s*/gm; const rightAnglePattern = /\s*>>/gm; return input.replace(leftAnglePattern, leftMark.concat(spaceChar)).replace(rightAnglePattern, spaceChar.concat(rightMark)); }; ================================================ FILE: packages/typographic-guillemets/package.json ================================================ { "name": "typographic-guillemets", "version": "1.1.1", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-guillemets", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-guillemets/src/db.js ================================================ const charsFr = { 'NARROW NO-BREAK SPACE': '\u202F', 'LEFT-POINTING ANGLE QUOTATION MARK': '\u00AB', 'RIGHT-POINTING ANGLE QUOTATION MARK': '\u00BB' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: charsFr, 'fr-sw': charsFr } ================================================ FILE: packages/typographic-guillemets/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input const chars = db[locale] const leftMark = chars['LEFT-POINTING ANGLE QUOTATION MARK'] const rightMark = chars['RIGHT-POINTING ANGLE QUOTATION MARK'] const spaceChar = chars['NARROW NO-BREAK SPACE'] const leftAnglePattern = /<<\s*/gm const rightAnglePattern = /\s*>>/gm return input .replace(leftAnglePattern, leftMark.concat(spaceChar)) .replace(rightAnglePattern, spaceChar.concat(rightMark)) } ================================================ FILE: packages/typographic-percent/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-percent/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-percent/README.md ================================================ # typographic-percent [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. This is mainly meant for French typography, it replaces the space preceding a percent sign by a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)). Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-percent ``` ## Usage ```js var percent = require('typographic-percent') percent(`Exemple % voici.`, { locale: 'fr' }) // this char ^ will be replaced by a narrow no-break space ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-percent/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-percent [textr]: https://github.com/A/textr ================================================ FILE: packages/typographic-percent/__tests__/index.js ================================================ import percent from '../src/' const chars = { 'NARROW NO-BREAK SPACE': '\u202F', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} test('should do nothing with no param at all', () => expect(percent()).toEqual('')) test('should do nothing if locale is undefined', () => expect(percent(`foo % bar`)).toEqual(`foo % bar`)) test('should ignore locale not in DB', () => expect(percent(`foo % bar`, american)).toEqual(`foo % bar`)) test('should handle fr[-*]', () => { expect(percent(`foo % bar`, fr)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}% bar`) expect(percent(`foo % bar`, frCH)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}% bar`) }) ================================================ FILE: packages/typographic-percent/dist/db.js ================================================ "use strict"; const chars = { 'NARROW NO-BREAK SPACE': '\u202F' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] }; ================================================ FILE: packages/typographic-percent/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input; const beforeSemiColon = db[locale]; const pattern = / %(\s|$)/gim; const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon}%${afterSemiColon}`; return input.replace(pattern, handleSemiColon); }; ================================================ FILE: packages/typographic-percent/package.json ================================================ { "name": "typographic-percent", "version": "1.0.17", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-percent", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-percent/src/db.js ================================================ const chars = { 'NARROW NO-BREAK SPACE': '\u202F' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] } ================================================ FILE: packages/typographic-percent/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input const beforeSemiColon = db[locale] const pattern = / %(\s|$)/gim const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon}%${afterSemiColon}` return input.replace(pattern, handleSemiColon) } ================================================ FILE: packages/typographic-permille/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-permille/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-permille/README.md ================================================ # typographic-permille [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to replace `%o` with `‰` and optionally replace the preceding space. This is meant for typography, where %o should be replaced by a per mille sign ([en](http://www.fileformat.info/info/unicode/char/2030/index.htm), [fr](https://fr.wikipedia.org/wiki/Pour_mille)). This is mainly meant for French typography, it replaces the space preceding a per mille sign by a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)). Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-permille ``` ## Usage ```js var permille = require('typographic-permille') permille(`Top 1%o.`, { locale: 'en' }) // Top 1‰ permille(`Top 1 %o.`, { locale: 'fr' }) // Top 1 ‰ // this char ^ will be replaced by a narrow no-break space ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-permille/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-permille [textr]: https://github.com/A/textr ================================================ FILE: packages/typographic-permille/__tests__/index.js ================================================ import permille from '../src/' const chars = { 'NARROW NO-BREAK SPACE': '\u202F', 'PER MILLE SIGN': '\u2030', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} test('should do nothing with no param at all', () => expect(permille()).toEqual('')) test('should handle all locales', () => { expect(permille(`foo %o`)).toEqual(`foo ${chars['PER MILLE SIGN']}`) expect(permille(`foo %o`, american)).toEqual(`foo ${chars['PER MILLE SIGN']}`) expect(permille(`foo %o`, american)).toEqual(`foo ${chars['PER MILLE SIGN']}`) expect(permille(`%o`, american)).toEqual(`${chars['PER MILLE SIGN']}`) }) test('should handle fr[-*]', () => { expect(permille(`foo %o`, fr)) .toEqual(`foo${chars['NARROW NO-BREAK SPACE']}${chars['PER MILLE SIGN']}`) expect(permille(`foo %o`, frCH)) .toEqual(`foo${chars['NARROW NO-BREAK SPACE']}${chars['PER MILLE SIGN']}`) expect(permille(`%o`, fr)).toEqual(`${chars['PER MILLE SIGN']}`) }) ================================================ FILE: packages/typographic-permille/dist/db.js ================================================ "use strict"; const chars = { 'NARROW NO-BREAK SPACE': '\u202F' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] }; ================================================ FILE: packages/typographic-permille/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { const chars = { 'PER MILLE SIGN': '\u2030' }; const permillePattern = /%o/gim; const result = input.replace(permillePattern, chars['PER MILLE SIGN']); if (Object.keys(db).includes(locale)) { // If we need to replace space before per mille signs const spaceBeforePermillePattern = /( )(\u2030)/g; return result.replace(spaceBeforePermillePattern, `${db[locale]}$2`); } return result; }; ================================================ FILE: packages/typographic-permille/package.json ================================================ { "name": "typographic-permille", "version": "1.0.17", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-permille", "type": "git" }, "author": "Sébastien (AmarOk) Blin <contact@enconn.fr>", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-permille/src/db.js ================================================ const chars = { 'NARROW NO-BREAK SPACE': '\u202F' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] } ================================================ FILE: packages/typographic-permille/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { const chars = { 'PER MILLE SIGN': '\u2030' } const permillePattern = /%o/gim const result = input.replace(permillePattern, chars['PER MILLE SIGN']) if (Object.keys(db).includes(locale)) { // If we need to replace space before per mille signs const spaceBeforePermillePattern = /( )(\u2030)/g return result.replace(spaceBeforePermillePattern, `${db[locale]}$2`) } return result } ================================================ FILE: packages/typographic-question-mark/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-question-mark/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-question-mark/README.md ================================================ # typographic-question-mark [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. This is mainly meant for French typography, it replaces the space preceding an interrogation point by a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)). Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-question-mark ``` ## Usage ```js var interrogation = require('typographic-question-mark') interrogation(`Exemple ? voici.`, { locale: 'fr' }) // this char ^ will be replaced by a narrow no-break space ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-question-mark/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-question-mark [textr]: https://github.com/A/textr ================================================ FILE: packages/typographic-question-mark/__tests__/index.js ================================================ import questionMark from '../src/' const chars = { 'NARROW NO-BREAK SPACE': '\u202F', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} test('should do nothing with no param at all', () => expect(questionMark()).toEqual('')) test('should do nothing if locale is undefined', () => expect(questionMark(`foo ? bar`)).toEqual(`foo ? bar`)) test('should ignore locale not in DB', () => expect(questionMark(`foo ? bar`, american)).toEqual(`foo ? bar`)) test('should handle fr[-*]', () => { expect(questionMark(`foo ? bar`, fr)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}? bar`) expect(questionMark(`foo ? bar`, frCH)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}? bar`) }) ================================================ FILE: packages/typographic-question-mark/dist/db.js ================================================ "use strict"; const chars = { 'NARROW NO-BREAK SPACE': '\u202F' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] }; ================================================ FILE: packages/typographic-question-mark/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input; const beforeSemiColon = db[locale]; const pattern = / \?(\s|$)/gim; const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon}?${afterSemiColon}`; return input.replace(pattern, handleSemiColon); }; ================================================ FILE: packages/typographic-question-mark/package.json ================================================ { "name": "typographic-question-mark", "version": "1.0.17", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-question-mark", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint .", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-question-mark/src/db.js ================================================ const chars = { 'NARROW NO-BREAK SPACE': '\u202F' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] } ================================================ FILE: packages/typographic-question-mark/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input const beforeSemiColon = db[locale] const pattern = / \?(\s|$)/gim const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon}?${afterSemiColon}` return input.replace(pattern, handleSemiColon) } ================================================ FILE: packages/typographic-semicolon/.npmignore ================================================ /index.js /__tests__ /.npmignore /coverage *.log /src ================================================ FILE: packages/typographic-semicolon/LICENSE-MIT ================================================ Copyright (c) Zeste de Savoir (https://zestedesavoir.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/typographic-semicolon/README.md ================================================ # typographic-semicolon [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] Micro module to fix a common typographic issue that is hard to fix with most keyboard layouts. This is mainly meant for French typography, it replaces the space preceding a semicolon by a _narrow no-break space_ ([en](http://www.fileformat.info/info/unicode/char/202f/index.htm), [fr](https://fr.wikipedia.org/wiki/Espace_fine_ins%C3%A9cable)). Submit a PR to `src/db.js` to add support for your locale. ## Install [npm][npm]: ```sh npm install --save typographic-semicolon ``` ## Usage ```js var semiColon = require('typographic-semicolon') semiColon(`Exemple ; voici.`, { locale: 'fr' }) // this char ^ will be replaced by a narrow no-break space ``` This module can also be used through [textr][textr]. ## License [MIT][license] © [Zeste de Savoir][zds] <!-- Definitions --> [build-badge]: https://img.shields.io/travis/zestedesavoir/zmarkdown.svg [build-status]: https://travis-ci.org/zestedesavoir/zmarkdown [coverage-badge]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown.svg [coverage-status]: https://coveralls.io/github/zestedesavoir/zmarkdown [license]: https://github.com/zestedesavoir/zmarkdown/blob/master/packages/typographic-semicolon/LICENSE-MIT [zds]: https://zestedesavoir.com [npm]: https://www.npmjs.com/package/typographic-semicolon [textr]: https://github.com/A/textr ================================================ FILE: packages/typographic-semicolon/__tests__/index.js ================================================ import semiColon from '../src/' const chars = { 'NARROW NO-BREAK SPACE': '\u202F', } const american = {locale: 'en-us'} const fr = {locale: 'fr'} const frCH = {locale: 'fr-sw'} test('should do nothing with no param at all', () => expect(semiColon()).toEqual('')) test('should do nothing if locale is undefined', () => expect(semiColon(`foo ; bar`)).toEqual(`foo ; bar`)) test('should ignore locale not in DB', () => expect(semiColon(`foo ; bar`, american)).toEqual(`foo ; bar`)) test('should handle fr[-*]', () => { expect(semiColon(`foo ; bar`, fr)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}; bar`) expect(semiColon(`foo ; bar`, frCH)).toEqual(`foo${chars['NARROW NO-BREAK SPACE']}; bar`) }) ================================================ FILE: packages/typographic-semicolon/dist/db.js ================================================ "use strict"; const chars = { 'NARROW NO-BREAK SPACE': '\u202F' }; // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] }; ================================================ FILE: packages/typographic-semicolon/dist/index.js ================================================ "use strict"; const db = require('./db'); module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input; const beforeSemiColon = db[locale]; const pattern = / ;(\s|$)/gim; const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon};${afterSemiColon}`; return input.replace(pattern, handleSemiColon); }; ================================================ FILE: packages/typographic-semicolon/package.json ================================================ { "name": "typographic-semicolon", "version": "1.0.18", "repository": { "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/typographic-semicolon", "type": "git" }, "author": "Victor Felder <victor@draft.li> (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin <contact@enconn.fr>", "François (artragis) Dambrine <perso@francoisdambrine.me>", "Victor Felder <victor@draft.li> (https://draft.li)" ], "scripts": { "pretest": "eslint src", "build": "babel --root-mode upward --delete-dir-on-start --env-name production --out-dir dist src", "test": "jest", "coverage": "jest --coverage" }, "main": "dist/index.js", "files": [ "LICENSE-MIT", "dist", "src", "README.md" ], "keywords": [ "textr" ], "license": "MIT" } ================================================ FILE: packages/typographic-semicolon/src/db.js ================================================ const chars = { 'NARROW NO-BREAK SPACE': '\u202F' } // Language codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes module.exports = { fr: chars['NARROW NO-BREAK SPACE'], 'fr-sw': chars['NARROW NO-BREAK SPACE'] } ================================================ FILE: packages/typographic-semicolon/src/index.js ================================================ const db = require('./db') module.exports = (input = '', { locale } = {}) => { if (!Object.keys(db).includes(locale)) return input const beforeSemiColon = db[locale] const pattern = / ;(\s|$)/gim const handleSemiColon = (withSemiColon, afterSemiColon) => `${beforeSemiColon};${afterSemiColon}` return input.replace(pattern, handleSemiColon) } ================================================ FILE: packages/zmarkdown/.gitignore ================================================ client/dist/ ================================================ FILE: packages/zmarkdown/README.md ================================================ # zmarkdown [![NPM Version][npm-image]][npm-url] [![Test Coverage][coveralls-image]][coveralls-url] This project is an **HTTP Server API** providing fast and extensible **markdown parser**. It is the Markdown engine powering [Zeste de Savoir][zds]. It is a small express server leveraging the [**remark** processor][processor] and its [**MDAST**][mdast] syntax tree, [**rehype**][rehype] (for HTML processing) and [**textr**][textr] (text transformation framework). It also provides [**MDAST**][mdast] to LaTeX compilation via [**rebber**][rebber] (and its [plugins][rebber-plugins]). ```log curl -H "Content-Type: application/json" -X POST -d '{"md":"Hello word"}' http://localhost:27272/html #return: ["<p>Hello word</p>",{"disableToc":true,"languages":[],"depth":1},[]] ``` [npm-image]: https://img.shields.io/npm/v/zmarkdown.svg [npm-url]: https://npmjs.org/package/zmarkdown [downloads-image]: https://img.shields.io/npm/dm/zmarkdown.svg [downloads-url]: https://npmjs.org/package/zmarkdown [travis-image]: https://img.shields.io/travis/zestedesavoir/zmarkdown/master.svg?label=linux [travis-url]: https://travis-ci.com/zestedesavoir/zmarkdown [coveralls-image]: https://img.shields.io/coveralls/zestedesavoir/zmarkdown/master.svg [coveralls-url]: https://coveralls.io/r/zestedesavoir/zmarkdown?branch=master [zds]: https://zestedesavoir.com [processor]: https://github.com/remarkjs/remark/blob/master/packages/remark [mdast]: https://github.com/wooorm/mdast [rehype]: https://github.com/rehypejs/rehype [textr]: https://github.com/A/textr [rebber]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rebber#rebber-- [rebber-plugins]: https://github.com/zestedesavoir/zmarkdown/tree/master/packages/rebber-plugins#rebber-plugins-- ## Features - Convert Markdown to HTML ; - Convert Markdown to EPUB compatible HTML ; - Convert Markdown to LaTeX ; - Convert Markdown to TEX file; - Convert ordered list of Markdown extracts into one of the above formats. ## Installation This is a [Node.js](https://nodejs.org/en/) module available through the [npm registry][npm-url]. Install with npm: ``` npm install zmarkdown ``` ## Getting started 1. Start your zmarkdown server `npm run server` 2. Send a `POST` request to `http://localhost:27272/{endpoint}` `pm2 monit`: provides a realtime dashboard that fits directly into your terminal, it is a [simple way to monitor][pm2-monit] the resource usage of you server. [pm2-monit]:http://pm2.keymetrics.io/docs/usage/monitoring/ ### Limit the resource usage of your server You can change the number of threads (default: `3`) and the max-memory of each thread (default: `150M`) in your package.json at `scripts.server` : ```json "server": "pm2 start -f server/index.js -i 3 --max-memory-restart 150M", ``` ## Requests All endpoints respond to `HTTP POST`. The request body must be JSON with a required `md` key. An optional `opts` key can be provided, the value of which depends on the endpoint. ### URL ``` POST http://localhost:27272/{endpoint} ``` ### Body #### Required Body JSON Value | Name | Type | Description | | - | - | - | | `md` | string | markdown source string or ordered list of Markdown extracts (see below) | #### Optional Body JSON Value | Name | Type | Description | | - | - | - | | `opts` | JSON | Options specific to this endpoint. This object is documented in the **Request** section of each endpoint. | ### Response All endpoints return `[contents, metadata, messages]` as JSON. | Name | Type | Description | | - | - | - | | `contents` | string | the rendered HTML or LaTeX. | | `metadata` | object | depends on request options. This object is documented in the **Response** section of each endpoint. | | `messages` | string[] | info/debug/errors from parsers, plugins, compilers, etc. | Only `metadata` is described in the **Response** sections below. # Endpoints [ref-epub]: #epub [ref-html]: #html [ref-latex]: #latex [ref-tex]: #tex ## epub Markdown to EPUB compatible HTML ### URL ``` POST http://localhost:27272/epub ``` ### Request `opts` values | Name | Type | Description | | - | - | - | | `opts.images_download_dir` | bool | [see `/latex`][ref-latex] | | `opts.local_url_to_local_path` | string | [see `/latex`][ref-latex] | ### Response `metadata` values | Name | Type | Description | | - | - | - | | `metadata.disableToc` | bool | Whether or not the input Markdown did **not** contain headings (`#`, `##`, …). This property is named that way because we use it to disable Table of Contents generation when no headings were found.<br>- `disableToc: true` means *no headings*<br>- `disableToc: false` means at least one *heading*. | ## html Markdown to HTML ### URL ``` POST http://localhost:27272/html ``` ### Request `opts` values | Name | Type | Description | | - | - | - | | `opts.disable_ping` | bool | default: `false`, [pings][ping] won't get parsed. | | `opts.disable_jsfiddle` | bool | default: `false`, JSFiddle [iframes][iframes] are disabled. | | `opts.inline` | bool | default: `false`, Only parse inline Markdown elements (such as links and emphasis, unlike lists and fenced code blocks). | | `opts.stats` | bool | default: `false`, Will compute and return statistics about markdown text. | ### Response `metadata` values | Name | Type | Description | | - | - | - | | `metadata.disableToc` | bool | Whether or not the input Markdown did **not** contain headings (`#`, `##`, …). This property is named that way because we use it to disable Table of Contents generation when no headings were found.<br>`disableToc: true` means *no headings*<br>`disableToc: false` means at least one *heading* | | `metadata.ping` | string[] | undefined if `opts.disable_ping: true`<br>The list of nicknames returned by `remark-ping`. Can be used to send "ping" notifications to the corresponding users.<br>Note: this is fully customizable, `remark-ping` can validate potential *ping*s by any means, including sending an HTTP request (we recommend `HEAD`) to a REST API to make sure this username actually exists. | | `metadata.languages` | string[] | A list of unique languages used in GitHub Flavoured Markdown fences with a flag. | | `metadata.stats` | object | stats about the parsed text:<br>- `signs`: number of chars, spaces included.<br>- ` words`: number of words. | ### Example Here is a quick example of the request to be made to the `http://localhost:27272/html` endpoint, using the software of your choice: ```json { "md": "# Hello\n\nThis is @zmarkdown, a wonderful [Markdown](https://fr.wikipedia.org/wiki/Markdown) parser. You can **embed** code in it:\n\n```js\n\nconst zmd = require('zmarkdown/modules/common')\n\n\n\nconst globalParser = common(opts, processor)\n\n```\n\n- Oh wait, this is *Mise en abyme*, isn't it?\n\n- Of course it is, you @silly.\n\n- Silly, me? Let me remind you that the main usage of this module is to launch the server directly, not using this Node.js crap:\n\n```bash\n\nnpm -g install pm2 && npm install zmarkdown\ncd ./node_modules/zmarkdown && npm run server\n\n```\n\nNow you know how it works, at least for the API endpoint, but we do support a lot more syntax, if you want.", "opts": { "stats": true } } ``` This request will trigger the following response from the server: ```json [ "<h3 id=\"hello\">Hello<a aria-hidden=\"true\" href=\"#hello\"><span class=\"icon icon-link\"></span></a></h3>\n<p>This is <a href=\"/@zmarkdown\" rel=\"nofollow\" class=\"ping ping-link\">@<span class=\"ping-username\">zmarkdown</span></a>, a wonderful <a href=\"https://fr.wikipedia.org/wiki/Markdown\">Markdown</a> parser. You can <strong>embed</strong> code in it:</p>\n<div class=\"hljs-code-div\"><div class=\"hljs-line-numbers\"><span></span><span></span><span></span><span></span><span></span></div><pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">const</span> zmd = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'zmarkdown/modules/common'</span>)\n\n\n\n<span class=\"hljs-keyword\">const</span> globalParser = common(opts, processor)\n</code></pre></div>\n<ul>\n<li>\n<p>Oh wait, this is <em>Mise en abyme</em>, isn’t it?</p>\n</li>\n<li>\n<p>Of course it is, you <a href=\"/@silly\" rel=\"nofollow\" class=\"ping ping-link\">@<span class=\"ping-username\">silly</span></a>.</p>\n</li>\n<li>\n<p>Silly, me? Let me remind you that the main usage of this module is to launch the server directly, not using this Node.js crap:</p>\n</li>\n</ul>\n<div class=\"hljs-code-div\"><div class=\"hljs-line-numbers\"><span></span><span></span></div><pre><code class=\"hljs language-bash\">npm -g install pm2 && npm install zmarkdown\n<span class=\"hljs-built_in\">cd</span> ./node_modules/zmarkdown && npm run server\n</code></pre></div>\n<p>Now you know how it works, at least for the API endpoint, but we do support a lot more syntax, if you want.<p>", { "ping": [ "zmarkdown", "silly" ], "disableToc": false, "languages": [ "js", "bash" ], "depth": 5, "stats": { "signs": 386, "words": 78 } }, [] ] ``` ## LaTeX Markdown to LaTeX ### URL ``` POST http://localhost:27272/latex ``` ### Request `opts` values | Name | Type | Description | | - | - | - | | `opts.disable_images_download` | bool | Default: `false`, does not download images. | | `opts.images_download_dir` | string | Where to download the images to. | | `opts.images_download_default` | string | Default: `black.png`, image used when the distant image is not found. | | `opts.images_download_timeout` | number | Default: `5000` ms. HTTP request timeout for each image, in milliseconds. | | `opts.local_url_to_local_path` | - | [see below](#optslocal_url_to_local_path) this table. | | `opts.disable_jsfiddle` | bool | [see `/html`][ref-html] | ### opts.local_url_to_local_path - \[from: string, to: string\], default: `<none>` If provided, local images referenced in Markdown source (such as `![](/img/example.png)`) will be copied to `images_download_dir` after replacing the string `from` with `to` using the following RegExp: ```js '/img/example.png'.replace(new RegExp(`^${from}`), to) ``` ### Response `metadata` values This endpoint only returns `{}` as metadata, i.e. an empty object. ## TeX Markdown to TEX file ### URL ``` POST http://localhost:27272/latex-document ``` ### Request required `opts` values These values are **required**. | Name | Type | Description | | - | - | - | | `opts.content_type` | string | (**required**) Will be interpolated in `\documentclass[${content_type}]{zmdocument}` | | `opts.title` | string | (**required**) Will be interpolated in `\title{${title}}` | | `opts.authors`, | string[] | (**required**) Will be interpolated in `\author{${authors.join(', ')}}` | | `opts.license` | string | (**required**) E.g. `CC-BY-SA` will be displayed as-is, using `${license_directory}/by-sa.svg` as license icon with a link to `https://creativecommons.org/licenses/by-sa/4.0/legalcode` | | `opts.license_directory` | string | (**required**) Path to the directory where CC license SVG icons are stored, see `license` above. | | `opts.smileys_directory` | string | (**required**) Path to the directory where smileys are stored. | ### Request `opts` values | Name | Type | Description | | - | - | - | | `opts.disable_images_download` | bool | [see `/latex`][ref-latex] | | `opts.images_download_dir` | string | [see `/latex`][ref-latex] | | `opts.images_download_default` | string | [see `/latex`][ref-latex] | | `opts.local_url_to_local_path` | string | [see `/latex`][ref-latex] | | `opts.disable_jsfiddle` | bool | [see `/html`][ref-html] | ### Response `metadata` values This endpoint only returns `{}` as metadata, i.e. an empty object. ## Manifest rendering Since version 10.0.0, ZMarkdown supports *manifest rendering*, which means it is capable of processing asynchronously an ordered list of Markdown extracts, and assembling them back together. Manifest rendering works with all the four endpoints described above. Let's take an example with HTML; the following request body: ```json { "md": {"text":"# foo\n\nHello @you", "children": [{"text": "Foobar"}, {"text": "Barfoo"}]}, "opts": { "stats": true } } ``` Will lead to the following response from the server: ```json [ { "text": "<h2 id=\"foo\">foo<a aria-hidden=\"true\" tabindex=\"-1\" href=\"#foo\"><span class=\"icon icon-link\"></span></a></h2>\n<p>Hello <a href=\"/@you\" rel=\"nofollow\" class=\"ping ping-link\">@<span class=\"ping-username\">you</span></a><p>", "children": [ { "text": "<p>Foobar</p>" }, { "text": "<p>Barfoo</p>" } ] }, { "ping": [ "you" ], "stats": { "signs": 24, "words": 5 }, "depth": 1, "disableToc": false, "languages": [] }, [] ] ``` As you can see, the extracts are positionned in the right order, and VFiles are automatically assembled. Calling the `latex-document` endpoint will also concatenate the extracts and produce a complete document. ## Client Architecture Four client builds are currently available (starting from version 9.0.0), they can all be found in the `client/dist` folder: - `zmarkdown-zmdast` compiles Markdown to MDAST and return the result, and optionally an inspector to get a pretty output; - `zmarkdown-zhtml` compiles Markdown to HTML, using the same modules as the server, but this renderer is quite huge (1.8 MB), so it is not recommended for use in a web browser; - `zmarkdown-zhlite` is a browser-friendly version of the MD-to-HTML renderer; it has the same capabilities, except for KaTeX and highlight.js, so you'll need to provide yourself if you want to use them; - `zmarkdown-zlatex` compiles Markdown to LaTeX, using the same modules as the server. ### Getting started Simply import one of the three files mentionned above, it will expose a `ZMarkdownZ*`, depending on the imported file. For instance, `zhlite` exposes a `ZMarkdownZHLITE` object. This exported object have a `render` method, that takes the input string and a callback. ### Example ```javascript ZMarkdownZHTML.render("# Hello", (err, vFile) => { console.log(vFile.contents); // will display: "<h1 id="title">Title<a aria-hidden="true" href="#title"><span class="icon icon-link"></span></a></h1>" }); ``` ### Specific MDAST renderer The MDAST renderer is synchronous, unlike the other renderers, so it will return instead of requiring a callback. Moreover, this renderer exposes an `inspect` method, from [unist-util-inspect]. ## Builds ### Dev build If you want to watch the local files while working zmarkdown, you can use `npm run watch:client`. Run the client by opening `./public/index.html`. *Note: the current implementation (parallel-webpack) doesn't support hot-reload, you will have to manually refresh the webpage after each change*. ### Production build To build for production, just run `npm run release`. Generated files are located in `./dist`. [ping]: https://www.npmjs.com/package/remark-ping [iframes]: https://www.npmjs.com/package/remark-iframes [unist-util-inspect]: https://www.npmjs.com/package/unist-util-inspect ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/api.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`html can take custom config 1`] = ` "<h1 id=\\"some-md\\">some md<a aria-hidden=\\"true\\" tabindex=\\"-1\\" href=\\"#some-md\\"><span class=\\"icon icon-link\\"></span></a></h1> <div class=\\"hljs-code-div\\"><pre><code class=\\"hljs language-latex\\">Some LaTeX </code></pre></div>" `; exports[`html uses default config 1`] = ` "<h1 id=\\"some-md\\">some md<a aria-hidden=\\"true\\" tabindex=\\"-1\\" href=\\"#some-md\\"><span class=\\"icon icon-link\\"></span></a></h1> <div class=\\"hljs-code-div hljs-code-latex\\"><pre><code class=\\"hljs language-latex\\">Some LaTeX </code></pre></div>" `; exports[`latex can take custom config 1`] = `"\\\\thisIsNotATitle{some md}"`; exports[`latex uses default config 1`] = `"\\\\levelOneTitle{some md}"`; exports[`mdast can take custom config 1`] = ` Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 10, "line": 1, "offset": 9, }, "indent": Array [], "start": Object { "column": 3, "line": 1, "offset": 2, }, }, "type": "text", "value": "some md", }, ], "depth": 1, "position": Position { "end": Object { "column": 10, "line": 1, "offset": 9, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "heading", }, Object { "children": Array [ Object { "position": Position { "end": Object { "column": 7, "line": 3, "offset": 17, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 11, }, }, "type": "text", "value": "@hello", }, ], "position": Position { "end": Object { "column": 7, "line": 3, "offset": 17, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 11, }, }, "type": "paragraph", }, ], "position": Object { "end": Object { "column": 7, "line": 3, "offset": 17, }, "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "root", } `; exports[`mdast uses default config 1`] = ` Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 10, "line": 1, "offset": 9, }, "indent": Array [], "start": Object { "column": 3, "line": 1, "offset": 2, }, }, "type": "text", "value": "some md", }, ], "depth": 1, "position": Position { "end": Object { "column": 10, "line": 1, "offset": 9, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "heading", }, Object { "children": Array [ Object { "children": Array [ Object { "type": "text", "value": "@", }, Object { "children": Array [ Object { "type": "text", "value": "hello", }, ], "data": Object { "hName": "span", "hProperties": Object { "class": "ping-username", }, }, "type": "emphasis", }, ], "data": Object { "hName": "a", "hProperties": Object { "class": "ping ping-link", "href": "/@hello", "rel": "nofollow", }, }, "position": Position { "end": Object { "column": 7, "line": 3, "offset": 17, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 11, }, }, "type": "ping", "url": "/@hello", "username": "hello", }, ], "position": Position { "end": Object { "column": 7, "line": 3, "offset": 17, }, "indent": Array [], "start": Object { "column": 1, "line": 3, "offset": 11, }, }, "type": "paragraph", }, ], "position": Object { "end": Object { "column": 7, "line": 3, "offset": 17, }, "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "root", } `; exports[`misc can use callback 1`] = `"\\\\levelOneTitle{some md}"`; ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/html-suite.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Sanitize HTML to prevent XSS advanced XSS 1`] = `"<p>This is [not obvious]( lives\\\\0cript:promp('It works !')) !</p>"`; exports[`code highlight special cases discards unused attributes 1`] = ` "<div class=\\"hljs-code-div hljs-code-python\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"1\\"></span><span data-count=\\"2\\"></span><span data-count=\\"3\\"></span><span data-count=\\"4\\"></span><span data-count=\\"5\\"></span></div><pre><code class=\\"hljs language-python\\"><span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">main</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'It\\\\'s amazing!'</span>) <span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">unused</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'Please use me!'</span>) </code></pre></div>" `; exports[`code highlight special cases does not count one-liners 1`] = ` "<div class=\\"hljs-code-div hljs-code-js\\"><pre><code class=\\"hljs language-js\\"><span class=\\"hljs-keyword\\">const</span> a = <span class=\\"hljs-number\\">1</span> </code></pre></div>" `; exports[`code highlight special cases does not highlight console 1`] = ` "<div class=\\"hljs-code-div hljs-code-console\\"><pre><code class=\\"language-console\\">echo \\"Hello world\\" </code></pre></div>" `; exports[`code highlight special cases highlights latex 1`] = ` "<div class=\\"hljs-code-div hljs-code-latex\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"1\\"></span><span data-count=\\"2\\"></span><span data-count=\\"3\\"></span><span data-count=\\"4\\"></span></div><pre><code class=\\"hljs language-latex\\"><span class=\\"hljs-keyword\\">\\\\usepackage</span>{inputenc}[utf8] <span class=\\"hljs-keyword\\">\\\\begin</span>{document} <span class=\\"hljs-keyword\\">\\\\texttt</span>{code} <span class=\\"hljs-keyword\\">\\\\end</span>{document} </code></pre></div>" `; exports[`code highlight special cases supports both hl_lines and linenostart 1`] = ` "<div class=\\"hljs-code-div hljs-code-python\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"3\\"></span><span data-count=\\"4\\" class=\\"hll\\"></span><span data-count=\\"5\\"></span><span data-count=\\"6\\"></span><span data-count=\\"7\\"></span></div><pre><code class=\\"hljs language-python\\"><span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">main</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'It\\\\'s amazing!'</span>) <span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">unused</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'Please use me!'</span>) </code></pre></div>" `; exports[`code highlight special cases supports hl_lines - comma syntax 1`] = ` "<div class=\\"hljs-code-div hljs-code-python\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"1\\"></span><span data-count=\\"2\\"></span><span data-count=\\"3\\"></span><span data-count=\\"4\\" class=\\"hll\\"></span><span data-count=\\"5\\" class=\\"hll\\"></span></div><pre><code class=\\"hljs language-python\\"><span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">main</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'It\\\\'s amazing!'</span>) <span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">unused</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'Please use me!'</span>) </code></pre></div>" `; exports[`code highlight special cases supports hl_lines - interval syntax 1`] = ` "<div class=\\"hljs-code-div hljs-code-python\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"1\\" class=\\"hll\\"></span><span data-count=\\"2\\" class=\\"hll\\"></span><span data-count=\\"3\\" class=\\"hll\\"></span><span data-count=\\"4\\"></span><span data-count=\\"5\\"></span></div><pre><code class=\\"hljs language-python\\"><span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">main</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'It\\\\'s amazing!'</span>) <span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">unused</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'Please use me!'</span>) </code></pre></div>" `; exports[`code highlight special cases supports hl_lines - interval syntax reversed 1`] = ` "<div class=\\"hljs-code-div hljs-code-python\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"1\\" class=\\"hll\\"></span><span data-count=\\"2\\" class=\\"hll\\"></span><span data-count=\\"3\\" class=\\"hll\\"></span><span data-count=\\"4\\"></span><span data-count=\\"5\\"></span></div><pre><code class=\\"hljs language-python\\"><span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">main</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'It\\\\'s amazing!'</span>) <span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">unused</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'Please use me!'</span>) </code></pre></div>" `; exports[`code highlight special cases supports hl_lines - spaced syntax 1`] = ` "<div class=\\"hljs-code-div hljs-code-python\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"1\\"></span><span data-count=\\"2\\"></span><span data-count=\\"3\\"></span><span data-count=\\"4\\" class=\\"hll\\"></span><span data-count=\\"5\\" class=\\"hll\\"></span></div><pre><code class=\\"hljs language-python\\"><span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">main</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'It\\\\'s amazing!'</span>) <span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">unused</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'Please use me!'</span>) </code></pre></div>" `; exports[`code highlight special cases supports linenostart 1`] = ` "<div class=\\"hljs-code-div hljs-code-python\\"><div class=\\"hljs-line-numbers\\"><span data-count=\\"3\\"></span><span data-count=\\"4\\"></span><span data-count=\\"5\\"></span><span data-count=\\"6\\"></span><span data-count=\\"7\\"></span></div><pre><code class=\\"hljs language-python\\"><span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">main</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'It\\\\'s amazing!'</span>) <span class=\\"hljs-function\\"><span class=\\"hljs-keyword\\">def</span> <span class=\\"hljs-title\\">unused</span>():</span> <span class=\\"hljs-built_in\\">print</span>(<span class=\\"hljs-string\\">'Please use me!'</span>) </code></pre></div>" `; exports[`images become figures: does not apply when a caption is present 1`] = ` "<figure><img src=\\"http://example.com\\" alt=\\"foo\\" loading=\\"lazy\\"><figcaption>foo</figcaption></figure> <figure><img src=\\"http://example.com\\" loading=\\"lazy\\"><figcaption>Caption</figcaption></figure> <figure><img src=\\"http://example.com\\" alt=\\"foo\\" loading=\\"lazy\\"><figcaption>Caption</figcaption></figure> <figure><img src=\\"http://example.com\\" loading=\\"lazy\\"><figcaption></figcaption></figure>" `; exports[`images become figures: works with only an image 1`] = `"<figure><img src=\\"http://blabla.fr\\" alt=\\"wrapped into _figure_\\" loading=\\"lazy\\"><figcaption>wrapped into _figure_</figcaption></figure>"`; exports[`pedantic mode disabled unordered lists markers 1`] = ` "<ul> <li>a</li> </ul> <ul> <li>b</li> </ul> <ul> <li>c</li> </ul>" `; exports[`smileys translates >_< 1`] = `"<p>This is funny <img src=\\"/static/smileys/svg/pinch.svg\\" alt=\\">_<\\" class=\\"smiley\\"></p>"`; exports[`smileys translates X/ 1`] = `"<p>This is funny <img src=\\"/static/smileys/svg/pinch.svg\\" alt=\\"X/\\" class=\\"smiley\\"></p>"`; exports[`smileys translates cthulhu 1`] = `"<p><img src=\\"/static/smileys/svg/cthulhu.svg\\" alt=\\"^(;,;)^\\" class=\\"smiley\\"></p>"`; ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/latex-suite.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`blockquote 1`] = ` "\\\\begin{Quotation} a quote \\\\end{Quotation} \\\\begin{Quotation} a multiline quote \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} a quote \\\\end{Quotation} \\\\horizontalLine \\\\begin{Quotation} a multiline quote \\\\end{Quotation}" `; exports[`code 1`] = ` "\\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\begin{CodeBlock}[][1,2]{python} print('bla') print('bla') print('bla') \\\\end{CodeBlock} \\\\begin{CodeBlock}[][][10]{python} print('bla') print('bla') print('bla') \\\\end{CodeBlock} \\\\begin{CodeBlock}[][1,2][10]{python} print('bla') print('bla') print('bla') \\\\end{CodeBlock} \\\\begin{CodeBlock}[][2]{css} .tmp { font-weight: bold; } \\\\end{CodeBlock} \\\\begin{CodeBlock}[][1,3]{css} .tmp { font-weight: bold; } \\\\end{CodeBlock} \\\\begin{CodeBlock}{text} a code without lang \\\\end{CodeBlock}" `; exports[`code+caption 1`] = ` "\\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\captionof{listing}{With Source} \\\\begin{CodeBlock}[][1,2]{python} print('bla') print('bla') print('bla') \\\\end{CodeBlock} \\\\captionof{listing}{With Source}" `; exports[`code+reversed-hl 1`] = ` "\\\\begin{CodeBlock}[][2-3]{python} print('bla') print('bla') print('bla') \\\\end{CodeBlock} \\\\begin{CodeBlock}[][1-2,3-4]{python} print('bla') print('bla') print('bla') print('bla') \\\\end{CodeBlock}" `; exports[`codes in notes 1`] = ` "hello \\\\textsuperscript{\\\\footnotemark[1]}\\\\textsuperscript{\\\\footnotemark[2]} \\\\footnotetext[1]{test \\\\hyperref[appendix-1]{Annexe de code 1}} \\\\footnotetext[2]{test \\\\hyperref[appendix-3]{Annexe de code 3}} \\\\begin{appendices} \\\\levelOneTitle{\\\\label{appendix-1}Annexes 1} \\\\begin{CodeBlock}{javascript} console.log('hello') \\\\end{CodeBlock} \\\\levelOneTitle{\\\\label{appendix-3}Annexes 3} \\\\begin{CodeBlock}{javascript} console.log('hello') \\\\end{CodeBlock} \\\\end{appendices}" `; exports[`custom-blocks 1`] = ` "\\\\begin{Information} info \\\\end{Information} \\\\begin{Warning} warning \\\\end{Warning} \\\\begin{Spoiler} secret \\\\end{Spoiler} \\\\begin{Spoiler} imbricated spoilers and another and that's it \\\\end{Spoiler} \\\\begin{Spoiler} imbricated spoilers second level second level too \\\\end{Spoiler} \\\\begin{Spoiler} imbricated spoilers second level with text in between second level too \\\\end{Spoiler} \\\\begin{Spoiler} imbricated spoilers \\\\begin{CodeBlock}{markdown} and here is some code \\\\end{CodeBlock} second level \\\\end{Spoiler} \\\\begin{Spoiler} do not over-flattenize expected to be flattened first level \\\\begin{Question} this remains a question \\\\end{Question} \\\\end{Spoiler} \\\\begin{Spoiler} flattenize children of children \\\\begin{Question} this remains a question but the content in it is flattened to the question \\\\end{Question} \\\\end{Spoiler} \\\\begin{Question} question \\\\CodeInline{coded} \\\\end{Question} \\\\begin{Error} \\\\textbf{error} foo bar \\\\\\\\ baz \\\\end{Error}" `; exports[`emoticon 1`] = `"foo \\\\smiley{langue.svg} bar \\\\smiley{smile.svg}"`; exports[`figure+caption 1`] = ` "\\\\begin{Quotation}[With Source] a multiline quote \\\\end{Quotation}" `; exports[`footnotes 1`] = ` "\\\\levelOneTitle{mytitle A\\\\textsuperscript{\\\\protect\\\\footnotemark[1]}} \\\\footnotetext[1]{reference in title} \\\\levelOneTitle{mytitle B\\\\textsuperscript{\\\\protect\\\\footnotemark[2]}\\\\protect\\\\footnotetext[2]{footnoterawhead inner}} \\\\levelOneTitle{myti\\\\textit{tle C\\\\textsuperscript{\\\\protect\\\\footnotemark[3]}\\\\protect\\\\footnotetext[3]{foo inner}}} a paragraph\\\\textsuperscript{\\\\footnotemark[4]}\\\\footnotetext[4]{footnoteRawPar inner}" `; exports[`heading 1`] = ` "\\\\levelOneTitle{first level title} \\\\levelTwoTitle{second level title} \\\\levelThreeTitle{third level title} \\\\levelFourTitle{fourth level title} \\\\levelFiveTitle{fifth level title} \\\\levelSixTitle{sixth level title} \\\\levelOneTitle{a \\\\textbackslash{} b} \\\\levelOneTitle{a \\\\} b} \\\\levelOneTitle{a \\\\} \\\\textbackslash{} b} \\\\levelOneTitle{a \\\\} b}" `; exports[`html nodes 1`] = ` "\\\\levelOneTitle{foo} \\\\textbf{something <a> else}" `; exports[`inline-code 1`] = ` "a paragraph \\\\CodeInline{with inline code}. \\\\CodeInline{a multiline inlinecode} \\\\CodeInline{a code with \\\\textbackslash{} } \\\\CodeInline{a \\\\textbackslash{}text in \\\\textbackslash{}LaTeX \\\\textbackslash{}\\\\textbackslash{}}" `; exports[`link 1`] = ` "\\\\externalLink{an external link}{https://wikipedia.com} \\\\externalLink{an internal link}{http://zestedesavoir.com/forums} \\\\externalLink{an \\\\CodeInline{external} link}{https://wikipedia.com}" `; exports[`link with special characters 1`] = `"\\\\externalLink{foo}{http://example.com?a=b\\\\%c\\\\textasciicircum{}\\\\{\\\\}\\\\#foo}"`; exports[`list 1`] = ` "\\\\begin{itemize} \\\\item\\\\relax an \\\\item\\\\relax \\\\textbf{unordered} \\\\item\\\\relax list \\\\end{itemize} \\\\begin{enumerate} \\\\item\\\\relax an \\\\item\\\\relax \\\\CodeInline{ordered} \\\\item\\\\relax list \\\\end{enumerate}" `; exports[`math 1`] = ` "A sentence ($S$) with \\\\textit{italic} and inline math ($C_L$) and $$b$$ another. \\\\[ L = \\\\frac{1}{2} \\\\rho v^2 S C_L \\\\] This should be escaped: $\\\\pink{\\\\text{I love \\\\LaTeX} {ls / > outputrce} {outputrce}}$. hehe" `; exports[`mix-1 1`] = ` "(for convenience, are replaced with simple single spaces in the tests) \\\\levelOneTitle{first 1} foo \\\\\\\\ bar Disrupt \\\\sout{asymmetrical} unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue \\\\textbf{bottle craft} beer hexagon artisan. Chia gochujang crucifix, \\\\textsuperscript{ready} \\\\textsuperscript{made} hammock \\\\textit{blog succulents} sriracha raw denim scenester cray typewriter fashion axe \\\\textsubscript{art} \\\\textit{party}. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street \\\\textsubscript{art} typewriter. \\\\levelTwoTitle{\\\\textit{second} 1} \\\\textbf{Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street \\\\textsubscript{art} synth. Godard synth truffaut selfies, vape fanny \\\\sout{pack} subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial \\\\textit{YOLO}. Williamsburg salvia photo booth \\\\textsuperscript{readymade} listicle man braid.} \\\\horizontalLine \\\\begin{Quotation}[Guru] Marfa pickled helvetica put a bird on it hot chicken williamsburg. Edison bulb asymmetrical \\\\CodeInline{keffiyeh} schlitz iceland put a bird on it hoodie affogato. Tofu tote bag distillery viral knausgaard, health goth asymmetrical. \\\\end{Quotation} Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR\\\\&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. \\\\levelThreeTitle{\\\\sout{third} 1} Art party keytar godard iceland neutra cronut. Austin \\\\textsuperscript{readymade} semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. \\\\CodeInline{foo bar} \\\\begin{Quotation} \\\\CodeInline{fo\\\\textbackslash{}o bar} \\\\end{Quotation} Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. \\\\levelThreeTitle{third 2} \\\\levelFourTitle{\\\\sout{fo}u\\\\textbf{r}t\\\\textit{h} \\\\textit{1}} Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. \\\\levelFiveTitle{fifth 1} Banjo bespoke subway tile, street \\\\textsubscript{a\\\\textit{r}}\\\\textsuperscript{t} drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR\\\\&B, \\\\textsubscript{art} party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. \\\\levelThreeTitle{third 3} Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR\\\\&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. \\\\levelFourTitle{fourth 2} Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. \\\\levelFourTitle{fourth 3} Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. \\\\levelFiveTitle{fifth 2} Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. \\\\levelOneTitle{first 2} \\\\levelTwoTitle{second 2} \\\\levelFiveTitle{fifth 3} Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR\\\\&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh." `; exports[`mix-2 1`] = ` "(for convenience, are replaced with simple single spaces in the tests) \\\\levelOneTitle{first 1} \\\\begin{Quotation}[Quotation Source] Code inside quote \\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\captionof{listing}{code source} \\\\end{Quotation} \\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\captionof{listing}{First \\\\keys{CTRL}+\\\\keys{S}} Code: Second \\\\begin{CodeBlock}{python} print('code without caption') \\\\end{CodeBlock}" `; exports[`mix-3 1`] = ` "\\\\levelOneTitle{right-aligned list} {\\\\raggedleft \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} } \\\\levelOneTitle{centered list} {\\\\centering \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize} } \\\\levelOneTitle{left list} \\\\begin{itemize} \\\\item\\\\relax item \\\\item\\\\relax item \\\\item\\\\relax item \\\\end{itemize}" `; exports[`mix-4 1`] = ` "\\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Sub & Headings & \\\\abbr{ABBR}{abbreviation} \\\\\\\\ \\\\SetCell[r=2]{l} cell \\\\endgraf spans \\\\endgraf rows & \\\\SetCell[c=2]{l} column spanning & \\\\\\\\ & normal & cell \\\\\\\\ multi \\\\endgraf line \\\\endgraf \\\\endgraf cells \\\\endgraf too & \\\\SetCell[c=2]{l} cells can be \\\\endgraf \\\\textit{formatted} \\\\endgraf \\\\textbf{paragraphs} & \\\\\\\\ \\\\end{zdstblr} \\\\captionof{table}{The new table \\\\abbr{ABBR}{abbreviation} \\\\textsuperscript{\\\\protect\\\\footnotemark[1]} with \\\\keys{CTRL} + \\\\keys{S}} \\\\footnotetext[1]{a foot} \\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} title & image \\\\\\\\ space & \\\\image{https://i.ytimg.com/vi/lt0WQ8JzLz4/maxresdefault.jpg}[space] \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} title & code \\\\\\\\ inline & \\\\CodeInline{inline} br \\\\endgraf \\\\CodeInline{inline} \\\\\\\\ block & \\\\hyperref[appendix-1]{Annexe de code 1} \\\\\\\\ \\\\end{zdstblr} \\\\begin{appendices} \\\\levelOneTitle{\\\\label{appendix-1}Annexes 1} \\\\begin{CodeBlock}{python} print('bla') \\\\end{CodeBlock} \\\\end{appendices}" `; exports[`mix-5 1`] = ` "\\\\image{http://www.numerama.com/content/uploads/2016/07/espace.jpg}[espace\\\\textsuperscript{\\\\protect\\\\footnotemark[1]}] \\\\footnotetext[1]{Two things are infinite: the universe and human stupidity.}" `; exports[`mix-6 1`] = ` "\\\\iframe{https://www.youtube.com/embed/8TQIvdFl4aU?feature=oembed}[Video][a caption] \\\\iframe{https://www.youtube.com/embed/8TQIvdFl4aU?feature=oembed}[Video][] no caption" `; exports[`paragraph 1`] = ` "\\\\levelOneTitle{first 1} Disrupt asymmetrical unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue bottle craft beer hexagon artisan. Chia gochujang crucifix, readymade hammock blog succulents sriracha raw denim scenester cray typewriter fashion axe art party. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street art typewriter. \\\\levelTwoTitle{second 1} Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street art synth. Godard synth truffaut selfies, vape fanny pack subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial YOLO. Williamsburg salvia photo booth readymade listicle man braid. Marfa pickled helvetica put a bird on it hot chicken williamsburg. Edison bulb asymmetrical keffiyeh schlitz iceland put a bird on it hoodie affogato. Tofu tote bag distillery viral knausgaard, health goth asymmetrical. Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR\\\\&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. \\\\levelThreeTitle{third 1} Art party keytar godard iceland neutra cronut. Austin readymade semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. \\\\levelThreeTitle{third 2} \\\\levelFourTitle{fourth 1} Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. \\\\levelFiveTitle{fifth 1} Banjo bespoke subway tile, street art drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR\\\\&B, art party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. \\\\levelThreeTitle{third 3} Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR\\\\&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. \\\\levelFourTitle{fourth 2} Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. \\\\levelFourTitle{fourth 3} Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. \\\\levelFiveTitle{fifth 2} Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. \\\\levelOneTitle{first 2} \\\\levelTwoTitle{second 2} \\\\levelFiveTitle{fifth 3} Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR\\\\&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh." `; exports[`ping 1`] = `"Hello \\\\ping{you} and \\\\ping{you_too}, and \\\\ping{also you}"`; exports[`quizz with answers 1`] = ` "\\\\begin{Neutral}[{{question}}] \\\\begin{itemize} \\\\item\\\\relax bad answer \\\\item\\\\relax good answer \\\\item\\\\relax other bad answer \\\\end{itemize} \\\\end{Neutral} \\\\begin{Spoiler}[{{Correction}}] \\\\begin{itemize} \\\\item[$\\\\square$]\\\\relax bad answer \\\\item[$\\\\boxtimes$]\\\\relax good answer \\\\item[$\\\\square$]\\\\relax other bad answer \\\\end{itemize} the good answer is \\"good answer\\" as it better matches the philosophy of what good is for answers \\\\end{Spoiler}" `; exports[`regression: code block without language 1`] = ` "\\\\begin{CodeBlock}{text} a \\\\end{CodeBlock}" `; exports[`table 1`] = ` "\\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} 1 & 2 \\\\\\\\ 1 & 2 \\\\\\\\ 1 & 2 \\\\\\\\ 1 & 2 \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} 1 & 2 & 3 \\\\\\\\ 1 & 2 \\\\\\\\ 1 & 2 & 3 \\\\\\\\ 1 & 2 & 3 \\\\\\\\ \\\\end{zdstblr}" `; ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/legacy-suite.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`basic renders amps-and-angle-encoding.txt 1`] = ` "<p>AT&T has an ampersand in their name.</p> <p>AT&amp;T is another way to write it.</p> <p>This & that.</p> <p>4 < 5.</p> <p>6 > 5.</p> <p>Here's a <a href=\\"http://example.com/?foo=1&bar=2\\">link</a> with an ampersand in the URL.</p> <p>Here's a link with an amersand in the link text: <a href=\\"http://att.com/\\" title=\\"AT&T\\">AT&T</a>.</p> <p>Here's an inline <a href=\\"/script?foo=1&bar=2\\">link</a>.</p> <p>Here's an inline <a href=\\"/script?foo=1&bar=2\\">link</a>.</p>" `; exports[`basic renders amps-and-angle-encoding.txt 2`] = ` "AT\\\\&T has an ampersand in their name. AT\\\\&T is another way to write it. This \\\\& that. 4 < 5. 6 > 5. Here's a \\\\hyperref[1]{link} with an ampersand in the URL. Here's a link with an amersand in the link text: \\\\hyperref[2]{AT\\\\&T}. Here's an inline \\\\externalLink{link}{http://zestedesavoir.com/script?foo=1\\\\&bar=2}. Here's an inline \\\\externalLink{link}{http://zestedesavoir.com/script?foo=1\\\\&bar=2}. \\\\footnote{\\\\label{1}\\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2}} \\\\footnote{\\\\label{2}\\\\externalLink{http://att.com/}{http://att.com/}}" `; exports[`basic renders angle-links-and-img.txt 1`] = ` "<p><a href=\\"simple%20link\\" title=\\"my title\\">link</a> <img src=\\"http://example.com/image.jpg\\" alt=\\"image\\" loading=\\"lazy\\"> <a href=\\"http://example.com/(()((())923)(\\">link</a> <img src=\\"link(()))(\\" alt=\\"image\\" loading=\\"lazy\\"></p>" `; exports[`basic renders angle-links-and-img.txt 2`] = ` "\\\\externalLink{link}{simple link} \\\\inlineImage{http://example.com/image.jpg} \\\\externalLink{link}{http://example.com/(()((())923)(} \\\\inlineImage{link(()))(}" `; exports[`basic renders auto-links.txt 1`] = ` "<p>Link: <a href=\\"http://example.com/\\">http://example.com/</a>.</p> <p>Https link: <a href=\\"https://example.com\\">https://example.com</a></p> <p>Ftp link: <a href=\\"ftp://example.com\\">ftp://example.com</a></p> <p>With an ampersand: <a href=\\"http://example.com/?foo=1&bar=2\\">http://example.com/?foo=1&bar=2</a></p> <ul> <li>In a list?</li> <li><a href=\\"http://example.com/\\">http://example.com/</a></li> <li>It should.</li> </ul> <blockquote> <p>Blockquoted: <a href=\\"http://example.com/\\">http://example.com/</a></p> </blockquote> <p>Auto-links should not occur here: <code><http://example.com/></code></p> <pre><code>or here: <http://example.com/> </code></pre>" `; exports[`basic renders auto-links.txt 2`] = ` "Link: \\\\externalLink{http://example.com/}{http://example.com/}. Https link: \\\\externalLink{https://example.com}{https://example.com} Ftp link: \\\\externalLink{ftp://example.com}{ftp://example.com} With an ampersand: \\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2} \\\\begin{itemize} \\\\item\\\\relax In a list? \\\\item\\\\relax \\\\externalLink{http://example.com/}{http://example.com/} \\\\item\\\\relax It should. \\\\end{itemize} \\\\begin{Quotation} Blockquoted: \\\\externalLink{http://example.com/}{http://example.com/} \\\\end{Quotation} Auto-links should not occur here: \\\\CodeInline{<http://example.com/>} \\\\begin{CodeBlock}{text} or here: <http://example.com/> \\\\end{CodeBlock}" `; exports[`basic renders backlash-escapes.txt 1`] = ` "<p>These should all get escaped:</p> <p>Backslash: \\\\</p> <p>Backtick: \`</p> <p>Asterisk: *</p> <p>Underscore: _</p> <p>Left brace: {</p> <p>Right brace: }</p> <p>Left bracket: [</p> <p>Right bracket: ]</p> <p>Left paren: (</p> <p>Right paren: )</p> <p>Greater-than: ></p> <p>Hash: #</p> <p>Period: .</p> <p>Bang: !</p> <p>Plus: +</p> <p>Minus: -</p> <p>These should not, because they occur within a code block:</p> <pre><code>Backslash: \\\\\\\\ Backtick: \\\\\` Asterisk: \\\\* Underscore: \\\\_ Left brace: \\\\{ Right brace: \\\\} Left bracket: \\\\[ Right bracket: \\\\] Left paren: \\\\( Right paren: \\\\) Greater-than: \\\\> Hash: \\\\# Period: \\\\. Bang: \\\\! Plus: \\\\+ Minus: \\\\- </code></pre> <p>Nor should these, which occur in code spans:</p> <p>Backslash: <code>\\\\\\\\</code></p> <p>Backtick: <code>\\\\\`</code></p> <p>Asterisk: <code>\\\\*</code></p> <p>Underscore: <code>\\\\_</code></p> <p>Left brace: <code>\\\\{</code></p> <p>Right brace: <code>\\\\}</code></p> <p>Left bracket: <code>\\\\[</code></p> <p>Right bracket: <code>\\\\]</code></p> <p>Left paren: <code>\\\\(</code></p> <p>Right paren: <code>\\\\)</code></p> <p>Greater-than: <code>\\\\></code></p> <p>Hash: <code>\\\\#</code></p> <p>Period: <code>\\\\.</code></p> <p>Bang: <code>\\\\!</code></p> <p>Plus: <code>\\\\+</code></p> <p>Minus: <code>\\\\-</code></p>" `; exports[`basic renders backlash-escapes.txt 2`] = ` "These should all get escaped: Backslash: \\\\textbackslash{} Backtick: \` Asterisk: * Underscore: \\\\_ Left brace: \\\\{ Right brace: \\\\} Left bracket: [ Right bracket: ] Left paren: ( Right paren: ) Greater-than: > Hash: \\\\# Period: . Bang: ! Plus: + Minus: - These should not, because they occur within a code block: \\\\begin{CodeBlock}{text} Backslash: \\\\\\\\ Backtick: \\\\\` Asterisk: \\\\* Underscore: \\\\_ Left brace: \\\\{ Right brace: \\\\} Left bracket: \\\\[ Right bracket: \\\\] Left paren: \\\\( Right paren: \\\\) Greater-than: \\\\> Hash: \\\\# Period: \\\\. Bang: \\\\! Plus: \\\\+ Minus: \\\\- \\\\end{CodeBlock} Nor should these, which occur in code spans: Backslash: \\\\CodeInline{\\\\textbackslash{}\\\\textbackslash{}} Backtick: \\\\CodeInline{\\\\textbackslash{}\`} Asterisk: \\\\CodeInline{\\\\textbackslash{}*} Underscore: \\\\CodeInline{\\\\textbackslash{}\\\\_} Left brace: \\\\CodeInline{\\\\textbackslash{}\\\\{} Right brace: \\\\CodeInline{\\\\textbackslash{}\\\\}} Left bracket: \\\\CodeInline{\\\\textbackslash{}[} Right bracket: \\\\CodeInline{\\\\textbackslash{}]} Left paren: \\\\CodeInline{\\\\textbackslash{}(} Right paren: \\\\CodeInline{\\\\textbackslash{})} Greater-than: \\\\CodeInline{\\\\textbackslash{}>} Hash: \\\\CodeInline{\\\\textbackslash{}\\\\#} Period: \\\\CodeInline{\\\\textbackslash{}.} Bang: \\\\CodeInline{\\\\textbackslash{}!} Plus: \\\\CodeInline{\\\\textbackslash{}+} Minus: \\\\CodeInline{\\\\textbackslash{}-}" `; exports[`basic renders blockquotes-with-code-blocks.txt 1`] = ` "<blockquote> <p>Example:</p> <pre><code>sub status { print \\"working\\"; } </code></pre> <p>Or:</p> <pre><code>sub status { return \\"working\\"; } </code></pre> </blockquote>" `; exports[`basic renders blockquotes-with-code-blocks.txt 2`] = ` "\\\\begin{Quotation} Example: \\\\begin{CodeBlock}{text} sub status { print \\"working\\"; } \\\\end{CodeBlock} Or: \\\\begin{CodeBlock}{text} sub status { return \\"working\\"; } \\\\end{CodeBlock} \\\\end{Quotation}" `; exports[`basic renders codeblock-in-list.txt 1`] = ` "<ul> <li> <p>A list item with a code block</p> <pre><code> Some *code* </code></pre> </li> <li> <p>Another list item</p> <pre><code> More code And more code </code></pre> </li> </ul>" `; exports[`basic renders codeblock-in-list.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax A list item with a code block \\\\begin{CodeBlock}{text} Some *code* \\\\end{CodeBlock} \\\\item\\\\relax Another list item \\\\begin{CodeBlock}{text} More code And more code \\\\end{CodeBlock} \\\\end{itemize}" `; exports[`basic renders hard-wrapped.txt 1`] = ` "<p>In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the middle of a paragraph looked like a list item.</p> <p>Here's one with a bullet.</p> <ul> <li>criminey.</li> </ul>" `; exports[`basic renders hard-wrapped.txt 2`] = ` "In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the middle of a paragraph looked like a list item. Here's one with a bullet. \\\\begin{itemize} \\\\item\\\\relax criminey. \\\\end{itemize}" `; exports[`basic renders horizontal-rules.txt 1`] = ` "<p>Dashes:</p> <hr> <hr> <hr> <hr> <pre><code>--- </code></pre> <hr> <hr> <hr> <hr> <pre><code>- - - </code></pre> <p>Asterisks:</p> <hr> <hr> <hr> <hr> <pre><code>*** </code></pre> <hr> <hr> <hr> <hr> <pre><code>* * * </code></pre> <p>Underscores:</p> <hr> <hr> <hr> <hr> <pre><code>___ </code></pre> <hr> <hr> <hr> <hr> <pre><code>_ _ _ </code></pre>" `; exports[`basic renders horizontal-rules.txt 2`] = ` "Dashes: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} --- \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} - - - \\\\end{CodeBlock} Asterisks: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} * * * \\\\end{CodeBlock} Underscores: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} ___ \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} _ _ _ \\\\end{CodeBlock}" `; exports[`basic renders inline-html-advanced.txt 1`] = ` "<p>Simple block on one line:</p> <p><div>foo</div></p> <p>And nested without indentation:</p> <p><div> <div> <div> foo </div> </div> <div>bar</div> </div></p>" `; exports[`basic renders inline-html-advanced.txt 2`] = ` "Simple block on one line: <div>foo</div> And nested without indentation: <div> <div> <div> foo </div> </div> <div>bar</div> </div>" `; exports[`basic renders inline-html-comments.txt 1`] = ` "<p>Paragraph one.</p> <p><!-- This is a simple comment --></p> <p><!-- This is another comment. --></p> <p>Paragraph two.</p> <p><!-- one comment block -- -- with two comments --></p> <p>The end.</p>" `; exports[`basic renders inline-html-comments.txt 2`] = ` "Paragraph one. <!-- This is a simple comment --> <!-- This is another comment. --> Paragraph two. <!-- one comment block -- -- with two comments --> The end." `; exports[`basic renders inline-html-simple.txt 1`] = ` "<p>Here's a simple block:</p> <p><div> foo </div></p> <p>This should be a code block, though:</p> <pre><code><div> foo </div> </code></pre> <p>As should this:</p> <pre><code><div>foo</div> </code></pre> <p>Now, nested:</p> <p><div> <div> <div> foo </div> </div> </div></p> <p>This should just be an HTML comment:</p> <p><!-- Comment --></p> <p>Multiline:</p> <p><!-- Blah Blah --></p> <p>Code block:</p> <pre><code><!-- Comment --> </code></pre> <p>Just plain comment, with trailing spaces on the line:</p> <p><!-- foo --> </p> <p>Code:</p> <pre><code><hr /> </code></pre> <p>Hr's:</p> <p><hr></p> <p><hr/></p> <p><hr /></p> <p><hr> </p> <p><hr/> </p> <p><hr /> </p> <p><hr class=\\"foo\\" id=\\"bar\\" /></p> <p><hr class=\\"foo\\" id=\\"bar\\"/></p> <p><hr class=\\"foo\\" id=\\"bar\\" ></p> <p><some <a href=\\"http://example.com\\">weird</a> stuff></p>" `; exports[`basic renders inline-html-simple.txt 2`] = ` "Here's a simple block: <div> foo </div> This should be a code block, though: \\\\begin{CodeBlock}{text} <div> foo </div> \\\\end{CodeBlock} As should this: \\\\begin{CodeBlock}{text} <div>foo</div> \\\\end{CodeBlock} Now, nested: <div> <div> <div> foo </div> </div> </div> This should just be an HTML comment: <!-- Comment --> Multiline: <!-- Blah Blah --> Code block: \\\\begin{CodeBlock}{text} <!-- Comment --> \\\\end{CodeBlock} Just plain comment, with trailing spaces on the line: <!-- foo --> Code: \\\\begin{CodeBlock}{text} <hr /> \\\\end{CodeBlock} Hr's: <hr> <hr/> <hr /> <hr> <hr/> <hr /> <hr class=\\"foo\\" id=\\"bar\\" /> <hr class=\\"foo\\" id=\\"bar\\"/> <hr class=\\"foo\\" id=\\"bar\\" > <some \\\\externalLink{weird}{http://example.com} stuff>" `; exports[`basic renders links-inline.txt 1`] = ` "<p>Just a <a href=\\"/url/\\">URL</a>.</p> <p><a href=\\"/url/\\" title=\\"title\\">URL and title</a>.</p> <p><a href=\\"/url/\\" title=\\"title preceded by two spaces\\">URL and title</a>.</p> <p><a href=\\"/url/\\" title=\\"title preceded by a tab\\">URL and title</a>.</p> <p><a href=\\"\\">Empty</a>.</p>" `; exports[`basic renders links-inline.txt 2`] = ` "Just a \\\\externalLink{URL}{http://zestedesavoir.com/url/}. \\\\externalLink{URL and title}{http://zestedesavoir.com/url/}. \\\\externalLink{URL and title}{http://zestedesavoir.com/url/}. \\\\externalLink{URL and title}{http://zestedesavoir.com/url/}. ." `; exports[`basic renders links-reference.txt 1`] = ` "<p>Foo <a href=\\"/url/\\" title=\\"Title\\">bar</a>.</p> <p>Foo <a href=\\"/url/\\" title=\\"Title\\">bar</a>.</p> <p>Foo <a href=\\"/url/\\" title=\\"Title\\">bar</a>.</p> <p>With <a href=\\"/url/\\">embedded [brackets]</a>.</p> <p>Indented <a href=\\"/url\\">once</a>.</p> <p>Indented <a href=\\"/url\\">twice</a>.</p> <p>Indented <a href=\\"/url\\">thrice</a>.</p> <p>Indented [four][] times.</p> <pre><code>[four]: /url </code></pre> <p>With <a href=\\"http://example.com/\\" title=\\"Angle Brackets\\">angle brackets</a>.</p> <p>And <a href=\\"http://example.com/\\" title=\\"Without angle brackets.\\">without</a>.</p> <p>With <a href=\\"http://example.com\\" title=\\"Yes this works\\">line breaks</a></p> <p>and <a href=\\"http://example.com\\" title=\\"Yes this works\\">line breaks</a> with one space.</p> <p>and [line<br> breaks[] with two spaces.</p> <p><a href=\\"http://example.com\\" title=\\"No more hanging empty bracket!\\">short ref</a></p> <p><a href=\\"http://example.com\\" title=\\"No more hanging empty bracket!\\">short ref</a></p> <p><a href=\\"http://example.com\\" title=\\"Title on next line.\\">a ref</a></p>" `; exports[`basic renders links-reference.txt 2`] = ` "Foo \\\\hyperref[1]{bar}. Foo \\\\hyperref[1]{bar}. Foo \\\\hyperref[1]{bar}. \\\\footnote{\\\\label{1}\\\\externalLink{/url/}{http://zestedesavoir.com/url/}} With \\\\hyperref[b]{embedded [brackets]}. Indented \\\\hyperref[once]{once}. Indented \\\\hyperref[twice]{twice}. Indented \\\\hyperref[thrice]{thrice}. Indented [four] times. \\\\footnote{\\\\label{once}\\\\externalLink{/url}{http://zestedesavoir.com/url}} \\\\footnote{\\\\label{twice}\\\\externalLink{/url}{http://zestedesavoir.com/url}} \\\\footnote{\\\\label{thrice}\\\\externalLink{/url}{http://zestedesavoir.com/url}} \\\\begin{CodeBlock}{text} [four]: /url \\\\end{CodeBlock} \\\\footnote{\\\\label{b}\\\\externalLink{/url/}{http://zestedesavoir.com/url/}} With \\\\hyperref[angle brackets]{angle brackets}. And \\\\hyperref[without]{without}. \\\\footnote{\\\\label{angle brackets}\\\\externalLink{http://example.com/}{http://example.com/}} \\\\footnote{\\\\label{without}\\\\externalLink{http://example.com/}{http://example.com/}} With \\\\hyperref[line breaks]{line breaks} and \\\\hyperref[line breaks]{line breaks} with one space. and [line \\\\\\\\ breaks[] with two spaces. \\\\footnote{\\\\label{line breaks}\\\\externalLink{http://example.com}{http://example.com}} \\\\hyperref[short ref]{short ref} \\\\hyperref[short ref]{short ref} \\\\footnote{\\\\label{short ref}\\\\externalLink{http://example.com}{http://example.com}} \\\\hyperref[a ref]{a ref} \\\\footnote{\\\\label{a ref}\\\\externalLink{http://example.com}{http://example.com}}" `; exports[`basic renders nested-blockquotes.txt 1`] = ` "<blockquote> <p>foo</p> <blockquote> <p>bar</p> </blockquote> <p>foo</p> </blockquote>" `; exports[`basic renders nested-blockquotes.txt 2`] = ` "\\\\begin{Quotation} foo \\\\begin{Quotation} bar \\\\end{Quotation} foo \\\\end{Quotation}" `; exports[`basic renders ordered-and-unordered-list.txt 1`] = ` "<p>Unordered</p> <p>Asterisks tight:</p> <ul> <li>asterisk 1</li> <li>asterisk 2</li> <li>asterisk 3</li> </ul> <p>Asterisks loose:</p> <ul> <li> <p>asterisk 1</p> </li> <li> <p>asterisk 2</p> </li> <li> <p>asterisk 3</p> </li> </ul> <hr> <p>Pluses tight:</p> <ul> <li>Plus 1</li> <li>Plus 2</li> <li>Plus 3</li> </ul> <p>Pluses loose:</p> <ul> <li> <p>Plus 1</p> </li> <li> <p>Plus 2</p> </li> <li> <p>Plus 3</p> </li> </ul> <hr> <p>Minuses tight:</p> <ul> <li>Minus 1</li> <li>Minus 2</li> <li>Minus 3</li> </ul> <p>Minuses loose:</p> <ul> <li> <p>Minus 1</p> </li> <li> <p>Minus 2</p> </li> <li> <p>Minus 3</p> </li> </ul> <hr> <p>Ordered</p> <p>Tight:</p> <ol> <li>First</li> <li>Second</li> <li>Third</li> </ol> <p>and:</p> <ol> <li>One</li> <li>Two</li> <li>Three</li> </ol> <p>Loose using tabs:</p> <ol> <li> <p>First</p> </li> <li> <p>Second</p> </li> <li> <p>Third</p> </li> </ol> <p>and using spaces:</p> <ol> <li> <p>One</p> </li> <li> <p>Two</p> </li> <li> <p>Three</p> </li> </ol> <p>Multiple paragraphs:</p> <ol> <li> <p>Item 1, graf one.</p> <p>Item 2. graf two. The quick brown fox jumped over the lazy dog's back.</p> </li> <li> <p>Item 2.</p> </li> <li> <p>Item 3.</p> </li> </ol> <hr> <p>Nested</p> <ul> <li>Tab <ul> <li>Tab <ul> <li>Tab</li> </ul> </li> </ul> </li> </ul> <p>Here's another:</p> <ol> <li>First</li> <li>Second: <ul> <li>Fee</li> <li>Fie</li> <li>Foe</li> </ul> </li> <li>Third</li> </ol> <p>Same thing but with paragraphs:</p> <ol> <li> <p>First</p> </li> <li> <p>Second:</p> <ul> <li>Fee</li> <li>Fie</li> <li>Foe</li> </ul> </li> <li> <p>Third</p> </li> </ol>" `; exports[`basic renders ordered-and-unordered-list.txt 2`] = ` "Unordered Asterisks tight: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} Asterisks loose: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\horizontalLine Pluses tight: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} Pluses loose: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} \\\\horizontalLine Minuses tight: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} Minuses loose: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} \\\\horizontalLine Ordered Tight: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Loose using tabs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and using spaces: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Multiple paragraphs: \\\\begin{enumerate} \\\\item\\\\relax Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. \\\\item\\\\relax Item 2. \\\\item\\\\relax Item 3. \\\\end{enumerate} \\\\horizontalLine Nested \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} Here's another: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate} Same thing but with paragraphs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate}" `; exports[`basic renders strong-and-em-together.txt 1`] = ` "<p><strong><em>This is strong and em.</em></strong></p> <p>So is <strong><em>this</em></strong> word.</p> <p><strong><em>This is strong and em.</em></strong></p> <p>So is <strong><em>this</em></strong> word.</p>" `; exports[`basic renders strong-and-em-together.txt 2`] = ` "\\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word. \\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word." `; exports[`basic renders tabs.txt 1`] = ` "<ul> <li> <p>this is a list item indented with tabs</p> </li> <li> <p>this is a list item indented with spaces</p> </li> </ul> <p>Code:</p> <pre><code>this code block is indented by one tab </code></pre> <p>And:</p> <pre><code> this code block is indented by two tabs </code></pre> <p>And:</p> <pre><code>+ this is an example list item indented with tabs + this is an example list item indented with spaces </code></pre>" `; exports[`basic renders tabs.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax this is a list item indented with tabs \\\\item\\\\relax this is a list item indented with spaces \\\\end{itemize} Code: \\\\begin{CodeBlock}{text} this code block is indented by one tab \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} this code block is indented by two tabs \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} + this is an example list item indented with tabs + this is an example list item indented with spaces \\\\end{CodeBlock}" `; exports[`basic renders tidyness.txt 1`] = ` "<blockquote> <p>A list within a blockquote:</p> <ul> <li>asterisk 1</li> <li>asterisk 2</li> <li>asterisk 3</li> </ul> </blockquote>" `; exports[`basic renders tidyness.txt 2`] = ` "\\\\begin{Quotation} A list within a blockquote: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\end{Quotation}" `; exports[`extensions renders abbr.txt 1`] = ` "<p>An <abbr title=\\"Abbreviation\\">ABBR</abbr>: \\"<abbr title=\\"Reference\\">REF</abbr>\\". ref and REFERENCE should be ignored.</p> <p>The <abbr title=\\"Hyper Text Markup Language\\">HTML</abbr> specification is maintained by the <abbr title=\\"World Wide Web Consortium\\">W3C</abbr>.</p>" `; exports[`extensions renders abbr.txt 2`] = ` "An \\\\abbr{ABBR}{Abbreviation}: \\"\\\\abbr{REF}{Reference}\\". ref and REFERENCE should be ignored. The \\\\abbr{HTML}{Hyper Text Markup Language} specification is maintained by the \\\\abbr{W3C}{World Wide Web Consortium}." `; exports[`extensions renders fenced_code.txt 1`] = ` "<p>index 0000000..6e956a9</p> <pre><code>--- /dev/null +++ b/test/data/stripped_text/mike-30-lili @@ -0,0 +1,27 @@ +Summary: + drift_mod.py | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +commit da4bfb04debdd994683740878d09988b2641513d +Author: Mike Dirolf <mike@dirolf.com> +Date: Tue Jan 17 13:42:28 2012 -0500 + +\`\`\` +minor: just wanted to push something. +\`\`\` + +diff --git a/drift_mod.py b/drift_mod.py +index 34dfba6..8a88a69 100644 + +\`\`\` +--- a/drift_mod.py ++++ b/drift_mod.py +@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^(' + '|\\\\+ .*' + '|- .*' + ')$') ++ + def wrap_context_diffs(message_text): + return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN, + CONTEXT_DIFF_LINE_PATTERN, +\`\`\` </code></pre>" `; exports[`extensions renders fenced_code.txt 2`] = ` "index 0000000..6e956a9 \\\\begin{CodeBlock}{text} --- /dev/null +++ b/test/data/stripped_text/mike-30-lili @@ -0,0 +1,27 @@ +Summary: + drift_mod.py | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +commit da4bfb04debdd994683740878d09988b2641513d +Author: Mike Dirolf <mike@dirolf.com> +Date: Tue Jan 17 13:42:28 2012 -0500 + +\`\`\` +minor: just wanted to push something. +\`\`\` + +diff --git a/drift_mod.py b/drift_mod.py +index 34dfba6..8a88a69 100644 + +\`\`\` +--- a/drift_mod.py ++++ b/drift_mod.py +@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^(' + '|\\\\+ .*' + '|- .*' + ')$') ++ + def wrap_context_diffs(message_text): + return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN, + CONTEXT_DIFF_LINE_PATTERN, +\`\`\` \\\\end{CodeBlock}" `; exports[`extensions renders footnote.txt 1`] = ` "<p>This is the body with a footnote<sup id=\\"fnref-1-shortId\\"><a href=\\"#fn-1-shortId\\" class=\\"footnote-ref\\">1</a></sup> or two<sup id=\\"fnref-2-shortId\\"><a href=\\"#fn-2-shortId\\" class=\\"footnote-ref\\">2</a></sup> or more<sup id=\\"fnref-3-shortId\\"><a href=\\"#fn-3-shortId\\" class=\\"footnote-ref\\">3</a></sup> <sup id=\\"fnref-4-shortId\\"><a href=\\"#fn-4-shortId\\" class=\\"footnote-ref\\">4</a></sup> <sup id=\\"fnref-5-shortId\\"><a href=\\"#fn-5-shortId\\" class=\\"footnote-ref\\">5</a></sup>.</p> <p>Also a reference that does not exist<sup id=\\"fnref-6-shortId\\"><a href=\\"#fn-6-shortId\\" class=\\"footnote-ref\\">6</a></sup>.</p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1-shortId\\"> <p>Footnote that ends with a list:</p> <ul> <li>item 1</li> <li>item 2</li> </ul> <p><a href=\\"#fnref-1-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 1\\">↩</a></p> </li> <li id=\\"fn-2-shortId\\"> <blockquote> <p>This footnote is a blockquote.</p> </blockquote> <p><a href=\\"#fnref-2-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 2\\">↩</a></p> </li> <li id=\\"fn-3-shortId\\"> <p>A simple oneliner.<a href=\\"#fnref-3-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 3\\">↩</a></p> </li> <li id=\\"fn-4-shortId\\"> <p>A footnote with multiple paragraphs.</p> <p>Paragraph two.<a href=\\"#fnref-4-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 4\\">↩</a></p> </li> <li id=\\"fn-5-shortId\\"> <p>First line of first paragraph. Second line of first paragraph is not intended. Nor is third...<a href=\\"#fnref-5-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 5\\">↩</a></p> </li> </ol> </div>" `; exports[`extensions renders footnote.txt 2`] = ` "This is the body with a footnote\\\\textsuperscript{\\\\footnotemark[1]} or two\\\\textsuperscript{\\\\footnotemark[2]} or more\\\\textsuperscript{\\\\footnotemark[3]} \\\\textsuperscript{\\\\footnotemark[4]} \\\\textsuperscript{\\\\footnotemark[5]}. Also a reference that does not exist\\\\textsuperscript{\\\\footnotemark[6]}. \\\\footnotetext[1]{Footnote that ends with a list: \\\\begin{itemize} \\\\item\\\\relax item 1 \\\\item\\\\relax item 2 \\\\end{itemize}} \\\\footnotetext[2]{\\\\begin{Quotation} This footnote is a blockquote. \\\\end{Quotation}} \\\\footnotetext[3]{A simple oneliner.} \\\\footnotetext[4]{A footnote with multiple paragraphs. Paragraph two.} \\\\footnotetext[5]{First line of first paragraph. Second line of first paragraph is not intended. Nor is third...}" `; exports[`extensions renders footnote_many_footnotes.txt 1`] = ` "<p>Something<sup id=\\"fnref-1-shortId\\"><a href=\\"#fn-1-shortId\\" class=\\"footnote-ref\\">1</a></sup></p> <p>Something<sup id=\\"fnref-2-shortId\\"><a href=\\"#fn-2-shortId\\" class=\\"footnote-ref\\">2</a></sup></p> <p>Something<sup id=\\"fnref-3-shortId\\"><a href=\\"#fn-3-shortId\\" class=\\"footnote-ref\\">3</a></sup></p> <p>Something<sup id=\\"fnref-4-shortId\\"><a href=\\"#fn-4-shortId\\" class=\\"footnote-ref\\">4</a></sup></p> <p>Something<sup id=\\"fnref-5-shortId\\"><a href=\\"#fn-5-shortId\\" class=\\"footnote-ref\\">5</a></sup></p> <p>Something<sup id=\\"fnref-6-shortId\\"><a href=\\"#fn-6-shortId\\" class=\\"footnote-ref\\">6</a></sup></p> <p>Something<sup id=\\"fnref-7-shortId\\"><a href=\\"#fn-7-shortId\\" class=\\"footnote-ref\\">7</a></sup></p> <p>Something<sup id=\\"fnref-8-shortId\\"><a href=\\"#fn-8-shortId\\" class=\\"footnote-ref\\">8</a></sup></p> <p>Something<sup id=\\"fnref-9-shortId\\"><a href=\\"#fn-9-shortId\\" class=\\"footnote-ref\\">9</a></sup></p> <p>Something<sup id=\\"fnref-10-shortId\\"><a href=\\"#fn-10-shortId\\" class=\\"footnote-ref\\">10</a></sup></p> <p>Something<sup id=\\"fnref-11-shortId\\"><a href=\\"#fn-11-shortId\\" class=\\"footnote-ref\\">11</a></sup></p> <p>Something<sup id=\\"fnref-12-shortId\\"><a href=\\"#fn-12-shortId\\" class=\\"footnote-ref\\">12</a></sup></p> <p>Something<sup id=\\"fnref-13-shortId\\"><a href=\\"#fn-13-shortId\\" class=\\"footnote-ref\\">13</a></sup></p> <p>Something<sup id=\\"fnref-14-shortId\\"><a href=\\"#fn-14-shortId\\" class=\\"footnote-ref\\">14</a></sup></p> <p>Something<sup id=\\"fnref-15-shortId\\"><a href=\\"#fn-15-shortId\\" class=\\"footnote-ref\\">15</a></sup></p> <p>Something<sup id=\\"fnref-16-shortId\\"><a href=\\"#fn-16-shortId\\" class=\\"footnote-ref\\">16</a></sup></p> <p>Something<sup id=\\"fnref-17-shortId\\"><a href=\\"#fn-17-shortId\\" class=\\"footnote-ref\\">17</a></sup></p> <p>Something<sup id=\\"fnref-18-shortId\\"><a href=\\"#fn-18-shortId\\" class=\\"footnote-ref\\">18</a></sup></p> <p>Something<sup id=\\"fnref-19-shortId\\"><a href=\\"#fn-19-shortId\\" class=\\"footnote-ref\\">19</a></sup></p> <p>Something<sup id=\\"fnref-20-shortId\\"><a href=\\"#fn-20-shortId\\" class=\\"footnote-ref\\">20</a></sup></p> <p>Something<sup id=\\"fnref-21-shortId\\"><a href=\\"#fn-21-shortId\\" class=\\"footnote-ref\\">21</a></sup></p> <p>Something<sup id=\\"fnref-22-shortId\\"><a href=\\"#fn-22-shortId\\" class=\\"footnote-ref\\">22</a></sup></p> <p>Something<sup id=\\"fnref-23-shortId\\"><a href=\\"#fn-23-shortId\\" class=\\"footnote-ref\\">23</a></sup></p> <p>Something<sup id=\\"fnref-24-shortId\\"><a href=\\"#fn-24-shortId\\" class=\\"footnote-ref\\">24</a></sup></p> <p>Something<sup id=\\"fnref-25-shortId\\"><a href=\\"#fn-25-shortId\\" class=\\"footnote-ref\\">25</a></sup></p> <p>Something<sup id=\\"fnref-26-shortId\\"><a href=\\"#fn-26-shortId\\" class=\\"footnote-ref\\">26</a></sup></p> <p>Something<sup id=\\"fnref-27-shortId\\"><a href=\\"#fn-27-shortId\\" class=\\"footnote-ref\\">27</a></sup></p> <p>Something<sup id=\\"fnref-28-shortId\\"><a href=\\"#fn-28-shortId\\" class=\\"footnote-ref\\">28</a></sup></p> <p>Something<sup id=\\"fnref-29-shortId\\"><a href=\\"#fn-29-shortId\\" class=\\"footnote-ref\\">29</a></sup></p> <p>Something<sup id=\\"fnref-30-shortId\\"><a href=\\"#fn-30-shortId\\" class=\\"footnote-ref\\">30</a></sup></p> <p>Something<sup id=\\"fnref-31-shortId\\"><a href=\\"#fn-31-shortId\\" class=\\"footnote-ref\\">31</a></sup></p> <p>Something<sup id=\\"fnref-32-shortId\\"><a href=\\"#fn-32-shortId\\" class=\\"footnote-ref\\">32</a></sup></p> <p>Something<sup id=\\"fnref-33-shortId\\"><a href=\\"#fn-33-shortId\\" class=\\"footnote-ref\\">33</a></sup></p> <p>Something<sup id=\\"fnref-34-shortId\\"><a href=\\"#fn-34-shortId\\" class=\\"footnote-ref\\">34</a></sup></p> <p>Something<sup id=\\"fnref-35-shortId\\"><a href=\\"#fn-35-shortId\\" class=\\"footnote-ref\\">35</a></sup></p> <p>Something<sup id=\\"fnref-36-shortId\\"><a href=\\"#fn-36-shortId\\" class=\\"footnote-ref\\">36</a></sup></p> <p>Something<sup id=\\"fnref-37-shortId\\"><a href=\\"#fn-37-shortId\\" class=\\"footnote-ref\\">37</a></sup></p> <p>Something<sup id=\\"fnref-38-shortId\\"><a href=\\"#fn-38-shortId\\" class=\\"footnote-ref\\">38</a></sup></p> <p>Something<sup id=\\"fnref-39-shortId\\"><a href=\\"#fn-39-shortId\\" class=\\"footnote-ref\\">39</a></sup></p> <p>Something<sup id=\\"fnref-40-shortId\\"><a href=\\"#fn-40-shortId\\" class=\\"footnote-ref\\">40</a></sup></p> <p>Something<sup id=\\"fnref-41-shortId\\"><a href=\\"#fn-41-shortId\\" class=\\"footnote-ref\\">41</a></sup></p> <p>Something<sup id=\\"fnref-42-shortId\\"><a href=\\"#fn-42-shortId\\" class=\\"footnote-ref\\">42</a></sup></p> <p>Something<sup id=\\"fnref-43-shortId\\"><a href=\\"#fn-43-shortId\\" class=\\"footnote-ref\\">43</a></sup></p> <p>Something<sup id=\\"fnref-44-shortId\\"><a href=\\"#fn-44-shortId\\" class=\\"footnote-ref\\">44</a></sup></p> <p>Something<sup id=\\"fnref-45-shortId\\"><a href=\\"#fn-45-shortId\\" class=\\"footnote-ref\\">45</a></sup></p> <p>Something<sup id=\\"fnref-46-shortId\\"><a href=\\"#fn-46-shortId\\" class=\\"footnote-ref\\">46</a></sup></p> <p>Something<sup id=\\"fnref-47-shortId\\"><a href=\\"#fn-47-shortId\\" class=\\"footnote-ref\\">47</a></sup></p> <p>Something<sup id=\\"fnref-48-shortId\\"><a href=\\"#fn-48-shortId\\" class=\\"footnote-ref\\">48</a></sup></p> <p>Something<sup id=\\"fnref-49-shortId\\"><a href=\\"#fn-49-shortId\\" class=\\"footnote-ref\\">49</a></sup></p> <p>Something<sup id=\\"fnref-50-shortId\\"><a href=\\"#fn-50-shortId\\" class=\\"footnote-ref\\">50</a></sup></p> <p>Something<sup id=\\"fnref-51-shortId\\"><a href=\\"#fn-51-shortId\\" class=\\"footnote-ref\\">51</a></sup></p> <p>Something<sup id=\\"fnref-52-shortId\\"><a href=\\"#fn-52-shortId\\" class=\\"footnote-ref\\">52</a></sup></p> <p>Something<sup id=\\"fnref-53-shortId\\"><a href=\\"#fn-53-shortId\\" class=\\"footnote-ref\\">53</a></sup></p> <p>Something<sup id=\\"fnref-54-shortId\\"><a href=\\"#fn-54-shortId\\" class=\\"footnote-ref\\">54</a></sup></p> <p>Something<sup id=\\"fnref-55-shortId\\"><a href=\\"#fn-55-shortId\\" class=\\"footnote-ref\\">55</a></sup></p> <p>Something<sup id=\\"fnref-56-shortId\\"><a href=\\"#fn-56-shortId\\" class=\\"footnote-ref\\">56</a></sup></p> <p>Something<sup id=\\"fnref-57-shortId\\"><a href=\\"#fn-57-shortId\\" class=\\"footnote-ref\\">57</a></sup></p> <p>Something<sup id=\\"fnref-58-shortId\\"><a href=\\"#fn-58-shortId\\" class=\\"footnote-ref\\">58</a></sup></p> <p>Something<sup id=\\"fnref-59-shortId\\"><a href=\\"#fn-59-shortId\\" class=\\"footnote-ref\\">59</a></sup></p> <p>Something<sup id=\\"fnref-60-shortId\\"><a href=\\"#fn-60-shortId\\" class=\\"footnote-ref\\">60</a></sup></p> <p>Something<sup id=\\"fnref-61-shortId\\"><a href=\\"#fn-61-shortId\\" class=\\"footnote-ref\\">61</a></sup></p> <p>Something<sup id=\\"fnref-62-shortId\\"><a href=\\"#fn-62-shortId\\" class=\\"footnote-ref\\">62</a></sup></p> <p>Something<sup id=\\"fnref-63-shortId\\"><a href=\\"#fn-63-shortId\\" class=\\"footnote-ref\\">63</a></sup></p> <p>Something<sup id=\\"fnref-64-shortId\\"><a href=\\"#fn-64-shortId\\" class=\\"footnote-ref\\">64</a></sup></p> <p>Something<sup id=\\"fnref-65-shortId\\"><a href=\\"#fn-65-shortId\\" class=\\"footnote-ref\\">65</a></sup></p> <p>Something<sup id=\\"fnref-66-shortId\\"><a href=\\"#fn-66-shortId\\" class=\\"footnote-ref\\">66</a></sup></p> <p>Something<sup id=\\"fnref-67-shortId\\"><a href=\\"#fn-67-shortId\\" class=\\"footnote-ref\\">67</a></sup></p> <p>Something<sup id=\\"fnref-68-shortId\\"><a href=\\"#fn-68-shortId\\" class=\\"footnote-ref\\">68</a></sup></p> <p>Something<sup id=\\"fnref-69-shortId\\"><a href=\\"#fn-69-shortId\\" class=\\"footnote-ref\\">69</a></sup></p> <p>Something<sup id=\\"fnref-70-shortId\\"><a href=\\"#fn-70-shortId\\" class=\\"footnote-ref\\">70</a></sup></p> <p>Something<sup id=\\"fnref-71-shortId\\"><a href=\\"#fn-71-shortId\\" class=\\"footnote-ref\\">71</a></sup></p> <p>Something<sup id=\\"fnref-72-shortId\\"><a href=\\"#fn-72-shortId\\" class=\\"footnote-ref\\">72</a></sup></p> <p>Something<sup id=\\"fnref-73-shortId\\"><a href=\\"#fn-73-shortId\\" class=\\"footnote-ref\\">73</a></sup></p> <p>Something<sup id=\\"fnref-74-shortId\\"><a href=\\"#fn-74-shortId\\" class=\\"footnote-ref\\">74</a></sup></p> <p>Something<sup id=\\"fnref-75-shortId\\"><a href=\\"#fn-75-shortId\\" class=\\"footnote-ref\\">75</a></sup></p> <p>Something<sup id=\\"fnref-76-shortId\\"><a href=\\"#fn-76-shortId\\" class=\\"footnote-ref\\">76</a></sup></p> <p>Something<sup id=\\"fnref-77-shortId\\"><a href=\\"#fn-77-shortId\\" class=\\"footnote-ref\\">77</a></sup></p> <p>Something<sup id=\\"fnref-78-shortId\\"><a href=\\"#fn-78-shortId\\" class=\\"footnote-ref\\">78</a></sup></p> <p>Something<sup id=\\"fnref-79-shortId\\"><a href=\\"#fn-79-shortId\\" class=\\"footnote-ref\\">79</a></sup></p> <p>Something<sup id=\\"fnref-80-shortId\\"><a href=\\"#fn-80-shortId\\" class=\\"footnote-ref\\">80</a></sup></p> <p>Something<sup id=\\"fnref-81-shortId\\"><a href=\\"#fn-81-shortId\\" class=\\"footnote-ref\\">81</a></sup></p> <p>Something<sup id=\\"fnref-82-shortId\\"><a href=\\"#fn-82-shortId\\" class=\\"footnote-ref\\">82</a></sup></p> <p>Something<sup id=\\"fnref-83-shortId\\"><a href=\\"#fn-83-shortId\\" class=\\"footnote-ref\\">83</a></sup></p> <p>Something<sup id=\\"fnref-84-shortId\\"><a href=\\"#fn-84-shortId\\" class=\\"footnote-ref\\">84</a></sup></p> <p>Something<sup id=\\"fnref-85-shortId\\"><a href=\\"#fn-85-shortId\\" class=\\"footnote-ref\\">85</a></sup></p> <p>Something<sup id=\\"fnref-86-shortId\\"><a href=\\"#fn-86-shortId\\" class=\\"footnote-ref\\">86</a></sup></p> <p>Something<sup id=\\"fnref-87-shortId\\"><a href=\\"#fn-87-shortId\\" class=\\"footnote-ref\\">87</a></sup></p> <p>Something<sup id=\\"fnref-88-shortId\\"><a href=\\"#fn-88-shortId\\" class=\\"footnote-ref\\">88</a></sup></p> <p>Something<sup id=\\"fnref-89-shortId\\"><a href=\\"#fn-89-shortId\\" class=\\"footnote-ref\\">89</a></sup></p> <p>Something<sup id=\\"fnref-90-shortId\\"><a href=\\"#fn-90-shortId\\" class=\\"footnote-ref\\">90</a></sup></p> <p>Something<sup id=\\"fnref-91-shortId\\"><a href=\\"#fn-91-shortId\\" class=\\"footnote-ref\\">91</a></sup></p> <p>Something<sup id=\\"fnref-92-shortId\\"><a href=\\"#fn-92-shortId\\" class=\\"footnote-ref\\">92</a></sup></p> <p>Something<sup id=\\"fnref-93-shortId\\"><a href=\\"#fn-93-shortId\\" class=\\"footnote-ref\\">93</a></sup></p> <p>Something<sup id=\\"fnref-94-shortId\\"><a href=\\"#fn-94-shortId\\" class=\\"footnote-ref\\">94</a></sup></p> <p>Something<sup id=\\"fnref-95-shortId\\"><a href=\\"#fn-95-shortId\\" class=\\"footnote-ref\\">95</a></sup></p> <p>Something<sup id=\\"fnref-96-shortId\\"><a href=\\"#fn-96-shortId\\" class=\\"footnote-ref\\">96</a></sup></p> <p>Something<sup id=\\"fnref-97-shortId\\"><a href=\\"#fn-97-shortId\\" class=\\"footnote-ref\\">97</a></sup></p> <p>Something<sup id=\\"fnref-98-shortId\\"><a href=\\"#fn-98-shortId\\" class=\\"footnote-ref\\">98</a></sup></p> <p>Something<sup id=\\"fnref-99-shortId\\"><a href=\\"#fn-99-shortId\\" class=\\"footnote-ref\\">99</a></sup></p> <p>Something<sup id=\\"fnref-100-shortId\\"><a href=\\"#fn-100-shortId\\" class=\\"footnote-ref\\">100</a></sup></p> <p>Something<sup id=\\"fnref-101-shortId\\"><a href=\\"#fn-101-shortId\\" class=\\"footnote-ref\\">101</a></sup></p> <p>Something<sup id=\\"fnref-102-shortId\\"><a href=\\"#fn-102-shortId\\" class=\\"footnote-ref\\">102</a></sup></p> <p>Something<sup id=\\"fnref-103-shortId\\"><a href=\\"#fn-103-shortId\\" class=\\"footnote-ref\\">103</a></sup></p> <p>Something<sup id=\\"fnref-104-shortId\\"><a href=\\"#fn-104-shortId\\" class=\\"footnote-ref\\">104</a></sup></p> <p>Something<sup id=\\"fnref-105-shortId\\"><a href=\\"#fn-105-shortId\\" class=\\"footnote-ref\\">105</a></sup></p> <p>Something<sup id=\\"fnref-106-shortId\\"><a href=\\"#fn-106-shortId\\" class=\\"footnote-ref\\">106</a></sup></p> <p>Something<sup id=\\"fnref-107-shortId\\"><a href=\\"#fn-107-shortId\\" class=\\"footnote-ref\\">107</a></sup></p> <p>Something<sup id=\\"fnref-108-shortId\\"><a href=\\"#fn-108-shortId\\" class=\\"footnote-ref\\">108</a></sup></p> <p>Something<sup id=\\"fnref-109-shortId\\"><a href=\\"#fn-109-shortId\\" class=\\"footnote-ref\\">109</a></sup></p> <p>Something<sup id=\\"fnref-110-shortId\\"><a href=\\"#fn-110-shortId\\" class=\\"footnote-ref\\">110</a></sup></p> <p>Something<sup id=\\"fnref-111-shortId\\"><a href=\\"#fn-111-shortId\\" class=\\"footnote-ref\\">111</a></sup></p> <p>Something<sup id=\\"fnref-112-shortId\\"><a href=\\"#fn-112-shortId\\" class=\\"footnote-ref\\">112</a></sup></p> <p>Something<sup id=\\"fnref-113-shortId\\"><a href=\\"#fn-113-shortId\\" class=\\"footnote-ref\\">113</a></sup></p> <p>Something<sup id=\\"fnref-114-shortId\\"><a href=\\"#fn-114-shortId\\" class=\\"footnote-ref\\">114</a></sup></p> <p>Something<sup id=\\"fnref-115-shortId\\"><a href=\\"#fn-115-shortId\\" class=\\"footnote-ref\\">115</a></sup></p> <p>Something<sup id=\\"fnref-116-shortId\\"><a href=\\"#fn-116-shortId\\" class=\\"footnote-ref\\">116</a></sup></p> <p>Something<sup id=\\"fnref-117-shortId\\"><a href=\\"#fn-117-shortId\\" class=\\"footnote-ref\\">117</a></sup></p> <p>Something<sup id=\\"fnref-118-shortId\\"><a href=\\"#fn-118-shortId\\" class=\\"footnote-ref\\">118</a></sup></p> <p>Something<sup id=\\"fnref-119-shortId\\"><a href=\\"#fn-119-shortId\\" class=\\"footnote-ref\\">119</a></sup></p> <p>Something<sup id=\\"fnref-120-shortId\\"><a href=\\"#fn-120-shortId\\" class=\\"footnote-ref\\">120</a></sup></p> <p>Something<sup id=\\"fnref-121-shortId\\"><a href=\\"#fn-121-shortId\\" class=\\"footnote-ref\\">121</a></sup></p> <p>Something<sup id=\\"fnref-122-shortId\\"><a href=\\"#fn-122-shortId\\" class=\\"footnote-ref\\">122</a></sup></p> <p>Something<sup id=\\"fnref-123-shortId\\"><a href=\\"#fn-123-shortId\\" class=\\"footnote-ref\\">123</a></sup></p> <p>Something<sup id=\\"fnref-124-shortId\\"><a href=\\"#fn-124-shortId\\" class=\\"footnote-ref\\">124</a></sup></p> <p>Something<sup id=\\"fnref-125-shortId\\"><a href=\\"#fn-125-shortId\\" class=\\"footnote-ref\\">125</a></sup></p> <p>Something<sup id=\\"fnref-126-shortId\\"><a href=\\"#fn-126-shortId\\" class=\\"footnote-ref\\">126</a></sup></p> <p>Something<sup id=\\"fnref-127-shortId\\"><a href=\\"#fn-127-shortId\\" class=\\"footnote-ref\\">127</a></sup></p> <p>Something<sup id=\\"fnref-128-shortId\\"><a href=\\"#fn-128-shortId\\" class=\\"footnote-ref\\">128</a></sup></p> <p>Something<sup id=\\"fnref-129-shortId\\"><a href=\\"#fn-129-shortId\\" class=\\"footnote-ref\\">129</a></sup></p> <p>Something<sup id=\\"fnref-130-shortId\\"><a href=\\"#fn-130-shortId\\" class=\\"footnote-ref\\">130</a></sup></p> <p>Something<sup id=\\"fnref-131-shortId\\"><a href=\\"#fn-131-shortId\\" class=\\"footnote-ref\\">131</a></sup></p> <p>Something<sup id=\\"fnref-132-shortId\\"><a href=\\"#fn-132-shortId\\" class=\\"footnote-ref\\">132</a></sup></p> <p>Something<sup id=\\"fnref-133-shortId\\"><a href=\\"#fn-133-shortId\\" class=\\"footnote-ref\\">133</a></sup></p> <p>Something<sup id=\\"fnref-134-shortId\\"><a href=\\"#fn-134-shortId\\" class=\\"footnote-ref\\">134</a></sup></p> <p>Something<sup id=\\"fnref-135-shortId\\"><a href=\\"#fn-135-shortId\\" class=\\"footnote-ref\\">135</a></sup></p> <p>Something<sup id=\\"fnref-136-shortId\\"><a href=\\"#fn-136-shortId\\" class=\\"footnote-ref\\">136</a></sup></p> <p>Something<sup id=\\"fnref-137-shortId\\"><a href=\\"#fn-137-shortId\\" class=\\"footnote-ref\\">137</a></sup></p> <p>Something<sup id=\\"fnref-138-shortId\\"><a href=\\"#fn-138-shortId\\" class=\\"footnote-ref\\">138</a></sup></p> <p>Something<sup id=\\"fnref-139-shortId\\"><a href=\\"#fn-139-shortId\\" class=\\"footnote-ref\\">139</a></sup></p> <p>Something<sup id=\\"fnref-140-shortId\\"><a href=\\"#fn-140-shortId\\" class=\\"footnote-ref\\">140</a></sup></p> <p>Something<sup id=\\"fnref-141-shortId\\"><a href=\\"#fn-141-shortId\\" class=\\"footnote-ref\\">141</a></sup></p> <p>Something<sup id=\\"fnref-142-shortId\\"><a href=\\"#fn-142-shortId\\" class=\\"footnote-ref\\">142</a></sup></p> <p>Something<sup id=\\"fnref-143-shortId\\"><a href=\\"#fn-143-shortId\\" class=\\"footnote-ref\\">143</a></sup></p> <p>Something<sup id=\\"fnref-144-shortId\\"><a href=\\"#fn-144-shortId\\" class=\\"footnote-ref\\">144</a></sup></p> <p>Something<sup id=\\"fnref-145-shortId\\"><a href=\\"#fn-145-shortId\\" class=\\"footnote-ref\\">145</a></sup></p> <p>Something<sup id=\\"fnref-146-shortId\\"><a href=\\"#fn-146-shortId\\" class=\\"footnote-ref\\">146</a></sup></p> <p>Something<sup id=\\"fnref-147-shortId\\"><a href=\\"#fn-147-shortId\\" class=\\"footnote-ref\\">147</a></sup></p> <p>Something<sup id=\\"fnref-148-shortId\\"><a href=\\"#fn-148-shortId\\" class=\\"footnote-ref\\">148</a></sup></p> <p>Something<sup id=\\"fnref-149-shortId\\"><a href=\\"#fn-149-shortId\\" class=\\"footnote-ref\\">149</a></sup></p> <p>Something<sup id=\\"fnref-150-shortId\\"><a href=\\"#fn-150-shortId\\" class=\\"footnote-ref\\">150</a></sup></p> <div class=\\"footnotes\\"> <hr> <ol> <li id=\\"fn-1-shortId\\">Another thing<a href=\\"#fnref-1-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 1\\">↩</a></li> <li id=\\"fn-2-shortId\\">Another thing<a href=\\"#fnref-2-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 2\\">↩</a></li> <li id=\\"fn-3-shortId\\">Another thing<a href=\\"#fnref-3-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 3\\">↩</a></li> <li id=\\"fn-4-shortId\\">Another thing<a href=\\"#fnref-4-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 4\\">↩</a></li> <li id=\\"fn-5-shortId\\">Another thing<a href=\\"#fnref-5-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 5\\">↩</a></li> <li id=\\"fn-6-shortId\\">Another thing<a href=\\"#fnref-6-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 6\\">↩</a></li> <li id=\\"fn-7-shortId\\">Another thing<a href=\\"#fnref-7-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 7\\">↩</a></li> <li id=\\"fn-8-shortId\\">Another thing<a href=\\"#fnref-8-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 8\\">↩</a></li> <li id=\\"fn-9-shortId\\">Another thing<a href=\\"#fnref-9-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 9\\">↩</a></li> <li id=\\"fn-10-shortId\\">Another thing<a href=\\"#fnref-10-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 10\\">↩</a></li> <li id=\\"fn-11-shortId\\">Another thing<a href=\\"#fnref-11-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 11\\">↩</a></li> <li id=\\"fn-12-shortId\\">Another thing<a href=\\"#fnref-12-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 12\\">↩</a></li> <li id=\\"fn-13-shortId\\">Another thing<a href=\\"#fnref-13-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 13\\">↩</a></li> <li id=\\"fn-14-shortId\\">Another thing<a href=\\"#fnref-14-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 14\\">↩</a></li> <li id=\\"fn-15-shortId\\">Another thing<a href=\\"#fnref-15-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 15\\">↩</a></li> <li id=\\"fn-16-shortId\\">Another thing<a href=\\"#fnref-16-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 16\\">↩</a></li> <li id=\\"fn-17-shortId\\">Another thing<a href=\\"#fnref-17-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 17\\">↩</a></li> <li id=\\"fn-18-shortId\\">Another thing<a href=\\"#fnref-18-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 18\\">↩</a></li> <li id=\\"fn-19-shortId\\">Another thing<a href=\\"#fnref-19-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 19\\">↩</a></li> <li id=\\"fn-20-shortId\\">Another thing<a href=\\"#fnref-20-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 20\\">↩</a></li> <li id=\\"fn-21-shortId\\">Another thing<a href=\\"#fnref-21-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 21\\">↩</a></li> <li id=\\"fn-22-shortId\\">Another thing<a href=\\"#fnref-22-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 22\\">↩</a></li> <li id=\\"fn-23-shortId\\">Another thing<a href=\\"#fnref-23-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 23\\">↩</a></li> <li id=\\"fn-24-shortId\\">Another thing<a href=\\"#fnref-24-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 24\\">↩</a></li> <li id=\\"fn-25-shortId\\">Another thing<a href=\\"#fnref-25-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 25\\">↩</a></li> <li id=\\"fn-26-shortId\\">Another thing<a href=\\"#fnref-26-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 26\\">↩</a></li> <li id=\\"fn-27-shortId\\">Another thing<a href=\\"#fnref-27-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 27\\">↩</a></li> <li id=\\"fn-28-shortId\\">Another thing<a href=\\"#fnref-28-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 28\\">↩</a></li> <li id=\\"fn-29-shortId\\">Another thing<a href=\\"#fnref-29-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 29\\">↩</a></li> <li id=\\"fn-30-shortId\\">Another thing<a href=\\"#fnref-30-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 30\\">↩</a></li> <li id=\\"fn-31-shortId\\">Another thing<a href=\\"#fnref-31-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 31\\">↩</a></li> <li id=\\"fn-32-shortId\\">Another thing<a href=\\"#fnref-32-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 32\\">↩</a></li> <li id=\\"fn-33-shortId\\">Another thing<a href=\\"#fnref-33-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 33\\">↩</a></li> <li id=\\"fn-34-shortId\\">Another thing<a href=\\"#fnref-34-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 34\\">↩</a></li> <li id=\\"fn-35-shortId\\">Another thing<a href=\\"#fnref-35-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 35\\">↩</a></li> <li id=\\"fn-36-shortId\\">Another thing<a href=\\"#fnref-36-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 36\\">↩</a></li> <li id=\\"fn-37-shortId\\">Another thing<a href=\\"#fnref-37-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 37\\">↩</a></li> <li id=\\"fn-38-shortId\\">Another thing<a href=\\"#fnref-38-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 38\\">↩</a></li> <li id=\\"fn-39-shortId\\">Another thing<a href=\\"#fnref-39-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 39\\">↩</a></li> <li id=\\"fn-40-shortId\\">Another thing<a href=\\"#fnref-40-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 40\\">↩</a></li> <li id=\\"fn-41-shortId\\">Another thing<a href=\\"#fnref-41-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 41\\">↩</a></li> <li id=\\"fn-42-shortId\\">Another thing<a href=\\"#fnref-42-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 42\\">↩</a></li> <li id=\\"fn-43-shortId\\">Another thing<a href=\\"#fnref-43-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 43\\">↩</a></li> <li id=\\"fn-44-shortId\\">Another thing<a href=\\"#fnref-44-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 44\\">↩</a></li> <li id=\\"fn-45-shortId\\">Another thing<a href=\\"#fnref-45-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 45\\">↩</a></li> <li id=\\"fn-46-shortId\\">Another thing<a href=\\"#fnref-46-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 46\\">↩</a></li> <li id=\\"fn-47-shortId\\">Another thing<a href=\\"#fnref-47-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 47\\">↩</a></li> <li id=\\"fn-48-shortId\\">Another thing<a href=\\"#fnref-48-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 48\\">↩</a></li> <li id=\\"fn-49-shortId\\">Another thing<a href=\\"#fnref-49-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 49\\">↩</a></li> <li id=\\"fn-50-shortId\\">Another thing<a href=\\"#fnref-50-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 50\\">↩</a></li> <li id=\\"fn-51-shortId\\">Another thing<a href=\\"#fnref-51-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 51\\">↩</a></li> <li id=\\"fn-52-shortId\\">Another thing<a href=\\"#fnref-52-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 52\\">↩</a></li> <li id=\\"fn-53-shortId\\">Another thing<a href=\\"#fnref-53-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 53\\">↩</a></li> <li id=\\"fn-54-shortId\\">Another thing<a href=\\"#fnref-54-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 54\\">↩</a></li> <li id=\\"fn-55-shortId\\">Another thing<a href=\\"#fnref-55-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 55\\">↩</a></li> <li id=\\"fn-56-shortId\\">Another thing<a href=\\"#fnref-56-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 56\\">↩</a></li> <li id=\\"fn-57-shortId\\">Another thing<a href=\\"#fnref-57-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 57\\">↩</a></li> <li id=\\"fn-58-shortId\\">Another thing<a href=\\"#fnref-58-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 58\\">↩</a></li> <li id=\\"fn-59-shortId\\">Another thing<a href=\\"#fnref-59-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 59\\">↩</a></li> <li id=\\"fn-60-shortId\\">Another thing<a href=\\"#fnref-60-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 60\\">↩</a></li> <li id=\\"fn-61-shortId\\">Another thing<a href=\\"#fnref-61-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 61\\">↩</a></li> <li id=\\"fn-62-shortId\\">Another thing<a href=\\"#fnref-62-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 62\\">↩</a></li> <li id=\\"fn-63-shortId\\">Another thing<a href=\\"#fnref-63-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 63\\">↩</a></li> <li id=\\"fn-64-shortId\\">Another thing<a href=\\"#fnref-64-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 64\\">↩</a></li> <li id=\\"fn-65-shortId\\">Another thing<a href=\\"#fnref-65-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 65\\">↩</a></li> <li id=\\"fn-66-shortId\\">Another thing<a href=\\"#fnref-66-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 66\\">↩</a></li> <li id=\\"fn-67-shortId\\">Another thing<a href=\\"#fnref-67-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 67\\">↩</a></li> <li id=\\"fn-68-shortId\\">Another thing<a href=\\"#fnref-68-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 68\\">↩</a></li> <li id=\\"fn-69-shortId\\">Another thing<a href=\\"#fnref-69-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 69\\">↩</a></li> <li id=\\"fn-70-shortId\\">Another thing<a href=\\"#fnref-70-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 70\\">↩</a></li> <li id=\\"fn-71-shortId\\">Another thing<a href=\\"#fnref-71-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 71\\">↩</a></li> <li id=\\"fn-72-shortId\\">Another thing<a href=\\"#fnref-72-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 72\\">↩</a></li> <li id=\\"fn-73-shortId\\">Another thing<a href=\\"#fnref-73-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 73\\">↩</a></li> <li id=\\"fn-74-shortId\\">Another thing<a href=\\"#fnref-74-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 74\\">↩</a></li> <li id=\\"fn-75-shortId\\">Another thing<a href=\\"#fnref-75-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 75\\">↩</a></li> <li id=\\"fn-76-shortId\\">Another thing<a href=\\"#fnref-76-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 76\\">↩</a></li> <li id=\\"fn-77-shortId\\">Another thing<a href=\\"#fnref-77-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 77\\">↩</a></li> <li id=\\"fn-78-shortId\\">Another thing<a href=\\"#fnref-78-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 78\\">↩</a></li> <li id=\\"fn-79-shortId\\">Another thing<a href=\\"#fnref-79-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 79\\">↩</a></li> <li id=\\"fn-80-shortId\\">Another thing<a href=\\"#fnref-80-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 80\\">↩</a></li> <li id=\\"fn-81-shortId\\">Another thing<a href=\\"#fnref-81-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 81\\">↩</a></li> <li id=\\"fn-82-shortId\\">Another thing<a href=\\"#fnref-82-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 82\\">↩</a></li> <li id=\\"fn-83-shortId\\">Another thing<a href=\\"#fnref-83-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 83\\">↩</a></li> <li id=\\"fn-84-shortId\\">Another thing<a href=\\"#fnref-84-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 84\\">↩</a></li> <li id=\\"fn-85-shortId\\">Another thing<a href=\\"#fnref-85-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 85\\">↩</a></li> <li id=\\"fn-86-shortId\\">Another thing<a href=\\"#fnref-86-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 86\\">↩</a></li> <li id=\\"fn-87-shortId\\">Another thing<a href=\\"#fnref-87-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 87\\">↩</a></li> <li id=\\"fn-88-shortId\\">Another thing<a href=\\"#fnref-88-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 88\\">↩</a></li> <li id=\\"fn-89-shortId\\">Another thing<a href=\\"#fnref-89-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 89\\">↩</a></li> <li id=\\"fn-90-shortId\\">Another thing<a href=\\"#fnref-90-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 90\\">↩</a></li> <li id=\\"fn-91-shortId\\">Another thing<a href=\\"#fnref-91-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 91\\">↩</a></li> <li id=\\"fn-92-shortId\\">Another thing<a href=\\"#fnref-92-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 92\\">↩</a></li> <li id=\\"fn-93-shortId\\">Another thing<a href=\\"#fnref-93-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 93\\">↩</a></li> <li id=\\"fn-94-shortId\\">Another thing<a href=\\"#fnref-94-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 94\\">↩</a></li> <li id=\\"fn-95-shortId\\">Another thing<a href=\\"#fnref-95-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 95\\">↩</a></li> <li id=\\"fn-96-shortId\\">Another thing<a href=\\"#fnref-96-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 96\\">↩</a></li> <li id=\\"fn-97-shortId\\">Another thing<a href=\\"#fnref-97-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 97\\">↩</a></li> <li id=\\"fn-98-shortId\\">Another thing<a href=\\"#fnref-98-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 98\\">↩</a></li> <li id=\\"fn-99-shortId\\">Another thing<a href=\\"#fnref-99-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 99\\">↩</a></li> <li id=\\"fn-100-shortId\\">Another thing<a href=\\"#fnref-100-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 100\\">↩</a></li> <li id=\\"fn-101-shortId\\">Another thing<a href=\\"#fnref-101-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 101\\">↩</a></li> <li id=\\"fn-102-shortId\\">Another thing<a href=\\"#fnref-102-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 102\\">↩</a></li> <li id=\\"fn-103-shortId\\">Another thing<a href=\\"#fnref-103-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 103\\">↩</a></li> <li id=\\"fn-104-shortId\\">Another thing<a href=\\"#fnref-104-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 104\\">↩</a></li> <li id=\\"fn-105-shortId\\">Another thing<a href=\\"#fnref-105-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 105\\">↩</a></li> <li id=\\"fn-106-shortId\\">Another thing<a href=\\"#fnref-106-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 106\\">↩</a></li> <li id=\\"fn-107-shortId\\">Another thing<a href=\\"#fnref-107-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 107\\">↩</a></li> <li id=\\"fn-108-shortId\\">Another thing<a href=\\"#fnref-108-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 108\\">↩</a></li> <li id=\\"fn-109-shortId\\">Another thing<a href=\\"#fnref-109-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 109\\">↩</a></li> <li id=\\"fn-110-shortId\\">Another thing<a href=\\"#fnref-110-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 110\\">↩</a></li> <li id=\\"fn-111-shortId\\">Another thing<a href=\\"#fnref-111-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 111\\">↩</a></li> <li id=\\"fn-112-shortId\\">Another thing<a href=\\"#fnref-112-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 112\\">↩</a></li> <li id=\\"fn-113-shortId\\">Another thing<a href=\\"#fnref-113-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 113\\">↩</a></li> <li id=\\"fn-114-shortId\\">Another thing<a href=\\"#fnref-114-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 114\\">↩</a></li> <li id=\\"fn-115-shortId\\">Another thing<a href=\\"#fnref-115-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 115\\">↩</a></li> <li id=\\"fn-116-shortId\\">Another thing<a href=\\"#fnref-116-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 116\\">↩</a></li> <li id=\\"fn-117-shortId\\">Another thing<a href=\\"#fnref-117-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 117\\">↩</a></li> <li id=\\"fn-118-shortId\\">Another thing<a href=\\"#fnref-118-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 118\\">↩</a></li> <li id=\\"fn-119-shortId\\">Another thing<a href=\\"#fnref-119-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 119\\">↩</a></li> <li id=\\"fn-120-shortId\\">Another thing<a href=\\"#fnref-120-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 120\\">↩</a></li> <li id=\\"fn-121-shortId\\">Another thing<a href=\\"#fnref-121-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 121\\">↩</a></li> <li id=\\"fn-122-shortId\\">Another thing<a href=\\"#fnref-122-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 122\\">↩</a></li> <li id=\\"fn-123-shortId\\">Another thing<a href=\\"#fnref-123-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 123\\">↩</a></li> <li id=\\"fn-124-shortId\\">Another thing<a href=\\"#fnref-124-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 124\\">↩</a></li> <li id=\\"fn-125-shortId\\">Another thing<a href=\\"#fnref-125-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 125\\">↩</a></li> <li id=\\"fn-126-shortId\\">Another thing<a href=\\"#fnref-126-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 126\\">↩</a></li> <li id=\\"fn-127-shortId\\">Another thing<a href=\\"#fnref-127-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 127\\">↩</a></li> <li id=\\"fn-128-shortId\\">Another thing<a href=\\"#fnref-128-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 128\\">↩</a></li> <li id=\\"fn-129-shortId\\">Another thing<a href=\\"#fnref-129-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 129\\">↩</a></li> <li id=\\"fn-130-shortId\\">Another thing<a href=\\"#fnref-130-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 130\\">↩</a></li> <li id=\\"fn-131-shortId\\">Another thing<a href=\\"#fnref-131-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 131\\">↩</a></li> <li id=\\"fn-132-shortId\\">Another thing<a href=\\"#fnref-132-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 132\\">↩</a></li> <li id=\\"fn-133-shortId\\">Another thing<a href=\\"#fnref-133-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 133\\">↩</a></li> <li id=\\"fn-134-shortId\\">Another thing<a href=\\"#fnref-134-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 134\\">↩</a></li> <li id=\\"fn-135-shortId\\">Another thing<a href=\\"#fnref-135-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 135\\">↩</a></li> <li id=\\"fn-136-shortId\\">Another thing<a href=\\"#fnref-136-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 136\\">↩</a></li> <li id=\\"fn-137-shortId\\">Another thing<a href=\\"#fnref-137-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 137\\">↩</a></li> <li id=\\"fn-138-shortId\\">Another thing<a href=\\"#fnref-138-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 138\\">↩</a></li> <li id=\\"fn-139-shortId\\">Another thing<a href=\\"#fnref-139-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 139\\">↩</a></li> <li id=\\"fn-140-shortId\\">Another thing<a href=\\"#fnref-140-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 140\\">↩</a></li> <li id=\\"fn-141-shortId\\">Another thing<a href=\\"#fnref-141-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 141\\">↩</a></li> <li id=\\"fn-142-shortId\\">Another thing<a href=\\"#fnref-142-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 142\\">↩</a></li> <li id=\\"fn-143-shortId\\">Another thing<a href=\\"#fnref-143-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 143\\">↩</a></li> <li id=\\"fn-144-shortId\\">Another thing<a href=\\"#fnref-144-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 144\\">↩</a></li> <li id=\\"fn-145-shortId\\">Another thing<a href=\\"#fnref-145-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 145\\">↩</a></li> <li id=\\"fn-146-shortId\\">Another thing<a href=\\"#fnref-146-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 146\\">↩</a></li> <li id=\\"fn-147-shortId\\">Another thing<a href=\\"#fnref-147-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 147\\">↩</a></li> <li id=\\"fn-148-shortId\\">Another thing<a href=\\"#fnref-148-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 148\\">↩</a></li> <li id=\\"fn-149-shortId\\">Another thing<a href=\\"#fnref-149-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 149\\">↩</a></li> <li id=\\"fn-150-shortId\\">Another thing<a href=\\"#fnref-150-shortId\\" class=\\"footnote-backref\\" title=\\"Retourner au texte de la note 150\\">↩</a></li> </ol> </div>" `; exports[`extensions renders footnote_many_footnotes.txt 2`] = ` "Something\\\\textsuperscript{\\\\footnotemark[1]} Something\\\\textsuperscript{\\\\footnotemark[2]} Something\\\\textsuperscript{\\\\footnotemark[3]} Something\\\\textsuperscript{\\\\footnotemark[4]} Something\\\\textsuperscript{\\\\footnotemark[5]} Something\\\\textsuperscript{\\\\footnotemark[6]} Something\\\\textsuperscript{\\\\footnotemark[7]} Something\\\\textsuperscript{\\\\footnotemark[8]} Something\\\\textsuperscript{\\\\footnotemark[9]} Something\\\\textsuperscript{\\\\footnotemark[10]} Something\\\\textsuperscript{\\\\footnotemark[11]} Something\\\\textsuperscript{\\\\footnotemark[12]} Something\\\\textsuperscript{\\\\footnotemark[13]} Something\\\\textsuperscript{\\\\footnotemark[14]} Something\\\\textsuperscript{\\\\footnotemark[15]} Something\\\\textsuperscript{\\\\footnotemark[16]} Something\\\\textsuperscript{\\\\footnotemark[17]} Something\\\\textsuperscript{\\\\footnotemark[18]} Something\\\\textsuperscript{\\\\footnotemark[19]} Something\\\\textsuperscript{\\\\footnotemark[20]} Something\\\\textsuperscript{\\\\footnotemark[21]} Something\\\\textsuperscript{\\\\footnotemark[22]} Something\\\\textsuperscript{\\\\footnotemark[23]} Something\\\\textsuperscript{\\\\footnotemark[24]} Something\\\\textsuperscript{\\\\footnotemark[25]} Something\\\\textsuperscript{\\\\footnotemark[26]} Something\\\\textsuperscript{\\\\footnotemark[27]} Something\\\\textsuperscript{\\\\footnotemark[28]} Something\\\\textsuperscript{\\\\footnotemark[29]} Something\\\\textsuperscript{\\\\footnotemark[30]} Something\\\\textsuperscript{\\\\footnotemark[31]} Something\\\\textsuperscript{\\\\footnotemark[32]} Something\\\\textsuperscript{\\\\footnotemark[33]} Something\\\\textsuperscript{\\\\footnotemark[34]} Something\\\\textsuperscript{\\\\footnotemark[35]} Something\\\\textsuperscript{\\\\footnotemark[36]} Something\\\\textsuperscript{\\\\footnotemark[37]} Something\\\\textsuperscript{\\\\footnotemark[38]} Something\\\\textsuperscript{\\\\footnotemark[39]} Something\\\\textsuperscript{\\\\footnotemark[40]} Something\\\\textsuperscript{\\\\footnotemark[41]} Something\\\\textsuperscript{\\\\footnotemark[42]} Something\\\\textsuperscript{\\\\footnotemark[43]} Something\\\\textsuperscript{\\\\footnotemark[44]} Something\\\\textsuperscript{\\\\footnotemark[45]} Something\\\\textsuperscript{\\\\footnotemark[46]} Something\\\\textsuperscript{\\\\footnotemark[47]} Something\\\\textsuperscript{\\\\footnotemark[48]} Something\\\\textsuperscript{\\\\footnotemark[49]} Something\\\\textsuperscript{\\\\footnotemark[50]} Something\\\\textsuperscript{\\\\footnotemark[51]} Something\\\\textsuperscript{\\\\footnotemark[52]} Something\\\\textsuperscript{\\\\footnotemark[53]} Something\\\\textsuperscript{\\\\footnotemark[54]} Something\\\\textsuperscript{\\\\footnotemark[55]} Something\\\\textsuperscript{\\\\footnotemark[56]} Something\\\\textsuperscript{\\\\footnotemark[57]} Something\\\\textsuperscript{\\\\footnotemark[58]} Something\\\\textsuperscript{\\\\footnotemark[59]} Something\\\\textsuperscript{\\\\footnotemark[60]} Something\\\\textsuperscript{\\\\footnotemark[61]} Something\\\\textsuperscript{\\\\footnotemark[62]} Something\\\\textsuperscript{\\\\footnotemark[63]} Something\\\\textsuperscript{\\\\footnotemark[64]} Something\\\\textsuperscript{\\\\footnotemark[65]} Something\\\\textsuperscript{\\\\footnotemark[66]} Something\\\\textsuperscript{\\\\footnotemark[67]} Something\\\\textsuperscript{\\\\footnotemark[68]} Something\\\\textsuperscript{\\\\footnotemark[69]} Something\\\\textsuperscript{\\\\footnotemark[70]} Something\\\\textsuperscript{\\\\footnotemark[71]} Something\\\\textsuperscript{\\\\footnotemark[72]} Something\\\\textsuperscript{\\\\footnotemark[73]} Something\\\\textsuperscript{\\\\footnotemark[74]} Something\\\\textsuperscript{\\\\footnotemark[75]} Something\\\\textsuperscript{\\\\footnotemark[76]} Something\\\\textsuperscript{\\\\footnotemark[77]} Something\\\\textsuperscript{\\\\footnotemark[78]} Something\\\\textsuperscript{\\\\footnotemark[79]} Something\\\\textsuperscript{\\\\footnotemark[80]} Something\\\\textsuperscript{\\\\footnotemark[81]} Something\\\\textsuperscript{\\\\footnotemark[82]} Something\\\\textsuperscript{\\\\footnotemark[83]} Something\\\\textsuperscript{\\\\footnotemark[84]} Something\\\\textsuperscript{\\\\footnotemark[85]} Something\\\\textsuperscript{\\\\footnotemark[86]} Something\\\\textsuperscript{\\\\footnotemark[87]} Something\\\\textsuperscript{\\\\footnotemark[88]} Something\\\\textsuperscript{\\\\footnotemark[89]} Something\\\\textsuperscript{\\\\footnotemark[90]} Something\\\\textsuperscript{\\\\footnotemark[91]} Something\\\\textsuperscript{\\\\footnotemark[92]} Something\\\\textsuperscript{\\\\footnotemark[93]} Something\\\\textsuperscript{\\\\footnotemark[94]} Something\\\\textsuperscript{\\\\footnotemark[95]} Something\\\\textsuperscript{\\\\footnotemark[96]} Something\\\\textsuperscript{\\\\footnotemark[97]} Something\\\\textsuperscript{\\\\footnotemark[98]} Something\\\\textsuperscript{\\\\footnotemark[99]} Something\\\\textsuperscript{\\\\footnotemark[100]} Something\\\\textsuperscript{\\\\footnotemark[101]} Something\\\\textsuperscript{\\\\footnotemark[102]} Something\\\\textsuperscript{\\\\footnotemark[103]} Something\\\\textsuperscript{\\\\footnotemark[104]} Something\\\\textsuperscript{\\\\footnotemark[105]} Something\\\\textsuperscript{\\\\footnotemark[106]} Something\\\\textsuperscript{\\\\footnotemark[107]} Something\\\\textsuperscript{\\\\footnotemark[108]} Something\\\\textsuperscript{\\\\footnotemark[109]} Something\\\\textsuperscript{\\\\footnotemark[110]} Something\\\\textsuperscript{\\\\footnotemark[111]} Something\\\\textsuperscript{\\\\footnotemark[112]} Something\\\\textsuperscript{\\\\footnotemark[113]} Something\\\\textsuperscript{\\\\footnotemark[114]} Something\\\\textsuperscript{\\\\footnotemark[115]} Something\\\\textsuperscript{\\\\footnotemark[116]} Something\\\\textsuperscript{\\\\footnotemark[117]} Something\\\\textsuperscript{\\\\footnotemark[118]} Something\\\\textsuperscript{\\\\footnotemark[119]} Something\\\\textsuperscript{\\\\footnotemark[120]} Something\\\\textsuperscript{\\\\footnotemark[121]} Something\\\\textsuperscript{\\\\footnotemark[122]} Something\\\\textsuperscript{\\\\footnotemark[123]} Something\\\\textsuperscript{\\\\footnotemark[124]} Something\\\\textsuperscript{\\\\footnotemark[125]} Something\\\\textsuperscript{\\\\footnotemark[126]} Something\\\\textsuperscript{\\\\footnotemark[127]} Something\\\\textsuperscript{\\\\footnotemark[128]} Something\\\\textsuperscript{\\\\footnotemark[129]} Something\\\\textsuperscript{\\\\footnotemark[130]} Something\\\\textsuperscript{\\\\footnotemark[131]} Something\\\\textsuperscript{\\\\footnotemark[132]} Something\\\\textsuperscript{\\\\footnotemark[133]} Something\\\\textsuperscript{\\\\footnotemark[134]} Something\\\\textsuperscript{\\\\footnotemark[135]} Something\\\\textsuperscript{\\\\footnotemark[136]} Something\\\\textsuperscript{\\\\footnotemark[137]} Something\\\\textsuperscript{\\\\footnotemark[138]} Something\\\\textsuperscript{\\\\footnotemark[139]} Something\\\\textsuperscript{\\\\footnotemark[140]} Something\\\\textsuperscript{\\\\footnotemark[141]} Something\\\\textsuperscript{\\\\footnotemark[142]} Something\\\\textsuperscript{\\\\footnotemark[143]} Something\\\\textsuperscript{\\\\footnotemark[144]} Something\\\\textsuperscript{\\\\footnotemark[145]} Something\\\\textsuperscript{\\\\footnotemark[146]} Something\\\\textsuperscript{\\\\footnotemark[147]} Something\\\\textsuperscript{\\\\footnotemark[148]} Something\\\\textsuperscript{\\\\footnotemark[149]} Something\\\\textsuperscript{\\\\footnotemark[150]} \\\\footnotetext[1]{Another thing} \\\\footnotetext[2]{Another thing} \\\\footnotetext[3]{Another thing} \\\\footnotetext[4]{Another thing} \\\\footnotetext[5]{Another thing} \\\\footnotetext[6]{Another thing} \\\\footnotetext[7]{Another thing} \\\\footnotetext[8]{Another thing} \\\\footnotetext[9]{Another thing} \\\\footnotetext[10]{Another thing} \\\\footnotetext[11]{Another thing} \\\\footnotetext[12]{Another thing} \\\\footnotetext[13]{Another thing} \\\\footnotetext[14]{Another thing} \\\\footnotetext[15]{Another thing} \\\\footnotetext[16]{Another thing} \\\\footnotetext[17]{Another thing} \\\\footnotetext[18]{Another thing} \\\\footnotetext[19]{Another thing} \\\\footnotetext[20]{Another thing} \\\\footnotetext[21]{Another thing} \\\\footnotetext[22]{Another thing} \\\\footnotetext[23]{Another thing} \\\\footnotetext[24]{Another thing} \\\\footnotetext[25]{Another thing} \\\\footnotetext[26]{Another thing} \\\\footnotetext[27]{Another thing} \\\\footnotetext[28]{Another thing} \\\\footnotetext[29]{Another thing} \\\\footnotetext[30]{Another thing} \\\\footnotetext[31]{Another thing} \\\\footnotetext[32]{Another thing} \\\\footnotetext[33]{Another thing} \\\\footnotetext[34]{Another thing} \\\\footnotetext[35]{Another thing} \\\\footnotetext[36]{Another thing} \\\\footnotetext[37]{Another thing} \\\\footnotetext[38]{Another thing} \\\\footnotetext[39]{Another thing} \\\\footnotetext[40]{Another thing} \\\\footnotetext[41]{Another thing} \\\\footnotetext[42]{Another thing} \\\\footnotetext[43]{Another thing} \\\\footnotetext[44]{Another thing} \\\\footnotetext[45]{Another thing} \\\\footnotetext[46]{Another thing} \\\\footnotetext[47]{Another thing} \\\\footnotetext[48]{Another thing} \\\\footnotetext[49]{Another thing} \\\\footnotetext[50]{Another thing} \\\\footnotetext[51]{Another thing} \\\\footnotetext[52]{Another thing} \\\\footnotetext[53]{Another thing} \\\\footnotetext[54]{Another thing} \\\\footnotetext[55]{Another thing} \\\\footnotetext[56]{Another thing} \\\\footnotetext[57]{Another thing} \\\\footnotetext[58]{Another thing} \\\\footnotetext[59]{Another thing} \\\\footnotetext[60]{Another thing} \\\\footnotetext[61]{Another thing} \\\\footnotetext[62]{Another thing} \\\\footnotetext[63]{Another thing} \\\\footnotetext[64]{Another thing} \\\\footnotetext[65]{Another thing} \\\\footnotetext[66]{Another thing} \\\\footnotetext[67]{Another thing} \\\\footnotetext[68]{Another thing} \\\\footnotetext[69]{Another thing} \\\\footnotetext[70]{Another thing} \\\\footnotetext[71]{Another thing} \\\\footnotetext[72]{Another thing} \\\\footnotetext[73]{Another thing} \\\\footnotetext[74]{Another thing} \\\\footnotetext[75]{Another thing} \\\\footnotetext[76]{Another thing} \\\\footnotetext[77]{Another thing} \\\\footnotetext[78]{Another thing} \\\\footnotetext[79]{Another thing} \\\\footnotetext[80]{Another thing} \\\\footnotetext[81]{Another thing} \\\\footnotetext[82]{Another thing} \\\\footnotetext[83]{Another thing} \\\\footnotetext[84]{Another thing} \\\\footnotetext[85]{Another thing} \\\\footnotetext[86]{Another thing} \\\\footnotetext[87]{Another thing} \\\\footnotetext[88]{Another thing} \\\\footnotetext[89]{Another thing} \\\\footnotetext[90]{Another thing} \\\\footnotetext[91]{Another thing} \\\\footnotetext[92]{Another thing} \\\\footnotetext[93]{Another thing} \\\\footnotetext[94]{Another thing} \\\\footnotetext[95]{Another thing} \\\\footnotetext[96]{Another thing} \\\\footnotetext[97]{Another thing} \\\\footnotetext[98]{Another thing} \\\\footnotetext[99]{Another thing} \\\\footnotetext[100]{Another thing} \\\\footnotetext[101]{Another thing} \\\\footnotetext[102]{Another thing} \\\\footnotetext[103]{Another thing} \\\\footnotetext[104]{Another thing} \\\\footnotetext[105]{Another thing} \\\\footnotetext[106]{Another thing} \\\\footnotetext[107]{Another thing} \\\\footnotetext[108]{Another thing} \\\\footnotetext[109]{Another thing} \\\\footnotetext[110]{Another thing} \\\\footnotetext[111]{Another thing} \\\\footnotetext[112]{Another thing} \\\\footnotetext[113]{Another thing} \\\\footnotetext[114]{Another thing} \\\\footnotetext[115]{Another thing} \\\\footnotetext[116]{Another thing} \\\\footnotetext[117]{Another thing} \\\\footnotetext[118]{Another thing} \\\\footnotetext[119]{Another thing} \\\\footnotetext[120]{Another thing} \\\\footnotetext[121]{Another thing} \\\\footnotetext[122]{Another thing} \\\\footnotetext[123]{Another thing} \\\\footnotetext[124]{Another thing} \\\\footnotetext[125]{Another thing} \\\\footnotetext[126]{Another thing} \\\\footnotetext[127]{Another thing} \\\\footnotetext[128]{Another thing} \\\\footnotetext[129]{Another thing} \\\\footnotetext[130]{Another thing} \\\\footnotetext[131]{Another thing} \\\\footnotetext[132]{Another thing} \\\\footnotetext[133]{Another thing} \\\\footnotetext[134]{Another thing} \\\\footnotetext[135]{Another thing} \\\\footnotetext[136]{Another thing} \\\\footnotetext[137]{Another thing} \\\\footnotetext[138]{Another thing} \\\\footnotetext[139]{Another thing} \\\\footnotetext[140]{Another thing} \\\\footnotetext[141]{Another thing} \\\\footnotetext[142]{Another thing} \\\\footnotetext[143]{Another thing} \\\\footnotetext[144]{Another thing} \\\\footnotetext[145]{Another thing} \\\\footnotetext[146]{Another thing} \\\\footnotetext[147]{Another thing} \\\\footnotetext[148]{Another thing} \\\\footnotetext[149]{Another thing} \\\\footnotetext[150]{Another thing}" `; exports[`extensions renders github_flavored.txt 1`] = ` "<p>index 0000000..6e956a9</p> <pre><code class=\\"language-diff\\">--- /dev/null +++ b/test/data/stripped_text/mike-30-lili @@ -0,0 +1,27 @@ +Summary: + drift_mod.py | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +commit da4bfb04debdd994683740878d09988b2641513d +Author: Mike Dirolf <mike@dirolf.com> +Date: Tue Jan 17 13:42:28 2012 -0500 + +\`\`\` +minor: just wanted to push something. +\`\`\` + +diff --git a/drift_mod.py b/drift_mod.py +index 34dfba6..8a88a69 100644 + +\`\`\` +--- a/drift_mod.py ++++ b/drift_mod.py +@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^(' + '|\\\\+ .*' + '|- .*' + ')$') ++ + def wrap_context_diffs(message_text): + return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN, + CONTEXT_DIFF_LINE_PATTERN, +\`\`\` </code></pre> <p>Test support for foo+bar lexer names.</p> <pre><code class=\\"language-html+jinja\\"><title>{% block title %}{% endblock %}</title> <ul> {% for user in users %} <li><a href=\\"{{ user.url }}\\">{{ user.username }}</a></li> {% endfor %} </ul> </code></pre> <p>Test support for foo+bar lexer names in citation.</p> <blockquote> <pre><code class=\\"language-html+jinja\\"><title>{% block title %}{% endblock %}</title> <ul> {% for user in users %} <li><a href=\\"{{ user.url }}\\">{{ user.username }}</a></li> {% endfor %} </ul> </code></pre> </blockquote> <p>Test support for foo+bar lexer names with hightlight.</p> <pre><code class=\\"language-html+jinja\\"><title>{% block title %}{% endblock %}</title> <ul> {% for user in users %} <li><a href=\\"{{ user.url }}\\">{{ user.username }}</a></li> {% endfor %} </ul> </code></pre> <p>Test support for foo+bar lexer names with linenostart.</p> <pre><code class=\\"language-html+jinja\\"><title>{% block title %}{% endblock %}</title> <ul> {% for user in users %} <li><a href=\\"{{ user.url }}\\">{{ user.username }}</a></li> {% endfor %} </ul> </code></pre> <p>Test support for foo+bar lexer names with both.</p> <pre><code class=\\"language-html+jinja\\"><title>{% block title %}{% endblock %}</title> <ul> {% for user in users %} <li><a href=\\"{{ user.url }}\\">{{ user.username }}</a></li> {% endfor %} </ul> </code></pre> <p>Code without matching end</p> <pre><code class=\\"language-html\\"> ~~~ Code into paragraph \`\`\`html+jinja hl_lines= \\"2-4\\" linenostart=10 <title>{% block title %}{% endblock %}</title> <ul> {% for user in users %} <li><a href=\\"{{ user.url }}\\">{{ user.username }}</a></li> {% endfor %} </ul> \`\`\` with end </code></pre>" `; exports[`extensions renders github_flavored.txt 2`] = ` "index 0000000..6e956a9 \\\\begin{CodeBlock}{diff} --- /dev/null +++ b/test/data/stripped_text/mike-30-lili @@ -0,0 +1,27 @@ +Summary: + drift_mod.py | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +commit da4bfb04debdd994683740878d09988b2641513d +Author: Mike Dirolf <mike@dirolf.com> +Date: Tue Jan 17 13:42:28 2012 -0500 + +\`\`\` +minor: just wanted to push something. +\`\`\` + +diff --git a/drift_mod.py b/drift_mod.py +index 34dfba6..8a88a69 100644 + +\`\`\` +--- a/drift_mod.py ++++ b/drift_mod.py +@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^(' + '|\\\\+ .*' + '|- .*' + ')$') ++ + def wrap_context_diffs(message_text): + return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN, + CONTEXT_DIFF_LINE_PATTERN, +\`\`\` \\\\end{CodeBlock} Test support for foo+bar lexer names. \\\\begin{CodeBlock}{html+jinja} <title>{% block title %}{% endblock %} \\\\end{CodeBlock} Test support for foo+bar lexer names in citation. \\\\begin{Quotation} \\\\begin{CodeBlock}{html+jinja} {% block title %}{% endblock %} \\\\end{CodeBlock} \\\\end{Quotation} Test support for foo+bar lexer names with hightlight. \\\\begin{CodeBlock}[][2-4]{html+jinja} {% block title %}{% endblock %} \\\\end{CodeBlock} Test support for foo+bar lexer names with linenostart. \\\\begin{CodeBlock}[][][10]{html+jinja} {% block title %}{% endblock %} \\\\end{CodeBlock} Test support for foo+bar lexer names with both. \\\\begin{CodeBlock}[][2-4][10]{html+jinja} {% block title %}{% endblock %} \\\\end{CodeBlock} Code without matching end \\\\begin{CodeBlock}{html} ~~~ Code into paragraph \`\`\`html+jinja hl_lines= \\"2-4\\" linenostart=10 {% block title %}{% endblock %} \`\`\` with end \\\\end{CodeBlock}" `; exports[`extensions renders tables.txt 1`] = ` "
First Header Second Header
Content Cell Content Cell
Content Cell Content Cell
First Header Second Header
Content Cell Content Cell
Content Cell Content Cell
Item Value
Computer $1600
Phone $12
Pipe $1
Function name Description
help() Display the help window.
destroy() Destroy your computer!
foo bar baz
Q
W W

Three spaces in front of a table:

First Header Second Header
Content Cell Content Cell
Content Cell Content Cell
First Header Second Header
Content Cell Content Cell
Content Cell Content Cell

Four spaces is a code block:

First Header | Second Header
------------ | -------------
Content Cell | Content Cell
Content Cell | Content Cell
" `; exports[`extensions renders tables.txt 2`] = ` "\\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} First Header & Second Header \\\\\\\\ Content Cell & Content Cell \\\\\\\\ Content Cell & Content Cell \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} First Header & Second Header \\\\\\\\ Content Cell & Content Cell \\\\\\\\ Content Cell & Content Cell \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Item & Value \\\\\\\\ Computer & \\\\$1600 \\\\\\\\ Phone & \\\\$12 \\\\\\\\ Pipe & \\\\$1 \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} Function name & Description \\\\\\\\ \\\\CodeInline{help()} & Display the help window. \\\\\\\\ \\\\CodeInline{destroy()} & \\\\textbf{Destroy your computer!} \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} foo & bar & baz \\\\\\\\ & Q & \\\\\\\\ W & & W \\\\\\\\ \\\\end{zdstblr} Three spaces in front of a table: \\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} First Header & Second Header \\\\\\\\ Content Cell & Content Cell \\\\\\\\ Content Cell & Content Cell \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} First Header & Second Header \\\\\\\\ Content Cell & Content Cell \\\\\\\\ Content Cell & Content Cell \\\\\\\\ \\\\end{zdstblr} Four spaces is a code block: \\\\begin{CodeBlock}{text} First Header | Second Header ------------ | ------------- Content Cell | Content Cell Content Cell | Content Cell \\\\end{CodeBlock}" `; exports[`extensions renders tables-2.txt 1`] = ` "
foo bar baz
Q
W W
" `; exports[`extensions renders tables-2.txt 2`] = ` "\\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} foo & bar & baz \\\\\\\\ Q \\\\\\\\ W & & W \\\\\\\\ \\\\end{zdstblr}" `; exports[`heading-shift shifts before range 1`] = `"

should be h1

"`; exports[`heading-shift shifts before range 2`] = `"\\\\levelOneTitle{should be h1}"`; exports[`heading-shift shifts in range 1`] = `"

should be h4

"`; exports[`heading-shift shifts in range 2`] = `"\\\\levelFourTitle{should be h4}"`; exports[`heading-shift shifts past range 1`] = `"
should be h6
"`; exports[`heading-shift shifts past range 2`] = `"\\\\levelSixTitle{should be h6}"`; exports[`misc renders CRLF_line_ends.txt 1`] = ` "

foo

<div> bar </div>

" `; exports[`misc renders CRLF_line_ends.txt 2`] = ` "foo
bar
" `; exports[`misc renders adjacent-headers.txt 1`] = ` "

this is a huge header

this is a smaller header

" `; exports[`misc renders adjacent-headers.txt 2`] = ` "\\\\levelOneTitle{this is a huge header} \\\\levelTwoTitle{this is a smaller header}" `; exports[`misc renders amp-in-url.txt 1`] = `"

link

"`; exports[`misc renders amp-in-url.txt 2`] = `"\\\\externalLink{link}{http://www.freewisdom.org/this\\\\&that}"`; exports[`misc renders ampersand.txt 1`] = ` "

&

AT&T

" `; exports[`misc renders ampersand.txt 2`] = ` "\\\\& AT\\\\&T" `; exports[`misc renders arabic.txt 1`] = ` "

بايثون

بايثون لغة برمجة حديثة بسيطة، واضحة، سريعة ، تستخدم أسلوب البرمجة الكائنية (OOP) وقابلة للتطوير بالإضافة إلى أنها مجانية و مفتوحة المصدر. صُنفت بالأساس كلغة تفسيرية ، بايثون مصممة أصلاً للأداء بعض المهام الخاصة أو المحدودة. إلا أنه يمكن استخدامها بايثون لإنجاز المشاريع الضخمه كأي لغة برمجية أخرى، غالباً ما يُنصح المبتدئين في ميدان البرمجة بتعلم هذه اللغة لأنها من بين أسهل اللغات البرمجية تعلماً.

نشأت بايثون في مركز CWI (مركز العلوم والحاسب الآلي) بأمستردام على يد جويدو فان رُزوم. تم تطويرها بلغة C. أطلق فان رُزوم اسم \\"بايثون\\" على لغته تعبيرًا عن إعجابه بفِرقَة مسرحية هزلية شهيرة من بريطانيا، كانت تطلق على نفسها اسم مونتي بايثون Monty Python.

تتميز بايثون بمجتمعها النشط ، كما أن لها الكثير من المكتبات البرمجية ذات الأغراض الخاصة والتي برمجها أشخاص من مجتمع هذه اللغة ، مثلاً مكتبة PyGame التي توفر مجموعه من الوظائف من اجل برمجة الالعاب. ويمكن لبايثون التعامل مع العديد من أنواع قواعد البيانات مثل MySQL وغيره.

أمثلة

مثال Hello World!

print \\"Hello World!\\"

مثال لاستخراج المضروب Factorial :

num = 1
x = raw_input('Insert the number please ')
x = int(x)

if x > 69:
 print 'Math Error !'
else:
 while x > 1:
  num *= x
  x = x-1

 print num

وصلات خارجية

" `; exports[`misc renders arabic.txt 2`] = ` "\\\\levelOneTitle{بايثون} \\\\textbf{بايثون} لغة برمجة حديثة بسيطة، واضحة، سريعة ، تستخدم أسلوب البرمجة الكائنية (OOP) وقابلة للتطوير بالإضافة إلى أنها مجانية و مفتوحة المصدر. صُنفت بالأساس كلغة تفسيرية ، بايثون مصممة أصلاً للأداء بعض المهام الخاصة أو المحدودة. إلا أنه يمكن استخدامها بايثون لإنجاز المشاريع الضخمه كأي لغة برمجية أخرى، غالباً ما يُنصح المبتدئين في ميدان البرمجة بتعلم هذه اللغة لأنها من بين أسهل اللغات البرمجية تعلماً. نشأت بايثون في مركز CWI (مركز العلوم والحاسب الآلي) بأمستردام على يد جويدو فان رُزوم. تم تطويرها بلغة C. أطلق فان رُزوم اسم \\"بايثون\\" على لغته تعبيرًا عن إعجابه بفِرقَة مسرحية هزلية شهيرة من بريطانيا، كانت تطلق على نفسها اسم مونتي بايثون Monty Python. تتميز بايثون بمجتمعها النشط ، كما أن لها الكثير من المكتبات البرمجية ذات الأغراض الخاصة والتي برمجها أشخاص من مجتمع هذه اللغة ، مثلاً مكتبة PyGame التي توفر مجموعه من الوظائف من اجل برمجة الالعاب. ويمكن لبايثون التعامل مع العديد من أنواع قواعد البيانات مثل MySQL وغيره. \\\\levelTwoTitle{أمثلة} مثال Hello World! \\\\begin{CodeBlock}{text} print \\"Hello World!\\" \\\\end{CodeBlock} مثال لاستخراج المضروب Factorial : \\\\begin{CodeBlock}{text} num = 1 x = raw_input('Insert the number please ') x = int(x) if x > 69: print 'Math Error !' else: while x > 1: num *= x x = x-1 print num \\\\end{CodeBlock} \\\\levelTwoTitle{وصلات خارجية} \\\\begin{itemize} \\\\item\\\\relax \\\\externalLink{الموقع الرسمي للغة بايثون}{http://www.python.org} بذرة حاس \\\\end{itemize}" `; exports[`misc renders autolinks_with_asterisks.txt 1`] = `"

http://some.site/weird*url*thing

"`; exports[`misc renders backtick-escape.txt 1`] = ` "

\`This also should not be in code.\` \\\\This should be in code.\\\\\\\\ \`And finally this should not be in code.\`

" `; exports[`misc renders backtick-escape.txt 2`] = ` "\`This also should not be in code.\` \\\\textbackslash{}\\\\CodeInline{This should be in code.\\\\textbackslash{}\\\\textbackslash{}} \`And finally this should not be in code.\`" `; exports[`misc renders blank_lines_in_codeblocks.txt 1`] = ` "

Preserve blank lines in code blocks with tabs:

a code block

two tabbed lines


three tabbed lines



four tabbed lines




five tabbed lines





six tabbed lines






End of tabbed block

And without tabs:

a code block

two blank lines


three blank lines



four blank lines




five blank lines





six blank lines






End of block

End of document

" `; exports[`misc renders blank_lines_in_codeblocks.txt 2`] = ` "Preserve blank lines in code blocks with tabs: \\\\begin{CodeBlock}{text} a code block two tabbed lines three tabbed lines four tabbed lines five tabbed lines six tabbed lines End of tabbed block \\\\end{CodeBlock} And without tabs: \\\\begin{CodeBlock}{text} a code block two blank lines three blank lines four blank lines five blank lines six blank lines End of block \\\\end{CodeBlock} End of document" `; exports[`misc renders blank-block-quote.txt 1`] = ` "

aaaaaaaaaaa

bbbbbbbbbbb

" `; exports[`misc renders blank-block-quote.txt 2`] = ` "aaaaaaaaaaa \\\\begin{Quotation} \\\\end{Quotation} bbbbbbbbbbb" `; exports[`misc renders block_html_attr.txt 1`] = ` "

<blockquote> Raw HTML processing should not confuse this with the blockquote below </blockquote> <div id=\\"current-content\\"> <div id=\\"primarycontent\\" class=\\"hfeed\\"> <div id=\\"post-\\"> <div class=\\"page-head\\"> <h2>Header2</h2> </div> <div class=\\"entry-content\\"> <h3>Header3</h3> <p>Paragraph</p> <h3>Header3</h3> <p>Paragraph</p> <blockquote> <p>Paragraph</p> </blockquote> <p>Paragraph</p> <p><a href=\\"/somelink\\">linktext</a></p> </div> </div><!-- #post-ID --> <!-- add contact form here --> </div><!-- #primarycontent --> </div><!-- #current-content -->

" `; exports[`misc renders block_html_attr.txt 2`] = ` "
Raw HTML processing should not confuse this with the blockquote below

Header2

Header3

Paragraph

Header3

Paragraph

Paragraph

Paragraph

linktext

" `; exports[`misc renders block_html_simple.txt 1`] = ` "

<p>foo</p> <ul> <li> <p>bar</p> </li> <li> <p>baz</p> </li> </ul>

" `; exports[`misc renders block_html_simple.txt 2`] = ` "

foo

  • bar

  • baz

" `; exports[`misc renders block_html5.txt 1`] = ` "

<section> <header> <hgroup> <h1>Hello :-)</h1> </hgroup> </header> <figure> <img src=\\"image.png\\" alt=\\"\\" /> <figcaption>Caption</figcaption> </figure> <footer> <p>Some footer</p> </footer> </section><figure></figure>

" `; exports[`misc renders block_html5.txt 2`] = ` "

Hello :-)

\\"\\"
Caption

Some footer

" `; exports[`misc renders blockquote.txt 1`] = ` "

blockquote with no whitespace before >.

foo

blockquote with one space before the >.

bar

blockquote with 2 spaces.

baz

this has three spaces so its a paragraph.

blah

> this one had four so it's a code block.

this nested blockquote has 0 on level one and 3 (one after the first > + 2 more) on level 2.

and this has 4 on level 2 - another code block.

" `; exports[`misc renders blockquote.txt 2`] = ` "\\\\begin{Quotation} blockquote with no whitespace before \\\\CodeInline{>}. \\\\end{Quotation} foo \\\\begin{Quotation} blockquote with one space before the \\\\CodeInline{>}. \\\\end{Quotation} bar \\\\begin{Quotation} blockquote with 2 spaces. \\\\end{Quotation} baz \\\\begin{Quotation} this has three spaces so its a paragraph. \\\\end{Quotation} blah \\\\begin{CodeBlock}{text} > this one had four so it's a code block. \\\\end{CodeBlock} \\\\begin{Quotation} \\\\begin{Quotation} this nested blockquote has 0 on level one and 3 (one after the first \\\\CodeInline{>} + 2 more) on level 2. \\\\end{Quotation} \\\\end{Quotation} \\\\begin{Quotation} \\\\begin{Quotation} and this has 4 on level 2 - another code block. \\\\end{Quotation} \\\\end{Quotation}" `; exports[`misc renders blockquote-below-paragraph.txt 1`] = ` "

Paragraph

Block quote Yep

Paragraph

no space Nope

Paragraph one

blockquote More blockquote.

" `; exports[`misc renders blockquote-below-paragraph.txt 2`] = ` "Paragraph \\\\begin{Quotation} Block quote Yep \\\\end{Quotation} Paragraph \\\\begin{Quotation} no space Nope \\\\end{Quotation} Paragraph one \\\\begin{Quotation} blockquote More blockquote. \\\\end{Quotation}" `; exports[`misc renders blockquote-hr.txt 1`] = ` "

This is a paragraph.


Block quote with horizontal lines.


Double block quote.


End of the double block quote.

A new paragraph. With multiple lines. Even a lazy line.


The last line.

" `; exports[`misc renders bold_links.txt 1`] = `"

bold link

"`; exports[`misc renders bold_links.txt 2`] = `"\\\\textbf{bold \\\\externalLink{link}{http://example.com}}"`; exports[`misc renders br.txt 1`] = ` "

Output:

<p>Some of these words <em>are emphasized</em>.
Some of these words <em>are emphasized also</em>.</p>

<p>Use two asterisks for <strong>strong emphasis</strong>.
Or, if you prefer, <strong>use two underscores instead</strong>.</p>

Unordered (bulleted) lists use asterisks, pluses, and hyphens (*, +, and -) as list markers. These three markers are interchangable; this:

" `; exports[`misc renders br.txt 2`] = ` "Output: \\\\begin{CodeBlock}{text}

Some of these words are emphasized. Some of these words are emphasized also.

Use two asterisks for strong emphasis. Or, if you prefer, use two underscores instead.

\\\\end{CodeBlock} Unordered (bulleted) lists use asterisks, pluses, and hyphens (\\\\CodeInline{*}, \\\\CodeInline{+}, and \\\\CodeInline{-}) as list markers. These three markers are interchangable; this:" `; exports[`misc renders bracket_re.txt 1`] = ` "

[x xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx

" `; exports[`misc renders bracket_re.txt 2`] = ` "[x xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx" `; exports[`misc renders brackets-in-img-title.txt 1`] = ` "

\\"alt\\" \\"alt\\" \\"alt\\"

\\"alt\\" \\"alt\\"

\\"alt\\" \\"alt\\" \\"alt\\" \\"alt\\"

" `; exports[`misc renders code-first-line.txt 1`] = ` "
print \\"This is a code block.\\"
" `; exports[`misc renders code-first-line.txt 2`] = ` "\\\\begin{CodeBlock}{text} print \\"This is a code block.\\" \\\\end{CodeBlock}" `; exports[`misc renders comments.txt 1`] = ` "

X<0

X>0

<!-- A comment -->

<div>as if</div>

<!-- comment -->

no blank line

" `; exports[`misc renders comments.txt 2`] = ` "X<0 X>0
as if
\\\\textbf{no blank line}" `; exports[`misc renders div.txt 1`] = ` "

<div id=\\"sidebar\\">

foo

</div>

And now in uppercase:

<DIV> foo </DIV>

" `; exports[`misc renders div.txt 2`] = ` "
\\\\textit{foo}
And now in uppercase:
foo
" `; exports[`misc renders em_strong.txt 1`] = ` "

One asterisk: *

One underscore: _

Two asterisks: **

With spaces: * *

Two underscores __

with spaces: _ _

three asterisks: ***

with spaces: * * *

three underscores: ___

with spaces: _ _ _

One char: a

" `; exports[`misc renders em_strong.txt 2`] = ` "One asterisk: * One underscore: \\\\_ Two asterisks: ** With spaces: * * Two underscores \\\\_\\\\_ with spaces: \\\\_ \\\\_ three asterisks: *** with spaces: * * * three underscores: \\\\_\\\\_\\\\_ with spaces: \\\\_ \\\\_ \\\\_ One char: \\\\textit{a}" `; exports[`misc renders em-around-links.txt 1`] = ` "

Python in Markdown by some great folks - This does work as expected.

" `; exports[`misc renders em-around-links.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax \\\\textit{\\\\externalLink{Python in Markdown}{https://pythonhosted.org/Markdown/} by some great folks} - This \\\\textit{does} work as expected. \\\\item\\\\relax \\\\textit{\\\\externalLink{Python in Markdown}{https://pythonhosted.org/Markdown/} by some great folks} - This \\\\textit{does} work as expected. \\\\item\\\\relax \\\\externalLink{\\\\textit{Python in Markdown}}{https://pythonhosted.org/Markdown/} by some great folks - This \\\\textit{does} work as expected. \\\\item\\\\relax \\\\externalLink{\\\\textit{Python in Markdown}}{https://pythonhosted.org/Markdown/} \\\\textit{by some great folks} - This \\\\textit{does} work as expected. \\\\end{itemize} \\\\textit{\\\\externalLink{Python in Markdown}{https://pythonhosted.org/Markdown/} by some great folks} - This \\\\textit{does} work as expected." `; exports[`misc renders email.txt 1`] = ` "

asdfasdfadsfasd yuri@freewisdom.org or you can say instead yuri@freewisdom.org

bob&sue@example.com

" `; exports[`misc renders email.txt 2`] = ` "asdfasdfadsfasd \\\\externalLink{yuri@freewisdom.org}{mailto:yuri@freewisdom.org} or you can say instead \\\\externalLink{yuri@freewisdom.org}{mailto:yuri@freewisdom.org} \\\\externalLink{bob\\\\&sue@example.com}{mailto:bob\\\\&sue@example.com}" `; exports[`misc renders escaped_chars_in_js.txt 1`] = ` "

<span id=\\"e116142240\\">[javascript protected email address]</span>

<script type=\\"text/javascript\\"> var a=\\"gqMjyw7lZCaKk6p0J3uAUYS1.dbIW2hXzDHmiVNotOPRe_Ev@c4Gs58+LBr-F9QTfxn\\"; var b=a.split(\\"\\").sort().join(\\"\\"); var c=\\"F_-F6F_-FMe_\\"; var d=\\"\\"; for(var e=0;e<c.length;e++) d+=b.charAt(a.indexOf(c.charAt(e))); document .getElementById(\\"e116142240\\") .innerHTML=\\"<a href=\\\\\\"mailto:\\"+d+\\"\\\\\\">\\"+d+\\"</a>\\"; </script>

" `; exports[`misc renders escaped_chars_in_js.txt 2`] = ` "[javascript protected email address] " `; exports[`misc renders h1.txt 1`] = ` "

Header

Header 2

H3

H1

H2

" `; exports[`misc renders h1.txt 2`] = ` "\\\\levelTwoTitle{Header} \\\\levelOneTitle{Header 2} \\\\levelThreeTitle{H3} \\\\levelOneTitle{H1} \\\\levelTwoTitle{H2}" `; exports[`misc renders image.txt 1`] = ` "
\\"Poster\\"
Poster

\\"Poster\\"

\\"Blank\\"
Blank
" `; exports[`misc renders image.txt 2`] = ` "\\\\image{http://humane_man.jpg}[Poster] \\\\image{http://humane_man.jpg} \\\\footnote{\\\\label{poster}\\\\externalLink{http://humane\\\\_man.jpg}{http://humane\\\\_man.jpg}} \\\\image{}[Blank]" `; exports[`misc renders image_in_links.txt 1`] = `"

\\"altname\\"

"`; exports[`misc renders image_in_links.txt 2`] = `"\\\\externalLink{\\\\image{path/to/img_thumb.png}}{path/to/image.png}"`; exports[`misc renders image-2.txt 1`] = ` "

link!

link

" `; exports[`misc renders image-2.txt 2`] = ` "\\\\externalLink{\\\\textit{link!}}{http://src.com/} \\\\textit{\\\\externalLink{link}{http://www.freewisdom.org}}" `; exports[`misc renders ins-at-start-of-paragraph.txt 1`] = `"

<ins>Hello, fellow developer</ins> this ins should be wrapped in a p.

"`; exports[`misc renders ins-at-start-of-paragraph.txt 2`] = `"Hello, fellow developer this ins should be wrapped in a p."`; exports[`misc renders inside_html.txt 1`] = `"

<a href=\\"stuff\\"> ok? </a>

"`; exports[`misc renders inside_html.txt 2`] = `" \\\\textbf{ok}? "`; exports[`misc renders lazy-block-quote.txt 1`] = ` "

Line one of lazy block quote. Line two of lazy block quote.

Line one of paragraph two. Line two of paragraph two.

" `; exports[`misc renders lazy-block-quote.txt 2`] = ` "\\\\begin{Quotation} Line one of lazy block quote. Line two of lazy block quote. \\\\end{Quotation} \\\\begin{Quotation} Line one of paragraph two. Line two of paragraph two. \\\\end{Quotation}" `; exports[`misc renders link-with-parenthesis.txt 1`] = `"

ZIP archives

"`; exports[`misc renders link-with-parenthesis.txt 2`] = `"\\\\externalLink{ZIP archives}{http://en.wikipedia.org/wiki/ZIP\\\\_(file\\\\_format)}"`; exports[`misc renders lists.txt 1`] = ` "
  • A multi-paragraph list, unindented.

Simple tight list

  • Uno
  • Due
  • Tri

A singleton tight list:

  • Uno

A lose list:

  • One

  • Two

  • Three

A lose list with paragraphs

  • One one one one

    one one one one

  • Two two two two

" `; exports[`misc renders lists.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax A multi-paragraph list, unindented. \\\\end{itemize} Simple tight list \\\\begin{itemize} \\\\item\\\\relax Uno \\\\item\\\\relax Due \\\\item\\\\relax Tri \\\\end{itemize} A singleton tight list: \\\\begin{itemize} \\\\item\\\\relax Uno \\\\end{itemize} A lose list: \\\\begin{itemize} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{itemize} A lose list with paragraphs \\\\begin{itemize} \\\\item\\\\relax One one one one one one one one \\\\item\\\\relax Two two two two \\\\end{itemize}" `; exports[`misc renders lists2.txt 1`] = ` "
  • blah blah blah sdf asdf asdf asdf asdf asda asdf asdfasd
" `; exports[`misc renders lists2.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax blah blah blah sdf asdf asdf asdf asdf asda asdf asdfasd \\\\end{itemize}" `; exports[`misc renders lists3.txt 1`] = ` "
  • blah blah blah sdf asdf asdf asdf asdf asda asdf asdfasd
" `; exports[`misc renders lists3.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax blah blah blah sdf asdf asdf asdf asdf asda asdf asdfasd \\\\end{itemize}" `; exports[`misc renders lists4.txt 1`] = ` "
  • item1
  • item2
    1. Number 1
    2. Number 2
" `; exports[`misc renders lists4.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax item1 \\\\item\\\\relax item2 \\\\begin{enumerate} \\\\item\\\\relax Number 1 \\\\item\\\\relax Number 2 \\\\end{enumerate} \\\\end{itemize}" `; exports[`misc renders lists5.txt 1`] = ` "

This is a test of a block quote With just two lines

A paragraph

This is a more difficult case With a list item inside the quote

  • Alpha
  • Beta Etc.
" `; exports[`misc renders lists5.txt 2`] = ` "\\\\begin{Quotation} This is a test of a block quote With just two lines \\\\end{Quotation} A paragraph \\\\begin{Quotation} This is a more difficult case With a list item inside the quote \\\\begin{itemize} \\\\item\\\\relax Alpha \\\\item\\\\relax Beta Etc. \\\\end{itemize} \\\\end{Quotation}" `; exports[`misc renders lists6.txt 1`] = ` "

Test five or more spaces as start of list:

  • five spaces
    

not first item:

  • one space
  • five spaces
    

loose list:

  • one space

  • five spaces
    
" `; exports[`misc renders lists8.txt 1`] = ` "
  1. Four-score and seven years ago...

  2. We have nothing to fear...

  3. This is it...


  • Four-score and sever years ago our fathers brought forth

  • We have nothing to fear but fear itself

  • This is it as far as I'm concerned

" `; exports[`misc renders lists8.txt 2`] = ` "\\\\begin{enumerate} \\\\item\\\\relax \\\\begin{Quotation} Four-score and seven years ago... \\\\end{Quotation} \\\\item\\\\relax \\\\begin{Quotation} We have nothing to fear... \\\\end{Quotation} \\\\item\\\\relax \\\\begin{Quotation} This is it... \\\\end{Quotation} \\\\end{enumerate} \\\\horizontalLine \\\\begin{itemize} \\\\item\\\\relax \\\\begin{Quotation} Four-score and sever years ago our fathers brought forth \\\\end{Quotation} \\\\item\\\\relax \\\\begin{Quotation} We have nothing to fear but fear itself \\\\end{Quotation} \\\\item\\\\relax \\\\begin{Quotation} This is it as far as I'm concerned \\\\end{Quotation} \\\\end{itemize}" `; exports[`misc renders mismatched-tags.txt 1`] = ` "

<p>Some text</p><div>some more text</div>

and a bit more

<p>And this output</p> Compatible with PHP Markdown Extra 1.2.2 and Markdown.pl1.0.2b8:

<!-- comment --><p><div>text</div><br /></p><br />

Should be in p

" `; exports[`misc renders mismatched-tags.txt 2`] = ` "

Some text

some more text
and a bit more

And this output

\\\\textit{Compatible with PHP Markdown Extra 1.2.2 and Markdown.pl1.0.2b8:}

text


Should be in p" `; exports[`misc renders missing-link-def.txt 1`] = `"

This is a [missing link][empty] and a valid and [missing][again].

"`; exports[`misc renders multi-line-tags.txt 1`] = ` "

<div>

asdf asdfasd

</div>

<div>

foo bar

</div> No blank line.

" `; exports[`misc renders multi-line-tags.txt 2`] = ` "
asdf asdfasd
foo bar
No blank line." `; exports[`misc renders multi-paragraph-block-quote.txt 1`] = ` "

This is line one of paragraph one This is line two of paragraph one

This is line one of paragraph two

This is another blockquote.

" `; exports[`misc renders multi-paragraph-block-quote.txt 2`] = ` "\\\\begin{Quotation} This is line one of paragraph one This is line two of paragraph one \\\\end{Quotation} \\\\begin{Quotation} This is line one of paragraph two \\\\end{Quotation} \\\\begin{Quotation} This is another blockquote. \\\\end{Quotation}" `; exports[`misc renders nested-lists.txt 1`] = ` "
  • item 1

    paragraph 2

  • item 2

    • item 2-1

    • item 2-2

      • item 2-2-1
    • item 2-3

      • item 2-3-1
  • item 3

plain text

  • item 1

    • item 1-1
    • item 1-2
      • item 1-2-1
  • item 2

  • item 3

  • item 4

    • item 4-1

    • item 4-2

    • item 4-3

      Code under item 4-3
      

      Paragraph under item 4

" `; exports[`misc renders nested-patterns.txt 1`] = ` "

link link link link link link link

I am italic and bold I am just bold

Example bold italic on the same line bold italic.

Example bold italic on the same line bold italic.

" `; exports[`misc renders nested-patterns.txt 2`] = ` "\\\\textbf{\\\\textit{\\\\externalLink{link}{http://example.com}}} \\\\textbf{\\\\textit{\\\\externalLink{link}{http://example.com}}} \\\\textbf{\\\\externalLink{\\\\textit{link}}{http://example.com}} \\\\textbf{\\\\externalLink{\\\\textit{link}}{http://example.com}} \\\\textbf{\\\\externalLink{\\\\textit{link}}{http://example.com}} \\\\textbf{\\\\externalLink{\\\\textit{link}}{http://example.com}} \\\\externalLink{\\\\textbf{\\\\textit{link}}}{http://example.com} \\\\textbf{\\\\textit{I am \\\\textbf{\\\\textit{italic} and} bold} I am \\\\CodeInline{just} bold} Example \\\\textbf{\\\\textit{bold italic}} on the same line \\\\textbf{\\\\textit{bold italic}}. Example \\\\textbf{\\\\textit{bold italic}} on the same line \\\\textbf{\\\\textit{bold italic}}." `; exports[`misc renders normalize.txt 1`] = `"

Link

"`; exports[`misc renders normalize.txt 2`] = `"\\\\externalLink{Link}{http://www.stuff.com/q?x=1\\\\&y=2<>}"`; exports[`misc renders numeric-entity.txt 1`] = ` "

user@gmail.com

This is an entity: &#234;

" `; exports[`misc renders numeric-entity.txt 2`] = ` "\\\\externalLink{user@gmail.com}{mailto:user@gmail.com} This is an entity: \\\\&\\\\#234;" `; exports[`misc renders para-with-hr.txt 1`] = ` "

Here is a paragraph, followed by a horizontal rule.


Followed by another paragraph.

Here is another paragraph, followed by: *** not an HR. Followed by more of the same paragraph.

" `; exports[`misc renders para-with-hr.txt 2`] = ` "Here is a paragraph, followed by a horizontal rule. \\\\horizontalLine Followed by another paragraph. Here is another paragraph, followed by: *** not an HR. Followed by more of the same paragraph." `; exports[`misc renders php.txt 1`] = ` "

<!DOCTYPE HTML PUBLIC \\"-//W3C//DTD HTML 4.01//EN\\" \\"http://www.w3.org/TR/html4/strict.dtd\\">

<b>This should have a p tag</b>

<!--This is a comment -->

<div>This shouldn't</div>

<?php echo \\"block_level\\";?>

<?php echo \\"not_block_level\\";?>

" `; exports[`misc renders php.txt 2`] = ` " This should have a p tag
This shouldn't
" `; exports[`misc renders smart_em.txt 1`] = ` "

emphasis

this_is_not_emphasis

[punctuation with emphasis]

[punctuation_with_emphasis]

[punctuation_without_emphasis]

" `; exports[`misc renders smart_em.txt 2`] = ` "\\\\textit{emphasis} this\\\\_is\\\\_not\\\\_emphasis [\\\\textit{punctuation with emphasis}] [\\\\textit{punctuation\\\\_with\\\\_emphasis}] [punctuation\\\\_without\\\\_emphasis]" `; exports[`misc renders span.txt 1`] = ` "

<span id=\\"someId\\"> Foo bar Baz </span>

<div><b>foo</b></div>

<div id=\\"someId\\"> Foo bar Baz </div>

<baza id=\\"someId\\"> Foo bar Baz </baza>

" `; exports[`misc renders span.txt 2`] = ` " Foo \\\\textit{bar} Baz
\\\\textit{foo}
Foo \\\\textit{bar} Baz
Foo \\\\textit{bar} Baz " `; exports[`misc renders strong-with-underscores.txt 1`] = `"

this_is_strong

"`; exports[`misc renders strong-with-underscores.txt 2`] = `"\\\\textbf{this\\\\_is\\\\_strong}"`; exports[`misc renders strongintags.txt 1`] = ` "

this is a test

this is a second test

reference [test][] reference [test][]

" `; exports[`misc renders underscores.txt 1`] = ` "

THIS_SHOULD_STAY_AS_IS

Here is some emphasis, ok?

Ok, at least this should work.

THISSHOULDSTAY

Here is some strong stuff.

THISSHOULDSTAY?

" `; exports[`misc renders underscores.txt 2`] = ` "THIS\\\\_SHOULD\\\\_STAY\\\\_AS\\\\_IS Here is some \\\\textit{emphasis}, ok? Ok, at least \\\\textit{this} should work. THIS\\\\textbf{SHOULD}STAY Here is some \\\\textbf{strong} stuff. THIS\\\\textbf{\\\\textit{SHOULD}}STAY?" `; exports[`options renders no-attributes.txt 1`] = ` "

Regression test for issue 87

It's run with enable_attributes=False so this {@id=explanation} should not become an attribute

" `; exports[`options renders no-attributes.txt 2`] = ` "Regression \\\\textit{test} for issue 87 It's run with enable\\\\_attributes=False so this \\\\{@id=explanation\\\\} should not become an attribute" `; exports[`php renders Code Spans.txt 1`] = ` "

From <!-- to --> on two lines.

From <!-- to --> on three lines.

" `; exports[`php renders Code Spans.txt 2`] = ` "From \\\\CodeInline{} on two lines. From \\\\CodeInline{} on three lines." `; exports[`php renders Code block on second line.txt 1`] = ` "
Codeblock on second line
" `; exports[`php renders Code block on second line.txt 2`] = ` "\\\\begin{CodeBlock}{text} Codeblock on second line \\\\end{CodeBlock}" `; exports[`php renders Horizontal Rules.txt 1`] = ` "

Horizontal rules:






Not horizontal rules (testing for a bug in 1.0.1j):

+++

,,,

===

???

AAA

jjj

j j j

n n n

" `; exports[`php renders Horizontal Rules.txt 2`] = ` "Horizontal rules: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine Not horizontal rules (testing for a bug in 1.0.1j): +++ ,,, === ??? AAA jjj j j j n n n" `; exports[`php renders Inline HTML comments.txt 1`] = ` "

Paragraph one.

<!-- double--dash (invalid SGML comment) -->

Paragraph two.

<!-- enclosed tag </div> -->

The end.

" `; exports[`php renders Inline HTML comments.txt 2`] = ` "Paragraph one. Paragraph two. The end." `; exports[`php renders MD5 Hashes.txt 1`] = ` "

The MD5 value for + is \\"26b17225b626fb9238849fd60eabdf60\\".

<p>test</p>

The MD5 value for <p>test</p> is:

6205333b793f34273d75379350b36826

" `; exports[`php renders MD5 Hashes.txt 2`] = ` "The MD5 value for \\\\CodeInline{+} is \\"26b17225b626fb9238849fd60eabdf60\\".

test

The MD5 value for \\\\CodeInline{

test

} is: 6205333b793f34273d75379350b36826" `; exports[`pl renders Amps and angle encoding.txt 1`] = ` "

AT&T has an ampersand in their name.

AT&amp;T is another way to write it.

This & that.

4 < 5.

6 > 5.

Here's a link with an ampersand in the URL.

Here's a link with an amersand in the link text: AT&T.

Here's an inline link.

Here's an inline link.

" `; exports[`pl renders Amps and angle encoding.txt 2`] = ` "AT\\\\&T has an ampersand in their name. AT\\\\&T is another way to write it. This \\\\& that. 4 < 5. 6 > 5. Here's a \\\\hyperref[1]{link} with an ampersand in the URL. Here's a link with an amersand in the link text: \\\\hyperref[2]{AT\\\\&T}. Here's an inline \\\\externalLink{link}{http://zestedesavoir.com/script?foo=1\\\\&bar=2}. Here's an inline \\\\externalLink{link}{http://zestedesavoir.com/script?foo=1\\\\&bar=2}. \\\\footnote{\\\\label{1}\\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2}} \\\\footnote{\\\\label{2}\\\\externalLink{http://att.com/}{http://att.com/}}" `; exports[`pl renders Auto links.txt 1`] = ` "

Link: http://example.com/.

With an ampersand: http://example.com/?foo=1&bar=2

Blockquoted: http://example.com/

Auto-links should not occur here: <http://example.com/>

or here: <http://example.com/>
" `; exports[`pl renders Auto links.txt 2`] = ` "Link: \\\\externalLink{http://example.com/}{http://example.com/}. With an ampersand: \\\\externalLink{http://example.com/?foo=1\\\\&bar=2}{http://example.com/?foo=1\\\\&bar=2} \\\\begin{itemize} \\\\item\\\\relax In a list? \\\\item\\\\relax \\\\externalLink{http://example.com/}{http://example.com/} \\\\item\\\\relax It should. \\\\end{itemize} \\\\begin{Quotation} Blockquoted: \\\\externalLink{http://example.com/}{http://example.com/} \\\\end{Quotation} Auto-links should not occur here: \\\\CodeInline{} \\\\begin{CodeBlock}{text} or here: \\\\end{CodeBlock}" `; exports[`pl renders Blockquotes with code blocks.txt 1`] = ` "

Example:

sub status {
    print \\"working\\";
}

Or:

sub status {
    return \\"working\\";
}
" `; exports[`pl renders Blockquotes with code blocks.txt 2`] = ` "\\\\begin{Quotation} Example: \\\\begin{CodeBlock}{text} sub status { print \\"working\\"; } \\\\end{CodeBlock} Or: \\\\begin{CodeBlock}{text} sub status { return \\"working\\"; } \\\\end{CodeBlock} \\\\end{Quotation}" `; exports[`pl renders Horizontal rules.txt 1`] = ` "

Dashes:





---




- - -

Asterisks:





***




* * *

Underscores:





___




_ _ _
" `; exports[`pl renders Horizontal rules.txt 2`] = ` "Dashes: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} --- \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} - - - \\\\end{CodeBlock} Asterisks: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} *** \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} * * * \\\\end{CodeBlock} Underscores: \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} ___ \\\\end{CodeBlock} \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\horizontalLine \\\\begin{CodeBlock}{text} _ _ _ \\\\end{CodeBlock}" `; exports[`pl renders Inline HTML comments.txt 1`] = ` "

Paragraph one.

<!-- This is a simple comment -->

<!-- This is another comment. -->

Paragraph two.

<!-- one comment block -- -- with two comments -->

The end.

" `; exports[`pl renders Inline HTML comments.txt 2`] = ` "Paragraph one. Paragraph two. The end." `; exports[`pl renders Links, shortcut references.txt 1`] = ` "

This is the simple case.

This one has a line break.

This one has a line break with a line-ending space.

this and the other

" `; exports[`pl renders Links, shortcut references.txt 2`] = ` "This is the \\\\hyperref[simple case]{simple case}. \\\\footnote{\\\\label{simple case}\\\\externalLink{/simple}{http://zestedesavoir.com/simple}} This one has a \\\\hyperref[line break]{line break}. This one has a \\\\hyperref[line break]{line break} with a line-ending space. \\\\footnote{\\\\label{line break}\\\\externalLink{/foo}{http://zestedesavoir.com/foo}} \\\\hyperref[that]{this} and the \\\\hyperref[other]{other} \\\\footnote{\\\\label{this}\\\\externalLink{/this}{http://zestedesavoir.com/this}} \\\\footnote{\\\\label{that}\\\\externalLink{/that}{http://zestedesavoir.com/that}} \\\\footnote{\\\\label{other}\\\\externalLink{/other}{http://zestedesavoir.com/other}}" `; exports[`pl renders Nested blockquotes.txt 1`] = ` "

foo

bar

foo

" `; exports[`pl renders Nested blockquotes.txt 2`] = ` "\\\\begin{Quotation} foo \\\\begin{Quotation} bar \\\\end{Quotation} foo \\\\end{Quotation}" `; exports[`pl renders Ordered and unordered lists.txt 1`] = ` "

Asterisks tight:

  • asterisk 1
  • asterisk 2
  • asterisk 3

Asterisks loose:

  • asterisk 1

  • asterisk 2

  • asterisk 3


Pluses tight:

  • Plus 1
  • Plus 2
  • Plus 3

Pluses loose:

  • Plus 1

  • Plus 2

  • Plus 3


Minuses tight:

  • Minus 1
  • Minus 2
  • Minus 3

Minuses loose:

  • Minus 1

  • Minus 2

  • Minus 3

Tight:

  1. First
  2. Second
  3. Third

and:

  1. One
  2. Two
  3. Three

Loose using tabs:

  1. First

  2. Second

  3. Third

and using spaces:

  1. One

  2. Two

  3. Three

Multiple paragraphs:

  1. Item 1, graf one.

    Item 2. graf two. The quick brown fox jumped over the lazy dog's back.

  2. Item 2.

  3. Item 3.

  • Tab
    • Tab
      • Tab

Here's another:

  1. First
  2. Second:
    • Fee
    • Fie
    • Foe
  3. Third

Same thing but with paragraphs:

  1. First

  2. Second:

    • Fee
    • Fie
    • Foe
  3. Third

This was an error in Markdown 1.0.1:

  • this

    • sub

    that

" `; exports[`pl renders Ordered and unordered lists.txt 2`] = ` "Asterisks tight: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} Asterisks loose: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\horizontalLine Pluses tight: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} Pluses loose: \\\\begin{itemize} \\\\item\\\\relax Plus 1 \\\\item\\\\relax Plus 2 \\\\item\\\\relax Plus 3 \\\\end{itemize} \\\\horizontalLine Minuses tight: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} Minuses loose: \\\\begin{itemize} \\\\item\\\\relax Minus 1 \\\\item\\\\relax Minus 2 \\\\item\\\\relax Minus 3 \\\\end{itemize} Tight: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Loose using tabs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second \\\\item\\\\relax Third \\\\end{enumerate} and using spaces: \\\\begin{enumerate} \\\\item\\\\relax One \\\\item\\\\relax Two \\\\item\\\\relax Three \\\\end{enumerate} Multiple paragraphs: \\\\begin{enumerate} \\\\item\\\\relax Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. \\\\item\\\\relax Item 2. \\\\item\\\\relax Item 3. \\\\end{enumerate} \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\begin{itemize} \\\\item\\\\relax Tab \\\\end{itemize} \\\\end{itemize} \\\\end{itemize} Here's another: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate} Same thing but with paragraphs: \\\\begin{enumerate} \\\\item\\\\relax First \\\\item\\\\relax Second: \\\\begin{itemize} \\\\item\\\\relax Fee \\\\item\\\\relax Fie \\\\item\\\\relax Foe \\\\end{itemize} \\\\item\\\\relax Third \\\\end{enumerate} This was an error in Markdown 1.0.1: \\\\begin{itemize} \\\\item\\\\relax this \\\\begin{itemize} \\\\item\\\\relax sub \\\\end{itemize} that \\\\end{itemize}" `; exports[`pl renders Strong and em together.txt 1`] = ` "

This is strong and em.

So is this word.

This is strong and em.

So is this word.

" `; exports[`pl renders Strong and em together.txt 2`] = ` "\\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word. \\\\textbf{\\\\textit{This is strong and em.}} So is \\\\textbf{\\\\textit{this}} word." `; exports[`pl renders Tabs.txt 1`] = ` "
  • this is a list item indented with tabs

  • this is a list item indented with spaces

Code:

this code block is indented by one tab

And:

	this code block is indented by two tabs

And:

+	this is an example list item
	indented with tabs

+   this is an example list item
    indented with spaces
" `; exports[`pl renders Tabs.txt 2`] = ` "\\\\begin{itemize} \\\\item\\\\relax this is a list item indented with tabs \\\\item\\\\relax this is a list item indented with spaces \\\\end{itemize} Code: \\\\begin{CodeBlock}{text} this code block is indented by one tab \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} this code block is indented by two tabs \\\\end{CodeBlock} And: \\\\begin{CodeBlock}{text} + this is an example list item indented with tabs + this is an example list item indented with spaces \\\\end{CodeBlock}" `; exports[`pl renders Tidyness.txt 1`] = ` "

A list within a blockquote:

  • asterisk 1
  • asterisk 2
  • asterisk 3
" `; exports[`pl renders Tidyness.txt 2`] = ` "\\\\begin{Quotation} A list within a blockquote: \\\\begin{itemize} \\\\item\\\\relax asterisk 1 \\\\item\\\\relax asterisk 2 \\\\item\\\\relax asterisk 3 \\\\end{itemize} \\\\end{Quotation}" `; exports[`zds renders align.txt 1`] = ` "

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A simple paragraph

A centered paragraph

a simple paragraph

A right aligned paragraph

an other simple paragraph

A centered paragraph.

Containing two paragraph

an other simple paragraph

A right aligned paragraph.

Containing two paragraph

an other simple paragraph

A centered paragraph.

An other centered paragraph.

a simple paragraph

->A started block without end.

" `; exports[`zds renders align.txt 2`] = ` "A simple paragraph {\\\\centering A centered paragraph } a simple paragraph {\\\\raggedleft A right aligned paragraph } an other simple paragraph A simple paragraph {\\\\centering A centered paragraph } a simple paragraph {\\\\raggedleft A right aligned paragraph } an other simple paragraph {\\\\centering A centered paragraph. Containing two paragraph } an other simple paragraph {\\\\centering A right aligned paragraph. Containing two paragraph } an other simple paragraph {\\\\centering A centered paragraph. An other centered paragraph. } a simple paragraph ->A started block without end." `; exports[`zds renders comments.txt 1`] = ` "

BlablaBalbla

Blabla<--COMMENTS hahaha COMMENTS-->Balbla

<--COMMENTS Unfinished block

" `; exports[`zds renders comments.txt 2`] = ` "Blabla\\\\begin{comment} hahaha \\\\end{comment} Balbla \\\\begin{CodeBlock}{text} Blabla<--COMMENTS hahaha COMMENTS-->Balbla \\\\end{CodeBlock} <--COMMENTS Unfinished block" `; exports[`zds renders customblock.txt 1`] = ` "

Secret Block

Secret Block

another

Blockquote in secret block in blockquote

Information Block

an other

Question Block

an other

Attention Block

an other

Erreur Block

an other

[[se]] | not a block

[[secretsecret]] | not a block

[[SECRET]] | not a block

Multiline block

with blockquote !

| Not a block

content before

A Block

with content after

a title

content

a title

content

" `; exports[`zds renders customblock.txt 2`] = ` "\\\\begin{Spoiler} Secret Block \\\\end{Spoiler} \\\\begin{Spoiler} Secret Block \\\\end{Spoiler} \\\\begin{Spoiler} another \\\\end{Spoiler} \\\\begin{Quotation} \\\\begin{Spoiler} \\\\begin{Quotation} Blockquote in secret block in blockquote \\\\end{Quotation} \\\\end{Spoiler} \\\\end{Quotation} \\\\begin{Information} Information Block \\\\end{Information} \\\\begin{Information} an other \\\\end{Information} \\\\begin{Question} Question Block \\\\end{Question} \\\\begin{Question} an other \\\\end{Question} \\\\begin{Warning} Attention Block \\\\end{Warning} \\\\begin{Warning} an other \\\\end{Warning} \\\\begin{Error} Erreur Block \\\\end{Error} \\\\begin{Error} an other \\\\end{Error} [[se]] | not a block [[secretsecret]] | not a block [[SECRET]] | not a block \\\\begin{Spoiler} Multiline block \\\\begin{Quotation} with blockquote ! \\\\end{Quotation} \\\\end{Spoiler} | Not a block content before \\\\begin{Spoiler} A Block \\\\end{Spoiler} with content after \\\\begin{Error}[{{a \\\\textbf{title}}}] content \\\\end{Error} \\\\begin{Neutral}[{{a \\\\textbf{title}}}] content \\\\end{Neutral}" `; exports[`zds renders delext.txt 1`] = ` "

Blabla truc kxcvj sdv sd sdff

sdf df

sfdgs ~ ~ dfg ~~ dgsg ~ qs

" `; exports[`zds renders delext.txt 2`] = ` "Blabla \\\\sout{truc} kxcvj \\\\sout{sdv sd} sdff sdf \\\\sout{} df sfdgs \\\\textasciitilde{} \\\\textasciitilde{} dfg \\\\textasciitilde{}\\\\textasciitilde{} dgsg \\\\textasciitilde{} qs" `; exports[`zds renders emoticons.txt 1`] = ` "

Lolilol \\":)\\" Hey \\":D\\"

\\":)\\"

Citation

\\":D\\" Ce n'est pas une légende

\\"toto\\"
toto

\\":D\\" ce n'est pas une légende non plus

" `; exports[`zds renders emoticons.txt 2`] = ` "Lolilol \\\\smiley{smile.svg} Hey \\\\smiley{heureux.svg} \\\\smiley{smile.svg} \\\\begin{Quotation} Citation \\\\end{Quotation} \\\\smiley{heureux.svg} Ce n'est pas une légende \\\\image{https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png}[toto] \\\\smiley{heureux.svg} ce n'est pas une légende non plus" `; exports[`zds renders grid_tables.txt 1`] = ` "

Grid table

Basic example

Table Headings

Here

Sub

Headings

Too

cell spans rows

column spanning

normal

cell

multi line

cells too

cells can be formatted paragraphs

A

B

C

D

E

F

G

A

B

C

D

E

A

B

C

D

E

C

D

E

F

G

H

I

A

B

C

D

E

F

G

A

B

C

D

E

F

G

A

B

C

D

E

F

G

A

B

C

D

E

F

G

H

I

A

B

C

D

E

F

G

H

H

He

Li

Be

B

C

N

O

F

Ne

Na

Mg

Al

Si

P

S

Cl

Ar

K

Ca

Sc

Ti

V

Cr

Mn

Fe

Co

Ni

Cu

Zn

Ga

Ge

As

Se

Br

Kr

Rb

Sr

Y

Zr

Nb

Mo

Tc

Ru

Rh

Pd

Ag

Cd

In

Sn

Sb

Te

I

Xe

Cs

Ba

LAN

Hf

Ta

W

Re

Os

Ir

Pt

Au

Hg

Tl

Pb

Bi

Po

At

Rn

Fr

Ra

ACT

Lanthanide

La

Ce

Pr

Nd

Pm

Sm

Eu

Gd

Tb

Dy

Ho

Er

Tm

Yb

Lu

Actinide

Ac

Th

Pa

U

Np

Pu

Am

Cm

Bk

Cf

Es

Fm

Md

No

Lw

A

Text at the end

A

Text at the

specific tests

In this examples, the second row should always be a full-cell

A

B | C

D

E

F

G

A

B | C

D E

F

G

A

B

C

D

B | C

D

E

F

G

A

B

C

D

B | C

D

E

F

G

Failing example

+--- A ---+

+---------+ +---------+

+---------+ | A | | |

+---------+ | A | +=========+ | B | +=========+

+--- A ---+ | |

Bug #107

case1

case2

case3

case4

case5

case6

case7

X

X

X

X

X

X

" `; exports[`zds renders grid_tables.txt 2`] = ` "\\\\levelOneTitle{Grid table} \\\\levelTwoTitle{Basic example} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=2,row{1,2}={font=\\\\bfseries}} \\\\SetCell[c=2]{l} Table Headings & & Here \\\\\\\\ Sub & Headings & Too \\\\\\\\ \\\\SetCell[r=2]{l} cell \\\\endgraf spans \\\\endgraf rows & \\\\SetCell[c=2]{l} column spanning & \\\\\\\\ & normal & cell \\\\\\\\ multi \\\\endgraf line \\\\endgraf \\\\endgraf cells \\\\endgraf too & \\\\SetCell[c=2]{l} cells can be \\\\endgraf \\\\textit{formatted} \\\\endgraf \\\\textbf{paragraphs} & \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & B & C \\\\\\\\ \\\\SetCell[r=2]{l} D & \\\\SetCell[c=2]{l} E & \\\\\\\\ & F & G \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[r=3]{l} A & \\\\SetCell[c=2]{l} B & \\\\\\\\ & C & D \\\\\\\\ & \\\\SetCell[c=2]{l} E & \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[c=3]{l} A & & \\\\\\\\ \\\\SetCell[r=2]{l} B & C & \\\\SetCell[r=2]{l} D \\\\\\\\ E & \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1]}} \\\\SetCell[r=4]{l} C & \\\\SetCell[r=2]{l} D & E \\\\\\\\ F & \\\\\\\\ \\\\SetCell[r=2]{l} G & H \\\\\\\\ & I \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1]}} A & \\\\SetCell[r=2]{l} B & \\\\SetCell[r=4]{l} C \\\\\\\\ D & \\\\\\\\ E & \\\\SetCell[r=2]{l} F & \\\\\\\\ G & \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} A & B & C & D \\\\\\\\ \\\\SetCell[c=2]{l} E & & \\\\SetCell[c=2]{l} F & \\\\\\\\ \\\\SetCell[c=4]{l} G & & & \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[c=4]{l} A & & & \\\\\\\\ \\\\SetCell[c=2]{l} B & & \\\\SetCell[c=2]{l} C & \\\\\\\\ D & E & F & G \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[r=3]{l} A & \\\\SetCell[r=2]{l} B & C & D & \\\\SetCell[r=2]{l} E & \\\\SetCell[r=3]{l} F \\\\\\\\ \\\\SetCell[c=2]{l} G & & \\\\\\\\ \\\\SetCell[c=4]{l} H & & & & \\\\\\\\ \\\\SetCell[c=6]{l} I & & & & & \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=1,row{1}={font=\\\\bfseries}} A & \\\\SetCell[c=6]{l} B & & & & & \\\\\\\\ \\\\SetCell[r=4]{l} C & \\\\SetCell[r=4]{l} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} D & E & F & G \\\\\\\\ \\\\SetCell[c=4]{l} H & & & \\\\\\\\ \\\\end{zdstblr} & & & & & \\\\\\\\ & & & & & \\\\\\\\ & & & & & \\\\\\\\ & & & & & \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} H & \\\\SetCell[c=16]{l} & & & & & & & & & & & & & & & & He \\\\\\\\ Li & Be & \\\\SetCell[r=2]{l} & & & & & & & & & & B & C & N & O & F & Ne \\\\\\\\ Na & Mg & & & & & & & & & & & Al & Si & P & S & Cl & Ar \\\\\\\\ K & Ca & Sc & Ti & V & Cr & Mn & Fe & Co & Ni & Cu & Zn & Ga & Ge & As & Se & Br & Kr \\\\\\\\ Rb & Sr & Y & Zr & Nb & Mo & Tc & Ru & Rh & Pd & Ag & Cd & In & Sn & Sb & Te & I & Xe \\\\\\\\ Cs & Ba & LAN & Hf & Ta & W & Re & Os & Ir & Pt & Au & Hg & Tl & Pb & Bi & Po & At & Rn \\\\\\\\ Fr & Ra & ACT & \\\\SetCell[c=15]{l} & & & & & & & & & & & & & & \\\\\\\\ \\\\SetCell[c=18]{l} & & & & & & & & & & & & & & & & & \\\\\\\\ \\\\SetCell[c=3]{l} Lanthanide & & & La & Ce & Pr & Nd & Pm & Sm & Eu & Gd & Tb & Dy & Ho & Er & Tm & Yb & Lu \\\\\\\\ \\\\SetCell[c=3]{l} Actinide & & & Ac & Th & Pa & U & Np & Pu & Am & Cm & Bk & Cf & Es & Fm & Md & No & Lw \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1]}} A \\\\\\\\ \\\\end{zdstblr} Text at the end \\\\begin{zdstblr}{colspec={X[-1]}} A \\\\\\\\ \\\\end{zdstblr} Text at the \\\\levelTwoTitle{specific tests} In this examples, the second row should always be a full-cell \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} \\\\SetCell[c=4]{l} A & & & \\\\\\\\ \\\\SetCell[c=4]{l} B | C & & & \\\\\\\\ D & E & F & G \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} A & \\\\SetCell[c=3]{l} & & \\\\\\\\ \\\\SetCell[c=4]{l} B | C & & & \\\\\\\\ \\\\SetCell[c=2]{l} D E & & F & G \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} A & B & C & D \\\\\\\\ \\\\SetCell[c=4]{l} B | C & & & \\\\\\\\ D & E & F & G \\\\\\\\ \\\\end{zdstblr} \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]}} A & B & C & D \\\\\\\\ \\\\SetCell[c=4]{l} B | C & & & \\\\\\\\ D & E & F & G \\\\\\\\ \\\\end{zdstblr} \\\\levelTwoTitle{Failing example} +--- A ---+ +---------+ +---------+ +---------+ | A | | | +---------+ | A | +=========+ | B | +=========+ +--- A ---+ | | \\\\begin{zdstblr}{colspec={X[-1]}} \\\\\\\\ \\\\end{zdstblr} Bug \\\\#107 \\\\begin{zdstblr}{colspec={X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1] X[-1]},rowhead=2,row{1,2}={font=\\\\bfseries}} & \\\\SetCell[c=2]{l} case1 & & \\\\SetCell[c=2]{l} case2 & & case3 \\\\\\\\ & case4 & case5 & case6 & case7 & \\\\\\\\ X & X & X & X & X & X \\\\\\\\ \\\\end{zdstblr}" `; exports[`zds renders kbd.txt 1`] = ` "

Blabla ok kxcvj ok foo sdff

sdf |||| df

sfdgs | | dfg || dgsg | qs

With two pipes: ||key|| you'll get key.

It parses inline elements inside:

  • hello?

but not block elements inside:

  • hello: [[secret]]?
" `; exports[`zds renders kbd.txt 2`] = ` "Blabla \\\\keys{ok} kxcvj \\\\keys{ok foo} sdff sdf |||| df sfdgs | | dfg || dgsg | qs With two pipes: ||key|| you'll get \\\\keys{key}. It parses inline elements inside: \\\\begin{itemize} \\\\item\\\\relax \\\\keys{hell\\\\externalLink{\\\\sout{o}}{\\\\#he}?} \\\\end{itemize} but not block elements inside: \\\\begin{itemize} \\\\item\\\\relax \\\\keys{hello: [[secret]]?} \\\\end{itemize}" `; exports[`zds renders subsuperscript.txt 1`] = ` "

Foo sup kxcvj sup string bar

not ^ here

neither ^ here ^ because it's escaped

Foo sup kxcvj sup string bar

not ~ here

neither ~ here ~ because it's escaped

foo ^a^ bar

" `; exports[`zds renders subsuperscript.txt 2`] = ` "Foo \\\\textsuperscript{sup} kxcvj \\\\textsuperscript{sup \\\\textit{string}} bar not \\\\textasciicircum{} here neither \\\\textasciicircum{} here \\\\textasciicircum{} because it's escaped Foo \\\\textsubscript{sup} kxcvj \\\\textsubscript{sup \\\\textit{string}} bar not \\\\textasciitilde{} here neither \\\\textasciitilde{} here \\\\textasciitilde{} because it's escaped foo \\\\textasciicircum{}\\\\textsuperscript{a}\\\\textasciicircum{} bar" `; exports[`zds renders urlize.txt 1`] = ` "

http://www.google.fr

https://www.google.fr

www.google.fr

google.fr

http://www.google.fr

Voici mon super lien qui termine une phrase http://www.google.fr.

https://fr.wikipedia.org/wiki/Compactifié_d'Alexandrov

https://fr.wikipedia.org/wiki/Compactifi%C3%A9_d%27Alexandrov

javascript:alert%28'Hello%20world!'%29

vbscript:msgbox%28%22Hello%20world!%22%29

livescript:alert%28'Hello%20world!'%29

mocha:[code])

jAvAsCrIpT:alert%28'Hello%20world!'%29

ja&#32;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29

ja&#00032;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29

ja&#x00020;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29

ja%09&#x20;%0Avas&#32;cr&#x0a;ipt:alert%28'Hello%20world!'%29

ja%20vas%20cr%20ipt:alert%28'Hello%20world!'%29

live%20script:alert%28'Hello%20world!'%29

javascript:alert%29'XSS'%29

sur isocpp.org

" `; exports[`zds renders urlize.txt 2`] = ` "\\\\externalLink{http://www.google.fr}{http://www.google.fr} \\\\externalLink{https://www.google.fr}{https://www.google.fr} \\\\externalLink{www.google.fr}{http://www.google.fr} google.fr \\\\externalLink{http://www.google.fr}{http://www.google.fr} Voici mon super lien qui termine une phrase \\\\externalLink{http://www.google.fr}{http://www.google.fr}. \\\\externalLink{https://fr.wikipedia.org/wiki/Compactifié\\\\_d'Alexandrov}{https://fr.wikipedia.org/wiki/Compactifié\\\\_d'Alexandrov} \\\\externalLink{https://fr.wikipedia.org/wiki/Compactifi\\\\%C3\\\\%A9\\\\_d\\\\%27Alexandrov}{https://fr.wikipedia.org/wiki/Compactifi\\\\%C3\\\\%A9\\\\_d\\\\%27Alexandrov} javascript:alert\\\\%28'Hello\\\\%20world!'\\\\%29 vbscript:msgbox\\\\%28\\\\%22Hello\\\\%20world!\\\\%22\\\\%29 livescript:alert\\\\%28'Hello\\\\%20world!'\\\\%29 mocha:[code]) jAvAsCrIpT:alert\\\\%28'Hello\\\\%20world!'\\\\%29 ja\\\\&\\\\#32;vas\\\\&\\\\#32;cr\\\\&\\\\#32;ipt:alert\\\\%28'Hello\\\\%20world!'\\\\%29 ja\\\\&\\\\#00032;vas\\\\&\\\\#32;cr\\\\&\\\\#32;ipt:alert\\\\%28'Hello\\\\%20world!'\\\\%29 ja\\\\&\\\\#x00020;vas\\\\&\\\\#32;cr\\\\&\\\\#32;ipt:alert\\\\%28'Hello\\\\%20world!'\\\\%29 ja\\\\%09\\\\&\\\\#x20;\\\\%0Avas\\\\&\\\\#32;cr\\\\&\\\\#x0a;ipt:alert\\\\%28'Hello\\\\%20world!'\\\\%29 ja\\\\%20vas\\\\%20cr\\\\%20ipt:alert\\\\%28'Hello\\\\%20world!'\\\\%29 live\\\\%20script:alert\\\\%28'Hello\\\\%20world!'\\\\%29 javascript:alert\\\\%29'XSS'\\\\%29 \\\\externalLink{sur isocpp.org}{https://isocpp.org/std/status}" `; exports[`zds renders video.txt 1`] = ` "

!(https://screen.yahoo.com/weatherman-gives-forecast-using-taylor-191821481.html)

This one should not be allowed:

!(http://jsfiddle.net/Sandhose/BcKhe/)

with text after

" `; exports[`zds renders video.txt 2`] = ` "\\\\iframe{https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed}[Video][] \\\\iframe{https://geo.dailymotion.com/player.html?video=x2y6lhm&}[Video][] \\\\iframe{https://player.vimeo.com/video/133693532?app_id=122963}[Video][] !(https://screen.yahoo.com/weatherman-gives-forecast-using-taylor-191821481.html) \\\\iframe{https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed}[Video][] \\\\iframe{https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed}[Video][] \\\\iframe{https://jsfiddle.net/Sandhose/BcKhe/1/embedded/result,js,html,css/}[Code][] \\\\iframe{https://jsfiddle.net/zgjhjv9j/embedded/result,js,html,css/}[Code][] \\\\iframe{https://jsfiddle.net/zgjhjv9j/1/embedded/result,js,html,css/}[Code][] \\\\iframe{https://www.youtube.com/embed/1Bh4DZ2xGmw?feature=oembed}[Video][] \\\\iframe{http://player.ina.fr/player/embed/MAN9062216517/1/1b0bd203fbcd702f9bc9b10ac3d0fc21/560/315/1/148db8}[Video][] This one should not be allowed: !(http://jsfiddle.net/Sandhose/BcKhe/) \\\\iframe{https://www.youtube.com/embed/FdltlrKFr1w?feature=oembed}[Video][] with text after" `; ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/mdast-suite.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 6, "line": 1, "offset": 5, }, "indent": Array [], "start": Object { "column": 3, "line": 1, "offset": 2, }, }, "type": "text", "value": "foo", }, ], "depth": 1, "position": Position { "end": Object { "column": 6, "line": 1, "offset": 5, }, "indent": Array [], "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "heading", }, Object { "children": Array [ Object { "children": Array [ Object { "position": Position { "end": Object { "column": 17, "line": 2, "offset": 22, }, "indent": Array [], "start": Object { "column": 3, "line": 2, "offset": 8, }, }, "type": "text", "value": "something else", }, ], "position": Position { "end": Object { "column": 19, "line": 2, "offset": 24, }, "indent": Array [], "start": Object { "column": 1, "line": 2, "offset": 6, }, }, "type": "strong", }, ], "position": Position { "end": Object { "column": 19, "line": 2, "offset": 24, }, "indent": Array [], "start": Object { "column": 1, "line": 2, "offset": 6, }, }, "type": "paragraph", }, ], "position": Object { "end": Object { "column": 19, "line": 2, "offset": 24, }, "start": Object { "column": 1, "line": 1, "offset": 0, }, }, "type": "root", } `; ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/misc.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`sanitizer do not oversanitize math - test color 1`] = `"

x\\\\color{red}{x}x

"`; exports[`sanitizer do not oversanitize math - test frac 1`] = `"

1+1x+y\\\\frac{1+1}{x+y}x+y1+1

"`; exports[`sanitizer do not oversanitize math - test notin 1`] = `"

\\\\notin/

"`; exports[`sanitizer do not oversanitize math - test overrightarrow 1`] = ` "

AB\\\\overrightarrow{AB}AB

" `; exports[`sanitizer do not oversanitize math - test sqrt 1`] = ` "

x3+y3+z33\\\\sqrt[3]{x^3 + y^3 + z^3}3x3+y3+z3

" `; exports[`sanitizer do not oversanitize math - test sub/sup 1`] = `"

1n+1n1_n + 1^n1n+1n

"`; exports[`sanitizer do not oversanitize math - test vec 1`] = ` "

a\\\\vec{a}a

" `; ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/regressions.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Regression tests HTML endpoint #188 It does not crash on unsupported languages fenced code blocks 1`] = ` "

foo

console.error(\\"foo\\", true)
console.error(\\"foo\\", true)
" `; exports[`Regression tests Latex endpoint It wraps image basenames containing dots 1`] = ` "\\\\image{{x.yz}.png} \\\\externalLink{\\\\image{/a/{w.x.y.z}.png}}{http://example.com} \\\\image{/{w.x.y.z}.png} \\\\image{/foo.bar/{x.yz}.png} " `; ================================================ FILE: packages/zmarkdown/__tests__/__snapshots__/server.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HTML endpoint accepts POSTed markdown 1`] = `"

foo

"`; exports[`HTML endpoint can use custom iframe elements 1`] = ` "

You won’t get my data, Google!

" `; exports[`HTML endpoint correctly renders manifest 1`] = ` Object { "children": Array [ Object { "text": "

On a balcony in summer air

", }, Object { "text": "

Escape this town for a little while

", }, Object { "text": "

Marry me, Juliet, you’ll never have to be alone

", }, ], "conclusion": "

Just say \\"Yes\\"

", "title": "

A story

", } `; exports[`HTML endpoint enforce level shifting by default 1`] = ` "

I have seen a dolphin

On a camera. What is happening with animals these days?\\"

" `; exports[`HTML endpoint produces statistics when configured 1`] = ` "

7 chars

13 chars here

13 chars here

\\"13
13 chars here
\\"no
13 chars here
" `; exports[`LaTeX endpoint accepts POSTed markdown 1`] = ` "\\\\levelOneTitle{foo} " `; exports[`LaTeX endpoint correctly renders manifest 1`] = ` Object { "children": Array [ Object { "text": "I’m standing there ", }, Object { "text": "And I was crying on the staircase ", }, Object { "text": "I got tired of waiting ", }, ], "conclusion": "\\\\begin{LevelOneConclusion} Just say \\"Yes\\" \\\\end{LevelOneConclusion} ", "title": "\\\\levelOneTitle{Another story} ", } `; exports[`Texfile endpoint accepts POSTed markdown 1`] = ` "\\\\documentclass[contentType]{zmdocument} \\\\usepackage{blindtext} \\\\title{The Title} \\\\author{FØØ, Bär} \\\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode} \\\\smileysPath{/tmp/s} \\\\makeglossaries \\\\begin{document} \\\\maketitle \\\\tableofcontents \\\\levelOneTitle{foo} \\\\end{document}" `; exports[`Texfile endpoint allows date 1`] = ` "\\\\documentclass[contentType]{zmdocument} \\\\usepackage{blindtext} \\\\title{The Title} \\\\author{FØØ, Bär} \\\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode} \\\\date{2 mai 1998} \\\\smileysPath{/tmp/s} \\\\makeglossaries \\\\begin{document} \\\\maketitle \\\\tableofcontents \\\\levelOneTitle{foo} \\\\end{document}" `; exports[`Texfile endpoint allows extra arguments 1`] = ` "\\\\documentclass[contentType]{zmdocument} \\\\usepackage{blindtext} \\\\title{The Title} \\\\author{FØØ, Bär} \\\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode} \\\\logo{/tmp/logo/h2g2.png} \\\\editorLogo{/tmp/logo/pmm.jpg} \\\\tutoLink{https://en.wikipedia.org/wiki/The_Hitchhiker%27s_Guide_to_the_Galaxy_(novel)} \\\\editor{https://www.panmacmillan.com/} \\\\smileysPath{/tmp/s} \\\\makeglossaries \\\\begin{document} \\\\maketitle \\\\tableofcontents \\\\levelOneTitle{foo} \\\\end{document}" `; exports[`Texfile endpoint correctly renders introduction & conclusion 1`] = ` "\\\\documentclass[contentType]{zmdocument} \\\\usepackage{blindtext} \\\\title{The Title} \\\\author{FØØ, Bär} \\\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode} \\\\smileysPath{/tmp/s} \\\\makeglossaries \\\\begin{document} \\\\maketitle \\\\tableofcontents \\\\levelOneTitle{My content™} \\\\begin{LevelOneIntroduction} Here I introduce My content™ \\\\end{LevelOneIntroduction} \\\\begin{LevelThreeIntroduction} Here I introduce My section™ \\\\end{LevelThreeIntroduction} \\\\begin{LevelThreeConclusion} Here I conclude My section™ \\\\end{LevelThreeConclusion} \\\\begin{LevelOneConclusion} Here I conclude My content™ \\\\end{LevelOneConclusion} \\\\end{document}" `; exports[`Texfile endpoint escapes title and author 1`] = ` "\\\\documentclass[contentType]{zmdocument} \\\\usepackage{blindtext} \\\\title{recap \\\\#1} \\\\author{titi\\\\_alone} \\\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode} \\\\smileysPath{/tmp/s} \\\\makeglossaries \\\\begin{document} \\\\maketitle \\\\tableofcontents \\\\levelOneTitle{foo} \\\\end{document}" `; exports[`Texfile endpoint shifts titles and only titles 1`] = ` "\\\\documentclass[contentType]{zmdocument} \\\\usepackage{blindtext} \\\\title{The Title} \\\\author{FØØ, Bär} \\\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode} \\\\smileysPath{/tmp/s} \\\\makeglossaries \\\\begin{document} \\\\maketitle \\\\tableofcontents \\\\levelThreeTitle{myTitle} \\\\begin{LevelOneIntroduction} myIntro \\\\end{LevelOneIntroduction} \\\\end{document}" `; exports[`Texfile endpoint transform quizzes for document 1`] = ` "\\\\documentclass[contentType]{zmdocument} \\\\usepackage{blindtext} \\\\title{The Title} \\\\author{FØØ, Bär} \\\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode} \\\\smileysPath{/tmp/s} \\\\makeglossaries \\\\begin{document} \\\\maketitle \\\\tableofcontents \\\\begin{Neutral}[{{What is true?}}] \\\\begin{itemize} \\\\item\\\\relax true \\\\item\\\\relax false \\\\end{itemize} \\\\end{Neutral} \\\\begin{Spoiler}[{{Correction}}] \\\\begin{itemize} \\\\item\\\\relax true \\\\item\\\\relax false \\\\end{itemize} \\\\end{Spoiler} \\\\end{document}" `; ================================================ FILE: packages/zmarkdown/__tests__/api.test.js ================================================ const clone = require('clone') const dedent = require('dedent') const zmarkdown = require('../common') const trimContent = vfile => vfile.contents.trim() describe('mdast', () => { it('uses default config', () => { const parser = zmarkdown(null) expect(parser('# some md\n\n@hello')).toMatchSnapshot() }) it('can take custom config', () => { const mdastConfig = clone(require('../config/mdast')) mdastConfig.ping.pingUsername = _ => false const parser = zmarkdown(null, mdastConfig) expect(parser('# some md\n\n@hello')).toMatchSnapshot() }) }) describe('html', () => { it('uses default config', () => { const parser = zmarkdown('html') expect(parser(dedent` # some md \`\`\`latex Some LaTeX \`\`\` `).then(trimContent)).resolves.toMatchSnapshot() }) it('can take custom config', () => { const mdastConfig = clone(require('../config/mdast')) const htmlConfig = clone(require('../config/html')) htmlConfig.postProcessors.wrapCode = false const parser = zmarkdown('html', mdastConfig, htmlConfig) expect(parser(dedent` # some md \`\`\`latex Some LaTeX \`\`\` `).then(trimContent)).resolves.toMatchSnapshot() }) }) describe('latex', () => { it('uses default config', () => { const parser = zmarkdown('latex') expect(parser('# some md').then(trimContent)).resolves.toMatchSnapshot() }) it('can take custom config', () => { const mdastConfig = clone(require('../config/mdast')) const latexConfig = clone(require('../config/latex')) latexConfig.headings[0] = (val) => `\\thisIsNotATitle{${val}}\n` const parser = zmarkdown('latex', mdastConfig, latexConfig) expect(parser('# some md').then(trimContent)).resolves.toMatchSnapshot() }) }) describe('misc', () => { it('can use callback', done => { const parser = zmarkdown('latex') parser('# some md', (err, vfile) => { expect(err).toBeNull() expect(trimContent(vfile)).toMatchSnapshot() done() }) }) }) ================================================ FILE: packages/zmarkdown/__tests__/html-suite.test.js ================================================ const dedent = require('dedent') const { defaultMdastConfig, defaultHtmlConfig, renderAs, } = require('../utils/renderer-tests') const renderString = renderAs('html') describe('math', () => { it('must escape a dollar with backslash', () => { const markdown = '$\\alpha\\$' expect(renderString(markdown)).resolves.not.toMatch('inlineMath') }) it('must not parse a raw starting dollar', () => { const markdown = '`$`\\alpha$' expect(renderString(markdown)).resolves.not.toMatch('inlineMath') }) it('must not parse a raw ending dollar', () => { const markdown = '$\\alpha`$` foo' expect(renderString(markdown)).resolves.not.toMatch('inlineMath') }) it("must not parse what's inside inline maths as markdown", () => { const markdown = '$`\\alpha`$' expect(renderString(markdown)).resolves.not.toMatch(' { const markdown = '$\\ce{H2O}$' const result = await renderString(markdown, true) expect(result.messages).toEqual([]) expect(result.contents).toContain('H') }) }) describe('pedantic', () => { it('must not parse * and _ surrounded by spaces', () => { const markdown = 'a * b * c' expect(renderString(markdown)).resolves.not.toMatch('strong') }) }) const maxNesting = defaultMdastConfig.postProcessors.limitDepth describe('depth checks', () => { it(`is fast enough with ${maxNesting} nested quotes`, () => { const base = ['foo', '\n'] const input = Array.from({length: maxNesting}).reduce((acc, _, i) => { return acc + base.map((x, j) => ('>'.repeat(i) + ((i && !j && ' ') || '') + x)).join('\n') }, '') const a = Date.now() const render = renderString(input).then((ok, fail) => { const b = Date.now() if ((b - a) < 2000) return Promise.resolve('ok') return Promise.reject(`Rendering ${maxNesting} nest blockquotes took too long: ${b - a}ms.`) }) return expect(render).resolves.toBe('ok') }) it(`fails with > ${maxNesting} nested quotes`, () => { const base = ['foo', '\n'] const input = Array.from({length: maxNesting + 1}).reduce((acc, _, i) => { return acc + base.map((x, j) => ('>'.repeat(i) + ((i && !j && ' ') || '') + x)).join('\n') }, '') return expect( renderString(input).catch((err) => Promise.reject(err.message)), ).rejects.toContain(`Markdown AST too complex: tree depth > ${maxNesting}`) }) }) describe('tables', () => { it(`with pipes in code in cells`, () => { const input = dedent` Titre 1 | Titre 2 --------|-------- \`ici ok\`| \`gauche | droite\` ` return expect(renderString(input)).resolves.toContain('`gauche') }) it(`with escaped pipes in code in cells`, () => { const input = dedent` Titre 1 | Titre 2 --------|-------- \`ici ok\`| \`gauche \| droite\` ` return expect(renderString(input)).resolves.toContain('gauche \\| droite') }) }) describe('images become figures:', () => { it('works with only an image', () => { const input = dedent` ![wrapped into _figure_](http://blabla.fr)` return expect(renderString(input)).resolves.toMatchSnapshot() }) it('does not apply to images not alone in a block', async () => { const input = dedent` ![wrapped into figure](http://blabla.fr) one image` expect(await renderString(input)).not.toContain(' { const input = dedent`![](http://example.com)` expect(await renderString(input)).not.toContain(' { const input = dedent` one image ![not wrapped into figure because inline](http://blabla.fr) ` expect(await renderString(input)).not.toContain(' { const input = dedent` ![foo](http://example.com) ![](http://example.com) Figure: Caption ![foo](http://example.com) Figure: Caption ![](http://example.com) Figure: ` const result = await renderString(input) expect(result).not.toContain('

{ beforeEach(() => { defaultMdastConfig.ping.pingUsername = () => true }) it(`does not ping parts of email addresses`, () => { const input = 'My email address is test@test.com.' return expect(renderString(input)).resolves.not.toContain('ping') }) afterEach(() => { defaultMdastConfig.ping.pingUsername = () => false }) }) describe('oembed', () => { it.skip(`correctly render oEmbed iframe`, () => { const input = '!(https://soundcloud.com/paresh-sankhe/sets/h2g2)' return expect(renderString(input)).resolves.toContain(' { it(`translates >_<`, () => { const input = 'This is funny >_<' return expect(renderString(input)).resolves.toMatchSnapshot() }) it(`translates X/`, () => { const input = 'This is funny X/' return expect(renderString(input)).resolves.toMatchSnapshot() }) it(`translates cthulhu`, () => { const input = '^(;,;)^' return expect(renderString(input)).resolves.toMatchSnapshot() }) }) describe('pedantic mode disabled', () => { it(`unordered lists markers`, () => { const input = dedent` * a - b * c ` return expect(renderString(input)).resolves.toMatchSnapshot() }) it(`leading spaces in list item`, async () => { const three = dedent` * a ` expect(await renderString(three)).not.toContain('

')

    const four = dedent`
      *     a
    `
    expect(await renderString(four)).toContain('
')

    const five = dedent`
      *      a
    `
    expect(await renderString(five)).toContain('
')
  })

  it(`em`, () => {
    const input = dedent`
      no_em_here

      http://localhost/foo_bar_baz
    `

    return expect(renderString(input)).resolves.not.toContain('')
  })
})


describe('code highlight special cases', () => {
  beforeEach(() => {
    defaultHtmlConfig.disableTokenizers.internal = []
    defaultHtmlConfig.bridge.handlers = {
      code: require('../utils/code-handler'),
    }
    defaultHtmlConfig.postProcessors.codeHighlight = true
  })

  it('does not count one-liners', () => {
    const input = dedent`
      \`\`\`js
      const a = 1
      \`\`\`
    `

    expect(renderString(input)).resolves.toMatchSnapshot()
  })

  it('does not highlight console', () => {
    const input = dedent `
      \`\`\`console
      echo "Hello world"
      \`\`\`
    `
    expect(renderString(input)).resolves.toMatchSnapshot()
  })

  it('highlights latex', async () => {
    const input = [
      '```latex',
      '\\usepackage{inputenc}[utf8]',
      '\\begin{document}',
      '\\texttt{code}',
      '\\end{document}',
      '```',
    ]

    const result1 = await renderString(input.join('\n'))
    expect(result1).toMatchSnapshot()
  })

  it('highlights latex as tex', async () => {
    const input = [
      '```latex',
      '\\usepackage{inputenc}[utf8]',
      '\\begin{document}',
      '\\texttt{code}',
      '\\end{document}',
      '```',
    ]

    const result1 = (await renderString(input.join('\n')))
      .replace('language-latex', '')
      .replace('hljs-code-latex', '')

    input[0] = '```tex'
    const result2 = (await renderString(input.join('\n')))
      .replace('language-tex', '')
      .replace('hljs-code-tex', '')

    expect(result2).toBe(result1)
  })

  it('supports hl_lines - spaced syntax', async () => {
    const input = dedent `
      \`\`\`python hl_lines="4 5"
      def main():
        print('It\'s amazing!')
      
      def unused():
        print('Please use me!')
      \`\`\`
    `

    const result = await renderString(input)
    expect((result.match(/class="hll"/g) || []).length).toBe(2)
    expect(result).toMatchSnapshot()
  })

  it('supports hl_lines - comma syntax', async () => {
    const input = dedent `
      \`\`\`python hl_lines=4,5
      def main():
        print('It\'s amazing!')
      
      def unused():
        print('Please use me!')
      \`\`\`
    `

    const result = await renderString(input)
    expect((result.match(/class="hll"/g) || []).length).toBe(2)
    expect(result).toMatchSnapshot()
  })

  it('supports hl_lines - interval syntax', async () => {
    const input = dedent `
    \`\`\`python hl_lines=1-3
    def main():
      print('It\'s amazing!')
    
    def unused():
      print('Please use me!')
    \`\`\`
    `

    const result = await renderString(input)
    expect((result.match(/class="hll"/g) || []).length).toBe(3)
    expect(result).toMatchSnapshot()
  })

  it('supports hl_lines - interval syntax reversed', async () => {
    const input = dedent `
    \`\`\`python hl_lines=3-1
    def main():
      print('It\'s amazing!')
    
    def unused():
      print('Please use me!')
    \`\`\`
    `

    const result = await renderString(input)
    expect((result.match(/class="hll"/g) || []).length).toBe(3)
    expect(result).toMatchSnapshot()
  })

  it('supports linenostart', async () => {
    const input = dedent `
      \`\`\`python linenostart=3
      def main():
        print('It\'s amazing!')
      
      def unused():
        print('Please use me!')
      \`\`\`
    `

    const result = await renderString(input)
    expect(result).not.toContain('data-count="1"')
    expect(result).toMatchSnapshot()
  })

  it('supports both hl_lines and linenostart', () => {
    const input = dedent `
      \`\`\`python hl_lines=4 linenostart=3
      def main():
        print('It\'s amazing!')
      
      def unused():
        print('Please use me!')
      \`\`\`
    `

    expect(renderString(input)).resolves.toMatchSnapshot()
  })

  it('discards unused attributes', async () => {
    const input = dedent `
      \`\`\`python none hl_lines
      def main():
        print('It\'s amazing!')
      
      def unused():
        print('Please use me!')
      \`\`\`
    `

    const result = await renderString(input)
    expect(result).not.toContain('class="hll"')
    expect(result).toMatchSnapshot()
  })

  afterEach(() => {
    defaultHtmlConfig.disableTokenizers.internal = ['highlight']
    defaultHtmlConfig.bridge.handlers = {}
    defaultHtmlConfig.postProcessors.codeHighlight = false
  })
})

describe('Sanitize HTML to prevent XSS', () => {
  it('XSS test', () => {
    const input = dedent `
    [test XSS](javascript:alert(11))
    `
    // remove href
    return expect(renderString(input)).resolves.toContain('')
  })
  it('auto-link XSS', () => {
    const input = dedent `
    
    `
    // Not auto-link anymore
    return expect(renderString(input)).resolves.not.toContain('')
  })
  it('advanced XSS', () => {
    const input = dedent `
    This is [not obvious](   lives\0cript:promp('It works !')) !
    `
    return expect(renderString(input)).resolves.toMatchSnapshot()
  })
  it('Iframe XSS', () => {
    const input = dedent `
    !(javascript:alert("XSS"))
    `
    return expect(renderString(input)).resolves.not.toContain('iframe')
  })
})

describe('footnotes', () => {
  it('orders footnotes as their definitions', async () => {
    const input = dedent `
      a[^first_footnote_reference]
      b[^\`b\` second footnote reference but first footnote definition]
      c[^last_footnote_reference]

      [^last_footnote_reference]: \`c\` last footnote reference but second footnote definition
      [^first_footnote_reference]: \`a\` first footnote reference but last footnote definition
    `
    const html = await renderString(input)
    let [, olContent] = html.split('
    ') ;[olContent] = olContent.split('
') const footnotes = olContent .split('\n') .filter(Boolean) .map((line) => parseInt(line.match(/"fn-(\d+)/)[1])) expect(footnotes).toStrictEqual(Array.from(footnotes).sort()) }) }) ================================================ FILE: packages/zmarkdown/__tests__/latex-suite.test.js ================================================ /* eslint-disable max-len */ const dedent = require('dedent') const { defaultMdastConfig, renderAs, } = require('../utils/renderer-tests') const renderString = renderAs('latex') test('html nodes', () => { const p = renderString(dedent` # foo **something else** `) return expect(p).resolves.toMatchSnapshot() }) test('link with special characters', () => { const p = renderString(dedent` [foo](http://example.com?a=b%c^{}#foo) `) return expect(p).resolves.toMatchSnapshot() }) test('heading', () => { const p = renderString(dedent` # first level title ## second level title ### third level title #### fourth level title ##### fifth level title ###### sixth level title # a \ b # a } b # a } \ b # a \} b `) return expect(p).resolves.toMatchSnapshot() }) test('paragraph', () => { const p = renderString(dedent` # first 1 Disrupt asymmetrical unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue bottle craft beer hexagon artisan. Chia gochujang crucifix, readymade hammock blog succulents sriracha raw denim scenester cray typewriter fashion axe art party. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street art typewriter. ## second 1 Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street art synth. Godard synth truffaut selfies, vape fanny pack subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial YOLO. Williamsburg salvia photo booth readymade listicle man braid. Marfa pickled helvetica put a bird on it hot chicken williamsburg. Edison bulb asymmetrical keffiyeh schlitz iceland put a bird on it hoodie affogato. Tofu tote bag distillery viral knausgaard, health goth asymmetrical. Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. ### third 1 Art party keytar godard iceland neutra cronut. Austin readymade semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. ### third 2 #### fourth 1 Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. ##### fifth 1 Banjo bespoke subway tile, street art drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR&B, art party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. ### third 3 Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. #### fourth 2 Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. #### fourth 3 Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. ##### fifth 2 Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. # first 2 ## second 2 ##### fifth 3 Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh. `) return expect(p).resolves.toMatchSnapshot() }) test('inline-code', () => { const p = renderString(dedent` a paragraph \`with inline code\`. \`a multiline inlinecode\` \`a code with \ \` \`a \text in \LaTeX \\\` `) return expect(p).resolves.toMatchSnapshot() }) test('emoticon', () => { const p = renderString(`foo :p bar :)`) return expect(p).resolves.toMatchSnapshot() }) test('table', () => { const p = renderString(dedent` 1 | 2 --|-- 1|2 1|2 1|2 1 | 2 | 3 --|---|-- 1 | 2 1 | 2 | 3 1 | 2 | 3 `) return expect(p).resolves.toMatchSnapshot() }) test('blockquote', () => { const p = renderString(dedent` > a quote > a > multiline > quote --- > a quote --- > a > multiline > quote `) return expect(p).resolves.toMatchSnapshot() }) test('figure+caption', () => { const p = renderString(dedent` > a > multiline > quote Source: With Source `) return expect(p).resolves.toMatchSnapshot() }) test('code', () => { const p = renderString(dedent` \`\`\`python print('bla') \`\`\` \`\`\`python hl_lines=1,2 print('bla') print('bla') print('bla') \`\`\` \`\`\`python linenostart=10 print('bla') print('bla') print('bla') \`\`\` \`\`\`python hl_lines=1,2 linenostart=10 print('bla') print('bla') print('bla') \`\`\` \`\`\`css hl_lines="2" .tmp { font-weight: bold; } \`\`\` \`\`\`css hl_lines='1,3' .tmp { font-weight: bold; } \`\`\` \`\`\` a code without lang \`\`\` `) return expect(p).resolves.toMatchSnapshot() }) test('code+reversed-hl', () => { const p = renderString(dedent` \`\`\`python hl_lines=3-2 print('bla') print('bla') print('bla') \`\`\` \`\`\`python hl_lines=2-1,4-3 print('bla') print('bla') print('bla') print('bla') \`\`\` `) return expect(p).resolves.toMatchSnapshot() && expect(p).not.toContain('3-2') && expect(p).not.toContain('2-1') && expect(p).not.toContain('4-3') }) test('code+caption', () => { const p = renderString(dedent` \`\`\`python print('bla') \`\`\` Code: With Source \`\`\`python hl_lines=1,2 print('bla') print('bla') print('bla') \`\`\` Code: With Source `) return expect(p).resolves.toMatchSnapshot() }) test('list', () => { const p = renderString(dedent` - an - **unordered** - list 1. an 1. \`ordered\` 1. list `) return expect(p).resolves.toMatchSnapshot() }) test('link', () => { const p = renderString(dedent` [an external link](https://wikipedia.com) [an internal link](/forums) [an \`external\` link](https://wikipedia.com) `) return expect(p).resolves.toMatchSnapshot() }) test('mix-1', () => { const p = renderString(dedent` (for convenience, · are replaced with simple single spaces in the tests) # first 1 foo·· bar Disrupt ~~asymmetrical~~ unicorn, pok pok kale chips umami selfies gastropub food truck flannel. Blue **bottle craft** beer hexagon artisan. Chia gochujang crucifix, ^ready^ ^made^ hammock _blog succulents_ sriracha raw denim scenester cray typewriter fashion axe ~art~ *party*. Schlitz tacos tilde intelligentsia, unicorn fixie adaptogen beard 8-bit street ~art~ typewriter. ## *second* 1 **Literally selfies tbh lo-fi. Actually health goth ennui, brooklyn retro polaroid sriracha. Kogi live-edge mixtape marfa street ~art~ synth. Godard synth truffaut selfies, vape fanny ~~pack~~ subway tile. Stumptown af pabst, try-hard fam ethical actually four dollar toast. Microdosing kogi brooklyn, locavore jianbing etsy sartorial _YOLO_. Williamsburg salvia photo booth ^readymade^ listicle man braid.** --- > Marfa pickled helvetica put a bird on it hot chicken williamsburg. > Edison bulb asymmetrical \`keffiyeh\` schlitz iceland put a bird on it hoodie affogato. > > Tofu tote bag distillery viral knausgaard, health goth asymmetrical. Source: Guru Asymmetrical pug scenester sustainable enamel pin distillery quinoa keffiyeh. Flannel humblebrag PBR&B polaroid banh mi wolf. Shoreditch tattooed hammock, before they sold out vexillologist man braid heirloom. Activated charcoal craft beer cloud bread meh biodiesel pabst. ### ~~third~~ 1 Art party keytar godard iceland neutra cronut. Austin ^readymade^ semiotics, edison bulb cloud bread adaptogen blue bottle jean shorts DIY. Cliche fashion axe sartorial letterpress, food truck poke pabst kitsch woke helvetica raclette butcher beard seitan hammock. \`foo bar\` > \`fo\o > bar\` Hexagon lo-fi seitan you probably haven't heard of them, bicycle rights small batch meditation try-hard green juice banh mi keffiyeh. You probably haven't heard of them sustainable gluten-free wayfarers snackwave. ### third 2 #### ~~fo~~u**r**t*h* _1_ Mumblecore kombucha offal, health goth next level before they sold out gluten-free chicharrones keffiyeh. Mumblecore kickstarter hoodie fixie keffiyeh. Microdosing lo-fi taiyaki irony pok pok. Banjo brooklyn umami succulents flexitarian. Swag sartorial scenester synth viral. ##### fifth 1 Banjo bespoke subway tile, street ~a*r*~^t^ drinking vinegar offal franzen. Pour-over green juice vaporware kale chips. Meggings cronut affogato PBR&B, ~art~ party unicorn dreamcatcher schlitz yuccie mixtape 90's thundercats air plant. Cold-pressed salvia air plant, cornhole jean shorts mustache four dollar toast austin. ### third 3 Meditation heirloom chicharrones, sartorial man braid hot chicken sustainable. Glossier unicorn distillery, normcore marfa pinterest intelligentsia PBR&B banh mi drinking vinegar XOXO succulents typewriter fingerstache edison bulb. Meditation ethical cronut glossier cliche kickstarter. #### fourth 2 Venmo bushwick food truck chartreuse fam. Everyday carry gastropub glossier, cold-pressed salvia migas keytar. Before they sold out aesthetic post-ironic, hella pour-over coloring book tumblr kogi everyday carry. Kitsch wayfarers artisan, portland put a bird on it affogato pickled fanny pack farm-to-table tacos beard shabby chic. #### fourth 3 Chambray intelligentsia vape listicle. Pok pok snackwave cronut retro hot chicken, trust fund master cleanse keytar forage dreamcatcher. Taiyaki actually deep v, godard small batch irony lumbersexual unicorn cardigan af. Irony lumbersexual salvia, pitchfork fam snackwave man bun. Kitsch jianbing intelligentsia freegan, waistcoat raw denim cloud bread vice cray etsy listicle skateboard jean shorts. ##### fifth 2 Asymmetrical normcore portland, vaporware viral tote bag DIY slow-carb kogi fanny pack. Keffiyeh ennui church-key, irony VHS man bun edison bulb listicle cloud bread try-hard cred lumbersexual lo-fi glossier. # first 2 ## second 2 ##### fifth 3 Messenger bag locavore swag raclette brunch whatever, portland food truck. PBR&B cred mlkshk, poke letterpress waistcoat celiac. La croix letterpress forage keffiyeh. `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('mix-2', () => { const p = renderString(dedent` (for convenience, · are replaced with simple single spaces in the tests) # first 1 > Code inside quote > \`\`\`python > print('bla') > \`\`\` > Code: code source Source: Quotation Source \`\`\`python print('bla') \`\`\` Code: First ||CTRL||+||S|| Code: Second \`\`\`python print('code without caption') \`\`\` `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('mix-3', () => { const p = renderString(dedent` # right-aligned list -> - item - item -> # centered list -> - item - item - item <- # left list <- - item - item - item <- `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('mix-4', () => { const p = renderString(dedent` +-------+----------+------+ | Sub | Headings | ABBR | +=======+==========+======+ | cell | column spanning | | spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ Table: The new table ABBR [^foot] with ||CTRL|| + ||S|| *[ABBR]: abbreviation [^foot]: a foot +-----------+---------------------------------------------------------------+ | title | image | +===========+===============================================================+ | space | ![space](https://i.ytimg.com/vi/lt0WQ8JzLz4/maxresdefault.jpg)| +-----------+---------------------------------------------------------------+ +-----------+--------------------+ | title | code | +===========+====================+ | inline | \`inline\` br | | | \`inline\` | +-----------+--------------------+ | block | \`\`\`python | | | print('bla') | | | \`\`\` | +-----------+--------------------+ `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('mix-5', () => { const p = renderString(dedent` ![](http://www.numerama.com/content/uploads/2016/07/espace.jpg) Figure: espace[^node] [^node]: Two things are infinite: the universe and human stupidity. `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('mix-6', () => { const p = renderString(dedent` !(https://www.youtube.com/watch?v=8TQIvdFl4aU) Video: a caption !(https://www.youtube.com/watch?v=8TQIvdFl4aU) no caption `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('footnotes', () => { const p = renderString(dedent` # mytitle A[^footnoteRef] [^footnoteRef]: reference in title # mytitle B^[footnoterawhead inner] # myti*tle C^[foo inner]* a paragraph^[footnoteRawPar inner] `) return expect(p).resolves.toMatchSnapshot() }) test('math', () => { const p = renderString(dedent` A sentence ($S$) with *italic* and inline math ($C_L$) and $$b$$ another. $$ L = \frac{1}{2} \rho v^2 S C_L $$ This should be escaped: $\pink{\text{I love \LaTeX} \immediate\write18{ls / > outputrce} \input{outputrce}}$. hehe `) return expect(p).resolves.toMatchSnapshot() }) test('ping', () => { defaultMdastConfig.ping.pingUsername = () => true const p = renderString(dedent` Hello @you and @you_too, and @**also you** `) return expect(p).resolves.toMatchSnapshot() }) test('custom-blocks', () => { defaultMdastConfig.ping.pingUsername = () => false const p = renderString(dedent` [[i]] | info [[a]] | warning [[s]] | secret [[s]] | imbricated spoilers | [[s]] | | and another | | [[s]] | | | and that's it [[s]] | imbricated spoilers | [[s]] | | second level | [[s]] | | second level too [[s]] | imbricated spoilers | [[s]] | | second level | | with text in between | | [[s]] | | second level too [[s]] | imbricated spoilers | | \`\`\`markdown | and here is some code | \`\`\` | | [[s]] | | second level [[s]] | do not over-flattenize | [[s]] | | expected to be flattened | | first level | | [[q]] | | this remains a question [[s]] | flattenize children of children | [[q]] | | this remains a question | | [[s]] | | | but the content in it is flattened to the question [[q]] | question \`coded\` [[e]] | **error** | foo bar·· | baz `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('regression: code block without language', () => { const p = renderString(dedent` \`\`\` a \`\`\` `) return expect(p).resolves.toMatchSnapshot() }) test('properly loads extensions - mhchem', () => { const markdown = '$\\ce{H2O}$' const result = renderString(markdown) return expect(result).resolves.toContain(markdown) }) test('codes in notes', () => { const p = renderString(dedent` hello [^note][^note-caption] [^note]: test \`\`\`javascript console.log('hello') \`\`\` [^note-caption]: test \`\`\`javascript console.log('hello') \`\`\` Code: with a legend `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) test('quizz with answers', () => { const p = renderString(dedent` [[quizz | question]] | - [ ] bad answer | - [x] good answer | - [ ] other bad answer | | the good answer is "good answer" as it better matches the philosophy | of what good is for answers `.replace(/·/g, ' ')) return expect(p).resolves.toMatchSnapshot() }) ================================================ FILE: packages/zmarkdown/__tests__/legacy-suite.test.js ================================================ /* eslint-disable no-tabs */ /* eslint-disable max-len */ const { defaultMdastConfig, defaultHtmlConfig, defaultLatexConfig, configOverride, renderAs, } = require('../utils/renderer-tests') const renderHtml = renderAs('html') const renderLatex = renderAs('latex') describe('heading-shift', () => { it(`shifts in range`, async () => { const input = '### should be h4' const newMdastConfig = configOverride(defaultMdastConfig, {headingShifter: 1}) const html = await renderHtml(newMdastConfig, defaultHtmlConfig)(input) const latex = await renderLatex(newMdastConfig, defaultLatexConfig)(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`shifts past range`, async () => { const input = '### should be h6' const newMdastConfig = configOverride(defaultMdastConfig, {headingShifter: 10}) const html = await renderHtml(newMdastConfig, defaultHtmlConfig)(input) const latex = await renderLatex(newMdastConfig, defaultLatexConfig)(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`shifts before range`, async () => { const input = '### should be h1' const newMdastConfig = configOverride(defaultMdastConfig, {headingShifter: -10}) const html = await renderHtml(newMdastConfig, defaultHtmlConfig)(input) const latex = await renderLatex(newMdastConfig, defaultLatexConfig)(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) describe('basic', () => { it(`renders amps-and-angle-encoding.txt`, async () => { const input = `AT&T has an ampersand in their name. AT&T is another way to write it. This & that. 4 < 5. 6 > 5. Here's a [link] [1] with an ampersand in the URL. Here's a link with an amersand in the link text: [AT&T] [2]. Here's an inline [link](/script?foo=1&bar=2). Here's an inline [link](). [1]: http://example.com/?foo=1&bar=2 [2]: http://att.com/ "AT&T"` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders angle-links-and-img.txt`, async () => { const input = `[link]( "my title") ![image]() [link]() ![image]() ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders auto-links.txt`, async () => { const input = `Link: . Https link: Ftp link: With an ampersand: * In a list? * * It should. > Blockquoted: Auto-links should not occur here: \`\` or here: ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders backlash-escapes.txt`, async () => { const input = `These should all get escaped: Backslash: \\\\ Backtick: \\\` Asterisk: \\* Underscore: \\_ Left brace: \\{ Right brace: \\} Left bracket: \\[ Right bracket: \\] Left paren: \\( Right paren: \\) Greater-than: \\> Hash: \\# Period: \\. Bang: \\! Plus: \\+ Minus: \\- These should not, because they occur within a code block: Backslash: \\\\ Backtick: \\\` Asterisk: \\* Underscore: \\_ Left brace: \\{ Right brace: \\} Left bracket: \\[ Right bracket: \\] Left paren: \\( Right paren: \\) Greater-than: \\> Hash: \\# Period: \\. Bang: \\! Plus: \\+ Minus: \\- Nor should these, which occur in code spans: Backslash: \`\\\\\` Backtick: \`\` \\\` \`\` Asterisk: \`\\*\` Underscore: \`\\_\` Left brace: \`\\{\` Right brace: \`\\}\` Left bracket: \`\\[\` Right bracket: \`\\]\` Left paren: \`\\(\` Right paren: \`\\)\` Greater-than: \`\\>\` Hash: \`\\#\` Period: \`\\.\` Bang: \`\\!\` Plus: \`\\+\` Minus: \`\\-\` ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders blockquotes-with-code-blocks.txt`, async () => { const input = `> Example: > > sub status { > print "working"; > } > > Or: > > sub status { > return "working"; > } ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders codeblock-in-list.txt`, async () => { const input = `* A list item with a code block Some *code* * Another list item More code And more code ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders hard-wrapped.txt`, async () => { const input = `In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the middle of a paragraph looked like a list item. Here's one with a bullet. * criminey. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders horizontal-rules.txt`, async () => { const input = `Dashes: --- --- --- --- --- - - - - - - - - - - - - - - - Asterisks: *** *** *** *** *** * * * * * * * * * * * * * * * Underscores: ___ ___ ___ ___ ___ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders inline-html-advanced.txt`, async () => { const input = `Simple block on one line:
foo
And nested without indentation:
foo
bar
` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders inline-html-comments.txt`, async () => { const input = `Paragraph one. Paragraph two. The end. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders inline-html-simple.txt`, async () => { const input = `Here's a simple block:
foo
This should be a code block, though:
foo
As should this:
foo
Now, nested:
foo
This should just be an HTML comment: Multiline: Code block: Just plain comment, with trailing spaces on the line: Code:
Hr's:








` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders links-inline.txt`, async () => { const input = `Just a [URL](/url/). [URL and title](/url/ "title"). [URL and title](/url/ "title preceded by two spaces"). [URL and title](/url/ "title preceded by a tab"). [Empty](). ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders links-reference.txt`, async () => { const input = `Foo [bar] [1]. Foo [bar][1]. Foo [bar] [1]. [1]: /url/ "Title" With [embedded [brackets]] [b]. Indented [once][]. Indented [twice][]. Indented [thrice][]. Indented [four][] times. [once]: /url [twice]: /url [thrice]: /url [four]: /url [b]: /url/ With [angle brackets][]. And [without][]. [angle brackets]: "Angle Brackets" [without]: http://example.com/ "Without angle brackets." With [line breaks][] and [line breaks][] with one space. and [line breaks[] with two spaces. [line breaks]: http://example.com "Yes this works" [short ref] [short ref] [short ref]: http://example.com "No more hanging empty bracket!" [a ref] [a ref]: http://example.com "Title on next line." ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders nested-blockquotes.txt`, async () => { const input = `> foo > > > bar > > foo ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders ordered-and-unordered-list.txt`, async () => { const input = `Unordered Asterisks tight: * asterisk 1 * asterisk 2 * asterisk 3 Asterisks loose: * asterisk 1 * asterisk 2 * asterisk 3 * * * Pluses tight: + Plus 1 + Plus 2 + Plus 3 Pluses loose: + Plus 1 + Plus 2 + Plus 3 * * * Minuses tight: - Minus 1 - Minus 2 - Minus 3 Minuses loose: - Minus 1 - Minus 2 - Minus 3 --- Ordered Tight: 1. First 2. Second 3. Third and: 1. One 2. Two 3. Three Loose using tabs: 1. First 2. Second 3. Third and using spaces: 1. One 2. Two 3. Three Multiple paragraphs: 1. Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. 2. Item 2. 3. Item 3. --- Nested * Tab * Tab * Tab Here's another: 1. First 2. Second: * Fee * Fie * Foe 3. Third Same thing but with paragraphs: 1. First 2. Second: * Fee * Fie * Foe 3. Third ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders strong-and-em-together.txt`, async () => { const input = `***This is strong and em.*** So is ***this*** word. ___This is strong and em.___ So is ___this___ word. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders tabs.txt`, async () => { const input = `+ this is a list item indented with tabs + this is a list item indented with spaces Code: this code block is indented by one tab And: this code block is indented by two tabs And: + this is an example list item indented with tabs + this is an example list item indented with spaces ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders tidyness.txt`, async () => { const input = `> A list within a blockquote: > > * asterisk 1 > * asterisk 2 > * asterisk 3 ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) describe('extensions', () => { it(`renders abbr.txt`, async () => { const input = `An ABBR: "REF". ref and REFERENCE should be ignored. *[REF]: Reference *[ABBR]: This gets overriden by the next one. *[ABBR]: Abbreviation The HTML specification is maintained by the W3C. *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders footnote.txt`, async () => { const input = `This is the body with a footnote[^1] or two[^2] or more[^3] [^4] [^5]. Also a reference that does not exist[^6]. [^1]: Footnote that ends with a list: * item 1 * item 2 [^2]: > This footnote is a blockquote. [^3]: A simple oneliner. [^4]: A footnote with multiple paragraphs. Paragraph two. [^5]: First line of first paragraph. Second line of first paragraph is not intended. Nor is third... ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders footnote_many_footnotes.txt`, async () => { const input = `Something[^1] Something[^2] Something[^3] Something[^4] Something[^5] Something[^6] Something[^7] Something[^8] Something[^9] Something[^10] Something[^11] Something[^12] Something[^13] Something[^14] Something[^15] Something[^16] Something[^17] Something[^18] Something[^19] Something[^20] Something[^21] Something[^22] Something[^23] Something[^24] Something[^25] Something[^26] Something[^27] Something[^28] Something[^29] Something[^30] Something[^31] Something[^32] Something[^33] Something[^34] Something[^35] Something[^36] Something[^37] Something[^38] Something[^39] Something[^40] Something[^41] Something[^42] Something[^43] Something[^44] Something[^45] Something[^46] Something[^47] Something[^48] Something[^49] Something[^50] Something[^51] Something[^52] Something[^53] Something[^54] Something[^55] Something[^56] Something[^57] Something[^58] Something[^59] Something[^60] Something[^61] Something[^62] Something[^63] Something[^64] Something[^65] Something[^66] Something[^67] Something[^68] Something[^69] Something[^70] Something[^71] Something[^72] Something[^73] Something[^74] Something[^75] Something[^76] Something[^77] Something[^78] Something[^79] Something[^80] Something[^81] Something[^82] Something[^83] Something[^84] Something[^85] Something[^86] Something[^87] Something[^88] Something[^89] Something[^90] Something[^91] Something[^92] Something[^93] Something[^94] Something[^95] Something[^96] Something[^97] Something[^98] Something[^99] Something[^100] Something[^101] Something[^102] Something[^103] Something[^104] Something[^105] Something[^106] Something[^107] Something[^108] Something[^109] Something[^110] Something[^111] Something[^112] Something[^113] Something[^114] Something[^115] Something[^116] Something[^117] Something[^118] Something[^119] Something[^120] Something[^121] Something[^122] Something[^123] Something[^124] Something[^125] Something[^126] Something[^127] Something[^128] Something[^129] Something[^130] Something[^131] Something[^132] Something[^133] Something[^134] Something[^135] Something[^136] Something[^137] Something[^138] Something[^139] Something[^140] Something[^141] Something[^142] Something[^143] Something[^144] Something[^145] Something[^146] Something[^147] Something[^148] Something[^149] Something[^150] [^1]: Another thing [^2]: Another thing [^3]: Another thing [^4]: Another thing [^5]: Another thing [^6]: Another thing [^7]: Another thing [^8]: Another thing [^9]: Another thing [^10]: Another thing [^11]: Another thing [^12]: Another thing [^13]: Another thing [^14]: Another thing [^15]: Another thing [^16]: Another thing [^17]: Another thing [^18]: Another thing [^19]: Another thing [^20]: Another thing [^21]: Another thing [^22]: Another thing [^23]: Another thing [^24]: Another thing [^25]: Another thing [^26]: Another thing [^27]: Another thing [^28]: Another thing [^29]: Another thing [^30]: Another thing [^31]: Another thing [^32]: Another thing [^33]: Another thing [^34]: Another thing [^35]: Another thing [^36]: Another thing [^37]: Another thing [^38]: Another thing [^39]: Another thing [^40]: Another thing [^41]: Another thing [^42]: Another thing [^43]: Another thing [^44]: Another thing [^45]: Another thing [^46]: Another thing [^47]: Another thing [^48]: Another thing [^49]: Another thing [^50]: Another thing [^51]: Another thing [^52]: Another thing [^53]: Another thing [^54]: Another thing [^55]: Another thing [^56]: Another thing [^57]: Another thing [^58]: Another thing [^59]: Another thing [^60]: Another thing [^61]: Another thing [^62]: Another thing [^63]: Another thing [^64]: Another thing [^65]: Another thing [^66]: Another thing [^67]: Another thing [^68]: Another thing [^69]: Another thing [^70]: Another thing [^71]: Another thing [^72]: Another thing [^73]: Another thing [^74]: Another thing [^75]: Another thing [^76]: Another thing [^77]: Another thing [^78]: Another thing [^79]: Another thing [^80]: Another thing [^81]: Another thing [^82]: Another thing [^83]: Another thing [^84]: Another thing [^85]: Another thing [^86]: Another thing [^87]: Another thing [^88]: Another thing [^89]: Another thing [^90]: Another thing [^91]: Another thing [^92]: Another thing [^93]: Another thing [^94]: Another thing [^95]: Another thing [^96]: Another thing [^97]: Another thing [^98]: Another thing [^99]: Another thing [^100]: Another thing [^101]: Another thing [^102]: Another thing [^103]: Another thing [^104]: Another thing [^105]: Another thing [^106]: Another thing [^107]: Another thing [^108]: Another thing [^109]: Another thing [^110]: Another thing [^111]: Another thing [^112]: Another thing [^113]: Another thing [^114]: Another thing [^115]: Another thing [^116]: Another thing [^117]: Another thing [^118]: Another thing [^119]: Another thing [^120]: Another thing [^121]: Another thing [^122]: Another thing [^123]: Another thing [^124]: Another thing [^125]: Another thing [^126]: Another thing [^127]: Another thing [^128]: Another thing [^129]: Another thing [^130]: Another thing [^131]: Another thing [^132]: Another thing [^133]: Another thing [^134]: Another thing [^135]: Another thing [^136]: Another thing [^137]: Another thing [^138]: Another thing [^139]: Another thing [^140]: Another thing [^141]: Another thing [^142]: Another thing [^143]: Another thing [^144]: Another thing [^145]: Another thing [^146]: Another thing [^147]: Another thing [^148]: Another thing [^149]: Another thing [^150]: Another thing ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders tables-2.txt`, async () => { const input = `foo|bar|baz ---|---|--- | Q | W | | W ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders tables.txt`, async () => { const input = `First Header | Second Header ------------- | ------------- Content Cell | Content Cell Content Cell | Content Cell | First Header | Second Header | | ------------- | ------------- | | Content Cell | Content Cell | | Content Cell | Content Cell | | Item | Value | | :-------- | -----:| | Computer | $1600 | | Phone | $12 | | Pipe | $1 | | Function name | Description | | ------------- | ------------------------------ | | \`help()\` | Display the help window. | | \`destroy()\` | **Destroy your computer!** | |foo|bar|baz| |:--|:-:|--:| | | Q | | |W | | W| Three spaces in front of a table: First Header | Second Header ------------ | ------------- Content Cell | Content Cell Content Cell | Content Cell | First Header | Second Header | | ------------ | ------------- | | Content Cell | Content Cell | | Content Cell | Content Cell | Four spaces is a code block: First Header | Second Header ------------ | ------------- Content Cell | Content Cell Content Cell | Content Cell ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders fenced_code.txt`, async () => { const input = `index 0000000..6e956a9 \`\`\` --- /dev/null +++ b/test/data/stripped_text/mike-30-lili @@ -0,0 +1,27 @@ +Summary: + drift_mod.py | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +commit da4bfb04debdd994683740878d09988b2641513d +Author: Mike Dirolf +Date: Tue Jan 17 13:42:28 2012 -0500 + +\`\`\` +minor: just wanted to push something. +\`\`\` + +diff --git a/drift_mod.py b/drift_mod.py +index 34dfba6..8a88a69 100644 + +\`\`\` +--- a/drift_mod.py ++++ b/drift_mod.py +@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^(' + '|\\+ .*' + '|- .*' + ')$') ++ + def wrap_context_diffs(message_text): + return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN, + CONTEXT_DIFF_LINE_PATTERN, +\`\`\` \`\`\` ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders github_flavored.txt`, async () => { const input = `index 0000000..6e956a9 \`\`\`diff --- /dev/null +++ b/test/data/stripped_text/mike-30-lili @@ -0,0 +1,27 @@ +Summary: + drift_mod.py | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +commit da4bfb04debdd994683740878d09988b2641513d +Author: Mike Dirolf +Date: Tue Jan 17 13:42:28 2012 -0500 + +\`\`\` +minor: just wanted to push something. +\`\`\` + +diff --git a/drift_mod.py b/drift_mod.py +index 34dfba6..8a88a69 100644 + +\`\`\` +--- a/drift_mod.py ++++ b/drift_mod.py +@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^(' + '|\\+ .*' + '|- .*' + ')$') ++ + def wrap_context_diffs(message_text): + return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN, + CONTEXT_DIFF_LINE_PATTERN, +\`\`\` \`\`\` Test support for foo+bar lexer names. \`\`\`html+jinja {% block title %}{% endblock %}
\`\`\` Test support for foo+bar lexer names in citation. > \`\`\`html+jinja > {% block title %}{% endblock %} > > \`\`\` Test support for foo+bar lexer names with hightlight. \`\`\`html+jinja hl_lines="2-4" {% block title %}{% endblock %} \`\`\` Test support for foo+bar lexer names with linenostart. \`\`\`html+jinja linenostart=10 {% block title %}{% endblock %} \`\`\` Test support for foo+bar lexer names with both. \`\`\`html+jinja hl_lines="2-4" linenostart=10 {% block title %}{% endblock %} \`\`\` Code without matching end ~~~~html ~~~ Code into paragraph \`\`\`html+jinja hl_lines= "2-4" linenostart=10 {% block title %}{% endblock %} \`\`\` with end ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) describe('misc', () => { it(`renders CRLF_line_ends.txt`, async () => { const input = `foo
bar
` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders adjacent-headers.txt`, async () => { const input = `# this is a huge header # ## this is a smaller header ## ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders amp-in-url.txt`, async () => { const input = `[link](http://www.freewisdom.org/this&that) ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders ampersand.txt`, async () => { const input = `& AT&T ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders arabic.txt`, async () => { const input = ` بايثون ===== **بايثون** لغة برمجة حديثة بسيطة، واضحة، سريعة ، تستخدم أسلوب البرمجة الكائنية (OOP) وقابلة للتطوير بالإضافة إلى أنها مجانية و مفتوحة المصدر. صُنفت بالأساس كلغة تفسيرية ، بايثون مصممة أصلاً للأداء بعض المهام الخاصة أو المحدودة. إلا أنه يمكن استخدامها بايثون لإنجاز المشاريع الضخمه كأي لغة برمجية أخرى، غالباً ما يُنصح المبتدئين في ميدان البرمجة بتعلم هذه اللغة لأنها من بين أسهل اللغات البرمجية تعلماً. نشأت بايثون في مركز CWI (مركز العلوم والحاسب الآلي) بأمستردام على يد جويدو فان رُزوم. تم تطويرها بلغة C. أطلق فان رُزوم اسم "بايثون" على لغته تعبيرًا عن إعجابه بفِرقَة مسرحية هزلية شهيرة من بريطانيا، كانت تطلق على نفسها اسم مونتي بايثون Monty Python. تتميز بايثون بمجتمعها النشط ، كما أن لها الكثير من المكتبات البرمجية ذات الأغراض الخاصة والتي برمجها أشخاص من مجتمع هذه اللغة ، مثلاً مكتبة PyGame التي توفر مجموعه من الوظائف من اجل برمجة الالعاب. ويمكن لبايثون التعامل مع العديد من أنواع قواعد البيانات مثل MySQL وغيره. ## أمثلة مثال Hello World! print "Hello World!" مثال لاستخراج المضروب Factorial : num = 1 x = raw_input('Insert the number please ') x = int(x) if x > 69: print 'Math Error !' else: while x > 1: num *= x x = x-1 print num ## وصلات خارجية * [الموقع الرسمي للغة بايثون](http://www.python.org) بذرة حاس ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders autolinks_with_asterisks.txt`, () => { const input = ` ` return expect(renderHtml(input)).resolves.toMatchSnapshot() }) it(`renders backtick-escape.txt`, async () => { const input = `\\\`This also should not be in code.\\\` \\\\\`This should be in code.\\\\\` \\\`And finally this should not be in code.\` ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders blank-block-quote.txt`, async () => { const input = ` aaaaaaaaaaa > bbbbbbbbbbb ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders blank_lines_in_codeblocks.txt`, async () => { const input = `Preserve blank lines in code blocks with tabs: a code block two tabbed lines three tabbed lines four tabbed lines five tabbed lines six tabbed lines End of tabbed block And without tabs: a code block two blank lines three blank lines four blank lines five blank lines six blank lines End of block End of document` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders block_html5.txt`, async () => { const input = `

Hello :-)

Caption

Some footer

` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders block_html_attr.txt`, async () => { const input = `
Raw HTML processing should not confuse this with the blockquote below

Header2

Header3

Paragraph

Header3

Paragraph

Paragraph

Paragraph

linktext

` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders block_html_simple.txt`, async () => { const input = `

foo

  • bar

  • baz

` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders blockquote-below-paragraph.txt`, async () => { const input = `Paragraph > Block quote > Yep Paragraph >no space >Nope Paragraph one > blockquote More blockquote. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders blockquote-hr.txt`, () => { const input = `This is a paragraph. --- > Block quote with horizontal lines. > > --- > > > Double block quote. > > > > --- > > > > End of the double block quote. > A new paragraph. > With multiple lines. Even a lazy line. > > --- > > The last line. ` return expect(renderHtml(input)).resolves.toMatchSnapshot() }) it(`renders blockquote.txt`, async () => { const input = `> blockquote with no whitespace before \`>\`. foo > blockquote with one space before the \`>\`. bar > blockquote with 2 spaces. baz > this has three spaces so its a paragraph. blah > this one had four so it's a code block. > > this nested blockquote has 0 on level one and 3 (one after the first \`>\` + 2 more) on level 2. > > and this has 4 on level 2 - another code block. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders bold_links.txt`, async () => { const input = `**bold [link](http://example.com)** ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders br.txt`, async () => { const input = `Output:

Some of these words are emphasized. Some of these words are emphasized also.

Use two asterisks for strong emphasis. Or, if you prefer, use two underscores instead.

Unordered (bulleted) lists use asterisks, pluses, and hyphens (\`*\`, \`+\`, and \`-\`) as list markers. These three markers are interchangable; this: ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders bracket_re.txt`, async () => { const input = ` [x xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders brackets-in-img-title.txt`, () => { const input = `![alt](local-img.jpg) ![alt](local-img.jpg "") ![alt](local-img.jpg "normal title") ![alt](local-img.jpg "(just title in brackets)") ![alt](local-img.jpg "title with brackets (I think)") ![alt](local-img.jpg "(") ![alt](local-img.jpg "(open only") ![alt](local-img.jpg ")") ![alt](local-img.jpg "close only)") ` return expect(renderHtml(input)).resolves.toMatchSnapshot() }) it(`renders code-first-line.txt`, async () => { const input = ` print "This is a code block." ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders comments.txt`, async () => { const input = `X<0 X>0
as if
__no blank line__ ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders div.txt`, async () => { const input = ` And now in uppercase:
foo
` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders em-around-links.txt`, async () => { const input = ` - *[Python in Markdown](https://pythonhosted.org/Markdown/) by some great folks* - This *does* work as expected. - _[Python in Markdown](https://pythonhosted.org/Markdown/) by some great folks_ - This *does* work as expected. - [_Python in Markdown_](https://pythonhosted.org/Markdown/) by some great folks - This *does* work as expected. - [_Python in Markdown_](https://pythonhosted.org/Markdown/) _by some great folks_ - This *does* work as expected. _[Python in Markdown](https://pythonhosted.org/Markdown/) by some great folks_ - This *does* work as expected. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders em_strong.txt`, async () => { const input = `One asterisk: * One underscore: _ Two asterisks: ** With spaces: * * Two underscores __ with spaces: _ _ three asterisks: *** with spaces: * * * three underscores: ___ with spaces: _ _ _ One char: _a_ ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders email.txt`, async () => { const input = ` asdfasdfadsfasd or you can say instead ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders escaped_chars_in_js.txt`, async () => { const input = `[javascript protected email address] ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders h1.txt`, async () => { const input = `Header ------ Header 2 ======== ### H3 H1 = H2 -- ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders image-2.txt`, async () => { const input = `[*link!*](http://src.com/) *[link](http://www.freewisdom.org)* ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders image.txt`, async () => { const input = ` ![Poster](http://humane_man.jpg "The most humane man.") ![Poster][] [Poster]:http://humane_man.jpg "The most humane man." ![Blank]()` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders image_in_links.txt`, async () => { const input = ` [![altname](path/to/img_thumb.png)](path/to/image.png) ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders ins-at-start-of-paragraph.txt`, async () => { const input = `Hello, fellow developer this ins should be wrapped in a p. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders inside_html.txt`, async () => { const input = ` __ok__? ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders lazy-block-quote.txt`, async () => { const input = `> Line one of lazy block quote. Line two of lazy block quote. > Line one of paragraph two. Line two of paragraph two. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders link-with-parenthesis.txt`, async () => { const input = `[ZIP archives](http://en.wikipedia.org/wiki/ZIP_(file_format) "ZIP (file format) - Wikipedia, the free encyclopedia") ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders lists.txt`, async () => { const input = ` * A multi-paragraph list, unindented. Simple tight list * Uno * Due * Tri A singleton tight list: * Uno A lose list: * One * Two * Three A lose list with paragraphs * One one one one one one one one * Two two two two ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders lists2.txt`, async () => { const input = `* blah blah blah sdf asdf asdf asdf asdf asda asdf asdfasd ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders lists3.txt`, async () => { const input = `* blah blah blah sdf asdf asdf asdf asdf asda asdf asdfasd ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders lists4.txt`, async () => { const input = ` * item1 * item2 1. Number 1 2. Number 2 ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders lists5.txt`, async () => { const input = `> This is a test of a block quote > With just two lines A paragraph > This is a more difficult case > With a list item inside the quote > > * Alpha > * Beta > Etc. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders lists6.txt`, async () => { const input = `Test five or more spaces as start of list: * five spaces not first item: * one space * five spaces loose list: * one space * five spaces ` return expect(renderHtml(input)).resolves.toMatchSnapshot() }) it(`renders lists8.txt`, async () => { const input = `1. > Four-score and seven years ago... 2. > We have nothing to fear... 3. > This is it... --- * > Four-score and sever years ago > our fathers brought forth * > We have nothing to fear > but fear itself * > This is it > as far as I'm concerned ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders mismatched-tags.txt`, async () => { const input = `

Some text

some more text
and a bit more

And this output

*Compatible with PHP Markdown Extra 1.2.2 and Markdown.pl1.0.2b8:*

text


Should be in p ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders missing-link-def.txt`, async () => { const input = `This is a [missing link][empty] and a [valid][link] and [missing][again]. [link]: http://example.com ` return expect(renderHtml(input)).resolves.toMatchSnapshot() }) it(`renders multi-line-tags.txt`, async () => { const input = `
asdf asdfasd
foo bar
No blank line. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders multi-paragraph-block-quote.txt`, async () => { const input = `> This is line one of paragraph one > This is line two of paragraph one > This is line one of paragraph two > This is another blockquote. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders nested-lists.txt`, () => { const input = `* item 1 paragraph 2 * item 2 * item 2-1 * item 2-2 * item 2-2-1 * item 2-3 * item 2-3-1 * item 3 plain text * item 1 * item 1-1 * item 1-2 * item 1-2-1 * item 2 * item 3 * item 4 * item 4-1 * item 4-2 * item 4-3 Code under item 4-3 Paragraph under item 4 ` return expect(renderHtml(input)).resolves.toMatchSnapshot() }) it(`renders nested-patterns.txt`, async () => { const input = `___[link](http://example.com)___ ***[link](http://example.com)*** **[*link*](http://example.com)** __[_link_](http://example.com)__ __[*link*](http://example.com)__ **[_link_](http://example.com)** [***link***](http://example.com) ***I am ___italic_ and__ bold* I am \`just\` bold** Example __*bold italic*__ on the same line __*bold italic*__. Example **_bold italic_** on the same line **_bold italic_**. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders normalize.txt`, async () => { const input = ` [Link](http://www.stuff.com/q?x=1&y=2<>) ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders numeric-entity.txt`, async () => { const input = ` This is an entity: ê ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders para-with-hr.txt`, async () => { const input = `Here is a paragraph, followed by a horizontal rule. *** Followed by another paragraph. Here is another paragraph, followed by: *** not an HR. Followed by more of the same paragraph. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders php.txt`, async () => { const input = ` This should have a p tag
This shouldn't
` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders smart_em.txt`, async () => { const input = `_emphasis_ this_is_not_emphasis [_punctuation with emphasis_] [_punctuation_with_emphasis_] [punctuation_without_emphasis] ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders span.txt`, async () => { const input = ` Foo *bar* Baz
*foo*
Foo *bar* Baz
Foo *bar* Baz ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders strong-with-underscores.txt`, async () => { const input = `__this_is_strong__ ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders strongintags.txt`, async () => { const input = `this is a [**test**](http://example.com/) this is a second **[test](http://example.com)** reference **[test][]** reference [**test**][] ` return expect(renderHtml(input)).resolves.toMatchSnapshot() }) it(`renders underscores.txt`, async () => { const input = `THIS_SHOULD_STAY_AS_IS Here is some _emphasis_, ok? Ok, at least _this_ should work. THIS__SHOULD__STAY Here is some __strong__ stuff. THIS___SHOULD___STAY? ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) describe('options', () => { it(`renders no-attributes.txt`, async () => { const input = `Regression *test* for issue 87 It's run with enable_attributes=False so this {@id=explanation} should not become an attribute ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) describe('php', () => { it(`renders Code Spans.txt`, async () => { const input = `From \`\` on two lines. From \`\` on three lines. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Code block on second line.txt`, async () => { const input = ` Codeblock on second line ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Horizontal Rules.txt`, async () => { const input = `Horizontal rules: - - - * * * *** --- ___ Not horizontal rules (testing for a bug in 1.0.1j): +++ ,,, === ??? AAA jjj j j j n n n ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Inline HTML comments.txt`, async () => { const input = `Paragraph one. Paragraph two. The end. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders MD5 Hashes.txt`, async () => { const input = `The MD5 value for \`+\` is "26b17225b626fb9238849fd60eabdf60".

test

The MD5 value for \`

test

\` is: 6205333b793f34273d75379350b36826 ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) describe('pl', () => { it(`renders Amps and angle encoding.txt`, async () => { const input = `AT&T has an ampersand in their name. AT&T is another way to write it. This & that. 4 < 5. 6 > 5. Here's a [link] [1] with an ampersand in the URL. Here's a link with an amersand in the link text: [AT&T] [2]. Here's an inline [link](/script?foo=1&bar=2). Here's an inline [link](). [1]: http://example.com/?foo=1&bar=2 [2]: http://att.com/ "AT&T"` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Auto links.txt`, async () => { const input = `Link: . With an ampersand: * In a list? * * It should. > Blockquoted: Auto-links should not occur here: \`\` or here: ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Blockquotes with code blocks.txt`, async () => { const input = `> Example: > > sub status { > print "working"; > } > > Or: > > sub status { > return "working"; > } ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Horizontal rules.txt`, async () => { const input = `Dashes: --- --- --- --- --- - - - - - - - - - - - - - - - Asterisks: *** *** *** *** *** * * * * * * * * * * * * * * * Underscores: ___ ___ ___ ___ ___ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Inline HTML comments.txt`, async () => { const input = `Paragraph one. Paragraph two. The end. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Links, shortcut references.txt`, async () => { const input = `This is the [simple case]. [simple case]: /simple This one has a [line break]. This one has a [line break] with a line-ending space. [line break]: /foo [this] [that] and the [other] [this]: /this [that]: /that [other]: /other ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Nested blockquotes.txt`, async () => { const input = `> foo > > > bar > > foo ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Ordered and unordered lists.txt`, async () => { const input = `Asterisks tight: * asterisk 1 * asterisk 2 * asterisk 3 Asterisks loose: * asterisk 1 * asterisk 2 * asterisk 3 * * * Pluses tight: + Plus 1 + Plus 2 + Plus 3 Pluses loose: + Plus 1 + Plus 2 + Plus 3 * * * Minuses tight: - Minus 1 - Minus 2 - Minus 3 Minuses loose: - Minus 1 - Minus 2 - Minus 3 Tight: 1. First 2. Second 3. Third and: 1. One 2. Two 3. Three Loose using tabs: 1. First 2. Second 3. Third and using spaces: 1. One 2. Two 3. Three Multiple paragraphs: 1. Item 1, graf one. Item 2. graf two. The quick brown fox jumped over the lazy dog's back. 2. Item 2. 3. Item 3. * Tab * Tab * Tab Here's another: 1. First 2. Second: * Fee * Fie * Foe 3. Third Same thing but with paragraphs: 1. First 2. Second: * Fee * Fie * Foe 3. Third This was an error in Markdown 1.0.1: * this * sub that ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Strong and em together.txt`, async () => { const input = `***This is strong and em.*** So is ***this*** word. ___This is strong and em.___ So is ___this___ word. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Tabs.txt`, async () => { const input = `+ this is a list item indented with tabs + this is a list item indented with spaces Code: this code block is indented by one tab And: this code block is indented by two tabs And: + this is an example list item indented with tabs + this is an example list item indented with spaces ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders Tidyness.txt`, async () => { const input = `> A list within a blockquote: > > * asterisk 1 > * asterisk 2 > * asterisk 3 ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) describe('zds', () => { it(`renders align.txt`, async () => { const input = `A simple paragraph ->A centered paragraph<- a simple paragraph ->A right aligned paragraph-> an other simple paragraph A simple paragraph ->A centered paragraph<- a simple paragraph ->A right aligned paragraph-> an other simple paragraph ->A centered paragraph. Containing two paragraph<- an other simple paragraph ->A right aligned paragraph. Containing two paragraph<- an other simple paragraph ->A centered paragraph.<- ->An other centered paragraph.<- a simple paragraph ->A started block without end. ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders comments.txt`, async () => { const input = `Blabla<--COMMENTS hahaha COMMENTS-->Balbla \`\`\` Blabla<--COMMENTS hahaha COMMENTS-->Balbla \`\`\` <--COMMENTS Unfinished block ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders customblock.txt`, async () => { const input = `[[s]] | Secret Block [[s]] |Secret Block [[secret]] | another > [[s]] > | > Blockquote in secret block in blockquote [[i]] | Information Block [[information]] | an other [[q]] | Question Block [[question]] | an other [[a]] | Attention Block [[attention]] | an other [[e]] | Erreur Block [[erreur]] | an other [[se]] | not a block [[secretsecret]] | not a block [[SECRET]] | not a block [[s]] | Multiline block | | > with blockquote ! | Not a block content before [[s]] |A Block with content after [[erreur | a **title**]] | content [[neutre | a **title**]] | content ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders delext.txt`, async () => { const input = `Blabla ~~truc~~ kxcvj ~~sdv sd~~ sdff sdf ~~~~ df sfdgs ~ ~ dfg ~~ dgsg ~ qs ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders emoticons.txt`, async () => { const input = `Lolilol :) Hey :D :) > Citation :D Ce n'est pas une légende ![toto](https://zestedesavoir.com/media/galleries/3014/bee33fae-2216-463a-8b85-d1d9efe2c374.png) :D ce n'est pas une légende non plus` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders grid_tables.txt`, async () => { const input = `# Grid table ## Basic example +-------+----------+------+ | Table Headings | Here | +-------+----------+------+ | Sub | Headings | Too | +=======+==========+======+ | cell | column spanning | + spans +----------+------+ | rows | normal | cell | +-------+----------+------+ | multi | cells can be | | line | *formatted* | | | **paragraphs** | | cells | | | too | | +-------+-----------------+ +---+---+---+ | A | B | C | +===+===+===+ | D | E | | +---+---+ | | F | G | +---+---+---+ +---+-------+ | A | B | | +---+---+ | | C | D | | +---+---+ | | E | +---+-------+ +-----------+ | A | +---+---+---+ | B | C | D | | +---+ | | | E | | +---+---+---+ +---+---+---+ | C | D | E | | | +---+ | | | F | | +---+---+ | | G | H | | | +---+ | | | I | +---+---+---+ +---+---+---+ | A | B | C | +---+ | | | D | | | +---+---+ | | E | F | | +---+ | | | G | | | +---+---+---+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | E | F | +-------+-------+ | G | +---------------+ +---------------+ | A | +-------+-------+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+---+---+---+---+---+ | A | B | C | D | E | F | | | +---+---+ | | | | | G | | | | +---+-------+---+ | | | H | | +---+---------------+---+ | I | +-----------------------+ +---+-------------------+ | A | B | +===+===================+ | C | | | | +---+---+---+---+ | | | | D | E | F | G | | | | +---+---+---+---+ | | | | H | | | | +---------------+ | | | | +---+-------------------+ +---+---------------------------------------------------------------+---+ | H | |He | +---+---+---------------------------------------+---+---+---+---+---+---+ |Li |Be | | B | C | N | O | F |Ne | +---+---+ +---+---+---+---+---+---+ |Na |Mg | |Al |Si | P | S |Cl |Ar | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | K |Ca |Sc |Ti | V |Cr |Mn |Fe |Co |Ni |Cu |Zn |Ga |Ge |As |Se |Br |Kr | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Rb |Sr | Y |Zr |Nb |Mo |Tc |Ru |Rh |Pd |Ag |Cd |In |Sn |Sb |Te | I |Xe | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Cs |Ba |LAN|Hf |Ta | W |Re |Os |Ir |Pt |Au |Hg |Tl |Pb |Bi |Po |At |Rn | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Fr |Ra |ACT| | +---+---+---+-----------------------------------------------------------+ | | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Lanthanide |La |Ce |Pr |Nd |Pm |Sm |Eu |Gd |Tb |Dy |Ho |Er |Tm |Yb |Lu | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |Actinide |Ac |Th |Pa | U |Np |Pu |Am |Cm |Bk |Cf |Es |Fm |Md |No |Lw | +-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---------+ | A | +---------+ Text at the end +---------+ | A | +---------+ Text at the ## specific tests In this examples, the second row should always be a full-cell +---------------+ | A | +---------------+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+-----------+ | A | | +---+-----------+ | B | C | +-------+---+---+ | D E | F | G | +-------+---+---+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | B | C | | | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ +---+---+---+---+ | A | B | C | D | +---+---+---+---+ | B | C | +---+---+---+---+ | D | E | F | G | +---+---+---+---+ ## Failing example +--- A ---+ +---------+ +---------+ +---------+ | A | | | +---------+ | A | +=========+ | B | +=========+ +--- A ---+ | | +---------+ | | +---------+ Bug #107 +-----+-------+-------+-------+-------+-------+ | | case1 | case2 | case3 | +-----+-------+-------+-------+-------+-------+ | | case4 | case5 | case6 | case7 | | +=====+=======+=======+=======+=======+=======+ | X | X | X | X | X | X | +-----+-------+-------+-------+-------+-------+ ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders math.txt without custom config`, async () => { const input = `inline $simple$ math *inline* $$doubledisplay$$ math $$ display $$ $$again$$ ` const newMdastConfig = configOverride(defaultMdastConfig, {math: {}}) const newHtmlConfig = configOverride(defaultHtmlConfig, {katex: {}}) return renderHtml(newMdastConfig, newHtmlConfig)(input).then((html) => { expect((html.match(/katex-mathml/g) || []).length).toBe(4) expect((html.match(/span class="katex-display"/g) || []).length).toBe(1) expect((html.match(/inlineMath math-display/g) || []).length).toBe(0) expect((html.match(/div class="math/g) || []).length).toBe(1) }) }) it(`renders math.txt`, async () => { const input = `inline $simple$ math *inline* $$doubledisplay$$ math $$ display $$ $$again$$ ` return renderHtml(input).then((html) => { expect((html.match(/katex-mathml/g) || []).length).toBe(4) expect((html.match(/span class="katex-display"/g) || []).length).toBe(3) expect((html.match(/math-inline math-display/g) || []).length).toBe(2) expect((html.match(/div class="math/g) || []).length).toBe(1) }) }) it(`renders kbd.txt`, async () => { const input = `Blabla ||ok|| kxcvj ||ok foo|| sdff sdf |||| df sfdgs | | dfg || dgsg | qs With two pipes: \\||key|| you'll get ||key||. It parses inline elements inside: * ||hell[~~o~~](#he)?|| but not block elements inside: * ||hello: [[secret]]?|| ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders subsuperscript.txt`, async () => { const input = `Foo ^sup^ kxcvj ^sup *string*^ bar not ^ here neither \\^ here ^ because it's escaped Foo ~sup~ kxcvj ~sup *string*~ bar not ~ here neither \\~ here ~ because it's escaped foo ^^a^^ bar ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders urlize.txt`, async () => { const input = `http://www.google.fr https://www.google.fr www.google.fr google.fr Voici mon super lien qui termine une phrase http://www.google.fr. https://fr.wikipedia.org/wiki/Compactifié_d'Alexandrov https://fr.wikipedia.org/wiki/Compactifi%C3%A9_d%27Alexandrov javascript:alert%28'Hello%20world!'%29 vbscript:msgbox%28%22Hello%20world!%22%29 livescript:alert%28'Hello%20world!'%29 mocha:[code]) jAvAsCrIpT:alert%28'Hello%20world!'%29 ja vas cr ipt:alert%28'Hello%20world!'%29 ja vas cr ipt:alert%28'Hello%20world!'%29 ja vas cr ipt:alert%28'Hello%20world!'%29 ja%09 %0Avas cr ipt:alert%28'Hello%20world!'%29 ja%20vas%20cr%20ipt:alert%28'Hello%20world!'%29 live%20script:alert%28'Hello%20world!'%29 javascript:alert%29'XSS'%29 [sur isocpp.org](https://isocpp.org/std/status) ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) it(`renders video.txt`, async () => { const input = `!(https://www.youtube.com/watch?v=FdltlrKFr1w) !(https://www.dailymotion.com/video/x2y6lhm) !(http://vimeo.com/133693532) !(https://screen.yahoo.com/weatherman-gives-forecast-using-taylor-191821481.html) !(https://youtu.be/FdltlrKFr1w) !(http://youtube.com/watch?v=FdltlrKFr1w) !(http://jsfiddle.net/Sandhose/BcKhe/1/) !(http://jsfiddle.net/zgjhjv9j/) !(http://jsfiddle.net/zgjhjv9j/1/) !(https://www.youtube.com/watch?v=1Bh4DZ2xGmw&ab_channel=DestinationPr%C3%A9pa) !(http://www.ina.fr/video/MAN9062216517/qui-a-vole-le-bolero-de-ravel-e01-la-naissance-du-bolero-et-la-mort-de-ravel-video.html) This one should not be allowed: !(http://jsfiddle.net/Sandhose/BcKhe/) !(https://www.youtube.com/watch?v=FdltlrKFr1w) with text after ` const html = await renderHtml(input) const latex = await renderLatex(input) expect(html).toMatchSnapshot() expect(latex).toMatchSnapshot() }) }) ================================================ FILE: packages/zmarkdown/__tests__/mdast-suite.test.js ================================================ const dedent = require('dedent') const renderString = require('../common')() test('renders correctly', () => { const p = renderString(dedent` # foo **something else** `) return expect(p).toMatchSnapshot() }) ================================================ FILE: packages/zmarkdown/__tests__/misc.test.js ================================================ const { defaultMdastConfig, renderAs, } = require('../utils/renderer-tests') const vfile = require('../server/utils/manifest') const renderString = renderAs('html') describe('sanitizer', () => { it('do not oversanitize ping', async () => { defaultMdastConfig.ping.pingUsername = () => true const rendered = await renderString('@Clem') expect(rendered).toContain('class="ping ping-link"') expect(rendered).toContain('class="ping-username"') defaultMdastConfig.ping.pingUsername = () => false }) it('do not oversanitize math - test sub/sup', () => { expect(renderString('$$1_n + 1^n$$')).resolves.toMatchSnapshot() }) it('do not oversanitize math - test frac', () => { expect(renderString('$$\\frac{1+1}{x+y}$$')).resolves.toMatchSnapshot() }) it('do not oversanitize math - test sqrt', () => { expect(renderString('$$\\sqrt[3]{x^3 + y^3 + z^3}$$')).resolves.toMatchSnapshot() }) it('do not oversanitize math - test notin', () => { expect(renderString('$$\\notin$$')).resolves.toMatchSnapshot() }) it('do not oversanitize math - test overrightarrow', () => { expect(renderString('$$\\overrightarrow{AB}$$')).resolves.toMatchSnapshot() }) it('do not oversanitize math - test vec', () => { expect(renderString('$$\\vec{a}$$')).resolves.toMatchSnapshot() }) it('do not oversanitize math - test color', () => { expect(renderString('$$\\color{red}{x}$$')).resolves.toMatchSnapshot() }) }) describe('manifest utils', () => { it('assembles content-only vfile', () => { const res = vfile.assemble({contents: 'aa'}, {contents: 'bb'}) expect(res.contents).toEqual('aa\nbb') // Check messages are dropped expect(res.messages).toEqual([]) }) it('assembles vfile with messages', () => { const messages = [ 'I dream\'d a dream to-night.', 'And so did I.', 'Well, what was yours?', 'That dreamers often lie.', ] const res = vfile.assemble( {messages: [messages[0], messages[1]]}, {messages: [messages[2], messages[3]]}, ) expect(res.messages).toEqual(messages) }) it('assembles properties - big test', () => { const res = vfile.assemble( {data: { disableToc: true, stats: {signs: 16, words: 4}, ping: ['Romeo', 'Juliet'], }}, {data: { languages: ['html', 'js'], stats: {signs: 15, words: 3}, ping: ['Romeo', 'Mercutio'], }}, ) expect(res.data).toEqual({ disableToc: true, languages: ['html', 'js'], stats: {signs: 31, words: 7}, ping: ['Romeo', 'Juliet', 'Mercutio'], }) }) it('assembles properties - unique property', () => { const res = vfile.assemble( {data: { disableToc: true, }}, {data: { disableToc: false, }}, ) expect(res.data).toEqual({ disableToc: false, }) }) it('gather extracts', () => { const extracts = ['introduction', '# foo\n\nHello @you', 'Foobar', 'Barfoo'] const manifest = { introduction: extracts[0], children: [{text: extracts[2]}, {text: extracts[3]}], text: extracts[1], } return expect(vfile.gatherExtracts(manifest).map(v => v.text)).toEqual(extracts) }) it('dispatch extracts - simple', () => { const extracts = ['introduction', '# foo\n\nHello @you', 'Foobar', 'Barfoo'] const manifest = { introduction: 1, children: [{text: 1}, {text: 1}], text: 1, } const expectedManifest = { introduction: extracts[0], children: [{text: extracts[2]}, {text: extracts[3]}], text: extracts[1], } const resultingVfile = vfile.dispatch(extracts.map(v => ({contents: v})), manifest) expect(resultingVfile.contents).toEqual(expectedManifest) }) it('dispatch extracts - with data', () => { const extracts = ['introduction', '# foo\n\nHello @you', 'Foobar', 'Barfoo'] const manifest = { introduction: 1, children: [{text: 1}, {text: 1}], text: 1, } const resultingVfile = vfile.dispatch(extracts.map((v, i) => ({ contents: v, messages: [`Message ${i}`], data: { stats: {words: 2}, }, })), manifest) expect(resultingVfile.data.stats.words).toEqual(8) expect(resultingVfile.messages).toEqual([0, 0, 0, 0].map((_, i) => `Message ${i}`)) }) }) ================================================ FILE: packages/zmarkdown/__tests__/regressions.test.js ================================================ const a = require('axios') const dedent = require('dedent') const u = (path) => `http://localhost:27272${path}` const html = u('/html') const latex = u('/latex') describe('Regression tests', () => { describe('HTML endpoint', () => { test('#188 It does not crash on unsupported languages fenced code blocks', async () => { const response = await a.post(html, { md: [ '# foo', '', '```foobar', 'console.error("foo", true)', '```', '', '```js', 'console.error("foo", true)', '```', ].join('\n'), opts: {}, }) const [rendered] = response.data expect(rendered).toMatchSnapshot() }) test('#431 It uses random footnote postfix correctly', async () => { const md = dedent` hello[^foo] world[^bar] [^foo]: foo [^bar]: bar ` const firstResponse = await a.post(html, { md, opts: {}, }) const secondResponse = await a.post(html, { md, opts: {}, }) const [firstRendered] = firstResponse.data const [secondRendered] = secondResponse.data const firstFooReference = firstRendered.match(/fnref-1-([a-zA-Z0-9-_]+)/) const firstFooDefinition = firstRendered.match(/fn-1-([a-zA-Z0-9-_]+)/) const firstBarReference = firstRendered.match(/fnref-2-([a-zA-Z0-9-_]+)/) const secondBarDefinition = secondRendered.match(/fn-2-([a-zA-Z0-9-_]+)/) // A unique token shall be generated for a given footnote expect(firstFooReference[1]).toEqual(firstFooDefinition[1]) // But also for a given render expect(firstFooReference[1]).toEqual(firstBarReference[1]) // Finally, we expect tokens to differ between two successive renderings expect(secondBarDefinition[1]).not.toEqual(firstBarReference[1]) }) }) describe('Latex endpoint', () => { test('It wraps image basenames containing dots', async () => { const response = await a.post(latex, { md: dedent` ![](x.yz.png) [![foo](/a/w.x.y.z.png)](http://example.com) ![](/w.x.y.z.png) ![](/foo.bar/x.yz.png) `, opts: {}, }) const [rendered] = response.data expect(rendered).toMatchSnapshot() }) }) }) ================================================ FILE: packages/zmarkdown/__tests__/server.test.js ================================================ /* eslint-disable max-len */ const clone = require('clone') const dedent = require('dedent') const a = require('axios') const fs = require('fs') const xtend = require('xtend') const u = (path) => `http://localhost:27272${path}` const epub = u('/epub') const html = u('/html') const latex = u('/latex') const texfile = u('/latex-document') const texfileOpts = { content_type: 'contentType', title: 'The Title', authors: ['FØØ', 'Bär'], license: 'CC-BY-NC-SA', license_logo: 'by-nc-sa.svg', license_url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode', license_directory: '/tmp/l', smileys_directory: '/tmp/s', } const rm = (dir, file) => new Promise((resolve, reject) => fs.unlink(`${dir}/${file}`, (err) => { if (err) reject(err) fs.rmdir(dir, (err) => { if (err) reject(err) resolve('ok') }) })) describe('HTML endpoint', () => { it('accepts POSTed markdown', async () => { const response = await a.post(html, {md: '# foo', opts: {}}) expect(response.status).toBe(200) const [string, metadata] = response.data expect(string).toMatchSnapshot() expect(metadata.disableToc).toBe(false) }) it('does not disable TOC', async () => { const response = await a.post(html, {md: '# foo', opts: {}}) const [, {disableToc}] = response.data expect(disableToc).toBe(false) }) it('disables TOC', async () => { const response = await a.post(html, {md: '*foo*', opts: {}}) const [, {disableToc}] = response.data expect(disableToc).toBe(true) }) it('gets ping as metadata', async () => { const response = await a.post(html, {md: 'waddup @Clem', opts: {}}) const [rendered, metadata] = response.data expect(rendered).toContain('class="ping ping-link"') expect(rendered).toContain('href="/@Clem"') expect(metadata.ping).toEqual(['Clem']) }) it('disables ping', async () => { const response = await a.post(html, {md: 'waddup @Clem', opts: {disable_ping: true}}) const [rendered, metadata] = response.data expect(rendered).not.toContain('class="ping"') expect(metadata.ping).toBe(undefined) expect(metadata.languages).toEqual([]) }) it('only parses inline things', async () => { const response = await a.post(html, { md: '# foo\n```js\nwindow\n```', opts: {inline: true}, }) const [rendered, {languages}] = response.data expect(rendered).not.toContain(' { const response = await a.post(html, { md: '# foo\nhello bar!', opts: {disable_tokenizers: {block: ['atxHeading']}}, }) const [rendered] = response.data expect(rendered).not.toContain(' { const response = await a.post(html, { md: '# foo\n*hello bar!*', opts: { inline: true, disable_tokenizers: {inline: ['emphasis']}, }, }) const [rendered, {languages}] = response.data expect(rendered).not.toContain(' { const response = await a.post(html, { md: dedent` \`\`\`js \`\`\` \`\`\`python \`\`\` \`\`\`java \`\`\` `, opts: {}, }) const [, {languages}] = response.data expect(languages).toEqual(['js', 'python', 'java']) }) it('does not crash with an invalid align that looks like a comment', async () => { const response = await a.post(html, { md: '<-- x -->', opts: {}, }) const rendered = response.data[0] expect(rendered).toBe('

<— x —>

') }) it('produces statistics when configured', async () => { const text = dedent(` 7 chars # 13 chars here [13 chars here](https.//github.com/zestedesavoir/zmarkdown) ![13 chars here](https.//github.com/zestedesavoir/zmarkdown) ![no chars here](https.//github.com/zestedesavoir/zmarkdown) Figure: 13 chars here `) const response = await a.post(html, {md: text, opts: {stats: true}}) expect(response.status).toBe(200) const [string, metadata] = response.data expect(string).toMatchSnapshot() expect(metadata.stats.signs).toBe(59) expect(metadata.stats.words).toBe(14) }) it('correctly renders manifest', async () => { const text = { title: 'A story', children: [ {'text': 'On a balcony in summer air'}, {'text': 'Escape this town for a little while'}, {'text': 'Marry me, Juliet, you\'ll never have to be alone'}, ], conclusion: 'Just say "Yes"', } const response = await a.post(html, {md: text}) expect(response.status).toBe(200) const [string] = response.data expect(string).toMatchSnapshot() }) it('reports quizzes', async () => { const text = dedent(` [[quizz | What is true?]] | - true | - false `) const response = await a.post(html, {md: text}) expect(response.status).toBe(200) const [, metadata] = response.data expect(metadata.hasQuizz).toBe(true) }) it('enforce level shifting by default', async () => { const text = dedent(` # I have seen a dolphin On a camera. What is happening with animals these days?" `) const response = await a.post(html, {md: text, opts: {heading_shift: 1}}) expect(response.status).toBe(200) const [content] = response.data expect(content).toMatchSnapshot() expect(content).toContain('h2') }) it('can use custom iframe elements', async () => { const text = dedent(` # You won't get my data, Google! !(https://www.youtube.com/watch?v=q3zqJs7JUCQ) `) const response = await a.post(html, {md: text, opts: {hide_iframes: true}}) expect(response.status).toBe(200) const [content] = response.data expect(content).not.toContain('iframe') expect(content).toContain('hidden-frame') expect(content).toMatchSnapshot() }) }) describe('LaTeX endpoint', () => { it('accepts POSTed markdown', async () => { const response = await a.post(latex, {md: '# foo', opts: {}}) expect(response.status).toBe(200) const [string, metadata] = response.data expect(string).toMatchSnapshot() expect(metadata.disableToc).toBe(false) }) it('does not disable TOC', async () => { const response = await a.post(latex, {md: '# foo', opts: {}}) const [, {disableToc}] = response.data expect(disableToc).toBe(false) }) it('disables TOC', async () => { const response = await a.post(latex, {md: '*foo*', opts: {}}) const [, {disableToc}] = response.data expect(disableToc).toBe(true) }) it('does not have pings', async () => { const response = await a.post(latex, {md: 'waddup @Clem', opts: {}}) const [rendered, metadata] = response.data expect(rendered).toEqual('waddup @Clem\n\n') expect(metadata.ping).toBe(undefined) }) it('disables ping', async () => { const response = await a.post(latex, {md: 'waddup @Clem', opts: {disable_ping: true}}) const [rendered, metadata] = response.data expect(rendered).not.toContain('class="ping"') expect(metadata.ping).toBe(undefined) }) it('only parses inline things', async () => { const response = await a.post(latex, { md: '# foo\n```js\nwindow\n```', opts: {inline: true}, }) const [rendered] = response.data expect(rendered).not.toContain(' { const destination = process.env.DEST || `${__dirname}/../public/` const response = await a.post(latex, { md: `![](${u('/static/img.png')})`, opts: {inline: true, images_download_dir: destination}, }) const [rendered, , messages] = response.data expect(messages).toEqual([]) const regex = /\/([a-zA-Z0-9_-]{7,14})\/([a-zA-Z0-9_-]{7,14})\.(.{1,4})}/ expect(rendered).toMatch(regex) const [, dir, file, ext] = rendered.match(regex) return expect(rm(`${destination}/${dir}`, `${file}.${ext}`)).resolves.toBe('ok') }) it('properly defaults image', async () => { const destination = process.env.DEST || `${__dirname}/../public/` const response = await a.post(latex, { md: `![](${u('/static/noimage.png')})`, opts: {inline: true, images_download_dir: destination}, }) const rendered = response.data[0] expect(rendered).toContain('black.png') }) it('properly defaults image with custom path', async () => { const destination = process.env.DEST || `${__dirname}/../public/` const response = await a.post(latex, { md: `![](${u('/static/noimage.png')})`, opts: { inline: true, images_download_dir: destination, images_download_default: 'default.png', }, }) const rendered = response.data[0] expect(rendered).toContain('default.png') }) it('correctly renders manifest', async () => { const text = { title: 'Another story', children: [ {'text': 'I\'m standing there'}, {'text': 'And I was crying on the staircase'}, {'text': 'I got tired of waiting'}, ], conclusion: 'Just say "Yes"', } const response = await a.post(latex, {md: text}) expect(response.status).toBe(200) const [string] = response.data expect(string).toMatchSnapshot() }) }) describe('Texfile endpoint', () => { it('accepts POSTed markdown', async () => { const response = await a.post(texfile, {md: '# foo', opts: texfileOpts}) expect(response.status).toBe(200) const [string, metadata] = response.data expect(string).toMatchSnapshot() expect(string).toContain( '\\licence[/tmp/l/by-nc-sa.svg]{CC-BY-NC-SA}' + '{https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode}') expect(metadata).toEqual({}) }) it('escapes title and author', async () => { const titleOpts = {authors: ['titi_alone'], title: 'recap #1'} const response = await a.post(texfile, {md: '# foo', opts: xtend(texfileOpts, titleOpts)}) expect(response.status).toBe(200) const result = response.data expect(result[0]).toMatchSnapshot() }) it('allows date', async () => { const specificOptions = {date: '2 mai 1998'} const response = await a.post(texfile, {md: '# foo', opts: xtend(texfileOpts, specificOptions)}) expect(response.status).toBe(200) const result = response.data expect(result[0]).toMatchSnapshot() }) it('does not return metadata', async () => { const response = await a.post(texfile, {md: '# foo', opts: texfileOpts}) const [, metadata] = response.data expect(metadata).toEqual({}) }) it('does not have pings', async () => { const response = await a.post(texfile, {md: 'waddup @Clem', opts: texfileOpts}) const [rendered, metadata] = response.data expect(rendered).toContain('waddup @Clem\n\n') expect(metadata.ping).toBe(undefined) }) it('only parses inline things', async () => { const response = await a.post(texfile, { md: '# foo\n```js\nwindow\n```', opts: texfileOpts, }) const [rendered] = response.data expect(rendered).not.toContain(' { const destination = process.env.DEST || `${__dirname}/../public/` const opts = clone(texfileOpts) opts.images_download_dir = destination const response = await a.post(texfile, { md: `![](${u('/static/img.png')})`, opts: opts, }) const [rendered, , messages] = response.data expect(messages).toEqual([]) const regex = /\/([a-zA-Z0-9_-]{7,14})\/([a-zA-Z0-9_-]{7,14})\.(.{1,4})}/ expect(rendered).toMatch(regex) const [, dir, file, ext] = rendered.match(regex) return expect(rm(`${destination}/${dir}`, `${file}.${ext}`)).resolves.toBe('ok') }) it('allows extra arguments', async () => { const additionalOpts = { logo_directory: '/tmp/logo', content_logo: 'h2g2.png', content_link: 'https://en.wikipedia.org/wiki/The_Hitchhiker%27s_Guide_to_the_Galaxy_(novel)', editor_logo: 'pmm.jpg', editor_link: 'https://www.panmacmillan.com/', } const opts = Object.assign({}, texfileOpts, additionalOpts) const response = await a.post(texfile, {md: '# foo', opts}) expect(response.status).toBe(200) const [string] = response.data expect(string).toMatchSnapshot() expect(string).toContain(dedent`\logo{/tmp/logo/h2g2.png} \editorLogo{/tmp/logo/pmm.jpg} \tutoLink{https://en.wikipedia.org/wiki/The_Hitchhiker%27s_Guide_to_the_Galaxy_(novel)} \editor{https://www.panmacmillan.com/}`) }) it('transform quizzes for document', async () => { const opts = clone(texfileOpts) const text = dedent(` [[quizz | What is true?]] | - true | - false `) const response = await a.post(texfile, {md: text, opts}) expect(response.status).toBe(200) const [content] = response.data expect(content).toMatchSnapshot() }) it('correctly renders introduction & conclusion', async () => { const opts = clone(texfileOpts) const manifest = { introduction: 'Here I introduce My content™', title: 'My content™', children: [{ children: [{ introduction: 'Here I introduce My section™', conclusion: 'Here I conclude My section™', }], }], conclusion: 'Here I conclude My content™', } const response = await a.post(texfile, {md: manifest, opts}) expect(response.status).toBe(200) const [content] = response.data expect(content).toMatchSnapshot() expect(content).toContain('LevelOneIntroduction') expect(content).toContain('LevelOneConclusion') expect(content).toContain('LevelThreeIntroduction') expect(content).toContain('LevelThreeConclusion') }) it('shifts titles and only titles', async () => { const opts = clone(texfileOpts) const manifest = { introduction: 'myIntro', title: 'myTitle', } opts.heading_shift = 2 const response = await a.post(texfile, {md: manifest, opts}) expect(response.status).toBe(200) const [content] = response.data expect(content).toMatchSnapshot() expect(content).toContain('LevelOneIntroduction') expect(content).toContain('levelThreeTitle') }) }) describe('EPUB endpoint', () => { it('downloads images', async () => { const destination = process.env.DEST || `${__dirname}/../public/` const opts = clone(texfileOpts) opts.images_download_dir = destination const response = await a.post(epub, { md: `![](${u('/static/img.png')})`, opts: opts, }) const [rendered, , messages] = response.data expect(messages).toEqual([]) const regex = /\/([a-zA-Z0-9_-]{7,14})\/([a-zA-Z0-9_-]{7,14})\.(.{1,4})"/ expect(rendered).toMatch(regex) const [, dir, file, ext] = rendered.match(regex) return expect(rm(`${destination}/${dir}`, `${file}.${ext}`)).resolves.toBe('ok') }) it('copies local images', async () => { const destination = process.env.DEST || `${__dirname}/../public/` const opts = clone(texfileOpts) opts.images_download_dir = destination opts.local_url_to_local_path = [ '/foobar', `${__dirname.replace('__tests__', 'server/static')}`, ] const response = await a.post(epub, { md: `![](file://tmp/passwd)`, opts: opts, }) const [rendered, , messages] = response.data expect(messages[0].message).toMatch("Protocol 'file:' not allowed.") expect(rendered).toBe('

') }) }) ================================================ FILE: packages/zmarkdown/client/zhlite.js ================================================ // This is the same as the `html` renderer, but highlight & katex were removed. // We need to duplicate the file because otherwise Webpack would // load the modules anyway... import {parser as mdastParser} from './zmdast' const rendererForge = require('../renderers/renderer-forge') const remark2rehype = require('remark-rehype') const rehypeStringify = require('rehype-stringify') const defaultMdastConfig = require('../config/mdast') const defaultHtmlConfig = require('../config/html') const defaultStringifierList = { slug: require('rehype-slug'), autolinkHeadings: require('rehype-autolink-headings'), footnotesTitles: require('rehype-footnotes-title'), htmlBlocks: require('rehype-html-blocks'), postfixFootnotes: require('rehype-postfix-footnote-anchors'), sanitize: require('rehype-sanitize'), } const postProcessorList = { wrapCode: require('../postprocessors/html-wrap-code'), iframeWrappers: require('../postprocessors/html-iframe-wrappers'), footnotesReorder: require('../postprocessors/html-footnotes-reorder'), } export function parser (tokenizer, config) { tokenizer .use(remark2rehype, config.bridge) rendererForge( tokenizer, defaultStringifierList, postProcessorList, )(config) return tokenizer .use(rehypeStringify, config.stringify) } export function render ( markdown, cb, mdConfig = defaultMdastConfig, htmlConfig = defaultHtmlConfig, ) { const processor = mdastParser(mdConfig) parser(processor, htmlConfig) processor.process(markdown, (err, vfile) => { if (err) return cb(err) cb(null, vfile) }) } ================================================ FILE: packages/zmarkdown/client/zhtml.js ================================================ const defaultMdastConfig = require('../config/mdast') const defaultHtmlConfig = require('../config/html') import {parser as mdastParser} from './zmdast' export const parser = require('../renderers/html') export function render ( markdown, cb, mdConfig = defaultMdastConfig, htmlConfig = defaultHtmlConfig, ) { const processor = mdastParser(mdConfig) parser(processor, htmlConfig) processor.process(markdown, (err, vfile) => { if (err) return cb(err) cb(null, vfile) }) } ================================================ FILE: packages/zmarkdown/client/zlatex.js ================================================ const defaultMdastConfig = require('../config/mdast') const defaultLatexConfig = require('../config/latex') import {parser as mdastParser} from './zmdast' export const parser = require('../renderers/latex') export function render ( markdown, cb, mdConfig = defaultMdastConfig, latexConfig = defaultLatexConfig, ) { const processor = mdastParser(mdConfig) parser(processor, latexConfig) processor.process(markdown, (err, vfile) => { if (err) return cb(err) cb(null, vfile) }) } ================================================ FILE: packages/zmarkdown/client/zmdast.js ================================================ // This is the same as the `mdast` renderer, but downloadImages was removed. // We need to duplicate the file because otherwise Webpack would // load the module anyway... export const inspect = require('unist-util-inspect') const rendererForge = require('../renderers/renderer-forge') const unified = require('unified') const remarkParse = require('remark-parse') const remarkDisableTokenizers = require('remark-disable-tokenizers/src') const defaultMdastConfig = require('../config/mdast') const defaultTokenizerList = { abbr: require('remark-abbr/src'), alignBlocks: require('remark-align/src'), captions: require('remark-captions/src'), codeMeta: require('../plugins/remark-code-meta'), comments: require('remark-comments/src'), customBlocks: require('remark-custom-blocks/src'), emoticons: require('remark-emoticons/src'), escapeEscaped: require('remark-escape-escaped/src'), footnotes: require('remark-footnotes'), gridTables: require('remark-grid-tables/src'), headingShifter: require('remark-heading-shift/src'), iframes: require('remark-iframes/src'), imageToFigure: require('../plugins/remark-image-to-figure'), kbd: require('remark-kbd/src'), math: require('remark-math'), numberedFootnotes: require('remark-numbered-footnotes/src'), ping: require('remark-ping/src'), subSuper: require('remark-sub-super/src'), textr: require('../plugins/remark-textr'), trailingSpaceHeading: require('remark-heading-trailing-spaces'), } const postProcessorList = { detectQuizzes: require('../postprocessors/md-detect-quizzes'), getStats: require('../postprocessors/md-get-stats'), limitDepth: require('../postprocessors/md-limit-depth'), listLanguages: require('../postprocessors/md-list-languages'), wrapIntroCcl: require('../postprocessors/md-wrap-intro-ccl'), } export function parser (config) { const baseTokenizer = unified() .use(remarkParse, config.parse) rendererForge( baseTokenizer, defaultTokenizerList, postProcessorList, )(config) return baseTokenizer .use(remarkDisableTokenizers, config.disableTokenizers) } export function render (markdown, cb, config = defaultMdastConfig) { const processor = parser(config) return processor.parse(markdown) } ================================================ FILE: packages/zmarkdown/common.js ================================================ const mdastRenderer = require('./renderers/mdast') const htmlRenderer = require('./renderers/html') const latexRenderer = require('./renderers/latex') const mdastConfig = require('./config/mdast') const htmlConfig = require('./config/html') const latexConfig = require('./config/latex') module.exports = ( processor = null, tokenizerConfig = mdastConfig, processorConfig ) => { const parser = mdastRenderer(tokenizerConfig) if (processor === 'html') { if (typeof processorConfig === 'undefined') { processorConfig = htmlConfig } htmlRenderer(parser, processorConfig) } else if (processor === 'latex') { if (typeof processorConfig === 'undefined') { processorConfig = latexConfig } latexRenderer(parser, processorConfig) // No processor given: output syntax tree } else if (processor === null) { return parser.parse // Custom processor: use it } else { parser.use(processor, processorConfig) } // Regenerate footnotes postfix on extracts const doRegenerate = (processor === 'html' && processorConfig._regenerateFootnotePostfix) const regenerator = processorConfig._regenerateFootnotePostfix return (input, cb) => { if (typeof cb !== 'function') { return new Promise((resolve, reject) => parser.process(input, (err, vfile) => { if (doRegenerate) regenerator() if (err) return reject(err) resolve(vfile) })) } parser.process(input, (err, vfile) => { if (doRegenerate) regenerator() if (err) return cb(err) cb(null, vfile) }) } } ================================================ FILE: packages/zmarkdown/config/html/iframe-wrappers.js ================================================ const createWrapper = require('../../utils/create-wrappers') const jsFiddleAndInaFilter = node => { if (node.properties.src) { return node.properties.src.includes('jsfiddle.') || node.properties.src.includes('ina.') } return false } module.exports = { iframe: [ createWrapper( 'iframe', ['div', 'div'], [['video-wrapper'], ['video-container']], node => !jsFiddleAndInaFilter(node) ), createWrapper('iframe', 'div', ['iframe-wrapper'], jsFiddleAndInaFilter) ], table: [ createWrapper('table', 'div', ['table-wrapper']) ] } ================================================ FILE: packages/zmarkdown/config/html/index.js ================================================ const shortid = require('shortid') let currentFootnotePostfix = shortid.generate() module.exports = { autolinkHeadings: { behaviour: 'append' }, bridge: { allowDangerousHtml: true, handlers: { code: require('../../utils/code-handler') } }, footnotesTitles: 'Retourner au texte de la note $id', highlight: { ignoreMissing: true, plainText: ['console'], aliases: { tex: ['latex'] } }, sanitize: require('../sanitize'), postfixFootnotes: (agg) => `${agg}-${currentFootnotePostfix}`, _regenerateFootnotePostfix: () => { currentFootnotePostfix = shortid.generate() }, postProcessors: { iframeWrappers: require('./iframe-wrappers'), footnotesReorder: true, lazyLoadImages: true, wrapCode: true } } ================================================ FILE: packages/zmarkdown/config/latex/index.js ================================================ /* eslint-disable max-len */ const remarkConfig = require('../mdast') const globalEscape = require('rebber/dist/escaper') const appendix = require('rebber-plugins/dist/preprocessors/codeVisitor') const mathEscape = require('rebber-plugins/dist/preprocessors/mathEscape') const footnoteProtect = require('rebber-plugins/dist/preprocessors/footnoteProtect') const rebberConfig = { preprocessors: { tableCell: appendix, footnoteDefinition: [appendix], spoilerFlatten: require('rebber-plugins/dist/preprocessors/spoilerFlatten')([ 'sCustomBlock', 'secretCustomBlock' ]), inlineMath: [mathEscape], inlineMathDouble: [mathEscape], math: [mathEscape], heading: footnoteProtect, figcaption: footnoteProtect, quizzCustomBlock: require('rebber-plugins/dist/preprocessors/prepareQuizz') }, overrides: { abbr: require('rebber-plugins/dist/type/abbr'), comments: require('rebber-plugins/dist/type/comments'), conclusion: require('rebber-plugins/dist/type/conclusion'), emoticon: require('rebber-plugins/dist/type/emoticon'), figure: require('rebber-plugins/dist/type/figure'), gridTable: require('rebber-plugins/dist/type/gridTable'), inlineMath: require('rebber-plugins/dist/type/math'), introduction: require('rebber-plugins/dist/type/introduction'), kbd: require('rebber-plugins/dist/type/kbd'), math: require('rebber-plugins/dist/type/math'), ping: require('rebber-plugins/dist/type/ping'), sub: require('rebber-plugins/dist/type/sub'), sup: require('rebber-plugins/dist/type/sup'), tableHeader: require('rebber-plugins/dist/type/tableHeader'), footnote: require('rebber-plugins/dist/type/footnote'), footnoteDefinition: require('rebber-plugins/dist/type/footnoteDefinition'), footnoteReference: require('rebber-plugins/dist/type/footnoteReference'), centerAligned: require('rebber-plugins/dist/type/align'), leftAligned: require('rebber-plugins/dist/type/align'), rightAligned: require('rebber-plugins/dist/type/align'), errorCustomBlock: require('rebber-plugins/dist/type/customBlocks'), informationCustomBlock: require('rebber-plugins/dist/type/customBlocks'), neutralCustomBlock: require('rebber-plugins/dist/type/customBlocks'), questionCustomBlock: require('rebber-plugins/dist/type/customBlocks'), secretCustomBlock: require('rebber-plugins/dist/type/customBlocks'), warningCustomBlock: require('rebber-plugins/dist/type/customBlocks'), inlineCode: (ctx, node) => { const escaped = globalEscape(node.value) return `\\CodeInline{${escaped}}` }, iframe: (ctx, node) => { const alternative = node.data.hProperties.src.includes('jsfiddle') ? 'Code' : 'Video' const caption = node.caption || '' return `\\iframe{${node.data.hProperties.src}}[${alternative}][${caption}]` } }, emoticons: { emoticons: Object.entries(remarkConfig.emoticons.emoticons).reduce((acc, [key, val]) => { acc[key] = val.substring(val.lastIndexOf('/') + 1) return acc }, {}) }, codeAppendiceTitle: 'Annexes', appendiceReferenceGenerator: (appendixIndex) => `Annexe de code ${appendixIndex}`, code: require('../../utils/latex-code'), customBlocks: { map: { error: 'Error', information: 'Information', question: 'Question', secret: 'Spoiler', warning: 'Warning', neutre: 'Neutral' } }, link: { prefix: 'http://zestedesavoir.com' }, image: { inlineImage: (node) => `\\inlineImage{${node.url}}`, image: (node) => `\\image{${node.url}}` }, firstLineRowFont: '\\rowfont[l]{\\bfseries}', tableEnvName: 'zdstblr', figure: { image: (_1, _2, caption, extra) => `\\image{${extra.url}}${caption ? `[${caption}]` : ''}\n` }, headings: [ (val) => `\\levelOneTitle{${val}}\n`, (val) => `\\levelTwoTitle{${val}}\n`, (val) => `\\levelThreeTitle{${val}}\n`, (val) => `\\levelFourTitle{${val}}\n`, (val) => `\\levelFiveTitle{${val}}\n`, (val) => `\\levelSixTitle{${val}}\n`, (val) => `\\levelSevenTitle{${val}}\n` ] } Object.assign(rebberConfig.overrides, { eCustomBlock: (ctx, node) => { node.type = 'errorCustomBlock' return rebberConfig.overrides.errorCustomBlock(ctx, node) }, erreurCustomBlock: (ctx, node) => { node.type = 'errorCustomBlock' return rebberConfig.overrides.errorCustomBlock(ctx, node) }, iCustomBlock: (ctx, node) => { node.type = 'informationCustomBlock' return rebberConfig.overrides.informationCustomBlock(ctx, node) }, qCustomBlock: (ctx, node) => { node.type = 'questionCustomBlock' return rebberConfig.overrides.questionCustomBlock(ctx, node) }, sCustomBlock: (ctx, node) => { node.type = 'secretCustomBlock' return rebberConfig.overrides.secretCustomBlock(ctx, node) }, aCustomBlock: (ctx, node) => { node.type = 'warningCustomBlock' return rebberConfig.overrides.warningCustomBlock(ctx, node) }, attentionCustomBlock: (ctx, node) => { node.type = 'warningCustomBlock' return rebberConfig.overrides.warningCustomBlock(ctx, node) }, nCustomBlock: (ctx, node) => { node.type = 'neutralCustomBlock' return rebberConfig.overrides.neutralCustomBlock(ctx, node) }, neutreCustomBlock: (ctx, node) => { node.type = 'neutralCustomBlock' return rebberConfig.overrides.neutralCustomBlock(ctx, node) } }) module.exports = rebberConfig ================================================ FILE: packages/zmarkdown/config/mdast/custom-blocks.js ================================================ module.exports = { secret: { classes: 'custom-block-spoiler', title: 'optional', details: true }, s: { classes: 'custom-block-spoiler', title: 'optional', details: true }, information: { classes: 'custom-block-information', title: 'optional' }, i: { classes: 'custom-block-information', title: 'optional' }, question: { classes: 'custom-block-question', title: 'optional' }, q: { classes: 'custom-block-question', title: 'optional' }, attention: { classes: 'custom-block-warning', title: 'optional' }, a: { classes: 'custom-block-warning', title: 'optional' }, erreur: { classes: 'custom-block-error', title: 'optional' }, e: { classes: 'custom-block-error', title: 'optional' }, neutre: { classes: 'custom-block-neutral', title: 'required' }, n: { classes: 'custom-block-neutral', title: 'required' }, quizz: { classes: 'custom-block-quizz', title: 'required' } } ================================================ FILE: packages/zmarkdown/config/mdast/emoticons.js ================================================ module.exports = { emoticons: { ':ange:': '/static/smileys/svg/ange.svg', ':colere:': '/static/smileys/svg/angry.svg', o_O: '/static/smileys/svg/blink.svg', ';)': '/static/smileys/svg/clin.svg', ':B': '/static/smileys/svg/b.svg', ':diable:': '/static/smileys/svg/diable.svg', ':D': '/static/smileys/svg/heureux.svg', '^^': '/static/smileys/svg/hihi.svg', ':o': '/static/smileys/svg/huh.svg', ':p': '/static/smileys/svg/langue.svg', ':magicien:': '/static/smileys/svg/magicien.svg', ':colere2:': '/static/smileys/svg/mechant.svg', ':ninja:': '/static/smileys/svg/ninja.svg', '>_<': '/static/smileys/svg/pinch.svg', 'X/': '/static/smileys/svg/pinch.svg', ':pirate:': '/static/smileys/svg/pirate.svg', ":'(": '/static/smileys/svg/pleure.svg', ':lol:': '/static/smileys/svg/rire.svg', ':honte:': '/static/smileys/svg/rouge.svg', ':-°': '/static/smileys/svg/siffle.svg', ':)': '/static/smileys/svg/smile.svg', ':soleil:': '/static/smileys/svg/soleil.svg', ':(': '/static/smileys/svg/triste.svg', ':euh:': '/static/smileys/svg/unsure.svg', ':waw:': '/static/smileys/svg/waw.svg', ':zorro:': '/static/smileys/svg/zorro.svg', '^(;,;)^': '/static/smileys/svg/cthulhu.svg', ':bounce:': '/static/smileys/svg/bounce.svg', ':popcorn:': '/static/smileys/svg/popcorn.svg', ':démon:': '/static/smileys/svg/1f47f.svg', ':demon:': '/static/smileys/svg/1f47f.svg', ':content:': '/static/smileys/svg/1f600.svg', ':joyeux:': '/static/smileys/svg/1f601.svg', ':mortderire:': '/static/smileys/svg/1f602.svg', ':daccord:': '/static/smileys/svg/1f603.svg', ':eneffet:': '/static/smileys/svg/1f604.svg', ':eneffetgené:': '/static/smileys/svg/1f605.svg', ':eneffetgene:': '/static/smileys/svg/1f605.svg', 'x)': '/static/smileys/svg/1f606.svg', ':innocent:': '/static/smileys/svg/1f607.svg', ':démonjoyeux:': '/static/smileys/svg/1f608.svg', ':demonjoyeux:': '/static/smileys/svg/1f608.svg', ':clindoeil:': '/static/smileys/svg/1f609.svg', ':rejouis:': '/static/smileys/svg/1f60a.svg', ':yum:': '/static/smileys/svg/1f60b.svg', ':soulagé:': '/static/smileys/svg/1f60c.svg', ':soulage:': '/static/smileys/svg/1f60c.svg', '<3': '/static/smileys/svg/1f60d.svg', ':confiant:': '/static/smileys/svg/1f60e.svg', ':malicieux:': '/static/smileys/svg/1f60f.svg', ':indifférent:': '/static/smileys/svg/1f610.svg', ':indifferent:': '/static/smileys/svg/1f610.svg', ':détaché:': '/static/smileys/svg/1f611.svg', ':detache:': '/static/smileys/svg/1f611.svg', ':lassé:': '/static/smileys/svg/1f612.svg', ':lasse:': '/static/smileys/svg/1f612.svg', ':sueurfroide:': '/static/smileys/svg/1f613.svg', ':insatisfait:': '/static/smileys/svg/1f614.svg', ':-/': '/static/smileys/svg/1f615.svg', ':contrarié:': '/static/smileys/svg/1f616.svg', ':contrarie:': '/static/smileys/svg/1f616.svg', ':bisou:': '/static/smileys/svg/1f617.svg', ':bisoucoeur:': '/static/smileys/svg/1f618.svg', ':bisousourire:': '/static/smileys/svg/1f619.svg', ':bisourougir:': '/static/smileys/svg/1f61a.svg', ':-P': '/static/smileys/svg/1f61b.svg', ';-P': '/static/smileys/svg/1f61c.svg', 'x-P': '/static/smileys/svg/1f61d.svg', ':déçuinquiet:': '/static/smileys/svg/1f61e.svg', ':decuinquiet:': '/static/smileys/svg/1f61e.svg', ':inquiet:': '/static/smileys/svg/1f61f.svg', ':fâché:': '/static/smileys/svg/1f620.svg', ':fache:': '/static/smileys/svg/1f620.svg', ':fâchérouge:': '/static/smileys/svg/1f621.svg', ':facherouge:': '/static/smileys/svg/1f621.svg', ':tristelarme:': '/static/smileys/svg/1f622.svg', 'x(': '/static/smileys/svg/1f623.svg', ':fulminant:': '/static/smileys/svg/1f624.svg', ':deçularme:': '/static/smileys/svg/1f625.svg', ':decularme:': '/static/smileys/svg/1f625.svg', ':déçu:': '/static/smileys/svg/1f626.svg', ':decu:': '/static/smileys/svg/1f626.svg', ':déçutriste:': '/static/smileys/svg/1f627.svg', ':decutriste:': '/static/smileys/svg/1f627.svg', ':déçuangoissé:': '/static/smileys/svg/1f628.svg', ':decuangoisse:': '/static/smileys/svg/1f628.svg', ':éreinté:': '/static/smileys/svg/1f629.svg', ':ereinte:': '/static/smileys/svg/1f629.svg', ':somnole:': '/static/smileys/svg/1f62a.svg', ':fatigué:': '/static/smileys/svg/1f62b.svg', ':fatigue:': '/static/smileys/svg/1f62b.svg', ':grimace:': '/static/smileys/svg/1f62c.svg', ':pleure:': '/static/smileys/svg/1f62d.svg', ':ébahi:': '/static/smileys/svg/1f62e.svg', ':ebahi:': '/static/smileys/svg/1f62e.svg', ':étonné:': '/static/smileys/svg/1f62f.svg', ':etonne:': '/static/smileys/svg/1f62f.svg', ':angoissé:': '/static/smileys/svg/1f630.svg', ':angoisse:': '/static/smileys/svg/1f630.svg', ':hurlantdepeur:': '/static/smileys/svg/1f631.svg', ':abasourdi:': '/static/smileys/svg/1f632.svg', ':surprisrougi:': '/static/smileys/svg/1f633.svg', ':dort:': '/static/smileys/svg/1f634.svg', ':vertige:': '/static/smileys/svg/1f635.svg', ':muet:': '/static/smileys/svg/1f636.svg', ':masquetissu:': '/static/smileys/svg/1f637.svg', ':nonsatisfait:': '/static/smileys/svg/1f641.svg', ':satisfait:': '/static/smileys/svg/1f642.svg', ':inversé:': '/static/smileys/svg/1f643.svg', ':inverse:': '/static/smileys/svg/1f643.svg', ':regardauciel:': '/static/smileys/svg/1f644.svg', ':bouchezipper:': '/static/smileys/svg/1f910.svg', ':appâtdugain:': '/static/smileys/svg/1f911.svg', ':appatdugain:': '/static/smileys/svg/1f911.svg', ':thermomètre:': '/static/smileys/svg/1f912.svg', ':intello4yeux:': '/static/smileys/svg/1f913.svg', ':pensif:': '/static/smileys/svg/1f914.svg', ':blessé:': '/static/smileys/svg/1f915.svg', ':blesse:': '/static/smileys/svg/1f915.svg', ':bienveillant:': '/static/smileys/svg/1f917.svg', ':nausée:': '/static/smileys/svg/1f922.svg', ':nausee:': '/static/smileys/svg/1f922.svg', ':pliéderire:': '/static/smileys/svg/1f923.svg', ':pliederire:': '/static/smileys/svg/1f923.svg', ':baver:': '/static/smileys/svg/1f924.svg', ':pinocchio:': '/static/smileys/svg/1f925.svg', ':eternuer:': '/static/smileys/svg/1f927.svg', ':quésaco:': '/static/smileys/svg/1f928.svg', ':quesaco:': '/static/smileys/svg/1f928.svg', ':regardfan:': '/static/smileys/svg/1f929.svg', ':hébété:': '/static/smileys/svg/1f92a.svg', ':hebete:': '/static/smileys/svg/1f92a.svg', ':chut:': '/static/smileys/svg/1f92b.svg', ':fureur:': '/static/smileys/svg/1f92c.svg', ':bailler:': '/static/smileys/svg/1f92d.svg', ':vomir:': '/static/smileys/svg/1f92e.svg', ':tropcogiter:': '/static/smileys/svg/1f92f.svg', ':amoureux:': '/static/smileys/svg/1f970.svg', ':fêter:': '/static/smileys/svg/1f973.svg', ':feter:': '/static/smileys/svg/1f973.svg', ':maldecoeur:': '/static/smileys/svg/1f974.svg', ':avoirchaud:': '/static/smileys/svg/1f975.svg', ':avoirfroid:': '/static/smileys/svg/1f976.svg', ':désolé:': '/static/smileys/svg/1f97a.svg', ':desole:': '/static/smileys/svg/1f97a.svg', ':triste:': '/static/smileys/svg/2639.svg', ':rougir:': '/static/smileys/svg/263a.svg', '🧐': '/static/smileys/svg/1f9d0.svg', '👿': '/static/smileys/svg/1f47f.svg', '😀': '/static/smileys/svg/1f600.svg', '😁': '/static/smileys/svg/1f601.svg', '😂': '/static/smileys/svg/1f602.svg', '😃': '/static/smileys/svg/1f603.svg', '😄': '/static/smileys/svg/1f604.svg', '😅': '/static/smileys/svg/1f605.svg', '😆': '/static/smileys/svg/1f606.svg', '😇': '/static/smileys/svg/1f607.svg', '😈': '/static/smileys/svg/1f608.svg', '😉': '/static/smileys/svg/1f609.svg', '😊': '/static/smileys/svg/1f60a.svg', '😋': '/static/smileys/svg/1f60b.svg', '😌': '/static/smileys/svg/1f60c.svg', '😍': '/static/smileys/svg/1f60d.svg', '😎': '/static/smileys/svg/1f60e.svg', '😏': '/static/smileys/svg/1f60f.svg', '😐': '/static/smileys/svg/1f610.svg', '😑': '/static/smileys/svg/1f611.svg', '😒': '/static/smileys/svg/1f612.svg', '😓': '/static/smileys/svg/1f613.svg', '😔': '/static/smileys/svg/1f614.svg', '😕': '/static/smileys/svg/1f615.svg', '😖': '/static/smileys/svg/1f616.svg', '😗': '/static/smileys/svg/1f617.svg', '😘': '/static/smileys/svg/1f618.svg', '😙': '/static/smileys/svg/1f619.svg', '😚': '/static/smileys/svg/1f61a.svg', '😛': '/static/smileys/svg/1f61b.svg', '😜': '/static/smileys/svg/1f61c.svg', '😝': '/static/smileys/svg/1f61d.svg', '😞': '/static/smileys/svg/1f61e.svg', '😟': '/static/smileys/svg/1f61f.svg', '😠': '/static/smileys/svg/1f620.svg', '😡': '/static/smileys/svg/1f621.svg', '😢': '/static/smileys/svg/1f622.svg', '😣': '/static/smileys/svg/1f623.svg', '😤': '/static/smileys/svg/1f624.svg', '😥': '/static/smileys/svg/1f625.svg', '😦': '/static/smileys/svg/1f626.svg', '😧': '/static/smileys/svg/1f627.svg', '😨': '/static/smileys/svg/1f628.svg', '😩': '/static/smileys/svg/1f629.svg', '😪': '/static/smileys/svg/1f62a.svg', '😫': '/static/smileys/svg/1f62b.svg', '😬': '/static/smileys/svg/1f62c.svg', '😭': '/static/smileys/svg/1f62d.svg', '😮': '/static/smileys/svg/1f62e.svg', '😯': '/static/smileys/svg/1f62f.svg', '😰': '/static/smileys/svg/1f630.svg', '😱': '/static/smileys/svg/1f631.svg', '😲': '/static/smileys/svg/1f632.svg', '😳': '/static/smileys/svg/1f633.svg', '😴': '/static/smileys/svg/1f634.svg', '😵': '/static/smileys/svg/1f635.svg', '😶': '/static/smileys/svg/1f636.svg', '😷': '/static/smileys/svg/1f637.svg', '🙁': '/static/smileys/svg/1f641.svg', '🙂': '/static/smileys/svg/1f642.svg', '🙃': '/static/smileys/svg/1f643.svg', '🙄': '/static/smileys/svg/1f644.svg', '🤐': '/static/smileys/svg/1f910.svg', '🤑': '/static/smileys/svg/1f911.svg', '🤒': '/static/smileys/svg/1f912.svg', '🤓': '/static/smileys/svg/1f913.svg', '🤔': '/static/smileys/svg/1f914.svg', '🤕': '/static/smileys/svg/1f915.svg', '🤗': '/static/smileys/svg/1f917.svg', '🤢': '/static/smileys/svg/1f922.svg', '🤣': '/static/smileys/svg/1f923.svg', '🤤': '/static/smileys/svg/1f924.svg', '🤥': '/static/smileys/svg/1f925.svg', '🤧': '/static/smileys/svg/1f927.svg', '🤨': '/static/smileys/svg/1f928.svg', '🤩': '/static/smileys/svg/1f929.svg', '🤪': '/static/smileys/svg/1f92a.svg', '🤫': '/static/smileys/svg/1f92b.svg', '🤬': '/static/smileys/svg/1f92c.svg', '🤭': '/static/smileys/svg/1f92d.svg', '🤮': '/static/smileys/svg/1f92e.svg', '🤯': '/static/smileys/svg/1f92f.svg', '🥰': '/static/smileys/svg/1f970.svg', '🥳': '/static/smileys/svg/1f973.svg', '🥴': '/static/smileys/svg/1f974.svg', '🥵': '/static/smileys/svg/1f975.svg', '🥶': '/static/smileys/svg/1f976.svg', '🥺': '/static/smileys/svg/1f97a.svg', '☹': '/static/smileys/svg/2639.svg', '☺': '/static/smileys/svg/263a.svg' }, classes: 'smiley' } ================================================ FILE: packages/zmarkdown/config/mdast/iframes.js ================================================ module.exports = { 'www.dailymotion.com': { width: 480, height: 270, disabled: false, oembed: 'https://www.dailymotion.com/services/oembed' }, 'www.vimeo.com': { width: 500, height: 281, disabled: false, oembed: 'https://vimeo.com/api/oembed.json' }, 'vimeo.com': { width: 500, height: 281, disabled: false, oembed: 'https://vimeo.com/api/oembed.json' }, 'www.youtube.com': { width: 560, height: 315, disabled: false, oembed: 'https://www.youtube.com/oembed' }, 'youtube.com': { width: 560, height: 315, disabled: false, oembed: 'https://www.youtube.com/oembed' }, 'youtu.be': { width: 560, height: 315, disabled: false, oembed: 'https://www.youtube.com/oembed' }, 'soundcloud.com': { width: 500, height: 305, disabled: false, oembed: 'https://soundcloud.com/oembed' }, 'www.ina.fr': { width: 620, height: 349, disabled: false, replace: [ ['www.', 'player.'], ['/video/', '/player/embed/'] ], append: '/1/1b0bd203fbcd702f9bc9b10ac3d0fc21/560/315/1/148db8', removeFileName: true }, 'www.jsfiddle.net': { width: 560, height: 560, disabled: false, replace: [ ['http://', 'https://'] ], append: 'embedded/result,js,html,css/', match: /https?:\/\/(www\.)?jsfiddle\.net\/([\w\d]+\/[\w\d]+\/\d+\/?|[\w\d]+\/\d+\/?|[\w\d]+\/?)$/, thumbnail: { format: 'http://www.unixstickers.com/image/data/stickers' + '/jsfiddle/JSfiddle-blue-w-type.sh.png' } }, 'jsfiddle.net': { width: 560, height: 560, disabled: false, replace: [ ['http://', 'https://'] ], append: 'embedded/result,js,html,css/', match: /https?:\/\/(www\.)?jsfiddle\.net\/([\w\d]+\/[\w\d]+\/\d+\/?|[\w\d]+\/\d+\/?|[\w\d]+\/?)$/, thumbnail: { format: 'http://www.unixstickers.com/image/data/stickers' + '/jsfiddle/JSfiddle-blue-w-type.sh.png' } } } ================================================ FILE: packages/zmarkdown/config/mdast/images-download.js ================================================ module.exports = { disabled: true, defaultImagePath: 'black.png', defaultOn: { statusCode: true, invalidPath: true, mimeType: false, fileTooBig: false }, downloadDestination: './img/', maxlength: 1000000, dirSizeLimit: 10000000 } ================================================ FILE: packages/zmarkdown/config/mdast/index.js ================================================ module.exports = { parse: { gfm: true, commonmark: false, footnotes: true, pedantic: false, /* sets list of known blocks to nothing, otherwise

hey

would become <h3>hey</h3> instead of

<h3>hey</h3>

*/ blocks: [] }, alignBlocks: { center: 'align-center', right: 'align-right' }, captions: { external: { table: 'Table:', gridTable: 'Table:', code: 'Code:', math: 'Equation:', iframe: 'Video:' }, internal: { math: 'Equation:', inlineMath: 'Equation:', image: 'Figure:' } }, customBlocks: require('./custom-blocks'), disableTokenizers: {}, emoticons: require('./emoticons'), escapeEscaped: ['&'], footnotes: { inlineNotes: true }, headingShifter: 0, iframes: require('./iframes'), imagesDownload: require('./images-download'), math: { inlineMathDouble: true }, ping: { pingUsername: (_username) => true, userURL: (username) => `/@${username}` }, postProcessors: { detectQuizzes: true, getStats: true, limitDepth: 100, listLanguages: true, wrapIntroCcl: false }, textr: require('./textr') } ================================================ FILE: packages/zmarkdown/config/mdast/textr.js ================================================ const textrModules = { apostrophes: require('typographic-apostrophes'), apostrophesForPlurals: require('typographic-apostrophes-for-possessive-plurals'), copyright: require('typographic-copyright'), ellipses: require('typographic-ellipses'), emDashes: require('typographic-em-dashes'), enDashes: require('typographic-en-dashes'), registeredTrademark: require('typographic-registered-trademark'), singleSpaces: require('typographic-single-spaces'), trademark: require('typographic-trademark'), colon: require('typographic-colon/src'), emDash: require('typographic-em-dash/src'), exclamationMark: require('typographic-exclamation-mark/src'), guillemets: require('typographic-guillemets/src'), percent: require('typographic-percent/src'), permille: require('typographic-permille/src'), questionMark: require('typographic-question-mark/src'), semicolon: require('typographic-semicolon/src') } module.exports = { plugins: Object.values(textrModules), options: { locale: 'fr' } } ================================================ FILE: packages/zmarkdown/config/sanitize/index.js ================================================ const gh = require('hast-util-sanitize/lib/github') const katex = require('./katex') const merge = require('deepmerge') module.exports = merge.all([gh, katex, { tagNames: ['span', 'abbr', 'figure', 'figcaption', 'iframe', 'hidden-frame'], attributes: { a: ['ariaHidden', 'class', 'className'], abbr: ['title'], code: ['class', 'className'], details: ['class', 'className'], div: ['id', 'class', 'className'], h1: ['ariaHidden'], h2: ['ariaHidden'], h3: ['ariaHidden'], iframe: ['allowfullscreen', 'frameborder', 'height', 'src', 'width'], 'hidden-frame': ['allowfullscreen', 'frameborder', 'height', 'src', 'width'], img: ['class', 'className'], span: ['id', 'data-count', 'class', 'className'], summary: ['class', 'className'], td: ['colspan', 'colSpan', 'rowSpan', 'rowspan'], th: ['colspan', 'colSpan', 'rowSpan', 'rowspan'] }, protocols: { href: ['ftp', 'dav', 'sftp', 'magnet', 'tftp', 'view-source'], src: ['ftp', 'dav', 'sftp', 'tftp'] }, clobberPrefix: '', clobber: [] }]) ================================================ FILE: packages/zmarkdown/config/sanitize/katex.json ================================================ { "tagNames": [ "span", "abbr", "figure", "figcaption", "iframe", "math", "maction", "maligngroup", "malignmark", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mlongdiv", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mscarries", "mscarry", "msgroup", "mstack", "msline", "mspace", "msqrt", "msrow", "mstyle", "msub", "msubsup", "msup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "semantics", "annotation", "annotation-xml", "svg", "path" ], "attributes": { "annotation": [ "encoding" ], "mo": [ "accent", "dir", "fence", "form", "indentalign", "indentalignfirst", "indentalignlast", "indentshift", "indentshiftfirst", "indentshiftlast", "indenttarget", "largeop", "linebreak", "linebreakmultchar", "linebreakstyle", "lineleading", "lspace", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "rspace", "separator", "stretchy", "symmetric" ], "mover": [ "accent", "align" ], "munderover": [ "accent", "accentunder", "align" ], "munder": [ "accentunder", "align" ], "maction": [ "actiontype", "selection" ], "mtable": [ "align", "alignmentscope", "columnalign", "columnlines", "columnspacing", "columnwidth", "displaystyle", "equalcolumns", "equalrows", "frame", "framespacing", "groupalign", "minlabelspacing", "rowalign", "rowlines", "rowspacing", "side", "width" ], "mstack": [ "align", "charalign", "stackalign" ], "math": [ "altimg", "dir", "display", "overflow", "xmlns" ], "mfrac": [ "bevelled", "denomalign", "linethickness", "numalign" ], "mfenced": [ "close", "open", "separators" ], "mtd": [ "columnalign", "columnspan", "groupalign", "rowalign", "rowspan" ], "mtr": [ "columnalign", "groupalign", "rowalign" ], "mlabeledtr": [ "columnalign" ], "mscarry": [ "crossout" ], "mstyle": [ "decimalpoint", "displaystyle", "infixlinebreakstyle", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "mathcolor" ], "mpadded": [ "depth", "height", "lspace", "voffset", "width" ], "mi": [ "dir", "mathsize", "mathvariant" ], "mrow": [ "dir" ], "ms": [ "dir", "lquote", "mathsize", "mathvariant", "rquote" ], "mtext": [ "dir", "mathsize", "mathvariant" ], "malignmark": [ "edge" ], "maligngroup": [ "groupalign" ], "mglyph": [ "height", "src", "width" ], "mspace": [ "height", "indentalign", "indentalignfirst", "indentalignlast", "indentshift", "indentshiftfirst", "indentshiftlast", "indenttarget", "linebreak", "linebreakmultchar", "linebreakstyle", "lineleading", "width" ], "msline": [ "length", "position" ], "mscarries": [ "location", "position" ], "mlongdiv": [ "longdivstyle" ], "mn": [ "mathsize", "mathvariant" ], "menclose": [ "notation" ], "msgroup": [ "position", "shift" ], "msrow": [ "position" ], "mmultiscripts": [ "subscriptshift", "supscriptshift" ], "msub": [ "subscriptshift" ], "msubsup": [ "subscriptshift", "supscriptshift" ], "msup": [ "supscriptshift" ], "span": [ "className", "class", "style", "ariaHidden" ], "svg": [ "width", "height", "style", "viewBox", "preserveAspectRatio" ], "path": [ "d" ] } } ================================================ FILE: packages/zmarkdown/munin/zmd.sh ================================================ #!/bin/bash # ln -s /path/to/this/folder/zmd.sh /etc/munin/plugins/zmd_status # ln -s /path/to/this/folder/zmd.sh /etc/munin/plugins/zmd_memory # ln -s /path/to/this/folder/zmd.sh /etc/munin/plugins/zmd_cpu # ln -s /path/to/this/folder/zmd.sh /etc/munin/plugins/zmd_event_loop_lag # ln -s /path/to/this/folder/zmd.sh /etc/munin/plugins/zmd_avg_per_process # ln -s /path/to/this/folder/zmd.sh /etc/munin/plugins/zmd_avg_per_endpoint destination=`basename $0 | sed 's/^zmd_//g'` if [ "$1" = "config" ]; then wget -qO- http://localhost:27272/munin/config/$destination else wget -qO- http://localhost:27272/munin/$destination fi ================================================ FILE: packages/zmarkdown/package.json ================================================ { "name": "zmarkdown", "version": "12.1.0", "description": "HTTP server API providing fast and extensible markdown parser", "keywords": [ "markdown", "md", "http", "api" ], "author": "Victor Felder (https://draft.li)", "contributors": [ "Sébastien (AmarOk) Blin ", "François (artragis) Dambrine ", "Victor Felder (https://draft.li)", "Titouan (Stalone) S. " ], "homepage": "https://zestedesavoir.github.io/zmarkdown/", "license": "MIT", "main": "server/index.js", "repository": { "type": "git", "url": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/zmarkdown" }, "scripts": { "lint": "eslint .", "pretest": "npm run server && eslint .", "test": "cross-env DEST=/tmp jest", "posttest": "npm run stop", "watch": "jest --watch", "server": "pm2 start -f server/index.js -i 3 --max-memory-restart 150M", "stop": "pm2 kill", "watch:client": "cross-env NODE_ENV=development webpack --watch", "release": "webpack" }, "engines": { "node": ">=18", "npm": ">=9" }, "dependencies": { "@pm2/io": "^4.3.5", "@sentry/node": "^7.111.0", "body-parser": "^1.19.0", "clone": "^2.1.2", "cors": "^2.8.5", "deepmerge": "^4.2.2", "express": "^4.17.1", "hast-util-sanitize": "^3.0.0", "katex": "0.11.1", "md-attr-parser": "^1.3.0", "pm2": "^4.4.1", "process": "^0.11.10", "rebber": "file:../rebber", "rebber-plugins": "file:../rebber-plugins", "rehype-autolink-headings": "^4.0.0", "rehype-footnotes-title": "file:../rehype-footnotes-title", "rehype-highlight": "^4.0.0", "rehype-html-blocks": "file:../rehype-html-blocks", "rehype-katex": "3.0.0", "rehype-parse": "^7.0.1", "rehype-postfix-footnote-anchors": "file:../rehype-postfix-footnote-anchors", "rehype-sanitize": "^4.0.0", "rehype-slug": "^3.0.0", "rehype-stringify": "^8.0.0", "remark-abbr": "file:../remark-abbr", "remark-align": "file:../remark-align", "remark-captions": "file:../remark-captions", "remark-comments": "file:../remark-comments", "remark-custom-blocks": "file:../remark-custom-blocks", "remark-disable-tokenizers": "file:../remark-disable-tokenizers", "remark-emoticons": "file:../remark-emoticons", "remark-escape-escaped": "file:../remark-escape-escaped", "remark-fix-guillemets": "file:../remark-fix-guillemets", "remark-footnotes": "^2.0.0", "remark-grid-tables": "file:../remark-grid-tables", "remark-heading-shift": "file:../remark-heading-shift", "remark-heading-trailing-spaces": "file:../remark-heading-trailing-spaces", "remark-iframes": "file:../remark-iframes", "remark-images-download": "file:../remark-images-download", "remark-kbd": "file:../remark-kbd", "remark-math": "^3.0.1", "remark-numbered-footnotes": "file:../remark-numbered-footnotes", "remark-parse": "^8.0.3", "remark-ping": "file:../remark-ping", "remark-rehype": "^7.0.0", "remark-sub-super": "file:../remark-sub-super", "shortid": "^2.2.15", "textr": "^0.3.0", "typographic-apostrophes": "^1.1.1", "typographic-apostrophes-for-possessive-plurals": "^1.0.5", "typographic-colon": "file:../typographic-colon", "typographic-copyright": "^1.0.1", "typographic-ellipses": "^1.0.11", "typographic-em-dash": "file:../typographic-em-dash", "typographic-em-dashes": "^1.0.2", "typographic-en-dashes": "^1.0.1", "typographic-exclamation-mark": "file:../typographic-exclamation-mark", "typographic-guillemets": "file:../typographic-guillemets", "typographic-percent": "file:../typographic-percent", "typographic-permille": "file:../typographic-permille", "typographic-question-mark": "file:../typographic-question-mark", "typographic-registered-trademark": "^1.0.1", "typographic-semicolon": "file:../typographic-semicolon", "typographic-single-spaces": "^1.0.2", "typographic-trademark": "^1.0.1", "unified": "^9.2.0", "unist-util-inspect": "^6.0.0", "unist-util-visit": "^2.0.3" }, "devDependencies": { "assert": "^2.1.0", "axios": "^0.21.1", "cross-env": "^7.0.3", "dedent": "^0.7.0", "path-browserify": "^1.0.1", "url": "^0.11.3", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", "xtend": "^4.0.2" } } ================================================ FILE: packages/zmarkdown/plugins/remark-code-meta.js ================================================ // A word of caution: this plugin is non-standard // as regard to the MDAST specification. // Indeed, it transforms `meta` from `string?` // to `object` for code blocks. // The goal of this plugin is to be able to parse // meta information only once for HTML and LaTeX. const visit = require('unist-util-visit') const attrsParser = require('md-attr-parser') module.exports = parseCodeMeta function parseCodeMeta () { // Normalize ranges for hl_lines const rangeNormalize = range => { let normalizedRange = '' let previousNumber = -1 let currentNumber = '' // Insert a number or range in the list function insert () { if (previousNumber >= 0) { const currentInt = parseInt(currentNumber) const previousInt = parseInt(previousNumber) const minLineNumber = Math.min(currentInt, previousInt) const maxLineNumber = Math.max(currentInt, previousInt) normalizedRange += `${minLineNumber}-${maxLineNumber} ` } else { normalizedRange += `${parseInt(currentNumber)} ` } } for (let charIndex = 0; charIndex < range.length; charIndex++) { const currentChar = range[charIndex] const currentCharCode = range.charCodeAt(charIndex) // Match 0-9 if (currentCharCode >= 48 && currentCharCode <= 57) { currentNumber += currentChar } else if (currentChar === '-') { previousNumber = parseInt(currentNumber) currentNumber = '' } else if (currentChar === ' ' || currentChar === ',') { insert() previousNumber = -1 currentNumber = '' } } // Parse the last property if any if (currentNumber) insert() return normalizedRange.trim() } return (tree) => { visit(tree, 'code', mutateCodeNode) } function mutateCodeNode (node) { const attrs = attrsParser(node.meta || '').prop const linenostart = parseInt(attrs.linenostart) || 1 const hlLines = rangeNormalize(attrs.hl_lines || '') node.meta = { linenostart, hlLines } } } ================================================ FILE: packages/zmarkdown/plugins/remark-image-to-figure.js ================================================ const clone = require('clone') const visit = require('unist-util-visit') module.exports = plugin function plugin () { return function transformer (tree) { visit(tree, 'image', imageToFigure) } } // when a block only contains an image with an `alt`, turn this image into // a `figure` for which the caption is the content of the `alt` attribute function imageToFigure (img, index, parent) { if (parent.children.length === 1 && parent.type === 'paragraph') { if (!img.alt) return const figureCaptionNode = { type: 'figcaption', children: [ { type: 'text', value: img.alt } ], data: { hName: 'figcaption' } } parent.type = 'figure' parent.children = [clone(img), figureCaptionNode] parent.data = { hName: 'figure' } } } ================================================ FILE: packages/zmarkdown/plugins/remark-textr.js ================================================ const visit = require('unist-util-visit') const textr = require('textr') module.exports = plugin function plugin ({ plugins = [], options = {} } = {}) { let fn return function transformer (tree) { fn = plugins.reduce( (processor, p) => processor.use(typeof p === 'string' ? require(p) : p), textr(options) ) visit(tree, 'text', visitor) } function visitor (node) { node.value = fn(node.value) } } ================================================ FILE: packages/zmarkdown/postprocessors/html-footnotes-reorder.js ================================================ const visit = require('unist-util-visit') module.exports = () => (tree) => { // Find .footnotes > ol visit(tree, (node, _, parent) => { if (node.type === 'element' && node.tagName === 'ol' && parent.properties && parent.properties.className && parent.properties.className.includes('footnotes') ) { // Get all the footnotes const footnotes = node.children.filter(c => c.type === 'element' && c.tagName === 'li') // Reorder footnotes footnotes.sort((a, b) => { const aId = parseInt(a.properties.id.split('-')[1]) const bId = parseInt(b.properties.id.split('-')[1]) // We assume the two ids are never equals return aId > bId ? -1 : 1 }) // Interchange footnotes in HAST node.children.forEach((child, id) => { if (child.type === 'element' && child.tagName === 'li') { node.children[id] = footnotes.pop() } }) } }) } ================================================ FILE: packages/zmarkdown/postprocessors/html-iframe-wrappers.js ================================================ const visit = require('unist-util-visit') module.exports = (wrappers) => (tree) => { Object.keys(wrappers).forEach(nodeName => wrappers[nodeName].forEach(wrapper => { visit(tree, wrapper) })) } ================================================ FILE: packages/zmarkdown/postprocessors/html-lazy-load-images.js ================================================ const visit = require('unist-util-visit') module.exports = () => tree => { visit(tree, node => { if (node.type !== 'element' || node.tagName !== 'img' || !node.properties.src) return // Ignore smileys, which may be eagerly loaded if (node.properties.class && node.properties.class.includes('smiley')) return node.properties.loading = 'lazy' }) } ================================================ FILE: packages/zmarkdown/postprocessors/html-wrap-code.js ================================================ /** * This plugin adds a class precising the language * to code blocks (.hljs-code-div). */ const visit = require('unist-util-visit') module.exports = () => (tree) => { visit(tree, (node) => { // filter super code blocks if (node.type === 'element' && node.tagName === 'div' && node.properties && node.properties.className && node.properties.className.includes('hljs-code-div') ) { // get pre > code const preElem = node.children.filter(c => c.type === 'element' && c.tagName === 'pre') if (preElem.length === 1) { const classes = preElem[0].children[0].properties.className if (!classes) return const langClass = classes.filter(c => c.startsWith('language')) if (langClass.length === 0 || langClass[0].length < 9) return const language = langClass[0].substring(9) // rewrite class name if (langClass.length === 1 && language !== 'div') { node.properties.className.push(`hljs-code-${language}`) } } } }) } ================================================ FILE: packages/zmarkdown/postprocessors/md-detect-quizzes.js ================================================ const visit = require('unist-util-visit') module.exports = () => (tree, vfile) => { visit(tree, 'quizzCustomBlock', () => { vfile.data.hasQuizz = true }) } ================================================ FILE: packages/zmarkdown/postprocessors/md-get-stats.js ================================================ const visit = require('unist-util-visit') module.exports = () => (tree, vfile) => { let signs = 0 let words = 0 visit(tree, 'text', (node) => { let wordMatchFlag = false for (let i = 0; i < node.value.length; i++) { const currentCharCode = node.value.charCodeAt(i) if ( // a-z (currentCharCode >= 65 && currentCharCode <= 90) || // A-Z (currentCharCode >= 97 && currentCharCode <= 122) || // 0-9 (currentCharCode >= 48 && currentCharCode <= 57) ) { signs++ wordMatchFlag = true } else if (currentCharCode === 32) { if (wordMatchFlag) words++ signs++ wordMatchFlag = false } } if (wordMatchFlag) words++ }) vfile.data.stats = { signs, words } } ================================================ FILE: packages/zmarkdown/postprocessors/md-limit-depth.js ================================================ const visit = require('unist-util-visit') // get max depth of an element's children function getDepth (node) { let maxDepth = 0 if (node.children) { node.children.forEach(child => { const depth = getDepth(child) if (depth > maxDepth) { maxDepth = depth } }) } return maxDepth + 1 } module.exports = (maxDepth) => (tree, vfile) => { // limit AST depth to config.maxNesting visit(tree, 'root', (node) => { vfile.data.depth = getDepth(node) - 2 }) if (vfile.data.depth > maxDepth) { vfile.fail(`Markdown AST too complex: tree depth > ${maxDepth}`) } } ================================================ FILE: packages/zmarkdown/postprocessors/md-list-languages.js ================================================ const visit = require('unist-util-visit') module.exports = () => (tree, vfile) => { // if we don't have any headings, we add a flag to disable // the Table of Contents directly in the latex template vfile.data.disableToc = true visit(tree, 'heading', () => { vfile.data.disableToc = false }) // get a unique list of languages used in input const languages = new Set() visit(tree, 'code', (node) => { if (node.lang) languages.add(node.lang) }) vfile.data.languages = [...languages] } ================================================ FILE: packages/zmarkdown/postprocessors/md-wrap-intro-ccl.js ================================================ module.exports = (config) => tree => { tree.type = config.type tree.data = { level: config.level - 1 } } ================================================ FILE: packages/zmarkdown/public/README.md ================================================ # Info To update the css you should build the css with `gulp css` on [`zds-site`](https://github.com/zestedesavoir/zds-site). ================================================ FILE: packages/zmarkdown/public/css/main.css ================================================ @charset "UTF-8"; /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}.hljs{display:block;overflow-x:auto;padding:.5em;color:#000;background:#fff}.hljs-subst,.hljs-title{font-weight:400;color:#000}.hljs-comment,.hljs-quote{color:gray;font-style:italic}.hljs-meta{color:olive}.hljs-tag{background:#efefef}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-type{font-weight:700;color:navy}.hljs-attribute,.hljs-link,.hljs-number,.hljs-regexp{font-weight:700;color:#00f}.hljs-link,.hljs-number,.hljs-regexp{font-weight:400}.hljs-string{color:green;font-weight:700}.hljs-bullet,.hljs-formula,.hljs-symbol{color:#000;background:#d0eded;font-style:italic}.hljs-doctag{text-decoration:underline}.hljs-template-variable,.hljs-variable{color:#660e7a}.hljs-addition{background:#baeeba}.hljs-deletion{background:#ffc8bd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}html{font-size:62.5%;overflow-x:hidden;word-wrap:break-word}body,html{height:100%;width:100%}body{background:#f7f7f7;font-size:14px;font-size:1.4rem;line-height:1.7em}::selection{color:#fff;background:#084561}::-moz-selection{color:#fff;background:#084561}.flexpage-header::selection,.flexpage-header ::selection,.header-menu::selection,.header-menu ::selection,.header-right::selection,.header-right ::selection,.page-footer::selection,.page-footer ::selection,.taglist::selection,.taglist ::selection,.write-tutorial::selection,.write-tutorial ::selection{color:#084561;background:#fff}.flexpage-header::-moz-selection,.flexpage-header ::-moz-selection,.header-menu::-moz-selection,.header-menu ::-moz-selection,.header-right::-moz-selection,.header-right ::-moz-selection,.page-footer::-moz-selection,.page-footer ::-moz-selection,.taglist::-moz-selection,.taglist ::-moz-selection,.write-tutorial::-moz-selection,.write-tutorial ::-moz-selection{color:#084561;background:#fff}.flexpage-header input::selection,.header-menu input::selection,.header-right input::selection,.page-footer input::selection,.taglist input::selection,.write-tutorial input::selection{color:#fff;background:#084561}.flexpage-header input::-moz-selection,.header-menu input::-moz-selection,.header-right input::-moz-selection,.page-footer input::-moz-selection,.taglist input::-moz-selection,.write-tutorial input::-moz-selection{color:#fff;background:#084561}.page-container{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.page-container .main-container{background:#f7f7f7;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:0;flex-shrink:0;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-preferred-size:auto;flex-basis:auto}.page-container #accessibility,.page-container .cookies-eu-banner,.page-container .header-container,.page-container .page-footer{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0;-ms-flex-preferred-size:auto;flex-basis:auto}.content-container{margin-bottom:50px}img{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.old-browser-warning{margin:0;background:#ddd;color:#000;padding:.2em 0;text-align:center;position:fixed;z-index:11;width:100%}body.old-browser-warning-shown .page-container{position:relative;top:3rem}.a11y{display:block;width:0;height:0;text-indent:-9999px}nav ol,nav ul{list-style:none}@media only screen and (min-width:960px){body,html{height:100%}.wrapper{width:95%;margin:0 2.5%}}@media only screen and (max-width:959px){body{background:#222}body:not(.swipping) .mobile-menu,body:not(.swipping) .page-container{-webkit-transition-property:-webkit-transform;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transition-timing-function:ease;transition-timing-function:ease}body.swipping *{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}}.table-wrapper{max-width:100%;overflow:auto}table{margin:15px auto;border-top:1px solid #ddd;border-collapse:collapse}table thead{background:#ddd;color:#084561}table td,table th{text-align:left;padding:5px 15px 5px 7px;border-right:1px solid #ddd}table td:first-child,table th:first-child{border-left:1px solid #ddd}table td p,table th p{margin:0}table tbody tr{background:#fdfdfd;border-bottom:1px solid #ddd}table tbody tr:nth-child(odd){background:#f7f7f7}table.fullwidth{width:100%}.diff_delta{overflow-x:auto;width:100%;margin:15px 0}.diff_delta table.diff{font-family:Source Code Pro,monospace,serif;font-size:.9em;border:2px solid gray;margin:0}.diff_delta table.diff tr{line-height:1em;border-bottom:none}.diff_delta table.diff .diff_header{background-color:#e0e0e0;padding:5px}.diff_delta table.diff td.diff_header{text-align:right}.diff_delta table.diff .diff_next{display:none}.diff_add{background-color:#afa}.diff_chg{background-color:#fff8ab}.diff_sub{background-color:#faa}.content-container form,.modals-container form{width:100%}.content-container form.content-wrapper,.modals-container form.content-wrapper{width:calc(100% - 20px);margin:0 10px}.content-container form p,.modals-container form p{position:relative}.content-container .search-form,.modals-container .search-form{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.content-container .search-form input,.modals-container .search-form input{margin-right:10px;height:auto}.content-container fieldset,.modals-container fieldset{border-top:1px solid #ddd;border-bottom:3px solid #ddd;background:#efefef;padding:0 4%}.content-container fieldset legend,.modals-container fieldset legend{padding:0 10px;border-top:1px solid #ddd;border-bottom:3px solid #ddd;background:#efefef}.content-container label,.modals-container label{display:block;color:#555;height:30px;line-height:30px}.content-container label .asteriskField,.modals-container label .asteriskField{color:#c0392b;margin-left:4px}.content-container .small-content-wrapper .asteriskField,.modals-container .small-content-wrapper .asteriskField{display:none}.content-container .form-error,.modals-container .form-error{display:block;font-size:13px;color:#c0392b}.content-container input,.content-container textarea,.modals-container input,.modals-container textarea{border:1px solid #d2d5d6}.content-container input:focus,.content-container textarea:focus,.modals-container input:focus,.modals-container textarea:focus{outline-color:#999}.content-container input.field-error,.content-container input:invalid,.content-container textarea.field-error,.content-container textarea:invalid,.modals-container input.field-error,.modals-container input:invalid,.modals-container textarea.field-error,.modals-container textarea:invalid{border-color:#c0392b}.content-container input.field-error:focus,.content-container input:invalid:focus,.content-container textarea.field-error:focus,.content-container textarea:invalid:focus,.modals-container input.field-error:focus,.modals-container input:invalid:focus,.modals-container textarea.field-error:focus,.modals-container textarea:invalid:focus{outline-color:#c0392b}.content-container input .disabled,.content-container input[disabled],.content-container textarea .disabled,.content-container textarea[disabled],.modals-container input .disabled,.modals-container input[disabled],.modals-container textarea .disabled,.modals-container textarea[disabled]{background:#ddd!important;color:#555}.content-container .btn,.content-container button,.content-container input,.content-container textarea,.modals-container .btn,.modals-container button,.modals-container input,.modals-container textarea{-webkit-appearance:none;-webkit-transition:color .15s ease,background .15s ease;transition:color .15s ease,background .15s ease}.content-container input:not([type=submit]):not([type=reset]):not([type=radio]):not([type=checkbox]),.modals-container input:not([type=submit]):not([type=reset]):not([type=radio]):not([type=checkbox]){width:calc(98% - 2px);padding:0 1%}.content-container textarea,.modals-container textarea{width:calc(98% - 2px);padding:10px 1%;font-family:Source Code Pro,monospace,serif;line-height:normal}.content-container .btn,.content-container button,.content-container input,.modals-container .btn,.modals-container button,.modals-container input{display:block;height:30px}.content-container .btn.ico-after,.content-container button.ico-after,.content-container input.ico-after,.modals-container .btn.ico-after,.modals-container button.ico-after,.modals-container input.ico-after{padding-left:30px}.content-container .btn.ico-after:after,.content-container button.ico-after:after,.content-container input.ico-after:after,.modals-container .btn.ico-after:after,.modals-container button.ico-after:after,.modals-container input.ico-after:after{margin:12px 0 0 7px}.content-container .btn,.content-container [type=submit],.content-container button:not(.link),.modals-container .btn,.modals-container [type=submit],.modals-container button:not(.link){position:relative;height:40px;line-height:40px;cursor:pointer;color:#ddd;padding:0 15px;border:none;text-decoration:none;margin-left:1px;outline:none}.content-container .btn.submitted,.content-container [type=submit].submitted,.content-container button:not(.link).submitted,.modals-container .btn.submitted,.modals-container [type=submit].submitted,.modals-container button:not(.link).submitted{color:#555}.content-container .btn.submitted .line-loading,.content-container [type=submit].submitted .line-loading,.content-container button:not(.link).submitted .line-loading,.modals-container .btn.submitted .line-loading,.modals-container [type=submit].submitted .line-loading,.modals-container button:not(.link).submitted .line-loading{display:block;position:absolute;left:0;bottom:0;width:0;height:1px;background:#555;-webkit-animation:a linear 1s infinite;animation:a linear 1s infinite}.content-container .btn:not(.btn-holder),.content-container [type=submit]:not(.btn-holder),.content-container button:not(.link):not(.btn-holder),.modals-container .btn:not(.btn-holder),.modals-container [type=submit]:not(.btn-holder),.modals-container button:not(.link):not(.btn-holder){float:right}.content-container .btn-submit:not(.link),.content-container [type=submit]:not(.link),.modals-container .btn-submit:not(.link),.modals-container [type=submit]:not(.link){color:#fff;background:#48a200}.content-container .btn-submit:not(.link):not(.disabled):focus,.content-container .btn-submit:not(.link):not(.disabled):hover,.content-container .btn-submit:not(.link):not([disabled]):focus,.content-container .btn-submit:not(.link):not([disabled]):hover,.content-container [type=submit]:not(.link):not(.disabled):focus,.content-container [type=submit]:not(.link):not(.disabled):hover,.content-container [type=submit]:not(.link):not([disabled]):focus,.content-container [type=submit]:not(.link):not([disabled]):hover,.modals-container .btn-submit:not(.link):not(.disabled):focus,.modals-container .btn-submit:not(.link):not(.disabled):hover,.modals-container .btn-submit:not(.link):not([disabled]):focus,.modals-container .btn-submit:not(.link):not([disabled]):hover,.modals-container [type=submit]:not(.link):not(.disabled):focus,.modals-container [type=submit]:not(.link):not(.disabled):hover,.modals-container [type=submit]:not(.link):not([disabled]):focus,.modals-container [type=submit]:not(.link):not([disabled]):hover{background:#58c600}.content-container .btn-submit:not(.link).disabled.submitted,.content-container [type=submit]:not(.link).disabled.submitted,.modals-container .btn-submit:not(.link).disabled.submitted,.modals-container [type=submit]:not(.link).disabled.submitted{color:#48a200}.content-container .btn-submit:not(.link).disabled.submitted .line-loading,.content-container [type=submit]:not(.link).disabled.submitted .line-loading,.modals-container .btn-submit:not(.link).disabled.submitted .line-loading,.modals-container [type=submit]:not(.link).disabled.submitted .line-loading{background:#48a200}.content-container .btn-cancel:not(.link),.modals-container .btn-cancel:not(.link){background:#c0392b}.content-container .btn-cancel:not(.link):not(.disabled):focus,.content-container .btn-cancel:not(.link):not(.disabled):hover,.content-container .btn-cancel:not(.link):not([disabled]):focus,.content-container .btn-cancel:not(.link):not([disabled]):hover,.modals-container .btn-cancel:not(.link):not(.disabled):focus,.modals-container .btn-cancel:not(.link):not(.disabled):hover,.modals-container .btn-cancel:not(.link):not([disabled]):focus,.modals-container .btn-cancel:not(.link):not([disabled]):hover{background:#d34a3b}.content-container .btn-cancel:not(.link).disabled.submitted,.modals-container .btn-cancel:not(.link).disabled.submitted{color:#c0392b}.content-container .btn-cancel:not(.link).disabled.submitted .line-loading,.modals-container .btn-cancel:not(.link).disabled.submitted .line-loading{background:#c0392b}.content-container .btn-grey:not(.link),.modals-container .btn-grey:not(.link){background:#eee;color:#555}.content-container .btn-grey:not(.link):not(.disabled):focus,.content-container .btn-grey:not(.link):not(.disabled):hover,.content-container .btn-grey:not(.link):not([disabled]):focus,.content-container .btn-grey:not(.link):not([disabled]):hover,.modals-container .btn-grey:not(.link):not(.disabled):focus,.modals-container .btn-grey:not(.link):not(.disabled):hover,.modals-container .btn-grey:not(.link):not([disabled]):focus,.modals-container .btn-grey:not(.link):not([disabled]):hover{background:#ccc;color:#333}.content-container .btn-grey:not(.link).disabled.submitted,.modals-container .btn-grey:not(.link).disabled.submitted{color:#555}.content-container .btn-grey:not(.link).disabled.submitted .line-loading,.modals-container .btn-grey:not(.link).disabled.submitted .line-loading{background:#999}.content-container .disabled,.content-container [disabled],.modals-container .disabled,.modals-container [disabled]{cursor:default!important;background:#eee!important}.content-container .disabled:not(.submitted),.content-container [disabled]:not(.submitted),.modals-container .disabled:not(.submitted),.modals-container [disabled]:not(.submitted){color:#bbb!important}.content-container .btn-facebook:not(.link),.modals-container .btn-facebook:not(.link){background:#3b5998}.content-container .btn-facebook:not(.link):focus,.content-container .btn-facebook:not(.link):hover,.modals-container .btn-facebook:not(.link):focus,.modals-container .btn-facebook:not(.link):hover{background:#2d4373}.content-container .btn-twitter:not(.link),.modals-container .btn-twitter:not(.link){background:#4099ff}.content-container .btn-twitter:not(.link):focus,.content-container .btn-twitter:not(.link):hover,.modals-container .btn-twitter:not(.link):focus,.modals-container .btn-twitter:not(.link):hover{background:#0d7eff}.content-container .btn-google-plus:not(.link),.modals-container .btn-google-plus:not(.link){background:#d34836}.content-container .btn-google-plus:not(.link):focus,.content-container .btn-google-plus:not(.link):hover,.modals-container .btn-google-plus:not(.link):focus,.modals-container .btn-google-plus:not(.link):hover{background:#b03626}.content-container .btn-facebook,.content-container .btn-google-plus,.content-container .btn-twitter,.modals-container .btn-facebook,.modals-container .btn-google-plus,.modals-container .btn-twitter{width:50%;margin:0 auto;text-align:center}.content-container .btn-holder,.content-container .buttonHolder{margin-top:25px;min-height:40px}.main-container input[type=checkbox],.main-container input[type=radio],.modals-container input[type=checkbox],.modals-container input[type=radio]{float:left;margin-right:5px;height:15px;width:15px;border:1px solid #bbb;background:#fcfcfc;-webkit-transition:none;transition:none;position:relative}.main-container input[type=checkbox]:after,.main-container input[type=radio]:after,.modals-container input[type=checkbox]:after,.modals-container input[type=radio]:after{display:block;content:"";position:absolute;top:0;left:0;bottom:0;right:0;opacity:0;background-image:url(../images/sprite.png)}.main-container input[type=checkbox]:checked:after,.main-container input[type=radio]:checked:after,.modals-container input[type=checkbox]:checked:after,.modals-container input[type=radio]:checked:after{opacity:1}.main-container input[type=radio],.modals-container input[type=radio]{border-radius:50%}.main-container input[type=radio]:after,.modals-container input[type=radio]:after{background-position:-47px -272px}.main-container input[type=checkbox]:after,.modals-container input[type=checkbox]:after{background-position:-60px -272px}.main-container .checkbox,.main-container .radio,.modals-container .checkbox,.modals-container .radio{padding:10px 0}.main-container .checkbox input,.main-container .radio input,.modals-container .checkbox input,.modals-container .radio input{margin-top:8px}.main-container .controls .radio,.modals-container .controls .radio{padding-top:3px;padding-bottom:0}.main-container #div_id_helps .checkbox,.main-container .checkbox-new-content,.modals-container #div_id_helps .checkbox,.modals-container .checkbox-new-content{padding:0}@media only screen and (min-width:960px){.content-container form.content-wrapper,.modals-container form.content-wrapper{margin:0;width:100%}}@-webkit-keyframes a{0%{width:0;left:0;right:inherit}49%{left:0;right:inherit}50%{width:100%;left:inherit;right:0}to{left:inherit;right:0}}@keyframes a{0%{width:0;left:0;right:inherit}49%{left:0;right:inherit}50%{width:100%;left:inherit;right:0}to{left:inherit;right:0}}body,button,html,input,select,textarea{font-family:Source Sans Pro,Segoe UI,Trebuchet MS,Helvetica,Helvetica Neue,Arial,sans-serif;color:#222}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}.link,a{color:#1088bf;-webkit-transition:color .15s ease,-webkit-text-decoration .15s ease;transition:color .15s ease,-webkit-text-decoration .15s ease;transition:color .15s ease,text-decoration .15s ease;transition:color .15s ease,text-decoration .15s ease,-webkit-text-decoration .15s ease}.link:hover,a:hover{color:#d68807;text-decoration:none}.page-container .header-container .staff-only a{color:#f8ad32}button.link{display:inline-block;background:none;border:none;text-decoration:underline}.ico{background-repeat:no-repeat;background-image:url(../images/sprite.png)}.custom-block-body,.ico-after{position:relative}.custom-block-body:after,.ico-after:after{content:" ";display:block;position:absolute;top:0;left:0;width:16px;height:16px;background-repeat:no-repeat;background-image:url(../images/sprite.png)}.custom-block-body.alert:after,.custom-block-body.ico-alert:after,.ico-after.alert:after,.ico-after.ico-alert:after{background-position:-292px 0}.custom-block-body.alert.blue:after,.custom-block-body.ico-alert.blue:after,.ico-after.alert.blue:after,.ico-after.ico-alert.blue:after{background-position:-80px -232px}.custom-block-body.alert.light:after,.custom-block-body.ico-alert.light:after,.ico-after.alert.light:after,.ico-after.ico-alert.light:after{background-position:-260px -80px}.custom-block-body.arrow-left:after,.ico-after.arrow-left:after{background-position:-212px 0}.custom-block-body.arrow-left.blue:after,.ico-after.arrow-left.blue:after{background-position:-308px -80px}.custom-block-body.arrow-left.light:after,.ico-after.arrow-left.light:after{background-position:-196px 0}.custom-block-body.arrow-right:after,.custom-block-body.offline:after,.ico-after.arrow-right:after,.ico-after.offline:after{background-position:-64px -232px}.custom-block-body.arrow-right.blue:after,.custom-block-body.offline.blue:after,.ico-after.arrow-right.blue:after,.ico-after.offline.blue:after{background-position:-244px -80px}.custom-block-body.arrow-right.light:after,.custom-block-body.offline.light:after,.ico-after.arrow-right.light:after,.ico-after.offline.light:after{background-position:-260px -40px}.custom-block-body.beta:after,.ico-after.beta:after{background-position:-276px -80px}.custom-block-body.beta.blue:after,.ico-after.beta.blue:after{background-position:-160px -232px}.custom-block-body.beta.light:after,.ico-after.beta.light:after{background-position:-276px -40px}.custom-block-body.cite:after,.ico-after.cite:after{background-position:-164px 0}.custom-block-body.cite.blue:after,.ico-after.cite.blue:after{background-position:-126px -112px}.custom-block-body.cite.light:after,.ico-after.cite.light:after{background-position:-142px -112px}.custom-block-body.cross:after,.ico-after.cross:after{background-position:-180px -80px}.custom-block-body.cross.blue:after,.ico-after.cross.blue:after{background-position:-164px -40px}.custom-block-body.cross.red:after,.ico-after.cross.red:after{background-position:-180px 0}.custom-block-body.cross.light:after,.ico-after.cross.light:after{background-position:-164px -80px}.custom-block-body.cross.white:after,.ico-after.cross.white:after{background-position:-180px -40px}.custom-block-body.download:after,.ico-after.download:after{background-position:-80px -152px}.custom-block-body.download.blue:after,.ico-after.download.blue:after{background-position:-48px -152px}.custom-block-body.download.light:after,.ico-after.download.light:after{background-position:-64px -152px}.custom-block-body.downvote:after,.ico-after.downvote:after{background-position:-292px -80px}.custom-block-body.downvote.voted:after,.ico-after.downvote.voted:after{background-position:-292px -40px}.custom-block-body.edit:after,.ico-after.edit:after{background-position:-128px -152px}.custom-block-body.edit.blue:after,.ico-after.edit.blue:after{background-position:-96px -152px}.custom-block-body.edit.light:after,.ico-after.edit.light:after{background-position:-112px -152px}.custom-block-body.email:after,.ico-after.email:after{background-position:-176px -152px}.custom-block-body.email.blue:after,.ico-after.email.blue:after{background-position:-144px -152px}.custom-block-body.email.light:after,.ico-after.email.light:after{background-position:-160px -152px}.custom-block-body.diaspora:after,.ico-after.diaspora:after{background-position:-32px -152px}.custom-block-body.diaspora.blue:after,.ico-after.diaspora.blue:after{background-position:0 -152px}.custom-block-body.diaspora.light:after,.ico-after.diaspora.light:after{background-position:-16px -152px}.custom-block-body.facebook:after,.ico-after.facebook:after{background-position:-196px -120px}.custom-block-body.facebook.blue:after,.ico-after.facebook.blue:after{background-position:-196px -40px}.custom-block-body.facebook.light:after,.ico-after.facebook.light:after{background-position:-196px -80px}.custom-block-body.foursquare:after,.ico-after.foursquare:after{background-position:-212px -120px}.custom-block-body.foursquare.blue:after,.ico-after.foursquare.blue:after{background-position:-212px -40px}.custom-block-body.foursquare.light:after,.ico-after.foursquare.light:after{background-position:-212px -80px}.custom-block-body.gear:after,.ico-after.gear:after{background-position:-228px -80px}.custom-block-body.gear.blue:after,.ico-after.gear.blue:after{background-position:-228px 0}.custom-block-body.gear.light:after,.ico-after.gear.light:after{background-position:-228px -40px}.custom-block-body.github:after,.ico-after.github:after{background-position:-16px -192px}.custom-block-body.github.blue:after,.ico-after.github.blue:after{background-position:-228px -120px}.custom-block-body.github.light:after,.ico-after.github.light:after{background-position:0 -192px}.custom-block-body.google-plus:after,.ico-after.google-plus:after{background-position:-64px -192px}.custom-block-body.google-plus.blue:after,.ico-after.google-plus.blue:after{background-position:-32px -192px}.custom-block-body.google-plus.light:after,.ico-after.google-plus.light:after{background-position:-48px -192px}.custom-block-body.help:after,.ico-after.help:after{background-position:-112px -192px}.custom-block-body.help.blue:after,.ico-after.help.blue:after{background-position:-80px -192px}.custom-block-body.help.light:after,.ico-after.help.light:after{background-position:-96px -192px}.custom-block-body.hide:after,.ico-after.hide:after{background-position:-160px -192px}.custom-block-body.hide.blue:after,.ico-after.hide.blue:after{background-position:-128px -192px}.custom-block-body.hide.light:after,.ico-after.hide.light:after{background-position:-144px -192px}.custom-block-body.history:after,.ico-after.history:after{background-position:-208px -192px}.custom-block-body.history.blue:after,.ico-after.history.blue:after{background-position:-176px -192px}.custom-block-body.history.light:after,.ico-after.history.light:after{background-position:-192px -192px}.custom-block-body.import:after,.ico-after.import:after{background-position:-244px -40px}.custom-block-body.import.blue:after,.ico-after.import.blue:after{background-position:-224px -192px}.custom-block-body.import.light:after,.ico-after.import.light:after{background-position:-244px 0}.custom-block-body.lock:after,.ico-after.lock:after{background-position:-260px 0}.custom-block-body.lock.blue:after,.ico-after.lock.blue:after{background-position:-244px -120px}.custom-block-body.lock.light:after,.ico-after.lock.light:after{background-position:-244px -160px}.custom-block-body.more:after,.ico-after.more:after{background-position:0 -232px}.custom-block-body.more.blue:after,.ico-after.more.blue:after{background-position:-260px -120px}.custom-block-body.more.light:after,.ico-after.more.light:after{background-position:-260px -160px}.custom-block-body.move:after,.ico-after.move:after{background-position:-48px -232px}.custom-block-body.move.blue:after,.ico-after.move.blue:after{background-position:-16px -232px}.custom-block-body.move.light:after,.ico-after.move.light:after{background-position:-32px -232px}.custom-block-body.pin:after,.ico-after.pin:after{background-position:-128px -232px}.custom-block-body.pin.blue:after,.ico-after.pin.blue:after{background-position:-96px -232px}.custom-block-body.pin.light:after,.ico-after.pin.light:after{background-position:-112px -232px}.custom-block-body.rss:after,.ico-after.rss:after{background-position:-240px -232px}.custom-block-body.rss.blue:after,.ico-after.rss.blue:after{background-position:-192px -232px}.custom-block-body.rss.orange:after,.ico-after.rss.orange:after{background-position:-224px -232px}.custom-block-body.rss.light:after,.ico-after.rss.light:after{background-position:-208px -232px}.custom-block-body.star:after,.ico-after.star:after{background-position:-276px -200px}.custom-block-body.star.yellow:after,.ico-after.star.yellow:after{background-position:-276px -160px}.custom-block-body.star.blue:after,.ico-after.star.blue:after{background-position:-276px 0}.custom-block-body.star.light:after,.ico-after.star.light:after{background-position:-276px -120px}.custom-block-body.tick:after,.ico-after.tick:after{background-position:-308px -40px}.custom-block-body.tick.green:after,.ico-after.tick.green:after{background-position:-292px -200px}.custom-block-body.tick.light:after,.ico-after.tick.light:after{background-position:-308px 0}.custom-block-body.twitter:after,.ico-after.twitter:after{background-position:-308px -200px}.custom-block-body.twitter.blue:after,.ico-after.twitter.blue:after{background-position:-308px -120px}.custom-block-body.twitter.light:after,.ico-after.twitter.light:after{background-position:-308px -160px}.custom-block-body.unread:after,.ico-after.unread:after{background-position:-292px -240px}.custom-block-body.upvote:after,.ico-after.upvote:after{background-position:-292px -160px}.custom-block-body.upvote.voted:after,.ico-after.upvote.voted:after{background-position:-292px -120px}.custom-block-body.online:after,.custom-block-body.view:after,.ico-after.online:after,.ico-after.view:after{background-position:-110px -112px}.custom-block-body.online.blue:after,.custom-block-body.view.blue:after,.ico-after.online.blue:after,.ico-after.view.blue:after{background-position:-176px -232px}.custom-block-body.online.light:after,.custom-block-body.view.light:after,.ico-after.online.light:after,.ico-after.view.light:after{background-position:-144px -232px}h3:hover span.icon-link:after,h4:hover span.icon-link:after,h5:hover span.icon-link:after,h6:hover span.icon-link:after{content:" ";display:inline-block;background-image:url(../images/sprite.png);background-position:-308px -240px;width:16px;height:16px}.ir{background-color:transparent;border:0;overflow:hidden;*text-indent:-9999px}.ir:before{content:"";display:block;width:0;height:150%}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:after,.clearfix:before{content:" ";display:table}.clearfix:after{clear:both}.clearfix{*zoom:1}hr.clearfix{clear:both;height:0;border:none}.unstyled-list{list-style:none;padding-left:0}.screen,.wide{display:none}@media only screen and (min-width:1140px){.wide{display:inline}table .wide{display:table-cell}}@media only screen and (min-width:960px){.screen{display:inline}}.header-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.header-container .sub-header,.header-container header{display:-webkit-box;display:-ms-flexbox;display:flex}@media only screen and (min-width:960px){.header-container .sub-header,.header-container header{padding:0 2rem}.header-container .sub-header .header-right .dropdown,.header-container header .header-right .dropdown{right:2rem}}.header-container header{background:#084561;border-bottom:3px solid #f8ad32}.header-container header a,.header-container header button{text-decoration:none;color:#fff;-webkit-transition-property:background;transition-property:background;-webkit-transition-duration:.15s;transition-duration:.15s}.header-container header a:focus,.header-container header button:focus{outline:none}.header-container .header-menu{height:60px;-ms-flex-preferred-size:auto;flex-basis:auto;display:-webkit-box;display:-ms-flexbox;display:flex}.header-container .header-menu-list{margin:0;padding:0;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.header-container .header-menu-list>li{display:block;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:1;flex-shrink:1;-ms-flex-preferred-size:auto;flex-basis:auto}.header-container .header-menu-list>li>a{padding:0 2.5rem;display:block;position:relative;text-align:center;line-height:60px;font-size:1.6rem;text-transform:uppercase;text-shadow:rgba(0,0,0,.75) 0 0 3px}.header-container .header-menu-list>li>a.active,.header-container .header-menu-list>li>a:focus,.header-container .header-menu-list>li>a:hover{background:#396a81}.header-container .header-menu-list>li>a.active .arrow:after,.header-container .header-menu-list>li>a:focus .arrow:after,.header-container .header-menu-list>li>a:hover .arrow:after{border-top:6px solid #fff}.header-container .header-menu-list>li>a.has-dropdown{padding-right:1.5rem}.header-container .header-menu-list>li>a.has-dropdown .arrow{display:inline-block}.header-container .header-menu-list>li>a.current:before{content:" ";display:block;position:absolute;bottom:0;left:0;right:0;height:2px;border-radius:2px 2px 0 0;background-color:#f8ad32}.header-container .header-menu-list>li>a.current.active:before{height:0}.header-container .header-logo{text-align:center;margin:0;padding:0}.header-container .header-logo-link{display:block;margin:0;text-indent:-9999px;width:250px;height:60px;background:url(../images/logo.png) no-repeat 50%;background-size:240px auto}@media only screen and (min-width:960px){.header-container .header-logo-link:focus,.header-container .header-logo-link:hover{background-color:#396a81}}.header-container .sub-header{background:#eee}.header-container .header-mobile-page-title{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin:0 .5em;line-height:50px;font-size:1.7rem}@media only screen and (min-width:960px){.header-container .header-mobile-page-title{display:none}}.header-container .arrow{display:none;width:20px;height:9px;position:relative}.header-container .arrow:after{content:"";display:block;position:absolute;right:0;height:0;width:0;border:6px solid transparent;border-top:6px solid hsla(0,0%,100%,.7);border-left:6px inset transparent}.logbox .notifs-links,.logbox>a{background:hsla(0,0%,100%,.05)}.logbox .notifs-links{display:-webkit-box;display:-ms-flexbox;display:flex}.logbox .notifs-links .ico-link{-webkit-box-flex:0;-ms-flex:0;flex:0;display:block;position:relative;width:60px;height:60px;line-height:60px}.logbox .notifs-links .ico-link .notif-count{display:block;position:absolute;z-index:1;top:50%;right:50%;margin:-20px -22px 0 0;padding:0 5px;height:16px;line-height:14px;background:#c0392b;border-radius:16px}.logbox .notifs-links .ico-link .notif-text{display:block;position:absolute;text-indent:-9999px;height:22px;width:22px;top:50%;left:50%;margin:-11px 0 0 -11px}.logbox .notifs-links .ico-link .notif-text.ico-messages{background-position:-22px -112px}.logbox .notifs-links .ico-link .notif-text.ico-notifs{background-position:-142px 0}.logbox .notifs-links .ico-link .notif-text.ico-alerts{background-position:-120px 0}.logbox .notifs-links .ico-link .notif-text.ico-params{background-position:-142px -40px}.logbox .notifs-links .ico-link.active,.logbox .notifs-links .ico-link:focus,.logbox .notifs-links .ico-link:hover{background:#396a81}.logbox .dropdown{overflow:hidden}.logbox .dropdown .dropdown-title{display:block;width:100%;height:35px;line-height:37px;text-align:center;border-bottom:1px solid #274a5a;background-color:#396a81}.logbox .dropdown,.logbox .dropdown .dropdown-list{margin:0;padding:0;list-style:none;background-color:#19526c}.logbox .dropdown .dropdown-list li,.logbox .dropdown li{display:block;width:100%;height:60px}.logbox .dropdown .dropdown-list li a,.logbox .dropdown li a{display:block;overflow:hidden;position:relative;height:100%}.logbox .dropdown .dropdown-list li a,.logbox .dropdown .dropdown-list li a:focus,.logbox .dropdown .dropdown-list li a:hover,.logbox .dropdown li a,.logbox .dropdown li a:focus,.logbox .dropdown li a:hover{opacity:1;-webkit-transition-property:opacity,background-color;transition-property:opacity,background-color}.logbox .dropdown .dropdown-list li a:focus,.logbox .dropdown .dropdown-list li a:hover,.logbox .dropdown li a:focus,.logbox .dropdown li a:hover{background-color:#396a81}.logbox .dropdown .dropdown-list li a:focus .username,.logbox .dropdown .dropdown-list li a:hover .username,.logbox .dropdown li a:focus .username,.logbox .dropdown li a:hover .username{text-shadow:rgba(0,0,0,.5) 0 0 5px}.logbox .dropdown .dropdown-list li a:focus .date,.logbox .dropdown .dropdown-list li a:hover .date,.logbox .dropdown li a:focus .date,.logbox .dropdown li a:hover .date{color:#95d7f5}.logbox .dropdown .dropdown-list li .avatar,.logbox .dropdown li .avatar{float:left;height:30px;width:30px}.logbox .dropdown .dropdown-list li .username,.logbox .dropdown li .username{display:block;float:left;margin:4px 0 0 7px;color:#95d7f5;width:50%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.logbox .dropdown .dropdown-list li .date,.logbox .dropdown li .date{color:#5196b6;float:right;padding:4px 10px 0 0;-webkit-transition-property:color;transition-property:color}.logbox .dropdown .dropdown-list li .topic,.logbox .dropdown li .topic{display:block;position:absolute;bottom:0;left:0;overflow:hidden;height:31px;padding:4px 7px 2px;text-overflow:ellipsis;white-space:nowrap;width:95%;width:calc(100% - 14px)}.logbox .dropdown .dropdown-list li:nth-child(odd),.logbox .dropdown .dropdown-list li:nth-child(odd) form button,.logbox .dropdown li:nth-child(odd),.logbox .dropdown li:nth-child(odd) form button{background-color:#084561}.logbox .dropdown .dropdown-pm{text-align:left;padding-left:15px}.logbox .dropdown .dropdown-pm .ico-after{float:right;padding-right:30px;top:9px}.logbox .dropdown .dropdown-pm .pm-new.white:after{background-position:-142px -80px;width:17px;height:16px}.logbox .my-account{display:block;height:60px;width:60px;float:right}.logbox .my-account .username{display:none}.logbox .my-account .avatar{background:#396a81}.logbox .dropdown.my-account-dropdown a,.logbox .dropdown.my-account-dropdown button{padding-left:10px}.logbox .dropdown.my-account-dropdown button{width:100%;height:30px;line-height:28px;background:transparent;text-align:left;border:0}.logbox .dropdown.my-account-dropdown button:focus,.logbox .dropdown.my-account-dropdown button:hover{background:#396a81}.logbox.unlogged a{display:block;width:120px;text-align:center;float:left;line-height:60px;height:60px}.logbox.unlogged a:focus,.logbox.unlogged a:hover{background-color:#396a81}@media only screen and (max-width:959px){.header-container .header-logo{width:40px;height:50px;margin-left:50px}.header-container .header-logo-link{background-image:url(../images/logo-mobile.png)!important;background-size:100%;width:100%;height:100%}.header-container header .header-menu{display:none}.header-container .logbox .notifs-links{background:none;width:100%}.header-container .logbox .notifs-links .ico-link{height:50px;width:50px}.header-container .logbox .dropdown{top:50px}.header-container .logbox .dropdown.my-account-dropdown .dropdown-list{bottom:0}.header-container .logbox .dropdown.my-account-dropdown .dropdown-list li{height:45px;line-height:45px}.header-container .logbox.unlogged{font-size:13px;font-size:1.3rem}.header-container .logbox.unlogged a{line-height:30px;height:30px;margin:10px 0;width:74px;margin-right:1px}}@media only screen and (min-width:960px){.header-container{z-index:1;position:relative;-webkit-box-shadow:0 0 4px rgba(0,0,0,.3);box-shadow:0 0 4px rgba(0,0,0,.3)}.header-container header{background-image:-webkit-gradient(linear,left top,right top,color-stop(20%,transparent),color-stop(40%,hsla(0,0%,100%,.07)),color-stop(60%,hsla(0,0%,100%,.07)),color-stop(80%,transparent));background-image:linear-gradient(90deg,transparent 20%,hsla(0,0%,100%,.07) 40%,hsla(0,0%,100%,.07) 60%,transparent 80%)}.header-container .header-menu{-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.header-logo{text-align:left;width:250px}.dropdown{top:60px}.logbox .dropdown.my-account-dropdown ul li{height:30px;line-height:30px}.logbox .dropdown.my-account-dropdown ul li button{cursor:pointer}.lt-ie9 .dropdown{top:90px}.header-logo,.header-right{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0}.header-right{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}}.dropdown{display:none;position:absolute;text-align:left;top:50px;left:0;right:0;background-color:#396a81;margin:0;padding:10px 2.5%;font-size:14px;font-size:1.4rem;border-bottom:3px solid #f8ad32;z-index:8}.dropdown .dropdown-title{color:#fff}.dropdown.header-menu-dropdown .dropdown-list>li:first-child:last-child,.dropdown.header-menu-dropdown .dropdown-list>li:first-child:last-child ~ li{width:100%}.dropdown.header-menu-dropdown .dropdown-list>li:first-child:nth-last-child(2),.dropdown.header-menu-dropdown .dropdown-list>li:first-child:nth-last-child(2)~li{width:50%}.dropdown.header-menu-dropdown .dropdown-list>li:first-child:nth-last-child(3),.dropdown.header-menu-dropdown .dropdown-list>li:first-child:nth-last-child(3)~li{width:33.33333%}.dropdown.header-menu-dropdown .dropdown-list>li:first-child:nth-last-child(4),.dropdown.header-menu-dropdown .dropdown-list>li:first-child:nth-last-child(4)~li{width:25%}.dropdown .dropdown-list{width:100%;padding:0;margin-top:15px}.dropdown .dropdown-list>li{width:20%;float:left}.dropdown .dropdown-list>li.dropdown-empty-message{color:hsla(0,0%,100%,.5);text-align:center;line-height:60px;background:none!important}.dropdown .dropdown-list>li ul{margin:0 0 10px;padding:0}.dropdown .dropdown-list>li ul li{position:relative}.dropdown .dropdown-list>li ul li a{display:block;width:95%;min-height:25px;line-height:25px;color:#95d7f5;overflow:hidden;-webkit-transition:padding-left .15s ease,background-color .15s ease;transition:padding-left .15s ease,background-color .15s ease}.dropdown .dropdown-list>li ul li a:focus,.dropdown .dropdown-list>li ul li a:hover{padding-left:3%;background-color:rgba(0,0,0,.3)}.dropdown .dropdown-link-all{display:block;clear:both;text-align:center;height:30px;line-height:30px;border-top:1px solid #274a5a;background-color:#396a81;-webkit-transition-property:color,background-color;transition-property:color,background-color}.dropdown .dropdown-link-all:first-child{border-top:0!important;border-bottom:1px solid #274a5a}.dropdown .dropdown-link-all:focus,.dropdown .dropdown-link-all:hover{color:#95d7f5;background-color:#274a5a;border-top:1px solid #396a81}.active+.dropdown{display:block}@media only screen and (min-width:760px){.dropdown{-webkit-box-shadow:0 5px 7px rgba(0,0,0,.3);box-shadow:0 5px 7px rgba(0,0,0,.3)}.header-right .dropdown{width:350px;left:auto;padding:0}.header-right .dropdown .dropdown-list{max-height:390px;overflow-x:hidden;overflow-y:auto}.header-right .dropdown .dropdown-list::-webkit-scrollbar{width:10px;height:10px}.header-right .dropdown .dropdown-list::-webkit-scrollbar-track{background-color:#06354a}.header-right .dropdown .dropdown-list::-webkit-scrollbar-thumb{background-color:#396a81;border:1px solid #06354a;-webkit-transition:background-color .15s ease;transition:background-color .15s ease}.header-right .dropdown .dropdown-list::-webkit-scrollbar-thumb:hover{background-color:#5196b6}.header-right .dropdown .dropdown-list::-webkit-scrollbar-thumb:active{background-color:#71b4d3}.header-right .dropdown.my-account-dropdown{width:350px}}@media only screen and (max-width:759px){html.dropdown-active{overflow:hidden}html.dropdown-active .page-container{width:100%}html.dropdown-active .main-container{display:none}.header-menu-dropdown{display:none!important}.dropdown{width:100%;top:180px;bottom:0;border-bottom:none}.dropdown .dropdown-list{overflow:auto;position:absolute;top:36px;bottom:50px}.dropdown .dropdown-link-all{position:absolute;left:0;right:0;bottom:0;height:50px;line-height:50px}}@media only screen and (min-width:960px){.dropdown{top:60px}}.search-form{margin-bottom:30px}.search{display:-webkit-box;display:-ms-flexbox;display:flex;position:relative}.search form button,.search form input{float:left;border:none;background:hsla(0,0%,100%,.25);height:40px;-webkit-transition-property:background;transition-property:background;-webkit-transition-duration:.15s;transition-duration:.15s}.search form button:focus,.search form button:hover,.search form input:focus,.search form input:hover{background-color:#fff}.search form button:focus,.search form input:focus{outline-color:#f8ad32}.search form input{height:30px;padding:5px 3%;width:70%}.search form button{width:12%;text-indent:-9999px}.search form button.disabled{opacity:.5;background:transparent;cursor:default}.search form button:after{display:block;content:" ";position:absolute;top:12px;left:50%;margin-left:-8px;height:17px;width:17px;background-position:-256px -232px}.search .search-more{display:block;float:left;height:40px;font-family:Arial,sans-serif;line-height:40px;width:12%;text-align:center;font-weight:700;text-decoration:none;font-size:24px;background:#fff;color:#084561;-webkit-transition:background .15s ease;transition:background .15s ease}.search .search-more:focus,.search .search-more:hover{background:#f8ad32;color:#fff;outline:none}.search .search-more:before{content:"+"}@media only screen and (min-width:960px){.search:before{content:" ";display:block;position:absolute;left:-20px;height:30px;width:20px;background:-webkit-gradient(linear,right top,left top,from(rgba(0,0,0,.03)),to(transparent));background:linear-gradient(270deg,rgba(0,0,0,.03),transparent)}.search form input{padding:6px 10px;height:30px;width:180px}.search form button{height:30px;line-height:30px;width:30px}.search form button:after{top:7px}.search .search-more{width:30px;height:30px;line-height:30px}}#accessibility{list-style:none;margin:0;padding:0 2.5%;background:#062e41;overflow:hidden;height:0}#accessibility.focused{height:auto}#accessibility li{display:inline;margin:0;padding:0}#accessibility li a{display:inline-block;padding:0 7px;color:#eee}#accessibility li a:focus,#accessibility li a:hover{color:#084561;background-color:#fff;text-decoration:none}#cookies-eu-banner{padding:0 3%;background:#062e41;display:none}#cookies-eu-banner #cookies-eu-reject,#cookies-eu-banner div{display:inline-block;margin:0;padding:7px 0;color:#eee;line-height:23px}#cookies-eu-banner #cookies-eu-reject{background:none;border:none;text-decoration:underline}#cookies-eu-banner #cookies-eu-reject:focus,#cookies-eu-banner #cookies-eu-reject:hover{text-decoration:none}#cookies-eu-banner #cookies-eu-accept,#cookies-eu-banner #cookies-eu-more{display:inline-block;margin-top:3px;padding:4px 15px;text-decoration:none;-webkit-transition:background .15s,color .15s;transition:background .15s,color .15s}#cookies-eu-banner #cookies-eu-more{margin-left:15px;color:#eee;background:#084561}#cookies-eu-banner #cookies-eu-more:focus,#cookies-eu-banner #cookies-eu-more:hover{color:#084561;background:#eee}#cookies-eu-banner #cookies-eu-accept{border:none;color:#084561;background:#eee}#cookies-eu-banner #cookies-eu-accept:focus,#cookies-eu-banner #cookies-eu-accept:hover{color:#eee;background:#084561}@media only screen and (max-width:759px){#cookies-eu-banner{position:absolute;top:50px;right:0;bottom:0;left:0;z-index:4}#cookies-eu-banner div{margin-top:40px;padding:0 5px}#cookies-eu-banner #cookies-eu-accept,#cookies-eu-banner #cookies-eu-more{display:block;width:100%;height:40px;padding:0!important;margin:15px 0 0!important;text-align:center}#cookies-eu-banner #cookies-eu-more{margin-top:40px!important;line-height:40px}}.main .sidebar{padding:0 0 10px;background:#f0f0f0;border-bottom:1px solid #fff;color:#424242;width:105%;margin:0 0 0 -2.7%}.main .sidebar .new-btn{display:block;height:40px;padding-left:11.5%;text-decoration:none;text-indent:25px;line-height:40px;font-size:16px;font-size:1.6rem;position:relative;color:#1088bf;-webkit-transition:background-color .15s ease;transition:background-color .15s ease}.main .sidebar .new-btn:first-child{margin-top:31px}.main .sidebar .new-btn:focus,.main .sidebar .new-btn:hover{background-color:#fff}.main .sidebar .new-btn:after{top:12px;left:11.5%}.main .sidebar h3,.main .sidebar h4{font-weight:400;margin:0;padding:0}.main .sidebar h3{font-size:18px;font-size:1.8rem;line-height:38px;line-height:3.8rem;color:#084561;border-bottom:1px solid #f8ad32;margin-top:30px}.main .sidebar h4{padding-top:20px;font-size:17px;font-size:1.7rem}.main .sidebar h4 a{text-decoration:none;color:#424242}.main .sidebar.accordeon h4{cursor:pointer}.main .sidebar h4[data-num]{position:relative;padding-left:calc(5% + 25px)}.main .sidebar h4[data-num]:before{content:attr(data-num);position:absolute;left:5%;text-align:right;width:50px;margin-left:-35px}.main .sidebar h3+ol,.main .sidebar h3+ul{margin:7px 0}.main .sidebar ol,.main .sidebar ul{margin:0;padding:0;list-style:none;width:100%}.main .sidebar ol li,.main .sidebar ul li{position:relative;padding:0 0 0 2.5%;-webkit-transition:background .15s ease;transition:background .15s ease}.main .sidebar ol li:not(.inactive):hover,.main .sidebar ol li a:focus,.main .sidebar ol li button:focus,.main .sidebar ul li:not(.inactive):hover,.main .sidebar ul li a:focus,.main .sidebar ul li button:focus{background:#fff;outline:none}.main .sidebar ol li:not(.inactive):hover .ico-after.action-hover,.main .sidebar ol li a:focus .ico-after.action-hover,.main .sidebar ol li button:focus .ico-after.action-hover,.main .sidebar ul li:not(.inactive):hover .ico-after.action-hover,.main .sidebar ul li a:focus .ico-after.action-hover,.main .sidebar ul li button:focus .ico-after.action-hover{display:block}.main .sidebar ol li.inactive>em,.main .sidebar ol li.inactive>span,.main .sidebar ol li a,.main .sidebar ol li button,.main .sidebar ul li.inactive>em,.main .sidebar ul li.inactive>span,.main .sidebar ul li a,.main .sidebar ul li button{display:block;cursor:pointer;padding-left:25px;padding-right:10px;text-decoration:none;color:#0079b2;overflow:hidden;height:30px;line-height:30px;font-size:14px;font-size:1.4rem;text-overflow:ellipsis;white-space:nowrap;border:0;text-align:left;background:transparent}.main .sidebar ol li.inactive>em[data-num],.main .sidebar ol li.inactive>span[data-num],.main .sidebar ol li a[data-num],.main .sidebar ol li button[data-num],.main .sidebar ul li.inactive>em[data-num],.main .sidebar ul li.inactive>span[data-num],.main .sidebar ul li a[data-num],.main .sidebar ul li button[data-num]{position:relative}.main .sidebar ol li.inactive>em[data-num]:after,.main .sidebar ol li.inactive>span[data-num]:after,.main .sidebar ol li a[data-num]:after,.main .sidebar ol li button[data-num]:after,.main .sidebar ul li.inactive>em[data-num]:after,.main .sidebar ul li.inactive>span[data-num]:after,.main .sidebar ul li a[data-num]:after,.main .sidebar ul li button[data-num]:after{content:attr(data-num) ".";position:absolute;left:0;width:18px;text-align:right;color:#424242}.main .sidebar ol li.inactive>em.selected,.main .sidebar ol li.inactive>span.selected,.main .sidebar ol li a.selected,.main .sidebar ol li button.selected,.main .sidebar ul li.inactive>em.selected,.main .sidebar ul li.inactive>span.selected,.main .sidebar ul li a.selected,.main .sidebar ul li button.selected{font-weight:700}.main .sidebar ol li.inactive>em img,.main .sidebar ol li.inactive>span img,.main .sidebar ol li a img,.main .sidebar ol li button img,.main .sidebar ul li.inactive>em img,.main .sidebar ul li.inactive>span img,.main .sidebar ul li a img,.main .sidebar ul li button img{border-right:7px solid transparent}.main .sidebar ol li.inactive>em img,.main .sidebar ol li.inactive>em span:not(.wide),.main .sidebar ol li.inactive>span img,.main .sidebar ol li.inactive>span span:not(.wide),.main .sidebar ol li a img,.main .sidebar ol li a span:not(.wide),.main .sidebar ol li button img,.main .sidebar ol li button span:not(.wide),.main .sidebar ul li.inactive>em img,.main .sidebar ul li.inactive>em span:not(.wide),.main .sidebar ul li.inactive>span img,.main .sidebar ul li.inactive>span span:not(.wide),.main .sidebar ul li a img,.main .sidebar ul li a span:not(.wide),.main .sidebar ul li button img,.main .sidebar ul li button span:not(.wide){vertical-align:middle}.main .sidebar ol li.inactive>em .icon,.main .sidebar ol li.inactive>span .icon,.main .sidebar ol li a .icon,.main .sidebar ol li button .icon,.main .sidebar ul li.inactive>em .icon,.main .sidebar ul li.inactive>span .icon,.main .sidebar ul li a .icon,.main .sidebar ul li button .icon{border-right:7px solid transparent;display:inline-block;width:16px;height:16px;margin:7px 5px;line-height:30px}.main .sidebar ol li.inactive>em.ico-after:after,.main .sidebar ol li.inactive>span.ico-after:after,.main .sidebar ol li a.ico-after:after,.main .sidebar ol li button.ico-after:after,.main .sidebar ul li.inactive>em.ico-after:after,.main .sidebar ul li.inactive>span.ico-after:after,.main .sidebar ul li a.ico-after:after,.main .sidebar ul li button.ico-after:after{top:7px;left:0;opacity:.7}.main .sidebar ol li.inactive>em.ico-after.disabled:after,.main .sidebar ol li.inactive>span.ico-after.disabled:after,.main .sidebar ol li a.ico-after.disabled:after,.main .sidebar ol li button.ico-after.disabled:after,.main .sidebar ul li.inactive>em.ico-after.disabled:after,.main .sidebar ul li.inactive>span.ico-after.disabled:after,.main .sidebar ul li a.ico-after.disabled:after,.main .sidebar ul li button.ico-after.disabled:after{opacity:.4!important}.main .sidebar ol li.inactive>em.ico-after:focus:after,.main .sidebar ol li.inactive>em.ico-after:hover:after,.main .sidebar ol li.inactive>span.ico-after:focus:after,.main .sidebar ol li.inactive>span.ico-after:hover:after,.main .sidebar ol li a.ico-after:focus:after,.main .sidebar ol li a.ico-after:hover:after,.main .sidebar ol li button.ico-after:focus:after,.main .sidebar ol li button.ico-after:hover:after,.main .sidebar ul li.inactive>em.ico-after:focus:after,.main .sidebar ul li.inactive>em.ico-after:hover:after,.main .sidebar ul li.inactive>span.ico-after:focus:after,.main .sidebar ul li.inactive>span.ico-after:hover:after,.main .sidebar ul li a.ico-after:focus:after,.main .sidebar ul li a.ico-after:hover:after,.main .sidebar ul li button.ico-after:focus:after,.main .sidebar ul li button.ico-after:hover:after{opacity:1}.main .sidebar ol li.inactive>em.ico-after.action-hover,.main .sidebar ol li.inactive>span.ico-after.action-hover,.main .sidebar ol li a.ico-after.action-hover,.main .sidebar ol li button.ico-after.action-hover,.main .sidebar ul li.inactive>em.ico-after.action-hover,.main .sidebar ul li.inactive>span.ico-after.action-hover,.main .sidebar ul li a.ico-after.action-hover,.main .sidebar ul li button.ico-after.action-hover{position:absolute;display:none;overflow:visible;top:0;left:10%;padding:0;z-index:1;width:30px;height:30px;text-indent:-9999px;background:#fff;right:-30px}.main .sidebar ol li.inactive>em.ico-after.action-hover[data-title]:hover:before,.main .sidebar ol li.inactive>span.ico-after.action-hover[data-title]:hover:before,.main .sidebar ol li a.ico-after.action-hover[data-title]:hover:before,.main .sidebar ol li button.ico-after.action-hover[data-title]:hover:before,.main .sidebar ul li.inactive>em.ico-after.action-hover[data-title]:hover:before,.main .sidebar ul li.inactive>span.ico-after.action-hover[data-title]:hover:before,.main .sidebar ul li a.ico-after.action-hover[data-title]:hover:before,.main .sidebar ul li button.ico-after.action-hover[data-title]:hover:before{content:attr(data-title);display:block;position:absolute;background:#fff;color:#555;top:-27px;left:0;height:27px;line-height:27px;line-height:2.7rem;text-indent:0;padding:0 15px;border:1px solid #eee;-webkit-box-shadow:rgba(0,0,0,.15) 0 0 7px;box-shadow:0 0 7px rgba(0,0,0,.15)}.main .sidebar ol li.inactive>em.ico-after.action-hover:after,.main .sidebar ol li.inactive>span.ico-after.action-hover:after,.main .sidebar ol li a.ico-after.action-hover:after,.main .sidebar ol li button.ico-after.action-hover:after,.main .sidebar ul li.inactive>em.ico-after.action-hover:after,.main .sidebar ul li.inactive>span.ico-after.action-hover:after,.main .sidebar ul li a.ico-after.action-hover:after,.main .sidebar ul li button.ico-after.action-hover:after{left:7px}.main .sidebar ol li.inactive>em,.main .sidebar ol li.inactive>span,.main .sidebar ul li.inactive>em,.main .sidebar ul li.inactive>span{color:#555}.main .sidebar ol li .count,.main .sidebar ul li .count{display:block;position:absolute;top:6px;right:20px;padding:1px 10px;height:16px;line-height:16px;font-style:normal;background:#aaa;color:#fff}.main .sidebar ol li .last-answer,.main .sidebar ul li .last-answer{display:block;visibility:hidden;position:absolute;top:-13px;left:102%;width:250px;height:40px;background:#fff;padding:7px 10px;border:1px solid #f0f0f0;-webkit-box-shadow:rgba(0,0,0,.1) 2px 2px 2px;box-shadow:2px 2px 2px rgba(0,0,0,.1);opacity:0;-webkit-transition:visibility 0s linear .15s,opacity .15s,left .15s;transition:visibility 0s linear .15s,opacity .15s,left .15s}.main .sidebar ol li .last-answer .avatar,.main .sidebar ul li .last-answer .avatar{height:40px;width:40px;float:left;border:1px solid #f0f0f0}.main .sidebar ol li .last-answer .topic-last-answer,.main .sidebar ul li .last-answer .topic-last-answer{display:block;margin-left:50px;line-height:18px;padding:3px 0;color:#555}.main .sidebar ol li .last-answer .topic-no-last-answer,.main .sidebar ul li .last-answer .topic-no-last-answer{display:block;line-height:40px;width:100%;text-align:center;color:#999}.main .sidebar ol li a:focus+.last-answer,.main .sidebar ol li a:hover+.last-answer,.main .sidebar ul li a:focus+.last-answer,.main .sidebar ul li a:hover+.last-answer{visibility:visible;left:100%;opacity:1;-webkit-transition:visibility 0s linear 0,opacity .15s,left .15s;transition:visibility 0s linear 0,opacity .15s,left .15s}.main .sidebar ol li a.unread,.main .sidebar ul li a.unread{font-weight:700}.main .sidebar ol li button,.main .sidebar ul li button{width:100%;line-height:28px}.main .sidebar ol li button.follow span,.main .sidebar ul li button.follow span{vertical-align:inherit}.main .sidebar ol li li,.main .sidebar ul li li{padding:0}.main .sidebar ol li li a,.main .sidebar ul li li a{position:relative;color:#084561;-webkit-transition-property:color,background,margin;transition-property:color,background,margin;-webkit-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.15s;transition-duration:.15s}.main .sidebar ol li li a:focus,.main .sidebar ol li li a:hover,.main .sidebar ul li li a:focus,.main .sidebar ul li li a:hover{color:#0079b2;background:#fff;margin-left:-11px}.main .sidebar ol li li a:focus:before,.main .sidebar ol li li a:hover:before,.main .sidebar ul li li a:focus:before,.main .sidebar ul li li a:hover:before{content:"> "}.main .sidebar.summary h4{border-bottom:1px solid #d8dada;padding-bottom:5px;padding-right:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.main .sidebar.summary h4+ol>li:first-child,.main .sidebar.summary h4+ul>li:first-child{margin-top:5px}.main .sidebar.summary ol li.current{margin-top:0!important;padding-top:5px;margin-bottom:5px;background-color:#fff}.main .sidebar.summary ol li.current+a{font-weight:700}.main .sidebar.summary ol li.current ol{margin-top:5px;padding-top:5px;padding-bottom:5px;margin-left:-42px;width:calc(105% + 25px);background:linear-gradient(180deg,rgba(0,0,0,.07),#f7f7f7 3px)}.main .sidebar.summary ol li.current ol a{padding-left:50px}.main{-webkit-box-flex:1;-ms-flex:1;flex:1;min-width:0}.main .content-container{padding-top:30px}.main .content-container h1,.main .content-container h2{font-size:22px;font-size:2.2rem;line-height:38px;line-height:3.8rem;color:#084561;font-weight:400;border-bottom:1px solid #f8ad32;margin:1px 0 15px}.main .content-container h1.illu,.main .content-container h2.illu{padding-left:60px}.main .content-container h1.illu img,.main .content-container h2.illu img{background:#fff}.main .content-container h1.ico-after,.main .content-container h2.ico-after{padding-left:50px}.main .content-container h1.ico-after:after,.main .content-container h2.ico-after:after{width:40px;height:40px}.main .content-container h1.ico-articles:after,.main .content-container h2.ico-articles:after{background-position:-40px 0}.main .content-container h1.ico-tutorials:after,.main .content-container h2.ico-tutorials:after{background-position:0 0}.main .content-container h1.ico-news:after,.main .content-container h2.ico-news:after{background-position:-40px -40px}.main .content-container h1.ico-forum:after,.main .content-container h2.ico-forum:after{background-position:0 -40px}.main .content-container h1.ico-tags:after,.main .content-container h2.ico-tags:after{background-position:-80px -40px}.main .content-container h1.illu img,.main .content-container h2.illu img{position:absolute;margin:-6px 0 0 -60px;border:1px solid #cdd0d1;width:50px;height:50px}.main .content-container h1.inline,.main .content-container h2.inline{display:inline-block}.main .content-container h1 .btn,.main .content-container h2 .btn{font-size:15px;line-height:38px;height:38px}.main .content-container .license{float:right;margin:0;margin-top:10px}.main .content-container .subtitle{font-size:18px;font-size:1.8rem;line-height:23px;color:#999;margin-top:-15px;margin-bottom:15px;padding:10px 0;font-weight:400;border-bottom:1px solid #eee}.main .content-container .pubdate{display:block;color:#999;margin-bottom:15px}.main .content-container .member-item .avatar{margin-top:-2px;height:20px;width:20px;border:1px solid #ccc}.main .content-container .member-item .avatar+span{padding-left:3px}.main .content-container .member-item:hover .avatar{border-color:#999}.main .content-container .member-item+.member-item{margin-left:7px}.main .content-container .authors .member-item{margin-right:0;margin-left:7px}.main .content-container .authors .member-item .avatar{height:30px;width:30px;margin:-3px 5px 0 -6px}.main .content-container .new-btn-container,.main .content-container .open-zen-mode{display:none}.home .main .content-container{margin-top:0}.pagination-bottom-clear{clear:both}@media only screen and (min-width:1360px){.main .content-container .content-wrapper{max-width:960px;margin:0 auto!important}}@media only screen and (min-width:960px){body.no-sidebar .main .content-container{width:100%}body.no-sidebar .main .sidebar{display:none}.main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;margin-left:0;padding-left:2.5%;min-width:0}.main .content-container{width:80%;margin-right:0}.main .content-container .taglist+.pubdate{margin-top:-40px}.main .content-container .open-zen-mode{display:block}.main .content-container.zen-mode{width:100%;min-height:calc(100% - 200px);position:absolute;top:0;left:0;right:0;z-index:6;background:#fcfcfc;padding:100px 0;margin-bottom:0}.main .content-container.zen-mode .alert-box,.main .content-container.zen-mode .comments-title,.main .content-container.zen-mode .pagination:not(.pagination-chapter),.main .content-container.zen-mode .sidebar,.main .content-container.zen-mode .topic-message{display:none}.main .content-container.zen-mode .content-wrapper{margin:0 4%}.main .content-container.zen-mode .summary-button{display:unset}.main .content-container h1,.main .content-container h2{margin-left:1px}.main .content-container .content-col-2{width:49.5%;margin:0 0 0 1%}.main .content-container .content-col-3{width:32%;margin:0 0 0 2%}.main .content-container .content-col-2,.main .content-container .content-col-3{float:left}.main .content-container .content-col-2:first-child,.main .content-container .content-col-3:first-child{margin:0}.main .content-container .article-content>.summary-part>li{float:left;width:50%}.main .content-container .article-content>.summary-part>li:nth-child(odd){clear:both}.main .sidebar{width:22.5%;border-bottom:none}.main .sidebar h3,.main .sidebar h4,.main .sidebar ol li,.main .sidebar ul li{padding-left:11.5%}.main .sidebar h3:first-child{margin-top:31px}.main .sidebar h4[data-num]{padding-left:calc(11% + 25px)}.main .sidebar h4[data-num]:before{left:11%}.main .sidebar.sommaire ul li.current ol,.main .sidebar.sommaire ul li.current ul{margin-left:calc(-11% - 10px);width:calc(111% + 10px);background:linear-gradient(180deg,rgba(0,0,0,.07),transparent 3px)}.main .sidebar.sommaire ul li.current ol a,.main .sidebar.sommaire ul li.current ul a{padding-left:calc(11% + 30px)}}@media only screen and (min-width:960px){.content-cols .main .content-container{width:79%;margin-left:1.5%}.full-content-wrapper .tutorial-list article{width:46%;float:left}.full-content-wrapper .tutorial-list article.extend{width:100%}}@media only screen and (max-width:959px){.main .content-container .new-btn-container{display:block;margin:30px 0;border-top:1px solid #ddd;overflow:hidden}.main .content-container .new-btn-container .new-btn{display:block;width:100%;padding:7px 10px 7px 35px;text-decoration:none;height:30px;line-height:30px;background:#eee;color:#333;border-bottom:1px solid #ddd}.main .content-container .new-btn-container .new-btn.ico-after:after{top:13px;left:10px}.main .content-container h1.ico-news:after,.main .content-container h2.ico-news:after{background-position:-80px 0}.main{width:100%}.main .content-container .content-col:not(:first-child),.main .sidebar{margin-top:50px}.main .sidebar{width:102.5%}.main .sidebar h3,.main .sidebar h4,.main .sidebar ul li{padding-left:5.5%}.main .sidebar h3 a,.main .sidebar h4 a,.main .sidebar ul li a{white-space:normal}.content-col-2:not(:first-child),.content-col-3:not(:first-child){margin-top:50px}}.small-content-wrapper{width:90%;max-width:500px;margin:20px auto}.main .content-container .content-wrapper.article-content,.main .content-container .content-wrapper.authors{padding-left:2%;padding-right:2%}.main .content-container .article-content>a,.main .content-container .article-content ol:not(.summary-part),.main .content-container .article-content p,.main .content-container .article-content p a,.main .content-container .article-content ul:not(.pagination){font-family:Merriweather,Liberation Serif,Times New Roman,Times,Georgia,FreeSerif,serif}.main .content-container .comment-author,.main .content-container .content-wrapper.comment-author{background:#eee;padding:7px 15px;margin-bottom:20px}.main .content-container .comment-author blockquote,.main .content-container .content-wrapper.comment-author blockquote{margin:10px 0;border-left:5px solid #ccc;padding:5px 0 5px 15px}.main .content-container .article-content .summary-part{font-size:20px;color:#ea9408}.main .content-container .article-content .summary-part h3,.main .content-container .article-content .summary-part h4{font-weight:400;width:90%}.main .content-container .article-content .summary-part h3 a,.main .content-container .article-content .summary-part h4 a{text-decoration:none}.main .content-container .article-content .summary-part h3 a:focus,.main .content-container .article-content .summary-part h3 a:hover,.main .content-container .article-content .summary-part h4 a:focus,.main .content-container .article-content .summary-part h4 a:hover{text-decoration:underline}.main .content-container .article-content .summary-part h3{font-size:20px;margin:0 0 5px}.main .content-container .article-content .summary-part .summary-part{list-style:none;padding-left:0;margin-bottom:15px}.main .content-container .article-content .summary-part .summary-part h4{font-size:14px;margin:2px 0}.main .content-container .article-content,.main .content-container .message-content{margin-top:20px;margin-bottom:20px;color:#424242}.main .content-container .article-content h2,.main .content-container .article-content h3,.main .content-container .message-content h2,.main .content-container .message-content h3{clear:both}.main .content-container .article-content h2,.main .content-container .article-content h2 a,.main .content-container .article-content h3,.main .content-container .article-content h3 a,.main .content-container .message-content h2,.main .content-container .message-content h2 a,.main .content-container .message-content h3,.main .content-container .message-content h3 a{color:#ea9408;margin-top:40px;text-decoration:none}.main .content-container .article-content h2 a:focus,.main .content-container .article-content h2 a:hover,.main .content-container .article-content h3 a:focus,.main .content-container .article-content h3 a:hover,.main .content-container .message-content h2 a:focus,.main .content-container .message-content h2 a:hover,.main .content-container .message-content h3 a:focus,.main .content-container .message-content h3 a:hover{text-decoration:underline}.main .content-container .article-content h2,.main .content-container .message-content h2{font-size:22px;font-size:2.2rem;line-height:50px;margin-bottom:20px;background:#fff;border-top:1px solid #e0e4e5;padding-left:1%;font-weight:400}.main .content-container .article-content h3,.main .content-container .message-content h3{font-size:20px;font-size:2rem;margin-bottom:14px}.main .content-container .article-content h4,.main .content-container .message-content h4{font-size:18px;font-size:1.8rem;margin-bottom:12px}.main .content-container .article-content h5,.main .content-container .message-content h5{font-size:16px;font-size:1.6rem;margin-bottom:10px}.main .content-container .article-content h6,.main .content-container .message-content h6{font-size:15px;font-size:1.5rem;margin-bottom:10px}.main .content-container .article-content .actions-title,.main .content-container .message-content .actions-title{float:right;margin:-60px 10px 0 0}.main .content-container .article-content .actions-title .btn,.main .content-container .message-content .actions-title .btn{height:30px;line-height:30px;margin-left:3px;opacity:.7;z-index:1}.main .content-container .article-content .actions-title .btn.ico-after:after,.main .content-container .message-content .actions-title .btn.ico-after:after{margin-top:7px}.main .content-container .article-content .actions-title .btn:focus,.main .content-container .article-content .actions-title .btn:hover,.main .content-container .message-content .actions-title .btn:focus,.main .content-container .message-content .actions-title .btn:hover{opacity:1}.main .content-container .article-content .custom-block,.main .content-container .message-content .custom-block{margin:25px 0}.main .content-container .article-content .custom-block-body,.main .content-container .message-content .custom-block-body{padding:7px 15px 7px 45px}.main .content-container .article-content .custom-block-body:after,.main .content-container .message-content .custom-block-body:after{position:absolute;top:50%;left:23px;margin:-11px 0 0 -11px;height:22px;width:22px}.main .content-container .article-content .custom-block-heading,.main .content-container .message-content .custom-block-heading{padding:3px 15px;font-weight:700}.main .content-container .article-content .custom-block-information,.main .content-container .message-content .custom-block-information{background:#daeaee}.main .content-container .article-content .custom-block-information .custom-block-heading,.main .content-container .message-content .custom-block-information .custom-block-heading{color:#fff;background:#53b2e4}.main .content-container .article-content .custom-block-information .custom-block-body:after,.main .content-container .message-content .custom-block-information .custom-block-body:after{background-position:-88px -112px}.main .content-container .article-content .custom-block-question,.main .content-container .message-content .custom-block-question{background:#e2daee}.main .content-container .article-content .custom-block-question .custom-block-heading,.main .content-container .message-content .custom-block-question .custom-block-heading{color:#fff;background:#9560e6}.main .content-container .article-content .custom-block-question .custom-block-body:after,.main .content-container .message-content .custom-block-question .custom-block-body:after{background-position:0 -112px}.main .content-container .article-content .custom-block-error,.main .content-container .message-content .custom-block-error{background:#eedada}.main .content-container .article-content .custom-block-error .custom-block-heading,.main .content-container .message-content .custom-block-error .custom-block-heading{color:#fff;background:#e45353}.main .content-container .article-content .custom-block-error .custom-block-body:after,.main .content-container .message-content .custom-block-error .custom-block-body:after{background-position:-44px -112px}.main .content-container .article-content .custom-block-warning,.main .content-container .message-content .custom-block-warning{background:#eee7da}.main .content-container .article-content .custom-block-warning .custom-block-heading,.main .content-container .message-content .custom-block-warning .custom-block-heading{color:#fff;background:#ebb552}.main .content-container .article-content .custom-block-warning .custom-block-body:after,.main .content-container .message-content .custom-block-warning .custom-block-body:after{background-position:-66px -112px}.main .content-container .article-content .custom-block-neutral,.main .content-container .article-content .custom-block-spoiler,.main .content-container .message-content .custom-block-neutral,.main .content-container .message-content .custom-block-spoiler{background:#eee}.main .content-container .article-content .custom-block-neutral .custom-block-heading,.main .content-container .article-content .custom-block-spoiler .custom-block-heading,.main .content-container .message-content .custom-block-neutral .custom-block-heading,.main .content-container .message-content .custom-block-spoiler .custom-block-heading{color:#fff;background:#888}.main .content-container .article-content .custom-block-neutral .custom-block-body,.main .content-container .article-content .custom-block-spoiler .custom-block-body,.main .content-container .message-content .custom-block-neutral .custom-block-body,.main .content-container .message-content .custom-block-spoiler .custom-block-body{padding-left:15px}.main .content-container .article-content .custom-block-neutral .custom-block-body:after,.main .content-container .article-content .custom-block-spoiler,.main .content-container .article-content .custom-block-spoiler .custom-block-body:after,.main .content-container .article-content .js .spoiler,.main .content-container .message-content .custom-block-neutral .custom-block-body:after,.main .content-container .message-content .custom-block-spoiler,.main .content-container .message-content .custom-block-spoiler .custom-block-body:after,.main .content-container .message-content .js .spoiler{display:none}.main .content-container .article-content .spoiler-title,.main .content-container .message-content .spoiler-title{display:block;background:#eee;margin:15px 0;padding:3px 15px 3px 40px;text-decoration:none;border-bottom:1px solid #ddd;color:#555}.main .content-container .article-content .spoiler-title.ico-after:after,.main .content-container .message-content .spoiler-title.ico-after:after{margin:8px 0 0 10px}.main .content-container .article-content .spoiler-title:nth-last-child(2),.main .content-container .message-content .spoiler-title:nth-last-child(2){margin-bottom:15px}.main .content-container .article-content .spoiler-title:hover,.main .content-container .message-content .spoiler-title:hover{text-decoration:underline}.main .content-container .article-content :not(.alert-box).error,.main .content-container .article-content :not(.alert-box).information,.main .content-container .article-content :not(.alert-box).question,.main .content-container .article-content :not(.alert-box).spoiler,.main .content-container .article-content :not(.alert-box).warning,.main .content-container .message-content :not(.alert-box).error,.main .content-container .message-content :not(.alert-box).information,.main .content-container .message-content :not(.alert-box).question,.main .content-container .message-content :not(.alert-box).spoiler,.main .content-container .message-content :not(.alert-box).warning{margin:25px 0;padding:7px 15px 7px 45px}.main .content-container .article-content :not(.alert-box).error.ico-after:after,.main .content-container .article-content :not(.alert-box).information.ico-after:after,.main .content-container .article-content :not(.alert-box).question.ico-after:after,.main .content-container .article-content :not(.alert-box).spoiler.ico-after:after,.main .content-container .article-content :not(.alert-box).warning.ico-after:after,.main .content-container .message-content :not(.alert-box).error.ico-after:after,.main .content-container .message-content :not(.alert-box).information.ico-after:after,.main .content-container .message-content :not(.alert-box).question.ico-after:after,.main .content-container .message-content :not(.alert-box).spoiler.ico-after:after,.main .content-container .message-content :not(.alert-box).warning.ico-after:after{position:absolute;top:50%;left:23px;margin:-11px 0 0 -11px;height:22px;width:22px}.main .content-container .article-content :not(.alert-box).information,.main .content-container .message-content :not(.alert-box).information{background:#daeaee}.main .content-container .article-content :not(.alert-box).information.ico-after:after,.main .content-container .message-content :not(.alert-box).information.ico-after:after{background-position:-88px -112px}.main .content-container .article-content :not(.alert-box).question,.main .content-container .message-content :not(.alert-box).question{background:#e2daee}.main .content-container .article-content :not(.alert-box).question.ico-after:after,.main .content-container .message-content :not(.alert-box).question.ico-after:after{background-position:0 -112px}.main .content-container .article-content :not(.alert-box).error,.main .content-container .message-content :not(.alert-box).error{background:#eedada}.main .content-container .article-content :not(.alert-box).error.ico-after:after,.main .content-container .message-content :not(.alert-box).error.ico-after:after{background-position:-44px -112px}.main .content-container .article-content :not(.alert-box).warning,.main .content-container .message-content :not(.alert-box).warning{background:#eee7da}.main .content-container .article-content :not(.alert-box).warning.ico-after:after,.main .content-container .message-content :not(.alert-box).warning.ico-after:after{background-position:-66px -112px}.main .content-container .article-content .spoiler,.main .content-container .message-content .spoiler{margin-top:0;padding-left:15px;background:#eee}.main .content-container .article-content img,.main .content-container .message-content img{max-width:100%}.main .content-container .article-content figure,.main .content-container .message-content figure{margin:30px 0;text-align:center}.main .content-container .article-content figure>blockquote,.main .content-container .article-content figure>code,.main .content-container .article-content figure>embed,.main .content-container .article-content figure>img,.main .content-container .article-content figure>pre,.main .content-container .article-content figure>table,.main .content-container .article-content figure>video,.main .content-container .message-content figure>blockquote,.main .content-container .message-content figure>code,.main .content-container .message-content figure>embed,.main .content-container .message-content figure>img,.main .content-container .message-content figure>pre,.main .content-container .message-content figure>table,.main .content-container .message-content figure>video{max-width:100%;margin:0 auto;text-align:left}.main .content-container .article-content figure>code,.main .content-container .article-content figure>figcaption,.main .content-container .article-content figure>img,.main .content-container .article-content figure>pre,.main .content-container .article-content figure>video,.main .content-container .message-content figure>code,.main .content-container .message-content figure>figcaption,.main .content-container .message-content figure>img,.main .content-container .message-content figure>pre,.main .content-container .message-content figure>video{display:block}.main .content-container .article-content figure>blockquote~figcaption,.main .content-container .message-content figure>blockquote~figcaption{padding:0 0 1px 2%;font-style:italic;text-align:left;color:#999;border-left:5px solid #ccc}.main .content-container .article-content figure>blockquote~figcaption p,.main .content-container .message-content figure>blockquote~figcaption p{margin:0 0 5px}.main .content-container .article-content figure>blockquote~figcaption p:before,.main .content-container .message-content figure>blockquote~figcaption p:before{content:"— "}.main .content-container .article-content blockquote,.main .content-container .message-content blockquote{margin:0;color:#777;padding:1px 2%;border-left:5px solid #ccc}.main .content-container .article-content blockquote>p:first-child,.main .content-container .message-content blockquote>p:first-child{margin-top:5px}.main .content-container .article-content blockquote>p:last-child,.main .content-container .message-content blockquote>p:last-child{margin-bottom:5px}.main .content-container .article-content blockquote figure,.main .content-container .message-content blockquote figure{margin:15px 0}.main .content-container .article-content blockquote:last-child,.main .content-container .message-content blockquote:last-child{margin-bottom:15px}.main .content-container .article-content code,.main .content-container .article-content kbd,.main .content-container .article-content pre,.main .content-container .article-content samp,.main .content-container .message-content code,.main .content-container .message-content kbd,.main .content-container .message-content pre,.main .content-container .message-content samp{font-family:Source Code Pro,monospace,serif}.main .content-container .article-content .hljs-code-div,.main .content-container .message-content .hljs-code-div{background-color:#fff;margin:0 auto;padding:.5em;border-radius:.25em;display:-webkit-box;display:-ms-flexbox;display:flex;line-height:18px;font-family:Source Code Pro,monospace,serif;font-style:normal;font-size:14px}.main .content-container .article-content .hljs-code-div .hljs-line-numbers,.main .content-container .message-content .hljs-code-div .hljs-line-numbers{counter-reset:a;border-right:1px solid #ddd;margin:0;padding:.5em;color:#888;text-align:right}.main .content-container .article-content .hljs-code-div .hljs-line-numbers span:before,.main .content-container .message-content .hljs-code-div .hljs-line-numbers span:before{counter-increment:a;content:counter(a);display:block}.main .content-container .article-content .hljs-code-div pre,.main .content-container .message-content .hljs-code-div pre{margin:0;width:100%;overflow-x:auto}.main .content-container .article-content kbd,.main .content-container .message-content kbd{background-color:#f8f6ea;padding:2px 6px;border-radius:3px;border:1px solid #e0dab6;border-bottom-width:3px;text-shadow:0 1px 0 #fff;color:#5e551f}.main .content-container .article-content li code,.main .content-container .article-content p code,.main .content-container .article-content td code,.main .content-container .message-content li code,.main .content-container .message-content p code,.main .content-container .message-content td code{color:#a00;background:#eee;border:1px solid #ccc;padding:0 5px}.main .content-container .article-content .ping,.main .content-container .message-content .ping{color:inherit;text-decoration:none}.main .content-container .article-content .ping:focus,.main .content-container .article-content .ping:hover,.main .content-container .message-content .ping:focus,.main .content-container .message-content .ping:hover{text-decoration:underline}.main .content-container .article-content .ping .ping-username,.main .content-container .message-content .ping .ping-username{font-weight:700}.main .content-container .article-content .mathjax-wrapper,.main .content-container .message-content .mathjax-wrapper{max-width:100%;overflow:auto}.main .content-container .article-content .mathjax-wrapper mathjax,.main .content-container .message-content .mathjax-wrapper mathjax{font-size:16px;font-size:1.6rem}.main .content-container .article-content .footnote,.main .content-container .article-content .footnotes,.main .content-container .message-content .footnote,.main .content-container .message-content .footnotes{opacity:.7}.main .content-container .article-content .footnote ol,.main .content-container .article-content .footnotes ol,.main .content-container .message-content .footnote ol,.main .content-container .message-content .footnotes ol{padding-left:25px}.main .content-container .article-content .footnote ol p,.main .content-container .article-content .footnotes ol p,.main .content-container .message-content .footnote ol p,.main .content-container .message-content .footnotes ol p{display:inline}.main .content-container .article-content .video-container,.main .content-container .message-content .video-container{margin:0 auto;max-width:560px;max-height:315px}.main .content-container .article-content .video-container .video-wrapper,.main .content-container .message-content .video-container .video-wrapper{position:relative;padding-bottom:56.25%}.main .content-container .article-content .video-container .video-wrapper iframe,.main .content-container .message-content .video-container .video-wrapper iframe{width:100%}.main .content-container .article-content .katex-display>.katex,.main .content-container .message-content .katex-display>.katex{display:contents}.main .content-container .article-content .inlineMathDouble .katex,.main .content-container .message-content .inlineMathDouble .katex{max-width:100%;overflow-x:auto}.main .content-container .article-content .iframe-wrapper,.main .content-container .message-content .iframe-wrapper{overflow-x:auto}.main .content-container .article-content div.align-center,.main .content-container .message-content div.align-center{text-align:center}.main .content-container .article-content div.align-right,.main .content-container .message-content div.align-right{text-align:right}.main .content-container .article-content div.align-left,.main .content-container .article-content figure pre code.hljs,.main .content-container .message-content div.align-left,.main .content-container .message-content figure pre code.hljs{text-align:left}.main .content-container .article-content .language-console,.main .content-container .message-content .language-console{color:#ddd;background-color:#000;font-family:Source Code Pro,monospace,serif;display:block;overflow-x:auto;padding:.5em}.main .content-container .comments-title{margin:50px 0 20px;color:#084561;border-bottom:1px solid #f8ad32;font-weight:400;font-size:22px;font-size:2.2rem;line-height:30px}@media only screen and (min-width:1140px){.full-content-wrapper .tutorial-list article{width:29.3%}.main .content-container .topic-message .message .message-metadata .date .short-date{display:none}.main .content-container .topic-message .message .message-metadata .date .long-date{display:inline}}@media only screen and (min-width:960px){.content-wrapper,.full-content-wrapper{margin:0 0 0 4%}.content-wrapper.without-margin,.full-content-wrapper.without-margin{margin:0}.content-wrapper .content-wrapper,.full-content-wrapper .content-wrapper{max-width:none;margin:0}}@media only screen and (max-width:959px){.main .content-container .pubdate,.main .content-container .taglist{margin-left:10px;margin-right:10px}.main .content-container .article-content ol,.main .content-container .article-content p,.main .content-container .article-content ul:not(.pagination){font-size:15px;font-size:1.5rem;font-size:1.8ex}.main .content-container .content-wrapper .subtitle,.main .content-container .content-wrapper h1:not(.ico-after),.main .content-container .content-wrapper h2:not(.ico-after),.main .content-container .content-wrapper h3,.main .content-container .full-content-wrapper .subtitle,.main .content-container .full-content-wrapper h1:not(.ico-after),.main .content-container .full-content-wrapper h2:not(.ico-after),.main .content-container .full-content-wrapper h3{padding-left:10px;padding-right:10px}.main .content-container .content-wrapper .illu img,.main .content-container .full-content-wrapper .illu img{display:none}.main .content-container .content-wrapper .authors,.main .content-container .content-wrapper blockquote,.main .content-container .content-wrapper figure,.main .content-container .content-wrapper h4,.main .content-container .content-wrapper h5,.main .content-container .content-wrapper h6,.main .content-container .content-wrapper p,.main .content-container .full-content-wrapper .authors,.main .content-container .full-content-wrapper blockquote,.main .content-container .full-content-wrapper figure,.main .content-container .full-content-wrapper h4,.main .content-container .full-content-wrapper h5,.main .content-container .full-content-wrapper h6,.main .content-container .full-content-wrapper p{margin-left:10px;margin-right:10px}.main .content-container .content-wrapper figure blockquote,.main .content-container .content-wrapper figure p,.main .content-container .full-content-wrapper figure blockquote,.main .content-container .full-content-wrapper figure p{margin-left:0;margin-right:0}.main .content-container .content-wrapper .license,.main .content-container .full-content-wrapper .license{position:absolute;margin-top:0;top:62px;right:15px}}@media only screen and (max-width:759px){.main .content-container .article-content .btn{float:none;text-align:center}}.footer-container footer{color:#424242;padding:20px 0}.page-footer{background:#084561;height:40px;line-height:40px;border-top:3px solid #f8ad32;font-size:14px;font-size:1.4rem}.page-footer .wrapper{display:-webkit-box;display:-ms-flexbox;display:flex}.page-footer .copyright,.page-footer .links{-ms-flex-negative:1;flex-shrink:1;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;min-width:0}.page-footer .copyright{margin:0;padding:0 1rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.page-footer .copyright,.page-footer .copyright a{color:hsla(0,0%,100%,.5)}.page-footer .copyright a:focus,.page-footer .copyright a:hover{color:#fff}.page-footer ul{list-style:none;margin:0;padding:0;white-space:nowrap}.page-footer ul.links{text-align:right}.page-footer ul.links li{display:inline-block;margin:0 1rem}.page-footer ul.links li a{text-decoration:none;color:#eee;border-bottom:1px solid transparent}.page-footer ul.links li a:focus,.page-footer ul.links li a:hover{border-bottom-color:#f8ad32}.page-footer ul.social{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0;-ms-flex-preferred-size:auto;flex-basis:auto;text-align:center}.page-footer ul.social li{margin:-2px 10px;display:inline-block}.page-footer ul.social li a{display:block;height:16px;width:16px}.page-footer ul.social li a:after{opacity:.6}.page-footer ul.social li a:hover{border-bottom-color:transparent}.page-footer ul.social li a:hover:after{opacity:1}@media only screen and (max-width:959px){.page-footer{text-align:center;height:auto}.page-footer .wrapper{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.page-footer .wrapper .social{-ms-flex:none}.page-footer .copyright,.page-footer .social{border-bottom:2px solid #0a5274;-ms-flex-preferred-size:auto;flex-basis:auto;-ms-flex-negative:0;flex-shrink:0}.page-footer ul{white-space:normal}.page-footer ul.links{text-align:inherit}.page-footer ul li{margin:0 5px}}.alert-box{position:relative;padding:8px 30px 8px 15px;margin:0 0 15px 2%;color:#fff;text-shadow:rgba(0,0,0,.2) 0 0 2px;background:#777}.alert-box.alert-box-not-closable{padding-right:15px}.alert-box .alert-box-text{display:block;float:left}.alert-box .close-alert-box,.alert-box .view-alert-box{display:block;position:absolute;top:8px;right:15px;height:20px;width:20px;text-indent:-9999px;text-decoration:none;background-color:transparent;line-height:22px;color:#fff}.alert-box .close-alert-box.ico-after:after,.alert-box .view-alert-box.ico-after:after{margin-top:4px}.alert-box .close-alert-box-text{width:auto;text-indent:0;top:8px}.alert-box .alert-box-title{margin:5px 0;padding:0;font-size:18px;font-weight:400}.alert-box.info,.alert-box.success{background:#48a200}.alert-box.error{background:#c0392b}.alert-box.alert,.alert-box.warning{background:#e67e22}.alert-box.not-member{background:#fdfdfd;color:#333;text-shadow:none;border-bottom:3px solid #d2d5d6}.alert-box.ico-after{padding-left:40px}.alert-box.ico-after:after{margin:12px 0 0 13px}.alert-box h4,.alert-box p{margin-left:0!important;margin-right:0!important}.alert-box p{margin:0}.alert-box a{color:#eee}.alert-box .alert-box-btn{display:inline-block;background:#084561;text-decoration:none;padding:8px 15px;margin:5px 0;color:#fff!important}.alert-box .alert-box-btn:focus,.alert-box .alert-box-btn:hover{background:#0b5c82}.alert-box .alert-box-btn.alert-box-btn-right{position:absolute;top:0;right:0;margin:0}.alert-box.empty{display:none}.content-wrapper .alert-box{margin:0 0 20px}.content-wrapper .alert-box+.not-member{margin-top:-20px}.opinion-alerts .alert-box-text{float:none}@media only screen and (min-width:760px){.alert-box .alert-box-text{display:inline}.topic-message .alert-box{padding:8px 75px 8px 15px}}@media only screen and (max-width:759px){.alert-box .alert-box-btn,.alert-box .alert-box-btn.alert-box-btn-right{position:relative;float:none;display:block;margin:5px 0 0;text-align:center}}.authors{color:#9c9c9c;padding-bottom:10px;border-bottom:1px solid #e0e4e5;margin-bottom:20px!important}.authors .authors-label{display:inline-block}.authors ul{list-style:none;padding:0}.authors ul,.authors ul li{display:inline-block;margin:0}.authors ul li .avatar{height:28px;width:28px;border:1px solid #cdd0d1;margin-right:3px;margin-top:-4px}.authors ul li a{display:block;text-decoration:none;color:#1088bf;height:36px;line-height:36px;padding:0 8px;-webkit-transition:background .15s ease,color .15s ease;transition:background .15s ease,color .15s ease}.authors ul li a.ico-after{padding-left:30px}.authors ul li a.ico-after:after{margin:10px 0 0 8px}.authors ul li a:focus,.authors ul li a:hover{background:#ddd;color:#084561}.authors ul li .info{padding-left:5px;color:#777}.autocomplete-wrapper{position:relative}.autocomplete-wrapper .autocomplete-dropdown{position:absolute;z-index:9}.autocomplete-wrapper .autocomplete-dropdown .autocomplete-dropdown-header{padding:0;padding-left:5px;background-color:#eee;font-weight:400}.autocomplete-wrapper .autocomplete-dropdown .autocomplete-dropdown-header,.autocomplete-wrapper .autocomplete-dropdown ul{margin:0;border-right:1px solid #ccc;border-left:1px solid #ccc;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.autocomplete-wrapper .autocomplete-dropdown ul{padding:0;background-color:#fff}.autocomplete-wrapper .autocomplete-dropdown ul li{padding:4px 10px;border-bottom:1px solid #ccc;list-style:none}.autocomplete-wrapper .autocomplete-dropdown ul li.active,.autocomplete-wrapper .autocomplete-dropdown ul li:hover{background-color:#0c6790;color:#fff}.modal .autocomplete-dropdown{margin-top:-15px}.breadcrumb{display:none}@media only screen and (min-width:960px){.breadcrumb{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;width:calc(100% - 60px * 4);height:30px;padding-left:2rem}.breadcrumb:after{content:" ";display:block;position:absolute;top:0;right:0;width:50px;height:100%;background-image:-webkit-gradient(linear,left top,right top,from(rgba(231,235,236,0)),to(rgba(231,235,236,.75)));background-image:linear-gradient(90deg,rgba(231,235,236,0),rgba(231,235,236,.75))}.breadcrumb ol{margin:0;padding:0;list-style:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.breadcrumb ol li{position:relative;display:inline-block;padding-right:30px;line-height:30px}.breadcrumb ol li a{text-decoration:none;color:#084561}.breadcrumb ol li a:focus,.breadcrumb ol li a:hover{text-decoration:underline;outline:none}.breadcrumb ol li:not(:last-child):after{display:block;position:absolute;top:0;right:7px;content:" ";height:30px;width:15px;background-image:url(../images/sprite.png);background-repeat:no-repeat;background-position:0 -272px;opacity:.2}}.content-item{background:#fff;min-height:60px;display:-webkit-box;display:-ms-flexbox;display:flex;border:1px solid #dedede;border-bottom-width:2px;margin:0 10px 15px;overflow:hidden;-webkit-box-flex:1;-ms-flex:1 1 400px;flex:1 1 400px;width:100%}.content-item.expand-description .content-description{height:36px;white-space:normal;font-size:14px;font-size:1.4rem;line-height:18px}.content-item.expand-description .content-meta{line-height:16px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.content-item.expand-description .content-meta:not(.inline)>*{display:inline}.content-item a{text-decoration:none}.content-item>a:not(.btn){display:-webkit-box;display:-ms-flexbox;display:flex;width:100%}.content-item .content-illu{-webkit-box-flex:0;-ms-flex:0 0 96px;flex:0 0 96px;height:96px;background-color:#dedede;background-size:contain}.content-item .content-illu img{width:100%;height:100%;background-color:#fff}.content-item .content-illu.article-illu{background-image:url(/static/images/article-illu.png)}.content-item .content-illu.tutorial-illu{background-image:url(/static/images/tutorial-illu.png)}.content-item .content-illu.opinion-illu{background-image:url(/static/images/opinion-illu.png)}.content-item .content-info{padding:10px 14px;height:76px;-webkit-box-flex:1;-ms-flex:1;flex:1;position:relative;min-width:100px}.content-item .content-reactions{position:absolute;z-index:0;bottom:6px;left:-14px;height:32px;width:32px;padding-left:1px;background-image:url(../images/sprite.png);background-position:-33px -80px;color:#f8ad32;text-align:center;line-height:32px;font-weight:700;font-size:14px;font-size:1.4rem}.content-item .content-reactions span{position:relative;z-index:2}.content-item .content-reactions:before{content:"";display:block;position:absolute;top:0;bottom:0;right:0;left:0;z-index:1;background-image:url(../images/sprite.png);background-position:0 -80px;opacity:0;-webkit-transition:opacity .15s;transition:opacity .15s}.content-item .content-reactions:focus,.content-item .content-reactions:hover{color:#fff}.content-item .content-reactions:focus:before,.content-item .content-reactions:hover:before{opacity:1}.content-item.has-reactions .content-meta{padding-left:14px}.content-item .content-title{margin:0;font-size:17px;font-size:1.7rem;font-weight:400;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#424242}.content-item a:focus,.content-item a:hover{outline:none}.content-item a:focus .content-title,.content-item a:hover .content-title{text-decoration:underline;outline:none}.content-item p{margin:0}.content-item .content-description{margin:0;font-size:15px;font-size:1.5rem;line-height:26px;height:26px;color:#999;margin-bottom:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.content-item .content-description .short{display:none}.content-item .content-meta{color:#f8ad32;font-size:13px;font-size:1.3rem;line-height:15px}.content-item .content-meta:not(.inline)>*{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}.content-item .content-meta .short{display:none}.content-item .content-meta a{color:#ef9708}.content-item .content-meta a:focus,.content-item .content-meta a:hover{text-decoration:underline}.content-item .content-tags{margin:0;padding:10px 0 0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.content-item .content-tags li{padding:0;-webkit-box-flex:0;-ms-flex:0 0 22px;flex:0 0 22px;background-color:#eee;margin-bottom:5px;color:#777;display:block;text-align:right;-webkit-transition:color .15s,background-color .15s;transition:color .15s,background-color .15s}.content-item .content-tags li a{color:inherit;padding:0 12px;line-height:22px;height:22px;display:block}.content-item .content-tags li a:focus,.content-item .content-tags li a:hover{color:#eee;background-color:#777}.content-item.write-tutorial{background-color:#084561;border-color:#084561;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#fff;height:96px}.content-item.write-tutorial .write-tutorial-text{-webkit-box-flex:1;-ms-flex:1;flex:1;text-align:center;margin:10px 14px}.content-item.write-tutorial .write-tutorial-text p{margin:0;font-size:16px}.content-item.write-tutorial .write-tutorial-text p.lead{font-size:18px;font-weight:700}.content-item.write-tutorial .btn-write-tutorial{background-color:#1c5b78;margin-right:28px;-webkit-transition:color .15s,background-color .15s;transition:color .15s,background-color .15s}.content-item.write-tutorial .btn-write-tutorial:focus,.content-item.write-tutorial .btn-write-tutorial:hover{background-color:#fff;color:#1c5b78}.content-item.topic-item .content-info{padding:14px 20px;height:68px}.content-item.topic-item .content-title{font-size:19px;font-size:1.9rem;line-height:24px;color:#084561}.content-item.topic-item .content-description{color:#505050;font-size:16px;font-size:1.6rem}.content-item.topic-item .member-item:focus,.content-item.topic-item .member-item:hover{text-decoration:underline}.content-item.topic-item .content-meta{font-size:14px;font-size:1.4rem;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.content-item-list{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;min-width:100%;margin:0 -10px}.content-item-list .fill{-webkit-box-flex:1;-ms-flex:1 1 400px;flex:1 1 400px;margin:0 10px}@media only screen and (max-width:959px){.full-content-wrapper .content-item .content-info h3{padding:0!important}.full-content-wrapper .content-item .content-info p:not(.content-meta){margin:0!important}}@media only screen and (max-width:759px){.content-item .content-tags,.content-item.write-tutorial{display:none}.content-item .content-description .short,.content-item .content-meta .short{display:inline}.content-item .content-description .long,.content-item .content-meta .long{display:none}}.zform-toolbar{margin:0;padding:2px;list-style-position:initial;list-style-type:none;border-bottom:none}.zform-toolbar a,.zform-toolbar button{display:block;float:left;cursor:pointer;border-bottom:1px solid transparent;text-decoration:none;color:#999;height:27px;line-height:30px;padding:0 10px;margin-left:1px;text-indent:-9999px;width:0}.zform-toolbar a .zform-popup,.zform-toolbar button .zform-popup{text-indent:0;line-height:20px}.zform-toolbar a.ico-after,.zform-toolbar button.ico-after{padding-left:30px}.zform-toolbar a:after,.zform-toolbar button:after{top:7px;left:12px}.zform-toolbar a:focus,.zform-toolbar a:hover,.zform-toolbar button:focus,.zform-toolbar button:hover{border-bottom-color:#1088bf;outline:none;background-color:#eee}.zform-toolbar button{padding:0 15px;height:30px;border-top:none;border-right:none;border-left:none}.zform-toolbar button[type=submit]{background:#084561;border-bottom-color:#084561;color:#ddd}.zform-toolbar button[type=submit]:focus,.zform-toolbar button[type=submit]:hover{color:#fff;background:#396a81;border-bottom-color:#396a81}.zform-button{background-repeat:no-repeat;background-position:50%}.zform-button-bold:after{background-position:-260px -200px}.zform-button-italic:after{background-position:-212px -176px}.zform-button-strike:after{background-position:-66px -80px}.zform-button-abbr:after{background-position:-292px -256px}.zform-button-key:after{background-position:-196px -160px}.zform-button-sup:after{background-position:-98px -80px}.zform-button-sub:after{background-position:-82px -80px}.zform-button-center:after{background-position:-260px -216px}.zform-button-right:after{background-position:-142px -96px}.zform-button-ul:after{background-position:-31px -272px}.zform-button-ol:after{background-position:-180px -136px}.zform-button-quote:after{background-position:-164px -136px}.zform-button-link:after{background-position:-308px -240px}.zform-button-image:after{background-position:-228px -176px}.zform-button-attention:after{background-position:-276px -240px}.zform-button-error:after{background-position:-244px -216px}.zform-button-question:after{background-position:-164px -120px}.zform-button-infoblocks:after,.zform-button-information:after{background-position:-212px -160px}.zform-button-secret:after{background-position:-120px -80px}.zform-button-blockcode:after,.zform-button-monospace:after{background-position:-276px -256px}.zform-button-titles:after{background-position:-308px -256px}.zform-button-title1:after{background-position:-82px -96px}.zform-button-title2:after{background-position:-98px -96px}.zform-button-title3:after{background-position:-15px -272px}.zform-button-title4:after{background-position:-196px -176px}.zform-button-table:after{background-position:-66px -96px}.zform-button-math:after{background-position:-180px -120px}.zform-button-footnote:after{background-position:-228px -160px}.zform-button-chars:after{background-position:-244px -200px}.zform-button-smilies:after{background-position:-120px -96px}div.zform-popup{top:32px;z-index:10;background:transparent;background-color:#fff;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(to,center),color-stop(8%,#ebebe5),color-stop(75%,#f9f9f6));background-image:linear-gradient(center to top,#ebebe5 8%,#f9f9f6 75%);border:1px solid #ccc;border-radius:3px;padding:2px}.zform-button-smilies .zform-code-col>span{text-align:center}.zform-code-col{display:inline-block;vertical-align:top;margin:2px;min-width:100px}.zform-code-col>span{display:block;color:#2677c9;cursor:pointer}.zform-code-col>span[data-zform-selected=true]{color:blue;font-weight:700}.zform-code-col>span:focus,.zform-code-col>span:hover{color:#c87b02}.featured-resource-item{-webkit-box-flex:1;-ms-flex:1;flex:1;margin-right:1px;background-color:#ccc;position:relative;overflow:hidden;max-width:228px;min-width:170px;z-index:0;background-color:#084561}.featured-resource-item:before{content:"";display:block;padding-top:100%}.featured-resource-item .featured-resource-illu{position:absolute;z-index:1;top:0;left:0;height:100%;width:auto;-webkit-transition:.15s ease;transition:.15s ease;-webkit-transition-property:opacity,-webkit-transform;transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;-webkit-transform:scale(0),translateZ(0);transform:scale(0),translateZ(0);-webkit-perspective:1000;perspective:1000;-webkit-backface-visibility:hidden;backface-visibility:hidden}.featured-resource-item .featured-resource-meta{position:absolute;z-index:3;color:#fff;bottom:0;right:0;left:0;padding:40px 14px 12px;text-shadow:1px 1px 0 rgba(0,0,0,.6);background-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.2) 30px,rgba(0,0,0,.4))}.featured-resource-item .featured-resource-meta h3{font-size:16px;line-height:20px;font-weight:400;margin:0;display:table-cell;vertical-align:middle;height:0;-webkit-transition:height .15s ease;transition:height .15s ease}.featured-resource-item .featured-resource-meta p{font-size:12px;margin:0;line-height:22px}.featured-resource-item a:focus .featured-resource-illu,.featured-resource-item a:hover .featured-resource-illu{opacity:.4;-webkit-filter:blur(5px);filter:blur(5px);-webkit-transform:scale(1.05),translateZ(0);transform:scale(1.05),translateZ(0)}.featured-resource-item a:focus .featured-resource-meta h3,.featured-resource-item a:hover .featured-resource-meta h3{height:190px;font-size:20px}.featured-resource-item>a{display:block}.featured-resource-edit-form{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.featured-resource-edit-form .featured-resource-item{margin-right:20px;-ms-flex-preferred-size:228px;flex-basis:228px}.featured-resource-edit-form form{width:auto;-webkit-box-flex:1;-ms-flex:1;flex:1}@media only screen and (max-width:759px){.featured-resource-edit-form{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:unset;-ms-flex-align:unset;align-items:unset}}.markdown-help{min-height:25px;overflow:hidden;background:#eee;padding:15px;margin-bottom:5px;border-bottom:1px solid #ccc}.mobile-menu,.mobile-menu-btn{display:none}@media only screen and (max-width:959px){.js .page-container{position:relative;z-index:4;-webkit-transform:translateZ(0);transform:translateZ(0)}.js .mobile-menu{display:block;position:absolute;position:fixed;overflow-x:hidden;overflow-y:auto;z-index:1;-webkit-transform:translate3d(-20%,0,0);transform:translate3d(-20%,0,0);width:90%;height:100%;padding-bottom:20px;background:#222;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.js .mobile-menu .search{height:50px;position:relative;top:0;left:0;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.js .mobile-menu .search form{-webkit-box-flex:1;-ms-flex:1;flex:1}.js .mobile-menu .search input{color:#eee;background-color:#333;height:30px;padding:10px 5%;font-size:16px;font-size:1.6rem;width:100%;height:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.js .mobile-menu .search input:focus,.js .mobile-menu .search input:hover{padding-bottom:7px;border-bottom:3px solid #084561;background-color:#333}.js .mobile-menu .search button{display:none}.js .mobile-menu .search .search-more{background-color:#3f3f3f;width:50px;height:50px;line-height:50px;color:#ccc}.js .mobile-menu .mobile-menu-bloc,.js .mobile-menu .mobile-menu-link{width:90%;line-height:40px;text-indent:0}.js .mobile-menu .mobile-menu-bloc{margin:0 5% 15px}.js .mobile-menu .mobile-menu-bloc:nth-child(2){margin-top:15px}.js .mobile-menu .mobile-menu-bloc li,.js .mobile-menu .mobile-menu-bloc ul{margin:0;padding:0}.js .mobile-menu .mobile-menu-bloc .mobile-menu-link{margin:0;width:100%}.js .mobile-menu .mobile-menu-bloc .mobile-menu-link.disabled{opacity:.5}.js .mobile-menu .mobile-menu-bloc:not(.mobile-show-ico) .ico-after:after{display:none}.js .mobile-menu .mobile-menu-bloc[data-title]:before{display:block;content:attr(data-title);height:30px;font-size:14px;font-size:1.4rem;text-transform:uppercase;padding-bottom:3px;border-bottom:2px solid #3f3f3f;font-weight:700;color:#666}.js .mobile-menu .mobile-menu-bloc.mobile-show-ico .ico-after{padding-left:30px;width:calc(100% - 30px)}.js .mobile-menu .mobile-menu-bloc.mobile-show-ico .ico-after:after{top:12px;left:2px}.js .mobile-menu .mobile-menu-bloc.mobile-show-ico .icon{display:inline-block;width:16px;height:16px;margin:7px;line-height:30px;padding-left:5px}.js .mobile-menu .mobile-menu-bloc.mobile-show-ico .icon:after{top:0;left:0}.js .mobile-menu .mobile-menu-link{display:block;height:40px;line-height:40px;text-decoration:none;color:#ccc;font-size:16px;font-size:1.6rem;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;background:none;border:none;text-align:left;padding:0}.js .mobile-menu .mobile-menu-link.mobile-menu-sublink{width:90%;margin:0 0 0 10%}.js .mobile-menu .mobile-menu-link.mobile-menu-bloc[data-title]{height:80px}.js .mobile-menu .mobile-menu-link.mobile-menu-bloc:not([data-title]){margin-bottom:0}.js .mobile-menu .mobile-menu-link:not(:last-child):not(.mobile-menu-bloc){border-bottom:1px solid #2c2c2c}.js .mobile-menu .mobile-menu-link[data-prefix]:before{content:"[" attr(data-prefix) "] "}.js .mobile-menu .mobile-menu-link.unread{font-weight:700;color:#eee}.js .mobile-menu .mobile-menu-link img,.js .mobile-menu .mobile-menu-link span{vertical-align:middle}.js .mobile-menu .mobile-menu-link img{float:left;margin:5px 5px 5px 0;width:30px;height:30px}.js .mobile-menu .mobile-menu-link .label{padding:0 0 0 50px}.js .mobile-menu .mobile-menu-link img+.label{padding:0 0 0 10px}.js.show-mobile-menu{width:100%}.js.show-mobile-menu body{position:fixed}.js.show-mobile-menu .page-container{-webkit-transform:translate3d(90%,0,0);transform:translate3d(90%,0,0);overflow:hidden;-webkit-box-shadow:0 0 7px rgba(0,0,0,.25);box-shadow:0 0 7px rgba(0,0,0,.25)}.js.show-mobile-menu .mobile-menu{-webkit-transform:translateZ(0);transform:translateZ(0)}.js.enable-mobile-menu .mobile-menu-hide,.js.enable-mobile-menu .page-container .mobile-menu-bloc,.js.enable-mobile-menu .page-container .mobile-menu-link,.js.enable-mobile-menu .page-container .search{display:none}.js.enable-mobile-menu .page-container .mobile-menu-btn+.header-logo{margin-left:0}.js.enable-mobile-menu .page-container .mobile-menu-btn{display:block;float:left;height:50px;width:50px;cursor:pointer}.js.enable-mobile-menu .page-container .mobile-menu-btn:after{display:block;content:" ";position:absolute;top:15px;left:13px;height:22px;width:22px;background-repeat:no-repeat;background-position:-120px -40px}html:not(.enable-mobile-menu) .header-container{border-bottom:1px solid #ccc}html:not(.enable-mobile-menu) .page-container .header-logo{margin-left:10px}html:not(.enable-mobile-menu) .page-container .header-logo-link:after{left:55px;right:205px}html:not(.enable-mobile-menu) .logbox .my-account,html:not(.enable-mobile-menu) .logbox .notifs-links .ico-link{position:absolute;top:0;right:0;height:50px;width:50px}html:not(.enable-mobile-menu) .logbox .my-account .avatar,html:not(.enable-mobile-menu) .logbox .notifs-links .ico-link .avatar{height:50px;width:50px}html:not(.enable-mobile-menu) .logbox .notifs-links :first-child .ico-link{right:150px}html:not(.enable-mobile-menu) .logbox .notifs-links :nth-child(2) .ico-link{right:100px}html:not(.enable-mobile-menu) .logbox .notifs-links .ico-link:nth-child(3),html:not(.enable-mobile-menu) .logbox .notifs-links :nth-child(3) .ico-link{right:50px}html:not(.enable-mobile-menu) .logbox.unlogged{position:absolute;top:0;right:0}}.modal{display:none}.modals-container{display:none;position:fixed;top:0;left:0;height:100vh;width:100vw;overflow-y:auto;z-index:8}.modals-container.open{display:block}.modals-container .modals-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;width:100vw;min-height:100vh;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-pack:distribute;justify-content:space-around;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.modals-container .modals-overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1;background-color:rgba(0,0,0,.7)}.modals-container .modal{position:relative;z-index:2;background:#eee;-webkit-box-flex:0;-ms-flex:0;flex:0;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.modals-container .modal.open{display:-webkit-box;display:-ms-flexbox;display:flex}.modals-container .modal .modal-title{display:block;border-bottom:3px solid #f8ad32;line-height:53px;height:50px;text-indent:15px;background:#084561;color:#fff;font-size:16px;font-size:1.6rem;text-shadow:rgba(0,0,0,.75) 0 0 3px}.modals-container .modal .modal-title.ico-after{text-indent:40px}.modals-container .modal .modal-title.ico-after:after{margin:18px 0 0 15px}.modals-container .modal .modal-body{padding:20px 15px 5px;-webkit-box-flex:1;-ms-flex:1;flex:1}.modals-container .modal .modal-body p{width:370px}.modals-container .modal .modal-body table{margin-top:0}.modals-container .modal .modal-body input:not([type=checkbox]):not([type=radio]),.modals-container .modal .modal-body p,.modals-container .modal .modal-body select,.modals-container .modal .modal-body textarea{margin:0 0 15px}.modals-container .modal .modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;border-top:1px solid #ccc;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.modals-container .modal .modal-footer>*{-webkit-box-flex:1;-ms-flex:1;flex:1;height:50px;line-height:50px;margin:0;padding:0;text-align:center;background:none!important;color:#333}.modals-container .modal .modal-footer>:not(:first-child){border-right:1px solid #ccc}.modals-container .modal .modal-footer>:only-child{font-weight:700}.modals-container .modal .modal-footer .btn-submit,.modals-container .modal .modal-footer [type=submit]{color:#084561;font-weight:700}.modals-container .modal .modal-footer .btn-cancel{color:#555}.enable-mobile-menu .modals-container .modal{margin:25px;-webkit-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000;max-width:100%}.enable-mobile-menu .modals-container .modal.modal-flex{width:400px}@media only screen and (min-width:960px){.enable-mobile-menu .modals-container .modal{-webkit-box-shadow:0 2px 7px rgba(0,0,0,.7);box-shadow:0 2px 7px rgba(0,0,0,.7)}.enable-mobile-menu .modals-container .modal .modal-title{line-height:50px}.enable-mobile-menu .modals-container .modal .btn-submit:not(.disabled):focus,.enable-mobile-menu .modals-container .modal .btn-submit:not(.disabled):hover,.enable-mobile-menu .modals-container .modal [type=submit]:not(.disabled):focus,.enable-mobile-menu .modals-container .modal [type=submit]:not(.disabled):hover{color:#eee;background:#48a200!important}.enable-mobile-menu .modals-container .modal .btn-cancel:focus,.enable-mobile-menu .modals-container .modal .btn-cancel:hover{color:#eee;background:#c0392b!important}}.modal .vote-details{display:-webkit-box;display:-ms-flexbox;display:flex;color:#444;max-height:400px;overflow-y:auto}.modal .vote-details .vote-col{-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 8px 15px}.modal .vote-details .vote-col h3{margin:0 0 2px}.modal .vote-details ul.vote-list{padding:0;margin:0;list-style:none}.modal .vote-details ul.vote-list li{line-height:24px;border-top:1px solid #ccc}.modal .vote-details ul.vote-list li a{padding:4px;text-decoration:none;color:inherit;display:block}.modal .vote-details ul.vote-list li a:focus,.modal .vote-details ul.vote-list li a:hover{background-color:#fff}.modal .vote-details ul.vote-list li.muted{color:#777;padding:4px}.modal .vote-details ul.vote-list li img{height:24px;width:24px;margin-right:6px}.pagination{list-style:none;margin:0;padding:0;border-top:1px solid #d2d5d6;border-bottom:1px solid #d2d5d6;background:#fbfbfb;margin-bottom:20px!important;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.pagination li{margin-bottom:-1px}.pagination li a{display:block;text-align:center;text-decoration:none;color:#084561;min-width:45px;height:40px;line-height:40px;-webkit-transition:background .15s ease;transition:background .15s ease}.pagination li a.current{height:38px;color:gray;background:#f7f7f7;margin-top:-1px;border-left:1px solid #d2d5d6;border-bottom:3px solid #d2d5d6;border-right:2px solid #d2d5d6}.pagination li a.ico-after:after{margin-top:12px}.pagination li a[href]:focus,.pagination li a[href]:hover{background:#d2d5d6}.pagination li.next a,.pagination li.prev a,.pagination li.summary-button a{padding:0 15px}.pagination li.prev .ico-after{padding-left:30px}.pagination li.prev .ico-after:after{margin-left:8px}.pagination li.next{margin-left:auto}.pagination li.next .ico-after{padding-right:30px}.pagination li.next .ico-after:after{right:8px;left:auto}.pagination li.summary-button{position:absolute;left:47%;display:none}.pagination.pagination-top li a.current{margin-top:0;border-top:3px solid #d2d5d6;border-bottom:none;height:35px;line-height:35px;padding-bottom:3px}.pagination.pagination-chapter{margin-left:0}.pagination.pagination-chapter li{max-width:43%}.pagination.pagination-chapter a{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}@media only screen and (min-width:960px){.pagination{border:1px solid #d2d5d6}}@media only screen and (max-width:759px){.pagination li.next a,.pagination li.prev a{min-width:0}.pagination li.next a span,.pagination li.prev a span{display:none}}@media only screen and (max-width:959px){.pagination li.summary-button{display:none}}.codehilite .hll{background-color:#ffc}.codehilite{background:#f8f8f8}.codehilite .c{color:#408080}.codehilite .k{color:green;font-weight:700}.codehilite .o{color:#666}.codehilite .cm{color:#408080}.codehilite .cp{color:#bc7a00}.codehilite .c1,.codehilite .cs{color:#408080}.codehilite .gd{color:#a00000}.codehilite .ge{font-style:italic}.codehilite .gr{color:red}.codehilite .gh{color:navy;font-weight:700}.codehilite .gi{color:#00a000}.codehilite .go{color:gray}.codehilite .gp{color:navy;font-weight:700}.codehilite .gs{font-weight:700}.codehilite .gu{color:purple;font-weight:700}.codehilite .gt{color:#0040d0}.codehilite .kc,.codehilite .kd,.codehilite .kn{color:green;font-weight:700}.codehilite .kp{color:green}.codehilite .kr{color:green;font-weight:700}.codehilite .kt{color:#b00040}.codehilite .m{color:#666}.codehilite .s{color:#ba2121}.codehilite .na{color:#7d9029}.codehilite .nb{color:green}.codehilite .nc{color:#00f;font-weight:700}.codehilite .no{color:#800}.codehilite .nd{color:#a2f}.codehilite .ni{color:#999;font-weight:700}.codehilite .ne{color:#d2413a;font-weight:700}.codehilite .nf{color:#00f}.codehilite .nl{color:#a0a000}.codehilite .nn{color:#00f;font-weight:700}.codehilite .nt{color:green;font-weight:700}.codehilite .nv{color:#19177c}.codehilite .ow{color:#a2f;font-weight:700}.codehilite .w{color:#bbb}.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo{color:#666}.codehilite .sb,.codehilite .sc{color:#ba2121}.codehilite .sd{color:#ba2121;font-style:italic}.codehilite .s2{color:#ba2121}.codehilite .se{color:#b62;font-weight:700}.codehilite .sh{color:#ba2121}.codehilite .si{color:#b68;font-weight:700}.codehilite .sx{color:green}.codehilite .sr{color:#b68}.codehilite .s1{color:#ba2121}.codehilite .ss{color:#19177c}.codehilite .bp{color:green}.codehilite .vc,.codehilite .vg,.codehilite .vi{color:#19177c}.codehilite .il{color:#666}.codehilitetable{width:100%!important;table-layout:fixed;border-color:rgba(0,0,0,.15)}.codehilitetable td{padding:0;vertical-align:top}.codehilitetable .linenos{background-color:#fbfbfc;border-right:1px solid #ececf0;width:46px}.codehilitetable .codehilite pre,.codehilitetable .linenos{padding-top:15px;padding-bottom:15px}.codehilitetable .linenodiv pre{text-align:right;padding-right:7px;color:#bebec5}.codehilitetable .codehilite{width:100%;height:auto;overflow:auto}.codehilitetable .codehilite pre{white-space:pre;overflow:auto}.codehilitetable .code pre{overflow:auto;word-wrap:normal;padding-left:7px;padding-right:7px}.search-box{background:#fff;position:relative;margin:30px auto 0;max-width:820px}.search-box form{display:-webkit-box;display:-ms-flexbox;display:flex}.search-box input,.search-box label{-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;line-height:50px;height:50px}.search-box label{text-align:right;padding:0 5px;font-size:2rem;font-weight:300;margin-left:50px}.search-box input{font-size:2rem;border:none;font-weight:300;-webkit-box-flex:1;-ms-flex:1;flex:1}.search-box button[type=submit]{background-color:#fff!important;width:50px;height:50px}.search-box button[type=submit]:focus,.search-box button[type=submit]:hover{background-color:#ccc!important}.search-box button[type=submit]:after{margin:16px!important;background-position:-256px -232px;width:16px;height:40px}.search-box:before{content:"";position:absolute;bottom:-6px;left:-28px;background:url(../images/home-clem.png);background-size:100%;width:68px;height:134px}body.vc-clem-christmas .search-box:before{background-image:url(../images/home-clem-christmas.png)}body.vc-clem-halloween .search-box:before{background-image:url(../images/home-clem-halloween.png);width:160px;left:-80px}.search-box .control-group{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;width:100%}.search-box .control-group .controls{width:100%}.search-box .control-group input{padding:0;width:100%!important}.search-results .content-item{margin-left:0}.search-filters{margin-right:auto;margin-left:auto;max-width:820px;padding-left:0;text-align:center;list-style:none}.search-filters li{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;padding-right:16px}.search-filters li input[type=checkbox]{margin-top:8px}.search-filters label{color:#fff}@media only screen and (max-width:759px){.search-box{margin:30px 0 0!important;padding-left:40px}.search-box label{display:none}.search-box:before{left:-46px}.search-filters{text-align:left;padding-left:10px}.search-filters li{display:list-item}}@media only screen and (max-width:959px){.search-box{margin:30px 40px 0}}.taglist{list-style:none;padding:0;margin:-14px 0 15px;height:30px;line-height:30px}.taglist li{float:right}.taglist li a{display:block;text-decoration:none;padding:0 10px;background:#396a81;color:#fff;margin-left:1px;-webkit-transition-property:color,background,border;transition-property:color,background,border;-webkit-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.15s;transition-duration:.15s}.taglist li a:focus,.taglist li a:hover{background:#fff;color:#396a81;border-bottom:1px solid #396a81}.content-tags-list{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.content-tags-list:after{content:"";display:block;-webkit-box-flex:20;-ms-flex:20;flex:20}.content-tag{margin:0 5px 20px;line-height:1.4em;white-space:nowrap;-webkit-box-flex:1;-ms-flex:auto;flex:auto}.content-tag a{color:#777;display:block;padding:8px 15px;text-decoration:none;background-color:#eee;-webkit-transition-property:color,background,border,outline;transition-property:color,background,border,outline;-webkit-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.15s;transition-duration:.15s;border:1px solid #ccc}.content-tag a:focus,.content-tag a:hover{color:#eee;background-color:#777;border-color:#777;outline:none}.content-tag a .tag-count{color:#aaa}.tooltips-container .tooltip-wrapper{position:absolute;z-index:7}.tooltips-container .tooltip-wrapper .tooltip{font-size:12px;line-height:16px;color:#fff;background-color:#333;padding:6px 8px}.tooltips-container .tooltip-wrapper.top:after{border-top:6px solid #333}.tooltips-container .tooltip-wrapper.bottom:before{border-bottom:6px solid #333}.tooltips-container .tooltip-wrapper.bottom:before,.tooltips-container .tooltip-wrapper.top:after{margin:auto;content:"";height:0;width:0;display:block;border-left:6px solid transparent;border-right:6px solid transparent}.topic-list{margin-top:50px!important;margin-bottom:50px!important}.topic-list h2{margin-bottom:0!important}.topic-list h2+.topic{border-top:none}.topic-list .topic{position:relative;min-height:81px;line-height:25px;border-top:1px solid #fff;border-bottom:1px solid #ccc;overflow:hidden;border-left:1px solid transparent;clear:both}.topic-list .topic:first-child{border-top:1px solid #ccc}.topic-list .topic:before{content:" ";display:block;position:absolute;background:transparent;height:100%;width:2px}.topic-list .topic.unread:before{background:#1088bf}.topic-list .topic:nth-child(2n){background:none}.topic-list .topic.unread{background:#fff}.topic-list .topic.unread .topic-description .topic-title{font-weight:700}.topic-list .topic.active:before,.topic-list .topic:hover:before{width:5px;background:#1088bf}.topic-list .topic.selected{background-color:#eaf7fd}.topic-list a{text-decoration:none;color:#0e77a8}.topic-list a:focus,.topic-list a:hover{color:#0e77a8;text-decoration:underline;outline:none}.topic-list .topic-answers,.topic-list .topic-description,.topic-list .topic-infos,.topic-list .topic-last-answer{display:block;float:left;padding:4px 0;margin:0}.topic-list .topic-infos{width:8%}.topic-list .topic-infos input[type=checkbox]{margin:29px 25% 0}.topic-list .topic-infos .ico-after{display:block;text-indent:-9999px}.topic-list .topic-infos .ico-after:after{margin:4px 0 0 15px}.topic-list .topic-description{position:relative;width:60%}.topic-list .topic-description .topic-image{float:left;max-height:60px;max-width:60px;margin:5px 15px 0 0}.topic-list .topic-description .topic-tags{list-style:none;padding:0;margin:0;display:inline}.topic-list .topic-description .topic-tags .topic-tag{display:block;height:23px;line-height:23px;float:left;padding:0 5px;margin:0 3px 0 0;color:#396a81;background:#fcfcfc;border:1px solid #ccc}.topic-list .topic-description .topic-tags .topic-tag:focus,.topic-list .topic-description .topic-tags .topic-tag:hover{background:#fff;color:#084561;border-color:#084561;text-decoration:none}.topic-list .topic-description .topic-tags .topic-tag:focus{-webkit-box-shadow:#396a81 0 0 3px;box-shadow:0 0 3px #396a81}.topic-list .topic-description .topic-tags li:last-child .topic-tag{margin-right:5px}.topic-list .topic-description .topic-title-link{display:block;min-height:48px}.topic-list .topic-description .topic-title-link:focus,.topic-list .topic-description .topic-title-link:hover{text-decoration:none}.topic-list .topic-description .topic-title-link:focus .topic-title,.topic-list .topic-description .topic-title-link:hover .topic-title{text-decoration:underline}.topic-list .topic-description .topic-subtitle,.topic-list .topic-description .topic-title{margin:0!important;padding:0}.topic-list .topic-description .topic-title{display:inline-block;font-size:16px;font-size:1.6rem;font-weight:400}.topic-list .topic-description .topic-subtitle{display:block;min-height:24px;line-height:1.5em;color:#777}.topic-list .topic-description .topic-members{margin:0;color:#777}.topic-list .topic-answers{width:12%;text-align:center;padding-top:29px}.topic-list .topic-last-answer{width:20%}.topic-list .topic-last-answer .topic-no-last-answer{display:block;margin-top:27px;color:#084561;opacity:.5}.topic-list .highlighted{background-color:rgba(255,255,100,.4);padding:0 3px;border-radius:2px}.forum-list .group-title{max-width:100%;margin-top:30px!important;clear:both;border-bottom:1px solid #ccc;color:#f8ad32}.topic-list-small .topic{min-height:60px}.topic-list-small .topic-infos input[type=checkbox]{margin-top:18px}.topic-list-small .topic-description{padding:4px 1.5%}.topic-list-small .topic-description .topic-title{display:block;font-weight:400;margin-top:2px}.topic-list-small .topic-infos+.topic-description{padding-left:0}.topic-list-small .topic-answers{padding-top:17px}.topic-list-small .topic-answers span{display:block;float:left;width:50%}.topic-list-small .topic-last-answer{width:18%}.topic-list-small .topic-last-answer .topic-no-last-answer{margin-top:13px}.topic-list-small .topic-last-answer .forum-last-message{display:block}.topic-list-small .topic-last-answer .forum-last-message .forum-last-message-long{display:none}.topic-list-small .topic-last-answer .forum-last-message-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media only screen and (min-width:960px){.topic-list .topic-last-answer-short-date,.topic-list .topic-members-short-date{display:none}.topic-list:not(.topic-list-small) .topic-last-answer .topic-no-last-answer{margin-top:24px}.forum-list .topic{min-height:0}.forum-list .topic-last-answer .forum-last-message .forum-last-message-long{display:none}}@media only screen and (max-width:959px){.topic-list .topic{background:none!important}.topic-list .topic p{margin:0!important}.topic-list .topic .topic-answers,.topic-list .topic .topic-members .topic-members-long-date{display:none}.topic-list .topic .topic-last-answer{width:30%;padding:0;text-align:right}.topic-list .topic .topic-last-answer .topic-last-answer-short-date{font-size:1.3rem}.topic-list .topic .topic-last-answer .topic-last-answer-long-date{display:none}.topic-list .topic .topic-last-answer .topic-no-last-answer{text-align:center}}@media only screen and (max-width:759px){.topic-list .topic-infos .ico-after:after{margin:4px 0 0 2px}.topic-list .topic-description .topic-subtitle:empty{display:none}.topic-list .topic-last-answer .topic-no-last-answer{font-size:1.3rem}.forum-list .topic-description .topic-subtitle{margin-left:10px}}.notification-list{margin-top:50px!important;margin-bottom:50px!important}.notification-list .notification{position:relative;line-height:25px;border-top:1px solid #fff;border-bottom:1px solid #ccc;overflow:hidden;border-left:1px solid transparent;clear:both}.notification-list .notification:first-child{border-top:1px solid #ccc}.notification-list .notification:before{content:" ";display:block;position:absolute;background:transparent;height:100%;width:2px}.notification-list .notification.unread:before{background:#1088bf}.notification-list .notification:nth-child(2n){background:none}.notification-list .notification.unread{background:#fff}.notification-list .notification.unread .notification-description .notification-title{font-weight:700}.notification-list .notification.active:before,.notification-list .notification:hover:before{width:5px;background:#1088bf}.notification-list .notification.selected{background-color:#eaf7fd}.notification-list a{text-decoration:none;color:#0e77a8}.notification-list a:focus,.notification-list a:hover{color:#0e77a8;text-decoration:underline;outline:none}.notification-list .notification-description,.notification-list .notification-infos,.notification-list .notification-last-answer{display:block;float:left;padding:4px 0;margin:0}.notification-list .notification-infos{width:5%}.notification-list .notification-infos .ico-after{display:block;text-indent:-9999px}.notification-list .notification-infos .ico-after:after{margin:4px 0 0 15px}.notification-list .notification-description{position:relative;width:60%}.notification-list .notification-description .notification-title-link{display:block}.notification-list .notification-description .notification-title-link:focus,.notification-list .notification-description .notification-title-link:hover{text-decoration:none}.notification-list .notification-description .notification-title-link:focus .topic-title,.notification-list .notification-description .notification-title-link:hover .topic-title{text-decoration:underline}.notification-list .notification-description .notification-title{display:block;margin:0!important;padding:0;font-size:16px;font-size:1.6rem;font-weight:400}.notification-list .notification-last-answer{width:35%}@media only screen and (min-width:960px){.notification-list .notification-last-answer-short-date{display:none}}@media only screen and (max-width:959px){.notification-list .notification{background:none!important}.notification-list .notification .notification-last-answer{width:30%;text-align:right}.notification-list .notification .notification-last-answer .notification-last-answer-short-date{font-size:1.3rem}.notification-list .notification .notification-last-answer .notification-last-answer-long-date{display:none}}@media only screen and (max-width:759px){.notification-list .notification-infos .ico-after:after{margin:4px 0 0 2px}.notification-list .notification{background:none!important}.notification-list .notification .notification-last-answer{width:20%}}.topic-message{position:relative}.topic-message.repeated .message,.topic-message.repeated .message .is-author{background:#eee}.topic-message.repeated .message:after{border-right-color:#eee}.topic-message.helpful .message,.topic-message.helpful .message .is-author{background:#e9f9dc}.topic-message.helpful .message:after{border-right-color:#e9f9dc}.topic-message.helpful.repeated .message,.topic-message.helpful.repeated .message .is-author{background:#eaefe6}.topic-message.helpful.repeated .message:after{border-right-color:#eaefe6}.topic-message .user .avatar-link{display:block;height:58px;width:58px;z-index:0;position:absolute;top:0;border:1px solid #ddd}.topic-message .user .avatar-link[href]:focus,.topic-message .user .avatar-link[href]:hover{border-color:#fff;overflow:hidden;-webkit-box-shadow:rgba(0,0,0,.3) 0 1px 7px;box-shadow:0 1px 7px rgba(0,0,0,.3)}.topic-message .user .avatar-link img{height:58px;width:58px}.topic-message .user .user-metadata{width:60px;height:25px}.topic-message .user .user-metadata a{display:block;float:left;border:1px solid #d2d5d6;border-top:0;text-align:center;background-color:#edefef;text-decoration:none;color:#424242;height:25px;line-height:26px;width:58px;color:#777;-webkit-transition:border .15s ease,background .15s ease;transition:border .15s ease,background .15s ease}.topic-message .user .user-metadata a:focus,.topic-message .user .user-metadata a:hover{border-bottom-width:1px;border-bottom-color:#777;background:#fff}.topic-message .user .user-metadata a.positive{color:#48a200}.topic-message .user .user-metadata a.negative{color:#c0392b;font-weight:700}.topic-message .message{position:relative;background-color:#fdfdfd;border:1px solid #d2d5d6;border-right-width:2px;border-bottom-width:3px;min-height:75px}.topic-message .message .is-author{position:absolute;top:-16px;left:10px;background:#fdfdfd;padding:0 5px;font-size:12px;line-height:20px;color:#999;border-top:1px solid #d2d5d6}.topic-message .message .is-author:after,.topic-message .message .is-author:before{content:" ";display:block;position:absolute;top:0;height:15px;width:1px;background:#d2d5d6}.topic-message .message .is-author:before{left:0}.topic-message .message .is-author:after{right:0}.topic-message .message .message-metadata{display:inline-block;font-size:14px;font-size:1.4rem;margin-left:5px}.topic-message .message .message-metadata a{display:block;float:left;color:#999;text-decoration:none;height:30px;line-height:30px;padding:0 5px;border-bottom:1px solid #d2d5d6;-webkit-transition-property:color,outline,border;transition-property:color,outline,border;-webkit-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.15s;transition-duration:.15s}.topic-message .message .message-metadata a:focus,.topic-message .message .message-metadata a:hover{border-bottom:1px solid #0e77a8;color:#0e77a8;outline:none}.topic-message .message .message-metadata .username{color:#484848;font-size:16px;font-size:1.6rem;margin-right:3px}.topic-message .message .message-metadata .date{line-height:32px}.topic-message .message .message-metadata .date .long-date{display:none}.topic-message .message .message-actions{margin:0;padding:0;list-style:none;position:absolute;top:0;right:0}.topic-message .message .message-actions li{float:left}.topic-message .message .message-content{clear:both;padding-top:1px}.topic-message .message .message-content>div>p:first-child{margin-top:7px}.topic-message .message .message-content>div>figure:first-child{margin-top:8px}.topic-message .message .message-content .message-hidden-content{display:none}.topic-message .message .message-content .with-hat{color:#fff;background-color:#6f6f6f;display:inline-block;padding:0;margin-top:5px;margin-bottom:5px}.topic-message .message .message-content .with-hat>a{color:#fff;text-decoration:none;display:inline-block;padding:0 6px}.topic-message .message .message-content .staff-hat{background-color:#2b5c73}.topic-message .message .message-content .message-edited,.topic-message .message .message-content .message-helpful,.topic-message .message .message-content .message-hidden,.topic-message .message .message-content .message-repeated{padding-top:3px 0 0}.topic-message .message .message-content .message-edited.ico-after,.topic-message .message .message-content .message-helpful.ico-after,.topic-message .message .message-content .message-hidden.ico-after,.topic-message .message .message-content .message-repeated.ico-after{text-indent:20px}.topic-message .message .message-content .message-edited.ico-after:after,.topic-message .message .message-content .message-helpful.ico-after:after,.topic-message .message .message-content .message-hidden.ico-after:after,.topic-message .message .message-content .message-repeated.ico-after:after{margin:4px 0}.topic-message .message .message-content .message-edited,.topic-message .message .message-content .message-hidden,.topic-message .message .message-content .message-repeated{font-style:italic;color:#999}.topic-message .message .message-content .message-edited>a,.topic-message .message .message-content .message-hidden>a,.topic-message .message .message-content .message-repeated>a{color:#999}.topic-message .message .message-content .message-edited:after,.topic-message .message .message-content .message-hidden:after,.topic-message .message .message-content .message-repeated:after{opacity:.5}.topic-message .message .message-content .message-hidden{margin-top:1px}.topic-message .message .message-content .message-helpful{color:#48a200;text-indent:20px}.topic-message .message .message-content textarea{margin:10px 0 10px -1px;background-color:transparent;min-height:150px}.topic-message .message .message-bottom{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;min-height:30px}.topic-message .message .message-bottom .signature{border-top:1px solid #d2d5d6;padding:3px 0 3px 10px;margin:0 10px 0 0;font-size:12px;font-size:1.2rem;color:#999;-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:hidden}.topic-message .message .message-bottom .signature p{margin:0;padding:0}.topic-message .message .message-bottom .signature a{color:#999;-webkit-transition:color .15s ease,-webkit-text-decoration .15s ease;transition:color .15s ease,-webkit-text-decoration .15s ease;transition:color .15s ease,text-decoration .15s ease;transition:color .15s ease,text-decoration .15s ease,-webkit-text-decoration .15s ease}.topic-message .message .message-bottom .signature a:focus,.topic-message .message .message-bottom .signature a:hover{text-decoration:none;color:#555}.topic-message .message .message-bottom .message-karma{margin-left:auto;margin-bottom:-2px}.topic-message .message .message-bottom .message-karma button.ico-after,.topic-message .message .message-bottom .message-karma span{border-bottom-width:3px;border-bottom-color:transparent;background:none!important;height:32px}.topic-message .message .message-bottom .message-karma span.downvote:not(.has-vote),.topic-message .message .message-bottom .message-karma span.upvote:not(.has-vote){border-bottom:none;opacity:.5}.topic-message .message .message-bottom .message-karma button{-webkit-transition-property:opacity,border;transition-property:opacity,border}.topic-message .message .message-bottom .message-karma button.voted:hover:after{opacity:.5}.topic-message .message .message-bottom .message-karma .downvote:after,.topic-message .message .message-bottom .message-karma .upvote:after{left:10px}.topic-message .message .message-bottom .message-karma .downvote.voted:after,.topic-message .message .message-bottom .message-karma .upvote.voted:after{opacity:1}.topic-message .message .message-bottom .message-karma .downvote:focus:not(.more-voted),.topic-message .message .message-bottom .message-karma .downvote:hover:not(.more-voted),.topic-message .message .message-bottom .message-karma .upvote:focus:not(.more-voted),.topic-message .message .message-bottom .message-karma .upvote:hover:not(.more-voted){border-bottom-color:transparent}.topic-message .message .message-bottom .message-karma .downvote:not(.has-vote),.topic-message .message .message-bottom .message-karma .upvote:not(.has-vote){text-indent:-9999px;width:0}.topic-message .message .message-bottom .message-karma .downvote.more-voted,.topic-message .message .message-bottom .message-karma .upvote.more-voted{font-weight:700}.topic-message .message .message-bottom .message-karma .upvote{color:#48a200}.topic-message .message .message-bottom .message-karma .upvote.more-voted{border-bottom-color:#48a200}.topic-message .message .message-bottom .message-karma .downvote{color:#c0392b}.topic-message .message .message-bottom .message-karma .downvote.more-voted{border-bottom-color:#c0392b}.topic-message .message .message-bottom .message-karma .tick{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.topic-message .message .message-bottom .message-karma .tick:focus,.topic-message .message .message-bottom .message-karma .tick:hover{color:#555;border-bottom-color:#48a200}.topic-message .message .message-bottom .message-karma .tick.active{color:#48a200}.topic-message .message .message-bottom .message-karma .tick.active:after{opacity:1}.topic-message .message .message-buttons{margin:0 0 0 10px;padding:0;list-style:none;border-bottom:none}.topic-message .message .message-buttons a{text-indent:-9999px;width:0}.topic-message .message .message-buttons a:after{left:12px!important}.topic-message .message .message-submit{margin-left:auto;margin-right:10px}.topic-message .message .message-actions,.topic-message .message .message-buttons,.topic-message .message .message-karma,.topic-message .message .message-submit{display:-webkit-box;display:-ms-flexbox;display:flex}.topic-message .message .message-actions form,.topic-message .message .message-buttons form,.topic-message .message .message-karma form,.topic-message .message .message-submit form{width:auto}.topic-message .message .message-actions .downvote,.topic-message .message .message-actions .upvote,.topic-message .message .message-actions a,.topic-message .message .message-actions button,.topic-message .message .message-buttons .downvote,.topic-message .message .message-buttons .upvote,.topic-message .message .message-buttons a,.topic-message .message .message-buttons button,.topic-message .message .message-karma .downvote,.topic-message .message .message-karma .upvote,.topic-message .message .message-karma a,.topic-message .message .message-karma button,.topic-message .message .message-submit .downvote,.topic-message .message .message-submit .upvote,.topic-message .message .message-submit a,.topic-message .message .message-submit button{display:block;float:left;margin-left:3px}.topic-message .message .message-actions .downvote.ico-after,.topic-message .message .message-actions .upvote.ico-after,.topic-message .message .message-actions a.ico-after,.topic-message .message .message-actions button.ico-after,.topic-message .message .message-buttons .downvote.ico-after,.topic-message .message .message-buttons .upvote.ico-after,.topic-message .message .message-buttons a.ico-after,.topic-message .message .message-buttons button.ico-after,.topic-message .message .message-karma .downvote.ico-after,.topic-message .message .message-karma .upvote.ico-after,.topic-message .message .message-karma a.ico-after,.topic-message .message .message-karma button.ico-after,.topic-message .message .message-submit .downvote.ico-after,.topic-message .message .message-submit .upvote.ico-after,.topic-message .message .message-submit a.ico-after,.topic-message .message .message-submit button.ico-after{padding-left:30px!important}.topic-message .message .message-actions .downvote:after,.topic-message .message .message-actions .upvote:after,.topic-message .message .message-actions a:after,.topic-message .message .message-actions button:after,.topic-message .message .message-buttons .downvote:after,.topic-message .message .message-buttons .upvote:after,.topic-message .message .message-buttons a:after,.topic-message .message .message-buttons button:after,.topic-message .message .message-karma .downvote:after,.topic-message .message .message-karma .upvote:after,.topic-message .message .message-karma a:after,.topic-message .message .message-karma button:after,.topic-message .message .message-submit .downvote:after,.topic-message .message .message-submit .upvote:after,.topic-message .message .message-submit a:after,.topic-message .message .message-submit button:after{top:7px;left:7px;opacity:.5;margin:0}.topic-message .message .message-actions .downvote,.topic-message .message .message-actions .upvote,.topic-message .message .message-actions a,.topic-message .message .message-actions button.ico-after,.topic-message .message .message-buttons .downvote,.topic-message .message .message-buttons .upvote,.topic-message .message .message-buttons a,.topic-message .message .message-buttons button.ico-after,.topic-message .message .message-karma .downvote,.topic-message .message .message-karma .upvote,.topic-message .message .message-karma a,.topic-message .message .message-karma button.ico-after,.topic-message .message .message-submit .downvote,.topic-message .message .message-submit .upvote,.topic-message .message .message-submit a,.topic-message .message .message-submit button.ico-after{border-bottom:1px solid #d2d5d6;text-decoration:none;color:#999;height:29px;line-height:30px;padding:0 10px}.topic-message .message .message-actions a,.topic-message .message .message-actions button.ico-after,.topic-message .message .message-buttons a,.topic-message .message .message-buttons button.ico-after,.topic-message .message .message-karma a,.topic-message .message .message-karma button.ico-after,.topic-message .message .message-submit a,.topic-message .message .message-submit button.ico-after{cursor:pointer}.topic-message .message .message-actions a:focus,.topic-message .message .message-actions a:hover,.topic-message .message .message-actions button.ico-after:focus,.topic-message .message .message-actions button.ico-after:hover,.topic-message .message .message-buttons a:focus,.topic-message .message .message-buttons a:hover,.topic-message .message .message-buttons button.ico-after:focus,.topic-message .message .message-buttons button.ico-after:hover,.topic-message .message .message-karma a:focus,.topic-message .message .message-karma a:hover,.topic-message .message .message-karma button.ico-after:focus,.topic-message .message .message-karma button.ico-after:hover,.topic-message .message .message-submit a:focus,.topic-message .message .message-submit a:hover,.topic-message .message .message-submit button.ico-after:focus,.topic-message .message .message-submit button.ico-after:hover{border-bottom-color:#0e77a8;outline:none;background:none;-webkit-transition-property:background,border,outline,opacity;transition-property:background,border,outline,opacity;-webkit-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.15s;transition-duration:.15s}.topic-message .message .message-actions a:focus:after,.topic-message .message .message-actions a:hover:after,.topic-message .message .message-actions button.ico-after:focus:after,.topic-message .message .message-actions button.ico-after:hover:after,.topic-message .message .message-buttons a:focus:after,.topic-message .message .message-buttons a:hover:after,.topic-message .message .message-buttons button.ico-after:focus:after,.topic-message .message .message-buttons button.ico-after:hover:after,.topic-message .message .message-karma a:focus:after,.topic-message .message .message-karma a:hover:after,.topic-message .message .message-karma button.ico-after:focus:after,.topic-message .message .message-karma button.ico-after:hover:after,.topic-message .message .message-submit a:focus:after,.topic-message .message .message-submit a:hover:after,.topic-message .message .message-submit button.ico-after:focus:after,.topic-message .message .message-submit button.ico-after:hover:after{opacity:1}.topic-message .message .message-actions a:focus,.topic-message .message .message-actions a:hover,.topic-message .message .message-buttons a:focus,.topic-message .message .message-buttons a:hover,.topic-message .message .message-karma button:focus,.topic-message .message .message-karma button:hover{color:#555;text-decoration:none}.topic-message .message .alert-box .alert-box-text{float:none}form.topic-message{margin-top:50px}@media only screen and (max-width:959px){.topic-message{padding:20px 0}.topic-message .user{position:absolute;top:7px;z-index:4;width:100%}.topic-message .user .avatar-link{float:left;display:none}.topic-message .user .badge{float:left;height:20px;line-height:20px;font-size:12px;width:50px;margin-top:-2px;margin-left:10px}.topic-message .user .badge.push-badge{margin-left:105px}.topic-message .user .user-metadata{float:right;width:140px;margin-right:10px}.topic-message .user .user-metadata a{float:left;height:20px;line-height:20px;border-bottom:none;width:68px}.topic-message .message{border-right:0;border-left:0;padding-top:65px}.topic-message .message .message-metadata{position:absolute;top:0;left:0;right:10px;z-index:5;height:30px;line-height:30px}.topic-message .message .message-metadata .username{margin-left:5px}.topic-message .message .message-metadata .date{float:right}.topic-message .message .message-actions{margin:35px 10px 0 0}.topic-message .message .message-bottom{min-height:0}.topic-message .message .message-bottom .signature{display:none}.topic-message .message .message-bottom .message-karma{position:absolute;top:35px;left:7px}.topic-message .message .message-bottom .message-karma .tick{text-indent:-9999px;margin-right:1px}.topic-message .message .message-bottom .message-karma .tick:after{left:12px}.topic-message .message .message-bottom .message-karma .downvote,.topic-message .message .message-bottom .message-karma .upvote{padding:0 7px;text-align:center}}.message-content .message-hat-choice{display:inline-block;margin:10px}@media only screen and (min-width:960px){.topic-message{margin:25px 0}.topic-message:first-child{margin-top:35px}.topic-message .message:after,.topic-message .user:after{content:" ";display:block;position:absolute;top:10px;height:0;width:0;border:20px solid transparent;border-left:0}.topic-message .user{position:absolute;padding-top:60px;top:0;left:0}.topic-message .user:after{left:60px;border-right-color:#d2d4d6}.topic-message .message{margin-left:80px}.topic-message .message:after{top:9px;left:-19px;border-right-color:#fdfdfd}.topic-message .message .is-author{left:5px}.topic-message .message .message-content{margin:0 10px}.topic-message .message .message-content>:first-child{margin-top:5px}.topic-message .message .message-content>figure:first-child{margin-top:10px}.topic-message .message .message-bottom .signature{cursor:pointer}.topic-message .message .message-bottom .signature p{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.topic-message .message .message-bottom .signature.full p{white-space:normal}}@media only screen and (max-width:759px){.topic-message .message .message-actions a{width:0;text-indent:-9999px}.topic-message .message .message-actions a:after{left:12px!important}.topic-message .message .message-submit{display:block!important;width:100%;margin:0}.topic-message .message .message-submit button{float:right;display:block;width:calc(50% - 2px);margin-left:1px!important}.topic-message .message .message-submit button.btn-grey{float:left}form .message{padding-top:0!important}}#topic-result-container{background-color:#fff;border:1px solid #d2d5d6}#topic-result-container ul{list-style:none;font-size:12px;padding:0;margin:0}#topic-result-container ul li{padding:1px 10px;border-bottom:1px solid #ccc}#topic-result-container ul li.active,#topic-result-container ul li:hover{background-color:#d7d7d7}#topic-result-container ul li.active.neither,#topic-result-container ul li:hover.neither{background-color:transparent}#topic-result-container ul li:last-child{border-bottom:none}.avatar{height:60px;width:60px;background-color:#fff}.badge{display:block;width:60px;height:25px;line-height:25px;text-align:center;text-transform:uppercase;color:#eee;text-shadow:rgba(0,0,0,.25) 0 0 3px;background:#777}.member-card .member-avatar{float:left;width:60px}.member-card .member-infos{float:left;list-style:none;margin:0;padding-left:15px}.member-social{list-style:none;margin:15px 0 0;padding:0}.content-linkbox-list{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0 0 0 -20px}.content-linkbox-list .linkbox-item{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:33.33%;width:calc(33.33% - 20px);margin:0 0 20px 20px;color:#fff}.content-linkbox-list .linkbox-item .icon{width:25px;background:#fff}.content-linkbox-list .linkbox-item .head{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px}.content-linkbox-list .linkbox-item .head h3{font-size:2.2rem;line-height:32px;font-weight:400;margin:0;padding:0;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.content-linkbox-list .linkbox-item .body{display:block;padding:10px 10px 5px;font-size:1.3rem;line-height:1.7rem;border-top:1px solid rgba(0,0,0,.25)}.content-linkbox-list .linkbox-item .body p{margin:0 0 5px;padding:0}.content-linkbox-list .linkbox-item .body p.right{text-align:right}.content-linkbox-list .linkbox-item .tail{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px;border-top:1px solid hsla(0,0%,100%,.2)}.content-linkbox-list .linkbox-item .tail p{margin:0;padding:0;line-height:2.2rem;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.content-linkbox-list .linkbox-item a{position:relative;color:#fff;text-decoration:none}.content-linkbox-list .linkbox-item a:after{content:"";position:absolute;top:15px;right:15px;width:10px;height:10px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);border-color:#fff;border-style:solid;border-width:2px 2px 0 0;opacity:.5}.content-linkbox-list .linkbox-item a:focus:after,.content-linkbox-list .linkbox-item a:hover:after{opacity:1}.content-linkbox-list .linkbox-item a.head{padding-right:30px}.content-linkbox-list .linkbox-item a.head:after{top:20px}.content-linkbox-list .linkbox-item a.body:after{display:none}.content-linkbox-list .linkbox-item a.tail{padding-right:30px}.content-linkbox-list .linkbox-item .head{background:#5e5e5e}.content-linkbox-list .linkbox-item .body{background:#777}.content-linkbox-list .linkbox-item .tail{background:#848484}.content-linkbox-list .linkbox-item a:focus.head,.content-linkbox-list .linkbox-item a:hover.head{background:#515151}.content-linkbox-list .linkbox-item a:focus.body,.content-linkbox-list .linkbox-item a:hover.body{background:#6a6a6a}.content-linkbox-list .linkbox-item a:focus.tail,.content-linkbox-list .linkbox-item a:hover.tail{background:#777}.content-linkbox-list .linkbox-item.primary .head{background:#063449}.content-linkbox-list .linkbox-item.primary .body{background:#0a5679}.content-linkbox-list .linkbox-item.primary .tail{background:#0e77a8}.content-linkbox-list .linkbox-item.primary a:focus.head,.content-linkbox-list .linkbox-item.primary a:hover.head{background:#042332}.content-linkbox-list .linkbox-item.primary a:focus.body,.content-linkbox-list .linkbox-item.primary a:hover.body{background:#084561}.content-linkbox-list .linkbox-item.primary a:focus.tail,.content-linkbox-list .linkbox-item.primary a:hover.tail{background:#0c6790}.content-linkbox-list .linkbox-item.secondary .head{background:#d68807}.content-linkbox-list .linkbox-item.secondary .body{background:#f7a319}.content-linkbox-list .linkbox-item.secondary .tail{background:#f8ad32}.content-linkbox-list .linkbox-item.secondary a:focus.head,.content-linkbox-list .linkbox-item.secondary a:hover.head{background:#be7806}.content-linkbox-list .linkbox-item.secondary a:focus.body,.content-linkbox-list .linkbox-item.secondary a:hover.body{background:#ef9708}.content-linkbox-list .linkbox-item.secondary a:focus.tail,.content-linkbox-list .linkbox-item.secondary a:hover.tail{background:#f7a319}@media only screen and (min-width:1140px){.content-linkbox-list .linkbox-item{width:25%;width:calc(25% - 20px)}}@media only screen and (max-width:959px){.content-linkbox-list .linkbox-item{width:50%;width:calc(50% - 20px)}}@media only screen and (max-width:759px){.content-linkbox-list .linkbox-item{width:100%;width:calc(100% - 20px)}}.more-link{background:#fff;height:40px;display:block;border:1px solid #dedede;border-bottom-width:2px;margin:0 0 15px;line-height:40px;text-align:center;font-size:1.7rem;text-decoration:none}.flexpage .main{display:block;height:auto;width:auto;margin:0;padding:0}.flexpage #content{width:100%;margin:0;padding:0}.flexpage .sub-header{display:none}.flexpage .flexpage-wrapper{max-width:1145px;margin:0 auto}.flexpage .flexpage-header{margin-bottom:20px;border-bottom:1px solid #fff;background-color:#19516b;background:#19516b radial-gradient(at top,hsla(0,0%,100%,.1),transparent 60%)}.flexpage .flexpage-column{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;margin-left:-20px}.flexpage .flexpage-column section{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:calc(50% - 20px);margin-left:20px}.flexpage .flexpage-title-tool{position:relative;padding:50px 50px 50px 200px;font-size:2rem;font-weight:100}.flexpage .flexpage-title-tool .picto{position:absolute;left:50px;top:50px;width:104px;height:60.04px;margin:30.02px 50px 30.02px 0;background:rgba(0,0,0,.2)}.flexpage .flexpage-title-tool .picto:after,.flexpage .flexpage-title-tool .picto:before{z-index:0;content:"";position:absolute;width:0;border-left:52px solid transparent;border-right:52px solid transparent}.flexpage .flexpage-title-tool .picto:before{bottom:100%;left:0;border-bottom:30.02px solid rgba(0,0,0,.2)}.flexpage .flexpage-title-tool .picto:after{top:100%;left:0;width:0;border-top:30.02px solid rgba(0,0,0,.2)}.flexpage .flexpage-title-tool .picto img{position:absolute;top:-20px;left:2px}.flexpage .flexpage-title-tool .title{display:block;color:#fff;font-size:2.2rem}.flexpage .flexpage-title-tool .title .line{display:block;line-height:34px}.flexpage .flexpage-title-tool .title .line .line-item{display:inline-block}.flexpage .flexpage-title-tool .title h1{display:block;margin:0;padding:0;color:inherit;font-size:5rem;line-height:50px;border:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.flexpage .flexpage-title-tool .title h2{display:inline-block;margin:0;padding:0;vertical-align:bottom;font-size:inherit;line-height:inherit;color:inherit;border:none}.flexpage .flexpage-title-tool .title .option{display:inline-block;margin:0 15px 0 0}.flexpage .flexpage-title-tool .title a{display:inline;color:#fff;text-decoration:none}.flexpage .flexpage-title-tool .title .has-separator{position:relative;padding-left:25px;padding-right:5px}.flexpage .flexpage-title-tool .title .has-separator:after{content:"";position:absolute;top:12px;left:0;width:10px;height:10px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);border-color:#fff;border-style:solid;border-width:2px 2px 0 0;opacity:.5}.flexpage .flexpage-title-tool .aside{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:10px;margin-right:150px;height:50px}.flexpage .flexpage-title-tool .aside .search{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;background:#fff}.flexpage .flexpage-title-tool .aside .search label{line-height:50px;margin:0;padding:0 15px}.flexpage .flexpage-title-tool .aside .search input{line-height:50px;height:50px;margin:0;padding:0 15px;border:none;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:auto!important;min-width:0}.flexpage .flexpage-title-tool .aside .search button{width:50px;height:50px;line-height:50px;background:#fff}.flexpage .flexpage-title-tool .aside .search button:after{margin:16px!important;background-position:-256px -232px;width:16px;height:40px}.flexpage .flexpage-title-tool .aside .search button:focus,.flexpage .flexpage-title-tool .aside .search button:hover{background:#ccc!important}@media only screen and (max-width:959px){.flexpage .flexpage-wrapper{padding:20px 10px}.flexpage .flexpage-column{display:block;margin-left:0}.flexpage .flexpage-column section{width:100%;margin-left:0}.flexpage .flexpage-title-tool{padding:15px 25px}.flexpage .flexpage-title-tool .picto{display:none}.flexpage .flexpage-title-tool .aside{max-width:100%}}@media only screen and (max-width:759px){.flexpage .flexpage-title-tool{padding:15px 0}.flexpage .flexpage-title-tool .aside{margin-right:0}.flexpage .flexpage-title-tool .aside .search label{padding-right:5px}}.home .home-row{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.home .flexpage-header{margin-bottom:-170px;padding-top:20px;padding-bottom:180px}.home .home-description{display:-webkit-box;display:-ms-flexbox;display:flex}.home .home-description p{margin:0;padding:0;color:#fff;text-align:justify}.home .home-description ul{color:#eee;margin:10px 0}.home .home-description a:not(.home-description-button){color:#fff}.home .home-description a:not(.home-description-button):focus,.home .home-description a:not(.home-description-button):hover{color:#90abb6;text-decoration:none}.home .home-description .column{-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 20px}.home .home-description .column h2{font-size:18px;font-size:1.8rem;color:#fff;margin:20px 0 10px;border-bottom-color:#fff;font-weight:300}.home .home-description blockquote{font-size:2.5rem;color:#fff;font-weight:300;padding:0;margin:0}.home .home-description blockquote span:first-of-type:before{content:"«\00A0"}.home .home-description blockquote span:last-of-type:after{content:"\00A0»"}.home .home-description:not(.connected):not(.short){padding-bottom:60px}.home .home-description.connected{text-align:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.home .home-description.connected .important{color:#f8ad32;text-transform:uppercase;font-weight:700}.home .home-description.connected p{margin-top:5px;text-align:center}.home .home-description.connected .home-description-button{margin-left:15px}.home .home-description.short{display:none;color:#fff;text-align:center;font-size:1.4em}.home .home-description.short .home-description-button{font-size:14px;font-size:1.4rem;line-height:24px;line-height:2.4rem;margin-top:12px;padding:0 10px}.home .home-description.short blockquote>span{display:inline-block}.home .home-description-button{display:inline-block;line-height:2rem;font-size:1.2rem;font-size:12px;color:#fff;text-decoration:none;border:1px solid hsla(0,0%,100%,.5);padding:0 6px;margin-top:5px}.home .home-description-button:focus,.home .home-description-button:hover{color:#084561;border-color:#fff;background-color:#fff}.home .home-description-button.close-description{display:none}.home .featured-resource-row{margin-bottom:30px;padding:1px 0 1px 1px;background-color:#f7f7f7;-ms-flex-wrap:wrap;flex-wrap:wrap;width:100%}.home .featured-resource-row,.home .featured-resource-row .no-featured-resource{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.home .featured-resource-row .no-featured-resource{margin:0;text-align:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#777;padding:1em;height:230px}.home .home-heading.heading-white{color:#fff;border-bottom-color:#fff}@media only screen and (max-width:759px){.home .home-description:not(.connected):not(.short){display:none}.home .home-description.short{display:block;width:auto;padding:0 20px}.home .home-description.short:target .home-description-button{display:none}.home .home-description.short:target .home-description-button.close-description{display:inline-block}.home .home-description.short:target~.home-description:not(.short){display:block;margin-top:20px}.home .home-description.connected{padding:0 20px!important}.home .home-description .featured-message{display:none}.home .home-heading .btn{visibility:hidden}.home .featured-resource-row .featured-resource-item:nth-of-type(4){display:none}}@media only screen and (max-width:959px){.home .flexpage-header{padding-top:10px;padding-bottom:10px;margin-bottom:0}.home .featured-resource-row .featured-resource-item{margin:4px;padding:0!important}.home .featured-resource-row .featured-resource-item:last-of-type{display:none}.home .home-heading{margin-top:18px}.home .home-heading.heading-white{color:#084561;border-color:#f8ad32}.home .home-row{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.home .home-description.connected{width:auto;padding:0 80px}}@media only screen and (min-width:960px){.home .home-row{margin-right:-10px;margin-left:-10px}.home .home-row>section{margin:0 10px;-webkit-box-flex:1;-ms-flex:1;flex:1;min-width:300px}.home .home-description .column h2{font-size:22px;font-size:2.2rem}.home .home-description .column p,.home .home-description .column ul{line-height:22px;font-size:15px;font-size:1.5rem}.home .home-description.connected{max-width:740px;margin:15px auto 0}}.gallery.grid-view{clear:both}.gallery.grid-view .gallery-item{position:relative;width:200px;height:200px;float:left;border:10px solid #fff;margin:10px;clear:none}.gallery.grid-view .gallery-item.active,.gallery.grid-view .gallery-item:hover{border-color:#1088bf!important}.gallery.grid-view .gallery-item.active:before,.gallery.grid-view .gallery-item:hover:before{display:none}.gallery.grid-view .gallery-item.active .topic-title,.gallery.grid-view .gallery-item:hover .topic-title{background:#1088bf!important;color:#fff;text-decoration:none}.gallery.grid-view .gallery-item .topic-infos{position:absolute;bottom:0;left:0;z-index:1;height:15px;width:15px;padding:3px 0}.gallery.grid-view .gallery-item .topic-infos input{margin:0}.gallery.grid-view .gallery-item .topic-description,.gallery.grid-view .gallery-item .topic-description a{display:block;width:100%;height:100%}.gallery.grid-view .gallery-item .topic-image{overflow:hidden;max-height:100%;min-width:100%}.gallery.grid-view .gallery-item .topic-title{height:15px;background-color:#fff;position:absolute;bottom:7px;left:0;right:0;padding:10px 20px 5px;font-size:15px;font-size:1.5rem;line-height:15px;color:#444}.gallery.grid-view .gallery-item.selected{border-color:#eaf7fd}.gallery.grid-view .gallery-item.selected .topic-title{background:#eaf7fd}.gallery.grid-view .gallery-item.add-image{font-size:120px;line-height:200px;text-align:center;background:#ddd;color:#555;text-decoration:none}.gallery.grid-view .gallery-item.add-image:focus,.gallery.grid-view .gallery-item.add-image:hover{background-color:#ccc;color:#1088bf}.gallery.list-view .topic .topic-description .topic-title{margin-top:12px}.gallery.list-view .add-image{display:none}.toggle-gallery-view{float:left!important}@media only screen and (min-width:960px){.gallery-col-image{float:left;width:50%}.gallery-col-image img{max-width:100%}.gallery-col-edit{float:right;width:calc(50% - 20px);padding-left:20px}}#resources_container .footer{display:none}.searchpage .flexpage-header{padding-top:50px}.searchpage .pagination-top{margin-top:30px}@media only screen and (max-width:959px){.pagination-top{margin-top:10px}}.tutorial-help-item{min-height:60px;padding:20px 2%;border-bottom:1px solid #e0e4e5;color:#424242;font-weight:400}.tutorial-help-item:nth-child(odd){background-color:hsla(0,0%,100%,.8)}.tutorial-help-item p{margin:0}.tutorial-help-item .tutorial-title{margin:0;padding:0;font-size:20px;font-size:2rem;height:27px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;clear:none;font-weight:400;color:#424242}.tutorial-help-item a{text-decoration:none}.tutorial-help-item a:focus,.tutorial-help-item a:hover{text-decoration:underline}.tutorial-help-item .tutorial-categories{margin:0 0 5px;padding:0;color:#ee8709}.tutorial-help-item .tutorial-categories a{color:#ee8709}.tutorial-help-item .tutorial-categories a:focus,.tutorial-help-item .tutorial-categories a:hover{text-decoration:underline}.tutorial-help-item .tutorial-illu{display:block;overflow:hidden;float:left}.tutorial-help-item .tutorial-infos{margin:7px 0 0 70px}.tutorial-help-item .tutorial-infos.no-illu{margin-left:0}.tutorial-help-item .tutorial-help{margin:12px 0 0}.tutorial-help-item .tutorial-help img.light{opacity:.2}.tutorial-help-item .tutorial-help img.light:focus,.tutorial-help-item .tutorial-help img.light:hover{opacity:.5}.commits-compare-form button{float:none!important}@media only screen and (-webkit-min-device-pixel-ratio:1.3),only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min--moz-device-pixel-ratio:1.3),only screen and (min-device-pixel-ratio:1.3),only screen and (min-resolution:2dppx),only screen and (min-resolution:192dpi){.header-container .header-logo-link{background-image:url(../images/logo@2x.png)}.breadcrumb ol li:not(:last-child):after,.content-item .content-reactions,.content-item .content-reactions:before,.ico,.ico-after:after,.main-container input[type=checkbox]:after,.main-container input[type=radio]:after,.modals-container input[type=checkbox]:after,.modals-container input[type=radio]:after{background-image:url(../images/sprite@2x.png);background-size:324px 312px}.home .home-search-box:before{background-image:url(../images/home-clem@2x.png)}body.vc-clem-christmas.home .home-search-box:before{background-image:url(../images/home-clem-christmas@2x.png)}body.vc-clem-halloween.home .home-search-box:before{background-image:url(../images/home-clem-halloween@2x.png)}}@media only screen and (-webkit-min-device-pixel-ratio:1.3) and (max-width:959px),only screen and (-webkit-min-device-pixel-ratio:2) and (max-width:959px),only screen and (min--moz-device-pixel-ratio:1.3) and (max-width:959px),only screen and (min-device-pixel-ratio:1.3) and (max-width:959px),only screen and (min-resolution:2dppx) and (max-width:959px),only screen and (min-resolution:192dpi) and (max-width:959px){.enable-mobile-menu .mobile-menu-hide .page-container .mobile-menu-btn:after{background-image:url(../images/sprite@2x.png);background-size:324px 312px}.page-container .header-logo-link{background-image:url(../images/logo-mobile@2x.png)!important}} /*# sourceMappingURL=main.css.map */ ================================================ FILE: packages/zmarkdown/public/css/zmd.css ================================================ @charset "UTF-8";.hljs{display:block;overflow-x:auto;padding:.5em;color:#000;background:#fff}.hljs-subst,.hljs-title{font-weight:400;color:#000}.hljs-comment,.hljs-quote{color:gray;font-style:italic}.hljs-meta{color:olive}.hljs-tag{background:#efefef}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-type{font-weight:700;color:navy}.hljs-attribute,.hljs-link,.hljs-number,.hljs-regexp{font-weight:700;color:#00f}.hljs-link,.hljs-number,.hljs-regexp{font-weight:400}.hljs-string{color:green;font-weight:700}.hljs-bullet,.hljs-formula,.hljs-symbol{color:#000;background:#d0eded;font-style:italic}.hljs-doctag{text-decoration:underline}.hljs-template-variable,.hljs-variable{color:#660e7a}.hljs-addition{background:#baeeba}.hljs-deletion{background:#ffc8bd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.alert-box{position:relative;padding:8px 30px 8px 15px;margin:0 0 15px 2%;color:#fff;text-shadow:rgba(0,0,0,.2) 0 0 2px;background:#777}.alert-box.alert-box-not-closable{padding-right:15px}.alert-box .alert-box-text{display:block;float:left}.alert-box .close-alert-box,.alert-box .view-alert-box{display:block;position:absolute;top:8px;right:15px;height:20px;width:20px;text-indent:-9999px;text-decoration:none;background-color:transparent;line-height:22px;color:#fff}.alert-box .close-alert-box.ico-after:after,.alert-box .view-alert-box.ico-after:after{margin-top:4px}.alert-box .close-alert-box-text{width:auto;text-indent:0;top:8px}.alert-box .alert-box-title{margin:5px 0;padding:0;font-size:18px;font-weight:400}.alert-box.info,.alert-box.success{background:#48a200}.alert-box.error{background:#c0392b}.alert-box.alert,.alert-box.warning{background:#e67e22}.alert-box.not-member{background:#fdfdfd;color:#333;text-shadow:none;border-bottom:3px solid #d2d5d6}.alert-box.ico-after{padding-left:40px}.alert-box.ico-after:after{margin:12px 0 0 13px}.alert-box h4,.alert-box p{margin-left:0!important;margin-right:0!important}.alert-box p{margin:0}.alert-box a{color:#eee}.alert-box .alert-box-btn{display:inline-block;background:#084561;text-decoration:none;padding:8px 15px;margin:5px 0;color:#fff!important}.alert-box .alert-box-btn:focus,.alert-box .alert-box-btn:hover{background:#0b5c82}.alert-box .alert-box-btn.alert-box-btn-right{position:absolute;top:0;right:0;margin:0}.alert-box.empty{display:none}.content-wrapper .alert-box{margin:0 0 20px}.content-wrapper .alert-box+.not-member{margin-top:-20px}.opinion-alerts .alert-box-text{float:none}@media only screen and (min-width:760px){.alert-box .alert-box-text{display:inline}.topic-message .alert-box{padding:8px 75px 8px 15px}}@media only screen and (max-width:759px){.alert-box .alert-box-btn,.alert-box .alert-box-btn.alert-box-btn-right{position:relative;float:none;display:block;margin:5px 0 0;text-align:center}}.ico{background-repeat:no-repeat;background-image:url(../images/sprite.png)}.custom-block-body,.ico-after{position:relative}.custom-block-body:after,.ico-after:after{content:" ";display:block;position:absolute;top:0;left:0;width:16px;height:16px;background-repeat:no-repeat;background-image:url(../images/sprite.png)}.custom-block-body.alert:after,.custom-block-body.ico-alert:after,.ico-after.alert:after,.ico-after.ico-alert:after{background-position:-292px 0}.custom-block-body.alert.blue:after,.custom-block-body.ico-alert.blue:after,.ico-after.alert.blue:after,.ico-after.ico-alert.blue:after{background-position:-80px -232px}.custom-block-body.alert.light:after,.custom-block-body.ico-alert.light:after,.ico-after.alert.light:after,.ico-after.ico-alert.light:after{background-position:-260px -80px}.custom-block-body.arrow-left:after,.ico-after.arrow-left:after{background-position:-212px 0}.custom-block-body.arrow-left.blue:after,.ico-after.arrow-left.blue:after{background-position:-308px -80px}.custom-block-body.arrow-left.light:after,.ico-after.arrow-left.light:after{background-position:-196px 0}.custom-block-body.arrow-right:after,.custom-block-body.offline:after,.ico-after.arrow-right:after,.ico-after.offline:after{background-position:-64px -232px}.custom-block-body.arrow-right.blue:after,.custom-block-body.offline.blue:after,.ico-after.arrow-right.blue:after,.ico-after.offline.blue:after{background-position:-244px -80px}.custom-block-body.arrow-right.light:after,.custom-block-body.offline.light:after,.ico-after.arrow-right.light:after,.ico-after.offline.light:after{background-position:-260px -40px}.custom-block-body.beta:after,.ico-after.beta:after{background-position:-276px -80px}.custom-block-body.beta.blue:after,.ico-after.beta.blue:after{background-position:-160px -232px}.custom-block-body.beta.light:after,.ico-after.beta.light:after{background-position:-276px -40px}.custom-block-body.cite:after,.ico-after.cite:after{background-position:-164px 0}.custom-block-body.cite.blue:after,.ico-after.cite.blue:after{background-position:-126px -112px}.custom-block-body.cite.light:after,.ico-after.cite.light:after{background-position:-142px -112px}.custom-block-body.cross:after,.ico-after.cross:after{background-position:-180px -80px}.custom-block-body.cross.blue:after,.ico-after.cross.blue:after{background-position:-164px -40px}.custom-block-body.cross.red:after,.ico-after.cross.red:after{background-position:-180px 0}.custom-block-body.cross.light:after,.ico-after.cross.light:after{background-position:-164px -80px}.custom-block-body.cross.white:after,.ico-after.cross.white:after{background-position:-180px -40px}.custom-block-body.download:after,.ico-after.download:after{background-position:-80px -152px}.custom-block-body.download.blue:after,.ico-after.download.blue:after{background-position:-48px -152px}.custom-block-body.download.light:after,.ico-after.download.light:after{background-position:-64px -152px}.custom-block-body.downvote:after,.ico-after.downvote:after{background-position:-292px -80px}.custom-block-body.downvote.voted:after,.ico-after.downvote.voted:after{background-position:-292px -40px}.custom-block-body.edit:after,.ico-after.edit:after{background-position:-128px -152px}.custom-block-body.edit.blue:after,.ico-after.edit.blue:after{background-position:-96px -152px}.custom-block-body.edit.light:after,.ico-after.edit.light:after{background-position:-112px -152px}.custom-block-body.email:after,.ico-after.email:after{background-position:-176px -152px}.custom-block-body.email.blue:after,.ico-after.email.blue:after{background-position:-144px -152px}.custom-block-body.email.light:after,.ico-after.email.light:after{background-position:-160px -152px}.custom-block-body.diaspora:after,.ico-after.diaspora:after{background-position:-32px -152px}.custom-block-body.diaspora.blue:after,.ico-after.diaspora.blue:after{background-position:0 -152px}.custom-block-body.diaspora.light:after,.ico-after.diaspora.light:after{background-position:-16px -152px}.custom-block-body.facebook:after,.ico-after.facebook:after{background-position:-196px -120px}.custom-block-body.facebook.blue:after,.ico-after.facebook.blue:after{background-position:-196px -40px}.custom-block-body.facebook.light:after,.ico-after.facebook.light:after{background-position:-196px -80px}.custom-block-body.foursquare:after,.ico-after.foursquare:after{background-position:-212px -120px}.custom-block-body.foursquare.blue:after,.ico-after.foursquare.blue:after{background-position:-212px -40px}.custom-block-body.foursquare.light:after,.ico-after.foursquare.light:after{background-position:-212px -80px}.custom-block-body.gear:after,.ico-after.gear:after{background-position:-228px -80px}.custom-block-body.gear.blue:after,.ico-after.gear.blue:after{background-position:-228px 0}.custom-block-body.gear.light:after,.ico-after.gear.light:after{background-position:-228px -40px}.custom-block-body.github:after,.ico-after.github:after{background-position:-16px -192px}.custom-block-body.github.blue:after,.ico-after.github.blue:after{background-position:-228px -120px}.custom-block-body.github.light:after,.ico-after.github.light:after{background-position:0 -192px}.custom-block-body.google-plus:after,.ico-after.google-plus:after{background-position:-64px -192px}.custom-block-body.google-plus.blue:after,.ico-after.google-plus.blue:after{background-position:-32px -192px}.custom-block-body.google-plus.light:after,.ico-after.google-plus.light:after{background-position:-48px -192px}.custom-block-body.help:after,.ico-after.help:after{background-position:-112px -192px}.custom-block-body.help.blue:after,.ico-after.help.blue:after{background-position:-80px -192px}.custom-block-body.help.light:after,.ico-after.help.light:after{background-position:-96px -192px}.custom-block-body.hide:after,.ico-after.hide:after{background-position:-160px -192px}.custom-block-body.hide.blue:after,.ico-after.hide.blue:after{background-position:-128px -192px}.custom-block-body.hide.light:after,.ico-after.hide.light:after{background-position:-144px -192px}.custom-block-body.history:after,.ico-after.history:after{background-position:-208px -192px}.custom-block-body.history.blue:after,.ico-after.history.blue:after{background-position:-176px -192px}.custom-block-body.history.light:after,.ico-after.history.light:after{background-position:-192px -192px}.custom-block-body.import:after,.ico-after.import:after{background-position:-244px -40px}.custom-block-body.import.blue:after,.ico-after.import.blue:after{background-position:-224px -192px}.custom-block-body.import.light:after,.ico-after.import.light:after{background-position:-244px 0}.custom-block-body.lock:after,.ico-after.lock:after{background-position:-260px 0}.custom-block-body.lock.blue:after,.ico-after.lock.blue:after{background-position:-244px -120px}.custom-block-body.lock.light:after,.ico-after.lock.light:after{background-position:-244px -160px}.custom-block-body.more:after,.ico-after.more:after{background-position:0 -232px}.custom-block-body.more.blue:after,.ico-after.more.blue:after{background-position:-260px -120px}.custom-block-body.more.light:after,.ico-after.more.light:after{background-position:-260px -160px}.custom-block-body.move:after,.ico-after.move:after{background-position:-48px -232px}.custom-block-body.move.blue:after,.ico-after.move.blue:after{background-position:-16px -232px}.custom-block-body.move.light:after,.ico-after.move.light:after{background-position:-32px -232px}.custom-block-body.pin:after,.ico-after.pin:after{background-position:-128px -232px}.custom-block-body.pin.blue:after,.ico-after.pin.blue:after{background-position:-96px -232px}.custom-block-body.pin.light:after,.ico-after.pin.light:after{background-position:-112px -232px}.custom-block-body.rss:after,.ico-after.rss:after{background-position:-240px -232px}.custom-block-body.rss.blue:after,.ico-after.rss.blue:after{background-position:-192px -232px}.custom-block-body.rss.orange:after,.ico-after.rss.orange:after{background-position:-224px -232px}.custom-block-body.rss.light:after,.ico-after.rss.light:after{background-position:-208px -232px}.custom-block-body.star:after,.ico-after.star:after{background-position:-276px -200px}.custom-block-body.star.yellow:after,.ico-after.star.yellow:after{background-position:-276px -160px}.custom-block-body.star.blue:after,.ico-after.star.blue:after{background-position:-276px 0}.custom-block-body.star.light:after,.ico-after.star.light:after{background-position:-276px -120px}.custom-block-body.tick:after,.ico-after.tick:after{background-position:-308px -40px}.custom-block-body.tick.green:after,.ico-after.tick.green:after{background-position:-292px -200px}.custom-block-body.tick.light:after,.ico-after.tick.light:after{background-position:-308px 0}.custom-block-body.twitter:after,.ico-after.twitter:after{background-position:-308px -200px}.custom-block-body.twitter.blue:after,.ico-after.twitter.blue:after{background-position:-308px -120px}.custom-block-body.twitter.light:after,.ico-after.twitter.light:after{background-position:-308px -160px}.custom-block-body.unread:after,.ico-after.unread:after{background-position:-292px -240px}.custom-block-body.upvote:after,.ico-after.upvote:after{background-position:-292px -160px}.custom-block-body.upvote.voted:after,.ico-after.upvote.voted:after{background-position:-292px -120px}.custom-block-body.online:after,.custom-block-body.view:after,.ico-after.online:after,.ico-after.view:after{background-position:-110px -112px}.custom-block-body.online.blue:after,.custom-block-body.view.blue:after,.ico-after.online.blue:after,.ico-after.view.blue:after{background-position:-176px -232px}.custom-block-body.online.light:after,.custom-block-body.view.light:after,.ico-after.online.light:after,.ico-after.view.light:after{background-position:-144px -232px}h3:hover span.icon-link:after,h4:hover span.icon-link:after,h5:hover span.icon-link:after,h6:hover span.icon-link:after{content:" ";display:inline-block;background-image:url(../images/sprite.png);background-position:-308px -240px;width:16px;height:16px}.zmarkdown{color:#424242}.zmarkdown h2,.zmarkdown h3{clear:both}.zmarkdown h2,.zmarkdown h2 a,.zmarkdown h3,.zmarkdown h3 a{color:#ea9408;margin-top:40px;text-decoration:none}.zmarkdown h2 a:focus,.zmarkdown h2 a:hover,.zmarkdown h3 a:focus,.zmarkdown h3 a:hover{text-decoration:underline}.zmarkdown h2{font-size:22px;font-size:2.2rem;line-height:50px;margin-bottom:20px;background:#fff;border-top:1px solid #e0e4e5;padding-left:1%;font-weight:400}.zmarkdown h3{font-size:20px;font-size:2rem;margin-bottom:14px}.zmarkdown h4{font-size:18px;font-size:1.8rem;margin-bottom:12px}.zmarkdown h5{font-size:16px;font-size:1.6rem;margin-bottom:10px}.zmarkdown h6{font-size:15px;font-size:1.5rem;margin-bottom:10px}.zmarkdown .actions-title{float:right;margin:-60px 10px 0 0}.zmarkdown .actions-title .btn{height:30px;line-height:30px;margin-left:3px;opacity:.7;z-index:1}.zmarkdown .actions-title .btn.ico-after:after{margin-top:7px}.zmarkdown .actions-title .btn:focus,.zmarkdown .actions-title .btn:hover{opacity:1}.zmarkdown .custom-block{margin:25px 0}.zmarkdown .custom-block-body{padding:7px 15px 7px 45px}.zmarkdown .custom-block-body:after{position:absolute;top:50%;left:23px;margin:-11px 0 0 -11px;height:22px;width:22px}.zmarkdown .custom-block-heading{padding:3px 15px;font-weight:700}.zmarkdown .custom-block-information{background:#daeaee}.zmarkdown .custom-block-information .custom-block-heading{color:#fff;background:#53b2e4}.zmarkdown .custom-block-information .custom-block-body:after{background-position:-88px -112px}.zmarkdown .custom-block-question{background:#e2daee}.zmarkdown .custom-block-question .custom-block-heading{color:#fff;background:#9560e6}.zmarkdown .custom-block-question .custom-block-body:after{background-position:0 -112px}.zmarkdown .custom-block-error{background:#eedada}.zmarkdown .custom-block-error .custom-block-heading{color:#fff;background:#e45353}.zmarkdown .custom-block-error .custom-block-body:after{background-position:-44px -112px}.zmarkdown .custom-block-warning{background:#eee7da}.zmarkdown .custom-block-warning .custom-block-heading{color:#fff;background:#ebb552}.zmarkdown .custom-block-warning .custom-block-body:after{background-position:-66px -112px}.zmarkdown .custom-block-neutral,.zmarkdown .custom-block-spoiler{background:#eee}.zmarkdown .custom-block-neutral .custom-block-heading,.zmarkdown .custom-block-spoiler .custom-block-heading{color:#fff;background:#888}.zmarkdown .custom-block-neutral .custom-block-body,.zmarkdown .custom-block-spoiler .custom-block-body{padding-left:15px}.zmarkdown .custom-block-neutral .custom-block-body:after,.zmarkdown .custom-block-spoiler,.zmarkdown .custom-block-spoiler .custom-block-body:after,.zmarkdown .js .spoiler{display:none}.zmarkdown .spoiler-title{display:block;background:#eee;margin:15px 0;padding:3px 15px 3px 40px;text-decoration:none;border-bottom:1px solid #ddd;color:#555}.zmarkdown .spoiler-title.ico-after:after{margin:8px 0 0 10px}.zmarkdown .spoiler-title:nth-last-child(2){margin-bottom:15px}.zmarkdown .spoiler-title:hover{text-decoration:underline}.zmarkdown :not(.alert-box).error,.zmarkdown :not(.alert-box).information,.zmarkdown :not(.alert-box).question,.zmarkdown :not(.alert-box).spoiler,.zmarkdown :not(.alert-box).warning{margin:25px 0;padding:7px 15px 7px 45px}.zmarkdown :not(.alert-box).error.ico-after:after,.zmarkdown :not(.alert-box).information.ico-after:after,.zmarkdown :not(.alert-box).question.ico-after:after,.zmarkdown :not(.alert-box).spoiler.ico-after:after,.zmarkdown :not(.alert-box).warning.ico-after:after{position:absolute;top:50%;left:23px;margin:-11px 0 0 -11px;height:22px;width:22px}.zmarkdown :not(.alert-box).information{background:#daeaee}.zmarkdown :not(.alert-box).information.ico-after:after{background-position:-88px -112px}.zmarkdown :not(.alert-box).question{background:#e2daee}.zmarkdown :not(.alert-box).question.ico-after:after{background-position:0 -112px}.zmarkdown :not(.alert-box).error{background:#eedada}.zmarkdown :not(.alert-box).error.ico-after:after{background-position:-44px -112px}.zmarkdown :not(.alert-box).warning{background:#eee7da}.zmarkdown :not(.alert-box).warning.ico-after:after{background-position:-66px -112px}.zmarkdown .spoiler{margin-top:0;padding-left:15px;background:#eee}.zmarkdown img{max-width:100%}.zmarkdown figure{margin:30px 0;text-align:center}.zmarkdown figure>blockquote,.zmarkdown figure>code,.zmarkdown figure>embed,.zmarkdown figure>img,.zmarkdown figure>pre,.zmarkdown figure>table,.zmarkdown figure>video{max-width:100%;margin:0 auto;text-align:left}.zmarkdown figure>code,.zmarkdown figure>figcaption,.zmarkdown figure>img,.zmarkdown figure>pre,.zmarkdown figure>video{display:block}.zmarkdown figure>blockquote~figcaption{padding:0 0 1px 2%;font-style:italic;text-align:left;color:#999;border-left:5px solid #ccc}.zmarkdown figure>blockquote~figcaption p{margin:0 0 5px}.zmarkdown figure>blockquote~figcaption p:before{content:"— "}.zmarkdown blockquote{margin:0;color:#777;padding:1px 2%;border-left:5px solid #ccc}.zmarkdown blockquote>p:first-child{margin-top:5px}.zmarkdown blockquote>p:last-child{margin-bottom:5px}.zmarkdown blockquote figure{margin:15px 0}.zmarkdown blockquote:last-child{margin-bottom:15px}.zmarkdown .hljs-code-div,.zmarkdown code,.zmarkdown kbd,.zmarkdown pre,.zmarkdown samp{font-family:Source Code Pro,monospace,serif}.zmarkdown .hljs-code-div{background-color:#fff;margin:0 auto;padding:.5em;border-radius:.25em;display:-webkit-box;display:-ms-flexbox;display:flex;line-height:18px;font-style:normal;font-size:14px}.zmarkdown .hljs-code-div .hljs-line-numbers{counter-reset:a;border-right:1px solid #ddd;margin:0;padding:.5em;color:#888;text-align:right}.zmarkdown .hljs-code-div .hljs-line-numbers span:before{counter-increment:a;content:counter(a);display:block}.zmarkdown .hljs-code-div pre{margin:0;width:100%;overflow-x:auto}.zmarkdown kbd{background-color:#f8f6ea;padding:2px 6px;border-radius:3px;border:1px solid #e0dab6;border-bottom-width:3px;text-shadow:0 1px 0 #fff;color:#5e551f}.zmarkdown li code,.zmarkdown p code,.zmarkdown td code{color:#a00;background:#eee;border:1px solid #ccc;padding:0 5px}.zmarkdown .ping{color:inherit;text-decoration:none}.zmarkdown .ping:focus,.zmarkdown .ping:hover{text-decoration:underline}.zmarkdown .ping .ping-username{font-weight:700}.zmarkdown .mathjax-wrapper{max-width:100%;overflow:auto}.zmarkdown .mathjax-wrapper mathjax{font-size:16px;font-size:1.6rem}.zmarkdown .footnote,.zmarkdown .footnotes{opacity:.7}.zmarkdown .footnote ol,.zmarkdown .footnotes ol{padding-left:25px}.zmarkdown .footnote ol p,.zmarkdown .footnotes ol p{display:inline}.zmarkdown .video-container{margin:0 auto;max-width:560px;max-height:315px}.zmarkdown .video-container .video-wrapper{position:relative;padding-bottom:56.25%}.zmarkdown .video-container .video-wrapper iframe{width:100%}.zmarkdown .katex-display>.katex{display:contents}.zmarkdown .inlineMathDouble .katex{max-width:100%;overflow-x:auto}.zmarkdown .iframe-wrapper{overflow-x:auto}.zmarkdown div.align-center{text-align:center}.zmarkdown div.align-right{text-align:right}.zmarkdown div.align-left,.zmarkdown figure pre code.hljs{text-align:left}.zmarkdown .language-console{color:#ddd;background-color:#000;font-family:Source Code Pro,monospace,serif;display:block;overflow-x:auto;padding:.5em}.zmarkdown .table-wrapper{max-width:100%;overflow:auto}.zmarkdown table{margin:15px auto;border-top:1px solid #ddd;border-collapse:collapse}.zmarkdown table thead{background:#ddd;color:#084561}.zmarkdown table td,.zmarkdown table th{text-align:left;padding:5px 15px 5px 7px;border-right:1px solid #ddd}.zmarkdown table td:first-child,.zmarkdown table th:first-child{border-left:1px solid #ddd}.zmarkdown table td p,.zmarkdown table th p{margin:0}.zmarkdown table tbody tr{background:#fdfdfd;border-bottom:1px solid #ddd}.zmarkdown table tbody tr:nth-child(odd){background:#f7f7f7}.zmarkdown table.fullwidth{width:100%}.zmarkdown .diff_delta{overflow-x:auto;width:100%;margin:15px 0}.zmarkdown .diff_delta table.diff{font-family:Source Code Pro,monospace,serif;font-size:.9em;border:2px solid gray;margin:0}.zmarkdown .diff_delta table.diff tr{line-height:1em;border-bottom:none}.zmarkdown .diff_delta table.diff .diff_header{background-color:#e0e0e0;padding:5px}.zmarkdown .diff_delta table.diff td.diff_header{text-align:right}.zmarkdown .diff_delta table.diff .diff_next{display:none}.zmarkdown .diff_add{background-color:#afa}.zmarkdown .diff_chg{background-color:#fff8ab}.zmarkdown .diff_sub{background-color:#faa} /*# sourceMappingURL=zmd.css.map */ ================================================ FILE: packages/zmarkdown/public/index.html ================================================ ZMarkdown client converter
================================================ FILE: packages/zmarkdown/public/main.css ================================================ /* Global style */ html, body { height: 100%; } body { bottom: 0; left: 0; right: 0; top: 0; height: 100%; margin: 0; box-sizing: border-box; overflow: hidden; } /* Editor style */ .editor { font-family: monospace; font-size: 14px; border: 0; -moz-box-sizing: border-box; box-sizing: border-box; color: #333; height: 100%; margin: 0; min-width: 0; outline-width: 0; overflow-y: auto; padding: 30px; resize: none; width: 100%; } #left-bottom, #center, #center-bottom, #center-top, #right { overflow-y: auto; } #left-bottom { background-color: #084561; } .result { padding: 30px; position: relative; } .bottom-style { height: 100%; margin: auto; padding: 0; } .latex-result { height: 100%; margin: 0; padding: 0 0 0 30px; } /* Resizer style */ .gutter { background-color: transparent; background-repeat: no-repeat; background-position: 50%; box-shadow: inset 0 1px 2px #CCC; background-color: #EEE; } .gutter:hover { background-color: #ddd; } .gutter.gutter-horizontal { cursor: col-resize; } .gutter.gutter-vertical { cursor: row-resize; } .split.split-horizontal, .gutter.gutter-horizontal { height: 100%; float: left; } .gutter.gutter-vertical { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=') } .gutter.gutter-horizontal { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==') } .zmarkdown > p > .inlineMathDouble { display: block; text-align: center; } .zmarkdown > .align-right { text-align: right; } .zmarkdown > .align-center { text-align: center; } table { border-collapse: collapse; } table, th, td { border: 1px solid black; } ================================================ FILE: packages/zmarkdown/public/script.js ================================================ Split(['#left', '#center', '#right'], { minSize: 200, }) Split(['#left-top', '#left-bottom'], { sizes: [75, 25], direction: 'vertical' }) Split(['#center-top', '#center-bottom'], { sizes: [75, 25], direction: 'vertical' }) const ansiUp = new AnsiUp() const editor = document.querySelector('#left-top > textarea') const ast = document.querySelector('#left-bottom > pre > code') const render = document.querySelector('#center-top > div') const html = document.querySelector('#center-bottom > pre > code') const latex = document.querySelector('#right > pre') editor.addEventListener('input', update) document.addEventListener('DOMContentLoaded', update) function buildSpoilers (elems) { elems.forEach(elem => { if (!elem.classList.contains('spoiler-build')) { let a = document.createElement('a'); a.textContent = 'Afficher/Masquer le contenu masqué' a.classList.add('spoiler-title') a.classList.add('ico-after') a.classList.add('view') a.href = '#' a.onclick = (e) => { elem.style.display = !elem.style.display || elem.style.display === 'none' ? 'block' : 'none' e.preventDefault() } elem.parentNode.insertBefore(a, elem) elem.classList.add('spoiler-build') } }) } function update () { ZMarkdownZHTML.render(editor.value, (err, vFile) => { render.innerHTML = vFile.toString().trim() html.textContent = vFile.toString().trim() buildSpoilers(render.querySelectorAll('.custom-block-spoiler')) }) ZMarkdownZLATEX.render(editor.value, (err, vFile) => { latex.textContent = vFile.toString().trim() }) const mdast = ZMarkdownZMDAST.render(editor.value) ast.innerHTML = ansiUp.ansi_to_html(ZMarkdownZMDAST.inspect.color(mdast)) } ================================================ FILE: packages/zmarkdown/renderers/html.js ================================================ const rendererForge = require('./renderer-forge') const remark2rehype = require('remark-rehype') const rehypeStringify = require('rehype-stringify') const defaultStringifierList = { highlight: require('rehype-highlight'), slug: require('rehype-slug'), autolinkHeadings: require('rehype-autolink-headings'), footnotesTitles: require('rehype-footnotes-title'), htmlBlocks: require('rehype-html-blocks'), katex: require('rehype-katex'), postfixFootnotes: require('rehype-postfix-footnote-anchors'), sanitize: require('rehype-sanitize') } // KaTeX uses globals: load the mhchem extension require('katex/dist/contrib/mhchem') const postProcessorList = { footnotesReorder: require('../postprocessors/html-footnotes-reorder'), iframeWrappers: require('../postprocessors/html-iframe-wrappers'), lazyLoadImages: require('../postprocessors/html-lazy-load-images'), wrapCode: require('../postprocessors/html-wrap-code') } module.exports = (tokenizer, config) => { tokenizer .use(remark2rehype, config.bridge) rendererForge( tokenizer, defaultStringifierList, postProcessorList )(config) return tokenizer .use(rehypeStringify, config.stringify) } ================================================ FILE: packages/zmarkdown/renderers/latex.js ================================================ const rebberStringify = require('rebber/src') // Rebber is actually a stringifier-only module.exports = (tokenizer, config) => { return tokenizer .use(rebberStringify, config) } ================================================ FILE: packages/zmarkdown/renderers/mdast.js ================================================ const rendererForge = require('./renderer-forge') const unified = require('unified') const remarkParse = require('remark-parse') const remarkDisableTokenizers = require('remark-disable-tokenizers/src') const defaultTokenizerList = { abbr: require('remark-abbr/src'), alignBlocks: require('remark-align/src'), captions: require('remark-captions/src'), codeMeta: require('../plugins/remark-code-meta'), comments: require('remark-comments/src'), customBlocks: require('remark-custom-blocks/src'), emoticons: require('remark-emoticons/src'), escapeEscaped: require('remark-escape-escaped/src'), fixGuillemets: require('remark-fix-guillemets/src'), footnotes: require('remark-footnotes'), gridTables: require('remark-grid-tables/src'), headingShifter: require('remark-heading-shift/src'), iframes: require('remark-iframes/src'), imageToFigure: require('../plugins/remark-image-to-figure'), imagesDownload: require('remark-images-download/src'), kbd: require('remark-kbd/src'), math: require('remark-math'), numberedFootnotes: require('remark-numbered-footnotes/src'), ping: require('remark-ping/src'), subSuper: require('remark-sub-super/src'), textr: require('../plugins/remark-textr'), trailingSpaceHeading: require('remark-heading-trailing-spaces') } const postProcessorList = { detectQuizzes: require('../postprocessors/md-detect-quizzes'), getStats: require('../postprocessors/md-get-stats'), limitDepth: require('../postprocessors/md-limit-depth'), listLanguages: require('../postprocessors/md-list-languages'), wrapIntroCcl: require('../postprocessors/md-wrap-intro-ccl') } module.exports = (config) => { const baseTokenizer = unified() .use(remarkParse, config.parse) rendererForge( baseTokenizer, defaultTokenizerList, postProcessorList )(config) return baseTokenizer .use(remarkDisableTokenizers, config.disableTokenizers) } ================================================ FILE: packages/zmarkdown/renderers/renderer-forge.js ================================================ module.exports = (base, defaultPluginList, postProcessorList) => config => { // Use defaults if (!config.disableTokenizers) { config.disableTokenizers = { internal: [], meta: [], inline: [] } } const disabledInternalTokenizers = config.disableTokenizers.internal || [] if (!config.extraPlugins) { config.extraPlugins = {} } // Create an unified list of plugins const pluginList = Object.assign({}, defaultPluginList, config.extraPlugins) const pluginNames = Object.keys(pluginList) const postProcessorNames = Object.keys(postProcessorList) const filteredPlugins = pluginNames .filter(name => !disabledInternalTokenizers.includes(name)) const filteredPostProcessors = postProcessorNames .filter(name => { const ppc = config.postProcessors return (ppc && ppc[name]) ? Boolean(ppc[name]) : false }) for (const pluginName of filteredPlugins) { base.use(pluginList[pluginName], config[pluginName]) } // Add postprocessors, that are handled differently for (const postProcessorName of filteredPostProcessors) { if (typeof config.postProcessors[postProcessorName] === 'boolean') { base.use(postProcessorList[postProcessorName]) } else { base.use(postProcessorList[postProcessorName], config.postProcessors[postProcessorName]) } } return base } ================================================ FILE: packages/zmarkdown/server/controllers/munin.js ================================================ const pm2 = require('pm2') module.exports = isConfig => (req, res) => { const endpoints = Object.keys(require('../factories/io-factory')) const status = { online: 0, stopped: 1, errored: 2 } const supportedStats = { status: { graph: [ 'graph_title Process Status', `graph_vlabel Status\n(${Object.entries(status).map(([l, s]) => `${s}=${l}`).join(', ')})`, 'graph_args --lower-limit 0 --upper-limit 3' ], fields: (name) => [ `${name}.label ${name}`, `${name}.critical 1` ] }, memory: { stack: true, graph: [ 'graph_title RAM Usage', 'graph_vlabel Bytes', 'graph_args --base 1024 --lower-limit 0' ], fields: (name) => [ `${name}.label ${name}`, `${name}.draw STACK` ] }, cpu: { graph: [ 'graph_title CPU Usage', 'graph_vlabel percent', 'graph_args --base 1000 --lower-limit 0 --rigid', 'graph_scale no' ], fields: (name) => `${name}.label ${name}` }, event_loop_lag: { graph: [ 'graph_title Event Loop Lag', 'graph_vlabel ms', 'graph_args --lower-limit 0' ], fields: (name) => `${name}.label ${name}` }, avg_per_process: { stack: true, graph: [ 'graph_title Requests Per Second Per Process', 'graph_vlabel requests per second', 'graph_args --lower-limit 0' ], fields: (name) => [ `${name}.label ${name}`, `${name}.draw STACK` ] }, avg_per_endpoint: { stack: true, graph: [ 'graph_title Requests Per Second Per Endpoint', 'graph_vlabel requests per second', 'graph_args --lower-limit 0' ], fields: () => endpoints.map(endpoint => `${endpoint}.label ${endpoint}\n${endpoint}.draw STACK`) } } const stat = req.params.plugin const collected = [] if (!(Object.prototype.hasOwnProperty.call(supportedStats, stat))) { return res.status(500).send({ error: `Invalid stat, given : ${stat}, ` + `expected one of ${JSON.stringify(supportedStats, null, 2)}.` }) } if (isConfig) { run(printConfig) } else { run(printStats) } function gatherData (procList) { let maxMem = 0 const procs = procList.reduce((acc, proc, i) => { let avgPerProcess let loopLag if (proc.pm2_env.axm_monitor) { avgPerProcess = endpoints.map(endpoint => { const metric = proc.pm2_env.axm_monitor[`${endpoint} rpm`] return metric ? parseFloat(metric.value) : 0 }).reduce((sum, val) => sum + val, 0) // Sometimes not available after start if (proc.pm2_env.axm_monitor['Event Loop Latency']) { loopLag = parseFloat(proc.pm2_env.axm_monitor['Event Loop Latency'].value) } } const data = { status: proc.pm2_env.status in status ? status[proc.pm2_env.status] : 3, memory: proc.monit.memory, cpu: proc.monit.cpu, event_loop_lag: loopLag || 'U', avg_per_process: avgPerProcess } if (!maxMem && proc.pm2_env.max_memory_restart) { maxMem = proc.pm2_env.max_memory_restart } if (maxMem) data.maxMem = maxMem acc[`${proc.name}-${proc.pm_id}`] = data return acc }, {}) const _endpoints = { avg_per_endpoint: endpoints.reduce((acc, endpoint) => { acc[endpoint] = procList.reduce((sum, proc) => { const metric = proc.pm2_env.axm_monitor[`${endpoint} rpm`] return sum + (metric ? parseFloat(metric.value) : 0) }, 0) return acc }, {}) } return { procs, endpoints: _endpoints } } // prints a munin readable config function printConfig (stats) { if (!supportedStats[stat]) { return res.status(500).send({ error: `"${stat}" not configured` }) } const procs = stats.procs l(supportedStats[stat].graph.join('\n')) l('graph_category zmd') let output = '' if (stat === 'avg_per_endpoint') { output = supportedStats[stat].fields().join('\n') } else { output = Object.keys(procs).map(job => { const tmp = supportedStats[stat].fields(job) return Array.isArray(tmp) ? tmp.join('\n') : tmp }).join('\n') } if (supportedStats[stat].stack) { output = output.replace(' STACK', ' AREA') } l(output) pm2.disconnect() res.send(collected.join('\n')) } // prints a munin readable output function printStats (stats) { const procs = stats.procs if (stat === 'avg_per_endpoint') { endpoints.forEach(endpoint => { l(`${endpoint}.value ${stats.endpoints[stat][endpoint]}`) }) } else { Object.keys(procs).forEach((procName) => { const value = procs[procName][stat] l(`${procName}.value ${value}`) }) } pm2.disconnect() res.send(collected.join('\n')) } function run (callback) { pm2.connect((err) => { if (err) { return res.status(500).send({ error: err }) } pm2.list((err, plist) => { if (err) { return res.status(500).send({ error: err }) } plist = plist.filter(process => !process.name.startsWith('pm2')) callback(gatherData(plist)) }) }) } function l (string) { collected.push(string) } } ================================================ FILE: packages/zmarkdown/server/factories/config-factory.js ================================================ const clone = require('clone') const defaultMdastConfig = require('../../config/mdast') module.exports = opts => { // Convert endpoint options to configuration const mdastConfig = clone(defaultMdastConfig) if (opts.disable_ping === true) { mdastConfig.ping.pingUsername = () => false } if (typeof opts.heading_shift === 'number') { mdastConfig.headingShifter = opts.heading_shift } if (opts.disable_jsfiddle === true) { mdastConfig.iframes['jsfiddle.net'].disabled = true mdastConfig.iframes['www.jsfiddle.net'].disabled = true } if ( typeof opts.disable_tokenizers === 'object' && !Array.isArray(opts.disable_tokenizers) ) { mdastConfig.disableTokenizers = opts.disable_tokenizers } mdastConfig.postProcessors.getStats = opts.stats === true mdastConfig.imagesDownload.disabled = opts.disable_images_download === true if (mdastConfig.imagesDownload.disabled !== true) { if (typeof opts.images_download_dir === 'string') { mdastConfig.imagesDownload.downloadDestination = opts.images_download_dir } if ( Array.isArray(opts.local_url_to_local_path) && opts.local_url_to_local_path.length === 2 ) { mdastConfig.imagesDownload.localUrlToLocalPath = opts.local_url_to_local_path } if (typeof opts.images_download_timeout === 'number') { mdastConfig.imagesDownload.httpRequestTimeout = opts.images_download_timeout } if (typeof opts.images_download_default === 'string') { mdastConfig.imagesDownload.defaultImagePath = opts.images_download_default } } // Allow using a custom element for iframes, initially for GDPR compliance if (opts.hide_iframes) { for (const providerName in mdastConfig.iframes) { mdastConfig.iframes[providerName].tag = 'hidden-frame' } } if (opts.inline === true) { if (!Array.isArray(mdastConfig.disableTokenizers.block)) { mdastConfig.disableTokenizers.block = [] } mdastConfig.disableTokenizers.block = mdastConfig.disableTokenizers.block.concat([ 'indentedCode', 'fencedCode', 'blockquote', 'atxHeading', 'setextHeading', 'footnote', 'table', 'custom_blocks' ]) } if (typeof opts.extract_type === 'string') { if (['introduction', 'conclusion'].includes(opts.extract_type)) { mdastConfig.postProcessors.wrapIntroCcl = { type: opts.extract_type, level: opts.ic_shift || 0 } } } return mdastConfig } ================================================ FILE: packages/zmarkdown/server/factories/controller-factory.js ================================================ const processorFactory = require('./processor-factory') const manifest = require('../utils/manifest') const io = require('../factories/io-factory') module.exports = (givenProc, template) => (req, res, next) => { // Gather data about the request const rawContent = req.body.md const options = req.body.opts || {} const manifestRender = (typeof rawContent !== 'string') const useTemplate = (typeof template === 'function') const baseShift = options.heading_shift || 0 // Increment endpoint usage for monitoring if (!useTemplate) io[givenProc]() else io['latex-document']() let extractPromises // Get a collection of Promises to execute if (manifestRender) { extractPromises = manifest .gatherExtracts(rawContent) .map(extract => { // Manifest rendering requires forging a new processor // to handle title depths const { text, options: localOptions } = extract if (localOptions.depth) { localOptions.heading_shift = baseShift + localOptions.depth localOptions.ic_shift = localOptions.depth } const mergedOptions = Object.assign({}, options, localOptions) const processor = processorFactory(givenProc, mergedOptions) return processor(text) }) } else { extractPromises = [processorFactory(givenProc, req.body.opts)(rawContent)] } Promise.all(extractPromises) .then(vfiles => { if (useTemplate) { let processedContent // When using the template, we need to assemble // the content first. if (manifestRender) processedContent = vfiles.reduce(manifest.assemble) else processedContent = vfiles[0] const templateOpts = Object.assign(options, { latex: processedContent.contents }) const finalDocument = template(templateOpts) // Replace the content, and discard metadata, which have no meaning in LaTeX Object.assign(processedContent, { contents: finalDocument, data: {} }) return processedContent } // Add parsed content to original manifest and return if (manifestRender) return manifest.dispatch(vfiles, rawContent) else return vfiles[0] }) .then(vfile => res.json([vfile.contents, vfile.data, vfile.messages])) .catch(e => next(e)) } ================================================ FILE: packages/zmarkdown/server/factories/io-factory.js ================================================ const io = require('@pm2/io') const endpoints = ['html', 'epub', 'latex', 'latex-document'] module.exports = endpoints.reduce((acc, endpoint) => { const meter = io.meter({ name: `${endpoint} rpm`, samples: 1, timeframe: 60 }) acc[endpoint] = () => { meter.mark() } return acc }, {}) ================================================ FILE: packages/zmarkdown/server/factories/processor-factory.js ================================================ const defaultMdastConfig = require('../../config/mdast') const configFactory = require('./config-factory') const zmd = require('../../common') // ZMd parser memoization const processors = {} module.exports = (processor, opts = {}) => { if (!['epub', 'html', 'latex'].includes(processor)) { const error = new Error(`Unknown target '${processor}'`) return (md, cb) => { if (typeof cb !== 'function') { return new Promise((resolve, reject) => reject(error)) } return cb(error) } } if (processor === 'html') { opts.disable_images_download = true } if (processor === 'latex') { opts.disable_ping = true opts.disable_jsfiddle = true } else { delete opts.extract_type } if (processor === 'epub') { opts.disable_ping = true opts.disable_jsfiddle = true opts.inline = false // EPub is HTML processor = 'html' } const procId = processor + JSON.stringify(opts) if (!Object.prototype.hasOwnProperty.call(processors, procId)) { // Merge new config with defaults const mdastConfig = Object.assign( {}, defaultMdastConfig, configFactory(opts) ) processors[procId] = zmd(processor, mdastConfig) } return processors[procId] } ================================================ FILE: packages/zmarkdown/server/index.js ================================================ /* eslint-disable no-console */ const Sentry = require('@sentry/node') const express = require('express') const cors = require('cors') const path = require('path') const zmdVersion = require('../package.json').version const app = express() Sentry.init({ dsn: process.env.SENTRY_DSN, release: process.env.SENTRY_RELEASE || zmdVersion, environment: process.env.SENTRY_ENVIRONMENT || process.env.ZDS_ENVIRONMENT }) app.use(Sentry.Handlers.requestHandler()) app.use(cors()) // Expose an image for tests if (process.env.ZMD_ENV !== 'production') { app.use('/static', express.static(path.join(__dirname, 'static'))) } // Depend on routers app.use('/', require('./routes/endpoints')) app.use('/munin', require('./routes/munin')) // Sentry error handler app.use(Sentry.Handlers.errorHandler()) // Custom error handler, called only if a route throws app.use((err, req, res, next) => { res.status(500).json({ type: 'InternalServerError', statusCode: 500, errorMessage: err.toString(), uniqueId: res.sentry }) // eslint-disable-next-line no-useless-return return }) const server = app.listen(process.env.PORT || 27272, () => { const host = server.address().address const port = server.address().port console.log('zmarkdown server listening at http://%s:%s', host, port) }) ================================================ FILE: packages/zmarkdown/server/routes/endpoints.js ================================================ const bodyParser = require('body-parser') const express = require('express') const controllerFactory = require('../factories/controller-factory') const router = express.Router() const limit = '4mb' router.post( '/html', bodyParser.json({ limit }), controllerFactory('html') ) router.post( '/latex', bodyParser.json({ limit }), controllerFactory('latex') ) router.post( '/epub', bodyParser.json({ limit }), controllerFactory('epub') ) router.post( '/latex-document', bodyParser.json({ limit }), controllerFactory('latex', require('../templates/latex-document')) ) router.get('/', (_, res) => { res.send('zmd is running!\n') }) module.exports = router ================================================ FILE: packages/zmarkdown/server/routes/munin.js ================================================ const express = require('express') const munin = require('../controllers/munin') const router = express.Router() router.get( '/config/:plugin', munin('config') ) router.get( '/:plugin', munin() ) module.exports = router ================================================ FILE: packages/zmarkdown/server/templates/latex-document.js ================================================ const assert = require('assert') const escape = require('rebber/dist/escaper') module.exports = opts => { const { disableToc = false, content_type: contentType, license_directory: licenseDirectory, license_url: licenseUrl, license_logo: licenseLogo, logo_directory: logoDirectory, content_logo: contentLogo, content_link: contentLink, editor_logo: editorLogo, editor_link: editorLink, smileys_directory: smileysDirectory, title, authors, license, date, latex } = opts // Required options assert(contentType, 'Error with argument: "contentType"') assert(title, 'Error with argument: "title"') assert(Array.isArray(authors), 'Error with argument: "authors"') assert(license, 'Error with argument: "license"') assert(licenseDirectory, 'Error with argument: "licenseDirectory"') assert(smileysDirectory, 'Error with argument: "smileysDirectory"') assert(latex, 'Error with argument: "latex"') const licenceLogoPath = licenseLogo ? `${licenseDirectory}/${licenseLogo}` : '' const licenseLine = `\\licence[${licenceLogoPath}]{${license}}{${licenseUrl || ''}}` // Optional arguments const extraHeaders = [] if (date) extraHeaders.push(`\\date{${date}}`) if (logoDirectory && contentLogo) extraHeaders.push(`\\logo{${logoDirectory}/${contentLogo}}`) if (logoDirectory && editorLogo) extraHeaders.push(`\\editorLogo{${logoDirectory}/${editorLogo}}`) if (contentLink) extraHeaders.push(`\\tutoLink{${contentLink}}`) if (editorLink) extraHeaders.push(`\\editor{${editorLink}}`) return `\\documentclass[${contentType}]{zmdocument} \\usepackage{blindtext} \\title{${escape(title)}} \\author{${escape(authors.join(', '))}} ${licenseLine} ${extraHeaders.join('\n')} \\smileysPath{${smileysDirectory}} \\makeglossaries \\begin{document} \\maketitle ${disableToc ? '' : '\\tableofcontents'} ${latex} \\end{document}` } ================================================ FILE: packages/zmarkdown/server/utils/manifest.js ================================================ const manifestUtils = {} // Concatenate two arrays, excluding duplicates const listConcatUnique = (a, b) => { if (Array.isArray(a) && Array.isArray(b)) { return Array.from(new Set(a.concat(b))) } return [] } // Execute a given function on a manifest // If the function returns, then content inside the manifest // will be replaced by the returned value. const executeOnExtracts = (children, execFunction, depth = 0) => { function changeIfReturn (obj, extractType, d) { const r = execFunction(obj[extractType], { extract_type: extractType, depth: d }) if (typeof r === 'string') obj[extractType] = r } depth++ children.forEach(child => { if (child.title) { // Titles need to be handled as titles child.title = `# ${child.title}` changeIfReturn(child, 'title', depth - 1) } if (child.introduction) changeIfReturn(child, 'introduction', depth) if (child.text) changeIfReturn(child, 'text', depth) // Process element's children if (child.children) executeOnExtracts(child.children, execFunction, depth) if (child.conclusion) changeIfReturn(child, 'conclusion', depth) }) return children } // List of rules to assemble VFile `data` section const metadataPropertiesRules = { disableToc: (a, b) => a && b, languages: listConcatUnique, stats: (a, b) => ({ signs: a.signs + b.signs, words: a.words + b.words }), ping: listConcatUnique } manifestUtils.gatherExtracts = manifest => { const rawExtracts = [] const appendToExtracts = (text, options = {}) => { rawExtracts.push({ text, options }) } // Start recursion by top-level element executeOnExtracts([manifest], appendToExtracts) return rawExtracts } manifestUtils.assemble = (beginVfile, endVfile) => { const assembledVfile = { contents: `${beginVfile.contents}\n${endVfile.contents}`, messages: [], data: {} } if (beginVfile.messages) { assembledVfile.messages = assembledVfile.messages.concat(beginVfile.messages) } if (endVfile.messages) { assembledVfile.messages = assembledVfile.messages.concat(endVfile.messages) } if (beginVfile.data) { // Append unique items from A for (const [item, value] of Object.entries(beginVfile.data)) { if (!Object.keys(endVfile.data).includes(item)) { assembledVfile.data[item] = value } } } if (endVfile.data) { for (const [item, value] of Object.entries(endVfile.data)) { // Append unique items from B if (!Object.keys(beginVfile.data).includes(item)) { assembledVfile.data[item] = value continue } // Append assembled cross-items const assemblyRule = metadataPropertiesRules[item] if (assemblyRule) { assembledVfile.data[item] = assemblyRule(beginVfile.data[item], endVfile.data[item]) } } } return assembledVfile } manifestUtils.dispatch = (parsedExtracts, originalManifest) => { // Create a clone of the original manifest const finalManifest = Object.assign({}, originalManifest) // Dispatching is quite weird: we want the manifest to be rendered // but returning every part as a vfile makes a huge response... const assembledExtracts = parsedExtracts.reduce(manifestUtils.assemble) // Replace each extract where it belongs let i = 0 executeOnExtracts([finalManifest], () => parsedExtracts[i++].contents) assembledExtracts.contents = finalManifest return assembledExtracts } module.exports = manifestUtils ================================================ FILE: packages/zmarkdown/utils/code-handler.js ================================================ module.exports = code const codeNodeConstructor = (lang, value) => { // no properties, except if a langage is specified // remark-highlight takes care of adding the right coloration const properties = {} if (lang) properties.className = [`language-${lang}`] // pre > code(.language-xxx)? return { type: 'element', tagName: 'pre', properties: {}, children: [{ type: 'element', tagName: 'code', properties, children: [{ type: 'text', value }] }] } } // div.hljs-code-div const parentDivConstructor = children => ({ type: 'element', tagName: 'div', properties: { className: ['hljs-code-div'] }, children }) // div.hljs-line-numbers const lineNumberDivConstructor = children => ({ type: 'element', tagName: 'div', properties: { className: ['hljs-line-numbers'] }, children }) // span[data-count](.hll)? const lineNumberElemConstructor = (count, hl) => { const properties = { 'data-count': String(count) } if (hl) properties.className = 'hll' return { type: 'element', tagName: 'span', properties, children: [] } } // Parse ranges for hl_lines const rangeHandler = range => { const fullRange = [] for (const bit of range.split(' ')) { if (bit.match('-')) { const [rangeStart, rangeStop] = bit.split('-') const minLineNumber = parseInt(rangeStart) const maxLineNumber = parseInt(rangeStop) const rangeLength = (maxLineNumber - minLineNumber) + 1 fullRange.push(...[...Array(rangeLength).keys()] .map(i => i + minLineNumber)) } else { fullRange.push(parseInt(bit)) } } return fullRange } function code (_, node) { const value = node.value ? `${node.value}\n` : '' const lang = (node.lang && node.lang.match(/^[^ \t]+(?=[ \t]|$)/)) || 'text' const attrs = node.meta const hlLines = rangeHandler(attrs.hlLines) const linesSplitted = value .split('\n') .slice(0, -1) const lineNumberElems = linesSplitted .map((_, i) => { const realLn = i + attrs.linenostart return lineNumberElemConstructor(realLn, hlLines.includes(realLn)) }) const parentDivChildren = [] if (linesSplitted.length > 1) { parentDivChildren.push(lineNumberDivConstructor(lineNumberElems)) } parentDivChildren.push(codeNodeConstructor(lang, value)) return parentDivConstructor(parentDivChildren) } ================================================ FILE: packages/zmarkdown/utils/create-wrappers.js ================================================ const assert = require('assert') module.exports = createWrapper const allowAll = () => true function createWrapper (tagToWrap, wrapInTags, classes, filter = allowAll) { if (!Array.isArray(wrapInTags)) wrapInTags = [wrapInTags] if (!Array.isArray(classes)) classes = [classes] assert( wrapInTags.length === classes.length, 'You should provide the same number of wrapInTags and classes' ) const visitor = (node, index, parent) => { if (node.type === 'element' && node.tagName === tagToWrap) { if ((filter && filter(node)) && !node.__wrapped) { wrap({ wrapInTags, classes }, { node, index, parent }) } } } return visitor } function wrap ({ wrapInTags, classes }, { node, index, parent }) { let wrapped = node for (let i = 0; i < wrapInTags.length; i++) { node.__wrapped = true wrapped = { type: 'element', tagName: wrapInTags[i] || 'div', properties: { class: classes[i] || [] }, children: [wrapped] } } parent.children.splice(index, 1, wrapped) } ================================================ FILE: packages/zmarkdown/utils/latex-code.js ================================================ const customCodeMacro = (content, lang, attrs) => { // Default language is "text" if (!lang) lang = 'text' // Escape CodeBlocks let escaped = content const escapeRegex = /\\end\s*{CodeBlock}/g while (escapeRegex.test(escaped)) { escaped = escaped.replace(escapeRegex, '') } // Detect features const hasHlLines = (attrs && attrs.hlLines && attrs.hlLines !== []) const hasLinenostart = (attrs && attrs.linenostart && attrs.linenostart !== 1) let params = '' // Optional caption is not used if (hasHlLines || hasLinenostart) { params += '[]' } // Line highlight if (hasHlLines) { params += `[${attrs.hlLines.split(' ').join(',')}]` } else if (hasLinenostart) { params += '[]' } if (hasLinenostart) { params += `[${attrs.linenostart}]` } return `\\begin{CodeBlock}${params}{${lang}}\n${escaped}\n\\end{CodeBlock}\n\n` } /* Expose. */ module.exports = customCodeMacro ================================================ FILE: packages/zmarkdown/utils/renderer-tests.js ================================================ const clone = require('clone') const zmarkdown = require('../common') const defaultMdastConfig = clone(require('../config/mdast')) const defaultHtmlConfig = clone(require('../config/html')) const defaultLatexConfig = clone(require('../config/latex')) defaultMdastConfig.disableTokenizers = { internal: ['textr'], meta: [], inline: [] } defaultMdastConfig.ping.pingUsername = () => false defaultHtmlConfig.disableTokenizers = { internal: ['highlight'] } defaultHtmlConfig.postfixFootnotes = '-shortId' // Remove custom handlers (only code for now) defaultHtmlConfig.bridge.handlers = {} defaultHtmlConfig.postProcessors.codeHighlight = false module.exports.defaultMdastConfig = defaultMdastConfig module.exports.defaultHtmlConfig = defaultHtmlConfig module.exports.defaultLatexConfig = defaultLatexConfig module.exports.configOverride = (a, b) => Object.assign({}, a, b) module.exports.renderAs = type => (mdastConfig, renderConfig) => { let usedMdastConfig = mdastConfig let usedRenderConfig = renderConfig const renderWithConfig = (input, vfile) => { if (!vfile) { return zmarkdown(type, usedMdastConfig, usedRenderConfig)(input) .then(vfile => vfile.contents.trim()) } else { return zmarkdown(type, usedMdastConfig, usedRenderConfig)(input) } } // Handle case where no config was given if (typeof mdastConfig === 'string') { usedMdastConfig = defaultMdastConfig usedRenderConfig = type === 'latex' ? defaultLatexConfig : defaultHtmlConfig return renderWithConfig(mdastConfig, renderConfig) } return renderWithConfig } ================================================ FILE: packages/zmarkdown/webpack.config.js ================================================ const webpack = require('webpack') const path = require('path') const mode = process.env.NODE_ENV ? process.env.NODE_ENV : 'production' const defaultConf = { resolve: { fallback: { assert: require.resolve('assert'), path: require.resolve('path-browserify'), url: require.resolve('url'), }, }, mode, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', }, }, ], }, plugins: [ new webpack.ProvidePlugin({ process: 'process/browser', }), ], } const makeExportObject = (type) => { const upperType = type.toUpperCase() return Object.assign({}, defaultConf, { name: `ZMarkdown${upperType}`, entry: [`./client/${type}`], output: { path: path.resolve(__dirname, 'client/dist'), filename: `zmarkdown-${type}.js`, library: `ZMarkdown${upperType}`, }, }) } module.exports = ['zmdast', 'zhtml', 'zhlite', 'zlatex'].map(makeExportObject)