@cloudflare/realtimekit-ui 1.1.0-staging.6 → 1.1.0-staging.7
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 +193 -195
- 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-messages-ui-paginated/rtk-chat-messages-ui-paginated.js +2 -2
- 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 +191 -193
- package/dist/components/{p-85872241.js → p-1d16490e.js} +2 -2
- package/dist/components/{p-b6781e91.js → p-7be71567.js} +3 -3
- package/dist/components/{p-e7e2156a.js → p-7f8d9afc.js} +191 -193
- 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 +1 -1
- package/dist/esm/loader.js +197 -196
- package/dist/esm/rtk-avatar_24.entry.js +193 -195
- package/dist/esm/rtk-chat-toggle.entry.js +1 -1
- package/dist/esm/rtk-notifications.entry.js +4 -1
- package/dist/realtimekit-ui/p-25c13ff8.entry.js +1 -0
- package/dist/realtimekit-ui/p-342b4926.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-paginated-list/rtk-paginated-list.d.ts +32 -46
- 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
|
@@ -1044,7 +1044,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
1044
1044
|
}
|
|
1045
1045
|
const isSelf = message.userId === this.meeting.self.userId;
|
|
1046
1046
|
const viewType = isSelf ? 'outgoing' : 'incoming';
|
|
1047
|
-
return (h("div", null, h("div", { class: "message-wrapper" }, h("rtk-message-view", { pinned: message.pinned, time: message.time, actions: this.getMessageActions(message), authorName: message.displayName, isSelf: isSelf, avatarUrl: displayPicture, hideAuthorName: isContinued, viewType: viewType, variant: "bubble", onAction: (event) => this.onMessageActionHandler(event.detail, message) }, h("div", null, h("div", { class: "body" }, message.type === 'text' && (h("rtk-text-message-view", { text: message.message, isMarkdown: true })), message.type === 'file' && (h("rtk-file-message-view", { name: message.name, url: message.link, size: message.size })), message.type === 'image' && (h("rtk-image-message-view", { url: message.link, onPreview: () => {
|
|
1047
|
+
return (h("div", null, h("div", { class: "message-wrapper", id: message.id }, h("rtk-message-view", { pinned: message.pinned, time: message.time, actions: this.getMessageActions(message), authorName: message.displayName, isSelf: isSelf, avatarUrl: displayPicture, hideAuthorName: isContinued, viewType: viewType, variant: "bubble", onAction: (event) => this.onMessageActionHandler(event.detail, message) }, h("div", null, h("div", { class: "body" }, message.type === 'text' && (h("rtk-text-message-view", { text: message.message, isMarkdown: true })), message.type === 'file' && (h("rtk-file-message-view", { name: message.name, url: message.link, size: message.size })), message.type === 'image' && (h("rtk-image-message-view", { url: message.link, onPreview: () => {
|
|
1048
1048
|
this.stateUpdate.emit({ image: message });
|
|
1049
1049
|
} }))))))));
|
|
1050
1050
|
};
|
|
@@ -1092,7 +1092,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
1092
1092
|
this.lastReadMessageIndex = -1;
|
|
1093
1093
|
}
|
|
1094
1094
|
render() {
|
|
1095
|
-
return (h(Host, { key: '
|
|
1095
|
+
return (h(Host, { key: 'c710da6e2fda420146905a2ed75d3444dd6d2c0b' }, h("rtk-paginated-list", { key: '51a36437e38e9c0242cca34bfda39f6d8309bee3', ref: (el) => (this.$paginatedListRef = el), pageSize: this.pageSize, pagesAllowed: 3, fetchData: this.getChatMessages, createNodes: this.createChatNodes, selectedItemId: this.selectedChannelId, emptyListLabel: this.t('chat.empty_channel') }, h("slot", { key: '69b54a41263510b425ce3e39af055321c4e2deb8' }))));
|
|
1096
1096
|
}
|
|
1097
1097
|
get host() { return getElement(this); }
|
|
1098
1098
|
static get watchers() { return {
|
|
@@ -2054,32 +2054,30 @@ const rtkPaginatedListCss = ".scrollbar{scrollbar-width:thin;scrollbar-color:var
|
|
|
2054
2054
|
const RtkPaginatedListStyle0 = rtkPaginatedListCss;
|
|
2055
2055
|
|
|
2056
2056
|
/**
|
|
2057
|
-
*
|
|
2057
|
+
* NOTE(ikabra): INFINITE SCROLL IMPLEMENTATION:
|
|
2058
2058
|
*
|
|
2059
|
-
*
|
|
2060
|
-
*
|
|
2059
|
+
* Uses scrollend listener for 2way scrolling.
|
|
2060
|
+
* Empty divs ($topRef, $bottomRef) act as scroll triggers to fetch new messages.
|
|
2061
2061
|
*
|
|
2062
|
-
*
|
|
2063
|
-
*
|
|
2064
|
-
*
|
|
2062
|
+
* UPWARD SCROLLING:
|
|
2063
|
+
* - Fetch top anchor (element currently visible to the user near top)
|
|
2064
|
+
* - Fetch older messages, push to end of 2D array
|
|
2065
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
2065
2066
|
*
|
|
2066
|
-
*
|
|
2067
|
-
*
|
|
2067
|
+
* DOWNWARD SCROLLING:
|
|
2068
|
+
* - Fetch bottom anchor (element currently visible to the user near bottom)
|
|
2069
|
+
* - Fetch new page, insert at the start
|
|
2070
|
+
* - Update timestamps & firstEmptyIndex, then rerender
|
|
2071
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
2068
2072
|
*
|
|
2069
|
-
*
|
|
2070
|
-
*
|
|
2071
|
-
*
|
|
2072
|
-
* if length exceeds pagesAllowed, we free up the pages and keep the first empty index in memory (firstEmptyIndex).
|
|
2073
|
+
* ADDING NEW NODES:
|
|
2074
|
+
* - If no pages exist, load old page
|
|
2075
|
+
* - If on 1st page, append messages till page size is full and then load new page
|
|
2073
2076
|
*
|
|
2074
|
-
*
|
|
2075
|
-
* If
|
|
2076
|
-
*
|
|
2077
|
-
*
|
|
2078
|
-
* If we have exceeded our page allowance we delete old pages.
|
|
2079
|
-
*
|
|
2080
|
-
* In this case deleting pages is okay as we are not relying on the index of dom elements to detect page end.
|
|
2081
|
-
*
|
|
2082
|
-
* This also simplifies the code because when a user scrolls up we do not need to manage a lastEmptyIndex.
|
|
2077
|
+
* DELETE NODE:
|
|
2078
|
+
* - If deleting the only available node, reset to initial state
|
|
2079
|
+
* - If page is empty, delete it
|
|
2080
|
+
* - Update timestamp curors
|
|
2083
2081
|
*/
|
|
2084
2082
|
var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
2085
2083
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2094,43 +2092,27 @@ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators,
|
|
|
2094
2092
|
const RtkPaginatedList = class {
|
|
2095
2093
|
constructor(hostRef) {
|
|
2096
2094
|
registerInstance(this, hostRef);
|
|
2097
|
-
/**
|
|
2098
|
-
* when scrolling up, we can't remove pages as intersectionObserver relies on
|
|
2099
|
-
* the index of dom elements to stay stable.
|
|
2100
|
-
* So, instead we free up the pages and keep the last empty index in memory.
|
|
2101
|
-
*/
|
|
2102
|
-
this.firstEmptyIndex = -1;
|
|
2103
|
-
this.maxTS = 0;
|
|
2104
2095
|
// the length of pages will always be pageSize + 2
|
|
2105
2096
|
this.pages = [];
|
|
2097
|
+
// Controls whether to keep auto-scrolling when a new page load.
|
|
2098
|
+
this.shouldScrollToBottom = false;
|
|
2099
|
+
// Shows "scroll to bottom" button when new nodes arrive and autoscroll is off.
|
|
2100
|
+
this.showNewMessagesCTR = false;
|
|
2106
2101
|
/** label to show when empty */
|
|
2107
2102
|
this.emptyListLabel = null;
|
|
2108
|
-
this.rerenderBoolean = false;
|
|
2109
|
-
this.showEmptyListLabel = false;
|
|
2110
2103
|
/** Icon pack */
|
|
2111
2104
|
this.iconPack = defaultIconPack;
|
|
2112
2105
|
/** Language */
|
|
2113
2106
|
this.t = useLanguage();
|
|
2107
|
+
this.rerenderBoolean = false;
|
|
2108
|
+
this.showEmptyListLabel = false;
|
|
2114
2109
|
this.isLoading = false;
|
|
2115
2110
|
this.isLoadingTop = false;
|
|
2116
2111
|
this.isLoadingBottom = false;
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
* */
|
|
2122
|
-
this.shouldScrollToBottom = false;
|
|
2123
|
-
/** UI Indicator for the "scroll to bottom" button.
|
|
2124
|
-
* Toggles on when a new node is added and autoscroll is disabled.
|
|
2125
|
-
* Toggles off when all nodes are loaded */
|
|
2126
|
-
this.showNewMessagesCTR = false;
|
|
2127
|
-
this.observe = (el) => {
|
|
2128
|
-
if (!el)
|
|
2129
|
-
return;
|
|
2130
|
-
this.intersectionObserver.observe(el);
|
|
2131
|
-
};
|
|
2132
|
-
this.isAtBottom = () => {
|
|
2133
|
-
const rect = this.$bottomRef.getBoundingClientRect();
|
|
2112
|
+
// Tells us if we need to scroll to a specific anchor after a rerender
|
|
2113
|
+
this.pendingScrollAnchor = null;
|
|
2114
|
+
this.isInView = (el) => {
|
|
2115
|
+
const rect = el.getBoundingClientRect();
|
|
2134
2116
|
return rect.top >= 0 && rect.bottom <= window.innerHeight;
|
|
2135
2117
|
};
|
|
2136
2118
|
}
|
|
@@ -2139,77 +2121,57 @@ const RtkPaginatedList = class {
|
|
|
2139
2121
|
* @param {DataNode} node - The data node to add to the beginning of the list
|
|
2140
2122
|
*/
|
|
2141
2123
|
async onNewNode(node) {
|
|
2142
|
-
//
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
this.
|
|
2124
|
+
// if there are no pages, load the first page
|
|
2125
|
+
if (this.pages.length < 1) {
|
|
2126
|
+
this.oldTS = node.timeMs + 1;
|
|
2127
|
+
this.loadPrevPage();
|
|
2128
|
+
}
|
|
2129
|
+
else {
|
|
2130
|
+
// append messages to the page if page has not reached full capacity
|
|
2131
|
+
if (this.pages[0].length < this.pageSize) {
|
|
2132
|
+
this.pages[0].unshift(node);
|
|
2133
|
+
this.newTS = node.timeMs;
|
|
2134
|
+
this.rerender();
|
|
2151
2135
|
}
|
|
2152
2136
|
else {
|
|
2153
|
-
//
|
|
2154
|
-
|
|
2155
|
-
this.pages[0].unshift(node);
|
|
2156
|
-
this.newTS = node.timeMs;
|
|
2157
|
-
this.rerender();
|
|
2158
|
-
}
|
|
2159
|
-
else {
|
|
2160
|
-
// if page is at full capacity, load next page
|
|
2161
|
-
this.loadNextPage();
|
|
2162
|
-
}
|
|
2137
|
+
// if page is at full capacity, load next page
|
|
2138
|
+
this.loadNextPage();
|
|
2163
2139
|
}
|
|
2164
2140
|
}
|
|
2165
|
-
// If autoscroll is enabled,
|
|
2141
|
+
// If autoscroll is enabled, scroll to the bottom
|
|
2166
2142
|
if (this.autoScroll) {
|
|
2167
2143
|
this.shouldScrollToBottom = true;
|
|
2168
2144
|
this.scrollToBottom();
|
|
2169
2145
|
}
|
|
2170
|
-
else {
|
|
2171
|
-
this.showNewMessagesCTR = true;
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
// this method is called recursively based on shouldScrollToBottom (see scrollEnd listener)
|
|
2175
|
-
scrollToBottom() {
|
|
2176
|
-
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
2177
2146
|
}
|
|
2178
2147
|
/**
|
|
2179
2148
|
* Deletes a node anywhere from the list
|
|
2180
2149
|
* @param {string} id - The id of the node to delete
|
|
2181
2150
|
* */
|
|
2182
2151
|
async onNodeDelete(id) {
|
|
2183
|
-
|
|
2184
|
-
|
|
2152
|
+
var _a, _b;
|
|
2153
|
+
let didDelete = false;
|
|
2154
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
2185
2155
|
const index = this.pages[i].findIndex((node) => node.id === id);
|
|
2186
|
-
// message
|
|
2187
|
-
if (index
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
this.pages.pop();
|
|
2206
|
-
// update timestamp
|
|
2207
|
-
const lastPage = this.pages[this.firstEmptyIndex + this.pagesAllowed];
|
|
2208
|
-
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
2209
|
-
}
|
|
2210
|
-
this.rerender();
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2156
|
+
// if message not found, move on
|
|
2157
|
+
if (index === -1)
|
|
2158
|
+
continue;
|
|
2159
|
+
// delete message
|
|
2160
|
+
this.pages[i].splice(index, 1);
|
|
2161
|
+
// if page is empty, delete it
|
|
2162
|
+
if (this.pages[i].length === 0)
|
|
2163
|
+
this.pages.splice(i, 1);
|
|
2164
|
+
didDelete = true;
|
|
2165
|
+
break;
|
|
2166
|
+
}
|
|
2167
|
+
if (!didDelete)
|
|
2168
|
+
return;
|
|
2169
|
+
// update timestamps
|
|
2170
|
+
const firstPage = this.pages[0];
|
|
2171
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
2172
|
+
this.newTS = (_a = firstPage === null || firstPage === void 0 ? void 0 : firstPage[0]) === null || _a === void 0 ? void 0 : _a.timeMs;
|
|
2173
|
+
this.oldTS = (_b = lastPage === null || lastPage === void 0 ? void 0 : lastPage[lastPage.length - 1]) === null || _b === void 0 ? void 0 : _b.timeMs;
|
|
2174
|
+
this.rerender();
|
|
2213
2175
|
}
|
|
2214
2176
|
/**
|
|
2215
2177
|
* Updates a new node anywhere in the list
|
|
@@ -2217,146 +2179,182 @@ const RtkPaginatedList = class {
|
|
|
2217
2179
|
* @param {DataNode} _node - The updated data node
|
|
2218
2180
|
* */
|
|
2219
2181
|
async onNodeUpdate(_id, _node) { }
|
|
2220
|
-
rerender() {
|
|
2221
|
-
this.rerenderBoolean = !this.rerenderBoolean;
|
|
2222
|
-
}
|
|
2223
2182
|
connectedCallback() {
|
|
2224
2183
|
this.rerender = debounce(this.rerender.bind(this), 50, { maxWait: 200 });
|
|
2225
|
-
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
2226
|
-
writeTask(async () => {
|
|
2227
|
-
for (const entry of entries) {
|
|
2228
|
-
if (entry.target.id === 'top-scroll' && entry.isIntersecting) {
|
|
2229
|
-
this.isLoadingTop = true;
|
|
2230
|
-
await this.loadPrevPage();
|
|
2231
|
-
this.isLoadingTop = false;
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
});
|
|
2235
|
-
});
|
|
2236
2184
|
}
|
|
2237
2185
|
componentDidLoad() {
|
|
2238
|
-
|
|
2186
|
+
// initial load
|
|
2187
|
+
this.loadPrevPage();
|
|
2239
2188
|
if (this.$containerRef) {
|
|
2240
2189
|
this.$containerRef.onscrollend = async () => {
|
|
2241
|
-
|
|
2242
|
-
* Load new page if:
|
|
2243
|
-
* if there are nodes to load at the bottom (maxTS > newTS)
|
|
2244
|
-
* or if there are pages to fill at the bottom (firstEmptyIndex > -1)
|
|
2245
|
-
*/
|
|
2246
|
-
if (this.isAtBottom() && (this.maxTS > this.newTS || this.firstEmptyIndex > -1)) {
|
|
2247
|
-
this.isLoadingBottom = true;
|
|
2190
|
+
if (this.isInView(this.$bottomRef)) {
|
|
2248
2191
|
await this.loadNextPage();
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2192
|
+
}
|
|
2193
|
+
else if (this.isInView(this.$topRef)) {
|
|
2194
|
+
this.showNewMessagesCTR = true;
|
|
2195
|
+
await this.loadPrevPage();
|
|
2252
2196
|
}
|
|
2253
2197
|
};
|
|
2254
2198
|
}
|
|
2255
2199
|
}
|
|
2200
|
+
componentDidRender() {
|
|
2201
|
+
if (!this.pendingScrollAnchor)
|
|
2202
|
+
return;
|
|
2203
|
+
const anchor = this.pendingScrollAnchor;
|
|
2204
|
+
this.pendingScrollAnchor = null;
|
|
2205
|
+
this.restoreScrollToAnchor(anchor);
|
|
2206
|
+
}
|
|
2256
2207
|
async loadPrevPage() {
|
|
2257
2208
|
if (this.isLoading)
|
|
2258
2209
|
return;
|
|
2259
|
-
|
|
2260
|
-
* NOTE(ikabra): this case also runs on initial load
|
|
2261
|
-
* if scrolling up ->
|
|
2262
|
-
* fetch older messages and push to the end of the array
|
|
2263
|
-
* cleanup 1st non empty page from the array if length exceeds pagesAllowed
|
|
2264
|
-
*/
|
|
2210
|
+
const scrollAnchor = this.getScrollAnchor('top');
|
|
2265
2211
|
// if no old timestamp, it means we are at initial state
|
|
2266
2212
|
if (!this.oldTS)
|
|
2267
2213
|
this.oldTS = new Date().getTime();
|
|
2268
2214
|
// load data
|
|
2269
2215
|
this.isLoading = true;
|
|
2216
|
+
this.isLoadingTop = true;
|
|
2270
2217
|
const data = await this.fetchData(this.oldTS - 1, this.pageSize, true);
|
|
2271
2218
|
this.isLoading = false;
|
|
2219
|
+
this.isLoadingTop = false;
|
|
2272
2220
|
// no more old messages to show, we are at the top of the page
|
|
2273
2221
|
if (!data.length)
|
|
2274
2222
|
return;
|
|
2275
2223
|
// add old data to the end of the array
|
|
2276
2224
|
this.pages.push(data);
|
|
2277
2225
|
// clear old pages when we reach the limit
|
|
2278
|
-
if (this.pages.length > this.pagesAllowed)
|
|
2279
|
-
this.pages
|
|
2280
|
-
|
|
2281
|
-
* find last non empty page in range (this.pages.length, this.firstEmptyIndex)
|
|
2282
|
-
* we are doing this because any of the middle pages in the currently rendered pages
|
|
2283
|
-
* could be empty as we allow deleting messages.
|
|
2284
|
-
* This helps us set the first empty index correctly.
|
|
2285
|
-
*/
|
|
2286
|
-
for (let i = this.firstEmptyIndex + 1; i < this.pages.length; i++) {
|
|
2287
|
-
if (this.pages[i].length > 0)
|
|
2288
|
-
break;
|
|
2289
|
-
this.firstEmptyIndex = i;
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
// update the old timestamp
|
|
2226
|
+
if (this.pages.length > this.pagesAllowed)
|
|
2227
|
+
this.pages.shift();
|
|
2228
|
+
// update timestamps
|
|
2293
2229
|
const lastPage = this.pages[this.pages.length - 1];
|
|
2294
2230
|
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
2295
|
-
|
|
2296
|
-
this.newTS = this.pages[this.firstEmptyIndex + 1][0].timeMs;
|
|
2231
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
2297
2232
|
this.rerender();
|
|
2233
|
+
// fix scroll position
|
|
2234
|
+
if (scrollAnchor)
|
|
2235
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
2298
2236
|
}
|
|
2299
2237
|
async loadNextPage() {
|
|
2300
2238
|
if (this.isLoading)
|
|
2301
2239
|
return;
|
|
2302
|
-
//
|
|
2240
|
+
// Do nothing. New timestamp needs to be assigned by loadPrevPage method
|
|
2303
2241
|
if (!this.newTS) {
|
|
2304
2242
|
this.showNewMessagesCTR = false;
|
|
2305
2243
|
this.shouldScrollToBottom = false;
|
|
2306
2244
|
return;
|
|
2307
2245
|
}
|
|
2308
|
-
//
|
|
2246
|
+
// for autoscroll or scroll to bottom button
|
|
2247
|
+
const maxAutoLoads = 200;
|
|
2248
|
+
let loads = 0;
|
|
2249
|
+
let prevNewTS = this.newTS;
|
|
2309
2250
|
this.isLoading = true;
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
this.
|
|
2315
|
-
this.
|
|
2316
|
-
//
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
this.pages[this.firstEmptyIndex] = data.reverse();
|
|
2324
|
-
}
|
|
2325
|
-
else {
|
|
2326
|
-
// when already at the bottom and loading messages in realtime
|
|
2251
|
+
this.isLoadingBottom = true;
|
|
2252
|
+
while (loads < maxAutoLoads) {
|
|
2253
|
+
const scrollAnchor = this.getScrollAnchor('bottom');
|
|
2254
|
+
const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
|
|
2255
|
+
this.isLoading = false;
|
|
2256
|
+
this.isLoadingBottom = false;
|
|
2257
|
+
// no more new messages to load
|
|
2258
|
+
if (!data.length) {
|
|
2259
|
+
this.showNewMessagesCTR = false;
|
|
2260
|
+
this.shouldScrollToBottom = false;
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2263
|
+
// load new messages and append to the start
|
|
2327
2264
|
this.pages.unshift(data.reverse());
|
|
2265
|
+
// remove pages if out of bounds
|
|
2266
|
+
if (this.pages.length > this.pagesAllowed)
|
|
2267
|
+
this.pages.pop();
|
|
2268
|
+
// update timestamps
|
|
2269
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
2270
|
+
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
2271
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
2272
|
+
this.rerender();
|
|
2273
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
2274
|
+
if (!this.shouldScrollToBottom)
|
|
2275
|
+
break;
|
|
2276
|
+
// if should scroll to bottom then retrigger
|
|
2277
|
+
await this.waitForNextFrame();
|
|
2278
|
+
this.scrollToBottom();
|
|
2279
|
+
await this.waitForNextFrame();
|
|
2280
|
+
// if no new messages, break
|
|
2281
|
+
if (this.newTS === prevNewTS)
|
|
2282
|
+
break;
|
|
2283
|
+
prevNewTS = this.newTS;
|
|
2284
|
+
loads++;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
// Find the element that is closest to the top/bottom of the container
|
|
2288
|
+
getScrollAnchor(edge = 'top') {
|
|
2289
|
+
if (!this.$containerRef)
|
|
2290
|
+
return null;
|
|
2291
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
2292
|
+
const candidates = Array.from(this.$containerRef.querySelectorAll('[id]')).filter((el) => el.id !== 'top-scroll' && el.id !== 'bottom-scroll');
|
|
2293
|
+
let best = null;
|
|
2294
|
+
for (const el of candidates) {
|
|
2295
|
+
const rect = el.getBoundingClientRect();
|
|
2296
|
+
const isVisibleInContainer = rect.bottom > containerRect.top && rect.top < containerRect.bottom;
|
|
2297
|
+
if (!isVisibleInContainer)
|
|
2298
|
+
continue;
|
|
2299
|
+
if (edge === 'top') {
|
|
2300
|
+
const offsetTop = rect.top - containerRect.top;
|
|
2301
|
+
if (best == null || (best.edge === 'top' && offsetTop < best.offsetTop)) {
|
|
2302
|
+
best = { id: el.id, edge: 'top', offsetTop };
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
else {
|
|
2306
|
+
const offsetBottom = containerRect.bottom - rect.bottom;
|
|
2307
|
+
if (best == null || (best.edge === 'bottom' && offsetBottom < best.offsetBottom)) {
|
|
2308
|
+
best = { id: el.id, edge: 'bottom', offsetBottom };
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2328
2311
|
}
|
|
2329
|
-
|
|
2330
|
-
|
|
2312
|
+
return best;
|
|
2313
|
+
}
|
|
2314
|
+
//instant scroll to anchor to make sure we are at the same position after a rerender
|
|
2315
|
+
restoreScrollToAnchor(anchor) {
|
|
2316
|
+
if (!this.$containerRef)
|
|
2317
|
+
return;
|
|
2318
|
+
// make element id safe to use inside a CSS selector
|
|
2319
|
+
const escapeId = (id) => {
|
|
2320
|
+
var _a;
|
|
2321
|
+
const cssEscape = (_a = globalThis.CSS) === null || _a === void 0 ? void 0 : _a.escape;
|
|
2322
|
+
return typeof cssEscape === 'function'
|
|
2323
|
+
? cssEscape(id)
|
|
2324
|
+
: id.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
|
|
2325
|
+
};
|
|
2326
|
+
const el = this.$containerRef.querySelector(`#${escapeId(anchor.id)}`);
|
|
2327
|
+
if (!el)
|
|
2328
|
+
return;
|
|
2329
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
2330
|
+
const rect = el.getBoundingClientRect();
|
|
2331
|
+
if (anchor.edge === 'top') {
|
|
2332
|
+
const newOffsetTop = rect.top - containerRect.top;
|
|
2333
|
+
this.$containerRef.scrollTop += newOffsetTop - anchor.offsetTop;
|
|
2331
2334
|
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
for (let i = this.pages.length - 1; i > this.firstEmptyIndex; i--) {
|
|
2336
|
-
if (this.pages[i].length > 0)
|
|
2337
|
-
break;
|
|
2338
|
-
// if page is empty, remove it
|
|
2339
|
-
this.pages.pop();
|
|
2335
|
+
else {
|
|
2336
|
+
const newOffsetBottom = containerRect.bottom - rect.bottom;
|
|
2337
|
+
this.$containerRef.scrollTop += anchor.offsetBottom - newOffsetBottom;
|
|
2340
2338
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2339
|
+
}
|
|
2340
|
+
// this method is called recursively based on shouldScrollToBottom (see loadNextPage)
|
|
2341
|
+
scrollToBottom() {
|
|
2342
|
+
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
2343
|
+
}
|
|
2344
|
+
waitForNextFrame() {
|
|
2345
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
2346
|
+
}
|
|
2347
|
+
rerender() {
|
|
2348
|
+
this.rerenderBoolean = !this.rerenderBoolean;
|
|
2350
2349
|
}
|
|
2351
2350
|
render() {
|
|
2352
2351
|
/**
|
|
2353
|
-
* div.container is flex=column-
|
|
2354
|
-
* which is why div#bottom-scroll comes before div#top-scroll
|
|
2352
|
+
* div.container is flex=column-reversewhich is why div#bottom-scroll comes before div#top-scroll
|
|
2355
2353
|
*/
|
|
2356
|
-
return (h(Host, { key: '
|
|
2354
|
+
return (h(Host, { key: 'e0f806cccdcba162d0c834476863b34630cb1a1e' }, h("div", { key: '6d54d50ed703a59df8d26399499533e3cb0d70fe', class: "scrollbar container", part: "container", ref: (el) => (this.$containerRef = el) }, h("div", { key: '4e9fcbed725fb55d9fbb67eca94b0e770662d51b', class: { 'show-new-messages-ctr': true, active: this.showNewMessagesCTR } }, h("rtk-button", { key: '7db0236d35db3fb9856fea7a4f62c1fbd421829e', class: "show-new-messages", kind: "icon", variant: "secondary", part: "show-new-messages", onClick: () => {
|
|
2357
2355
|
this.shouldScrollToBottom = true;
|
|
2358
2356
|
this.scrollToBottom();
|
|
2359
|
-
} }, h("rtk-icon", { key: '
|
|
2357
|
+
} }, h("rtk-icon", { key: '6e247e86029560601080e0b4d6dcfccbd90fcdd6', icon: this.iconPack.chevron_down }))), h("div", { key: '01d1ba7eacc67ece70b805fb2dfb493cdf1c9d23', class: "smallest-dom-element", id: "bottom-scroll", ref: (el) => (this.$bottomRef = el) }), this.isLoadingBottom && this.pages.length > 0 && h("rtk-spinner", { key: '24391bfc34914675cbfd0c287332bfdf3f5f5000', size: "sm" }), this.isLoading && this.pages.length < 1 && h("rtk-spinner", { key: 'e6c2cb44fce52ca54c9f1e543d91a29648745408', 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: 'd90a15494bcd7ec6c9c7ffc5e9a55054252a4258', size: "sm" }), h("div", { key: '2a65f98c3c4e6f2510c6cf1b4e2bcf1e607a7552', class: "smallest-dom-element", id: "top-scroll", ref: (el) => (this.$topRef = el) }))));
|
|
2360
2358
|
}
|
|
2361
2359
|
};
|
|
2362
2360
|
__decorate$2([
|
|
@@ -92,7 +92,7 @@ const RtkChatToggle = class {
|
|
|
92
92
|
const newMessages = messages.filter((m) => m.timeMs > meetingStartedTimeMs);
|
|
93
93
|
if (newMessages.length === this.pageSize && newMessages.length > 0) {
|
|
94
94
|
// all messages are new, so we can't know the exact count, but we know there are at least pageSize - 1 new messages
|
|
95
|
-
this.unreadMessageCount =
|
|
95
|
+
this.unreadMessageCount = newMessages.length;
|
|
96
96
|
}
|
|
97
97
|
else {
|
|
98
98
|
this.unreadMessageCount = newMessages.length;
|
|
@@ -228,7 +228,10 @@ const RtkNotifications = class {
|
|
|
228
228
|
this.waitlistedParticipantLeftListener = (participant) => {
|
|
229
229
|
this.remove(`${participant.id}-joined-waitlist`);
|
|
230
230
|
};
|
|
231
|
-
this.chatUpdateListener = ({ message }) => {
|
|
231
|
+
this.chatUpdateListener = ({ message, action }) => {
|
|
232
|
+
// NOTE(ikabra): we only want notifications for new messages
|
|
233
|
+
if (action !== 'add')
|
|
234
|
+
return;
|
|
232
235
|
const parsedMessage = parseMessageForTarget(message);
|
|
233
236
|
if (parsedMessage != null) {
|
|
234
237
|
if (parsedMessage.userId === meeting.self.userId) {
|