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