feat: implement whisper, (double)click on avatar

Users can insert a mention by clicking, and initiate a whisper by
double-clicking on the avatar on a user's chat message
This commit is contained in:
dvdrw 2023-02-14 00:23:07 +01:00
parent 38f926b32f
commit 7d581afcaf
Signed by: dvdrw
GPG Key ID: 044B8425E3CD03E0
6 changed files with 52 additions and 9 deletions

View File

@ -1,6 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { Message } from "fedwave-chat-client"; import type { Message } from "fedwave-chat-client";
import { room, username } from "./chat"; import { room } from "./chat";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let globalMode = false; export let globalMode = false;
export let message: Message; export let message: Message;
export let isHeader: boolean = true; export let isHeader: boolean = true;
@ -8,7 +12,11 @@
<div class="message d-flex p-relative pl-8 pr-24" class:subseq="{!isHeader}"> <div class="message d-flex p-relative pl-8 pr-24" class:subseq="{!isHeader}">
{#if isHeader} {#if isHeader}
<div class="avatar radius-pill overflow-hidden no-select" style="background-color: {message.color}"> <!-- TODO: Use <Avatar/> here -->
<div class="avatar radius-pill overflow-hidden no-select clickable"
on:click="{() => dispatch('avatar:click', message)}"
on:dblclick="{() => dispatch('avatar:dblclick', message)}"
style="background-color: {message.color}">
<img class="w-100" alt="{message.username}'s avatar" src="{message.avatar || '/troll_haz2.png'}"> <img class="w-100" alt="{message.username}'s avatar" src="{message.avatar || '/troll_haz2.png'}">
</div> </div>
{/if} {/if}

View File

@ -18,6 +18,8 @@
let viewport: Element, contents: Element; let viewport: Element, contents: Element;
let tweet: HTMLAudioElement; let tweet: HTMLAudioElement;
let input: string = '';
shouldTweet.subscribe((should) => { shouldTweet.subscribe((should) => {
if (should) { if (should) {
if (tweet) { if (tweet) {
@ -91,6 +93,10 @@
bind:viewport bind:viewport
bind:contents bind:contents
on:scroll={onScroll} on:scroll={onScroll}
on:avatar:click={(e) => (
(input += ` @${e.detail.username}#${e.detail.unum} `), (input = input.trim())
)}
on:avatar:dblclick={(e) => (input = `/w ${e.detail.username}#${e.detail.unum} `)}
/> />
{#if !stickToBottom} {#if !stickToBottom}
<FAB --background={'var(--base-700)'} on:click={() => scrollToBottom()} <FAB --background={'var(--base-700)'} on:click={() => scrollToBottom()}
@ -98,7 +104,7 @@
> >
{/if} {/if}
</div> </div>
<Input /> <Input bind:input />
<style> <style>
.scroller { .scroller {

View File

@ -1,11 +1,20 @@
<script lang="ts"> <script lang="ts">
import { tick } from "svelte" import { tick } from "svelte"
import { send } from "./chat"; import { send, whisper } from "./chat";
let input = ""; export let input = "";
async function keydown(e: KeyboardEvent) { async function keydown(e: KeyboardEvent) {
if(e.key === "Enter") { if(e.key === "Enter") {
send(input); console.log(e);
const tokens = input.trim().split(" ");
if(tokens[0] === '/w') {
const who = tokens[1];
const m = tokens.slice(2).join(' ');
whisper(who, m);
} else {
send(input);
}
e.preventDefault(); e.preventDefault();
await tick(); await tick();
input = ""; input = "";

View File

@ -12,7 +12,10 @@
<div class="viewport" bind:this={viewport} on:scroll> <div class="viewport" bind:this={viewport} on:scroll>
<div class="contents" bind:this={contents}> <div class="contents" bind:this={contents}>
{#each messages as message} {#each messages as message}
<ChatMessage message={message.m} isHeader={message.isHeader} globalMode={showChannel} /> <ChatMessage message={message.m}
isHeader={message.isHeader}
globalMode={showChannel}
on:avatar:click on:avatar:dblclick />
{/each} {/each}
</div> </div>
</div> </div>

View File

@ -20,6 +20,10 @@ export function send(m: string | OutgoingMessage) {
chat.sendMessage(m); chat.sendMessage(m);
} }
export function whisper(who: string, m: string) {
chat.sendWhisper(who, m);
}
let setConnected: Subscriber<boolean> | undefined; let setConnected: Subscriber<boolean> | undefined;
export const connected = readable<boolean>(false, (setf) => { export const connected = readable<boolean>(false, (setf) => {
setConnected = setf; setConnected = setf;

View File

@ -37,6 +37,10 @@
flex-grow: 0; flex-grow: 0;
} }
.shrink-0 {
flex-shrink: 0;
}
.basis-0 { .basis-0 {
flex-basis: 0; flex-basis: 0;
} }
@ -62,8 +66,10 @@
} }
} }
.d-flex { @each $i in (block, inline, inline-block, flex, grid, contents) {
display: flex; .d-#{$i} {
display: #{$i};
}
} }
.col { .col {
@ -82,9 +88,16 @@
width: 100vw; width: 100vw;
} }
.mw-0 { min-width: 0 }
.w-100 { width: 100%; } .w-100 { width: 100%; }
.h-100 { height: 100%; } .h-100 { height: 100%; }
.ellipsis {
text-overflow: ellipsis;
overflow: hidden;
}
.hide-scrollbar { .hide-scrollbar {
/* hide scrollbar */ /* hide scrollbar */
-ms-overflow-style: none !important; -ms-overflow-style: none !important;