@cloudflare/realtimekit-ui 1.1.0-staging.5 → 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 +186 -175
- package/dist/cjs/rtk-chat-toggle.cjs.entry.js +2 -2
- 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 +2 -2
- package/dist/collection/components/rtk-notifications/rtk-notifications.js +4 -1
- package/dist/collection/components/rtk-paginated-list/rtk-paginated-list.js +184 -173
- package/dist/components/{p-32c6e86d.js → p-1d16490e.js} +2 -2
- package/dist/components/{p-ae376177.js → p-7be71567.js} +3 -3
- package/dist/components/{p-0d472019.js → p-7f8d9afc.js} +184 -173
- 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 +2 -2
- 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 +191 -177
- package/dist/esm/rtk-avatar_24.entry.js +186 -175
- package/dist/esm/rtk-chat-toggle.entry.js +2 -2
- 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-f457ae6f.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-83db4de1.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,10 +2125,12 @@ 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
|
-
|
|
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 {
|
|
2150
2134
|
// append messages to the page if page has not reached full capacity
|
|
2151
2135
|
if (this.pages[0].length < this.pageSize) {
|
|
2152
2136
|
this.pages[0].unshift(node);
|
|
@@ -2158,49 +2142,40 @@ const RtkPaginatedList = class {
|
|
|
2158
2142
|
this.loadNextPage();
|
|
2159
2143
|
}
|
|
2160
2144
|
}
|
|
2161
|
-
// If autoscroll is enabled,
|
|
2145
|
+
// If autoscroll is enabled, scroll to the bottom
|
|
2162
2146
|
if (this.autoScroll) {
|
|
2163
2147
|
this.shouldScrollToBottom = true;
|
|
2164
2148
|
this.scrollToBottom();
|
|
2165
2149
|
}
|
|
2166
|
-
else {
|
|
2167
|
-
this.showNewMessagesCTR = true;
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
// this method is called recursively based on shouldScrollToBottom (see scrollEnd listener)
|
|
2171
|
-
scrollToBottom() {
|
|
2172
|
-
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
2173
2150
|
}
|
|
2174
2151
|
/**
|
|
2175
2152
|
* Deletes a node anywhere from the list
|
|
2176
2153
|
* @param {string} id - The id of the node to delete
|
|
2177
2154
|
* */
|
|
2178
2155
|
async onNodeDelete(id) {
|
|
2179
|
-
|
|
2180
|
-
|
|
2156
|
+
var _a, _b;
|
|
2157
|
+
let didDelete = false;
|
|
2158
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
2181
2159
|
const index = this.pages[i].findIndex((node) => node.id === id);
|
|
2182
|
-
// message
|
|
2183
|
-
if (index
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
this.rerender();
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
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();
|
|
2204
2179
|
}
|
|
2205
2180
|
/**
|
|
2206
2181
|
* Updates a new node anywhere in the list
|
|
@@ -2208,146 +2183,182 @@ const RtkPaginatedList = class {
|
|
|
2208
2183
|
* @param {DataNode} _node - The updated data node
|
|
2209
2184
|
* */
|
|
2210
2185
|
async onNodeUpdate(_id, _node) { }
|
|
2211
|
-
rerender() {
|
|
2212
|
-
this.rerenderBoolean = !this.rerenderBoolean;
|
|
2213
|
-
}
|
|
2214
2186
|
connectedCallback() {
|
|
2215
2187
|
this.rerender = debounce.debounce(this.rerender.bind(this), 50, { maxWait: 200 });
|
|
2216
|
-
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
2217
|
-
index$1.writeTask(async () => {
|
|
2218
|
-
for (const entry of entries) {
|
|
2219
|
-
if (entry.target.id === 'top-scroll' && entry.isIntersecting) {
|
|
2220
|
-
this.isLoadingTop = true;
|
|
2221
|
-
await this.loadPrevPage();
|
|
2222
|
-
this.isLoadingTop = false;
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
});
|
|
2226
|
-
});
|
|
2227
2188
|
}
|
|
2228
2189
|
componentDidLoad() {
|
|
2229
|
-
|
|
2190
|
+
// initial load
|
|
2191
|
+
this.loadPrevPage();
|
|
2230
2192
|
if (this.$containerRef) {
|
|
2231
2193
|
this.$containerRef.onscrollend = async () => {
|
|
2232
|
-
|
|
2233
|
-
* Load new page if:
|
|
2234
|
-
* if there are nodes to load at the bottom (maxTS > newTS)
|
|
2235
|
-
* or if there are pages to fill at the bottom (firstEmptyIndex > -1)
|
|
2236
|
-
*/
|
|
2237
|
-
if (this.isAtBottom() && (this.maxTS > this.newTS || this.firstEmptyIndex > -1)) {
|
|
2238
|
-
this.isLoadingBottom = true;
|
|
2194
|
+
if (this.isInView(this.$bottomRef)) {
|
|
2239
2195
|
await this.loadNextPage();
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2196
|
+
}
|
|
2197
|
+
else if (this.isInView(this.$topRef)) {
|
|
2198
|
+
this.showNewMessagesCTR = true;
|
|
2199
|
+
await this.loadPrevPage();
|
|
2243
2200
|
}
|
|
2244
2201
|
};
|
|
2245
2202
|
}
|
|
2246
2203
|
}
|
|
2204
|
+
componentDidRender() {
|
|
2205
|
+
if (!this.pendingScrollAnchor)
|
|
2206
|
+
return;
|
|
2207
|
+
const anchor = this.pendingScrollAnchor;
|
|
2208
|
+
this.pendingScrollAnchor = null;
|
|
2209
|
+
this.restoreScrollToAnchor(anchor);
|
|
2210
|
+
}
|
|
2247
2211
|
async loadPrevPage() {
|
|
2248
2212
|
if (this.isLoading)
|
|
2249
2213
|
return;
|
|
2250
|
-
|
|
2251
|
-
* NOTE(ikabra): this case also runs on initial load
|
|
2252
|
-
* if scrolling up ->
|
|
2253
|
-
* fetch older messages and push to the end of the array
|
|
2254
|
-
* cleanup 1st non empty page from the array if length exceeds pagesAllowed
|
|
2255
|
-
*/
|
|
2214
|
+
const scrollAnchor = this.getScrollAnchor('top');
|
|
2256
2215
|
// if no old timestamp, it means we are at initial state
|
|
2257
2216
|
if (!this.oldTS)
|
|
2258
2217
|
this.oldTS = new Date().getTime();
|
|
2259
2218
|
// load data
|
|
2260
2219
|
this.isLoading = true;
|
|
2220
|
+
this.isLoadingTop = true;
|
|
2261
2221
|
const data = await this.fetchData(this.oldTS - 1, this.pageSize, true);
|
|
2262
2222
|
this.isLoading = false;
|
|
2223
|
+
this.isLoadingTop = false;
|
|
2263
2224
|
// no more old messages to show, we are at the top of the page
|
|
2264
2225
|
if (!data.length)
|
|
2265
2226
|
return;
|
|
2266
2227
|
// add old data to the end of the array
|
|
2267
2228
|
this.pages.push(data);
|
|
2268
2229
|
// clear old pages when we reach the limit
|
|
2269
|
-
if (this.pages.length > this.pagesAllowed)
|
|
2270
|
-
this.pages
|
|
2271
|
-
|
|
2272
|
-
* find last non empty page in range (this.pages.length, this.firstEmptyIndex)
|
|
2273
|
-
* we are doing this because any of the middle pages in the currently rendered pages
|
|
2274
|
-
* could be empty as we allow deleting messages.
|
|
2275
|
-
* This helps us set the first empty index correctly.
|
|
2276
|
-
*/
|
|
2277
|
-
for (let i = this.firstEmptyIndex + 1; i < this.pages.length; i++) {
|
|
2278
|
-
if (this.pages[i].length > 0)
|
|
2279
|
-
break;
|
|
2280
|
-
this.firstEmptyIndex = i;
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
// update the old timestamp
|
|
2230
|
+
if (this.pages.length > this.pagesAllowed)
|
|
2231
|
+
this.pages.shift();
|
|
2232
|
+
// update timestamps
|
|
2284
2233
|
const lastPage = this.pages[this.pages.length - 1];
|
|
2285
2234
|
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
2286
|
-
|
|
2287
|
-
this.newTS = this.pages[this.firstEmptyIndex + 1][0].timeMs;
|
|
2235
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
2288
2236
|
this.rerender();
|
|
2237
|
+
// fix scroll position
|
|
2238
|
+
if (scrollAnchor)
|
|
2239
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
2289
2240
|
}
|
|
2290
2241
|
async loadNextPage() {
|
|
2291
2242
|
if (this.isLoading)
|
|
2292
2243
|
return;
|
|
2293
|
-
//
|
|
2244
|
+
// Do nothing. New timestamp needs to be assigned by loadPrevPage method
|
|
2294
2245
|
if (!this.newTS) {
|
|
2295
2246
|
this.showNewMessagesCTR = false;
|
|
2296
2247
|
this.shouldScrollToBottom = false;
|
|
2297
2248
|
return;
|
|
2298
2249
|
}
|
|
2299
|
-
//
|
|
2250
|
+
// for autoscroll or scroll to bottom button
|
|
2251
|
+
const maxAutoLoads = 200;
|
|
2252
|
+
let loads = 0;
|
|
2253
|
+
let prevNewTS = this.newTS;
|
|
2300
2254
|
this.isLoading = true;
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
this.
|
|
2306
|
-
this.
|
|
2307
|
-
//
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
this.pages[this.firstEmptyIndex] = data.reverse();
|
|
2315
|
-
}
|
|
2316
|
-
else {
|
|
2317
|
-
// 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
|
|
2318
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
|
+
}
|
|
2319
2315
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
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;
|
|
2322
2338
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
for (let i = this.pages.length - 1; i > this.firstEmptyIndex; i--) {
|
|
2327
|
-
if (this.pages[i].length > 0)
|
|
2328
|
-
break;
|
|
2329
|
-
// if page is empty, remove it
|
|
2330
|
-
this.pages.pop();
|
|
2339
|
+
else {
|
|
2340
|
+
const newOffsetBottom = containerRect.bottom - rect.bottom;
|
|
2341
|
+
this.$containerRef.scrollTop += anchor.offsetBottom - newOffsetBottom;
|
|
2331
2342
|
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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;
|
|
2341
2353
|
}
|
|
2342
2354
|
render() {
|
|
2343
2355
|
/**
|
|
2344
|
-
* div.container is flex=column-
|
|
2345
|
-
* 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
|
|
2346
2357
|
*/
|
|
2347
|
-
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: () => {
|
|
2348
2359
|
this.shouldScrollToBottom = true;
|
|
2349
2360
|
this.scrollToBottom();
|
|
2350
|
-
} }, 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) }))));
|
|
2351
2362
|
}
|
|
2352
2363
|
};
|
|
2353
2364
|
__decorate$2([
|
|
@@ -94,9 +94,9 @@ const RtkChatToggle = class {
|
|
|
94
94
|
const { messages } = await chat.getMessages(new Date().getTime(), this.pageSize, true);
|
|
95
95
|
const meetingStartedTimeMs = (_b = (_a = this.meeting.meta) === null || _a === void 0 ? void 0 : _a.meetingStartedTimestamp.getTime()) !== null && _b !== void 0 ? _b : 0;
|
|
96
96
|
const newMessages = messages.filter((m) => m.timeMs > meetingStartedTimeMs);
|
|
97
|
-
if (newMessages.length ===
|
|
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"; }
|
|
@@ -96,9 +96,9 @@ export class RtkChatToggle {
|
|
|
96
96
|
const { messages } = await chat.getMessages(new Date().getTime(), this.pageSize, true);
|
|
97
97
|
const meetingStartedTimeMs = (_b = (_a = this.meeting.meta) === null || _a === void 0 ? void 0 : _a.meetingStartedTimestamp.getTime()) !== null && _b !== void 0 ? _b : 0;
|
|
98
98
|
const newMessages = messages.filter((m) => m.timeMs > meetingStartedTimeMs);
|
|
99
|
-
if (newMessages.length ===
|
|
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) {
|