Skip to content

Commit 8a9195a

Browse files
committed
Merge branch 'master' into 2503-offline-first-pwa-caching
2 parents 08e79b6 + a11b226 commit 8a9195a

File tree

17 files changed

+271
-75
lines changed

17 files changed

+271
-75
lines changed

frontend/controller/actions/group-kv.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default (sbp('sbp/selectors/register', {
5353
throw new Error('Unable to update lastLoggedIn without an active session')
5454
}
5555

56-
const now = sbp('chelonia/time') * 1000
56+
const now = sbp('chelonia/time')
5757
if (throttle) {
5858
const state = sbp('state/vuex/state')
5959
const lastLoggedInRawValue: ?string = state.lastLoggedIn?.[contractID]?.[identityContractID]

frontend/controller/actions/group.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export default (sbp('sbp/selectors/register', {
191191
quantity: MAX_GROUP_MEMBER_COUNT,
192192
...(INVITE_EXPIRES_IN_DAYS.ON_BOARDING && {
193193
expires:
194-
await sbp('chelonia/time') * 1000 + DAYS_MILLIS * INVITE_EXPIRES_IN_DAYS.ON_BOARDING
194+
await sbp('chelonia/time') + DAYS_MILLIS * INVITE_EXPIRES_IN_DAYS.ON_BOARDING
195195
}),
196196
private: {
197197
content: inviteKeyS
@@ -997,7 +997,7 @@ export default (sbp('sbp/selectors/register', {
997997
'gi.actions/group/fixAnyoneCanJoinLink': function ({ contractID }) {
998998
// Queue ensures that the update happens as atomically as possible
999999
return sbp('chelonia/queueInvocation', `${contractID}-FIX-ANYONE-CAN-JOIN`, async () => {
1000-
const now = await sbp('chelonia/time') * 1000
1000+
const now = await sbp('chelonia/time')
10011001
const state = await sbp('chelonia/contract/wait', contractID).then(() => sbp('chelonia/contract/state', contractID))
10021002

10031003
const quantity = doesGroupAnyoneCanJoinNeedUpdating(state)

frontend/model/contracts/shared/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const GROUP_PERMISSIONS_PRESET = {
107107
export const CHATROOM_GENERAL_NAME = 'general' // Chatroom name must be lowercase-only.
108108
export const CHATROOM_NAME_LIMITS_IN_CHARS = 50
109109
export const CHATROOM_DESCRIPTION_LIMITS_IN_CHARS = 280
110+
export const CHATROOM_REPLYING_MESSAGE_LIMITS_IN_CHARS = 180
110111
export const CHATROOM_MAX_MESSAGE_LEN = 20000
111112
export const CHATROOM_MAX_MESSAGES = 20
112113
export const CHATROOM_ACTIONS_PER_PAGE = 40

frontend/model/notifications/selectors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ sbp('sbp/selectors/register', {
3636
// Sets 'groupID' if this notification only pertains to a certain group.
3737
...(template.scope === 'group' ? { groupID: data.groupID } : {}),
3838
// Store integer timestamps rather than ISO strings here to make age comparisons easier.
39-
timestamp: new Date(data.createdDate ? data.createdDate : (sbp('chelonia/time') * 1000)).getTime(),
39+
timestamp: new Date(data.createdDate ? data.createdDate : sbp('chelonia/time')).getTime(),
4040
type
4141
}
4242
const rootState = sbp('chelonia/rootState')

frontend/model/settings/vuexModule.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const defaultSettings = {
3232
fontSize: 16,
3333
increasedContrast: false,
3434
notificationEnabled: null, // 3 values: null (unset), true (user-enabled), false (user-disabled)
35+
notificationVolume: 1,
3536
reducedMotion: false,
3637
theme: defaultTheme,
3738
themeColor: defaultColor
@@ -50,6 +51,9 @@ const getters = {
5051
isReducedMotionMode (state) {
5152
return state.reducedMotion === true
5253
},
54+
notificationVolume (state) {
55+
return state.notificationVolume
56+
},
5357
theme (state) {
5458
return state.theme
5559
}
@@ -89,6 +93,9 @@ const mutations = {
8993
console.error(`[setNotificationEnabled] Error calling setup-push-subscription (enabled=${enabled})`, e)
9094
})
9195
},
96+
setNotificationVolume (state, value) {
97+
state.notificationVolume = value
98+
},
9299
setReducedMotion (state, isChecked) {
93100
state.reducedMotion = isChecked
94101
},

frontend/model/state.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const initialState = {
8484
reverseNamespaceLookups: Object.create(null), // { [contractID]: username }
8585
contractSigningKeys: Object.create(null),
8686
lastLoggedIn: {}, // Group last logged in information
87-
preferences: {},
87+
preferences: {}, // { hideDistributionBanner: { [groupContractID]: boolean } }
8888
periodicNotificationAlreadyFiredMap: {
8989
alreadyFired: Object.create(null), // { notificationKey: boolean },
9090
lastRun: Object.create(null) // { notificationKey: number },

frontend/views/components/sounds/Background.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export default ({
1616
sbp('okTurtles.events/on', MESSAGE_SEND, this.playMessageSend)
1717
},
1818
computed: {
19+
volumeFromStore () {
20+
return this.$store.getters.notificationVolume ?? 1 // The volume value in the store can be 0 too and we use it if that's the case.
21+
},
1922
isAppIdle () {
2023
// NOTE: idle-vue plugin will provide this.isAppIdle
2124
// but sometimes it returns undefined, so redefine here
@@ -35,6 +38,19 @@ export default ({
3538
if (this.shouldPlay()) {
3639
this.$refs.msgSend.play()
3740
}
41+
},
42+
updateAudioVolumes () {
43+
this.$refs.msgReceive.volume = this.volumeFromStore
44+
this.$refs.msgSend.volume = this.volumeFromStore
45+
}
46+
},
47+
mounted () {
48+
this.updateAudioVolumes()
49+
},
50+
watch: {
51+
volumeFromStore () {
52+
// Update the audio elements accordingly when the volume change in the store is detected.
53+
this.updateAudioVolumes()
3854
}
3955
}
4056
}: Object)

frontend/views/containers/chatroom/ChatMain.vue

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
:isGroupCreator='isGroupCreator'
8787
:class='{removed: message.delete}'
8888
@retry='retryMessage(index)'
89-
@reply='replyMessage(message)'
89+
@reply='replyToMessage(message)'
9090
@scroll-to-replying-message='scrollToMessage(message.replyingMessage.hash)'
9191
@edit-message='(newMessage) => editMessage(message, newMessage)'
9292
@pin-to-channel='pinToChannel(message)'
@@ -143,13 +143,19 @@ import ViewArea from './ViewArea.vue'
143143
import Emoticons from './Emoticons.vue'
144144
import TouchLinkHelper from './TouchLinkHelper.vue'
145145
import DragActiveOverlay from './file-attachment/DragActiveOverlay.vue'
146-
import { MESSAGE_TYPES, MESSAGE_VARIANTS, CHATROOM_ACTIONS_PER_PAGE, CHATROOM_MEMBER_MENTION_SPECIAL_CHAR } from '@model/contracts/shared/constants.js'
146+
import {
147+
MESSAGE_TYPES, MESSAGE_VARIANTS,
148+
CHATROOM_ACTIONS_PER_PAGE,
149+
CHATROOM_MEMBER_MENTION_SPECIAL_CHAR,
150+
CHATROOM_REPLYING_MESSAGE_LIMITS_IN_CHARS
151+
} from '@model/contracts/shared/constants.js'
147152
import { CHATROOM_EVENTS, NEW_CHATROOM_UNREAD_POSITION, DELETE_ATTACHMENT_FEEDBACK } from '@utils/events.js'
148153
import { findMessageIdx } from '@model/contracts/shared/functions.js'
149154
import { proximityDate, MINS_MILLIS } from '@model/contracts/shared/time.js'
150155
import { cloneDeep, debounce, throttle, delay } from 'turtledash'
151156
import { EVENT_HANDLED } from '~/shared/domains/chelonia/events.js'
152157
import { compressImage } from '@utils/image.js'
158+
import { swapMentionIDForDisplayname } from '@model/chatroom/utils.js'
153159
154160
const ignorableScrollDistanceInPixel = 500
155161
@@ -253,14 +259,19 @@ export default ({
253259
// NOTE: messagesInitiated describes if the messages are fully re-rendered
254260
// according to this, we could display loading/skeleton component
255261
messagesInitiated: undefined,
256-
scrollHashOnInitialLoad: null, // Message hash to scroll to on chatroom's initial load
257262
replyingMessage: null,
258263
replyingTo: null,
259264
unprocessedEvents: [],
260265
261266
// Related to switching chatrooms
262267
chatroomIdToSwitchTo: null,
263-
renderingChatRoomId: null
268+
renderingChatRoomId: null,
269+
270+
// Related to auto-scrolling to initial position
271+
initialScroll: {
272+
hash: null,
273+
timeoutId: null
274+
}
264275
},
265276
messageState: {
266277
contract: {}
@@ -281,7 +292,7 @@ export default ({
281292
},
282293
mounted () {
283294
// setup various event listeners.
284-
this.ephemeral.onChatScroll = debounce(onChatScroll.bind(this), 500)
295+
this.ephemeral.onChatScroll = debounce(onChatScroll.bind(this), 300)
285296
sbp('okTurtles.events/on', EVENT_HANDLED, this.listenChatRoomActions)
286297
window.addEventListener('resize', this.resizeEventHandler)
287298
@@ -365,7 +376,9 @@ export default ({
365376
}
366377
},
367378
replyingMessageText (message) {
368-
return message.replyingMessage?.text || ''
379+
return message.replyingMessage?.text
380+
? message.replyingMessage.text.slice(0, CHATROOM_REPLYING_MESSAGE_LIMITS_IN_CHARS)
381+
: ''
369382
},
370383
time (strTime) {
371384
return new Date(strTime)
@@ -395,15 +408,7 @@ export default ({
395408
396409
const data = { type: MESSAGE_TYPES.TEXT, text }
397410
if (replyingMessage) {
398-
// NOTE: If not replying to a message, use original data; otherwise, append replyingMessage to data.
399411
data.replyingMessage = replyingMessage
400-
// NOTE: for the messages with only images, the text should be updated with file name
401-
if (!replyingMessage.text) {
402-
const msg = this.messages.find(m => (m.hash === replyingMessage.hash))
403-
if (msg) {
404-
data.replyingMessage.text = msg.attachments[0].name
405-
}
406-
}
407412
}
408413
409414
const sendMessage = (beforePrePublish) => {
@@ -590,7 +595,7 @@ export default ({
590595
}
591596
}
592597
},
593-
updateScroll (scrollTargetMessage = null, effect = false) {
598+
updateScroll (scrollTargetMessage = null, effect = false, delay = 100) {
594599
const contractID = this.ephemeral.renderingChatRoomId
595600
if (contractID) {
596601
return new Promise((resolve) => {
@@ -604,7 +609,7 @@ export default ({
604609
}
605610
606611
resolve()
607-
}, 100)
612+
}, delay)
608613
})
609614
}
610615
},
@@ -624,21 +629,30 @@ export default ({
624629
this.messages.splice(index, 1)
625630
this.handleSendMessage(message.text, message.attachments, message.replyingMessage)
626631
},
627-
replyMessage (message) {
628-
const { text, hash, type } = message
632+
replyToMessage (message) {
633+
const { text, hash, type, attachments } = message
634+
const isTypeInteractive = type === MESSAGE_TYPES.INTERACTIVE
629635
630-
if (type === MESSAGE_TYPES.INTERACTIVE) {
636+
if (isTypeInteractive) {
631637
const proposal = message.proposal
632638
633639
this.ephemeral.replyingMessage = {
634-
text: interactiveMessage(proposal, { from: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${proposal.creatorID}` }),
635-
hash
640+
hash, text: interactiveMessage(proposal, { from: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${proposal.creatorID}` })
636641
}
637-
this.ephemeral.replyingTo = L('Proposal notification')
642+
} else if (!text && attachments?.length) {
643+
this.ephemeral.replyingMessage = { hash, text: attachments[0].name }
638644
} else {
639-
this.ephemeral.replyingMessage = { text, hash }
640-
this.ephemeral.replyingTo = this.who(message)
645+
const sanitizeAndTruncate = text => {
646+
return swapMentionIDForDisplayname(text)
647+
.replace(/\s+/g, ' ') // Normalize spaces
648+
.trim()
649+
.slice(0, CHATROOM_REPLYING_MESSAGE_LIMITS_IN_CHARS)
650+
}
651+
652+
this.ephemeral.replyingMessage = { hash, text: sanitizeAndTruncate(text) }
641653
}
654+
655+
this.ephemeral.replyingTo = isTypeInteractive ? L('Proposal notification') : this.who(message)
642656
},
643657
editMessage (message, newMessage) {
644658
message.text = newMessage
@@ -863,7 +877,7 @@ export default ({
863877
864878
if (!this.ephemeral.messagesInitiated) {
865879
this.setStartNewMessageIndex()
866-
this.ephemeral.scrollHashOnInitialLoad = messageHashToScroll
880+
this.ephemeral.initialScroll.hash = messageHashToScroll
867881
}
868882
869883
return events.length > 0 && SPMessage.deserializeHEAD(events[0]).head.height === 0
@@ -889,6 +903,30 @@ export default ({
889903
Vue.set(this.messageState, 'contract', state)
890904
}
891905
},
906+
scrollToIntialPosition () {
907+
const hashTo = this.ephemeral.initialScroll.hash
908+
if (hashTo) {
909+
const scrollingToSpecificMessage = this.$route.query?.mhash === hashTo
910+
this.$nextTick(() => {
911+
this.updateScroll(
912+
hashTo,
913+
// NOTE: we do want the 'c-focused' animation if there is a scroll-to-message query.
914+
scrollingToSpecificMessage,
915+
0 // don't need the delay here
916+
)
917+
// NOTE: delete mhash in the query after scroll and highlight the message with mhash
918+
if (scrollingToSpecificMessage) {
919+
const newQuery = { ...this.$route.query }
920+
delete newQuery.mhash
921+
this.$router.replace({ query: newQuery })
922+
}
923+
924+
// Once scrolling is complete, reset the hash to null.
925+
// This ensures that 'auto-scroll to initial position' happens only once.
926+
this.ephemeral.initialScroll.hash = null
927+
})
928+
}
929+
},
892930
setStartNewMessageIndex () {
893931
this.ephemeral.startedUnreadMessageHash = null
894932
if (this.currentChatRoomReadUntil) {
@@ -1055,6 +1093,10 @@ export default ({
10551093
10561094
if (this.ephemeral.messagesInitiated === undefined) return
10571095
1096+
if (this.ephemeral.initialScroll.hash) {
1097+
clearTimeout(this.ephemeral.initialScroll.timeoutId)
1098+
}
1099+
10581100
const targetChatroomID = this.ephemeral.renderingChatRoomId
10591101
sbp('okTurtles.eventQueue/queueEvent', CHATROOM_EVENTS, async () => {
10601102
if (this.chatroomHasSwitchedFrom(targetChatroomID)) return
@@ -1087,29 +1129,20 @@ export default ({
10871129
}
10881130
10891131
if (completed !== undefined && !this.ephemeral.messagesInitiated) {
1090-
// NOTE: 'this.ephemeral.messagesInitiated' can be set true only when renderMoreMessages are successfully proceeded
1132+
// NOTE: 'this.ephemeral.messagesInitiated' can be set true only when renderMoreMessages are successfully proceeded
10911133
this.ephemeral.messagesInitiated = true
1092-
1093-
if (this.ephemeral.scrollHashOnInitialLoad) {
1094-
const scrollingToSpecificMessage = this.$route.query?.mhash === this.ephemeral.scrollHashOnInitialLoad
1095-
this.$nextTick(() => {
1096-
this.updateScroll(
1097-
this.ephemeral.scrollHashOnInitialLoad,
1098-
scrollingToSpecificMessage // NOTE: we do want the 'c-focused' animation if there is a scroll-to-message query.
1099-
)
1100-
// NOTE: delete mhash in the query after scroll and highlight the message with mhash
1101-
if (scrollingToSpecificMessage) {
1102-
const newQuery = { ...this.$route.query }
1103-
delete newQuery.mhash
1104-
this.$router.replace({ query: newQuery })
1105-
}
1106-
this.ephemeral.scrollHashOnInitialLoad = null
1107-
})
1108-
}
11091134
}
11101135
} catch (e) {
11111136
console.error('ChatMain infiniteHandler() error:', e)
11121137
}
1138+
1139+
if (this.ephemeral.messagesInitiated) {
1140+
// Sometimes even after 'messagesInitiated' is set to 'true', infiniteHandler() is called again and loads more messages.
1141+
// In that case, we should defer 'auto-scrolling to the initial-position' until those additional messages are rendered.
1142+
// This can be achieved by calling 'scrollToInitialPosition' here with setTimeout(),
1143+
// and calling clearTimeout() at the start of infiniteHandler().
1144+
this.ephemeral.initialScroll.timeoutId = setTimeout(this.scrollToIntialPosition, 150)
1145+
}
11131146
})
11141147
},
11151148
onChatScroll () {

frontend/views/containers/chatroom/MessageActionsMobile.vue

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@ export default ({
5252
isActive () {
5353
return this.Menu.isActive
5454
}
55-
},
56-
watch: {
57-
isActive (newVal) {
58-
console.log('!@# isActive: ', newVal)
59-
}
6055
}
6156
}: Object)
6257
</script>

frontend/views/containers/chatroom/MessageBase.vue

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,12 @@
2525
span.has-text-1 {{ humanDate(datetime, { hour: 'numeric', minute: 'numeric' }) }}
2626

2727
slot(name='body')
28-
template(v-if='replyingMessage')
29-
render-message-with-markdown(
30-
v-if='shouldRenderMarkdown'
31-
:isReplyingMessage='true'
32-
:text='replyingMessage'
33-
@click='$emit("reply-message-clicked")'
34-
)
35-
render-message-text.c-replying(
36-
v-else
37-
:isReplyingMessage='true'
38-
:text='replyingMessage'
39-
@click='$emit("reply-message-clicked")'
40-
)
28+
render-message-text.c-replying(
29+
v-if='replyingMessage'
30+
:isReplyingMessage='true'
31+
:text='replyingMessage'
32+
@click='$emit("reply-message-clicked")'
33+
)
4134

4235
send-area.c-edit-send-area(
4336
v-if='isEditing'

0 commit comments

Comments
 (0)