Adding New Features to Existing Themes

Often, new features add new elements to our chat UI. But since the UI is largely rendered using themes, you may need to update your theme to take advantage of new features. If a component in your theme is unchanged from the default, then it is automatically kept up to date. But if a component has been changed, your changed version will be rendered, without the additions we introduced. Below, you'll find an overview of new features, and the changes you'd need to make in your theme to take advantage of them.

Voice messages (2022-10-03)

We added support for playing voice messages directly within TalkJS. Within our themes, this uses the existing mechanism for displaying attachments so it works out of the box, but if you've created a theme before 2022-10-03, you may want to apply these changes to the MessageBody component, in order to differenciate voice messages from regular audio files:

- <span t:else-if="{{ body.type == 'file' and body.hasThumbnail }}" class="thumbnail {{ body.file.type }}">
+ <span t:else-if="{{ body.type == 'file' and body.hasThumbnail }}" class="thumbnail {{ body.file.type }} {{body.thumbnailError | then: 'has-error'}}">
<Thumbnail file="{{ body.file }}" gradients="{{darkenMenuArea | then: 'top-right'}}"/>
</span>
- <div class="text timestamp-float-{{ floatTimestamp }}">
+ <div t:if="{{ body.type != 'file' or body.isVoiceMessage == false or body.thumbnailError == true }}" class="text timestamp-float-{{ floatTimestamp }}">
<span t:if="{{ isLongEmailMessage }}">
<small><i><Icon type="email" /> {{ strings.MESSAGE_SENT_VIA_EMAIL }}</i></small><br/><br/>
</span>
<a class="download-link" href="{{ body.file.url }}" target="_blank" rel="noopener noreferrer">
<Icon type="download"/>
- <span>{{ body.file.formattedFilename }}</span>
+ <span t:if="{{ body.isVoiceMessage == false }}">{{ body.file.formattedFilename }}</span>
+ <span t:else><Icon type="microphone" /> {{ strings.VOICE_MESSAGE }} ({{ body.recordingDuration | format_duration }})</span>
</a>

Then, in the CSS part of the MessageBody:

+ .thumbnail.audio.has-error {
+ padding: 0.5rem;
+ }

Lastly, to show that the last message was a voice message in the conversation list, make the following changes to the ConversationListItem component:

- <span t:if="{{ lastMessage.body.type == 'file' }}">
+ <span t:if="{{ lastMessage.body.type == 'file' and lastMessage.body.isVoiceMessage == false }}">
<Icon type="image" t:if="{{lastMessage.body.file.type == 'image'}}" />
<Icon type="movie" t:if="{{lastMessage.body.file.type == 'video'}}" />
<Icon type="attachment" t:if="{{lastMessage.body.file.type == 'other'}}" />
<span> {{lastMessage.body.file.filename}}</span>
</span>
+ <span t:if="{{ lastMessage.body.type == 'file' and lastMessage.body.isVoiceMessage == true }}">
+ <span><Icon type="microphone" /> {{ strings.VOICE_MESSAGE }} ({{ lastMessage.body.recordingDuration | format_duration }})</span>
+ </span>

Status indicator (2022-09-23)

We've added a new StatusIndicator element, which shows if a user is online or offline. If you've created your theme before 2022-09-23, you might have to make some changes to the ChatHeader component in your theme.

To use the Statusindicator element, change these lines in your chatHeader component:

- <t:set names="{{ conversation.others | map: 'name' | join: ', ' }}" />
- <div t:if="{{ conversation.formattedSubject }}" class="info">
- <div class="title">{{ conversation.formattedSubject }}</div>
- <div class="subtitle">{{ names }}</div>
- </div>
- <div t:else class="info">
- <div class="title">{{ names }}</div>
- </div>
+ <div t:if="{{ conversation.formattedSubject }}" class="info">
+ <div class="title">{{ conversation.formattedSubject }}</div>
+ <div class="subtitle" >
+ <div class="inline-block user {{ presenceEnabled | then: 'presence-enabled' }}" t:for="{{ user in conversation.others }}" t:key="{{ user.id }}">
+ <span>{{user.name}}</span>
+ <StatusIndicator class="status-indicator" user="{{user}}" />
+ <span class="participant-separator" t:if="{{ presenceEnabled == false and forloop.last == false }}">, </span>
+ </div>
+ </div>
+ </div>
+ <div t:else class="info">
+ <div class="title">
+ <div class="inline-block user {{ presenceEnabled | then: 'presence-enabled' }}" t:for="{{ user in conversation.others }}" t:key="{{ user.id }}">
+ <span>{{user.name}}</span>
+ <StatusIndicator class="status-indicator" user="{{user}}" />
+ <span class="participant-separator" t:if="{{ presenceEnabled == false and forloop.last == false }}">, </span>
+ </div>
+ </div>
+ </div>
+ .inline-block {
+ display: inline-block;
+ }
+
+ .participant-separator {
+ white-space: pre-wrap;
+ }
+
+ .status-indicator {
+ margin: 0 0.75rem -1px 0.275rem;
+ }

Flexible video aspect ratio (2022-09-02)

We added support for displaying videos with a wider range of aspect ratios. If you've created a theme before 2022-09-02, you might have to change some styling in the UserMessage component.

To use the styling of the default UserMessage component, change these lines:

...
- <span t:if="{{ body.type == 'location' }}" class="thumbnail">
+ <span t:if="{{ body.type == 'location' }}" class="thumbnail location">
...
.thumbnail {
- height: 200px;
display: block;
}
+ .thumbnail.image, .thumbnail.location {
+ height: 200px
+ }
+ .thumbnail.video {
+ max-height: 400px
+ }

Emoji reactions (2022-08-18)

We added support for emoji reactions within TalkJS. If you've created a theme before 2022-08-18, you have to add the code for displaying emoji reactions in the UserMessage component. We added 2 new built-in components for this:

  • <EmojiReactionButton> renders a button that the user can click to react with a given emoji, or click again to remove their reaction.
  • <AddEmojiReaction> a button that also lets the user add an emoji reaction, but it'll pop up an emoji picker for the user to choose an emoji.

So, before the closing </template> tag, add the following snippet to the UserMessage component:

<div t:if="{{ reactions and reactions.length > 0 }}" class="emoji-reactions {{ sender.isMe | then: 'by-me' | else: 'by-other' }}">
<EmojiReactionButton t:for="{{ reaction in reactions }}" t:key="{{ reaction.emoji }}" class="emoji-reaction {{ reaction.iReacted | then: 'i-reacted' }}" emoji="{{ reaction.emoji }}" numReactions="{{ reaction.numReactions }}" iReacted="{{ reaction.iReacted }}">
<span class="reaction">{{ reaction.emoji }}</span>
<span class="num-reactions" t:if="{{ reaction.numReactions > 1 }}">{{ reaction.numReactions }}</span>
</EmojiReactionButton>
<AddEmojiReaction class="add-emoji-reaction" t:if="{{ canReact }}">
<Icon type="addEmoji" class="icon" />
</AddEmojiReaction>
</div>

Then, to style the emoji reactions, if you used the above snippet, you will have:

  • .emoji-reactions which points to the emoji reactions <div>. This <div> can hav either the by-me or the by-other class, depending whether the message is by the current user or not.
  • .emoji-reaction is the button containing the emoji reaction. This button can also have the i-reacted class if the current user reacted with that emoji. This button also has 2 children:
    • .reaction which is the emoji
    • .num-reactions which is the number of people who reacted with that emoji
  • .add-emoji-reaction is the button for adding an emoji reaction

To use the styling of the default theme, add the following in the <style> section of the UserMessage component:

.emoji-reactions {
margin-top: -0.25rem;
margin-bottom: 1.75rem;
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.emoji-reactions.by-me {
justify-content: flex-end;
margin-right: 3.25rem;
}
.emoji-reactions.by-other {
margin-left: 3.25rem;
}
.emoji-reaction {
border: 1px solid transparent;
margin: 0;
padding: 0.2rem 0.55rem;
border-radius: 8rem;
background-color: #e8ecee;
}
.emoji-reaction.i-reacted {
background: #1E60E1;
border: 1px solid transparent;
color: white;
}
.emoji-reaction.i-reacted:hover {
background: #1E60E1;
border: 1px solid transparent;
}
.emoji-reaction span {
display: inline-block;
vertical-align: middle;
}
.emoji-reaction .reaction {
font-size: 1.25rem;
}
.emoji-reaction .num-reactions {
padding-left: 0.25rem;
font-size: 0.85rem;
}
.add-emoji-reaction {
border: 1px solid transparent;
margin: 0rem;
padding: 0.15rem 0.75rem;
border-radius: 8rem;
background-color: #e8ecee;
color: #111;
}
.emoji-reaction:hover,
.add-emoji-reaction:hover {
border: 1px solid #b5c0c6;
}
.add-emoji-reaction .icon {
width: 1.5rem;
height: 1.5rem;
}

After saving the aformentioned changes, emoji reactions will be rendered in the TalkJS UI.

Audio player (2022-08-17)

We added support for playing audio attachments directly within TalkJS. Within our themes, this uses the existing mechanism for displaying attachments so it works out of the box, but if you've created a theme before 2022-08-17, you may want to apply a style tweak to give the player the correct height.

The thumbnail is rendered inside the MessageBody component, so that's where we'll make the change. Since we want to apply the styling only for audio files, we'll add the file type as a class on the <span> that wraps the thumbnail. We can then add some CSS to set the height to auto when there's a thumbnail for an audio file.

MessageBody component:

...
- <span t:else-if="{{ body.type == 'file' and body.hasThumbnail }}" class="thumbnail">
+ <span t:else-if="{{ body.type == 'file' and body.hasThumbnail }}" class="thumbnail {{ body.file.type }}">
<Thumbnail file="{{ body.file }}" gradients="{{darkenMenuArea | then: 'top-right'}}"/>
</span>
...
.thumbnail {
height: 200px;
display: block;
}
+ .thumbnail.audio {
+ height: auto;
+ }

After saving the aformentioned changes, the audio player will have the correct size.

Replies (2022-07-08)

If you want to allow users to reply to messages (described in this changelog entry) and you edited the theme before 08/07/2022 you need to manually pass hasReferencedMessage from UserMessage component to the MessageBody component, and then inside the message body, render the referenced message.

UserMessage component:

<MessageBody
body="{{ body }}"
timestamp="{{ timestamp }}"
editedAt="{{ editedAt }}"
floatTimestamp="auto"
showStatus="{{ sender.isMe }}"
isLongEmailMessage="{{isLongEmailMessage}}"
darkenMenuArea="{{ darkenMenuArea }}"
isSystemMessage="{{ false }}"
+ hasReferencedMessage="{{ hasReferencedMessage }}
/>

MessageBody component:

...
<template>
+ <div t:if="{{ hasReferencedMessage }}" class="referencedMessage">
+ <ReferencedMessage />
+ </div>
<span t:if="{{ body.type == 'location' }}" class="thumbnail">
<Thumbnail location="{{ body.location }}" gradients="{{darkenMenuArea | then: 'top-right'}}"/>
</span>
<span t:else-if="{{ body.type == 'file' and body.hasThumbnail }}" class="thumbnail">
<Thumbnail file="{{ body.file }}" gradients="{{darkenMenuArea | then: 'top-right'}}"/>
</span>
...
+ .referencedMessage {
+ padding: 10px 0px 5px 0px;
+ }
+ .referencedMessage ~ .text {
+ padding-top: 0;
+ }

After saving the aformentioned changes, replies will be rendered in the TalkJS UI.