publish: use CodeMirror as editor

This commit is contained in:
Liam Fitzgerald 2020-09-23 13:12:35 +10:00
parent 2a0dc1a3d8
commit 30ee75413d
10 changed files with 143 additions and 43 deletions

View File

@ -9,7 +9,7 @@
"@reach/menu-button": "^0.10.5",
"@reach/tabs": "^0.10.5",
"@tlon/indigo-light": "^1.0.3",
"@tlon/indigo-react": "github:liam-fitzgerald/indigo-react#lf/1.2.5",
"@tlon/indigo-react": "1.2.5",
"aws-sdk": "^2.726.0",
"classnames": "^2.2.6",
"codemirror": "^5.55.0",

View File

@ -0,0 +1,2 @@
export type PropFunc<T extends (...args: any[]) => any> = Parameters<T>[0];

View File

@ -0,0 +1,73 @@
import React, { useCallback } from "react";
import { UnControlled as CodeEditor } from "react-codemirror2";
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import { PropFunc } from "~/types/util";
import CodeMirror from "codemirror";
import "codemirror/mode/markdown/markdown";
import "codemirror/addon/display/placeholder";
import "codemirror/lib/codemirror.css";
import { Box } from "@tlon/indigo-react";
const MARKDOWN_CONFIG = {
name: "markdown",
};
interface MarkdownEditorProps {
placeholder?: string;
value: string;
onChange: (s: string) => void;
onBlur?: (e: any) => void;
}
export function MarkdownEditor(
props: MarkdownEditorProps & PropFunc<typeof Box>
) {
const { onBlur, placeholder, value, onChange, ...boxProps } = props;
const options = {
mode: MARKDOWN_CONFIG,
theme: "tlon",
lineNumbers: false,
lineWrapping: true,
scrollbarStyle: "native",
// cursorHeight: 0.85,
placeholder: placeholder || "",
};
const handleChange = useCallback(
(_e, _d, v: string) => {
onChange(v);
},
[onChange]
);
const handleBlur = useCallback(
(_i, e: any) => {
onBlur && onBlur(e);
},
[onBlur]
);
return (
<Box
flexGrow={1}
position="static"
className="publish"
p={1}
border={1}
borderColor="lightGray"
borderRadius={2}
{...boxProps}
>
<CodeEditor
onBlur={onBlur}
value={value}
options={options}
onChange={handleChange}
/>
</Box>
);
}

View File

@ -1,27 +1,43 @@
import React from 'react';
import styled from 'styled-components';
import { MarkdownEditor as _MarkdownEditor, Box, ErrorMessage } from '@tlon/indigo-react';
import { useField } from 'formik';
import React, { useCallback } from "react";
import _ from "lodash";
import { Box, ErrorLabel } from "@tlon/indigo-react";
import { useField } from "formik";
import { MarkdownEditor } from "./MarkdownEditor";
const MarkdownEditor = styled(_MarkdownEditor)`
border: 1px solid ${(p) => p.theme.colors.lightGray};
border-radius: ${(p) => p.theme.radii[2]}px;
`;
export const MarkdownField = ({
id,
...rest
}: { id: string } & Parameters<typeof Box>[0]) => {
const [{ value, onBlur }, { error, touched }, { setValue }] = useField(id);
export const MarkdownField = ({ id, ...rest }: { id: string; } & Parameters<typeof Box>[0]) => {
const [{ value }, { error, touched }, { setValue, setTouched }] = useField(id);
const handleBlur = useCallback(
(e: any) => {
_.set(e, "target.id", id);
console.log(e);
onBlur && onBlur(e);
},
[onBlur, id]
);
const hasError = !!(error && touched);
return (
<Box overflowY="hidden" width="100%" display="flex" flexDirection="column" {...rest}>
<Box
overflowY="hidden"
width="100%"
display="flex"
flexDirection="column"
{...rest}
>
<MarkdownEditor
onFocus={() => setTouched(true)}
onBlur={() => setTouched(false)}
borderColor={hasError ? "red" : "lightGray"}
onBlur={handleBlur}
value={value}
onBeforeChange={(e, d, v) => setValue(v)}
onChange={setValue}
/>
<ErrorMessage>{touched && error}</ErrorMessage>
<ErrorLabel mt="2" hasError={!!(error && touched)}>
{error}
</ErrorLabel>
</Box>
);
};

View File

@ -44,6 +44,7 @@ export function PostForm(props: PostFormProps) {
validationSchema={formSchema}
initialValues={initial}
onSubmit={onSubmit}
validateOnBlur
>
<Form style={{ display: "contents" }}>
<Input width="100%" placeholder="Post Title" id="title" />

View File

@ -29,6 +29,7 @@ interface NotebookProps {
interface NotebookState {
isUnsubscribing: boolean;
tab: string;
}
export class Notebook extends PureComponent<
@ -44,7 +45,7 @@ export class Notebook extends PureComponent<
this.setTab = this.setTab.bind(this);
}
setTab(tab) {
setTab(tab: string) {
this.setState({ tab });
}
@ -102,9 +103,7 @@ export class Notebook extends PureComponent<
<Row justifyContent={["flex-start", "flex-end"]}>
{isWriter && (
<Link to={`/~publish/notebook/${ship}/${book}/new`}>
<Button primary border>
New Post
</Button>
<Button primary>New Post</Button>
</Link>
)}
{!isOwn ? (
@ -117,8 +116,7 @@ export class Notebook extends PureComponent<
) : (
<Button
ml={isWriter ? 2 : 0}
error
border
destructive
onClick={() => {
this.setState({ isUnsubscribing: true });
api.publish
@ -150,18 +148,22 @@ export class Notebook extends PureComponent<
label="About"
id="about"
/>
<Tab
selected={state.tab}
setSelected={this.setTab}
label="Subscribers"
id="subscribers"
/>
<Tab
selected={state.tab}
setSelected={this.setTab}
label="Settings"
id="settings"
/>
{isAdmin && (
<>
<Tab
selected={state.tab}
setSelected={this.setTab}
label="Subscribers"
id="subscribers"
/>
<Tab
selected={state.tab}
setSelected={this.setTab}
label="Settings"
id="settings"
/>
</>
)}
</Tabs>
{state.tab === "all" && (
<NotebookPosts
@ -173,7 +175,11 @@ export class Notebook extends PureComponent<
hideNicknames={hideNicknames}
/>
)}
{state.tab === "about" && <Box color="black">{notebook?.about}</Box>}
{state.tab === "about" && (
<Box mt="3" color="black">
{notebook?.about}
</Box>
)}
{state.tab === "subscribers" && (
<Subscribers
host={ship}

View File

@ -15,7 +15,7 @@ interface NotebookPostsProps {
export function NotebookPosts(props: NotebookPostsProps) {
return (
<Col>
<Col mt="3">
{props.list.map((noteId: NoteId) => {
const note = props.notes[noteId];
if (!note) {

View File

@ -20,7 +20,7 @@ interface SettingsProps {
}
const Divider = (props) => (
<Box {...props} mb={4} borderBottom={1} borderBottomColor="lightGray" />
<Box {...props} borderBottom={1} borderBottomColor="lightGray" />
);
export function Settings(props: SettingsProps) {
const history = useHistory();
@ -36,10 +36,11 @@ export function Settings(props: SettingsProps) {
<Box
mx="auto"
maxWidth="300px"
mb={4}
my={4}
gridTemplateColumns="1fr"
gridAutoRows="auto"
display="grid"
gridRowGap={5}
>
{isUnmanaged && (
<>
@ -55,7 +56,7 @@ export function Settings(props: SettingsProps) {
Permanently delete this notebook. (All current members will no longer
see this notebook.)
</Label>
<Button onClick={onDelete} mt={1} border error>
<Button mt="2" onClick={onDelete} destructive>
Delete this notebook
</Button>
</Col>

View File

@ -80,7 +80,7 @@ export class Subscribers extends Component<SubscribersProps> {
const role = roleForShip(group, window.ship)
return (
<Box>
<Box mt="3">
{ role === 'admin' && (
<Button mb={3} border onClick={this.addAll}>
Add all members as writers

View File

@ -65,12 +65,13 @@
.publish .react-codemirror2 {
width: 100%;
height: 100%;
}
.publish .CodeMirror {
padding: 12px;
height: 100% !important;
max-width: 700px;
overflow-y: hidden;
width: 100% !important;
cursor: text;
font-size: 12px;