Skip to content

Commit fc072d2

Browse files
committed
chat: slate wysiwyg editing for chat, mostly done and working nicely.
1 parent 408f76d commit fc072d2

File tree

15 files changed

+245
-59
lines changed

15 files changed

+245
-59
lines changed

src/packages/frontend/chat/input.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*/
55

66
import { useActions, useRedux } from "../app-framework";
7-
import { MarkdownInput } from "../editors/markdown-input";
7+
//import { MarkdownInput } from "../editors/markdown-input";
8+
import MarkdownInput from "@cocalc/frontend/editors/markdown-input/multimode";
89
import { IS_MOBILE } from "../feature";
910

1011
interface Props {
@@ -23,8 +24,6 @@ export const ChatInput: React.FC<Props> = (props) => {
2324
const font_size = useRedux(["font_size"], props.project_id, props.path);
2425
return (
2526
<MarkdownInput
26-
project_id={props.project_id}
27-
path={props.path}
2827
value={props.input}
2928
enableUpload={true}
3029
onUploadStart={() => actions?.set_uploading(true)}

src/packages/frontend/chat/message.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,15 @@ export const Message: React.FC<Props> = React.memo((props) => {
7878
[props.message] /* note -- edited_message is a function of props.message */
7979
);
8080

81-
const history_size = useMemo(() => props.message.get("history").size, [
82-
props.message,
83-
]);
81+
const history_size = useMemo(
82+
() => props.message.get("history").size,
83+
[props.message]
84+
);
8485

85-
const isEditing = useMemo(() => is_editing(props.message, props.account_id), [
86-
props.message,
87-
props.account_id,
88-
]);
86+
const isEditing = useMemo(
87+
() => is_editing(props.message, props.account_id),
88+
[props.message, props.account_id]
89+
);
8990

9091
const editor_name = useMemo(() => {
9192
return props.get_user_name(
@@ -297,9 +298,11 @@ export const Message: React.FC<Props> = React.memo((props) => {
297298
className="smc-chat-message"
298299
onDoubleClick={edit_message}
299300
>
300-
<span style={lighten}>
301-
<Time message={props.message} edit={edit_message} />
302-
</span>
301+
{!isEditing && (
302+
<span style={lighten}>
303+
<Time message={props.message} edit={edit_message} />
304+
</span>
305+
)}
303306
{!isEditing ? (
304307
<Markdown
305308
value={value}

src/packages/frontend/chat/side-chat.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,6 @@ export const SideChat: React.FC<Props> = ({ project_id, path }: Props) => {
190190
style={{
191191
marginTop: "auto",
192192
padding: "5px",
193-
paddingLeft: "15px",
194-
paddingRight: "15px",
195193
display: "flex",
196194
flexDirection: "column",
197195
}}
@@ -216,9 +214,8 @@ export const SideChat: React.FC<Props> = ({ project_id, path }: Props) => {
216214
width: INPUT_HEIGHT /* yes, to make it square */,
217215
}}
218216
>
219-
<div style={{ flex: 1 }} />
220217
<Button
221-
style={{ height: INPUT_HEIGHT }}
218+
style={{ flex: 1, marginLeft: "5px" }}
222219
onClick={() => {
223220
send_chat();
224221
user_activity("side_chat", "send_chat", "click");

src/packages/frontend/chat/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { redux } from "../app-framework";
1212
import { MentionList } from "./store";
1313
import { Message } from "./types";
1414

15-
export const INPUT_HEIGHT = "100px";
15+
export const INPUT_HEIGHT = "130px";
1616

1717
export const USER_MENTION_MARKUP =
1818
'<span class="user-mention" account-id=__id__ >@__display__</span>';

src/packages/frontend/editors/markdown-input/component.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,15 @@ import {
7373
} from "@cocalc/util/misc";
7474
import { IS_MOBILE } from "../../feature";
7575
import { A } from "../../components";
76+
import { useTypedRedux, useRedux, redux, ReactDOM } from "../../app-framework";
7677
import {
77-
React,
78-
ReactDOM,
78+
CSSProperties,
79+
FC,
80+
ReactNode,
7981
useEffect,
8082
useRef,
81-
useRedux,
8283
useState,
83-
useTypedRedux,
84-
redux,
85-
} from "../../app-framework";
84+
} from "react";
8685
import { Dropzone, FileUploadWrapper } from "../../file-upload";
8786
import { alert_message } from "../../alerts";
8887
import { Complete, Item } from "./complete";
@@ -92,11 +91,11 @@ import { mentionableUsers } from "./mentionable-users";
9291
// This code depends on codemirror being initialized.
9392
import "@cocalc/frontend/codemirror/init";
9493

95-
const BLURED_STYLE: React.CSSProperties = {
94+
export const BLURED_STYLE: CSSProperties = {
9695
border: "1px solid rgb(204,204,204)", // focused will be rgb(112, 178, 230);
9796
};
9897

99-
const FOCUSED_STYLE: React.CSSProperties = {
98+
export const FOCUSED_STYLE: CSSProperties = {
10099
outline: "none !important",
101100
boxShadow: "0px 0px 5px #719ECE",
102101
border: "1px solid #719ECE",
@@ -117,22 +116,22 @@ interface Props {
117116
onUploadEnd?: () => void;
118117
enableMentions?: boolean;
119118
submitMentionsRef?: any;
120-
style?: React.CSSProperties;
119+
style?: CSSProperties;
121120
onShiftEnter?: (value: string) => void; // also ctrl/alt/cmd-enter call this; see https://github.com/sagemathinc/cocalc/issues/1914
122121
onEscape?: () => void;
123122
onBlur?: (value: string) => void;
124123
onFocus?: () => void;
125124
placeholder?: string;
126125
height?: string;
127-
extraHelp?: string | JSX.Element;
126+
extraHelp?: ReactNode;
128127
hideHelp?: boolean;
129128
fontSize?: number;
130129
styleActiveLine?: boolean;
131130
lineWrapping?: boolean;
132131
autoFocus?: boolean;
133132
}
134133

135-
export const MarkdownInput: React.FC<Props> = ({
134+
export const MarkdownInput: FC<Props> = ({
136135
project_id,
137136
path,
138137
value,
@@ -181,6 +180,10 @@ export const MarkdownInput: React.FC<Props> = ({
181180
useEffect(() => {
182181
// initialize the codemirror editor
183182
const node = ReactDOM.findDOMNode(textarea_ref.current);
183+
if (node == null) {
184+
// maybe unmounted right as this happened.
185+
return;
186+
}
184187
const extraKeys: CodeMirror.KeyMap = {};
185188
if (onShiftEnter != null) {
186189
const f = (cm) => onShiftEnter(cm.getValue());
@@ -465,7 +468,12 @@ export const MarkdownInput: React.FC<Props> = ({
465468
// TODO: make clicking on drag and drop thing pop up dialog
466469
return (
467470
<div
468-
style={{ color: "#767676", fontSize: "12.5px", marginBottom: "5px" }}
471+
style={{
472+
color: "#767676",
473+
fontSize: "12.5px",
474+
padding: "5px 15px",
475+
background: "white",
476+
}}
469477
>
470478
{render_mention_instructions()}
471479
{render_mention_email()}. Use{" "}
@@ -487,7 +495,14 @@ export const MarkdownInput: React.FC<Props> = ({
487495
// TODO: make clicking on drag and drop thing pop up dialog
488496
if (hideHelp) return;
489497
return (
490-
<div style={{ fontSize: "12.5px", marginBottom: "5px" }}>
498+
<div
499+
style={{
500+
fontSize: "12.5px",
501+
padding: "5px 15px",
502+
color: "#767676",
503+
background: "white",
504+
}}
505+
>
491506
Shift+Enter when done. {render_mention_instructions()}
492507
Use{" "}
493508
<A href="https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/">
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
Edit with either plain text input **or** WYSIWYG slate-based input.
3+
4+
Work in progress!s
5+
*/
6+
7+
import { Checkbox } from "antd";
8+
import "@cocalc/frontend/editors/slate/elements/math/math-widget";
9+
import { EditableMarkdown } from "@cocalc/frontend/editors/slate/editable-markdown";
10+
import { MarkdownInput } from "./component";
11+
import { CSSProperties, ReactNode, useState } from "react";
12+
import { useFrameContext } from "@cocalc/frontend/frame-editors/frame-tree/frame-context";
13+
import { FOCUSED_STYLE, BLURED_STYLE } from "./component";
14+
15+
export type Mode = "markdown" | "editor";
16+
17+
const LOCAL_STORAGE_KEY = "markdown-editor-mode";
18+
19+
interface Props {
20+
value: string;
21+
defaultMode?: Mode; // defaults to editor or whatever was last used (as stored in localStorage)
22+
onChange?: (value: string) => void;
23+
onShiftEnter?: () => void;
24+
placeholder?: string;
25+
fontSize?: number;
26+
height?: string;
27+
style?: CSSProperties;
28+
autoFocus?: boolean;
29+
enableMentions?: boolean;
30+
enableUpload?: boolean;
31+
onUploadStart?: () => void;
32+
onUploadEnd?: () => void;
33+
submitMentionsRef?: any;
34+
extraHelp?: ReactNode;
35+
lineWrapping?: boolean;
36+
}
37+
38+
export default function MultiMarkdownInput({
39+
value,
40+
defaultMode,
41+
onChange,
42+
onShiftEnter,
43+
placeholder,
44+
fontSize,
45+
height,
46+
style,
47+
autoFocus,
48+
enableMentions,
49+
enableUpload,
50+
onUploadStart,
51+
onUploadEnd,
52+
submitMentionsRef,
53+
extraHelp,
54+
lineWrapping,
55+
}: Props) {
56+
const { project_id, path } = useFrameContext();
57+
const [mode, setMode0] = useState<Mode>(
58+
defaultMode ?? localStorage[LOCAL_STORAGE_KEY] ?? "editor"
59+
);
60+
const setMode = (mode: Mode) => {
61+
localStorage[LOCAL_STORAGE_KEY] = mode;
62+
setMode0(mode);
63+
};
64+
const [focused, setFocused] = useState<boolean>(!!autoFocus);
65+
return (
66+
<div
67+
style={{
68+
background: "white",
69+
color: "black",
70+
position: "relative",
71+
width: "100%",
72+
...(focused ? FOCUSED_STYLE : BLURED_STYLE),
73+
}}
74+
>
75+
<Checkbox
76+
style={{
77+
...(mode == "editor" || !value
78+
? { position: "absolute", right: 1, top: 1, zIndex: 100 }
79+
: { float: "right" }),
80+
fontWeight: 250,
81+
}}
82+
checked={mode == "markdown"}
83+
onClick={(e: any) => setMode(e.target.checked ? "markdown" : "editor")}
84+
>
85+
Markdown
86+
</Checkbox>
87+
{mode == "markdown" && (
88+
<MarkdownInput
89+
value={value}
90+
onChange={onChange}
91+
project_id={project_id}
92+
path={path}
93+
enableUpload={enableUpload}
94+
onUploadStart={onUploadStart}
95+
onUploadEnd={onUploadEnd}
96+
enableMentions={enableMentions}
97+
onShiftEnter={onShiftEnter}
98+
placeholder={placeholder}
99+
fontSize={fontSize}
100+
lineWrapping={lineWrapping}
101+
height={height}
102+
style={style}
103+
autoFocus={autoFocus}
104+
submitMentionsRef={submitMentionsRef}
105+
extraHelp={extraHelp}
106+
/>
107+
)}
108+
{mode == "editor" && (
109+
<div
110+
style={{
111+
...style,
112+
height: height ?? "100%",
113+
width: "100%",
114+
}}
115+
className="smc-vfill"
116+
>
117+
<EditableMarkdown
118+
value={value}
119+
is_current={true}
120+
hidePath
121+
disableWindowing
122+
pageStyle={{
123+
padding: "5px 15px",
124+
height: height ?? "100%",
125+
}}
126+
actions={{
127+
set_value: (value) => {
128+
onChange?.(value);
129+
},
130+
}}
131+
font_size={fontSize}
132+
autoFocus={autoFocus}
133+
onFocus={() => setFocused(true)}
134+
onBlur={() => setFocused(false)}
135+
hideSearch
136+
/>
137+
</div>
138+
)}
139+
</div>
140+
);
141+
}

src/packages/frontend/editors/slate/edit-bar/component.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface Props {
2020
listProperties: ListProperties | undefined;
2121
editor: SlateEditor;
2222
style?: React.CSSProperties;
23+
hideSearch?: boolean; // often on SMALL docs, e.g., when embedding in chat, it's pointless to have our own find.
2324
}
2425

2526
const HEIGHT = "25px";
@@ -32,16 +33,19 @@ export const EditBar: React.FC<Props> = ({
3233
listProperties,
3334
editor,
3435
style,
36+
hideSearch,
3537
}) => {
3638
function renderContent() {
3739
return (
3840
<>
3941
<MarksBar marks={marks} editor={editor} />
4042
<LinkEdit linkURL={linkURL} editor={editor} />
4143
<ListEdit listProperties={listProperties} editor={editor} />
42-
<div style={{ flex: 1, maxWidth: "50ex", marginRight: "15px" }}>
43-
{Search}
44-
</div>
44+
{!hideSearch && (
45+
<div style={{ flex: 1, maxWidth: "50ex", marginRight: "15px" }}>
46+
{Search}
47+
</div>
48+
)}
4549
</>
4650
);
4751
}

0 commit comments

Comments
 (0)