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