Earlier, the toggle was only happening from the outside, but now we can toggle the options menu from the menu itself. This is useful when we want to close the menu from inside, for example when an option has been selected or the back button has been pressed.
273 lines
6.2 KiB
Svelte
273 lines
6.2 KiB
Svelte
<script lang="ts">
|
|
import Text from '../components/Text.svelte'
|
|
import ChatMessage from '../components/ChatMessage.svelte'
|
|
import OptionsMenu from '../components/OptionsMenu.svelte'
|
|
import ListItem from '../components/ListItem.svelte'
|
|
import Input from '../components/Input.svelte'
|
|
|
|
import { onMount } from 'svelte'
|
|
import { push } from 'svelte-spa-router'
|
|
import { converse, _converse } from '@converse/headless/core'
|
|
|
|
import {
|
|
titleStore,
|
|
softkeysStore,
|
|
xmppConnected,
|
|
} from '../stores.ts'
|
|
|
|
export let params = {}
|
|
let title
|
|
let centerSoftkeyLabel = 'Enter'
|
|
let leftSoftkeyLabel = ' '
|
|
let chatbox
|
|
let messages
|
|
let chatListEl
|
|
let composeBox
|
|
|
|
// Whether the options menu is visible or not
|
|
let optionsMenuVisible = false
|
|
function toggleOptionsMenu() {
|
|
optionsMenuVisible = !optionsMenuVisible
|
|
}
|
|
|
|
|
|
$: title = chatbox?.attributes?.nickname || chatbox?.attributes?.id || params.chatID || 'Convo'
|
|
$: titleStore.update(() => title)
|
|
|
|
function scrollToLatest(onlyOnChatbox=false) {
|
|
|
|
// Skip if necessary when chatbox not selected
|
|
if (onlyOnChatbox) {
|
|
let el = document.querySelector('.compose-box input')
|
|
if(document.activeElement != el) return
|
|
}
|
|
|
|
let messageListEl = document.querySelector('.message-list')
|
|
if (messageListEl) {
|
|
messageListEl.scrollTo(0, messageListEl.scrollHeight)
|
|
}
|
|
}
|
|
|
|
$: {
|
|
messages
|
|
setTimeout(scrollToLatest, 1000) // delay to make sure it happens
|
|
}
|
|
|
|
softkeysStore.update((k) => {
|
|
k.left.label = leftSoftkeyLabel
|
|
k.left.callback = async () => {
|
|
let el = document.querySelector('.compose-box input')
|
|
|
|
// Send message if textbox activated
|
|
if (document.activeElement == el) {
|
|
// Send only if not blank
|
|
if (!!composeBox) {
|
|
let messageText = composeBox
|
|
composeBox = ''
|
|
|
|
// We can't process these till the emoji are loaded
|
|
await converse.emojis.initialized_promise
|
|
chatbox.sendMessage({
|
|
body: messageText,
|
|
})
|
|
|
|
// update the message list
|
|
messages = [...chatbox.messages]
|
|
}
|
|
}
|
|
}
|
|
|
|
k.center.label = centerSoftkeyLabel
|
|
k.center.callback = () => {
|
|
|
|
// Focus input if not already focused
|
|
let el = document.querySelector('.compose-box input')
|
|
if (document.activeElement != el) {
|
|
// focus the input element
|
|
el.focus()
|
|
} else {
|
|
|
|
// Otherwise, we literally type an Enter
|
|
if (!composeBox) composeBox = ''
|
|
composeBox = composeBox + '\n'
|
|
|
|
// FIXME: compose box should be a textarea for
|
|
// this to actually work.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
k.right.label = 'Options'
|
|
k.right.callback = toggleOptionsMenu
|
|
|
|
return k
|
|
})
|
|
|
|
// Run this and unsubscribe after one
|
|
// second. (The xmppConnected.subscribe function
|
|
// returns the unsubscribe callback, which we
|
|
// then call with setTimeout after 1000 ms)
|
|
setTimeout(xmppConnected.subscribe(value => {
|
|
if (!value) {
|
|
push('/redirect') // redirects to home
|
|
}
|
|
}), 1000)
|
|
|
|
// Manage the compose box
|
|
|
|
function onComposeBoxFocus() {
|
|
// scroll to last message, for convenience
|
|
scrollToLatest()
|
|
|
|
centerSoftkeyLabel = 'Enter'
|
|
|
|
if (!!composeBox) {
|
|
leftSoftkeyLabel = 'Send'
|
|
} else {
|
|
leftSoftkeyLabel = ' '
|
|
}
|
|
}
|
|
|
|
function onComposeBoxBlur() {
|
|
centerSoftkeyLabel = 'Compose'
|
|
leftSoftkeyLabel = ' '
|
|
}
|
|
|
|
function updateCenterSoftkeyLabel() {
|
|
softkeysStore.update((k) => {
|
|
k.center.label = centerSoftkeyLabel
|
|
|
|
return k
|
|
})
|
|
}
|
|
|
|
function updateLeftSoftkeyLabel() {
|
|
softkeysStore.update((k) => {
|
|
k.left.label = leftSoftkeyLabel
|
|
|
|
return k
|
|
})
|
|
}
|
|
|
|
function onComposeBoxUpdate() {
|
|
/*
|
|
* We're assuming the compose box is focused here,
|
|
* because how else did it get updated? If this is
|
|
* problematic, we can add an `if` condition here
|
|
* to check if it is indeed the active element.
|
|
* Either way, it only updates the label, not the
|
|
* action itself (which is admittedly more or
|
|
* less confusing depending on how you look at it).
|
|
*/
|
|
|
|
// Clear 'Send' button if empty
|
|
if (leftSoftkeyLabel == 'Send' && !composeBox) {
|
|
leftSoftkeyLabel = ' '
|
|
|
|
// We have to do this since reactivity doesn't recurse
|
|
updateLeftSoftkeyLabel()
|
|
} else if (leftSoftkeyLabel == ' ' && !!composeBox) {
|
|
leftSoftkeyLabel = 'Send'
|
|
|
|
// Again, no recursion in reactivity
|
|
updateLeftSoftkeyLabel()
|
|
}
|
|
}
|
|
|
|
$: softkeysStore.update((k) => {
|
|
k.center.label = centerSoftkeyLabel
|
|
|
|
return k
|
|
})
|
|
|
|
$: softkeysStore.update((k) => {
|
|
k.left.label = leftSoftkeyLabel
|
|
|
|
return k
|
|
})
|
|
|
|
$: {
|
|
!composeBox // whenever this changes
|
|
onComposeBoxUpdate()
|
|
}
|
|
|
|
// Function to close a conversation
|
|
function closeChat() {
|
|
chatbox.close().then(() => {
|
|
push('/messages')
|
|
})
|
|
}
|
|
|
|
// Get it all running!
|
|
onMount(() => {
|
|
// Initialise the chatbox
|
|
chatbox = _converse.chatboxes.get(params.chatID)
|
|
if (chatbox) {
|
|
messages = [...chatbox.messages]
|
|
|
|
_converse.api.listen.on('message', () => {
|
|
// TODO: refresh only when the message is
|
|
// coming to this particular chatbox
|
|
messages = [...chatbox.messages]
|
|
})
|
|
}
|
|
|
|
setTimeout(scrollToLatest, 1000)
|
|
})
|
|
</script>
|
|
|
|
{#if !chatbox}
|
|
<Text>
|
|
<p>Empty conversation</p>
|
|
</Text>
|
|
{:else}
|
|
<div class="message-list">
|
|
{#each messages as message, index (message.id)}
|
|
<ChatMessage
|
|
{message}
|
|
tabindex={index}
|
|
ourJID={_converse.bare_jid}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="compose-box">
|
|
<Input
|
|
bind:value={composeBox}
|
|
tabindex={chatbox?.messages?.length || 0}
|
|
autofocus={true}
|
|
label='Compose'
|
|
on:focus={onComposeBoxFocus}
|
|
on:blur={onComposeBoxBlur}
|
|
/>
|
|
</div>
|
|
|
|
{#if optionsMenuVisible}
|
|
<OptionsMenu
|
|
title="Options"
|
|
toggler={toggleOptionsMenu}
|
|
>
|
|
<ListItem
|
|
text="Close conversation"
|
|
onclick={closeChat}
|
|
/>
|
|
</OptionsMenu>
|
|
{/if}
|
|
|
|
<style>
|
|
.compose-box {
|
|
height: 8rem;
|
|
position: absolute;
|
|
bottom: 3rem; /* for the softkey bar */
|
|
left: 0;
|
|
right: 0;
|
|
}
|
|
.message-list {
|
|
margin-bottom: 9rem;
|
|
overflow: scroll;
|
|
height: calc(100vh - 13rem);
|
|
}
|
|
</style>
|