2022-05-14 12:45:29 +00:00
|
|
|
import { AppLayout } from "components/AppLayout";
|
|
|
|
import { Button } from "components/Inputs/Button";
|
|
|
|
import { Markdawn } from "components/Markdown/Markdawn";
|
|
|
|
import {
|
|
|
|
ContentPanel,
|
2022-02-17 16:55:44 +00:00
|
|
|
ContentPanelWidthSizes,
|
|
|
|
} from "components/Panels/ContentPanel";
|
2022-05-14 12:45:29 +00:00
|
|
|
import { Popup } from "components/Popup";
|
|
|
|
import { ToolTip } from "components/ToolTip";
|
2022-05-08 10:26:36 +00:00
|
|
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
2022-05-08 19:37:41 +00:00
|
|
|
import { Immutable } from "helpers/types";
|
|
|
|
import { GetStaticPropsContext } from "next";
|
2022-03-26 16:09:42 +00:00
|
|
|
import { useCallback, useState } from "react";
|
2022-04-04 09:08:41 +00:00
|
|
|
import TurndownService from "turndown";
|
2022-05-22 14:24:16 +00:00
|
|
|
import { Ico, Icon } from "components/Ico";
|
2022-02-17 16:55:44 +00:00
|
|
|
|
2022-04-03 08:34:21 +00:00
|
|
|
interface Props extends AppStaticProps {}
|
2022-02-17 16:55:44 +00:00
|
|
|
|
2022-05-08 19:37:41 +00:00
|
|
|
export default function Editor(props: Immutable<Props>): JSX.Element {
|
2022-04-04 09:08:41 +00:00
|
|
|
const handleInput = useCallback((text: string) => {
|
|
|
|
setMarkdown(text);
|
2022-02-17 16:55:44 +00:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
const [markdown, setMarkdown] = useState("");
|
2022-04-04 09:08:41 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-02-17 16:55:44 +00:00
|
|
|
|
|
|
|
const contentPanel = (
|
|
|
|
<ContentPanel width={ContentPanelWidthSizes.large}>
|
2022-04-04 09:08:41 +00:00
|
|
|
<Popup setState={setConverterOpened} state={converterOpened}>
|
|
|
|
<div className="text-center">
|
|
|
|
<h2 className="mt-4">Convert HTML to markdown</h2>
|
|
|
|
<p>
|
|
|
|
Copy and paste any HTML content (content from web pages) here.{" "}
|
|
|
|
<br />
|
|
|
|
The text will immediatly be converted to valid Markdown.
|
|
|
|
<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();
|
|
|
|
}}
|
2022-05-21 11:18:57 +00:00
|
|
|
className="h-[50vh] w-[50vw] font-monospace mobile:w-[75vw]"
|
2022-04-04 09:08:41 +00:00
|
|
|
/>
|
|
|
|
</Popup>
|
2022-05-21 11:18:57 +00:00
|
|
|
<div className="mb-4 flex flex-row gap-2">
|
2022-04-04 09:08:41 +00:00
|
|
|
<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 (
|
2022-05-22 14:24:16 +00:00
|
|
|
<Ico className="text-xs" icon={Icon.RecordVoiceOver} />)
|
2022-04-04 09:08:41 +00:00
|
|
|
</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);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.QuestionAnswer}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</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,
|
2022-04-04 09:09:30 +00:00
|
|
|
'<Line name="speaker">',
|
2022-04-04 09:08:41 +00:00
|
|
|
"</Line>\n",
|
|
|
|
selectionStart,
|
|
|
|
selectionEnd
|
|
|
|
);
|
|
|
|
handleInput(textarea.value);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.RecordVoiceOver}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</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);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.DensityLarge}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</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);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.CheckBoxOutlineBlank}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</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);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.MoreHoriz}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</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);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.Link}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</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);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.Link}
|
|
|
|
text="+ target"
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</ToolTip>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
>
|
2022-05-22 14:24:16 +00:00
|
|
|
<Button icon={Icon.Link} />
|
2022-04-04 09:08:41 +00:00
|
|
|
</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);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.Person}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</ToolTip>
|
|
|
|
|
|
|
|
<ToolTip
|
|
|
|
placement="bottom"
|
|
|
|
content={<h3 className="text-lg">Open HTML Converter</h3>}
|
|
|
|
>
|
|
|
|
<Button
|
|
|
|
onClick={() => {
|
|
|
|
setConverterOpened(true);
|
|
|
|
}}
|
2022-05-22 14:24:16 +00:00
|
|
|
icon={Icon.Html}
|
|
|
|
/>
|
2022-04-04 09:08:41 +00:00
|
|
|
</ToolTip>
|
|
|
|
</div>
|
2022-02-17 16:55:44 +00:00
|
|
|
|
2022-02-17 18:49:28 +00:00
|
|
|
<div className="grid grid-cols-2 gap-8">
|
2022-02-17 16:55:44 +00:00
|
|
|
<div>
|
|
|
|
<h2>Editor</h2>
|
|
|
|
<textarea
|
|
|
|
id="editorTextArea"
|
2022-04-04 09:08:41 +00:00
|
|
|
onInput={(event) => {
|
|
|
|
const textarea = event.target as HTMLTextAreaElement;
|
|
|
|
handleInput(textarea.value);
|
|
|
|
}}
|
2022-05-21 11:18:57 +00:00
|
|
|
className="h-[70vh] w-full rounded-xl
|
|
|
|
bg-mid !bg-opacity-40 p-8 font-monospace text-black outline-none"
|
2022-02-17 18:49:28 +00:00
|
|
|
value={markdown}
|
2022-03-07 14:50:00 +00:00
|
|
|
title="Input textarea"
|
2022-02-17 18:49:28 +00:00
|
|
|
/>
|
2022-02-17 16:55:44 +00:00
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<h2>Preview</h2>
|
2022-05-21 11:18:57 +00:00
|
|
|
<div className="h-[70vh] overflow-scroll rounded-xl bg-mid bg-opacity-40 p-8">
|
2022-04-04 09:08:41 +00:00
|
|
|
<Markdawn className="w-full" text={markdown} />
|
2022-02-17 16:55:44 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</ContentPanel>
|
|
|
|
);
|
2022-02-17 18:49:28 +00:00
|
|
|
return (
|
|
|
|
<AppLayout
|
2022-02-24 03:50:00 +00:00
|
|
|
navTitle="Markdawn Editor"
|
2022-02-17 18:49:28 +00:00
|
|
|
contentPanel={contentPanel}
|
2022-03-07 14:50:00 +00:00
|
|
|
{...props}
|
2022-02-17 18:49:28 +00:00
|
|
|
/>
|
|
|
|
);
|
2022-02-17 16:55:44 +00:00
|
|
|
}
|
|
|
|
|
2022-03-27 15:01:14 +00:00
|
|
|
export async function getStaticProps(
|
|
|
|
context: GetStaticPropsContext
|
2022-04-03 08:34:21 +00:00
|
|
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
|
|
|
const props: Props = {
|
2022-03-07 14:50:00 +00:00
|
|
|
...(await getAppStaticProps(context)),
|
|
|
|
};
|
|
|
|
return {
|
|
|
|
props: props,
|
|
|
|
};
|
2022-03-27 15:01:14 +00:00
|
|
|
}
|