@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
|
@@ -507,6 +507,13 @@ const RtkChat = class {
|
|
|
507
507
|
const message = event.detail;
|
|
508
508
|
this.meeting.chat.deleteMessage(message.id);
|
|
509
509
|
};
|
|
510
|
+
this.onMessageEdit = (event) => {
|
|
511
|
+
const message = event.detail;
|
|
512
|
+
if (message.type !== 'text')
|
|
513
|
+
return;
|
|
514
|
+
this.replyMessage = null;
|
|
515
|
+
this.editingMessage = message;
|
|
516
|
+
};
|
|
510
517
|
this.getPrivateChatRecipients = () => {
|
|
511
518
|
const participants = this.getFilteredParticipants().map((participant) => {
|
|
512
519
|
const key = chat.generateChatGroupKey([participant.userId, this.meeting.self.userId]);
|
|
@@ -691,14 +698,21 @@ const RtkChat = class {
|
|
|
691
698
|
const uiProps = { iconPack: this.iconPack, t: this.t, size: this.size };
|
|
692
699
|
const message = this.editingMessage ? this.editingMessage.message : '';
|
|
693
700
|
const quotedMessage = this.replyMessage ? this.replyMessage.message : '';
|
|
694
|
-
|
|
701
|
+
const draftStorageKey = this.selectedChannelId
|
|
702
|
+
? `rtk-chat-draft-${this.selectedChannelId}`
|
|
703
|
+
: 'rtk-chat-draft';
|
|
704
|
+
const editStorageKey = this.editingMessage
|
|
705
|
+
? `rtk-chat-edit-${(_a = this.selectedChannelId) !== null && _a !== void 0 ? _a : 'no-channel'}-${this.editingMessage.id}`
|
|
706
|
+
: 'rtk-chat-edit';
|
|
707
|
+
const storageKey = this.editingMessage ? editStorageKey : draftStorageKey;
|
|
708
|
+
return (index$1.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), index$1.h("slot", { name: "chat-addon", slot: "chat-addon" })));
|
|
695
709
|
}
|
|
696
710
|
render() {
|
|
697
711
|
var _a;
|
|
698
712
|
if (!this.meeting) {
|
|
699
713
|
return null;
|
|
700
714
|
}
|
|
701
|
-
return (index$1.h(index$1.Host, null, index$1.h("div", { class: "chat-container" }, index$1.h("div", { class: "chat" }, this.isFileMessagingAllowed() && (index$1.h("div", { id: "dropzone", class: { active: this.dropzoneActivated }, part: "dropzone" }, index$1.h("rtk-icon", { icon: this.iconPack.attach }), index$1.h("p", null, this.t('chat.send_attachment')))), this.renderPinnedMessagesHeader(), this.isPrivateChatSupported() && (index$1.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" })), index$1.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()))));
|
|
715
|
+
return (index$1.h(index$1.Host, null, index$1.h("div", { class: "chat-container" }, index$1.h("div", { class: "chat" }, this.isFileMessagingAllowed() && (index$1.h("div", { id: "dropzone", class: { active: this.dropzoneActivated }, part: "dropzone" }, index$1.h("rtk-icon", { icon: this.iconPack.attach }), index$1.h("p", null, this.t('chat.send_attachment')))), this.renderPinnedMessagesHeader(), this.isPrivateChatSupported() && (index$1.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" })), index$1.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()))));
|
|
702
716
|
}
|
|
703
717
|
get host() { return index$1.getElement(this); }
|
|
704
718
|
static get watchers() { return {
|
|
@@ -941,6 +955,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
941
955
|
index$1.registerInstance(this, hostRef);
|
|
942
956
|
this.editMessageInit = index$1.createEvent(this, "editMessageInit", 7);
|
|
943
957
|
this.onPinMessage = index$1.createEvent(this, "pinMessage", 7);
|
|
958
|
+
this.onEditMessage = index$1.createEvent(this, "editMessage", 7);
|
|
944
959
|
this.onDeleteMessage = index$1.createEvent(this, "deleteMessage", 7);
|
|
945
960
|
this.stateUpdate = index$1.createEvent(this, "rtkStateUpdate", 7);
|
|
946
961
|
/** Icon pack */
|
|
@@ -991,22 +1006,18 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
991
1006
|
};
|
|
992
1007
|
this.getMessageActions = (message) => {
|
|
993
1008
|
const actions = [];
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
const canDelete = message.userId === this.meeting.self.userId;
|
|
1002
|
-
if (this.meeting.self.permissions.pinParticipant) {
|
|
1009
|
+
const messageBelongsToSelf = message.userId === this.meeting.self.userId;
|
|
1010
|
+
actions.push({
|
|
1011
|
+
id: 'pin_message',
|
|
1012
|
+
label: message.pinned ? this.t('unpin') : this.t('pin'),
|
|
1013
|
+
icon: this.iconPack.pin,
|
|
1014
|
+
});
|
|
1015
|
+
if (messageBelongsToSelf) {
|
|
1003
1016
|
actions.push({
|
|
1004
|
-
id: '
|
|
1005
|
-
label:
|
|
1006
|
-
icon: this.iconPack.
|
|
1017
|
+
id: 'edit_message',
|
|
1018
|
+
label: this.t('chat.edit_msg'),
|
|
1019
|
+
icon: this.iconPack.edit,
|
|
1007
1020
|
});
|
|
1008
|
-
}
|
|
1009
|
-
if (canDelete) {
|
|
1010
1021
|
actions.push({
|
|
1011
1022
|
id: 'delete_message',
|
|
1012
1023
|
label: this.t('chat.delete_msg'),
|
|
@@ -1020,6 +1031,9 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
1020
1031
|
case 'pin_message':
|
|
1021
1032
|
this.onPinMessage.emit(message);
|
|
1022
1033
|
break;
|
|
1034
|
+
case 'edit_message':
|
|
1035
|
+
this.onEditMessage.emit(message);
|
|
1036
|
+
break;
|
|
1023
1037
|
case 'delete_message':
|
|
1024
1038
|
this.onDeleteMessage.emit(message);
|
|
1025
1039
|
break;
|
|
@@ -1048,7 +1062,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
1048
1062
|
}
|
|
1049
1063
|
const isSelf = message.userId === this.meeting.self.userId;
|
|
1050
1064
|
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: () => {
|
|
1065
|
+
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
1066
|
this.stateUpdate.emit({ image: message });
|
|
1053
1067
|
} }))))))));
|
|
1054
1068
|
};
|
|
@@ -1096,7 +1110,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
1096
1110
|
this.lastReadMessageIndex = -1;
|
|
1097
1111
|
}
|
|
1098
1112
|
render() {
|
|
1099
|
-
return (index$1.h(index$1.Host, { key: '
|
|
1113
|
+
return (index$1.h(index$1.Host, { key: '55b594d1fad2c164a70b71e297f63273ece0bc4f' }, index$1.h("rtk-paginated-list", { key: '1554ee896643feec72a02672f156f95c988813ce', 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: '03aeb2069b87cb217b9759cabd3835c9bac81f11' }))));
|
|
1100
1114
|
}
|
|
1101
1115
|
get host() { return index$1.getElement(this); }
|
|
1102
1116
|
static get watchers() { return {
|
|
@@ -2058,32 +2072,30 @@ const rtkPaginatedListCss = ".scrollbar{scrollbar-width:thin;scrollbar-color:var
|
|
|
2058
2072
|
const RtkPaginatedListStyle0 = rtkPaginatedListCss;
|
|
2059
2073
|
|
|
2060
2074
|
/**
|
|
2061
|
-
*
|
|
2062
|
-
*
|
|
2063
|
-
* We use intersectionObserver to scroll up.
|
|
2064
|
-
* We use scrollEnd listener to scroll down.
|
|
2065
|
-
*
|
|
2066
|
-
* Why?
|
|
2067
|
-
* intersectionObserver doesn't work reliably for 2 way scrolling but has great ux,
|
|
2068
|
-
* so we use it to smoothly scroll up.
|
|
2075
|
+
* NOTE(ikabra): INFINITE SCROLL IMPLEMENTATION:
|
|
2069
2076
|
*
|
|
2070
|
-
*
|
|
2071
|
-
*
|
|
2077
|
+
* Uses scrollend listener for 2way scrolling.
|
|
2078
|
+
* Empty divs ($topRef, $bottomRef) act as scroll triggers to fetch new messages.
|
|
2072
2079
|
*
|
|
2073
|
-
*
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2076
|
-
*
|
|
2080
|
+
* UPWARD SCROLLING:
|
|
2081
|
+
* - Fetch top anchor (element currently visible to the user near top)
|
|
2082
|
+
* - Fetch older messages, push to end of 2D array
|
|
2083
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
2077
2084
|
*
|
|
2078
|
-
*
|
|
2079
|
-
*
|
|
2080
|
-
*
|
|
2085
|
+
* DOWNWARD SCROLLING:
|
|
2086
|
+
* - Fetch bottom anchor (element currently visible to the user near bottom)
|
|
2087
|
+
* - Fetch new page, insert at the start
|
|
2088
|
+
* - Update timestamps & firstEmptyIndex, then rerender
|
|
2089
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
2081
2090
|
*
|
|
2082
|
-
*
|
|
2091
|
+
* ADDING NEW NODES:
|
|
2092
|
+
* - If no pages exist, load old page
|
|
2093
|
+
* - If on 1st page, append messages till page size is full and then load new page
|
|
2083
2094
|
*
|
|
2084
|
-
*
|
|
2085
|
-
*
|
|
2086
|
-
*
|
|
2095
|
+
* DELETE NODE:
|
|
2096
|
+
* - If deleting the only available node, reset to initial state
|
|
2097
|
+
* - If page is empty, delete it
|
|
2098
|
+
* - Update timestamp curors
|
|
2087
2099
|
*/
|
|
2088
2100
|
var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
2089
2101
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2098,43 +2110,27 @@ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators,
|
|
|
2098
2110
|
const RtkPaginatedList = class {
|
|
2099
2111
|
constructor(hostRef) {
|
|
2100
2112
|
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
2113
|
// the length of pages will always be pageSize + 2
|
|
2109
2114
|
this.pages = [];
|
|
2115
|
+
// Controls whether to keep auto-scrolling when a new page load.
|
|
2116
|
+
this.shouldScrollToBottom = false;
|
|
2117
|
+
// Shows "scroll to bottom" button when new nodes arrive and autoscroll is off.
|
|
2118
|
+
this.showNewMessagesCTR = false;
|
|
2110
2119
|
/** label to show when empty */
|
|
2111
2120
|
this.emptyListLabel = null;
|
|
2112
|
-
this.rerenderBoolean = false;
|
|
2113
|
-
this.showEmptyListLabel = false;
|
|
2114
2121
|
/** Icon pack */
|
|
2115
2122
|
this.iconPack = uiStore.defaultIconPack;
|
|
2116
2123
|
/** Language */
|
|
2117
2124
|
this.t = uiStore.useLanguage();
|
|
2125
|
+
this.rerenderBoolean = false;
|
|
2126
|
+
this.showEmptyListLabel = false;
|
|
2118
2127
|
this.isLoading = false;
|
|
2119
2128
|
this.isLoadingTop = false;
|
|
2120
2129
|
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();
|
|
2130
|
+
// Tells us if we need to scroll to a specific anchor after a rerender
|
|
2131
|
+
this.pendingScrollAnchor = null;
|
|
2132
|
+
this.isInView = (el) => {
|
|
2133
|
+
const rect = el.getBoundingClientRect();
|
|
2138
2134
|
return rect.top >= 0 && rect.bottom <= window.innerHeight;
|
|
2139
2135
|
};
|
|
2140
2136
|
}
|
|
@@ -2143,224 +2139,255 @@ const RtkPaginatedList = class {
|
|
|
2143
2139
|
* @param {DataNode} node - The data node to add to the beginning of the list
|
|
2144
2140
|
*/
|
|
2145
2141
|
async onNewNode(node) {
|
|
2146
|
-
//
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2142
|
+
// if there are no pages, load the first page
|
|
2143
|
+
if (this.pages.length < 1) {
|
|
2144
|
+
this.oldTS = node.timeMs + 1;
|
|
2145
|
+
this.loadPrevPage();
|
|
2146
|
+
}
|
|
2147
|
+
else if (this.maxTS === this.newTS) {
|
|
2148
|
+
this.maxTS = node.timeMs;
|
|
2149
|
+
// append messages to the page if page has not reached full capacity
|
|
2150
|
+
if (this.pages[0].length < this.pageSize) {
|
|
2151
|
+
this.pages[0].unshift(node);
|
|
2152
|
+
this.newTS = node.timeMs;
|
|
2153
|
+
this.rerender();
|
|
2155
2154
|
}
|
|
2156
2155
|
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
|
-
}
|
|
2156
|
+
// if page is at full capacity, load next page
|
|
2157
|
+
this.loadNextPage();
|
|
2167
2158
|
}
|
|
2168
2159
|
}
|
|
2169
|
-
// If autoscroll is enabled,
|
|
2160
|
+
// If autoscroll is enabled, scroll to the bottom
|
|
2170
2161
|
if (this.autoScroll) {
|
|
2171
2162
|
this.shouldScrollToBottom = true;
|
|
2172
2163
|
this.scrollToBottom();
|
|
2173
2164
|
}
|
|
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
2165
|
}
|
|
2182
2166
|
/**
|
|
2183
2167
|
* Deletes a node anywhere from the list
|
|
2184
2168
|
* @param {string} id - The id of the node to delete
|
|
2185
2169
|
* */
|
|
2186
2170
|
async onNodeDelete(id) {
|
|
2187
|
-
|
|
2188
|
-
|
|
2171
|
+
var _a, _b;
|
|
2172
|
+
let didDelete = false;
|
|
2173
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
2189
2174
|
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
|
-
}
|
|
2175
|
+
// if message not found, move on
|
|
2176
|
+
if (index === -1)
|
|
2177
|
+
continue;
|
|
2178
|
+
// delete message
|
|
2179
|
+
this.pages[i].splice(index, 1);
|
|
2180
|
+
// if page is empty, delete it
|
|
2181
|
+
if (this.pages[i].length === 0)
|
|
2182
|
+
this.pages.splice(i, 1);
|
|
2183
|
+
didDelete = true;
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
if (!didDelete)
|
|
2187
|
+
return;
|
|
2188
|
+
// update timestamps
|
|
2189
|
+
const firstPage = this.pages[0];
|
|
2190
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
2191
|
+
this.newTS = (_a = firstPage === null || firstPage === void 0 ? void 0 : firstPage[0]) === null || _a === void 0 ? void 0 : _a.timeMs;
|
|
2192
|
+
this.oldTS = (_b = lastPage === null || lastPage === void 0 ? void 0 : lastPage[lastPage.length - 1]) === null || _b === void 0 ? void 0 : _b.timeMs;
|
|
2193
|
+
this.rerender();
|
|
2217
2194
|
}
|
|
2218
2195
|
/**
|
|
2219
2196
|
* Updates a new node anywhere in the list
|
|
2220
|
-
* @param {string}
|
|
2221
|
-
* @param {DataNode}
|
|
2197
|
+
* @param {string} id - The id of the node to update
|
|
2198
|
+
* @param {DataNode} node - The updated data node
|
|
2222
2199
|
* */
|
|
2223
|
-
async onNodeUpdate(
|
|
2224
|
-
|
|
2225
|
-
|
|
2200
|
+
async onNodeUpdate(id, node) {
|
|
2201
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
2202
|
+
const index = this.pages[i].findIndex((node) => node.id === id);
|
|
2203
|
+
// if message not found, move on
|
|
2204
|
+
if (index === -1)
|
|
2205
|
+
continue;
|
|
2206
|
+
// edit message
|
|
2207
|
+
this.pages[i][index] = node;
|
|
2208
|
+
this.rerender();
|
|
2209
|
+
break;
|
|
2210
|
+
}
|
|
2226
2211
|
}
|
|
2227
2212
|
connectedCallback() {
|
|
2228
2213
|
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
2214
|
}
|
|
2241
2215
|
componentDidLoad() {
|
|
2242
|
-
|
|
2216
|
+
// initial load
|
|
2217
|
+
this.loadPrevPage();
|
|
2243
2218
|
if (this.$containerRef) {
|
|
2244
2219
|
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;
|
|
2220
|
+
if (this.isInView(this.$bottomRef)) {
|
|
2252
2221
|
await this.loadNextPage();
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2222
|
+
}
|
|
2223
|
+
else if (this.isInView(this.$topRef)) {
|
|
2224
|
+
this.showNewMessagesCTR = true;
|
|
2225
|
+
await this.loadPrevPage();
|
|
2256
2226
|
}
|
|
2257
2227
|
};
|
|
2258
2228
|
}
|
|
2259
2229
|
}
|
|
2230
|
+
componentDidRender() {
|
|
2231
|
+
if (!this.pendingScrollAnchor)
|
|
2232
|
+
return;
|
|
2233
|
+
const anchor = this.pendingScrollAnchor;
|
|
2234
|
+
this.pendingScrollAnchor = null;
|
|
2235
|
+
this.restoreScrollToAnchor(anchor);
|
|
2236
|
+
}
|
|
2260
2237
|
async loadPrevPage() {
|
|
2261
2238
|
if (this.isLoading)
|
|
2262
2239
|
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
|
-
*/
|
|
2240
|
+
const scrollAnchor = this.getScrollAnchor('top');
|
|
2269
2241
|
// if no old timestamp, it means we are at initial state
|
|
2270
2242
|
if (!this.oldTS)
|
|
2271
2243
|
this.oldTS = new Date().getTime();
|
|
2272
2244
|
// load data
|
|
2273
2245
|
this.isLoading = true;
|
|
2246
|
+
this.isLoadingTop = true;
|
|
2274
2247
|
const data = await this.fetchData(this.oldTS - 1, this.pageSize, true);
|
|
2275
2248
|
this.isLoading = false;
|
|
2249
|
+
this.isLoadingTop = false;
|
|
2276
2250
|
// no more old messages to show, we are at the top of the page
|
|
2277
2251
|
if (!data.length)
|
|
2278
2252
|
return;
|
|
2279
2253
|
// add old data to the end of the array
|
|
2280
2254
|
this.pages.push(data);
|
|
2281
2255
|
// 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
|
|
2256
|
+
if (this.pages.length > this.pagesAllowed)
|
|
2257
|
+
this.pages.shift();
|
|
2258
|
+
// update timestamps
|
|
2297
2259
|
const lastPage = this.pages[this.pages.length - 1];
|
|
2298
2260
|
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
2299
|
-
|
|
2300
|
-
|
|
2261
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
2262
|
+
if (!this.maxTS)
|
|
2263
|
+
this.maxTS = this.newTS;
|
|
2301
2264
|
this.rerender();
|
|
2265
|
+
// fix scroll position
|
|
2266
|
+
if (scrollAnchor)
|
|
2267
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
2302
2268
|
}
|
|
2303
2269
|
async loadNextPage() {
|
|
2304
2270
|
if (this.isLoading)
|
|
2305
2271
|
return;
|
|
2306
|
-
//
|
|
2272
|
+
// Do nothing. New timestamp needs to be assigned by loadPrevPage method
|
|
2307
2273
|
if (!this.newTS) {
|
|
2308
2274
|
this.showNewMessagesCTR = false;
|
|
2309
2275
|
this.shouldScrollToBottom = false;
|
|
2310
2276
|
return;
|
|
2311
2277
|
}
|
|
2312
|
-
//
|
|
2278
|
+
// for autoscroll or scroll to bottom button
|
|
2279
|
+
const maxAutoLoads = 200;
|
|
2280
|
+
let loads = 0;
|
|
2281
|
+
let prevNewTS = this.newTS;
|
|
2313
2282
|
this.isLoading = true;
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
this.
|
|
2319
|
-
this.
|
|
2320
|
-
//
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
}
|
|
2329
|
-
else {
|
|
2330
|
-
// when already at the bottom and loading messages in realtime
|
|
2283
|
+
this.isLoadingBottom = true;
|
|
2284
|
+
while (loads < maxAutoLoads) {
|
|
2285
|
+
const scrollAnchor = this.getScrollAnchor('bottom');
|
|
2286
|
+
const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
|
|
2287
|
+
this.isLoading = false;
|
|
2288
|
+
this.isLoadingBottom = false;
|
|
2289
|
+
// no more new messages to load
|
|
2290
|
+
if (!data.length) {
|
|
2291
|
+
this.maxTS = this.newTS;
|
|
2292
|
+
this.showNewMessagesCTR = false;
|
|
2293
|
+
this.shouldScrollToBottom = false;
|
|
2294
|
+
break;
|
|
2295
|
+
}
|
|
2296
|
+
// load new messages and append to the start
|
|
2331
2297
|
this.pages.unshift(data.reverse());
|
|
2298
|
+
// remove pages if out of bounds
|
|
2299
|
+
if (this.pages.length > this.pagesAllowed)
|
|
2300
|
+
this.pages.pop();
|
|
2301
|
+
// update timestamps
|
|
2302
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
2303
|
+
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
2304
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
2305
|
+
this.rerender();
|
|
2306
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
2307
|
+
if (!this.shouldScrollToBottom)
|
|
2308
|
+
break;
|
|
2309
|
+
// if should scroll to bottom then retrigger
|
|
2310
|
+
await this.waitForNextFrame();
|
|
2311
|
+
this.scrollToBottom();
|
|
2312
|
+
await this.waitForNextFrame();
|
|
2313
|
+
// if no new messages, break
|
|
2314
|
+
if (this.newTS === prevNewTS)
|
|
2315
|
+
break;
|
|
2316
|
+
prevNewTS = this.newTS;
|
|
2317
|
+
loads++;
|
|
2332
2318
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2319
|
+
}
|
|
2320
|
+
// Find the element that is closest to the top/bottom of the container
|
|
2321
|
+
getScrollAnchor(edge = 'top') {
|
|
2322
|
+
if (!this.$containerRef)
|
|
2323
|
+
return null;
|
|
2324
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
2325
|
+
const candidates = Array.from(this.$containerRef.querySelectorAll('[id]')).filter((el) => el.id !== 'top-scroll' && el.id !== 'bottom-scroll');
|
|
2326
|
+
let best = null;
|
|
2327
|
+
for (const el of candidates) {
|
|
2328
|
+
const rect = el.getBoundingClientRect();
|
|
2329
|
+
const isVisibleInContainer = rect.bottom > containerRect.top && rect.top < containerRect.bottom;
|
|
2330
|
+
if (!isVisibleInContainer)
|
|
2331
|
+
continue;
|
|
2332
|
+
if (edge === 'top') {
|
|
2333
|
+
const offsetTop = rect.top - containerRect.top;
|
|
2334
|
+
if (best == null || (best.edge === 'top' && offsetTop < best.offsetTop)) {
|
|
2335
|
+
best = { id: el.id, edge: 'top', offsetTop };
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
else {
|
|
2339
|
+
const offsetBottom = containerRect.bottom - rect.bottom;
|
|
2340
|
+
if (best == null || (best.edge === 'bottom' && offsetBottom < best.offsetBottom)) {
|
|
2341
|
+
best = { id: el.id, edge: 'bottom', offsetBottom };
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2335
2344
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2345
|
+
return best;
|
|
2346
|
+
}
|
|
2347
|
+
//instant scroll to anchor to make sure we are at the same position after a rerender
|
|
2348
|
+
restoreScrollToAnchor(anchor) {
|
|
2349
|
+
if (!this.$containerRef)
|
|
2350
|
+
return;
|
|
2351
|
+
// make element id safe to use inside a CSS selector
|
|
2352
|
+
const escapeId = (id) => {
|
|
2353
|
+
var _a;
|
|
2354
|
+
const cssEscape = (_a = globalThis.CSS) === null || _a === void 0 ? void 0 : _a.escape;
|
|
2355
|
+
return typeof cssEscape === 'function'
|
|
2356
|
+
? cssEscape(id)
|
|
2357
|
+
: id.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
|
|
2358
|
+
};
|
|
2359
|
+
const el = this.$containerRef.querySelector(`#${escapeId(anchor.id)}`);
|
|
2360
|
+
if (!el)
|
|
2361
|
+
return;
|
|
2362
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
2363
|
+
const rect = el.getBoundingClientRect();
|
|
2364
|
+
if (anchor.edge === 'top') {
|
|
2365
|
+
const newOffsetTop = rect.top - containerRect.top;
|
|
2366
|
+
this.$containerRef.scrollTop += newOffsetTop - anchor.offsetTop;
|
|
2344
2367
|
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
this.
|
|
2353
|
-
|
|
2368
|
+
else {
|
|
2369
|
+
const newOffsetBottom = containerRect.bottom - rect.bottom;
|
|
2370
|
+
this.$containerRef.scrollTop += anchor.offsetBottom - newOffsetBottom;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
// this method is called recursively based on shouldScrollToBottom (see loadNextPage)
|
|
2374
|
+
scrollToBottom() {
|
|
2375
|
+
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
2376
|
+
}
|
|
2377
|
+
waitForNextFrame() {
|
|
2378
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
2379
|
+
}
|
|
2380
|
+
rerender() {
|
|
2381
|
+
this.rerenderBoolean = !this.rerenderBoolean;
|
|
2354
2382
|
}
|
|
2355
2383
|
render() {
|
|
2356
2384
|
/**
|
|
2357
|
-
* div.container is flex=column-
|
|
2358
|
-
* which is why div#bottom-scroll comes before div#top-scroll
|
|
2385
|
+
* div.container is flex=column-reversewhich is why div#bottom-scroll comes before div#top-scroll
|
|
2359
2386
|
*/
|
|
2360
|
-
return (index$1.h(index$1.Host, { key: '
|
|
2387
|
+
return (index$1.h(index$1.Host, { key: '5f036ac16ace127734d5ee172d537c64baeab415' }, index$1.h("div", { key: 'b6d8cf3019a72350f7a3a5b4d020b6ab39793f53', class: "scrollbar container", part: "container", ref: (el) => (this.$containerRef = el) }, index$1.h("div", { key: '5c63462ffd995a3e266652bba4e3377636c5f9ca', class: { 'show-new-messages-ctr': true, active: this.showNewMessagesCTR } }, index$1.h("rtk-button", { key: 'c1fc4f2759d5be662047245b0dae3eb6f65a9b50', class: "show-new-messages", kind: "icon", variant: "secondary", part: "show-new-messages", onClick: () => {
|
|
2361
2388
|
this.shouldScrollToBottom = true;
|
|
2362
2389
|
this.scrollToBottom();
|
|
2363
|
-
} }, index$1.h("rtk-icon", { key: '
|
|
2390
|
+
} }, index$1.h("rtk-icon", { key: '96b19395a2ca8e87ca5004f675cf79f8d58f036c', icon: this.iconPack.chevron_down }))), index$1.h("div", { key: '84789a3d0fa4645be711a87cda1e109e4f7d0db2', class: "smallest-dom-element", id: "bottom-scroll", ref: (el) => (this.$bottomRef = el) }), this.isLoadingBottom && this.pages.length > 0 && index$1.h("rtk-spinner", { key: 'd17f28cc01695220ed6e705d528e8f555b77e8ea', size: "sm" }), this.isLoading && this.pages.length < 1 && index$1.h("rtk-spinner", { key: '4ee308d335cdc1f86e2bfdcc20611f50a51ef816', 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: 'c07c4dd4c4ed0adf01d2fc224b7196a0f86243fd', size: "sm" }), index$1.h("div", { key: '52161ac30062c2262f1cdbcded50f2716f6ed20e', class: "smallest-dom-element", id: "top-scroll", ref: (el) => (this.$topRef = el) }))));
|
|
2364
2391
|
}
|
|
2365
2392
|
};
|
|
2366
2393
|
__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;
|