Skip to content

Commit b594aa8

Browse files
committed
Merge remote-tracking branch 'origin/master' into api-exec-await
2 parents 2794561 + ea33cd2 commit b594aa8

File tree

21 files changed

+445
-220
lines changed

21 files changed

+445
-220
lines changed

src/packages/frontend/chat/input.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export default function ChatInput({
7070
() => redux.getStore("account").get_account_id(),
7171
[],
7272
);
73+
7374
const [input, setInput] = useState<string>(() => {
7475
const dbInput = syncdb
7576
?.get_one({
@@ -85,6 +86,7 @@ export default function ChatInput({
8586
const input = dbInput ?? propsInput;
8687
return input;
8788
});
89+
const currentInputRef = useRef<string>(input);
8890

8991
const isMountedRef = useIsMountedRef();
9092
const lastSavedRef = useRef<string | null>(null);
@@ -97,7 +99,6 @@ export default function ChatInput({
9799
// but definitely don't save (thus updating active) if
98100
// the input didn't really change, since we use active for
99101
// showing that a user is writing to other users.
100-
console.log("saveChat", { date });
101102
const input0 = syncdb
102103
.get_one({
103104
event: "draft",
@@ -123,9 +124,42 @@ export default function ChatInput({
123124
}
124125
},
125126
SAVE_DEBOUNCE_MS,
126-
{ leading: true },
127+
{
128+
leading: true,
129+
},
127130
);
128131

132+
useEffect(() => {
133+
return () => {
134+
// save before unmounting. This is very important since if a new reply comes in,
135+
// then the input component gets unmounted, then remounted BELOW the reply.
136+
// Note: it is still slightly annoying, due to loss of focus... however, data
137+
// loss is NOT ok, whereas loss of focus is.
138+
const input = currentInputRef.current;
139+
if (input == null || syncdb == null) {
140+
return;
141+
}
142+
if (
143+
syncdb.get_one({
144+
event: "draft",
145+
sender_id,
146+
date,
147+
}) == null
148+
) {
149+
return;
150+
}
151+
152+
syncdb.set({
153+
event: "draft",
154+
sender_id,
155+
input,
156+
date, // it's a primary key so can't use this to represent when user last edited this; use other date for editing past chats.
157+
active: Date.now(),
158+
});
159+
syncdb.commit();
160+
};
161+
}, []);
162+
129163
useEffect(() => {
130164
if (syncdb == null) return;
131165
const onSyncdbChange = () => {
@@ -138,6 +172,7 @@ export default function ChatInput({
138172
const input = x?.get("input");
139173
if (input != null && input !== lastSavedRef.current) {
140174
setInput(input);
175+
currentInputRef.current = input;
141176
lastSavedRef.current = null;
142177
}
143178
};
@@ -161,6 +196,7 @@ export default function ChatInput({
161196
enableMentions={true}
162197
submitMentionsRef={submitMentionsRef}
163198
onChange={(input) => {
199+
currentInputRef.current = input;
164200
/* BUG: in Markdown mode this stops getting
165201
called after you paste in an image. It works
166202
fine in Slate/Text mode. See

src/packages/frontend/editors/slate/elements/code-block/editable.tsx

Lines changed: 107 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { getHistory, isPreviousSiblingCodeBlock } from "./history";
1818
import InsertBar from "./insert-bar";
1919
import { useFileContext } from "@cocalc/frontend/lib/file-context";
2020
import { isEqual } from "lodash";
21+
import Mermaid from "./mermaid";
2122

2223
function Element({ attributes, children, element }: RenderElementProps) {
2324
if (element.type != "code_block") {
@@ -29,9 +30,7 @@ function Element({ attributes, children, element }: RenderElementProps) {
2930
const [info, setInfo] = useState<string>(element.info ?? "");
3031
const infoFocusedRef = useRef<boolean>(false);
3132
const [output, setOutput] = useState<null | ReactNode>(null);
32-
3333
const runRef = useRef<RunFunction | null>(null);
34-
3534
const setElement = useSetElement(editor, element);
3635
// textIndent: 0 is needed due to task lists -- see https://github.com/sagemathinc/cocalc/issues/6074
3736
const { change } = useChange();
@@ -64,108 +63,115 @@ function Element({ attributes, children, element }: RenderElementProps) {
6463
above={true}
6564
/>
6665
)}
67-
<SlateCodeMirror
68-
options={{ lineWrapping: true }}
69-
value={element.value}
70-
info={infoToMode(element.info, { value: element.value })}
71-
onChange={(value) => {
72-
setElement({ value });
73-
}}
74-
onFocus={async () => {
75-
await delay(1); // must be a little longer than the onBlur below.
76-
if (!isMountedRef.current) return;
77-
}}
78-
onBlur={async () => {
79-
await delay(0);
80-
if (!isMountedRef.current) return;
81-
}}
82-
onShiftEnter={() => {
83-
runRef.current?.();
84-
}}
85-
addonBefore={
86-
<div
87-
style={{
88-
borderBottom: "1px solid #ccc",
89-
padding: "3px",
90-
display: "flex",
91-
background: "#f8f8f8",
66+
<div style={{ display: "flex", flexDirection: "column" }}>
67+
<div style={{ flex: 1 }}>
68+
<SlateCodeMirror
69+
options={{ lineWrapping: true }}
70+
value={element.value}
71+
info={infoToMode(element.info, { value: element.value })}
72+
onChange={(value) => {
73+
setElement({ value });
9274
}}
93-
>
94-
<div style={{ flex: 1 }}></div>
95-
{element.fence && (
96-
<Input
97-
size="small"
98-
onKeyDown={(e) => {
99-
if (e.keyCode == 13 && e.shiftKey) {
100-
runRef.current?.();
101-
} else if (e.keyCode == 40) {
102-
// down arrow and 38 is up. TODO
103-
}
104-
}}
75+
onFocus={async () => {
76+
await delay(1); // must be a little longer than the onBlur below.
77+
if (!isMountedRef.current) return;
78+
}}
79+
onBlur={async () => {
80+
await delay(0);
81+
if (!isMountedRef.current) return;
82+
}}
83+
onShiftEnter={() => {
84+
runRef.current?.();
85+
}}
86+
addonBefore={
87+
<div
10588
style={{
106-
flex: 1,
107-
color: "#666",
108-
minWidth: "100px",
109-
maxWidth: "300px",
110-
margin: "0 5px",
89+
borderBottom: "1px solid #ccc",
90+
padding: "3px",
91+
display: "flex",
92+
background: "#f8f8f8",
11193
}}
112-
placeholder="Info string (py, r, jl, tex, md, etc.)..."
113-
value={info}
114-
onFocus={() => {
115-
infoFocusedRef.current = true;
116-
editor.setIgnoreSelection(true);
117-
}}
118-
onBlur={() => {
119-
infoFocusedRef.current = false;
120-
editor.setIgnoreSelection(false);
121-
}}
122-
onChange={(e) => {
123-
const info = e.target.value;
124-
setInfo(info);
125-
setElement({ info });
126-
}}
127-
/>
128-
)}
129-
{!disableMarkdownCodebar && (
130-
<ActionButtons
131-
auto
132-
size="small"
133-
input={element.value}
134-
history={history}
135-
setOutput={setOutput}
136-
output={output}
137-
info={info}
138-
runRef={runRef}
139-
setInfo={(info) => {
140-
setElement({ info });
141-
}}
142-
/>
143-
)}
144-
</div>
145-
}
146-
addonAfter={
147-
disableMarkdownCodebar || output == null ? null : (
148-
<div
149-
onMouseDown={() => {
150-
editor.setIgnoreSelection(true);
151-
}}
152-
onMouseUp={() => {
153-
// Re-enable slate listing for selection changes again in next render loop.
154-
setTimeout(() => {
155-
editor.setIgnoreSelection(false);
156-
}, 0);
157-
}}
158-
style={{
159-
borderTop: "1px dashed #ccc",
160-
background: "white",
161-
padding: "5px 0 5px 30px",
162-
}}
163-
>
164-
{output}
165-
</div>
166-
)
167-
}
168-
/>
94+
>
95+
<div style={{ flex: 1 }}></div>
96+
{element.fence && (
97+
<Input
98+
size="small"
99+
onKeyDown={(e) => {
100+
if (e.keyCode == 13 && e.shiftKey) {
101+
runRef.current?.();
102+
} else if (e.keyCode == 40) {
103+
// down arrow and 38 is up. TODO
104+
}
105+
}}
106+
style={{
107+
flex: 1,
108+
color: "#666",
109+
minWidth: "100px",
110+
maxWidth: "300px",
111+
margin: "0 5px",
112+
}}
113+
placeholder="Info string (py, r, jl, tex, md, etc.)..."
114+
value={info}
115+
onFocus={() => {
116+
infoFocusedRef.current = true;
117+
editor.setIgnoreSelection(true);
118+
}}
119+
onBlur={() => {
120+
infoFocusedRef.current = false;
121+
editor.setIgnoreSelection(false);
122+
}}
123+
onChange={(e) => {
124+
const info = e.target.value;
125+
setInfo(info);
126+
setElement({ info });
127+
}}
128+
/>
129+
)}
130+
{!disableMarkdownCodebar && (
131+
<ActionButtons
132+
auto
133+
size="small"
134+
input={element.value}
135+
history={history}
136+
setOutput={setOutput}
137+
output={output}
138+
info={info}
139+
runRef={runRef}
140+
setInfo={(info) => {
141+
setElement({ info });
142+
}}
143+
/>
144+
)}
145+
</div>
146+
}
147+
addonAfter={
148+
disableMarkdownCodebar || output == null ? null : (
149+
<div
150+
onMouseDown={() => {
151+
editor.setIgnoreSelection(true);
152+
}}
153+
onMouseUp={() => {
154+
// Re-enable slate listing for selection changes again in next render loop.
155+
setTimeout(() => {
156+
editor.setIgnoreSelection(false);
157+
}, 0);
158+
}}
159+
style={{
160+
borderTop: "1px dashed #ccc",
161+
background: "white",
162+
padding: "5px 0 5px 30px",
163+
}}
164+
>
165+
{output}
166+
</div>
167+
)
168+
}
169+
/>
170+
</div>
171+
{element.info == "mermaid" && (
172+
<Mermaid style={{ flex: 1 }} value={element.value} />
173+
)}
174+
</div>
169175
<InsertBar
170176
editor={editor}
171177
element={element}

src/packages/frontend/editors/slate/elements/code-block/index.tsx

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { DARK_GREY_BORDER } from "../../util";
1616
import { useFileContext } from "@cocalc/frontend/lib/file-context";
1717
import { Icon } from "@cocalc/frontend/components/icon";
1818
import { isEqual } from "lodash";
19-
import ShowError from "@cocalc/frontend/components/error";
19+
import Mermaid from "./mermaid";
2020

2121
export interface CodeBlock extends SlateElement {
2222
type: "code_block";
@@ -26,7 +26,7 @@ export interface CodeBlock extends SlateElement {
2626
info: string;
2727
}
2828

29-
const StaticElement: React.FC<RenderElementProps> = ({
29+
export const StaticElement: React.FC<RenderElementProps> = ({
3030
attributes,
3131
element,
3232
}) => {
@@ -48,7 +48,6 @@ const StaticElement: React.FC<RenderElementProps> = ({
4848

4949
const [newValue, setNewValue] = useState<string | null>(null);
5050
const runRef = useRef<any>(null);
51-
const mermaidRef = useRef<any>(null);
5251

5352
const [output, setOutput] = useState<null | ReactNode>(null);
5453

@@ -90,38 +89,11 @@ const StaticElement: React.FC<RenderElementProps> = ({
9089
}, 1);
9190
};
9291

93-
const isMermaid = temporaryInfo ?? element.info == "mermaid";
94-
const [mermaidError, setMermaidError] = useState<string>("");
95-
96-
useEffect(() => {
97-
const elt = mermaidRef.current;
98-
if (!isMermaid || !elt) {
99-
return;
100-
}
101-
(async () => {
102-
try {
103-
setMermaidError("");
104-
const mermaid = (await import("mermaid")).default;
105-
mermaid.initialize({
106-
startOnLoad: false,
107-
});
108-
elt.removeAttribute("data-processed");
109-
await mermaid.run({
110-
nodes: [elt],
111-
});
112-
} catch (err) {
113-
setMermaidError(err.str ?? `${err}`);
114-
}
115-
})();
116-
}, [isMermaid, newValue ?? element.value]);
117-
92+
const isMermaid = element.info == "mermaid";
11893
if (isMermaid) {
11994
return (
12095
<div {...attributes} style={{ marginBottom: "1em", textIndent: 0 }}>
121-
<pre className="mermaid" ref={mermaidRef}>
122-
{newValue ?? element.value}
123-
</pre>
124-
<ShowError error={mermaidError} setError={setMermaidError} />
96+
<Mermaid value={newValue ?? element.value} />
12597
</div>
12698
);
12799
}

0 commit comments

Comments
 (0)