@cloudflare/realtimekit-ui 1.1.0-staging.6 → 1.1.0-staging.8
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/browser.js +1 -1
- package/dist/cjs/rtk-avatar_24.cjs.entry.js +240 -213
- package/dist/cjs/rtk-chat-toggle.cjs.entry.js +1 -1
- package/dist/cjs/rtk-notifications.cjs.entry.js +4 -1
- package/dist/collection/components/rtk-chat/rtk-chat.js +16 -2
- package/dist/collection/components/rtk-chat-messages-ui-paginated/rtk-chat-messages-ui-paginated.js +36 -16
- package/dist/collection/components/rtk-chat-toggle/rtk-chat-toggle.js +1 -1
- package/dist/collection/components/rtk-notifications/rtk-notifications.js +4 -1
- package/dist/collection/components/rtk-paginated-list/rtk-paginated-list.js +213 -200
- package/dist/components/{p-85872241.js → p-7e90e964.js} +18 -4
- package/dist/components/{p-b6781e91.js → p-9213c3fc.js} +17 -17
- package/dist/components/{p-e7e2156a.js → p-ad8282dc.js} +208 -195
- package/dist/components/rtk-chat-messages-ui-paginated.js +1 -1
- package/dist/components/rtk-chat-search-results.js +1 -1
- package/dist/components/rtk-chat-toggle.js +1 -1
- package/dist/components/rtk-chat.js +1 -1
- package/dist/components/rtk-meeting.js +3 -3
- package/dist/components/rtk-notifications.js +4 -1
- package/dist/components/rtk-paginated-list.js +1 -1
- package/dist/docs/docs-components.json +29 -9
- package/dist/esm/loader.js +244 -214
- package/dist/esm/rtk-avatar_24.entry.js +240 -213
- package/dist/esm/rtk-chat-toggle.entry.js +1 -1
- package/dist/esm/rtk-notifications.entry.js +4 -1
- package/dist/realtimekit-ui/p-342b4926.entry.js +1 -0
- package/dist/realtimekit-ui/p-8f4f3160.entry.js +1 -0
- package/dist/realtimekit-ui/{p-421e4c6f.entry.js → p-ec5ed8a4.entry.js} +1 -1
- package/dist/realtimekit-ui/realtimekit-ui.esm.js +1 -1
- package/dist/types/components/rtk-chat/rtk-chat.d.ts +1 -0
- package/dist/types/components/rtk-chat-messages-ui-paginated/rtk-chat-messages-ui-paginated.d.ts +2 -0
- package/dist/types/components/rtk-paginated-list/rtk-paginated-list.d.ts +35 -48
- package/dist/types/components.d.ts +8 -3
- package/package.json +1 -1
- package/dist/realtimekit-ui/p-19587963.entry.js +0 -1
- package/dist/realtimekit-ui/p-a859d883.entry.js +0 -1
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* NOTE(ikabra): INFINITE SCROLL IMPLEMENTATION:
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Uses scrollend listener for 2way scrolling.
|
|
5
|
+
* Empty divs ($topRef, $bottomRef) act as scroll triggers to fetch new messages.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* UPWARD SCROLLING:
|
|
8
|
+
* - Fetch top anchor (element currently visible to the user near top)
|
|
9
|
+
* - Fetch older messages, push to end of 2D array
|
|
10
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* DOWNWARD SCROLLING:
|
|
13
|
+
* - Fetch bottom anchor (element currently visible to the user near bottom)
|
|
14
|
+
* - Fetch new page, insert at the start
|
|
15
|
+
* - Update timestamps & firstEmptyIndex, then rerender
|
|
16
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
13
17
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* if length exceeds pagesAllowed, we free up the pages and keep the first empty index in memory (firstEmptyIndex).
|
|
18
|
+
* ADDING NEW NODES:
|
|
19
|
+
* - If no pages exist, load old page
|
|
20
|
+
* - If on 1st page, append messages till page size is full and then load new page
|
|
18
21
|
*
|
|
19
|
-
*
|
|
20
|
-
* If
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* If we have exceeded our page allowance we delete old pages.
|
|
24
|
-
*
|
|
25
|
-
* In this case deleting pages is okay as we are not relying on the index of dom elements to detect page end.
|
|
26
|
-
*
|
|
27
|
-
* This also simplifies the code because when a user scrolls up we do not need to manage a lastEmptyIndex.
|
|
22
|
+
* DELETE NODE:
|
|
23
|
+
* - If deleting the only available node, reset to initial state
|
|
24
|
+
* - If page is empty, delete it
|
|
25
|
+
* - Update timestamp curors
|
|
28
26
|
*/
|
|
29
27
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
30
28
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -36,50 +34,34 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
36
34
|
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
37
35
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
38
36
|
};
|
|
39
|
-
import { Host, h
|
|
37
|
+
import { Host, h } from "@stencil/core";
|
|
40
38
|
import { defaultIconPack } from "../../lib/icons";
|
|
41
39
|
import { useLanguage } from "../../lib/lang";
|
|
42
40
|
import { SyncWithStore } from "../../utils/sync-with-store";
|
|
43
41
|
import { debounce } from "lodash-es";
|
|
44
42
|
export class RtkPaginatedList {
|
|
45
43
|
constructor() {
|
|
46
|
-
/**
|
|
47
|
-
* when scrolling up, we can't remove pages as intersectionObserver relies on
|
|
48
|
-
* the index of dom elements to stay stable.
|
|
49
|
-
* So, instead we free up the pages and keep the last empty index in memory.
|
|
50
|
-
*/
|
|
51
|
-
this.firstEmptyIndex = -1;
|
|
52
|
-
this.maxTS = 0;
|
|
53
44
|
// the length of pages will always be pageSize + 2
|
|
54
45
|
this.pages = [];
|
|
46
|
+
// Controls whether to keep auto-scrolling when a new page load.
|
|
47
|
+
this.shouldScrollToBottom = false;
|
|
48
|
+
// Shows "scroll to bottom" button when new nodes arrive and autoscroll is off.
|
|
49
|
+
this.showNewMessagesCTR = false;
|
|
55
50
|
/** label to show when empty */
|
|
56
51
|
this.emptyListLabel = null;
|
|
57
|
-
this.rerenderBoolean = false;
|
|
58
|
-
this.showEmptyListLabel = false;
|
|
59
52
|
/** Icon pack */
|
|
60
53
|
this.iconPack = defaultIconPack;
|
|
61
54
|
/** Language */
|
|
62
55
|
this.t = useLanguage();
|
|
56
|
+
this.rerenderBoolean = false;
|
|
57
|
+
this.showEmptyListLabel = false;
|
|
63
58
|
this.isLoading = false;
|
|
64
59
|
this.isLoadingTop = false;
|
|
65
60
|
this.isLoadingBottom = false;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* */
|
|
71
|
-
this.shouldScrollToBottom = false;
|
|
72
|
-
/** UI Indicator for the "scroll to bottom" button.
|
|
73
|
-
* Toggles on when a new node is added and autoscroll is disabled.
|
|
74
|
-
* Toggles off when all nodes are loaded */
|
|
75
|
-
this.showNewMessagesCTR = false;
|
|
76
|
-
this.observe = (el) => {
|
|
77
|
-
if (!el)
|
|
78
|
-
return;
|
|
79
|
-
this.intersectionObserver.observe(el);
|
|
80
|
-
};
|
|
81
|
-
this.isAtBottom = () => {
|
|
82
|
-
const rect = this.$bottomRef.getBoundingClientRect();
|
|
61
|
+
// Tells us if we need to scroll to a specific anchor after a rerender
|
|
62
|
+
this.pendingScrollAnchor = null;
|
|
63
|
+
this.isInView = (el) => {
|
|
64
|
+
const rect = el.getBoundingClientRect();
|
|
83
65
|
return rect.top >= 0 && rect.bottom <= window.innerHeight;
|
|
84
66
|
};
|
|
85
67
|
}
|
|
@@ -88,224 +70,255 @@ export class RtkPaginatedList {
|
|
|
88
70
|
* @param {DataNode} node - The data node to add to the beginning of the list
|
|
89
71
|
*/
|
|
90
72
|
async onNewNode(node) {
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
73
|
+
// if there are no pages, load the first page
|
|
74
|
+
if (this.pages.length < 1) {
|
|
75
|
+
this.oldTS = node.timeMs + 1;
|
|
76
|
+
this.loadPrevPage();
|
|
77
|
+
}
|
|
78
|
+
else if (this.maxTS === this.newTS) {
|
|
79
|
+
this.maxTS = node.timeMs;
|
|
80
|
+
// append messages to the page if page has not reached full capacity
|
|
81
|
+
if (this.pages[0].length < this.pageSize) {
|
|
82
|
+
this.pages[0].unshift(node);
|
|
83
|
+
this.newTS = node.timeMs;
|
|
84
|
+
this.rerender();
|
|
100
85
|
}
|
|
101
86
|
else {
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
this.pages[0].unshift(node);
|
|
105
|
-
this.newTS = node.timeMs;
|
|
106
|
-
this.rerender();
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
// if page is at full capacity, load next page
|
|
110
|
-
this.loadNextPage();
|
|
111
|
-
}
|
|
87
|
+
// if page is at full capacity, load next page
|
|
88
|
+
this.loadNextPage();
|
|
112
89
|
}
|
|
113
90
|
}
|
|
114
|
-
// If autoscroll is enabled,
|
|
91
|
+
// If autoscroll is enabled, scroll to the bottom
|
|
115
92
|
if (this.autoScroll) {
|
|
116
93
|
this.shouldScrollToBottom = true;
|
|
117
94
|
this.scrollToBottom();
|
|
118
95
|
}
|
|
119
|
-
else {
|
|
120
|
-
this.showNewMessagesCTR = true;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// this method is called recursively based on shouldScrollToBottom (see scrollEnd listener)
|
|
124
|
-
scrollToBottom() {
|
|
125
|
-
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
126
96
|
}
|
|
127
97
|
/**
|
|
128
98
|
* Deletes a node anywhere from the list
|
|
129
99
|
* @param {string} id - The id of the node to delete
|
|
130
100
|
* */
|
|
131
101
|
async onNodeDelete(id) {
|
|
132
|
-
|
|
133
|
-
|
|
102
|
+
var _a, _b;
|
|
103
|
+
let didDelete = false;
|
|
104
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
134
105
|
const index = this.pages[i].findIndex((node) => node.id === id);
|
|
135
|
-
// message
|
|
136
|
-
if (index
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// if newest page is empty, update first empty index
|
|
146
|
-
if (this.pages[i].length === 0)
|
|
147
|
-
this.firstEmptyIndex++;
|
|
148
|
-
// update timestamp, first empty index could be -1, so we need to cap it at 0
|
|
149
|
-
this.newTS = this.pages[Math.max(this.firstEmptyIndex, 0)][0].timeMs;
|
|
150
|
-
}
|
|
151
|
-
else if (i === this.firstEmptyIndex + this.pagesAllowed) {
|
|
152
|
-
// if oldest page is empty, remove it
|
|
153
|
-
if (this.pages[i].length === 0)
|
|
154
|
-
this.pages.pop();
|
|
155
|
-
// update timestamp
|
|
156
|
-
const lastPage = this.pages[this.firstEmptyIndex + this.pagesAllowed];
|
|
157
|
-
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
158
|
-
}
|
|
159
|
-
this.rerender();
|
|
160
|
-
}
|
|
106
|
+
// if message not found, move on
|
|
107
|
+
if (index === -1)
|
|
108
|
+
continue;
|
|
109
|
+
// delete message
|
|
110
|
+
this.pages[i].splice(index, 1);
|
|
111
|
+
// if page is empty, delete it
|
|
112
|
+
if (this.pages[i].length === 0)
|
|
113
|
+
this.pages.splice(i, 1);
|
|
114
|
+
didDelete = true;
|
|
115
|
+
break;
|
|
161
116
|
}
|
|
117
|
+
if (!didDelete)
|
|
118
|
+
return;
|
|
119
|
+
// update timestamps
|
|
120
|
+
const firstPage = this.pages[0];
|
|
121
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
122
|
+
this.newTS = (_a = firstPage === null || firstPage === void 0 ? void 0 : firstPage[0]) === null || _a === void 0 ? void 0 : _a.timeMs;
|
|
123
|
+
this.oldTS = (_b = lastPage === null || lastPage === void 0 ? void 0 : lastPage[lastPage.length - 1]) === null || _b === void 0 ? void 0 : _b.timeMs;
|
|
124
|
+
this.rerender();
|
|
162
125
|
}
|
|
163
126
|
/**
|
|
164
127
|
* Updates a new node anywhere in the list
|
|
165
|
-
* @param {string}
|
|
166
|
-
* @param {DataNode}
|
|
128
|
+
* @param {string} id - The id of the node to update
|
|
129
|
+
* @param {DataNode} node - The updated data node
|
|
167
130
|
* */
|
|
168
|
-
async onNodeUpdate(
|
|
169
|
-
|
|
170
|
-
|
|
131
|
+
async onNodeUpdate(id, node) {
|
|
132
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
133
|
+
const index = this.pages[i].findIndex((node) => node.id === id);
|
|
134
|
+
// if message not found, move on
|
|
135
|
+
if (index === -1)
|
|
136
|
+
continue;
|
|
137
|
+
// edit message
|
|
138
|
+
this.pages[i][index] = node;
|
|
139
|
+
this.rerender();
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
171
142
|
}
|
|
172
143
|
connectedCallback() {
|
|
173
144
|
this.rerender = debounce(this.rerender.bind(this), 50, { maxWait: 200 });
|
|
174
|
-
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
175
|
-
writeTask(async () => {
|
|
176
|
-
for (const entry of entries) {
|
|
177
|
-
if (entry.target.id === 'top-scroll' && entry.isIntersecting) {
|
|
178
|
-
this.isLoadingTop = true;
|
|
179
|
-
await this.loadPrevPage();
|
|
180
|
-
this.isLoadingTop = false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
145
|
}
|
|
186
146
|
componentDidLoad() {
|
|
187
|
-
|
|
147
|
+
// initial load
|
|
148
|
+
this.loadPrevPage();
|
|
188
149
|
if (this.$containerRef) {
|
|
189
150
|
this.$containerRef.onscrollend = async () => {
|
|
190
|
-
|
|
191
|
-
* Load new page if:
|
|
192
|
-
* if there are nodes to load at the bottom (maxTS > newTS)
|
|
193
|
-
* or if there are pages to fill at the bottom (firstEmptyIndex > -1)
|
|
194
|
-
*/
|
|
195
|
-
if (this.isAtBottom() && (this.maxTS > this.newTS || this.firstEmptyIndex > -1)) {
|
|
196
|
-
this.isLoadingBottom = true;
|
|
151
|
+
if (this.isInView(this.$bottomRef)) {
|
|
197
152
|
await this.loadNextPage();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
153
|
+
}
|
|
154
|
+
else if (this.isInView(this.$topRef)) {
|
|
155
|
+
this.showNewMessagesCTR = true;
|
|
156
|
+
await this.loadPrevPage();
|
|
201
157
|
}
|
|
202
158
|
};
|
|
203
159
|
}
|
|
204
160
|
}
|
|
161
|
+
componentDidRender() {
|
|
162
|
+
if (!this.pendingScrollAnchor)
|
|
163
|
+
return;
|
|
164
|
+
const anchor = this.pendingScrollAnchor;
|
|
165
|
+
this.pendingScrollAnchor = null;
|
|
166
|
+
this.restoreScrollToAnchor(anchor);
|
|
167
|
+
}
|
|
205
168
|
async loadPrevPage() {
|
|
206
169
|
if (this.isLoading)
|
|
207
170
|
return;
|
|
208
|
-
|
|
209
|
-
* NOTE(ikabra): this case also runs on initial load
|
|
210
|
-
* if scrolling up ->
|
|
211
|
-
* fetch older messages and push to the end of the array
|
|
212
|
-
* cleanup 1st non empty page from the array if length exceeds pagesAllowed
|
|
213
|
-
*/
|
|
171
|
+
const scrollAnchor = this.getScrollAnchor('top');
|
|
214
172
|
// if no old timestamp, it means we are at initial state
|
|
215
173
|
if (!this.oldTS)
|
|
216
174
|
this.oldTS = new Date().getTime();
|
|
217
175
|
// load data
|
|
218
176
|
this.isLoading = true;
|
|
177
|
+
this.isLoadingTop = true;
|
|
219
178
|
const data = await this.fetchData(this.oldTS - 1, this.pageSize, true);
|
|
220
179
|
this.isLoading = false;
|
|
180
|
+
this.isLoadingTop = false;
|
|
221
181
|
// no more old messages to show, we are at the top of the page
|
|
222
182
|
if (!data.length)
|
|
223
183
|
return;
|
|
224
184
|
// add old data to the end of the array
|
|
225
185
|
this.pages.push(data);
|
|
226
186
|
// clear old pages when we reach the limit
|
|
227
|
-
if (this.pages.length > this.pagesAllowed)
|
|
228
|
-
this.pages
|
|
229
|
-
|
|
230
|
-
* find last non empty page in range (this.pages.length, this.firstEmptyIndex)
|
|
231
|
-
* we are doing this because any of the middle pages in the currently rendered pages
|
|
232
|
-
* could be empty as we allow deleting messages.
|
|
233
|
-
* This helps us set the first empty index correctly.
|
|
234
|
-
*/
|
|
235
|
-
for (let i = this.firstEmptyIndex + 1; i < this.pages.length; i++) {
|
|
236
|
-
if (this.pages[i].length > 0)
|
|
237
|
-
break;
|
|
238
|
-
this.firstEmptyIndex = i;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
// update the old timestamp
|
|
187
|
+
if (this.pages.length > this.pagesAllowed)
|
|
188
|
+
this.pages.shift();
|
|
189
|
+
// update timestamps
|
|
242
190
|
const lastPage = this.pages[this.pages.length - 1];
|
|
243
191
|
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
244
|
-
|
|
245
|
-
|
|
192
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
193
|
+
if (!this.maxTS)
|
|
194
|
+
this.maxTS = this.newTS;
|
|
246
195
|
this.rerender();
|
|
196
|
+
// fix scroll position
|
|
197
|
+
if (scrollAnchor)
|
|
198
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
247
199
|
}
|
|
248
200
|
async loadNextPage() {
|
|
249
201
|
if (this.isLoading)
|
|
250
202
|
return;
|
|
251
|
-
//
|
|
203
|
+
// Do nothing. New timestamp needs to be assigned by loadPrevPage method
|
|
252
204
|
if (!this.newTS) {
|
|
253
205
|
this.showNewMessagesCTR = false;
|
|
254
206
|
this.shouldScrollToBottom = false;
|
|
255
207
|
return;
|
|
256
208
|
}
|
|
257
|
-
//
|
|
209
|
+
// for autoscroll or scroll to bottom button
|
|
210
|
+
const maxAutoLoads = 200;
|
|
211
|
+
let loads = 0;
|
|
212
|
+
let prevNewTS = this.newTS;
|
|
258
213
|
this.isLoading = true;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.
|
|
264
|
-
this.
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
// when already at the bottom and loading messages in realtime
|
|
214
|
+
this.isLoadingBottom = true;
|
|
215
|
+
while (loads < maxAutoLoads) {
|
|
216
|
+
const scrollAnchor = this.getScrollAnchor('bottom');
|
|
217
|
+
const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
|
|
218
|
+
this.isLoading = false;
|
|
219
|
+
this.isLoadingBottom = false;
|
|
220
|
+
// no more new messages to load
|
|
221
|
+
if (!data.length) {
|
|
222
|
+
this.maxTS = this.newTS;
|
|
223
|
+
this.showNewMessagesCTR = false;
|
|
224
|
+
this.shouldScrollToBottom = false;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
// load new messages and append to the start
|
|
276
228
|
this.pages.unshift(data.reverse());
|
|
229
|
+
// remove pages if out of bounds
|
|
230
|
+
if (this.pages.length > this.pagesAllowed)
|
|
231
|
+
this.pages.pop();
|
|
232
|
+
// update timestamps
|
|
233
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
234
|
+
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
235
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
236
|
+
this.rerender();
|
|
237
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
238
|
+
if (!this.shouldScrollToBottom)
|
|
239
|
+
break;
|
|
240
|
+
// if should scroll to bottom then retrigger
|
|
241
|
+
await this.waitForNextFrame();
|
|
242
|
+
this.scrollToBottom();
|
|
243
|
+
await this.waitForNextFrame();
|
|
244
|
+
// if no new messages, break
|
|
245
|
+
if (this.newTS === prevNewTS)
|
|
246
|
+
break;
|
|
247
|
+
prevNewTS = this.newTS;
|
|
248
|
+
loads++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Find the element that is closest to the top/bottom of the container
|
|
252
|
+
getScrollAnchor(edge = 'top') {
|
|
253
|
+
if (!this.$containerRef)
|
|
254
|
+
return null;
|
|
255
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
256
|
+
const candidates = Array.from(this.$containerRef.querySelectorAll('[id]')).filter((el) => el.id !== 'top-scroll' && el.id !== 'bottom-scroll');
|
|
257
|
+
let best = null;
|
|
258
|
+
for (const el of candidates) {
|
|
259
|
+
const rect = el.getBoundingClientRect();
|
|
260
|
+
const isVisibleInContainer = rect.bottom > containerRect.top && rect.top < containerRect.bottom;
|
|
261
|
+
if (!isVisibleInContainer)
|
|
262
|
+
continue;
|
|
263
|
+
if (edge === 'top') {
|
|
264
|
+
const offsetTop = rect.top - containerRect.top;
|
|
265
|
+
if (best == null || (best.edge === 'top' && offsetTop < best.offsetTop)) {
|
|
266
|
+
best = { id: el.id, edge: 'top', offsetTop };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const offsetBottom = containerRect.bottom - rect.bottom;
|
|
271
|
+
if (best == null || (best.edge === 'bottom' && offsetBottom < best.offsetBottom)) {
|
|
272
|
+
best = { id: el.id, edge: 'bottom', offsetBottom };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
277
275
|
}
|
|
278
|
-
|
|
279
|
-
|
|
276
|
+
return best;
|
|
277
|
+
}
|
|
278
|
+
//instant scroll to anchor to make sure we are at the same position after a rerender
|
|
279
|
+
restoreScrollToAnchor(anchor) {
|
|
280
|
+
if (!this.$containerRef)
|
|
281
|
+
return;
|
|
282
|
+
// make element id safe to use inside a CSS selector
|
|
283
|
+
const escapeId = (id) => {
|
|
284
|
+
var _a;
|
|
285
|
+
const cssEscape = (_a = globalThis.CSS) === null || _a === void 0 ? void 0 : _a.escape;
|
|
286
|
+
return typeof cssEscape === 'function'
|
|
287
|
+
? cssEscape(id)
|
|
288
|
+
: id.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
|
|
289
|
+
};
|
|
290
|
+
const el = this.$containerRef.querySelector(`#${escapeId(anchor.id)}`);
|
|
291
|
+
if (!el)
|
|
292
|
+
return;
|
|
293
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
294
|
+
const rect = el.getBoundingClientRect();
|
|
295
|
+
if (anchor.edge === 'top') {
|
|
296
|
+
const newOffsetTop = rect.top - containerRect.top;
|
|
297
|
+
this.$containerRef.scrollTop += newOffsetTop - anchor.offsetTop;
|
|
280
298
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
for (let i = this.pages.length - 1; i > this.firstEmptyIndex; i--) {
|
|
285
|
-
if (this.pages[i].length > 0)
|
|
286
|
-
break;
|
|
287
|
-
// if page is empty, remove it
|
|
288
|
-
this.pages.pop();
|
|
299
|
+
else {
|
|
300
|
+
const newOffsetBottom = containerRect.bottom - rect.bottom;
|
|
301
|
+
this.$containerRef.scrollTop += anchor.offsetBottom - newOffsetBottom;
|
|
289
302
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
303
|
+
}
|
|
304
|
+
// this method is called recursively based on shouldScrollToBottom (see loadNextPage)
|
|
305
|
+
scrollToBottom() {
|
|
306
|
+
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
307
|
+
}
|
|
308
|
+
waitForNextFrame() {
|
|
309
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
310
|
+
}
|
|
311
|
+
rerender() {
|
|
312
|
+
this.rerenderBoolean = !this.rerenderBoolean;
|
|
299
313
|
}
|
|
300
314
|
render() {
|
|
301
315
|
/**
|
|
302
|
-
* div.container is flex=column-
|
|
303
|
-
* which is why div#bottom-scroll comes before div#top-scroll
|
|
316
|
+
* div.container is flex=column-reversewhich is why div#bottom-scroll comes before div#top-scroll
|
|
304
317
|
*/
|
|
305
|
-
return (h(Host, { key: '
|
|
318
|
+
return (h(Host, { key: '5f036ac16ace127734d5ee172d537c64baeab415' }, h("div", { key: 'b6d8cf3019a72350f7a3a5b4d020b6ab39793f53', class: "scrollbar container", part: "container", ref: (el) => (this.$containerRef = el) }, h("div", { key: '5c63462ffd995a3e266652bba4e3377636c5f9ca', class: { 'show-new-messages-ctr': true, active: this.showNewMessagesCTR } }, h("rtk-button", { key: 'c1fc4f2759d5be662047245b0dae3eb6f65a9b50', class: "show-new-messages", kind: "icon", variant: "secondary", part: "show-new-messages", onClick: () => {
|
|
306
319
|
this.shouldScrollToBottom = true;
|
|
307
320
|
this.scrollToBottom();
|
|
308
|
-
} }, h("rtk-icon", { key: '
|
|
321
|
+
} }, h("rtk-icon", { key: '96b19395a2ca8e87ca5004f675cf79f8d58f036c', icon: this.iconPack.chevron_down }))), h("div", { key: '84789a3d0fa4645be711a87cda1e109e4f7d0db2', class: "smallest-dom-element", id: "bottom-scroll", ref: (el) => (this.$bottomRef = el) }), this.isLoadingBottom && this.pages.length > 0 && h("rtk-spinner", { key: 'd17f28cc01695220ed6e705d528e8f555b77e8ea', size: "sm" }), this.isLoading && this.pages.length < 1 && h("rtk-spinner", { key: '4ee308d335cdc1f86e2bfdcc20611f50a51ef816', size: "lg" }), !this.isLoading && this.pages.flat().length === 0 ? (h("div", { class: "empty-list" }, this.t('list.empty'))) : (h("div", { class: "page-wrapper" }, this.pages.map((page, pageIndex) => (h("div", { class: "page", "data-page-index": pageIndex }, this.createNodes([...page].reverse())))))), this.isLoadingTop && this.pages.length > 0 && h("rtk-spinner", { key: 'c07c4dd4c4ed0adf01d2fc224b7196a0f86243fd', size: "sm" }), h("div", { key: '52161ac30062c2262f1cdbcded50f2716f6ed20e', class: "smallest-dom-element", id: "top-scroll", ref: (el) => (this.$topRef = el) }))));
|
|
309
322
|
}
|
|
310
323
|
static get is() { return "rtk-paginated-list"; }
|
|
311
324
|
static get encapsulation() { return "shadow"; }
|
|
@@ -578,13 +591,13 @@ export class RtkPaginatedList {
|
|
|
578
591
|
},
|
|
579
592
|
"onNodeUpdate": {
|
|
580
593
|
"complexType": {
|
|
581
|
-
"signature": "(
|
|
594
|
+
"signature": "(id: string, node: DataNode) => Promise<void>",
|
|
582
595
|
"parameters": [{
|
|
583
|
-
"name": "
|
|
596
|
+
"name": "id",
|
|
584
597
|
"type": "string",
|
|
585
598
|
"docs": "- The id of the node to update"
|
|
586
599
|
}, {
|
|
587
|
-
"name": "
|
|
600
|
+
"name": "node",
|
|
588
601
|
"type": "DataNode",
|
|
589
602
|
"docs": "- The updated data node"
|
|
590
603
|
}],
|
|
@@ -605,10 +618,10 @@ export class RtkPaginatedList {
|
|
|
605
618
|
"text": "Updates a new node anywhere in the list",
|
|
606
619
|
"tags": [{
|
|
607
620
|
"name": "param",
|
|
608
|
-
"text": "
|
|
621
|
+
"text": "id - The id of the node to update"
|
|
609
622
|
}, {
|
|
610
623
|
"name": "param",
|
|
611
|
-
"text": "
|
|
624
|
+
"text": "node - The updated data node"
|
|
612
625
|
}]
|
|
613
626
|
}
|
|
614
627
|
}
|
|
@@ -7,7 +7,7 @@ import { d as defineCustomElement$m } from './p-241a8245.js';
|
|
|
7
7
|
import { d as defineCustomElement$l } from './p-1391bef0.js';
|
|
8
8
|
import { d as defineCustomElement$k } from './p-a73665b4.js';
|
|
9
9
|
import { d as defineCustomElement$j } from './p-28170a8d.js';
|
|
10
|
-
import { d as defineCustomElement$i } from './p-
|
|
10
|
+
import { d as defineCustomElement$i } from './p-9213c3fc.js';
|
|
11
11
|
import { d as defineCustomElement$h } from './p-1f5a4682.js';
|
|
12
12
|
import { d as defineCustomElement$g } from './p-598dc3f2.js';
|
|
13
13
|
import { d as defineCustomElement$f } from './p-0e5cc539.js';
|
|
@@ -20,7 +20,7 @@ import { d as defineCustomElement$9 } from './p-2447a26f.js';
|
|
|
20
20
|
import { d as defineCustomElement$8 } from './p-819cb785.js';
|
|
21
21
|
import { d as defineCustomElement$7 } from './p-7148ec6a.js';
|
|
22
22
|
import { d as defineCustomElement$6 } from './p-0f2de0f8.js';
|
|
23
|
-
import { d as defineCustomElement$5 } from './p-
|
|
23
|
+
import { d as defineCustomElement$5 } from './p-ad8282dc.js';
|
|
24
24
|
import { d as defineCustomElement$4 } from './p-4ebf9684.js';
|
|
25
25
|
import { d as defineCustomElement$3 } from './p-4902c5cf.js';
|
|
26
26
|
import { d as defineCustomElement$2 } from './p-6739a399.js';
|
|
@@ -306,6 +306,13 @@ const RtkChat = /*@__PURE__*/ proxyCustomElement(class RtkChat extends H {
|
|
|
306
306
|
const message = event.detail;
|
|
307
307
|
this.meeting.chat.deleteMessage(message.id);
|
|
308
308
|
};
|
|
309
|
+
this.onMessageEdit = (event) => {
|
|
310
|
+
const message = event.detail;
|
|
311
|
+
if (message.type !== 'text')
|
|
312
|
+
return;
|
|
313
|
+
this.replyMessage = null;
|
|
314
|
+
this.editingMessage = message;
|
|
315
|
+
};
|
|
309
316
|
this.getPrivateChatRecipients = () => {
|
|
310
317
|
const participants = this.getFilteredParticipants().map((participant) => {
|
|
311
318
|
const key = generateChatGroupKey([participant.userId, this.meeting.self.userId]);
|
|
@@ -490,14 +497,21 @@ const RtkChat = /*@__PURE__*/ proxyCustomElement(class RtkChat extends H {
|
|
|
490
497
|
const uiProps = { iconPack: this.iconPack, t: this.t, size: this.size };
|
|
491
498
|
const message = this.editingMessage ? this.editingMessage.message : '';
|
|
492
499
|
const quotedMessage = this.replyMessage ? this.replyMessage.message : '';
|
|
493
|
-
|
|
500
|
+
const draftStorageKey = this.selectedChannelId
|
|
501
|
+
? `rtk-chat-draft-${this.selectedChannelId}`
|
|
502
|
+
: 'rtk-chat-draft';
|
|
503
|
+
const editStorageKey = this.editingMessage
|
|
504
|
+
? `rtk-chat-edit-${(_a = this.selectedChannelId) !== null && _a !== void 0 ? _a : 'no-channel'}-${this.editingMessage.id}`
|
|
505
|
+
: 'rtk-chat-edit';
|
|
506
|
+
const storageKey = this.editingMessage ? editStorageKey : draftStorageKey;
|
|
507
|
+
return (h("rtk-chat-composer-view", Object.assign({ message: message, storageKey: storageKey, quotedMessage: quotedMessage, isEditing: !!this.editingMessage, canSendTextMessage: this.isTextMessagingAllowed(), canSendFiles: this.isFileMessagingAllowed(), disableEmojiPicker: this.overrides.disableEmojiPicker, maxLength: this.meeting.chat.maxTextLimit, rateLimits: this.meeting.chat.rateLimits, inputTextPlaceholder: this.t('chat.message_placeholder'), onNewMessage: this.onNewMessageHandler, onEditMessage: this.onEditMessageHandler, onEditCancel: this.onEditCancel, onQuotedMessageDismiss: this.onQuotedMessageDismiss }, uiProps), h("slot", { name: "chat-addon", slot: "chat-addon" })));
|
|
494
508
|
}
|
|
495
509
|
render() {
|
|
496
510
|
var _a;
|
|
497
511
|
if (!this.meeting) {
|
|
498
512
|
return null;
|
|
499
513
|
}
|
|
500
|
-
return (h(Host, null, h("div", { class: "chat-container" }, h("div", { class: "chat" }, this.isFileMessagingAllowed() && (h("div", { id: "dropzone", class: { active: this.dropzoneActivated }, part: "dropzone" }, h("rtk-icon", { icon: this.iconPack.attach }), h("p", null, this.t('chat.send_attachment')))), this.renderPinnedMessagesHeader(), this.isPrivateChatSupported() && (h("rtk-channel-selector-view", { channels: this.getPrivateChatRecipients(), selectedChannelId: ((_a = this.selectedParticipant) === null || _a === void 0 ? void 0 : _a.userId) || 'everyone', onChannelChange: this.updateRecipients, t: this.t, viewAs: "dropdown" })), h("rtk-chat-messages-ui-paginated", { meeting: this.meeting, onPinMessage: this.onPinMessage, onDeleteMessage: this.onDeleteMessage, size: this.size, iconPack: this.iconPack, t: this.t }), this.renderComposerUI()))));
|
|
514
|
+
return (h(Host, null, h("div", { class: "chat-container" }, h("div", { class: "chat" }, this.isFileMessagingAllowed() && (h("div", { id: "dropzone", class: { active: this.dropzoneActivated }, part: "dropzone" }, h("rtk-icon", { icon: this.iconPack.attach }), h("p", null, this.t('chat.send_attachment')))), this.renderPinnedMessagesHeader(), this.isPrivateChatSupported() && (h("rtk-channel-selector-view", { channels: this.getPrivateChatRecipients(), selectedChannelId: ((_a = this.selectedParticipant) === null || _a === void 0 ? void 0 : _a.userId) || 'everyone', onChannelChange: this.updateRecipients, t: this.t, viewAs: "dropdown" })), h("rtk-chat-messages-ui-paginated", { meeting: this.meeting, onPinMessage: this.onPinMessage, onEditMessage: this.onMessageEdit, onDeleteMessage: this.onDeleteMessage, size: this.size, iconPack: this.iconPack, t: this.t }), this.renderComposerUI()))));
|
|
501
515
|
}
|
|
502
516
|
get host() { return this; }
|
|
503
517
|
static get watchers() { return {
|