Editor now has buttons to easily add custom content
This commit is contained in:
parent
3f452c529a
commit
286187135f
|
@ -128,7 +128,7 @@ export default function Markdawn(props: Props): JSX.Element {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Sep: {
|
Sep: {
|
||||||
component: () => <div className="my-24"></div>,
|
component: () => <div className="my-18"></div>,
|
||||||
},
|
},
|
||||||
SceneBreak: {
|
SceneBreak: {
|
||||||
component: (compProps: { id: string }) => (
|
component: (compProps: { id: string }) => (
|
||||||
|
|
|
@ -1,80 +1,351 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import AppLayout from "components/AppLayout";
|
||||||
|
import Button from "components/Button";
|
||||||
import Markdawn from "components/Markdown/Markdawn";
|
import Markdawn from "components/Markdown/Markdawn";
|
||||||
import ContentPanel, {
|
import ContentPanel, {
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
|
import Popup from "components/Popup";
|
||||||
|
import ToolTip from "components/ToolTip";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import Script from "next/script";
|
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { default as TurndownService } from "turndown";
|
import TurndownService from "turndown";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function Editor(props: Props): JSX.Element {
|
export default function Editor(props: Props): JSX.Element {
|
||||||
const handleInput = useCallback((event) => {
|
const handleInput = useCallback((text: string) => {
|
||||||
setMarkdown(event.target.value);
|
setMarkdown(text);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [markdown, setMarkdown] = useState("");
|
const [markdown, setMarkdown] = useState("");
|
||||||
|
const [converterOpened, setConverterOpened] = useState(false);
|
||||||
|
|
||||||
|
function insert(
|
||||||
|
text: string,
|
||||||
|
prepend: string,
|
||||||
|
append: string,
|
||||||
|
selectionStart: number,
|
||||||
|
selectionEnd: number
|
||||||
|
): string {
|
||||||
|
let newText = text.slice(0, selectionStart);
|
||||||
|
newText += prepend;
|
||||||
|
newText += text.slice(selectionStart, selectionEnd);
|
||||||
|
newText += append;
|
||||||
|
newText += text.slice(selectionEnd);
|
||||||
|
return newText;
|
||||||
|
}
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||||
<Script
|
<Popup setState={setConverterOpened} state={converterOpened}>
|
||||||
id="autoFitTextArea"
|
<div className="text-center">
|
||||||
strategy="afterInteractive"
|
<h2 className="mt-4">Convert HTML to markdown</h2>
|
||||||
dangerouslySetInnerHTML={{
|
<p>
|
||||||
__html: `
|
Copy and paste any HTML content (content from web pages) here.{" "}
|
||||||
const el = document.querySelector("#editorTextArea")
|
<br />
|
||||||
el.addEventListener('input', function() {
|
The text will immediatly be converted to valid Markdown.
|
||||||
this.style.height = (this.scrollHeight) + 'px';
|
<br />
|
||||||
});
|
You can then copy the converted text and paste it anywhere you want
|
||||||
`,
|
in the editor
|
||||||
}}
|
</p>
|
||||||
/>
|
</div>
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
id="htmlMdTextArea"
|
||||||
|
title="Ouput textarea"
|
||||||
|
onPaste={(event) => {
|
||||||
|
const turndownService = new TurndownService({
|
||||||
|
headingStyle: "atx",
|
||||||
|
codeBlockStyle: "fenced",
|
||||||
|
bulletListMarker: "-",
|
||||||
|
emDelimiter: "_",
|
||||||
|
strongDelimiter: "**",
|
||||||
|
});
|
||||||
|
|
||||||
|
let paste = event.clipboardData.getData("text/html");
|
||||||
|
paste = paste.replace(/<!--.*?-->/u, "");
|
||||||
|
paste = turndownService.turndown(paste);
|
||||||
|
paste = paste.replace(/<!--.*?-->/u, "");
|
||||||
|
|
||||||
|
const target = event.target as HTMLTextAreaElement;
|
||||||
|
target.value = paste;
|
||||||
|
target.select();
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
className="font-monospace w-[50vw] h-[50vh] mobile:w-[75vw]"
|
||||||
|
/>
|
||||||
|
</Popup>
|
||||||
|
<div className="flex flex-row gap-2 mb-4">
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Transcript container</h3>
|
||||||
|
<p>
|
||||||
|
Use this to create dialogues and transcripts. You can then add
|
||||||
|
transcript speech line within (
|
||||||
|
<span className="material-icons text-xs">
|
||||||
|
record_voice_over
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
"\n<Transcript>\n",
|
||||||
|
"\n</Transcript>\n",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">question_answer</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Transcript speech line</h3>
|
||||||
|
<p>
|
||||||
|
Use to add a dialogue/transcript line. Change the{" "}
|
||||||
|
<kbd>name</kbd> property to chang the name of the speaker
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
"<Line name=\"speaker\">",
|
||||||
|
"</Line>\n",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">record_voice_over</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Vertical spacer</h3>}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
"<Sep />",
|
||||||
|
"",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">density_large</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Inset box</h3>}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
"\n<InsetBox>\n",
|
||||||
|
"\n</InsetBox>\n",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">check_box_outline_blank</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Scene break</h3>}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
"\n\n<SceneBreak />\n\n",
|
||||||
|
"",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">more_horiz</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
content={
|
||||||
|
<div className="flex flex-col place-items-center gap-2">
|
||||||
|
<h3 className="text-lg">Intralink</h3>
|
||||||
|
<ToolTip
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Intralink</h3>
|
||||||
|
<p className="text-xs">
|
||||||
|
Interlinks are used to add links to a header within the
|
||||||
|
same document
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
"<IntraLink>",
|
||||||
|
"</IntraLink>",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">link</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Intralink (with target)</h3>{" "}
|
||||||
|
<p className="text-xs">
|
||||||
|
Use this one if you want the intralink text to be
|
||||||
|
different from the target header’s name.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
'<IntraLink target="target">',
|
||||||
|
"</IntraLink>",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="flex place-items-center gap-1">
|
||||||
|
<span className="material-icons">link</span>+ target
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button>
|
||||||
|
<span className="material-icons">link</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Player’s name placeholder</h3>}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const textarea = document.querySelector(
|
||||||
|
"#editorTextArea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
textarea.value = insert(
|
||||||
|
value,
|
||||||
|
"<player>",
|
||||||
|
"",
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd
|
||||||
|
);
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">person</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Open HTML Converter</h3>}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setConverterOpened(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="material-icons">html</span>
|
||||||
|
</Button>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8">
|
<div className="grid grid-cols-2 gap-8">
|
||||||
<div>
|
<div>
|
||||||
<h2>Editor</h2>
|
<h2>Editor</h2>
|
||||||
<textarea
|
<textarea
|
||||||
id="editorTextArea"
|
id="editorTextArea"
|
||||||
onInput={handleInput}
|
onInput={(event) => {
|
||||||
className="bg-mid rounded-xl p-8 w-full font-monospace"
|
const textarea = event.target as HTMLTextAreaElement;
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
className="bg-mid !bg-opacity-40 rounded-xl outline-none p-8 w-full text-black font-monospace h-[70vh]"
|
||||||
value={markdown}
|
value={markdown}
|
||||||
title="Input textarea"
|
title="Input textarea"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h2 className="mt-4">Convert text to markdown</h2>
|
|
||||||
<textarea
|
|
||||||
readOnly
|
|
||||||
id="htmlMdTextArea"
|
|
||||||
title="Ouput textarea"
|
|
||||||
onPaste={(event) => {
|
|
||||||
const turndownService = new TurndownService({
|
|
||||||
headingStyle: "atx",
|
|
||||||
codeBlockStyle: "fenced",
|
|
||||||
bulletListMarker: "-",
|
|
||||||
emDelimiter: "*",
|
|
||||||
strongDelimiter: "**",
|
|
||||||
});
|
|
||||||
|
|
||||||
let paste = event.clipboardData.getData("text/html");
|
|
||||||
paste = paste.replace(/<!--.*?-->/u, "");
|
|
||||||
paste = turndownService.turndown(paste);
|
|
||||||
paste = paste.replace(/<!--.*?-->/u, "");
|
|
||||||
|
|
||||||
const target = event.target as HTMLTextAreaElement;
|
|
||||||
target.value = paste;
|
|
||||||
target.select();
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
className="font-monospace"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Preview</h2>
|
<h2>Preview</h2>
|
||||||
<div className="bg-mid rounded-xl p-8">
|
<div className="bg-mid bg-opacity-40 rounded-xl p-8 h-[70vh] overflow-scroll">
|
||||||
<Markdawn className="max-w-full" text={markdown} />
|
<Markdawn className="w-full" text={markdown} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue