Skip to content

Commit 0ad2e0e

Browse files
committed
Merge remote-tracking branch 'origin/master' into api-exec-await
2 parents 78e484d + 7a6d9da commit 0ad2e0e

File tree

23 files changed

+416
-401
lines changed

23 files changed

+416
-401
lines changed

src/packages/frontend/account/editor-settings/checkboxes.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const EDITOR_SETTINGS_CHECKBOXES: { [setting: string]: string | Rendered } = {
3030
build_on_save: "build LaTex/Rmd files whenever it is saved to disk",
3131
show_exec_warning: "warn that certain files are not directly executable",
3232
ask_jupyter_kernel: "ask which kernel to use for a new Jupyter Notebook",
33+
disable_jupyter_virtualization:
34+
"render entire notebook instead of just visible part (slower and not recommended)",
3335
jupyter_classic: (
3436
<span>
3537
<A href="https://github.com/sagemathinc/cocalc/issues/7706">

src/packages/frontend/chat/actions.ts

+16-8
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,21 @@ export class ChatActions extends Actions<ChatState> {
287287
this.syncdb.set(message);
288288
if (!reply_to) {
289289
this.delete_draft(0);
290+
// NOTE: we also clear search, since it's confusing to send a message and not
291+
// even see it (if it doesn't match search). We do NOT clear the hashtags though,
292+
// since by default the message you are sending has those tags.
293+
// Also, only do this clearing when not replying.
294+
// For replies search find full threads not individual messages.
295+
this.setState({
296+
input: "",
297+
search: "",
298+
});
299+
} else {
300+
// TODO: but until we fix search, do this:
301+
this.setState({
302+
search: "",
303+
});
290304
}
291-
// NOTE: we also clear search, since it's confusing to send a message and not
292-
// even see it (if it doesn't match search). We do NOT clear the hashtags though,
293-
// since by default the message you are sending has those tags.
294-
this.setState({
295-
input: "",
296-
search: "",
297-
});
298305
this.ensureDraftStartsWithHashtags(false);
299306

300307
if (this.store) {
@@ -558,11 +565,12 @@ export class ChatActions extends Actions<ChatState> {
558565
public scrollToBottom(index: number = -1) {
559566
if (this.syncdb == null) return;
560567
// this triggers scroll behavior in the chat-log component.
561-
this.setState({ scrollToBottom: null }); // noop, but necessary to trigger a change
568+
this.setState({ scrollToBottom: null }); // no-op, but necessary to trigger a change
562569
this.setState({ scrollToBottom: index });
563570
}
564571

565572
public set_uploading(is_uploading: boolean): void {
573+
console.log("set_uploading", is_uploading);
566574
this.setState({ is_uploading });
567575
}
568576

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

+8
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,14 @@ function isThread(messages: ChatMessages, message: ChatMessageTyped) {
286286
if (message.get("reply_to") != null) {
287287
return true;
288288
}
289+
// TODO/WARNING!!! This is a linear search
290+
// through all messages to decide if a message is the root of a thread.
291+
// This is VERY BAD and must to be redone at some point, since we call isThread
292+
// on all messages (in getSortedDates), making that algorithm O(n^2),
293+
// which is hideous as the number of messages scales. Instead one must
294+
// use a proper data structure (or even a cache) to track this once
295+
// and for all. It's more complicated but everything needs to be at
296+
// most O(n).
289297
return messages.some(
290298
(m) => m.get("reply_to") === message.get("date").toISOString(),
291299
);

src/packages/frontend/chat/chatroom.tsx

+22-7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { LLMCostEstimationChat } from "./llm-cost-estimation";
4343
import { SubmitMentionsFn } from "./types";
4444
import { INPUT_HEIGHT, markChatAsReadIfUnseen } from "./utils";
4545
import VideoChatButton from "./video/launch-button";
46+
import { FrameContext } from "@cocalc/frontend/frame-editors/frame-tree/frame-context";
4647

4748
const FILTER_RECENT_NONE = { value: 0, label: "All" } as const;
4849

@@ -75,9 +76,10 @@ const CHAT_LOG_STYLE: React.CSSProperties = {
7576
interface Props {
7677
project_id: string;
7778
path: string;
79+
is_visible?: boolean;
7880
}
7981

80-
export const ChatRoom: React.FC<Props> = ({ project_id, path }) => {
82+
export const ChatRoom: React.FC<Props> = ({ project_id, path, is_visible }) => {
8183
const actions: ChatActions = useActions(project_id, path);
8284

8385
const is_uploading = useRedux(["is_uploading"], project_id, path);
@@ -550,13 +552,26 @@ export const ChatRoom: React.FC<Props> = ({ project_id, path }) => {
550552
if (messages == null || input == null) {
551553
return <Loading theme={"medium"} />;
552554
}
555+
// remove frameContext once the chatroom is part of a frame tree.
556+
// we need this now, e.g., since some markdown editing components
557+
// for input assume in a frame tree, e.g., to fix
558+
// https://github.com/sagemathinc/cocalc/issues/7554
553559
return (
554-
<div
555-
onMouseMove={mark_as_read}
556-
onClick={mark_as_read}
557-
className="smc-vfill"
560+
<FrameContext.Provider
561+
value={{
562+
project_id,
563+
path,
564+
isVisible: !!is_visible,
565+
redux,
566+
} as any}
558567
>
559-
{render_body()}
560-
</div>
568+
<div
569+
onMouseMove={mark_as_read}
570+
onClick={mark_as_read}
571+
className="smc-vfill"
572+
>
573+
{render_body()}
574+
</div>
575+
</FrameContext.Provider>
561576
);
562577
};

src/packages/frontend/chat/input.tsx

+5-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import { CSSProperties, useEffect, useMemo, useRef, useState } from "react";
77
import { useDebouncedCallback } from "use-debounce";
8-
98
import {
109
CSS,
1110
redux,
@@ -61,6 +60,10 @@ export default function ChatInput({
6160
submitMentionsRef,
6261
syncdb,
6362
}: Props) {
63+
const onSendRef = useRef<Function>(on_send);
64+
useEffect(() => {
65+
onSendRef.current = on_send;
66+
}, [on_send]);
6467
const { project_id, path, actions } = useFrameContext();
6568
const fontSize = useRedux(["font_size"], project_id, path);
6669
if (font_size == null) {
@@ -213,16 +216,7 @@ export default function ChatInput({
213216
setInput(input);
214217
saveChat(input);
215218
}}
216-
onShiftEnter={(input) => {
217-
// no need to save on unmount, since we are saving
218-
// the correct state below.
219-
saveOnUnmountRef.current = false;
220-
lastSavedRef.current = input;
221-
setInput(input);
222-
saveChat(input);
223-
saveChat.cancel();
224-
on_send(input);
225-
}}
219+
onShiftEnter={on_send}
226220
height={height}
227221
placeholder={getPlaceholder(project_id, placeholder)}
228222
extraHelp={

src/packages/frontend/chat/message.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -628,10 +628,12 @@ export default function Message(props: Readonly<Props>) {
628628
);
629629
}
630630

631-
function sendReply() {
631+
function sendReply(reply?: string) {
632632
if (props.actions == null) return;
633633
setReplying(false);
634-
const reply = replyMentionsRef.current?.() ?? replyMessageRef.current;
634+
if (!reply) {
635+
reply = replyMentionsRef.current?.() ?? replyMessageRef.current;
636+
}
635637
props.actions.send_reply({ message: message.toJS(), reply });
636638
props.actions.scrollToBottom(props.index);
637639
}
@@ -680,7 +682,12 @@ export default function Message(props: Readonly<Props>) {
680682
>
681683
Cancel
682684
</Button>
683-
<Button onClick={sendReply} type="primary">
685+
<Button
686+
onClick={() => {
687+
sendReply();
688+
}}
689+
type="primary"
690+
>
684691
<Icon name="paper-plane" /> Send
685692
</Button>
686693
<LLMCostEstimationChat

src/packages/frontend/components/sortable-list.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ being virtualized with react-virtuoso.
1212

1313
import { CSSProperties, ReactNode, useState } from "react";
1414
import { Icon } from "./icon";
15-
1615
import { DndContext, DragOverlay } from "@dnd-kit/core";
1716
import {
1817
SortableContext,

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

+14-8
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export function MarkdownInput(props: Props) {
151151
unregisterEditor,
152152
value,
153153
} = props;
154-
const { actions } = useFrameContext();
154+
const { actions, isVisible } = useFrameContext();
155155
const cm = useRef<CodeMirror.Editor>();
156156
const textarea_ref = useRef<HTMLTextAreaElement>(null);
157157
const editor_settings = useRedux(["account", "editor_settings"]);
@@ -561,9 +561,7 @@ export function MarkdownInput(props: Props) {
561561
// console.log("upload_sending", file);
562562
if (current_uploads_ref.current == null) {
563563
current_uploads_ref.current = { [file.name]: true };
564-
if (onUploadStart != null) {
565-
onUploadStart();
566-
}
564+
onUploadStart?.();
567565
} else {
568566
current_uploads_ref.current[file.name] = true;
569567
}
@@ -582,16 +580,16 @@ export function MarkdownInput(props: Props) {
582580
if (path == null) {
583581
throw Error("path must be set if enableUploads is set.");
584582
}
583+
const filename = file.name ?? file.upload.filename;
585584

586585
if (current_uploads_ref.current != null) {
587-
delete current_uploads_ref.current[file.name];
586+
delete current_uploads_ref.current[filename];
588587
if (len(current_uploads_ref.current) == 0) {
589588
current_uploads_ref.current = null;
590-
if (onUploadEnd != null) {
591-
onUploadEnd();
592-
}
589+
onUploadEnd?.();
593590
}
594591
}
592+
595593
if (cm.current == null) return;
596594
const input = cm.current.getValue();
597595
const s0 = upload_temp_link(file);
@@ -803,6 +801,14 @@ export function MarkdownInput(props: Props) {
803801
}
804802
}
805803

804+
// make sure that mentions is closed if we switch to another tab.
805+
useEffect(() => {
806+
console.log("")
807+
if (mentions && !isVisible) {
808+
close_mentions();
809+
}
810+
}, [isVisible]);
811+
806812
function render_mentions_popup() {
807813
if (mentions == null || mentions_offset == null) return;
808814

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

+24-7
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@ export default function MultiMarkdownInput(props: Props) {
189189
path,
190190
} = useFrameContext();
191191

192+
// We use refs for shiftEnter and onChange to be absolutely
193+
// 100% certain that if either of these functions is changed,
194+
// then the new function is used, even if the components
195+
// implementing our markdown editor mess up somehow and hang on.
196+
const onShiftEnterRef = useRef<any>(onShiftEnter);
197+
useEffect(() => {
198+
onShiftEnterRef.current = onShiftEnter;
199+
}, [onShiftEnter]);
200+
const onChangeRef = useRef<any>(onChange);
201+
useEffect(() => {
202+
onChangeRef.current = onChange;
203+
}, [onChange]);
204+
192205
const editBar2 = useRef<JSX.Element | undefined>(undefined);
193206

194207
function getCache() {
@@ -380,7 +393,9 @@ export default function MultiMarkdownInput(props: Props) {
380393
divRef={editorDivRef}
381394
selectionRef={selectionRef}
382395
value={value}
383-
onChange={onChange}
396+
onChange={(value) => {
397+
onChangeRef.current?.(value);
398+
}}
384399
saveDebounceMs={saveDebounceMs}
385400
getValueRef={getValueRef}
386401
project_id={project_id}
@@ -389,7 +404,9 @@ export default function MultiMarkdownInput(props: Props) {
389404
onUploadStart={onUploadStart}
390405
onUploadEnd={onUploadEnd}
391406
enableMentions={enableMentions}
392-
onShiftEnter={onShiftEnter}
407+
onShiftEnter={(value) => {
408+
onShiftEnterRef.current?.(value);
409+
}}
393410
placeholder={placeholder ?? "Type markdown..."}
394411
fontSize={fontSize}
395412
cmOptions={cmOptions}
@@ -400,7 +417,7 @@ export default function MultiMarkdownInput(props: Props) {
400417
extraHelp={extraHelp}
401418
hideHelp={hideHelp}
402419
onBlur={(value) => {
403-
onChange?.(value);
420+
onChangeRef.current?.(value);
404421
if (!ignoreBlur.current) {
405422
onBlur?.();
406423
}
@@ -463,14 +480,14 @@ export default function MultiMarkdownInput(props: Props) {
463480
getValueRef={getValueRef}
464481
actions={{
465482
set_value: (value) => {
466-
onChange?.(value);
483+
onChangeRef.current?.(value);
467484
},
468485
shiftEnter: (value) => {
469-
onChange?.(value);
470-
onShiftEnter?.(value);
486+
onChangeRef.current?.(value);
487+
onShiftEnterRef.current?.(value);
471488
},
472489
altEnter: (value) => {
473-
onChange?.(value);
490+
onChangeRef.current?.(value);
474491
setMode("markdown");
475492
},
476493
set_cursor_locs: onCursors,

src/packages/frontend/editors/slate/editable-markdown.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export const EditableMarkdown: React.FC<Props> = React.memo((props: Props) => {
160160
unregisterEditor,
161161
value,
162162
} = props;
163-
const { project_id, path, desc } = useFrameContext();
163+
const { project_id, path, desc, isVisible } = useFrameContext();
164164
const isMountedRef = useIsMountedRef();
165165
const id = id0 ?? "";
166166
const actions = actions0 ?? {};
@@ -334,6 +334,7 @@ export const EditableMarkdown: React.FC<Props> = React.memo((props: Props) => {
334334
const mentionableUsers = useMentionableUsers();
335335

336336
const mentions = useMentions({
337+
isVisible,
337338
editor,
338339
insertMention: (editor, account_id) => {
339340
Transforms.insertNodes(editor, [

src/packages/frontend/editors/slate/slate-mentions/hook.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Editor, Range, Text, Transforms } from "slate";
1414
import { ReactEditor } from "../slate-react";
1515
import React from "react";
1616
import { useIsMountedRef } from "@cocalc/frontend/app-framework";
17-
import { useCallback, useMemo, useState } from "react";
17+
import { useCallback, useEffect, useMemo, useState } from "react";
1818
import {
1919
Complete,
2020
Item,
@@ -25,6 +25,7 @@ interface Options {
2525
editor: ReactEditor;
2626
insertMention: (Editor, string) => void;
2727
matchingUsers: (search: string) => (string | JSX.Element)[];
28+
isVisible?: boolean;
2829
}
2930

3031
interface MentionsControl {
@@ -34,6 +35,7 @@ interface MentionsControl {
3435
}
3536

3637
export const useMentions: (Options) => MentionsControl = ({
38+
isVisible,
3739
editor,
3840
insertMention,
3941
matchingUsers,
@@ -42,6 +44,12 @@ export const useMentions: (Options) => MentionsControl = ({
4244
const [search, setSearch] = useState("");
4345
const isMountedRef = useIsMountedRef();
4446

47+
useEffect(() => {
48+
if (!isVisible && target) {
49+
setTarget(undefined);
50+
}
51+
}, [isVisible]);
52+
4553
const items: Item[] = useMemo(() => {
4654
return matchingUsers(search);
4755
}, [search]);

0 commit comments

Comments
 (0)