@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
package/dist/esm/loader.js
CHANGED
|
@@ -11045,6 +11045,13 @@ const RtkChat = class {
|
|
|
11045
11045
|
const message = event.detail;
|
|
11046
11046
|
this.meeting.chat.deleteMessage(message.id);
|
|
11047
11047
|
};
|
|
11048
|
+
this.onMessageEdit = (event) => {
|
|
11049
|
+
const message = event.detail;
|
|
11050
|
+
if (message.type !== 'text')
|
|
11051
|
+
return;
|
|
11052
|
+
this.replyMessage = null;
|
|
11053
|
+
this.editingMessage = message;
|
|
11054
|
+
};
|
|
11048
11055
|
this.getPrivateChatRecipients = () => {
|
|
11049
11056
|
const participants = this.getFilteredParticipants().map((participant) => {
|
|
11050
11057
|
const key = generateChatGroupKey([participant.userId, this.meeting.self.userId]);
|
|
@@ -11229,14 +11236,21 @@ const RtkChat = class {
|
|
|
11229
11236
|
const uiProps = { iconPack: this.iconPack, t: this.t, size: this.size };
|
|
11230
11237
|
const message = this.editingMessage ? this.editingMessage.message : '';
|
|
11231
11238
|
const quotedMessage = this.replyMessage ? this.replyMessage.message : '';
|
|
11232
|
-
|
|
11239
|
+
const draftStorageKey = this.selectedChannelId
|
|
11240
|
+
? `rtk-chat-draft-${this.selectedChannelId}`
|
|
11241
|
+
: 'rtk-chat-draft';
|
|
11242
|
+
const editStorageKey = this.editingMessage
|
|
11243
|
+
? `rtk-chat-edit-${(_a = this.selectedChannelId) !== null && _a !== void 0 ? _a : 'no-channel'}-${this.editingMessage.id}`
|
|
11244
|
+
: 'rtk-chat-edit';
|
|
11245
|
+
const storageKey = this.editingMessage ? editStorageKey : draftStorageKey;
|
|
11246
|
+
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" })));
|
|
11233
11247
|
}
|
|
11234
11248
|
render() {
|
|
11235
11249
|
var _a;
|
|
11236
11250
|
if (!this.meeting) {
|
|
11237
11251
|
return null;
|
|
11238
11252
|
}
|
|
11239
|
-
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()))));
|
|
11253
|
+
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()))));
|
|
11240
11254
|
}
|
|
11241
11255
|
get host() { return getElement(this); }
|
|
11242
11256
|
static get watchers() { return {
|
|
@@ -11479,6 +11493,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
11479
11493
|
registerInstance(this, hostRef);
|
|
11480
11494
|
this.editMessageInit = createEvent(this, "editMessageInit", 7);
|
|
11481
11495
|
this.onPinMessage = createEvent(this, "pinMessage", 7);
|
|
11496
|
+
this.onEditMessage = createEvent(this, "editMessage", 7);
|
|
11482
11497
|
this.onDeleteMessage = createEvent(this, "deleteMessage", 7);
|
|
11483
11498
|
this.stateUpdate = createEvent(this, "rtkStateUpdate", 7);
|
|
11484
11499
|
/** Icon pack */
|
|
@@ -11529,22 +11544,18 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
11529
11544
|
};
|
|
11530
11545
|
this.getMessageActions = (message) => {
|
|
11531
11546
|
const actions = [];
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
const canDelete = message.userId === this.meeting.self.userId;
|
|
11540
|
-
if (this.meeting.self.permissions.pinParticipant) {
|
|
11547
|
+
const messageBelongsToSelf = message.userId === this.meeting.self.userId;
|
|
11548
|
+
actions.push({
|
|
11549
|
+
id: 'pin_message',
|
|
11550
|
+
label: message.pinned ? this.t('unpin') : this.t('pin'),
|
|
11551
|
+
icon: this.iconPack.pin,
|
|
11552
|
+
});
|
|
11553
|
+
if (messageBelongsToSelf) {
|
|
11541
11554
|
actions.push({
|
|
11542
|
-
id: '
|
|
11543
|
-
label:
|
|
11544
|
-
icon: this.iconPack.
|
|
11555
|
+
id: 'edit_message',
|
|
11556
|
+
label: this.t('chat.edit_msg'),
|
|
11557
|
+
icon: this.iconPack.edit,
|
|
11545
11558
|
});
|
|
11546
|
-
}
|
|
11547
|
-
if (canDelete) {
|
|
11548
11559
|
actions.push({
|
|
11549
11560
|
id: 'delete_message',
|
|
11550
11561
|
label: this.t('chat.delete_msg'),
|
|
@@ -11558,6 +11569,9 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
11558
11569
|
case 'pin_message':
|
|
11559
11570
|
this.onPinMessage.emit(message);
|
|
11560
11571
|
break;
|
|
11572
|
+
case 'edit_message':
|
|
11573
|
+
this.onEditMessage.emit(message);
|
|
11574
|
+
break;
|
|
11561
11575
|
case 'delete_message':
|
|
11562
11576
|
this.onDeleteMessage.emit(message);
|
|
11563
11577
|
break;
|
|
@@ -11586,7 +11600,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
11586
11600
|
}
|
|
11587
11601
|
const isSelf = message.userId === this.meeting.self.userId;
|
|
11588
11602
|
const viewType = isSelf ? 'outgoing' : 'incoming';
|
|
11589
|
-
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: () => {
|
|
11603
|
+
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: () => {
|
|
11590
11604
|
this.stateUpdate.emit({ image: message });
|
|
11591
11605
|
} }))))))));
|
|
11592
11606
|
};
|
|
@@ -11634,7 +11648,7 @@ const RtkChatMessagesUiPaginated = class {
|
|
|
11634
11648
|
this.lastReadMessageIndex = -1;
|
|
11635
11649
|
}
|
|
11636
11650
|
render() {
|
|
11637
|
-
return (h(Host, { key: '
|
|
11651
|
+
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' }))));
|
|
11638
11652
|
}
|
|
11639
11653
|
get host() { return getElement(this); }
|
|
11640
11654
|
static get watchers() { return {
|
|
@@ -12590,32 +12604,30 @@ const rtkPaginatedListCss = ".scrollbar{scrollbar-width:thin;scrollbar-color:var
|
|
|
12590
12604
|
const RtkPaginatedListStyle0 = rtkPaginatedListCss;
|
|
12591
12605
|
|
|
12592
12606
|
/**
|
|
12593
|
-
*
|
|
12594
|
-
*
|
|
12595
|
-
* We use intersectionObserver to scroll up.
|
|
12596
|
-
* We use scrollEnd listener to scroll down.
|
|
12597
|
-
*
|
|
12598
|
-
* Why?
|
|
12599
|
-
* intersectionObserver doesn't work reliably for 2 way scrolling but has great ux,
|
|
12600
|
-
* so we use it to smoothly scroll up.
|
|
12607
|
+
* NOTE(ikabra): INFINITE SCROLL IMPLEMENTATION:
|
|
12601
12608
|
*
|
|
12602
|
-
*
|
|
12603
|
-
*
|
|
12609
|
+
* Uses scrollend listener for 2way scrolling.
|
|
12610
|
+
* Empty divs ($topRef, $bottomRef) act as scroll triggers to fetch new messages.
|
|
12604
12611
|
*
|
|
12605
|
-
*
|
|
12606
|
-
*
|
|
12607
|
-
*
|
|
12608
|
-
*
|
|
12612
|
+
* UPWARD SCROLLING:
|
|
12613
|
+
* - Fetch top anchor (element currently visible to the user near top)
|
|
12614
|
+
* - Fetch older messages, push to end of 2D array
|
|
12615
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
12609
12616
|
*
|
|
12610
|
-
*
|
|
12611
|
-
*
|
|
12612
|
-
*
|
|
12617
|
+
* DOWNWARD SCROLLING:
|
|
12618
|
+
* - Fetch bottom anchor (element currently visible to the user near bottom)
|
|
12619
|
+
* - Fetch new page, insert at the start
|
|
12620
|
+
* - Update timestamps & firstEmptyIndex, then rerender
|
|
12621
|
+
* - When exceeding pagesAllowed, delete pages and scroll back to anchor
|
|
12613
12622
|
*
|
|
12614
|
-
*
|
|
12623
|
+
* ADDING NEW NODES:
|
|
12624
|
+
* - If no pages exist, load old page
|
|
12625
|
+
* - If on 1st page, append messages till page size is full and then load new page
|
|
12615
12626
|
*
|
|
12616
|
-
*
|
|
12617
|
-
*
|
|
12618
|
-
*
|
|
12627
|
+
* DELETE NODE:
|
|
12628
|
+
* - If deleting the only available node, reset to initial state
|
|
12629
|
+
* - If page is empty, delete it
|
|
12630
|
+
* - Update timestamp curors
|
|
12619
12631
|
*/
|
|
12620
12632
|
var __decorate$2$8 = function (decorators, target, key, desc) {
|
|
12621
12633
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -12630,43 +12642,27 @@ var __decorate$2$8 = function (decorators, target, key, desc) {
|
|
|
12630
12642
|
const RtkPaginatedList = class {
|
|
12631
12643
|
constructor(hostRef) {
|
|
12632
12644
|
registerInstance(this, hostRef);
|
|
12633
|
-
/**
|
|
12634
|
-
* when scrolling up, we can't remove pages as intersectionObserver relies on
|
|
12635
|
-
* the index of dom elements to stay stable.
|
|
12636
|
-
* So, instead we free up the pages and keep the last empty index in memory.
|
|
12637
|
-
*/
|
|
12638
|
-
this.firstEmptyIndex = -1;
|
|
12639
|
-
this.maxTS = 0;
|
|
12640
12645
|
// the length of pages will always be pageSize + 2
|
|
12641
12646
|
this.pages = [];
|
|
12647
|
+
// Controls whether to keep auto-scrolling when a new page load.
|
|
12648
|
+
this.shouldScrollToBottom = false;
|
|
12649
|
+
// Shows "scroll to bottom" button when new nodes arrive and autoscroll is off.
|
|
12650
|
+
this.showNewMessagesCTR = false;
|
|
12642
12651
|
/** label to show when empty */
|
|
12643
12652
|
this.emptyListLabel = null;
|
|
12644
|
-
this.rerenderBoolean = false;
|
|
12645
|
-
this.showEmptyListLabel = false;
|
|
12646
12653
|
/** Icon pack */
|
|
12647
12654
|
this.iconPack = defaultIconPack;
|
|
12648
12655
|
/** Language */
|
|
12649
12656
|
this.t = useLanguage();
|
|
12657
|
+
this.rerenderBoolean = false;
|
|
12658
|
+
this.showEmptyListLabel = false;
|
|
12650
12659
|
this.isLoading = false;
|
|
12651
12660
|
this.isLoadingTop = false;
|
|
12652
12661
|
this.isLoadingBottom = false;
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
* */
|
|
12658
|
-
this.shouldScrollToBottom = false;
|
|
12659
|
-
/** UI Indicator for the "scroll to bottom" button.
|
|
12660
|
-
* Toggles on when a new node is added and autoscroll is disabled.
|
|
12661
|
-
* Toggles off when all nodes are loaded */
|
|
12662
|
-
this.showNewMessagesCTR = false;
|
|
12663
|
-
this.observe = (el) => {
|
|
12664
|
-
if (!el)
|
|
12665
|
-
return;
|
|
12666
|
-
this.intersectionObserver.observe(el);
|
|
12667
|
-
};
|
|
12668
|
-
this.isAtBottom = () => {
|
|
12669
|
-
const rect = this.$bottomRef.getBoundingClientRect();
|
|
12662
|
+
// Tells us if we need to scroll to a specific anchor after a rerender
|
|
12663
|
+
this.pendingScrollAnchor = null;
|
|
12664
|
+
this.isInView = (el) => {
|
|
12665
|
+
const rect = el.getBoundingClientRect();
|
|
12670
12666
|
return rect.top >= 0 && rect.bottom <= window.innerHeight;
|
|
12671
12667
|
};
|
|
12672
12668
|
}
|
|
@@ -12675,224 +12671,255 @@ const RtkPaginatedList = class {
|
|
|
12675
12671
|
* @param {DataNode} node - The data node to add to the beginning of the list
|
|
12676
12672
|
*/
|
|
12677
12673
|
async onNewNode(node) {
|
|
12678
|
-
//
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12674
|
+
// if there are no pages, load the first page
|
|
12675
|
+
if (this.pages.length < 1) {
|
|
12676
|
+
this.oldTS = node.timeMs + 1;
|
|
12677
|
+
this.loadPrevPage();
|
|
12678
|
+
}
|
|
12679
|
+
else if (this.maxTS === this.newTS) {
|
|
12680
|
+
this.maxTS = node.timeMs;
|
|
12681
|
+
// append messages to the page if page has not reached full capacity
|
|
12682
|
+
if (this.pages[0].length < this.pageSize) {
|
|
12683
|
+
this.pages[0].unshift(node);
|
|
12684
|
+
this.newTS = node.timeMs;
|
|
12685
|
+
this.rerender();
|
|
12687
12686
|
}
|
|
12688
12687
|
else {
|
|
12689
|
-
//
|
|
12690
|
-
|
|
12691
|
-
this.pages[0].unshift(node);
|
|
12692
|
-
this.newTS = node.timeMs;
|
|
12693
|
-
this.rerender();
|
|
12694
|
-
}
|
|
12695
|
-
else {
|
|
12696
|
-
// if page is at full capacity, load next page
|
|
12697
|
-
this.loadNextPage();
|
|
12698
|
-
}
|
|
12688
|
+
// if page is at full capacity, load next page
|
|
12689
|
+
this.loadNextPage();
|
|
12699
12690
|
}
|
|
12700
12691
|
}
|
|
12701
|
-
// If autoscroll is enabled,
|
|
12692
|
+
// If autoscroll is enabled, scroll to the bottom
|
|
12702
12693
|
if (this.autoScroll) {
|
|
12703
12694
|
this.shouldScrollToBottom = true;
|
|
12704
12695
|
this.scrollToBottom();
|
|
12705
12696
|
}
|
|
12706
|
-
else {
|
|
12707
|
-
this.showNewMessagesCTR = true;
|
|
12708
|
-
}
|
|
12709
|
-
}
|
|
12710
|
-
// this method is called recursively based on shouldScrollToBottom (see scrollEnd listener)
|
|
12711
|
-
scrollToBottom() {
|
|
12712
|
-
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
12713
12697
|
}
|
|
12714
12698
|
/**
|
|
12715
12699
|
* Deletes a node anywhere from the list
|
|
12716
12700
|
* @param {string} id - The id of the node to delete
|
|
12717
12701
|
* */
|
|
12718
12702
|
async onNodeDelete(id) {
|
|
12719
|
-
|
|
12720
|
-
|
|
12703
|
+
var _a, _b;
|
|
12704
|
+
let didDelete = false;
|
|
12705
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
12721
12706
|
const index = this.pages[i].findIndex((node) => node.id === id);
|
|
12722
|
-
// message
|
|
12723
|
-
if (index
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
// if newest page is empty, update first empty index
|
|
12733
|
-
if (this.pages[i].length === 0)
|
|
12734
|
-
this.firstEmptyIndex++;
|
|
12735
|
-
// update timestamp, first empty index could be -1, so we need to cap it at 0
|
|
12736
|
-
this.newTS = this.pages[Math.max(this.firstEmptyIndex, 0)][0].timeMs;
|
|
12737
|
-
}
|
|
12738
|
-
else if (i === this.firstEmptyIndex + this.pagesAllowed) {
|
|
12739
|
-
// if oldest page is empty, remove it
|
|
12740
|
-
if (this.pages[i].length === 0)
|
|
12741
|
-
this.pages.pop();
|
|
12742
|
-
// update timestamp
|
|
12743
|
-
const lastPage = this.pages[this.firstEmptyIndex + this.pagesAllowed];
|
|
12744
|
-
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
12745
|
-
}
|
|
12746
|
-
this.rerender();
|
|
12747
|
-
}
|
|
12707
|
+
// if message not found, move on
|
|
12708
|
+
if (index === -1)
|
|
12709
|
+
continue;
|
|
12710
|
+
// delete message
|
|
12711
|
+
this.pages[i].splice(index, 1);
|
|
12712
|
+
// if page is empty, delete it
|
|
12713
|
+
if (this.pages[i].length === 0)
|
|
12714
|
+
this.pages.splice(i, 1);
|
|
12715
|
+
didDelete = true;
|
|
12716
|
+
break;
|
|
12748
12717
|
}
|
|
12718
|
+
if (!didDelete)
|
|
12719
|
+
return;
|
|
12720
|
+
// update timestamps
|
|
12721
|
+
const firstPage = this.pages[0];
|
|
12722
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
12723
|
+
this.newTS = (_a = firstPage === null || firstPage === void 0 ? void 0 : firstPage[0]) === null || _a === void 0 ? void 0 : _a.timeMs;
|
|
12724
|
+
this.oldTS = (_b = lastPage === null || lastPage === void 0 ? void 0 : lastPage[lastPage.length - 1]) === null || _b === void 0 ? void 0 : _b.timeMs;
|
|
12725
|
+
this.rerender();
|
|
12749
12726
|
}
|
|
12750
12727
|
/**
|
|
12751
12728
|
* Updates a new node anywhere in the list
|
|
12752
|
-
* @param {string}
|
|
12753
|
-
* @param {DataNode}
|
|
12729
|
+
* @param {string} id - The id of the node to update
|
|
12730
|
+
* @param {DataNode} node - The updated data node
|
|
12754
12731
|
* */
|
|
12755
|
-
async onNodeUpdate(
|
|
12756
|
-
|
|
12757
|
-
|
|
12732
|
+
async onNodeUpdate(id, node) {
|
|
12733
|
+
for (let i = this.pages.length - 1; i >= 0; i--) {
|
|
12734
|
+
const index = this.pages[i].findIndex((node) => node.id === id);
|
|
12735
|
+
// if message not found, move on
|
|
12736
|
+
if (index === -1)
|
|
12737
|
+
continue;
|
|
12738
|
+
// edit message
|
|
12739
|
+
this.pages[i][index] = node;
|
|
12740
|
+
this.rerender();
|
|
12741
|
+
break;
|
|
12742
|
+
}
|
|
12758
12743
|
}
|
|
12759
12744
|
connectedCallback() {
|
|
12760
12745
|
this.rerender = debounce$1(this.rerender.bind(this), 50, { maxWait: 200 });
|
|
12761
|
-
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
12762
|
-
writeTask(async () => {
|
|
12763
|
-
for (const entry of entries) {
|
|
12764
|
-
if (entry.target.id === 'top-scroll' && entry.isIntersecting) {
|
|
12765
|
-
this.isLoadingTop = true;
|
|
12766
|
-
await this.loadPrevPage();
|
|
12767
|
-
this.isLoadingTop = false;
|
|
12768
|
-
}
|
|
12769
|
-
}
|
|
12770
|
-
});
|
|
12771
|
-
});
|
|
12772
12746
|
}
|
|
12773
12747
|
componentDidLoad() {
|
|
12774
|
-
|
|
12748
|
+
// initial load
|
|
12749
|
+
this.loadPrevPage();
|
|
12775
12750
|
if (this.$containerRef) {
|
|
12776
12751
|
this.$containerRef.onscrollend = async () => {
|
|
12777
|
-
|
|
12778
|
-
* Load new page if:
|
|
12779
|
-
* if there are nodes to load at the bottom (maxTS > newTS)
|
|
12780
|
-
* or if there are pages to fill at the bottom (firstEmptyIndex > -1)
|
|
12781
|
-
*/
|
|
12782
|
-
if (this.isAtBottom() && (this.maxTS > this.newTS || this.firstEmptyIndex > -1)) {
|
|
12783
|
-
this.isLoadingBottom = true;
|
|
12752
|
+
if (this.isInView(this.$bottomRef)) {
|
|
12784
12753
|
await this.loadNextPage();
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12754
|
+
}
|
|
12755
|
+
else if (this.isInView(this.$topRef)) {
|
|
12756
|
+
this.showNewMessagesCTR = true;
|
|
12757
|
+
await this.loadPrevPage();
|
|
12788
12758
|
}
|
|
12789
12759
|
};
|
|
12790
12760
|
}
|
|
12791
12761
|
}
|
|
12762
|
+
componentDidRender() {
|
|
12763
|
+
if (!this.pendingScrollAnchor)
|
|
12764
|
+
return;
|
|
12765
|
+
const anchor = this.pendingScrollAnchor;
|
|
12766
|
+
this.pendingScrollAnchor = null;
|
|
12767
|
+
this.restoreScrollToAnchor(anchor);
|
|
12768
|
+
}
|
|
12792
12769
|
async loadPrevPage() {
|
|
12793
12770
|
if (this.isLoading)
|
|
12794
12771
|
return;
|
|
12795
|
-
|
|
12796
|
-
* NOTE(ikabra): this case also runs on initial load
|
|
12797
|
-
* if scrolling up ->
|
|
12798
|
-
* fetch older messages and push to the end of the array
|
|
12799
|
-
* cleanup 1st non empty page from the array if length exceeds pagesAllowed
|
|
12800
|
-
*/
|
|
12772
|
+
const scrollAnchor = this.getScrollAnchor('top');
|
|
12801
12773
|
// if no old timestamp, it means we are at initial state
|
|
12802
12774
|
if (!this.oldTS)
|
|
12803
12775
|
this.oldTS = new Date().getTime();
|
|
12804
12776
|
// load data
|
|
12805
12777
|
this.isLoading = true;
|
|
12778
|
+
this.isLoadingTop = true;
|
|
12806
12779
|
const data = await this.fetchData(this.oldTS - 1, this.pageSize, true);
|
|
12807
12780
|
this.isLoading = false;
|
|
12781
|
+
this.isLoadingTop = false;
|
|
12808
12782
|
// no more old messages to show, we are at the top of the page
|
|
12809
12783
|
if (!data.length)
|
|
12810
12784
|
return;
|
|
12811
12785
|
// add old data to the end of the array
|
|
12812
12786
|
this.pages.push(data);
|
|
12813
12787
|
// clear old pages when we reach the limit
|
|
12814
|
-
if (this.pages.length > this.pagesAllowed)
|
|
12815
|
-
this.pages
|
|
12816
|
-
|
|
12817
|
-
* find last non empty page in range (this.pages.length, this.firstEmptyIndex)
|
|
12818
|
-
* we are doing this because any of the middle pages in the currently rendered pages
|
|
12819
|
-
* could be empty as we allow deleting messages.
|
|
12820
|
-
* This helps us set the first empty index correctly.
|
|
12821
|
-
*/
|
|
12822
|
-
for (let i = this.firstEmptyIndex + 1; i < this.pages.length; i++) {
|
|
12823
|
-
if (this.pages[i].length > 0)
|
|
12824
|
-
break;
|
|
12825
|
-
this.firstEmptyIndex = i;
|
|
12826
|
-
}
|
|
12827
|
-
}
|
|
12828
|
-
// update the old timestamp
|
|
12788
|
+
if (this.pages.length > this.pagesAllowed)
|
|
12789
|
+
this.pages.shift();
|
|
12790
|
+
// update timestamps
|
|
12829
12791
|
const lastPage = this.pages[this.pages.length - 1];
|
|
12830
12792
|
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
12831
|
-
|
|
12832
|
-
|
|
12793
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
12794
|
+
if (!this.maxTS)
|
|
12795
|
+
this.maxTS = this.newTS;
|
|
12833
12796
|
this.rerender();
|
|
12797
|
+
// fix scroll position
|
|
12798
|
+
if (scrollAnchor)
|
|
12799
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
12834
12800
|
}
|
|
12835
12801
|
async loadNextPage() {
|
|
12836
12802
|
if (this.isLoading)
|
|
12837
12803
|
return;
|
|
12838
|
-
//
|
|
12804
|
+
// Do nothing. New timestamp needs to be assigned by loadPrevPage method
|
|
12839
12805
|
if (!this.newTS) {
|
|
12840
12806
|
this.showNewMessagesCTR = false;
|
|
12841
12807
|
this.shouldScrollToBottom = false;
|
|
12842
12808
|
return;
|
|
12843
12809
|
}
|
|
12844
|
-
//
|
|
12810
|
+
// for autoscroll or scroll to bottom button
|
|
12811
|
+
const maxAutoLoads = 200;
|
|
12812
|
+
let loads = 0;
|
|
12813
|
+
let prevNewTS = this.newTS;
|
|
12845
12814
|
this.isLoading = true;
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
this.
|
|
12851
|
-
this.
|
|
12852
|
-
//
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
}
|
|
12861
|
-
else {
|
|
12862
|
-
// when already at the bottom and loading messages in realtime
|
|
12815
|
+
this.isLoadingBottom = true;
|
|
12816
|
+
while (loads < maxAutoLoads) {
|
|
12817
|
+
const scrollAnchor = this.getScrollAnchor('bottom');
|
|
12818
|
+
const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
|
|
12819
|
+
this.isLoading = false;
|
|
12820
|
+
this.isLoadingBottom = false;
|
|
12821
|
+
// no more new messages to load
|
|
12822
|
+
if (!data.length) {
|
|
12823
|
+
this.maxTS = this.newTS;
|
|
12824
|
+
this.showNewMessagesCTR = false;
|
|
12825
|
+
this.shouldScrollToBottom = false;
|
|
12826
|
+
break;
|
|
12827
|
+
}
|
|
12828
|
+
// load new messages and append to the start
|
|
12863
12829
|
this.pages.unshift(data.reverse());
|
|
12830
|
+
// remove pages if out of bounds
|
|
12831
|
+
if (this.pages.length > this.pagesAllowed)
|
|
12832
|
+
this.pages.pop();
|
|
12833
|
+
// update timestamps
|
|
12834
|
+
const lastPage = this.pages[this.pages.length - 1];
|
|
12835
|
+
this.oldTS = lastPage[lastPage.length - 1].timeMs;
|
|
12836
|
+
this.newTS = this.pages[0][0].timeMs;
|
|
12837
|
+
this.rerender();
|
|
12838
|
+
this.pendingScrollAnchor = scrollAnchor;
|
|
12839
|
+
if (!this.shouldScrollToBottom)
|
|
12840
|
+
break;
|
|
12841
|
+
// if should scroll to bottom then retrigger
|
|
12842
|
+
await this.waitForNextFrame();
|
|
12843
|
+
this.scrollToBottom();
|
|
12844
|
+
await this.waitForNextFrame();
|
|
12845
|
+
// if no new messages, break
|
|
12846
|
+
if (this.newTS === prevNewTS)
|
|
12847
|
+
break;
|
|
12848
|
+
prevNewTS = this.newTS;
|
|
12849
|
+
loads++;
|
|
12864
12850
|
}
|
|
12865
|
-
|
|
12866
|
-
|
|
12851
|
+
}
|
|
12852
|
+
// Find the element that is closest to the top/bottom of the container
|
|
12853
|
+
getScrollAnchor(edge = 'top') {
|
|
12854
|
+
if (!this.$containerRef)
|
|
12855
|
+
return null;
|
|
12856
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
12857
|
+
const candidates = Array.from(this.$containerRef.querySelectorAll('[id]')).filter((el) => el.id !== 'top-scroll' && el.id !== 'bottom-scroll');
|
|
12858
|
+
let best = null;
|
|
12859
|
+
for (const el of candidates) {
|
|
12860
|
+
const rect = el.getBoundingClientRect();
|
|
12861
|
+
const isVisibleInContainer = rect.bottom > containerRect.top && rect.top < containerRect.bottom;
|
|
12862
|
+
if (!isVisibleInContainer)
|
|
12863
|
+
continue;
|
|
12864
|
+
if (edge === 'top') {
|
|
12865
|
+
const offsetTop = rect.top - containerRect.top;
|
|
12866
|
+
if (best == null || (best.edge === 'top' && offsetTop < best.offsetTop)) {
|
|
12867
|
+
best = { id: el.id, edge: 'top', offsetTop };
|
|
12868
|
+
}
|
|
12869
|
+
}
|
|
12870
|
+
else {
|
|
12871
|
+
const offsetBottom = containerRect.bottom - rect.bottom;
|
|
12872
|
+
if (best == null || (best.edge === 'bottom' && offsetBottom < best.offsetBottom)) {
|
|
12873
|
+
best = { id: el.id, edge: 'bottom', offsetBottom };
|
|
12874
|
+
}
|
|
12875
|
+
}
|
|
12867
12876
|
}
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12877
|
+
return best;
|
|
12878
|
+
}
|
|
12879
|
+
//instant scroll to anchor to make sure we are at the same position after a rerender
|
|
12880
|
+
restoreScrollToAnchor(anchor) {
|
|
12881
|
+
if (!this.$containerRef)
|
|
12882
|
+
return;
|
|
12883
|
+
// make element id safe to use inside a CSS selector
|
|
12884
|
+
const escapeId = (id) => {
|
|
12885
|
+
var _a;
|
|
12886
|
+
const cssEscape = (_a = globalThis.CSS) === null || _a === void 0 ? void 0 : _a.escape;
|
|
12887
|
+
return typeof cssEscape === 'function'
|
|
12888
|
+
? cssEscape(id)
|
|
12889
|
+
: id.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
|
|
12890
|
+
};
|
|
12891
|
+
const el = this.$containerRef.querySelector(`#${escapeId(anchor.id)}`);
|
|
12892
|
+
if (!el)
|
|
12893
|
+
return;
|
|
12894
|
+
const containerRect = this.$containerRef.getBoundingClientRect();
|
|
12895
|
+
const rect = el.getBoundingClientRect();
|
|
12896
|
+
if (anchor.edge === 'top') {
|
|
12897
|
+
const newOffsetTop = rect.top - containerRect.top;
|
|
12898
|
+
this.$containerRef.scrollTop += newOffsetTop - anchor.offsetTop;
|
|
12876
12899
|
}
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
this.
|
|
12885
|
-
|
|
12900
|
+
else {
|
|
12901
|
+
const newOffsetBottom = containerRect.bottom - rect.bottom;
|
|
12902
|
+
this.$containerRef.scrollTop += anchor.offsetBottom - newOffsetBottom;
|
|
12903
|
+
}
|
|
12904
|
+
}
|
|
12905
|
+
// this method is called recursively based on shouldScrollToBottom (see loadNextPage)
|
|
12906
|
+
scrollToBottom() {
|
|
12907
|
+
this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
|
|
12908
|
+
}
|
|
12909
|
+
waitForNextFrame() {
|
|
12910
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
12911
|
+
}
|
|
12912
|
+
rerender() {
|
|
12913
|
+
this.rerenderBoolean = !this.rerenderBoolean;
|
|
12886
12914
|
}
|
|
12887
12915
|
render() {
|
|
12888
12916
|
/**
|
|
12889
|
-
* div.container is flex=column-
|
|
12890
|
-
* which is why div#bottom-scroll comes before div#top-scroll
|
|
12917
|
+
* div.container is flex=column-reversewhich is why div#bottom-scroll comes before div#top-scroll
|
|
12891
12918
|
*/
|
|
12892
|
-
return (h(Host, { key: '
|
|
12919
|
+
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: () => {
|
|
12893
12920
|
this.shouldScrollToBottom = true;
|
|
12894
12921
|
this.scrollToBottom();
|
|
12895
|
-
} }, h("rtk-icon", { key: '
|
|
12922
|
+
} }, 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) }))));
|
|
12896
12923
|
}
|
|
12897
12924
|
};
|
|
12898
12925
|
__decorate$2$8([
|
|
@@ -15418,7 +15445,7 @@ const RtkChatToggle = class {
|
|
|
15418
15445
|
const newMessages = messages.filter((m) => m.timeMs > meetingStartedTimeMs);
|
|
15419
15446
|
if (newMessages.length === this.pageSize && newMessages.length > 0) {
|
|
15420
15447
|
// all messages are new, so we can't know the exact count, but we know there are at least pageSize - 1 new messages
|
|
15421
|
-
this.unreadMessageCount =
|
|
15448
|
+
this.unreadMessageCount = newMessages.length;
|
|
15422
15449
|
}
|
|
15423
15450
|
else {
|
|
15424
15451
|
this.unreadMessageCount = newMessages.length;
|
|
@@ -18708,7 +18735,10 @@ const RtkNotifications = class {
|
|
|
18708
18735
|
this.waitlistedParticipantLeftListener = (participant) => {
|
|
18709
18736
|
this.remove(`${participant.id}-joined-waitlist`);
|
|
18710
18737
|
};
|
|
18711
|
-
this.chatUpdateListener = ({ message }) => {
|
|
18738
|
+
this.chatUpdateListener = ({ message, action }) => {
|
|
18739
|
+
// NOTE(ikabra): we only want notifications for new messages
|
|
18740
|
+
if (action !== 'add')
|
|
18741
|
+
return;
|
|
18712
18742
|
const parsedMessage = parseMessageForTarget(message);
|
|
18713
18743
|
if (parsedMessage != null) {
|
|
18714
18744
|
if (parsedMessage.userId === meeting.self.userId) {
|