Skip to content

Commit

Permalink
Merge pull request #178 from skychatorg/dev/7ph
Browse files Browse the repository at this point in the history
Sticker autosuggest & Various improvements
  • Loading branch information
7PH authored Dec 26, 2023
2 parents d4df33d + bf93973 commit 7e499b9
Show file tree
Hide file tree
Showing 15 changed files with 338 additions and 52 deletions.
11 changes: 11 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"printWidth": 140,
"tabWidth": 4,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "auto"
}
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["typescript", "vue", "html"]
}
9 changes: 9 additions & 0 deletions app/api/SkyChatClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const defaultUser: SanitizedUser = {

export declare interface SkyChatClient {
on(event: 'config', listener: (config: PublicConfig) => any): this;
on(event: 'sticker-list', listener: (stickers: Record<string, string>) => any): this;
on(event: 'custom', listener: (custom: CustomizationElements) => any): this;
on(event: 'set-user', listener: (user: SanitizedUser) => any): this;
on(event: 'auth-token', listener: (token: AuthToken | null) => any): this;
Expand Down Expand Up @@ -78,6 +79,7 @@ export class SkyChatClient extends EventEmitter {

private _user: SanitizedUser = defaultUser;
private _config: PublicConfig | null = null;
private _stickers: Record<string, string> = {};
private _custom: CustomizationElements = {};
private _token: AuthToken | null = null;
private _connectedList: Array<SanitizedSession> = [];
Expand Down Expand Up @@ -113,6 +115,7 @@ export class SkyChatClient extends EventEmitter {

// Auth & Config
this.on('config', this._onConfig.bind(this));
this.on('sticker-list', this._onStickerList.bind(this));
this.on('custom', this._onCustom.bind(this));
this.on('set-user', this._onUser.bind(this));
this.on('auth-token', this._onToken.bind(this));
Expand Down Expand Up @@ -159,6 +162,11 @@ export class SkyChatClient extends EventEmitter {
this.emit('update', this.state);
}

private _onStickerList(stickers: Record<string, string>) {
this._stickers = stickers;
this.emit('update', this.state);
}

private _onCustom(custom: CustomizationElements) {
this._custom = custom;
this.emit('update', this.state);
Expand Down Expand Up @@ -379,6 +387,7 @@ export class SkyChatClient extends EventEmitter {
websocketReadyState: this._websocket ? this._websocket.readyState : WebSocket.CLOSED,
user: this._user,
config: this._config,
stickers: this._stickers,
custom: this._custom,
token: this._token,
connectedList: this._connectedList,
Expand Down
29 changes: 24 additions & 5 deletions app/client/src/components/message/MessagePannel.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
<script setup>
import { onMounted, nextTick, watch, ref, reactive } from 'vue';
import { useAppStore } from '@/stores/app';
import { useClientStore } from '@/stores/client';
import SingleMessage from '@/components/message/SingleMessage.vue';
const app = useAppStore();
const client = useClientStore();
const messagePannel = ref(null);
Expand Down Expand Up @@ -111,18 +109,39 @@ const onScroll = () => {
scrollState.auto = true;
}
};
function isMessageFirstOfDay(index) {
if (index === 0) {
return true;
}
const message = client.messages[index];
const previousMessage = client.messages[index - 1];
const messageDate = new Date(message.createdTimestamp * 1000);
const previousMessageDate = new Date(previousMessage?.createdTimestamp * 1000);
return (
messageDate.getDate() !== previousMessageDate.getDate() ||
messageDate.getMonth() !== previousMessageDate.getMonth() ||
messageDate.getFullYear() !== previousMessageDate.getFullYear()
);
}
</script>
<template>
<div
class="overflow-x-hidden overflow-y-auto pl-2 scrollbar"
ref="messagePannel"
@scroll="onScroll"
class="overflow-x-hidden overflow-y-auto pl-2 scrollbar"
:style="{
'scroll-behavior': scrollState.smooth && scrollState.auto ? 'smooth' : 'auto',
}"
@scroll="onScroll"
>
<SingleMessage v-for="message in client.messages" :key="message.id" :message="message" @content-size-changed="scrollToBottomIfAutoScroll" />
<SingleMessage
v-for="(message, index) in client.messages"
:key="message.id"
:message="message"
:show-date="isMessageFirstOfDay(index)"
@content-size-changed="scrollToBottomIfAutoScroll"
/>
</div>
</template>
Expand Down
121 changes: 89 additions & 32 deletions app/client/src/components/message/NewMessageForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAppStore } from '@/stores/app';
import { useClientStore } from '@/stores/client';
import { AudioRecorder } from '@/lib/AudioRecorder';
import { RisiBank } from 'risibank-web-api';
import { Mentionable } from 'vue-mention';
const MESSAGE_HISTORY_LENGTH = 500;
Expand Down Expand Up @@ -150,22 +151,6 @@ const openRisiBank = function () {
});
};
/**
* Autocomplete the username
*/
const onKeyUpTab = function () {
const messageMatch = app.newMessage.match(/([*a-zA-Z0-9_-]+)$/);
const username = messageMatch ? messageMatch[0].toLowerCase() : null;
if (!username) {
return;
}
const matches = client.state.connectedList.map((entry) => entry.identifier).filter((identifier) => identifier.indexOf(username) === 0);
if (matches.length !== 1) {
return;
}
app.setMessage(app.newMessage.substr(0, app.newMessage.length - username.length) + matches[0]);
};
/**
* When the file input changed
*/
Expand Down Expand Up @@ -212,9 +197,44 @@ const cancelAudio = function () {
}
recordingAudio.value = false;
};
const autoSuggestItems = ref([]);
const autoSuggestOpen = ref(false);
function onOpen(key) {
autoSuggestOpen.value = true;
if (key === '#') {
autoSuggestItems.value = Object.values(client.state.rooms).map((room) => ({
value: room.name,
searchText: room.name,
label: room.name,
}));
} else if (key === '@') {
autoSuggestItems.value = Object.values(client.state.connectedList).map((user) => ({
value: user.identifier,
searchText: user.identifier,
label: user.identifier,
}));
} else if (key === ':') {
autoSuggestItems.value = Object.entries(client.state.stickers).map(([name, sticker]) => ({
value: name.substring(1),
searchText: name,
label: name,
url: sticker,
}));
} else {
autoSuggestItems.value = [];
}
}
function onClose() {
autoSuggestOpen.value = false;
autoSuggestItems.value = [];
}
</script>

<template>
<!-- eslint-disable vue/valid-attribute-name -->
<!-- eslint-disable vue/no-lone-template -->
<div class="p-2">
<!-- New message form -->
<div class="flex flex-col-reverse lg:flex-row flex-nowrap">
Expand Down Expand Up @@ -278,21 +298,54 @@ const cancelAudio = function () {
<p class="h-5 pl-2 text-xs text-skygray-lightest">
{{ typingListText }}
</p>
<textarea
ref="message"
:rows="messageTextAreaRows"
class="mousetrap form-control lg:ml-2 scrollbar resize-none"
type="text"
:placeholder="textAreaPlaceholder"
:disabled="!client.state.currentRoom"
:maxlength="client.state.currentRoom.plugins.messagelimiter ?? null"
@input="onMessageInput"
@keyup.up.exact="onNavigateIntoHistory($event, -1)"
@keyup.down.exact="onNavigateIntoHistory($event, 1)"
@keydown.tab.prevent="onKeyUpTab"
@keydown.shift.enter.stop=""
@keydown.enter.exact.stop="sendMessage"
></textarea>

<Mentionable
:keys="['@', '#', ':']"
:items="autoSuggestItems"
offset="6"
insert-space
class="d-flex"
@open="onOpen"
@close="onClose"
>
<textarea
ref="message"
type="text"
:rows="messageTextAreaRows"
class="mousetrap form-control lg:ml-2 scrollbar resize-none w-full"
:placeholder="textAreaPlaceholder"
:disabled="!client.state.currentRoom"
:maxlength="client.state.currentRoom.plugins.messagelimiter ?? null"
@input="onMessageInput"
@keyup.up.exact="!autoSuggestOpen && onNavigateIntoHistory($event, -1)"
@keyup.down.exact="!autoSuggestOpen && onNavigateIntoHistory($event, 1)"
@keydown.shift.enter.stop=""
@keydown.enter.exact.stop="!autoSuggestOpen && sendMessage()"
></textarea>
<template #no-result>
<div class="dim">No result</div>
</template>
<template #item-@="{ item }">
<div class="autosuggest-item">
{{ item.label }}
</div>
</template>
<template #item-#="{ item }">
<div class="autosuggest-item">
{{ item.label }}
</div>
</template>
<template #item-:="{ item }">
<div class="autosuggest-item flex items-center gap-4">
<img :src="item.url" class="w-4 h-4" />
<span>{{ item.label }}</span>
</div>
</template>
</Mentionable>
</div>
<!-- Send button -->
Expand All @@ -306,4 +359,8 @@ const cancelAudio = function () {
</div>
</template>
<style scoped></style>
<style scoped lang="postcss">
.autosuggest-item {
@apply px-1 cursor-pointer;
}
</style>
Loading

0 comments on commit 7e499b9

Please sign in to comment.