@shaxpir/duiduidui-models 1.20.0 → 1.23.0
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/dist/models/Chat.d.ts +29 -9
- package/dist/models/Chat.js +81 -27
- package/dist/models/Model.d.ts +2 -2
- package/dist/models/Model.js +10 -7
- package/dist/models/SharedContent.d.ts +30 -0
- package/dist/models/SharedContent.js +44 -0
- package/dist/models/Workspace.d.ts +13 -1
- package/dist/models/Workspace.js +48 -9
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/repo/ShareSync.js +4 -0
- package/package.json +1 -1
package/dist/models/Chat.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
|
2
2
|
import { CompactDateTime, MultiTime } from '@shaxpir/shaxpir-common';
|
|
3
3
|
import { ShareSync } from '../repo';
|
|
4
4
|
import { ArrayView } from './ArrayView';
|
|
5
|
-
import {
|
|
5
|
+
import { ContentBody, ContentId, ContentMeta } from './Content';
|
|
6
|
+
import { SharedContent } from './SharedContent';
|
|
7
|
+
import { SocialUser } from './Social';
|
|
6
8
|
export interface TextBlock {
|
|
7
9
|
type: 'text';
|
|
8
10
|
text: string;
|
|
@@ -43,6 +45,7 @@ export interface ChatMessage {
|
|
|
43
45
|
role: 'user' | 'assistant';
|
|
44
46
|
content: ContentBlock[];
|
|
45
47
|
timestamp: CompactDateTime;
|
|
48
|
+
sender_id?: ContentId;
|
|
46
49
|
system_prompt_hash?: string;
|
|
47
50
|
model?: string;
|
|
48
51
|
usage?: ChatUsage;
|
|
@@ -60,32 +63,49 @@ export interface ChatScratchpad {
|
|
|
60
63
|
export interface ChatStub {
|
|
61
64
|
id: ContentId;
|
|
62
65
|
title?: string;
|
|
66
|
+
participants: SocialUser[];
|
|
63
67
|
last_message_preview?: string;
|
|
64
68
|
updated_at: CompactDateTime;
|
|
69
|
+
has_unread: {
|
|
70
|
+
[user_id: string]: boolean;
|
|
71
|
+
};
|
|
65
72
|
}
|
|
66
73
|
export interface ChatPayload {
|
|
67
74
|
title?: string;
|
|
75
|
+
participants: SocialUser[];
|
|
68
76
|
messages: ChatMessage[];
|
|
69
|
-
|
|
77
|
+
last_read: {
|
|
78
|
+
[user_id: string]: CompactDateTime;
|
|
79
|
+
};
|
|
70
80
|
}
|
|
71
81
|
export interface ChatBody extends ContentBody {
|
|
72
82
|
meta: ContentMeta;
|
|
73
83
|
payload: ChatPayload;
|
|
74
84
|
}
|
|
75
|
-
export declare class Chat extends
|
|
85
|
+
export declare class Chat extends SharedContent {
|
|
76
86
|
private _messagesView;
|
|
87
|
+
private _participantsView;
|
|
77
88
|
constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
|
|
78
89
|
static makeChatId(userId: ContentId, chatKey: string): ContentId;
|
|
79
|
-
static
|
|
80
|
-
|
|
90
|
+
static create(userId: ContentId, chatKey: string, participants: SocialUser[], createdAt?: MultiTime): Chat;
|
|
91
|
+
getAllSharedUserIds(): ContentId[];
|
|
92
|
+
/**
|
|
93
|
+
* Override to update manifests for all participants (via SharedContent)
|
|
94
|
+
* and update workspace chat stubs for each participant.
|
|
95
|
+
*/
|
|
96
|
+
modelUpdated(): Promise<void>;
|
|
81
97
|
get payload(): ChatPayload;
|
|
82
98
|
get title(): string | undefined;
|
|
99
|
+
get participants(): ArrayView<SocialUser>;
|
|
83
100
|
get messages(): ArrayView<ChatMessage>;
|
|
84
|
-
get
|
|
101
|
+
get lastRead(): {
|
|
102
|
+
[user_id: string]: CompactDateTime;
|
|
103
|
+
};
|
|
85
104
|
setTitle(title: string): void;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
addParticipant(user: SocialUser): void;
|
|
106
|
+
removeParticipant(userId: ContentId): void;
|
|
107
|
+
markRead(userId: ContentId): void;
|
|
108
|
+
hasUnread(userId: ContentId): boolean;
|
|
89
109
|
toStub(): ChatStub;
|
|
90
110
|
appendMessage(message: ChatMessage): void;
|
|
91
111
|
setBreakpoint(messageIndex: number, ttl: 'ephemeral' | 'standard'): void;
|
package/dist/models/Chat.js
CHANGED
|
@@ -4,30 +4,24 @@ exports.Chat = void 0;
|
|
|
4
4
|
const shaxpir_common_1 = require("@shaxpir/shaxpir-common");
|
|
5
5
|
const repo_1 = require("../repo");
|
|
6
6
|
const ArrayView_1 = require("./ArrayView");
|
|
7
|
-
const Content_1 = require("./Content");
|
|
8
7
|
const ContentKind_1 = require("./ContentKind");
|
|
9
8
|
const Operation_1 = require("./Operation");
|
|
9
|
+
const SharedContent_1 = require("./SharedContent");
|
|
10
10
|
// ---- Chat model ----
|
|
11
|
-
class Chat extends
|
|
11
|
+
class Chat extends SharedContent_1.SharedContent {
|
|
12
12
|
constructor(doc, shouldAcquire, shareSync) {
|
|
13
13
|
super(doc, shouldAcquire, shareSync);
|
|
14
14
|
this._messagesView = new ArrayView_1.ArrayView(this, ['payload', 'messages']);
|
|
15
|
+
this._participantsView = new ArrayView_1.ArrayView(this, ['payload', 'participants']);
|
|
15
16
|
}
|
|
16
17
|
// ---- ID generation ----
|
|
17
18
|
static makeChatId(userId, chatKey) {
|
|
18
19
|
return shaxpir_common_1.CachingHasher.makeMd5Base62Hash(`${ContentKind_1.ContentKind.CHAT}-${userId}-${chatKey}`);
|
|
19
20
|
}
|
|
20
21
|
// ---- Factory ----
|
|
21
|
-
static
|
|
22
|
-
return {
|
|
23
|
-
messages: [],
|
|
24
|
-
scratchpad: {},
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
static create(userId, chatKey, payload, createdAt) {
|
|
22
|
+
static create(userId, chatKey, participants, createdAt) {
|
|
28
23
|
const now = shaxpir_common_1.ClockService.getClock().now();
|
|
29
24
|
createdAt ??= now;
|
|
30
|
-
payload ??= Chat.createDefaultPayload();
|
|
31
25
|
const chatId = Chat.makeChatId(userId, chatKey);
|
|
32
26
|
return repo_1.ShareSyncFactory.get().createContent({
|
|
33
27
|
meta: {
|
|
@@ -38,9 +32,45 @@ class Chat extends Content_1.Content {
|
|
|
38
32
|
created_at: createdAt,
|
|
39
33
|
updated_at: createdAt
|
|
40
34
|
},
|
|
41
|
-
payload
|
|
35
|
+
payload: {
|
|
36
|
+
participants,
|
|
37
|
+
messages: [],
|
|
38
|
+
last_read: {},
|
|
39
|
+
}
|
|
42
40
|
});
|
|
43
41
|
}
|
|
42
|
+
// ---- SharedContent implementation ----
|
|
43
|
+
getAllSharedUserIds() {
|
|
44
|
+
return this._participantsView.values.map(p => p.user_id);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Override to update manifests for all participants (via SharedContent)
|
|
48
|
+
* and update workspace chat stubs for each participant.
|
|
49
|
+
*/
|
|
50
|
+
async modelUpdated() {
|
|
51
|
+
this.checkDisposed('Chat.modelUpdated');
|
|
52
|
+
// Update manifests for all participants
|
|
53
|
+
await super.modelUpdated();
|
|
54
|
+
// Update workspace stubs for all participants
|
|
55
|
+
const stub = this.toStub();
|
|
56
|
+
const userIds = this.getAllSharedUserIds();
|
|
57
|
+
for (const userId of userIds) {
|
|
58
|
+
try {
|
|
59
|
+
const workspaceId = shaxpir_common_1.CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind_1.ContentKind.WORKSPACE);
|
|
60
|
+
const workspace = this.shareSync.load(ContentKind_1.ContentKind.WORKSPACE, workspaceId);
|
|
61
|
+
if (workspace) {
|
|
62
|
+
await workspace.acquire();
|
|
63
|
+
if (workspace.exists()) {
|
|
64
|
+
workspace.upsertChatStub(stub);
|
|
65
|
+
}
|
|
66
|
+
workspace.release();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Workspace may not exist yet for this user
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
44
74
|
// ---- Getters ----
|
|
45
75
|
get payload() {
|
|
46
76
|
return this.doc.data.payload;
|
|
@@ -49,13 +79,17 @@ class Chat extends Content_1.Content {
|
|
|
49
79
|
this.checkDisposed('Chat.title');
|
|
50
80
|
return this.payload.title;
|
|
51
81
|
}
|
|
82
|
+
get participants() {
|
|
83
|
+
this.checkDisposed('Chat.participants');
|
|
84
|
+
return this._participantsView;
|
|
85
|
+
}
|
|
52
86
|
get messages() {
|
|
53
87
|
this.checkDisposed('Chat.messages');
|
|
54
88
|
return this._messagesView;
|
|
55
89
|
}
|
|
56
|
-
get
|
|
57
|
-
this.checkDisposed('Chat.
|
|
58
|
-
return this.payload.
|
|
90
|
+
get lastRead() {
|
|
91
|
+
this.checkDisposed('Chat.lastRead');
|
|
92
|
+
return this.payload.last_read;
|
|
59
93
|
}
|
|
60
94
|
// ---- Setters ----
|
|
61
95
|
setTitle(title) {
|
|
@@ -64,24 +98,37 @@ class Chat extends Content_1.Content {
|
|
|
64
98
|
batch.setPathValue(['payload', 'title'], title);
|
|
65
99
|
batch.commit();
|
|
66
100
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
101
|
+
// ---- Participant operations ----
|
|
102
|
+
addParticipant(user) {
|
|
103
|
+
this.checkDisposed('Chat.addParticipant');
|
|
104
|
+
const exists = this._participantsView.firstIndexWhere(p => p.user_id === user.user_id) >= 0;
|
|
105
|
+
if (!exists) {
|
|
106
|
+
this._participantsView.push(user);
|
|
107
|
+
}
|
|
72
108
|
}
|
|
73
|
-
|
|
74
|
-
this.checkDisposed('Chat.
|
|
75
|
-
|
|
76
|
-
batch.setPathValue(['payload', 'scratchpad', key], value);
|
|
77
|
-
batch.commit();
|
|
109
|
+
removeParticipant(userId) {
|
|
110
|
+
this.checkDisposed('Chat.removeParticipant');
|
|
111
|
+
this._participantsView.removeFirstWhere(p => p.user_id === userId);
|
|
78
112
|
}
|
|
79
|
-
|
|
80
|
-
|
|
113
|
+
// ---- Read tracking ----
|
|
114
|
+
markRead(userId) {
|
|
115
|
+
this.checkDisposed('Chat.markRead');
|
|
116
|
+
const now = shaxpir_common_1.ClockService.getClock().utc();
|
|
81
117
|
const batch = new Operation_1.BatchOperation(this);
|
|
82
|
-
batch.setPathValue(['payload', '
|
|
118
|
+
batch.setPathValue(['payload', 'last_read', userId], now);
|
|
83
119
|
batch.commit();
|
|
84
120
|
}
|
|
121
|
+
hasUnread(userId) {
|
|
122
|
+
this.checkDisposed('Chat.hasUnread');
|
|
123
|
+
const lastRead = this.payload.last_read[userId];
|
|
124
|
+
if (!lastRead)
|
|
125
|
+
return this._messagesView.length > 0;
|
|
126
|
+
const msgs = this._messagesView.values;
|
|
127
|
+
if (msgs.length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
const lastMessageTimestamp = msgs[msgs.length - 1].timestamp;
|
|
130
|
+
return shaxpir_common_1.Time.isDateTimeAfter(lastMessageTimestamp, lastRead);
|
|
131
|
+
}
|
|
85
132
|
// ---- Stub generation ----
|
|
86
133
|
toStub() {
|
|
87
134
|
this.checkDisposed('Chat.toStub');
|
|
@@ -94,11 +141,18 @@ class Chat extends Content_1.Content {
|
|
|
94
141
|
preview = textBlock.text.slice(0, 100);
|
|
95
142
|
}
|
|
96
143
|
}
|
|
144
|
+
const has_unread = {};
|
|
145
|
+
for (let i = 0; i < this._participantsView.length; i++) {
|
|
146
|
+
const participant = this._participantsView.get(i);
|
|
147
|
+
has_unread[participant.user_id] = this.hasUnread(participant.user_id);
|
|
148
|
+
}
|
|
97
149
|
return {
|
|
98
150
|
id: this.id,
|
|
99
151
|
title: this.title,
|
|
152
|
+
participants: this.participants.values,
|
|
100
153
|
last_message_preview: preview,
|
|
101
154
|
updated_at: this.updatedAt.utc_time,
|
|
155
|
+
has_unread,
|
|
102
156
|
};
|
|
103
157
|
}
|
|
104
158
|
// ---- Message operations ----
|
package/dist/models/Model.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
2
|
import { CompactDateTime, DispatchTopic } from '@shaxpir/shaxpir-common';
|
|
3
3
|
import { ShareSync } from '../repo';
|
|
4
|
-
import {
|
|
4
|
+
import { ContentBody, ContentId, ContentRef } from "./Content";
|
|
5
5
|
import { ContentKind } from './ContentKind';
|
|
6
6
|
import { ManifestBody } from './Manifest';
|
|
7
7
|
import { PermissionType } from './Permissions';
|
|
@@ -40,6 +40,6 @@ export declare abstract class Model {
|
|
|
40
40
|
ensureRecentData(minVersion: CompactDateTime): Promise<Model>;
|
|
41
41
|
doesUserHavePermission(userId: ContentId, type: PermissionType): boolean;
|
|
42
42
|
static doesUserHaveManifestPermission(data: ManifestBody, userId: ContentId, type: PermissionType): boolean;
|
|
43
|
-
static doesUserHaveContentPermission(kind: ContentKind,
|
|
43
|
+
static doesUserHaveContentPermission(kind: ContentKind, body: ContentBody, userId: ContentId, type: PermissionType): boolean;
|
|
44
44
|
private log;
|
|
45
45
|
}
|
package/dist/models/Model.js
CHANGED
|
@@ -269,24 +269,27 @@ class Model {
|
|
|
269
269
|
return Model.doesUserHaveManifestPermission(this.doc.data, userId, type);
|
|
270
270
|
}
|
|
271
271
|
else {
|
|
272
|
-
|
|
273
|
-
const meta = this.doc.data.meta;
|
|
274
|
-
return Model.doesUserHaveContentPermission(this.kind, meta, userId, type);
|
|
272
|
+
return Model.doesUserHaveContentPermission(this.kind, this.doc.data, userId, type);
|
|
275
273
|
}
|
|
276
274
|
}
|
|
277
275
|
static doesUserHaveManifestPermission(data, userId, type) {
|
|
278
276
|
return data.meta.owner === userId;
|
|
279
277
|
}
|
|
280
|
-
static doesUserHaveContentPermission(kind,
|
|
281
|
-
// Users are allowed to read any of their own content, but they are not allowed to write their User, Billing, or
|
|
282
|
-
if (meta.owner === userId) {
|
|
278
|
+
static doesUserHaveContentPermission(kind, body, userId, type) {
|
|
279
|
+
// Users are allowed to read any of their own content, but they are not allowed to write their User, Billing, Social, or Chat objects.
|
|
280
|
+
if (body.meta.owner === userId) {
|
|
283
281
|
if (type === Permissions_1.PermissionType.READ) {
|
|
284
282
|
return true;
|
|
285
283
|
}
|
|
286
284
|
else if (type === Permissions_1.PermissionType.WRITE) {
|
|
287
|
-
return kind !== ContentKind_1.ContentKind.USER && kind !== ContentKind_1.ContentKind.BILLING && kind !== ContentKind_1.ContentKind.SOCIAL;
|
|
285
|
+
return kind !== ContentKind_1.ContentKind.USER && kind !== ContentKind_1.ContentKind.BILLING && kind !== ContentKind_1.ContentKind.SOCIAL && kind !== ContentKind_1.ContentKind.CHAT;
|
|
288
286
|
}
|
|
289
287
|
}
|
|
288
|
+
// Shared content: participants can read but not write (server-controlled).
|
|
289
|
+
if (kind === ContentKind_1.ContentKind.CHAT && type === Permissions_1.PermissionType.READ) {
|
|
290
|
+
const chatPayload = body.payload;
|
|
291
|
+
return chatPayload.participants.some(p => p.user_id === userId);
|
|
292
|
+
}
|
|
290
293
|
// If all else fails, deny permission.
|
|
291
294
|
return false;
|
|
292
295
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
+
import { ContentId } from './Content';
|
|
3
|
+
import { Content } from './Content';
|
|
4
|
+
import { ShareSync } from '../repo';
|
|
5
|
+
/**
|
|
6
|
+
* Base class for content models that are shared between multiple users.
|
|
7
|
+
*
|
|
8
|
+
* Unlike Content (which updates only the owner's manifest), SharedContent
|
|
9
|
+
* updates the manifests of all users who have access to the model. This
|
|
10
|
+
* enables reactive sync for all participants.
|
|
11
|
+
*
|
|
12
|
+
* Subclasses must implement getAllSharedUserIds() to identify which users
|
|
13
|
+
* should receive manifest notifications when the model changes.
|
|
14
|
+
*
|
|
15
|
+
* SharedContent models are server-controlled: clients can READ them but
|
|
16
|
+
* not WRITE them directly. All mutations go through server endpoints.
|
|
17
|
+
*/
|
|
18
|
+
export declare abstract class SharedContent extends Content {
|
|
19
|
+
constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
|
|
20
|
+
/**
|
|
21
|
+
* Return the user IDs of all users who should receive manifest
|
|
22
|
+
* notifications when this model changes.
|
|
23
|
+
*/
|
|
24
|
+
abstract getAllSharedUserIds(): ContentId[];
|
|
25
|
+
/**
|
|
26
|
+
* Override modelUpdated to update manifests for ALL shared users,
|
|
27
|
+
* not just the owner.
|
|
28
|
+
*/
|
|
29
|
+
modelUpdated(): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SharedContent = void 0;
|
|
4
|
+
const ContentKind_1 = require("./ContentKind");
|
|
5
|
+
const Content_1 = require("./Content");
|
|
6
|
+
const Manifest_1 = require("./Manifest");
|
|
7
|
+
/**
|
|
8
|
+
* Base class for content models that are shared between multiple users.
|
|
9
|
+
*
|
|
10
|
+
* Unlike Content (which updates only the owner's manifest), SharedContent
|
|
11
|
+
* updates the manifests of all users who have access to the model. This
|
|
12
|
+
* enables reactive sync for all participants.
|
|
13
|
+
*
|
|
14
|
+
* Subclasses must implement getAllSharedUserIds() to identify which users
|
|
15
|
+
* should receive manifest notifications when the model changes.
|
|
16
|
+
*
|
|
17
|
+
* SharedContent models are server-controlled: clients can READ them but
|
|
18
|
+
* not WRITE them directly. All mutations go through server endpoints.
|
|
19
|
+
*/
|
|
20
|
+
class SharedContent extends Content_1.Content {
|
|
21
|
+
constructor(doc, shouldAcquire, shareSync) {
|
|
22
|
+
super(doc, shouldAcquire, shareSync);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Override modelUpdated to update manifests for ALL shared users,
|
|
26
|
+
* not just the owner.
|
|
27
|
+
*/
|
|
28
|
+
async modelUpdated() {
|
|
29
|
+
this.checkDisposed("SharedContent.modelUpdated");
|
|
30
|
+
this.acquire();
|
|
31
|
+
const userIds = this.getAllSharedUserIds();
|
|
32
|
+
for (const userId of userIds) {
|
|
33
|
+
const manifestId = Manifest_1.Manifest.makeManifestId(userId);
|
|
34
|
+
const manifest = this.shareSync.load(ContentKind_1.ContentKind.MANIFEST, manifestId);
|
|
35
|
+
if (manifest) {
|
|
36
|
+
await manifest.acquire();
|
|
37
|
+
manifest.update(this);
|
|
38
|
+
manifest.release();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
this.release();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.SharedContent = SharedContent;
|
|
@@ -2,7 +2,7 @@ import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
|
2
2
|
import { CompactDate, CompactDateTime, MultiTime } from "@shaxpir/shaxpir-common";
|
|
3
3
|
import { ShareSync } from '../repo';
|
|
4
4
|
import { ArrayView } from './ArrayView';
|
|
5
|
-
import { ChatStub } from './Chat';
|
|
5
|
+
import { ChatScratchpad, ChatStub } from './Chat';
|
|
6
6
|
import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
|
|
7
7
|
import { Conditions } from './Condition';
|
|
8
8
|
import { Session } from './Session';
|
|
@@ -22,6 +22,9 @@ export interface WorkspacePayload {
|
|
|
22
22
|
global_conditions: Conditions;
|
|
23
23
|
uploaded_avatars: ContentId[];
|
|
24
24
|
default_voice?: VoicePreference;
|
|
25
|
+
chat_scratchpads: {
|
|
26
|
+
[chatId: string]: ChatScratchpad;
|
|
27
|
+
};
|
|
25
28
|
}
|
|
26
29
|
export interface WorkspaceBody extends ContentBody {
|
|
27
30
|
meta: ContentMeta;
|
|
@@ -49,8 +52,17 @@ export declare class Workspace extends Content {
|
|
|
49
52
|
/**
|
|
50
53
|
* Update a chat stub in the chats array and move it to the top.
|
|
51
54
|
* If the stub doesn't exist yet, it is prepended.
|
|
55
|
+
*
|
|
56
|
+
* When the stub already exists but isn't at the top, we remove it
|
|
57
|
+
* entirely and insert a fresh copy at index 0 (cleaner for OT than
|
|
58
|
+
* editing fields and then moving). When it's already at the top,
|
|
59
|
+
* we update its fields in place to avoid unnecessary churn.
|
|
52
60
|
*/
|
|
53
61
|
upsertChatStub(stub: ChatStub): void;
|
|
62
|
+
getChatScratchpad(chatId: string): ChatScratchpad;
|
|
63
|
+
updateChatScratchpadText(chatId: string, text: string): void;
|
|
64
|
+
updateChatScratchpadField(chatId: string, key: string, value: any): void;
|
|
65
|
+
clearChatScratchpad(chatId: string): void;
|
|
54
66
|
get devices(): ArrayView<ContentId>;
|
|
55
67
|
get uploadedAvatars(): ArrayView<ContentId>;
|
|
56
68
|
acquireSessionForUtcTime(utcTime: CompactDateTime): Promise<Session | null>;
|
package/dist/models/Workspace.js
CHANGED
|
@@ -56,6 +56,9 @@ class Workspace extends Content_1.Content {
|
|
|
56
56
|
if (!Array.isArray(payload.uploaded_avatars)) {
|
|
57
57
|
throw new Error('Workspace.create: payload.uploaded_avatars must be an array');
|
|
58
58
|
}
|
|
59
|
+
if (!payload.chat_scratchpads || typeof payload.chat_scratchpads !== 'object' || Array.isArray(payload.chat_scratchpads)) {
|
|
60
|
+
throw new Error('Workspace.create: payload.chat_scratchpads must be an object');
|
|
61
|
+
}
|
|
59
62
|
}
|
|
60
63
|
static create(userId, payload) {
|
|
61
64
|
Workspace.validatePayload(payload);
|
|
@@ -88,25 +91,61 @@ class Workspace extends Content_1.Content {
|
|
|
88
91
|
/**
|
|
89
92
|
* Update a chat stub in the chats array and move it to the top.
|
|
90
93
|
* If the stub doesn't exist yet, it is prepended.
|
|
94
|
+
*
|
|
95
|
+
* When the stub already exists but isn't at the top, we remove it
|
|
96
|
+
* entirely and insert a fresh copy at index 0 (cleaner for OT than
|
|
97
|
+
* editing fields and then moving). When it's already at the top,
|
|
98
|
+
* we update its fields in place to avoid unnecessary churn.
|
|
91
99
|
*/
|
|
92
100
|
upsertChatStub(stub) {
|
|
93
101
|
this.checkDisposed("Workspace.upsertChatStub");
|
|
94
102
|
const index = this._chatsView.firstIndexWhere(s => s.id === stub.id);
|
|
95
|
-
if (index
|
|
96
|
-
//
|
|
97
|
-
this._chatsView.setObjectValueAtIndex(
|
|
98
|
-
this._chatsView.setObjectValueAtIndex(
|
|
99
|
-
this._chatsView.setObjectValueAtIndex(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
if (index === 0) {
|
|
104
|
+
// Already at the top — update fields in place
|
|
105
|
+
this._chatsView.setObjectValueAtIndex(0, 'title', stub.title);
|
|
106
|
+
this._chatsView.setObjectValueAtIndex(0, 'last_message_preview', stub.last_message_preview);
|
|
107
|
+
this._chatsView.setObjectValueAtIndex(0, 'updated_at', stub.updated_at);
|
|
108
|
+
}
|
|
109
|
+
else if (index > 0) {
|
|
110
|
+
// Exists but not at top — remove and reinsert at head
|
|
111
|
+
this._chatsView.removeAt(index);
|
|
112
|
+
this._chatsView.unshift(stub);
|
|
104
113
|
}
|
|
105
114
|
else {
|
|
106
115
|
// New chat — prepend
|
|
107
116
|
this._chatsView.unshift(stub);
|
|
108
117
|
}
|
|
109
118
|
}
|
|
119
|
+
// ---- Chat scratchpads (per-user draft state, keyed by chat ID) ----
|
|
120
|
+
// Use empty string '' as the key for the "new chat" scratchpad.
|
|
121
|
+
getChatScratchpad(chatId) {
|
|
122
|
+
this.checkDisposed("Workspace.getChatScratchpad");
|
|
123
|
+
return this.payload.chat_scratchpads?.[chatId] || {};
|
|
124
|
+
}
|
|
125
|
+
updateChatScratchpadText(chatId, text) {
|
|
126
|
+
this.checkDisposed("Workspace.updateChatScratchpadText");
|
|
127
|
+
const batch = new Operation_1.BatchOperation(this);
|
|
128
|
+
batch.editPathText(['payload', 'chat_scratchpads', chatId, 'text'], text);
|
|
129
|
+
batch.commit();
|
|
130
|
+
}
|
|
131
|
+
updateChatScratchpadField(chatId, key, value) {
|
|
132
|
+
this.checkDisposed("Workspace.updateChatScratchpadField");
|
|
133
|
+
const batch = new Operation_1.BatchOperation(this);
|
|
134
|
+
batch.setPathValue(['payload', 'chat_scratchpads', chatId, key], value);
|
|
135
|
+
batch.commit();
|
|
136
|
+
}
|
|
137
|
+
clearChatScratchpad(chatId) {
|
|
138
|
+
this.checkDisposed("Workspace.clearChatScratchpad");
|
|
139
|
+
const path = ['payload', 'chat_scratchpads', chatId];
|
|
140
|
+
const current = this.payload.chat_scratchpads?.[chatId];
|
|
141
|
+
if (!current)
|
|
142
|
+
return;
|
|
143
|
+
const batch = new Operation_1.BatchOperation(this);
|
|
144
|
+
for (const key of Object.keys(current)) {
|
|
145
|
+
batch.removeValueAtPath([...path, key]);
|
|
146
|
+
}
|
|
147
|
+
batch.commit();
|
|
148
|
+
}
|
|
110
149
|
// NOTE: As the user adds devices, we will always add new devices to the end of the array.
|
|
111
150
|
// So the most-recently-added device will always be at the end.
|
|
112
151
|
get devices() {
|
package/dist/models/index.d.ts
CHANGED
package/dist/models/index.js
CHANGED
|
@@ -43,6 +43,7 @@ __exportStar(require("./RecordType"), exports);
|
|
|
43
43
|
__exportStar(require("./Review"), exports);
|
|
44
44
|
__exportStar(require("./SearchState"), exports);
|
|
45
45
|
__exportStar(require("./Session"), exports);
|
|
46
|
+
__exportStar(require("./SharedContent"), exports);
|
|
46
47
|
__exportStar(require("./SkillLevel"), exports);
|
|
47
48
|
__exportStar(require("./Social"), exports);
|
|
48
49
|
__exportStar(require("./Streaks"), exports);
|
package/dist/repo/ShareSync.js
CHANGED
|
@@ -42,6 +42,7 @@ const shaxpir_common_1 = require("@shaxpir/shaxpir-common");
|
|
|
42
42
|
const reconnecting_websocket_1 = __importDefault(require("reconnecting-websocket"));
|
|
43
43
|
const models_1 = require("../models");
|
|
44
44
|
const Billing_1 = require("../models/Billing");
|
|
45
|
+
const Chat_1 = require("../models/Chat");
|
|
45
46
|
const Collection_1 = require("../models/Collection");
|
|
46
47
|
const Content_1 = require("../models/Content");
|
|
47
48
|
const ContentKind_1 = require("../models/ContentKind");
|
|
@@ -478,6 +479,9 @@ class ShareSync {
|
|
|
478
479
|
if (kind === ContentKind_1.ContentKind.BILLING) {
|
|
479
480
|
return new Billing_1.Billing(doc, shouldAcquire, shareSync);
|
|
480
481
|
}
|
|
482
|
+
else if (kind === ContentKind_1.ContentKind.CHAT) {
|
|
483
|
+
return new Chat_1.Chat(doc, shouldAcquire, shareSync);
|
|
484
|
+
}
|
|
481
485
|
else if (kind === ContentKind_1.ContentKind.COLLECTION) {
|
|
482
486
|
return new Collection_1.Collection(doc, shouldAcquire, shareSync);
|
|
483
487
|
}
|