jupyterlab-chat 0.23.0 → 0.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__tests__/utils.spec.d.ts +1 -0
- package/lib/__tests__/utils.spec.js +22 -0
- package/lib/model.d.ts +15 -0
- package/lib/model.js +25 -0
- package/lib/utils.d.ts +14 -0
- package/lib/utils.js +26 -0
- package/lib/widget.js +5 -0
- package/package.json +16 -16
- package/src/__tests__/utils.spec.ts +36 -0
- package/src/model.ts +26 -0
- package/src/utils.ts +33 -0
- package/src/widget.tsx +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { resolveChatRenamePath } from '../utils';
|
|
6
|
+
describe('resolveChatRenamePath', () => {
|
|
7
|
+
it('keeps a renamed chat in its original directory', () => {
|
|
8
|
+
expect(resolveChatRenamePath('chats/untitled.chat', 'test')).toBe('chats/test.chat');
|
|
9
|
+
});
|
|
10
|
+
it('ensures the .chat extension', () => {
|
|
11
|
+
expect(resolveChatRenamePath('chats/untitled.chat', 'test.chat')).toBe('chats/test.chat');
|
|
12
|
+
});
|
|
13
|
+
it('leaves root-level chats at the root', () => {
|
|
14
|
+
expect(resolveChatRenamePath('untitled.chat', 'test')).toBe('test.chat');
|
|
15
|
+
});
|
|
16
|
+
it('respects an explicit directory in the new name', () => {
|
|
17
|
+
expect(resolveChatRenamePath('chats/untitled.chat', 'archive/test')).toBe('archive/test.chat');
|
|
18
|
+
});
|
|
19
|
+
it('preserves nested directories', () => {
|
|
20
|
+
expect(resolveChatRenamePath('chats/2024/untitled.chat', 'test')).toBe('chats/2024/test.chat');
|
|
21
|
+
});
|
|
22
|
+
});
|
package/lib/model.d.ts
CHANGED
|
@@ -64,6 +64,21 @@ export declare class LabChatModel extends AbstractChatModel implements DocumentR
|
|
|
64
64
|
get readOnly(): boolean;
|
|
65
65
|
set readOnly(value: boolean);
|
|
66
66
|
set id(value: string | undefined);
|
|
67
|
+
/**
|
|
68
|
+
* Notify the model that its shared document has been synchronized with the
|
|
69
|
+
* server, and give it an ID if the document does not carry one yet.
|
|
70
|
+
*
|
|
71
|
+
* The model is only ready once it has an ID, so this must be called as soon as
|
|
72
|
+
* the document content is known - the ID cannot be created earlier, or a chat
|
|
73
|
+
* that already has one stored would end up with a conflicting ID.
|
|
74
|
+
*
|
|
75
|
+
* Callers should hook this to `IDocumentProvider.ready` (or the document
|
|
76
|
+
* context's `ready`) rather than to the document becoming clean: since
|
|
77
|
+
* jupyter-collaboration 5 a room stays dirty after being loaded, so the
|
|
78
|
+
* transition out of the dirty state may only happen on the first save, about a
|
|
79
|
+
* second later, leaving the chat unusable until then.
|
|
80
|
+
*/
|
|
81
|
+
markDocumentSynced(): void;
|
|
67
82
|
dispose(): void;
|
|
68
83
|
toString(): string;
|
|
69
84
|
fromString(data: string): void;
|
package/lib/model.js
CHANGED
|
@@ -226,6 +226,10 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
226
226
|
});
|
|
227
227
|
}
|
|
228
228
|
// Create a chat ID if not created when the document is not dirty.
|
|
229
|
+
//
|
|
230
|
+
// This is a fallback for chats whose document is not backed by a
|
|
231
|
+
// collaborative provider, and so never reports being synchronized. When
|
|
232
|
+
// there is one, `markDocumentSynced()` gets there first.
|
|
229
233
|
if (changes.stateChange && !this._sharedModel.id) {
|
|
230
234
|
if (changes.stateChange.some(change => change.name === 'dirty' && !change.newValue)) {
|
|
231
235
|
this._sharedModel.id = UUID.uuid4();
|
|
@@ -295,6 +299,27 @@ export class LabChatModel extends AbstractChatModel {
|
|
|
295
299
|
this.setReady();
|
|
296
300
|
}
|
|
297
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Notify the model that its shared document has been synchronized with the
|
|
304
|
+
* server, and give it an ID if the document does not carry one yet.
|
|
305
|
+
*
|
|
306
|
+
* The model is only ready once it has an ID, so this must be called as soon as
|
|
307
|
+
* the document content is known - the ID cannot be created earlier, or a chat
|
|
308
|
+
* that already has one stored would end up with a conflicting ID.
|
|
309
|
+
*
|
|
310
|
+
* Callers should hook this to `IDocumentProvider.ready` (or the document
|
|
311
|
+
* context's `ready`) rather than to the document becoming clean: since
|
|
312
|
+
* jupyter-collaboration 5 a room stays dirty after being loaded, so the
|
|
313
|
+
* transition out of the dirty state may only happen on the first save, about a
|
|
314
|
+
* second later, leaving the chat unusable until then.
|
|
315
|
+
*/
|
|
316
|
+
markDocumentSynced() {
|
|
317
|
+
if (!this._sharedModel.id) {
|
|
318
|
+
// Assigning the shared ID emits a metadata change, which sets the model
|
|
319
|
+
// ID - and therefore resolves `ready` - through `_onchange`.
|
|
320
|
+
this._sharedModel.id = UUID.uuid4();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
298
323
|
dispose() {
|
|
299
324
|
if (this.isDisposed) {
|
|
300
325
|
return;
|
package/lib/utils.d.ts
CHANGED
|
@@ -6,3 +6,17 @@
|
|
|
6
6
|
* @returns - the display name of the chat.
|
|
7
7
|
*/
|
|
8
8
|
export declare function getDisplayName(path: string, defaultDirectory?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the target path for a chat rename.
|
|
11
|
+
*
|
|
12
|
+
* When the new name is a bare basename (the usual case when renaming via the
|
|
13
|
+
* dialog), the chat must stay in the same directory as the original file.
|
|
14
|
+
* Otherwise `Contents.rename` treats the name as root-relative and moves the
|
|
15
|
+
* chat out of its directory (e.g. the configured `defaultDirectory`).
|
|
16
|
+
*
|
|
17
|
+
* @param oldPath - the current path of the chat file (e.g. `chats/untitled.chat`).
|
|
18
|
+
* @param newName - the new name or path entered by the user (e.g. `test`).
|
|
19
|
+
* @returns - the resolved path, with the `.chat` extension ensured and the
|
|
20
|
+
* original directory preserved when `newName` has no directory component.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveChatRenamePath(oldPath: string, newName: string): string;
|
package/lib/utils.js
CHANGED
|
@@ -22,3 +22,29 @@ export function getDisplayName(path, defaultDirectory) {
|
|
|
22
22
|
: path
|
|
23
23
|
: '/' + path).replace(pattern, '');
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the target path for a chat rename.
|
|
27
|
+
*
|
|
28
|
+
* When the new name is a bare basename (the usual case when renaming via the
|
|
29
|
+
* dialog), the chat must stay in the same directory as the original file.
|
|
30
|
+
* Otherwise `Contents.rename` treats the name as root-relative and moves the
|
|
31
|
+
* chat out of its directory (e.g. the configured `defaultDirectory`).
|
|
32
|
+
*
|
|
33
|
+
* @param oldPath - the current path of the chat file (e.g. `chats/untitled.chat`).
|
|
34
|
+
* @param newName - the new name or path entered by the user (e.g. `test`).
|
|
35
|
+
* @returns - the resolved path, with the `.chat` extension ensured and the
|
|
36
|
+
* original directory preserved when `newName` has no directory component.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveChatRenamePath(oldPath, newName) {
|
|
39
|
+
let newPath = newName;
|
|
40
|
+
// Ensure the `.chat` extension.
|
|
41
|
+
if (!newPath.endsWith(chatFileType.extensions[0])) {
|
|
42
|
+
newPath = `${newPath}${chatFileType.extensions[0]}`;
|
|
43
|
+
}
|
|
44
|
+
// Bare basename (no separator) -> keep it in the original file's directory.
|
|
45
|
+
// An explicit directory in `newName` is respected as-is.
|
|
46
|
+
if (!newPath.includes('/')) {
|
|
47
|
+
newPath = PathExt.join(PathExt.dirname(oldPath), newPath);
|
|
48
|
+
}
|
|
49
|
+
return newPath;
|
|
50
|
+
}
|
package/lib/widget.js
CHANGED
|
@@ -27,6 +27,11 @@ export class LabChatPanel extends DocumentWidget {
|
|
|
27
27
|
this.addClass(MAIN_PANEL_CLASS);
|
|
28
28
|
this.model.name = this.context.localPath;
|
|
29
29
|
this.model.unreadChanged.connect(this._unreadChanged);
|
|
30
|
+
// The context is ready once the document content has been loaded, which is
|
|
31
|
+
// when the chat can be given an ID and become usable.
|
|
32
|
+
this.context.ready
|
|
33
|
+
.then(() => this.model.markDocumentSynced())
|
|
34
|
+
.catch(e => console.error('The chat document failed to load', e));
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
32
37
|
* Dispose of the resources held by the widget.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab-chat",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.2",
|
|
4
4
|
"description": "The library to build a chat based on shared document",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -42,21 +42,21 @@
|
|
|
42
42
|
"watch:src": "tsc -w --sourceMap"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@jupyter/chat": "^0.23.
|
|
46
|
-
"@jupyter/collaborative-drive": "^4.4.0 || ^5.0.0
|
|
45
|
+
"@jupyter/chat": "^0.23.2",
|
|
46
|
+
"@jupyter/collaborative-drive": "^4.4.0 || ^5.0.0",
|
|
47
47
|
"@jupyter/ydoc": "^3.0.0 || ^4.0.0",
|
|
48
|
-
"@jupyterlab/application": "^4.
|
|
49
|
-
"@jupyterlab/apputils": "^4.
|
|
50
|
-
"@jupyterlab/coreutils": "^6.
|
|
51
|
-
"@jupyterlab/docmanager": "^4.
|
|
52
|
-
"@jupyterlab/docregistry": "^4.
|
|
53
|
-
"@jupyterlab/launcher": "^4.
|
|
54
|
-
"@jupyterlab/notebook": "^4.
|
|
55
|
-
"@jupyterlab/rendermime": "^4.
|
|
56
|
-
"@jupyterlab/services": "^7.
|
|
57
|
-
"@jupyterlab/settingregistry": "^4.
|
|
58
|
-
"@jupyterlab/translation": "^4.
|
|
59
|
-
"@jupyterlab/ui-components": "^4.
|
|
48
|
+
"@jupyterlab/application": "^4.5.0",
|
|
49
|
+
"@jupyterlab/apputils": "^4.6.0",
|
|
50
|
+
"@jupyterlab/coreutils": "^6.5.0",
|
|
51
|
+
"@jupyterlab/docmanager": "^4.5.0",
|
|
52
|
+
"@jupyterlab/docregistry": "^4.5.0",
|
|
53
|
+
"@jupyterlab/launcher": "^4.5.0",
|
|
54
|
+
"@jupyterlab/notebook": "^4.5.0",
|
|
55
|
+
"@jupyterlab/rendermime": "^4.5.0",
|
|
56
|
+
"@jupyterlab/services": "^7.5.0",
|
|
57
|
+
"@jupyterlab/settingregistry": "^4.5.0",
|
|
58
|
+
"@jupyterlab/translation": "^4.5.0",
|
|
59
|
+
"@jupyterlab/ui-components": "^4.5.0",
|
|
60
60
|
"@lumino/commands": "^2.3.3",
|
|
61
61
|
"@lumino/coreutils": "^2.2.2",
|
|
62
62
|
"@lumino/signaling": "^2.1.5",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"yjs": "^13.5.40"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@jupyterlab/testing": "^4.
|
|
69
|
+
"@jupyterlab/testing": "^4.5.0",
|
|
70
70
|
"@types/jest": "^29.2.0",
|
|
71
71
|
"@types/json-schema": "^7.0.11",
|
|
72
72
|
"@types/react": "^18.2.0",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { resolveChatRenamePath } from '../utils';
|
|
7
|
+
|
|
8
|
+
describe('resolveChatRenamePath', () => {
|
|
9
|
+
it('keeps a renamed chat in its original directory', () => {
|
|
10
|
+
expect(resolveChatRenamePath('chats/untitled.chat', 'test')).toBe(
|
|
11
|
+
'chats/test.chat'
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('ensures the .chat extension', () => {
|
|
16
|
+
expect(resolveChatRenamePath('chats/untitled.chat', 'test.chat')).toBe(
|
|
17
|
+
'chats/test.chat'
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('leaves root-level chats at the root', () => {
|
|
22
|
+
expect(resolveChatRenamePath('untitled.chat', 'test')).toBe('test.chat');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('respects an explicit directory in the new name', () => {
|
|
26
|
+
expect(resolveChatRenamePath('chats/untitled.chat', 'archive/test')).toBe(
|
|
27
|
+
'archive/test.chat'
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('preserves nested directories', () => {
|
|
32
|
+
expect(resolveChatRenamePath('chats/2024/untitled.chat', 'test')).toBe(
|
|
33
|
+
'chats/2024/test.chat'
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
});
|
package/src/model.ts
CHANGED
|
@@ -169,6 +169,28 @@ export class LabChatModel
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Notify the model that its shared document has been synchronized with the
|
|
174
|
+
* server, and give it an ID if the document does not carry one yet.
|
|
175
|
+
*
|
|
176
|
+
* The model is only ready once it has an ID, so this must be called as soon as
|
|
177
|
+
* the document content is known - the ID cannot be created earlier, or a chat
|
|
178
|
+
* that already has one stored would end up with a conflicting ID.
|
|
179
|
+
*
|
|
180
|
+
* Callers should hook this to `IDocumentProvider.ready` (or the document
|
|
181
|
+
* context's `ready`) rather than to the document becoming clean: since
|
|
182
|
+
* jupyter-collaboration 5 a room stays dirty after being loaded, so the
|
|
183
|
+
* transition out of the dirty state may only happen on the first save, about a
|
|
184
|
+
* second later, leaving the chat unusable until then.
|
|
185
|
+
*/
|
|
186
|
+
markDocumentSynced(): void {
|
|
187
|
+
if (!this._sharedModel.id) {
|
|
188
|
+
// Assigning the shared ID emits a metadata change, which sets the model
|
|
189
|
+
// ID - and therefore resolves `ready` - through `_onchange`.
|
|
190
|
+
this._sharedModel.id = UUID.uuid4();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
172
194
|
dispose(): void {
|
|
173
195
|
if (this.isDisposed) {
|
|
174
196
|
return;
|
|
@@ -563,6 +585,10 @@ export class LabChatModel
|
|
|
563
585
|
}
|
|
564
586
|
|
|
565
587
|
// Create a chat ID if not created when the document is not dirty.
|
|
588
|
+
//
|
|
589
|
+
// This is a fallback for chats whose document is not backed by a
|
|
590
|
+
// collaborative provider, and so never reports being synchronized. When
|
|
591
|
+
// there is one, `markDocumentSynced()` gets there first.
|
|
566
592
|
if (changes.stateChange && !this._sharedModel.id) {
|
|
567
593
|
if (
|
|
568
594
|
changes.stateChange.some(
|
package/src/utils.ts
CHANGED
|
@@ -31,3 +31,36 @@ export function getDisplayName(
|
|
|
31
31
|
: '/' + path
|
|
32
32
|
).replace(pattern, '');
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the target path for a chat rename.
|
|
37
|
+
*
|
|
38
|
+
* When the new name is a bare basename (the usual case when renaming via the
|
|
39
|
+
* dialog), the chat must stay in the same directory as the original file.
|
|
40
|
+
* Otherwise `Contents.rename` treats the name as root-relative and moves the
|
|
41
|
+
* chat out of its directory (e.g. the configured `defaultDirectory`).
|
|
42
|
+
*
|
|
43
|
+
* @param oldPath - the current path of the chat file (e.g. `chats/untitled.chat`).
|
|
44
|
+
* @param newName - the new name or path entered by the user (e.g. `test`).
|
|
45
|
+
* @returns - the resolved path, with the `.chat` extension ensured and the
|
|
46
|
+
* original directory preserved when `newName` has no directory component.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveChatRenamePath(
|
|
49
|
+
oldPath: string,
|
|
50
|
+
newName: string
|
|
51
|
+
): string {
|
|
52
|
+
let newPath = newName;
|
|
53
|
+
|
|
54
|
+
// Ensure the `.chat` extension.
|
|
55
|
+
if (!newPath.endsWith(chatFileType.extensions[0])) {
|
|
56
|
+
newPath = `${newPath}${chatFileType.extensions[0]}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Bare basename (no separator) -> keep it in the original file's directory.
|
|
60
|
+
// An explicit directory in `newName` is respected as-is.
|
|
61
|
+
if (!newPath.includes('/')) {
|
|
62
|
+
newPath = PathExt.join(PathExt.dirname(oldPath), newPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return newPath;
|
|
66
|
+
}
|
package/src/widget.tsx
CHANGED
|
@@ -23,6 +23,11 @@ export class LabChatPanel
|
|
|
23
23
|
this.addClass(MAIN_PANEL_CLASS);
|
|
24
24
|
this.model.name = this.context.localPath;
|
|
25
25
|
this.model.unreadChanged.connect(this._unreadChanged);
|
|
26
|
+
// The context is ready once the document content has been loaded, which is
|
|
27
|
+
// when the chat can be given an ID and become usable.
|
|
28
|
+
this.context.ready
|
|
29
|
+
.then(() => this.model.markDocumentSynced())
|
|
30
|
+
.catch(e => console.error('The chat document failed to load', e));
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|