Implement conversation view!

This has a few hacks and is a bit buggy, but at least it's usable
and you can begin texting people now :D
This commit is contained in:
Badri Sunderarajan 2022-09-17 14:43:23 +05:30
parent db067d14fb
commit d671932569
8 changed files with 278 additions and 6 deletions

View file

@ -37,6 +37,13 @@
--button-background-focused: #873eff;
--button-color-focused: #ffffff;
--chatbox-indicator-background-focused: #6a6a6a;
--chatbox-indicator-color-focused: #ffffff;
--chatbox-background: #cccccc;
--chatbox-background-primary: #323232;
--chatbox-color: #323232;
--chatbox-color-primary: #ffffff;
--checkbox-color: #873eff;
--checkbox-color-focused: #ffffff;

View file

@ -6,6 +6,7 @@
import Home from './routes/Home.svelte'
import Login from './routes/Login.svelte'
import Messages from './routes/Messages.svelte'
import Chat from './routes/Chat.svelte'
import Redirect from './routes/Redirect.svelte'
import { titleStore, softkeysStore } from './stores.ts'
@ -21,6 +22,7 @@
'/': Home,
'/login': Login,
'/messages': Messages,
'/chat/:chatID': Chat,
'*': Redirect,
}

View file

@ -0,0 +1,86 @@
<script>
export let message
export let tabindex = -1
export let autofocus = false
export let onclick = () => {}
</script>
{#if message}
<div
class="chat-item-indicator focusable"
{tabindex}
{autofocus}
on:click={onclick}
>
<div class="chat-item-row">
<div class="avatar"></div>
<div class="chat-item">
<p>{message.attributes?.message}</p>
</div>
</div>
<p class="chat-item-info">
{message.attributes?.nick || message.attributes?.nickname || message.attributes?.from}
{new Date(message.attributes?.time).toLocaleString()}
</p>
</div>
{/if}
<style>
.chat-item-indicator {
background: var(--item-background);
position: relative;
box-sizing: border-box;
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
}
.chat-item-row {
vertical-align: top;
}
.avatar {
height: 3rem;
width: 3rem;
margin-right: 0.5rem;
display: inline-block;
vertical-align: top;
background: var(--chatbox-background);
margin-bottom: auto;
}
.chat-item {
background: var(--chatbox-background);
color: var(--chatbox-color);
padding: 0;
display: inline-block;
width: calc(100% - 4rem);
border-radius: 0 1rem 1rem 1rem;
}
.chat-item p{
word-wrap: break-word;
overflow: hidden;
font-size: 1.7rem;
font-weight: 400;
margin: 1rem;
}
.chat-item-indicator:focus-within {
background: var(--chatbox-indicator-background-focused);
}
.chat-item-info {
margin-left: 4.5rem;
font-size: 1.4rem;
font-weight: 400;
color: var(--chatbox-color);
margin-top: 0;
}
.chat-item-indicator:focus-within .chat-item-info {
color: var(--chatbox-indicator-color-focused);
}
</style>

View file

@ -4,14 +4,16 @@
export let hidden = false
export let autofocus = false
export let value = null
export let onfocus = () => {}
export let onblur = () => {}
</script>
<div class="input-container">
<label class="input-container__label">{label}</label>
{#if hidden}
<input type="password" tabindex="{tabindex}" class="input-container__input focusable" bind:value={value} {autofocus}/>
<input type="password" tabindex="{tabindex}" class="input-container__input focusable" bind:value={value} {autofocus} on:focus={onfocus} on:blur={onblur}/>
{:else}
<input type="text" tabindex="{tabindex}" class="input-container__input focusable" bind:value={value} {autofocus}/>
<input type="text" tabindex="{tabindex}" class="input-container__input focusable" bind:value={value} {autofocus} on:focus={onfocus} on:blur={onblur}/>
{/if}
</div>

View file

@ -5,9 +5,15 @@
export let tabindex = -1
export let autofocus = false
export let fullheight = false
export let onclick = () => {}
</script>
<div class="list-item-indicator focusable{fullheight ? ' fullheight' : ''}" {tabindex} {autofocus}>
<div
class="list-item-indicator focusable{fullheight ? ' fullheight' : ''}"
{tabindex}
{autofocus}
on:click={onclick}
>
{#if text}
<p class="list-item-indicator__text">{text}</p>
{/if}

View file

@ -17,6 +17,8 @@ import "@converse/headless/plugins/smacks/index.js"; // XEP-0198 Stream Mana
import "@converse/headless/plugins/status/index.js";
import "@converse/headless/plugins/vcard/index.js"; // XEP-0054 VCard-temp
import "@converse/headless/plugins/emojis/index.js"; // Emojis
// We're just importing these to activate the addon
import {
convertASCII2Emoji,
@ -32,6 +34,7 @@ import {
hexToArrayBuffer,
stringToArrayBuffer
} from '@converse/headless/utils/arraybuffer.js';
import { getHyperlinkTemplate } from '@converse/headless/utils/html.js'
window.converse = converse;
@ -77,6 +80,10 @@ converse.plugins.add('convo', {
//_converse.api.listen.on('message', m => console.log('message', m));
console.debug('Handlers ready!')
// emoji don't seem to be getting initialized,
// so let's do it manually
_converse.api.emojis.initialize()
})
},
})

154
src/routes/Chat.svelte Normal file
View file

@ -0,0 +1,154 @@
<script lang="ts">
import Text from '../components/Text.svelte'
import ChatMessage from '../components/ChatMessage.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 = 'Send'
let chatbox
let messages
let chatListEl
let composeBox
$: title = chatbox?.attributes?.nickname || chatbox?.attributes?.id || params.chatID || 'Convo'
$: titleStore.update(() => title)
function scrollToLatest() {
let messageListEl = document.querySelector('.message-list')
if (messageListEl) {
messageListEl.scrollTo(0, messageListEl.scrollHeight)
}
}
softkeysStore.update((k) => {
k.left.label = 'Back'
k.left.callback = () => { push('/messages') }
k.center.label = 'Enter'
k.center.callback = async () => {
let el = document.querySelector('.compose-box input')
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 messsage list
messages = [...chatbox.messages]
// Scroll to the latest chat
setTimeout(scrollToLatest, 500)
}
} else {
// focus the input element
el.focus()
}
}
k.right.label = 'Options'
k.right.callback = () => {}
return k
})
$: softkeysStore.update((k) => {
k.center.label = centerSoftkeyLabel
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)
function onComposeBoxFocus() {
// scroll to last message, for convenience
scrollToLatest()
centerSoftkeyLabel = 'Send'
}
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]
// Scroll down if we're on the compose box
// TODO: only do this if it's for the current chat
let el = document.querySelector('.compose-box input')
if(document.activeElement == el) {
scrollToLatest()
}
})
}
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}/>
{/each}
</div>
{/if}
<div class="compose-box">
<Input
bind:value={composeBox}
tabindex={chatbox?.messages?.length || 0}
autofocus={true}
label='Compose'
onfocus={onComposeBoxFocus}
onblur={() => { centerSoftkeyLabel = 'Compose' }}
/>
</div>
<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>

View file

@ -18,7 +18,9 @@
k.left.callback = () => {}
k.center.label = 'Open'
k.center.callback = () => {}
k.center.callback = () => {
document.activeElement.click()
}
k.right.label = 'Options'
k.right.callback = () => {}
@ -26,6 +28,11 @@
return k
})
// Main function for this view: get a conversation going!
function openChat(chatID) {
push(`/chat/${chatID}`)
}
// Run this and unsubscribe after one
// second. (The xmppConnected.subscribe function
// returns the unsubscribe callback, which we
@ -36,7 +43,7 @@
}
}), 1000)
let chatboxes = []
let chatboxes = _converse?.chatboxes?.toArray() || []
_converse.on('chatBoxesFetched', () => {
chatboxes = _converse.chatboxes?.toArray() || []
@ -52,11 +59,12 @@
<p>You have no conversations. How about starting one?</p>
</Text>
{:else}
{#each chatboxes as convo, index}
{#each chatboxes as convo, index (convo.id)}
<ListItem
text={convo.attributes.nickname || convo.attributes.id}
subtext={convo.messages?.last()?.attributes?.body || undefined}
tabindex={index}
onclick={() => openChat(convo.id)}
/>
{/each}
{/if}