feat: Add HTML export option (#4056)

* tidy

* Add title to HTML export

* fix: Add compatability for documents without collab state

* Add HTML download option to UI

* docs

* fix nodes that required document to render

* Refactor to allow for styling of HTML export

* div>article for easier programatic content extraction
This commit is contained in:
Tom Moor
2022-09-07 13:34:39 +02:00
committed by GitHub
parent eb5126335c
commit e8a6de3f18
30 changed files with 1756 additions and 1790 deletions

View File

@@ -1,129 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders blockquote 1`] = `
"<blockquote>
<p>blockquote</p>
</blockquote>"
`;
exports[`renders bold marks 1`] = `"<p>this is <strong>bold</strong> text</p>"`;
exports[`renders bullet list 1`] = `
"<ul>
<li>item one</li>
<li>item two
<ul>
<li>nested item</li>
</ul>
</li>
</ul>"
`;
exports[`renders checkbox list 1`] = `
"<ul>
<li class=\\"checkbox-list-item\\"><span class=\\"checkbox \\">[ ]</span>unchecked</li>
<li class=\\"checkbox-list-item\\"><span class=\\"checkbox checked\\">[x]</span>checked</li>
</ul>"
`;
exports[`renders code block 1`] = `
"<pre><code>this is indented code
</code></pre>"
`;
exports[`renders code fence 1`] = `
"<pre><code class=\\"language-javascript\\">this is code
</code></pre>"
`;
exports[`renders code marks 1`] = `"<p>this is <code>inline code</code> text</p>"`;
exports[`renders headings 1`] = `
"<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>"
`;
exports[`renders highlight marks 1`] = `"<p>this is <span class=\\"highlight\\">highlighted</span> text</p>"`;
exports[`renders horizontal rule 1`] = `"<hr>"`;
exports[`renders image 1`] = `"<p><img src=\\"https://lorempixel.com/200/200\\" alt=\\"caption\\"></p>"`;
exports[`renders image with alignment 1`] = `"<p><img src=\\"https://lorempixel.com/200/200\\" alt=\\"caption\\" title=\\"left-40\\"></p>"`;
exports[`renders info notice 1`] = `
"<div class=\\"notice notice-info\\">
<p>content of notice</p>
</div>"
`;
exports[`renders italic marks 1`] = `"<p>this is <em>italic</em> text</p>"`;
exports[`renders italic marks 2`] = `"<p>this is <em>also italic</em> text</p>"`;
exports[`renders link marks 1`] = `"<p>this is <a href=\\"https://www.example.com\\">linked</a> text</p>"`;
exports[`renders ordered list 1`] = `
"<ol>
<li>item one</li>
<li>item two</li>
</ol>"
`;
exports[`renders ordered list 2`] = `
"<ol>
<li>item one</li>
<li>item two</li>
</ol>"
`;
exports[`renders plain text as paragraph 1`] = `"<p>plain text</p>"`;
exports[`renders table 1`] = `
"<table>
<tr>
<th>
<p>heading</p></th>
<th style=\\"text-align:center\\">
<p>centered</p></th>
<th style=\\"text-align:right\\">
<p>right aligned</p></th>
</tr>
<tr>
<td>
<p></p></td>
<td style=\\"text-align:center\\">
<p>center</p></td>
<td style=\\"text-align:right\\">
<p></p></td>
</tr>
<tr>
<td>
<p></p></td>
<td style=\\"text-align:center\\">
<p></p></td>
<td style=\\"text-align:right\\">
<p>bottom r</p></td>
</tr>
</table>"
`;
exports[`renders template placeholder marks 1`] = `"<p>this is <span class=\\"placeholder\\">a placeholder</span></p>"`;
exports[`renders tip notice 1`] = `
"<div class=\\"notice notice-tip\\">
<p>content of notice</p>
</div>"
`;
exports[`renders underline marks 1`] = `"<p>this is <underline>underlined</underline> text</p>"`;
exports[`renders underline marks 2`] = `"<p>this is <s>strikethrough</s> text</p>"`;
exports[`renders warning notice 1`] = `
"<div class=\\"notice notice-warning\\">
<p>content of notice</p>
</div>"
`;

View File

@@ -1,7 +1,6 @@
import { Schema } from "prosemirror-model";
import ExtensionManager from "@shared/editor/lib/ExtensionManager";
import fullPackage from "@shared/editor/packages/full";
import render from "./renderToHtml";
const extensions = new ExtensionManager(fullPackage);
@@ -16,6 +15,3 @@ export const parser = extensions.parser({
});
export const serializer = extensions.serializer();
export const renderToHtml = (markdown: string): string =>
render(markdown, extensions.rulePlugins);

View File

@@ -1,154 +0,0 @@
import renderToHtml from "./renderToHtml";
test("renders an empty string", () => {
expect(renderToHtml("")).toBe("");
});
test("renders plain text as paragraph", () => {
expect(renderToHtml("plain text")).toMatchSnapshot();
});
test("renders blockquote", () => {
expect(renderToHtml("> blockquote")).toMatchSnapshot();
});
test("renders code block", () => {
expect(
renderToHtml(`
this is indented code
`)
).toMatchSnapshot();
});
test("renders code fence", () => {
expect(
renderToHtml(`\`\`\`javascript
this is code
\`\`\``)
).toMatchSnapshot();
});
test("renders checkbox list", () => {
expect(
renderToHtml(`- [ ] unchecked
- [x] checked`)
).toMatchSnapshot();
});
test("renders bullet list", () => {
expect(
renderToHtml(`- item one
- item two
- nested item`)
).toMatchSnapshot();
});
test("renders info notice", () => {
expect(
renderToHtml(`:::info
content of notice
:::`)
).toMatchSnapshot();
});
test("renders warning notice", () => {
expect(
renderToHtml(`:::warning
content of notice
:::`)
).toMatchSnapshot();
});
test("renders tip notice", () => {
expect(
renderToHtml(`:::tip
content of notice
:::`)
).toMatchSnapshot();
});
test("renders headings", () => {
expect(
renderToHtml(`# Heading 1
## Heading 2
### Heading 3
#### Heading 4`)
).toMatchSnapshot();
});
test("renders horizontal rule", () => {
expect(renderToHtml(`---`)).toMatchSnapshot();
});
test("renders image", () => {
expect(
renderToHtml(`![caption](https://lorempixel.com/200/200)`)
).toMatchSnapshot();
});
test("renders image with alignment", () => {
expect(
renderToHtml(`![caption](https://lorempixel.com/200/200 "left-40")`)
).toMatchSnapshot();
});
test("renders table", () => {
expect(
renderToHtml(`
| heading | centered | right aligned |
|---------|:--------:|--------------:|
| | center | |
| | | bottom r |
`)
).toMatchSnapshot();
});
test("renders bold marks", () => {
expect(renderToHtml(`this is **bold** text`)).toMatchSnapshot();
});
test("renders code marks", () => {
expect(renderToHtml(`this is \`inline code\` text`)).toMatchSnapshot();
});
test("renders highlight marks", () => {
expect(renderToHtml(`this is ==highlighted== text`)).toMatchSnapshot();
});
test("renders italic marks", () => {
expect(renderToHtml(`this is *italic* text`)).toMatchSnapshot();
expect(renderToHtml(`this is _also italic_ text`)).toMatchSnapshot();
});
test("renders template placeholder marks", () => {
expect(renderToHtml(`this is !!a placeholder!!`)).toMatchSnapshot();
});
test("renders underline marks", () => {
expect(renderToHtml(`this is __underlined__ text`)).toMatchSnapshot();
});
test("renders link marks", () => {
expect(
renderToHtml(`this is [linked](https://www.example.com) text`)
).toMatchSnapshot();
});
test("renders underline marks", () => {
expect(renderToHtml(`this is ~~strikethrough~~ text`)).toMatchSnapshot();
});
test("renders ordered list", () => {
expect(
renderToHtml(`1. item one
1. item two`)
).toMatchSnapshot();
expect(
renderToHtml(`1. item one
2. item two`)
).toMatchSnapshot();
});

View File

@@ -1,31 +0,0 @@
import { PluginSimple } from "markdown-it";
import createMarkdown from "@shared/editor/lib/markdown/rules";
import attachmentsRule from "@shared/editor/rules/attachments";
import breakRule from "@shared/editor/rules/breaks";
import checkboxRule from "@shared/editor/rules/checkboxes";
import embedsRule from "@shared/editor/rules/embeds";
import emojiRule from "@shared/editor/rules/emoji";
import markRule from "@shared/editor/rules/mark";
import noticesRule from "@shared/editor/rules/notices";
import tablesRule from "@shared/editor/rules/tables";
import underlinesRule from "@shared/editor/rules/underlines";
const defaultRules = [
embedsRule([]),
breakRule,
checkboxRule,
markRule({ delim: "==", mark: "highlight" }),
markRule({ delim: "!!", mark: "placeholder" }),
underlinesRule,
tablesRule,
noticesRule,
attachmentsRule,
emojiRule,
];
export default function renderToHtml(
markdown: string,
rulePlugins: PluginSimple[] = defaultRules
): string {
return createMarkdown({ plugins: rulePlugins }).render(markdown).trim();
}