Skip to content

Commit c3b0299

Browse files
committed
side chat: also support chat= fragment links
1 parent caa4782 commit c3b0299

File tree

11 files changed

+102
-76
lines changed

11 files changed

+102
-76
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default function SideChat({
4949
const search = desc?.get("data-search") ?? "";
5050
const selectedHashtags = desc?.get("data-selectedHashtags");
5151
const scrollToIndex = desc.get("data-scrollToIndex") ?? null;
52-
const scrollToDate = desc.get("data-scrollToIDate") ?? null;
52+
const scrollToDate = desc.get("data-scrollToDate") ?? null;
5353
const fragmentId = desc.get("data-fragmentId") ?? null;
5454
const addCollab: boolean = useRedux(["add_collab"], project_id, path);
5555
const is_uploading = useRedux(["is_uploading"], project_id, path);

src/packages/frontend/frame-editors/chat-editor/actions.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { handleSyncDBChange, initFromSyncDB } from "@cocalc/frontend/chat/sync";
2222
import { redux_name } from "@cocalc/frontend/app-framework";
2323
import { aux_file } from "@cocalc/util/misc";
2424
import type { FragmentId } from "@cocalc/frontend/misc/fragment-id";
25+
import { delay } from "awaiting";
2526

2627
const FRAME_TYPE = "chatroom";
2728

@@ -139,7 +140,7 @@ export class Actions extends CodeEditorActions<ChatEditorState> {
139140
this.getChatActions(frameId)?.scrollToIndex(0);
140141
};
141142

142-
gotoFragment = async (fragmentId: FragmentId) => {
143+
async gotoFragment(fragmentId: FragmentId) {
143144
const { chat } = fragmentId as any;
144145
if (!chat) {
145146
return;
@@ -150,18 +151,10 @@ export class Actions extends CodeEditorActions<ChatEditorState> {
150151
if (!frameId) {
151152
return;
152153
}
153-
const actions = this.getChatActions(frameId);
154-
if (actions == null) {
155-
return;
156-
}
157-
// if id is an iso string, just pass that in; otherwise, it could be a string
158-
// repr of ms since epoch and in that case we have to convert it to a number
159-
actions.scrollToDate(chat);
160-
// do it again since above scrollTo will be wrong if frame just opened, since
161-
// new chat frames typically scroll to bottom on initial render.
162-
// TODO: we could obviously do better here!
163-
for (const d of [5, 50, 500]) {
164-
setTimeout(() => actions.scrollToDate(chat), d);
154+
for (const d of [1, 10, 50, 500, 1000]) {
155+
const actions = this.getChatActions(frameId);
156+
actions?.scrollToDate(chat);
157+
await delay(d);
165158
}
166-
};
159+
}
167160
}

src/packages/frontend/frame-editors/code-editor/actions.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ import { SHELLS } from "./editor";
105105
import { test_line } from "./simulate_typing";
106106
import { misspelled_words } from "./spell-check";
107107
import { VideoChat } from "@cocalc/frontend/chat/video/video-chat";
108+
import type { FragmentId } from "@cocalc/frontend/misc/fragment-id";
109+
import {
110+
chat,
111+
getSideChatActions,
112+
} from "@cocalc/frontend/frame-editors/generic/chat";
108113

109114
interface gutterMarkerParams {
110115
line: number;
@@ -2957,7 +2962,7 @@ export class Actions<
29572962
type: string;
29582963
}): Promise<string> {
29592964
const state = (syncdoc ?? this._syncstring).get_state();
2960-
if (!(await this.wait_until_syncdoc_ready(syncdoc))) {
2965+
if (state != "ready" && !(await this.wait_until_syncdoc_ready(syncdoc))) {
29612966
return "";
29622967
}
29632968
const frameId = this.show_focused_frame_of_type(type);
@@ -3061,4 +3066,31 @@ export class Actions<
30613066
getVideoChat = () => {
30623067
return this.videoChat;
30633068
};
3069+
3070+
// NOTE: can't be an arrow function because gets called in derived classes
3071+
async gotoFragment(fragmentId: FragmentId) {
3072+
if (fragmentId == null) {
3073+
return;
3074+
}
3075+
3076+
if (fragmentId.line) {
3077+
this.programmatical_goto_line?.(fragmentId.line);
3078+
}
3079+
3080+
if (fragmentId.chat && !this.path.endsWith(".sage-chat")) {
3081+
// open side chat
3082+
this.redux
3083+
.getProjectActions(this.project_id)
3084+
.open_chat({ path: this.path });
3085+
this.show_focused_frame_of_type(chat.type);
3086+
for (const d of [1, 10, 50, 500, 1000]) {
3087+
const actions = getSideChatActions({
3088+
project_id: this.project_id,
3089+
path: this.path,
3090+
});
3091+
actions?.scrollToDate(fragmentId.chat);
3092+
await delay(d);
3093+
}
3094+
}
3095+
}
30643096
}

src/packages/frontend/frame-editors/generic/chat.tsx

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

66
import { useEffect, useState } from "react";
7-
87
import { redux } from "@cocalc/frontend/app-framework";
98
import type { ChatActions } from "@cocalc/frontend/chat/actions";
109
import { initChat } from "@cocalc/frontend/chat/register";
@@ -17,7 +16,6 @@ import { EditorDescription } from "../frame-tree/types";
1716
export function chatFile(path: string): string {
1817
return hidden_meta_file(path, "sage-chat");
1918
}
20-
2119
interface Props {
2220
font_size: number;
2321
desc;
@@ -72,13 +70,25 @@ export const chat: EditorDescription = {
7270
component: Chat,
7371
} as const;
7472

73+
export function getSideChatActions({
74+
project_id,
75+
path,
76+
}: {
77+
project_id: string;
78+
path: string;
79+
}): ChatActions | null {
80+
const actions = redux.getEditorActions(project_id, chatFile(path));
81+
if (actions == null) {
82+
return null;
83+
}
84+
return actions as ChatActions;
85+
}
86+
7587
// TODO: this is an ugly special case for now to make the title bar buttons work.
7688
// TODO: but wait -- those buttons are gone now, so maybe this can be deleted?!
77-
export function undo(project_id, path0) {
78-
const path = hidden_meta_file(path0, "sage-chat");
79-
(redux.getEditorActions(project_id, path) as ChatActions)?.undo();
89+
export function undo(project_id, path) {
90+
return getSideChatActions({ project_id, path })?.undo();
8091
}
81-
export function redo(project_id, path0) {
82-
const path = hidden_meta_file(path0, "sage-chat");
83-
(redux.getEditorActions(project_id, path) as ChatActions)?.redo();
92+
export function redo(project_id, path) {
93+
return getSideChatActions({ project_id, path })?.redo();
8494
}

src/packages/frontend/frame-editors/jupyter-editor/actions.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,17 @@ export class JupyterEditorActions extends BaseActions<JupyterEditorState> {
398398
this.close_recently_focused_frame_of_type("introspect");
399399
}
400400

401-
gotoFragment = async (fragmentId: FragmentId) => {
401+
async gotoFragment(fragmentId: FragmentId) {
402+
if (fragmentId.chat) {
403+
// deal with side chat in base class
404+
await super.gotoFragment(fragmentId);
405+
}
402406
const frameId = await this.waitUntilFrameReady({
403407
type: "jupyter_cell_notebook",
404408
syncdoc: this.jupyter_actions.syncdb,
405409
});
406410
if (!frameId) return;
407-
const { id, anchor } = fragmentId as any;
411+
const { id, anchor } = fragmentId;
408412

409413
const goto = (cellId: string) => {
410414
const actions = this.get_frame_actions(frameId);
@@ -463,7 +467,7 @@ export class JupyterEditorActions extends BaseActions<JupyterEditorState> {
463467
}
464468
return;
465469
}
466-
};
470+
}
467471

468472
languageModelGetText(
469473
frameId: string,

src/packages/frontend/frame-editors/whiteboard-editor/actions.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,11 @@ export class Actions<T extends State = State> extends BaseActions<T | State> {
13181318
this.zoom100(frameId);
13191319
}
13201320

1321-
gotoFragment = async (fragmentId: FragmentId) => {
1321+
async gotoFragment(fragmentId: FragmentId) {
1322+
if (fragmentId.chat) {
1323+
// deal with side chat in base class
1324+
await super.gotoFragment(fragmentId);
1325+
}
13221326
// console.log("gotoFragment ", fragmentId);
13231327
const frameId = await this.waitUntilFrameReady({
13241328
type: this.mainFrameType,
@@ -1341,7 +1345,7 @@ export class Actions<T extends State = State> extends BaseActions<T | State> {
13411345
this.scrollElementIntoView(id, frameId);
13421346
return;
13431347
}
1344-
};
1348+
}
13451349

13461350
public async show_table_of_contents(
13471351
_id: string | undefined = undefined,

src/packages/frontend/misc/fragment-id.ts

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,22 @@ The different types are inspired by https://en.wikipedia.org/wiki/URI_fragment
77
import { debounce } from "lodash";
88
import { IS_EMBEDDED } from "@cocalc/frontend/client/handle-target";
99

10-
interface Chat {
10+
export interface FragmentId {
1111
chat?: string; // fragment refers to ms since epoch of chat message
12+
anchor?: string;
13+
// a specific line in a document
14+
line?: number;
15+
// an id of an element or cell, e.g., in a whiteboard or Jupyter notebook.
16+
// These ids are assumed globally unique, so no page is specified.
17+
id?: string;
18+
// a specific page in a document, but where no line or element in that page is specified.
19+
page?: string;
1220
}
1321

14-
interface Anchor extends Chat {
15-
anchor: string;
22+
export function isPageFragment(x: any): x is FragmentId {
23+
return !!x?.page;
1624
}
1725

18-
// a specific line in a document
19-
interface Line extends Chat {
20-
line: number;
21-
}
22-
23-
// an id of an element or cell, e.g., in a whiteboard or Jupyter notebook.
24-
// These ids are assumed globally unique, so no page is specified.
25-
interface Id extends Chat {
26-
id: string;
27-
}
28-
29-
// a specific page in a document, but where no line or element in that page is specified.
30-
interface Page {
31-
page: string;
32-
}
33-
34-
export function isPageFragment(x: any): x is Page {
35-
return typeof x?.page === "string";
36-
}
37-
38-
export type FragmentId = Chat | Line | Id | Page | Anchor;
39-
4026
namespace Fragment {
4127
// set is debounced so you can call it as frequently as you want...
4228
export const set = debounce((fragmentId: FragmentId | undefined) => {
@@ -60,8 +46,8 @@ namespace Fragment {
6046
console.warn("encode -- invalid fragmentId object: ", fragmentId);
6147
throw Error(`attempt to encode invalid fragmentId -- "${fragmentId}"`);
6248
}
63-
if (fragmentId["anchor"] != null) {
64-
return fragmentId["anchor"];
49+
if (fragmentId.anchor != null) {
50+
return fragmentId.anchor;
6551
}
6652
const v: string[] = [];
6753
for (const key in fragmentId) {

src/packages/frontend/notifications/fragment.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6-
import Fragment, { isPageFragment } from "@cocalc/frontend/misc/fragment-id";
6+
import Fragment from "@cocalc/frontend/misc/fragment-id";
77
import { NotificationFilter, isNotificationFilter } from "./mentions/types";
88

99
export function getNotificationFilterFromFragment():
1010
| NotificationFilter
1111
| undefined {
12-
const fragID = Fragment.get();
13-
if (isPageFragment(fragID)) {
14-
const { page } = fragID;
15-
if (isNotificationFilter(page)) {
16-
return page;
17-
}
12+
const fragmentId = Fragment.get();
13+
if (fragmentId == null) {
14+
return;
15+
}
16+
const { page } = fragmentId;
17+
if (!page) {
18+
return;
19+
}
20+
if (isNotificationFilter(page)) {
21+
return page;
1822
}
1923
}

src/packages/frontend/notifications/mentions/mention-row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export function MentionRow(props: Props) {
9898
// file.txt#chat=true,id=092ab039
9999
redux.getProjectActions(project_id).open_file({
100100
path: path,
101-
chat: !fragmentId ? true : fragmentId["chat"],
101+
chat: !!fragmentId?.chat,
102102
fragmentId,
103103
});
104104

src/packages/frontend/project/open-file.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
// Implement the open_file actions for opening one single file in a project.
77

88
import { callback } from "awaiting";
9-
109
import { alert_message } from "@cocalc/frontend/alerts";
1110
import { redux } from "@cocalc/frontend/app-framework";
1211
import { local_storage } from "@cocalc/frontend/editor-local-storage";

0 commit comments

Comments
 (0)