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:
8
shared/editor/components/Styles.ts
Normal file
8
shared/editor/components/Styles.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import styled from "styled-components";
|
||||
import style, { Props } from "../../styles/editor";
|
||||
|
||||
const EditorContainer = styled.div<Props>`
|
||||
${style};
|
||||
`;
|
||||
|
||||
export default EditorContainer;
|
||||
@@ -33,12 +33,16 @@ export default class CheckboxItem extends Node {
|
||||
},
|
||||
],
|
||||
toDOM: (node) => {
|
||||
const input = document.createElement("span");
|
||||
input.tabIndex = -1;
|
||||
input.className = "checkbox";
|
||||
input.setAttribute("aria-checked", node.attrs.checked.toString());
|
||||
input.setAttribute("role", "checkbox");
|
||||
input.addEventListener("click", this.handleClick);
|
||||
const checked = node.attrs.checked.toString();
|
||||
let input;
|
||||
if (typeof document !== "undefined") {
|
||||
input = document.createElement("span");
|
||||
input.tabIndex = -1;
|
||||
input.className = "checkbox";
|
||||
input.setAttribute("aria-checked", checked);
|
||||
input.setAttribute("role", "checkbox");
|
||||
input.addEventListener("click", this.handleClick);
|
||||
}
|
||||
|
||||
return [
|
||||
"li",
|
||||
@@ -51,7 +55,9 @@ export default class CheckboxItem extends Node {
|
||||
{
|
||||
contentEditable: "false",
|
||||
},
|
||||
input,
|
||||
...(input
|
||||
? [input]
|
||||
: [["span", { class: "checkbox", "aria-checked": checked }]]),
|
||||
],
|
||||
["div", 0],
|
||||
];
|
||||
|
||||
@@ -122,44 +122,53 @@ export default class CodeFence extends Node {
|
||||
},
|
||||
],
|
||||
toDOM: (node) => {
|
||||
const button = document.createElement("button");
|
||||
button.innerText = this.options.dictionary.copy;
|
||||
button.type = "button";
|
||||
button.addEventListener("click", this.handleCopyToClipboard);
|
||||
let actions;
|
||||
if (typeof document !== "undefined") {
|
||||
const button = document.createElement("button");
|
||||
button.innerText = this.options.dictionary.copy;
|
||||
button.type = "button";
|
||||
button.addEventListener("click", this.handleCopyToClipboard);
|
||||
|
||||
const select = document.createElement("select");
|
||||
select.addEventListener("change", this.handleLanguageChange);
|
||||
const select = document.createElement("select");
|
||||
select.addEventListener("change", this.handleLanguageChange);
|
||||
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "code-actions";
|
||||
actions.appendChild(select);
|
||||
actions.appendChild(button);
|
||||
actions = document.createElement("div");
|
||||
actions.className = "code-actions";
|
||||
actions.appendChild(select);
|
||||
actions.appendChild(button);
|
||||
|
||||
this.languageOptions.forEach(([key, label]) => {
|
||||
const option = document.createElement("option");
|
||||
const value = key === "none" ? "" : key;
|
||||
option.value = value;
|
||||
option.innerText = label;
|
||||
option.selected = node.attrs.language === value;
|
||||
select.appendChild(option);
|
||||
});
|
||||
this.languageOptions.forEach(([key, label]) => {
|
||||
const option = document.createElement("option");
|
||||
const value = key === "none" ? "" : key;
|
||||
option.value = value;
|
||||
option.innerText = label;
|
||||
option.selected = node.attrs.language === value;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
// For the Mermaid language we add an extra button to toggle between
|
||||
// source code and a rendered diagram view.
|
||||
if (node.attrs.language === "mermaidjs") {
|
||||
const showSourceButton = document.createElement("button");
|
||||
showSourceButton.innerText = this.options.dictionary.showSource;
|
||||
showSourceButton.type = "button";
|
||||
showSourceButton.classList.add("show-source-button");
|
||||
showSourceButton.addEventListener("click", this.handleToggleDiagram);
|
||||
actions.prepend(showSourceButton);
|
||||
// For the Mermaid language we add an extra button to toggle between
|
||||
// source code and a rendered diagram view.
|
||||
if (node.attrs.language === "mermaidjs") {
|
||||
const showSourceButton = document.createElement("button");
|
||||
showSourceButton.innerText = this.options.dictionary.showSource;
|
||||
showSourceButton.type = "button";
|
||||
showSourceButton.classList.add("show-source-button");
|
||||
showSourceButton.addEventListener(
|
||||
"click",
|
||||
this.handleToggleDiagram
|
||||
);
|
||||
actions.prepend(showSourceButton);
|
||||
|
||||
const showDiagramButton = document.createElement("button");
|
||||
showDiagramButton.innerText = this.options.dictionary.showDiagram;
|
||||
showDiagramButton.type = "button";
|
||||
showDiagramButton.classList.add("show-digram-button");
|
||||
showDiagramButton.addEventListener("click", this.handleToggleDiagram);
|
||||
actions.prepend(showDiagramButton);
|
||||
const showDiagramButton = document.createElement("button");
|
||||
showDiagramButton.innerText = this.options.dictionary.showDiagram;
|
||||
showDiagramButton.type = "button";
|
||||
showDiagramButton.classList.add("show-digram-button");
|
||||
showDiagramButton.addEventListener(
|
||||
"click",
|
||||
this.handleToggleDiagram
|
||||
);
|
||||
actions.prepend(showDiagramButton);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -168,7 +177,7 @@ export default class CodeFence extends Node {
|
||||
class: "code-block",
|
||||
"data-language": node.attrs.language,
|
||||
},
|
||||
["div", { contentEditable: "false" }, actions],
|
||||
...(actions ? [["div", { contentEditable: "false" }, actions]] : []),
|
||||
["pre", ["code", { spellCheck: "false" }, 0]],
|
||||
];
|
||||
},
|
||||
|
||||
@@ -50,23 +50,28 @@ export default class Heading extends Node {
|
||||
contentElement: ".heading-content",
|
||||
})),
|
||||
toDOM: (node) => {
|
||||
const anchor = document.createElement("button");
|
||||
anchor.innerText = "#";
|
||||
anchor.type = "button";
|
||||
anchor.className = "heading-anchor";
|
||||
anchor.addEventListener("click", (event) => this.handleCopyLink(event));
|
||||
let anchor, fold;
|
||||
if (typeof document !== "undefined") {
|
||||
anchor = document.createElement("button");
|
||||
anchor.innerText = "#";
|
||||
anchor.type = "button";
|
||||
anchor.className = "heading-anchor";
|
||||
anchor.addEventListener("click", (event) =>
|
||||
this.handleCopyLink(event)
|
||||
);
|
||||
|
||||
const fold = document.createElement("button");
|
||||
fold.innerText = "";
|
||||
fold.innerHTML =
|
||||
'<svg fill="currentColor" width="12" height="24" viewBox="6 0 12 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.23823905,10.6097108 L11.207376,14.4695888 L11.207376,14.4695888 C11.54411,14.907343 12.1719566,14.989236 12.6097108,14.652502 C12.6783439,14.5997073 12.7398293,14.538222 12.792624,14.4695888 L15.761761,10.6097108 L15.761761,10.6097108 C16.0984949,10.1719566 16.0166019,9.54410997 15.5788477,9.20737601 C15.4040391,9.07290785 15.1896811,9 14.969137,9 L9.03086304,9 L9.03086304,9 C8.47857829,9 8.03086304,9.44771525 8.03086304,10 C8.03086304,10.2205442 8.10377089,10.4349022 8.23823905,10.6097108 Z" /></svg>';
|
||||
fold.type = "button";
|
||||
fold.className = `heading-fold ${
|
||||
node.attrs.collapsed ? "collapsed" : ""
|
||||
}`;
|
||||
fold.addEventListener("mousedown", (event) =>
|
||||
this.handleFoldContent(event)
|
||||
);
|
||||
fold = document.createElement("button");
|
||||
fold.innerText = "";
|
||||
fold.innerHTML =
|
||||
'<svg fill="currentColor" width="12" height="24" viewBox="6 0 12 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.23823905,10.6097108 L11.207376,14.4695888 L11.207376,14.4695888 C11.54411,14.907343 12.1719566,14.989236 12.6097108,14.652502 C12.6783439,14.5997073 12.7398293,14.538222 12.792624,14.4695888 L15.761761,10.6097108 L15.761761,10.6097108 C16.0984949,10.1719566 16.0166019,9.54410997 15.5788477,9.20737601 C15.4040391,9.07290785 15.1896811,9 14.969137,9 L9.03086304,9 L9.03086304,9 C8.47857829,9 8.03086304,9.44771525 8.03086304,10 C8.03086304,10.2205442 8.10377089,10.4349022 8.23823905,10.6097108 Z" /></svg>';
|
||||
fold.type = "button";
|
||||
fold.className = `heading-fold ${
|
||||
node.attrs.collapsed ? "collapsed" : ""
|
||||
}`;
|
||||
fold.addEventListener("mousedown", (event) =>
|
||||
this.handleFoldContent(event)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
`h${node.attrs.level + (this.options.offset || 0)}`,
|
||||
@@ -78,8 +83,7 @@ export default class Heading extends Node {
|
||||
node.attrs.collapsed ? "collapsed" : ""
|
||||
}`,
|
||||
},
|
||||
anchor,
|
||||
fold,
|
||||
...(anchor ? [anchor, fold] : []),
|
||||
],
|
||||
[
|
||||
"span",
|
||||
|
||||
@@ -52,40 +52,43 @@ export default class Notice extends Node {
|
||||
},
|
||||
],
|
||||
toDOM: (node) => {
|
||||
const select = document.createElement("select");
|
||||
select.addEventListener("change", this.handleStyleChange);
|
||||
let icon, actions;
|
||||
if (typeof document !== "undefined") {
|
||||
const select = document.createElement("select");
|
||||
select.addEventListener("change", this.handleStyleChange);
|
||||
|
||||
this.styleOptions.forEach(([key, label]) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = key;
|
||||
option.innerText = label;
|
||||
option.selected = node.attrs.style === key;
|
||||
select.appendChild(option);
|
||||
});
|
||||
this.styleOptions.forEach(([key, label]) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = key;
|
||||
option.innerText = label;
|
||||
option.selected = node.attrs.style === key;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "notice-actions";
|
||||
actions.appendChild(select);
|
||||
actions = document.createElement("div");
|
||||
actions.className = "notice-actions";
|
||||
actions.appendChild(select);
|
||||
|
||||
let component;
|
||||
let component;
|
||||
|
||||
if (node.attrs.style === "tip") {
|
||||
component = <StarredIcon color="currentColor" />;
|
||||
} else if (node.attrs.style === "warning") {
|
||||
component = <WarningIcon color="currentColor" />;
|
||||
} else {
|
||||
component = <InfoIcon color="currentColor" />;
|
||||
if (node.attrs.style === "tip") {
|
||||
component = <StarredIcon color="currentColor" />;
|
||||
} else if (node.attrs.style === "warning") {
|
||||
component = <WarningIcon color="currentColor" />;
|
||||
} else {
|
||||
component = <InfoIcon color="currentColor" />;
|
||||
}
|
||||
|
||||
icon = document.createElement("div");
|
||||
icon.className = "icon";
|
||||
ReactDOM.render(component, icon);
|
||||
}
|
||||
|
||||
const icon = document.createElement("div");
|
||||
icon.className = "icon";
|
||||
ReactDOM.render(component, icon);
|
||||
|
||||
return [
|
||||
"div",
|
||||
{ class: `notice-block ${node.attrs.style}` },
|
||||
icon,
|
||||
["div", { contentEditable: "false" }, actions],
|
||||
...(icon ? [icon] : []),
|
||||
["div", { contentEditable: "false" }, ...(actions ? [actions] : [])],
|
||||
["div", { class: "content" }, 0],
|
||||
];
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user