Update
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -15,7 +15,7 @@
|
||||
"@angular/forms": "^21.2.0",
|
||||
"@angular/platform-browser": "^21.2.0",
|
||||
"@angular/router": "^21.2.0",
|
||||
"@chatenium/chatenium-sdk": "^1.0.6",
|
||||
"@chatenium/chatenium-sdk": "^1.0.8",
|
||||
"@ngx-translate/core": "^17.0.0",
|
||||
"@ngx-translate/http-loader": "^17.0.0",
|
||||
"@taiga-ui/addon-charts": "^5.1.0",
|
||||
@@ -987,9 +987,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@chatenium/chatenium-sdk": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.0.6.tgz",
|
||||
"integrity": "sha512-lNrgIiXJWYc7KiQCtzWqgLQ0RpEoRWU2Z/GVEjcMyMlCMCX6Lu0g0cKj/8mREhk7kbJUAEcHnB18w5VjNicbIg==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.0.8.tgz",
|
||||
"integrity": "sha512-avJ61UPEk6GQ6+0fbA9Tl2VZU7O7RE/evAXIsTxA0S0oQzltWFA4k0ttPCPw8LLEpNNQCoQk/2dd0l2+y75t4Q==",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.4.0",
|
||||
"axios": "^1.14.0",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@angular/forms": "^21.2.0",
|
||||
"@angular/platform-browser": "^21.2.0",
|
||||
"@angular/router": "^21.2.0",
|
||||
"@chatenium/chatenium-sdk": "^1.0.6",
|
||||
"@chatenium/chatenium-sdk": "^1.0.8",
|
||||
"@ngx-translate/core": "^17.0.0",
|
||||
"@ngx-translate/http-loader": "^17.0.0",
|
||||
"@taiga-ui/addon-charts": "^5.1.0",
|
||||
|
||||
@@ -27,6 +27,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"elements": {
|
||||
"messageBox": {
|
||||
"placeholder": "Type a message...",
|
||||
"uplDrag": {
|
||||
"upload": "Drop here to upload",
|
||||
"transfer": "Drop here to transfer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: 350px minmax(0, 1fr);
|
||||
height: 100svh;
|
||||
overflow: hidden;
|
||||
|
||||
#chatnav {
|
||||
display: grid;
|
||||
@@ -36,7 +37,6 @@
|
||||
}
|
||||
|
||||
main {
|
||||
padding-top: 65px;
|
||||
background: var(--tui-background-base-alt);
|
||||
border-radius: 20px 0 0 20px;
|
||||
margin: 10px 0 10px 10px;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {LoadStatus, ServiceManager} from '../service-manager';
|
||||
import {IndexedDB} from '../storage/indexed-db';
|
||||
import {DmList} from './dm-list/dm-list';
|
||||
import {JsonPipe} from '@angular/common';
|
||||
import {WebSocketHandler} from '@chatenium/chatenium-sdk/core/webSocketHandler';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chat',
|
||||
@@ -30,7 +31,9 @@ export class Chat implements OnInit {
|
||||
|
||||
async ngOnInit() {
|
||||
this.indexedDb.openDatabase().then(async () => {
|
||||
this.serviceManager.currentSession.set(await this.serviceManager.sessionManager.loadPreferredSession())
|
||||
const session = await this.serviceManager.sessionManager.loadPreferredSession()
|
||||
this.serviceManager.currentSession.set(session)
|
||||
await WebSocketHandler.getInstance().connect(session.userData.userid, session.token)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
@defer (when store) {
|
||||
<navbar>
|
||||
<div class="items-left">
|
||||
<oimg [src]="store.chatData.pfp" height="50px" width="50px" [radius]="15"></oimg>
|
||||
<oimg [src]="store.chatData().pfp" height="50px" width="50px" [radius]="15"></oimg>
|
||||
<div class="chat-data">
|
||||
@if (store.chatData.displayName == "") {
|
||||
<span class="main-name">{{'@'+store.chatData.username}}</span>
|
||||
@if (store.chatData().displayName == "") {
|
||||
<span class="main-name">{{'@'+store.chatData().username}}</span>
|
||||
} @else {
|
||||
<span class="main-name">{{store.chatData.displayName}}</span>
|
||||
<span class="alt-name">{{'@'+store.chatData.username}}</span>
|
||||
<span class="main-name">{{store.chatData().displayName}}</span>
|
||||
<span class="alt-name">{{'@'+store.chatData().username}}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,11 +17,10 @@
|
||||
<tui-icon icon="@tui.phone"/>
|
||||
</button>
|
||||
</div>
|
||||
{{chatid}}
|
||||
</navbar>
|
||||
|
||||
<main>
|
||||
<messages [messages]="store.messages()"/>
|
||||
|
||||
</main>
|
||||
|
||||
<message-box/>
|
||||
<message-box [viewModel]="store.messageBox"/>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:host {
|
||||
height: 100%;
|
||||
height: 95svh;
|
||||
display: grid;
|
||||
grid-template-rows: 70px minmax(0, 1fr) auto;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {Component, inject, OnInit, signal} from '@angular/core';
|
||||
import {DmStorage, ServiceManager} from '../../service-manager';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {DMService} from '@chatenium/chatenium-sdk/services/dmService';
|
||||
@@ -7,6 +7,10 @@ import {Navbar} from '../elements/navbar/navbar';
|
||||
import {Oimg} from '../elements/oimg/oimg';
|
||||
import {TuiButton, TuiIcon} from '@taiga-ui/core';
|
||||
import {MessageBox} from '../elements/message-box/message-box';
|
||||
import {Messages} from '../elements/messages/messages';
|
||||
import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema';
|
||||
import {Message} from '@chatenium/chatenium-sdk/domain/dmService.schema';
|
||||
import {MessageBoxViewModel} from '../elements/message-box/message-box-viewmodel';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dm',
|
||||
@@ -15,7 +19,8 @@ import {MessageBox} from '../elements/message-box/message-box';
|
||||
Oimg,
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
MessageBox
|
||||
MessageBox,
|
||||
Messages
|
||||
],
|
||||
templateUrl: './dm.html',
|
||||
styleUrl: './dm.scss',
|
||||
@@ -31,14 +36,23 @@ export class Dm implements OnInit {
|
||||
return this.serviceManager.dmServices()[this.chatid]
|
||||
}
|
||||
|
||||
async sendMessage(message: string) {
|
||||
await this.store.service.sendMessage(message)
|
||||
}
|
||||
|
||||
onWsListen(action: string, message: string) {
|
||||
console.log(action, message)
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.params.subscribe(params => {
|
||||
this.route.params.subscribe(async params => {
|
||||
const chatid = params['chatid']
|
||||
this.chatid = chatid
|
||||
console.log(this.serviceManager.chats())
|
||||
const session = this.serviceManager.currentSession();
|
||||
const chatData = this.serviceManager.chats().find(chat => chat.chatid == chatid)
|
||||
|
||||
// Setup storage
|
||||
if (!this.serviceManager.dmServices()[chatid] && session != null && chatData != null) {
|
||||
this.serviceManager.dmServices()[chatid] = {
|
||||
service: new DMService(
|
||||
@@ -46,11 +60,20 @@ export class Dm implements OnInit {
|
||||
session.token,
|
||||
chatid,
|
||||
this.indexedDb.getApi(),
|
||||
() => {}
|
||||
(action, data) => {
|
||||
this.onWsListen(action, data)
|
||||
}
|
||||
),
|
||||
chatData: chatData
|
||||
chatData: signal<Chat>(chatData),
|
||||
messages: signal<Message[]>([]),
|
||||
messageBox: new MessageBoxViewModel(
|
||||
(msg) => this.sendMessage(msg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.store.messages.set(await this.serviceManager.dmServices()[chatid].service.get())
|
||||
await this.store.service.joinWebSocketRoom()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
11
src/app/chat/elements/message-box/message-box-viewmodel.ts
Normal file
11
src/app/chat/elements/message-box/message-box-viewmodel.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {signal} from '@angular/core';
|
||||
|
||||
export class MessageBoxViewModel {
|
||||
onMessageSend: (message: string) => void
|
||||
|
||||
constructor(onMessageSend: (message: string) => void) {
|
||||
this.onMessageSend = onMessageSend
|
||||
}
|
||||
|
||||
message = signal<string>("")
|
||||
}
|
||||
@@ -1,7 +1,36 @@
|
||||
<div id="message-box">
|
||||
<div></div>
|
||||
<div class="items-middle">
|
||||
<textarea></textarea>
|
||||
<div id="dragOverlay" tuiGroup orientation="vertical" [class.show]="isDraggingOverWindow">
|
||||
<div class="method">
|
||||
<div class="icon-holder">
|
||||
<tui-icon icon="@tui.file-up"/>
|
||||
</div>
|
||||
<span>{{ "chat.elements.messageBox.uplDrag.upload"|translate }}</span>
|
||||
</div>
|
||||
<div class="method">
|
||||
<div class="icon-holder">
|
||||
<tui-icon icon="@tui.cloud-sync"/>
|
||||
</div>
|
||||
<span>{{ "chat.elements.messageBox.uplDrag.transfer"|translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="message-box" [style]="'border-radius:'+messageBoxRadius+'px;'">
|
||||
<div class="items-left">
|
||||
<button tuiButton appearance="flat">
|
||||
<tui-icon icon="@tui.file-up"/>
|
||||
</button>
|
||||
|
||||
<button tuiButton appearance="flat">
|
||||
<tui-icon icon="@tui.cloud-sync"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="items-middle">
|
||||
<textarea [style]="'height:'+textareaHeight+'px;'" #message (input)="onTextAreaInput(message)" [(ngModel)]="viewModel().message"></textarea>
|
||||
<span class="placeholder"
|
||||
[class.hidden]="message.value != ''">{{ "chat.elements.messageBox.placeholder"|translate }}</span>
|
||||
</div>
|
||||
<div class="items-right">
|
||||
<button tuiButton appearance="flat" (click)="viewModel().onMessageSend(message.value)">
|
||||
<tui-icon icon="@tui.send"/>
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
@@ -3,27 +3,117 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
#dragOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.method {
|
||||
width: 800px;
|
||||
height: 200px;
|
||||
background: var(--tui-background-accent-1-hover);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
|
||||
.icon-holder {
|
||||
border-radius: 50%;
|
||||
background: var(--tui-background-accent-1);
|
||||
padding: 20px;
|
||||
|
||||
tui-icon {
|
||||
font-size: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#message-box {
|
||||
transition: all 0.2s ease-in-out;
|
||||
width: 60%;
|
||||
background: var(--tui-background-base-alt);
|
||||
height: 75px;
|
||||
border-radius: 200px;
|
||||
min-height: 75px;
|
||||
max-height: 200px;
|
||||
border: 2px solid var(--tui-border-normal);
|
||||
display: grid;
|
||||
grid-template-columns: 10% 80% 10%;
|
||||
grid-template-columns: 85px 1fr 100px;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
|
||||
.items-left, .items-middle, .items-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.items-left, .items-right {
|
||||
button {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
|
||||
tui-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-middle {
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
color: var(--tui-text-01);
|
||||
font-size: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
height: 25px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
color: gray;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&.hidden {
|
||||
margin-left: 10px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-right {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,59 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {TuiAppearance} from '@taiga-ui/core';
|
||||
import {Component, HostListener, inject, input} from '@angular/core';
|
||||
import {TuiAppearance, TuiButton, TuiGroup, TuiIcon, TuiScrollbarDirective} from '@taiga-ui/core';
|
||||
import {TranslatePipe} from '@ngx-translate/core';
|
||||
import {ServiceManager} from '../../../service-manager';
|
||||
import {MessageBoxViewModel} from './message-box-viewmodel';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'message-box',
|
||||
imports: [
|
||||
TuiAppearance
|
||||
TuiAppearance,
|
||||
TranslatePipe,
|
||||
TuiScrollbarDirective,
|
||||
TuiGroup,
|
||||
TuiIcon,
|
||||
TuiButton,
|
||||
FormsModule
|
||||
],
|
||||
templateUrl: './message-box.html',
|
||||
styleUrl: './message-box.scss',
|
||||
})
|
||||
export class MessageBox {}
|
||||
export class MessageBox {
|
||||
viewModel = input.required<MessageBoxViewModel>()
|
||||
|
||||
textareaHeight = 25
|
||||
messageBoxRadius = 200
|
||||
|
||||
private dragCounter = 0;
|
||||
isDraggingOverWindow = false;
|
||||
|
||||
@HostListener('window:dragenter', ['$event'])
|
||||
onDragEnter(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
this.dragCounter++;
|
||||
|
||||
if (this.dragCounter === 1) {
|
||||
this.isDraggingOverWindow = true;
|
||||
console.log("Drag entered the window area");
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:dragleave', ['$event'])
|
||||
onDragLeave(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
this.dragCounter--;
|
||||
|
||||
if (this.dragCounter === 0) {
|
||||
this.isDraggingOverWindow = false;
|
||||
console.log("Drag left the window completely");
|
||||
}
|
||||
}
|
||||
|
||||
onTextAreaInput(e: HTMLTextAreaElement) {
|
||||
const calculatedHeight = e.value.split(/\r?\n/).length * 25
|
||||
const calculatedRadius = (200 - this.textareaHeight)
|
||||
this.textareaHeight = calculatedHeight > 180 ? 180 : calculatedHeight
|
||||
this.messageBoxRadius = calculatedRadius < 30 ? 30 : calculatedRadius
|
||||
}
|
||||
}
|
||||
|
||||
19
src/app/chat/elements/messages/messages.html
Normal file
19
src/app/chat/elements/messages/messages.html
Normal file
@@ -0,0 +1,19 @@
|
||||
@if (serviceManager.currentSession != null) {
|
||||
@for (message of messages(); track message.msgid; let i = $index) {
|
||||
<div
|
||||
class="message"
|
||||
[class.author]="message.author == serviceManager.currentSession()!.userData.userid"
|
||||
[class.chained_start]="isMessageStartOfChain(i)"
|
||||
[class.chained_middle]="isMessageMiddleInChain(i)"
|
||||
[class.chained_end]="isMessageEndOfChain(i)"
|
||||
>
|
||||
<div class="above">
|
||||
<span>{{message.sent_at.T * 1000 | date: 'HH:mm'}}</span>
|
||||
</div>
|
||||
<div class="bubble">
|
||||
<span class="message-text">{{message.message}}</span>
|
||||
</div>
|
||||
<div class="below"></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
69
src/app/chat/elements/messages/messages.scss
Normal file
69
src/app/chat/elements/messages/messages.scss
Normal file
@@ -0,0 +1,69 @@
|
||||
:host {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
overflow-y: scroll;
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.author {
|
||||
align-items: end;
|
||||
|
||||
.bubble {
|
||||
background: var(--tui-background-accent-1-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.chained_start {
|
||||
margin-top: 10px;
|
||||
|
||||
.above, .below {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.above {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
border-radius: 25px 10px 10px 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.chained_middle {
|
||||
.bubble {
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.chained_end {
|
||||
.bubble {
|
||||
border-radius: 10px 10px 10px 25px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.above, .below {
|
||||
color: gray;
|
||||
opacity: 70%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
background: var(--tui-background-neutral-2);
|
||||
max-width: 350px;
|
||||
min-width: 250px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
|
||||
.message-text {
|
||||
white-space: none;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/app/chat/elements/messages/messages.spec.ts
Normal file
22
src/app/chat/elements/messages/messages.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Messages } from './messages';
|
||||
|
||||
describe('Messages', () => {
|
||||
let component: Messages;
|
||||
let fixture: ComponentFixture<Messages>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Messages],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Messages);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
109
src/app/chat/elements/messages/messages.ts
Normal file
109
src/app/chat/elements/messages/messages.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import {Component, inject, input} from '@angular/core';
|
||||
import {Message} from '@chatenium/chatenium-sdk/domain/dmService.schema';
|
||||
import {Message as NetworkMessage} from '@chatenium/chatenium-sdk/domain/textChannelService.schema'
|
||||
import {ServiceManager} from '../../../service-manager';
|
||||
import {DatePipe} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'messages',
|
||||
imports: [
|
||||
DatePipe
|
||||
],
|
||||
templateUrl: './messages.html',
|
||||
styleUrl: './messages.scss',
|
||||
})
|
||||
export class Messages {
|
||||
serviceManager = inject(ServiceManager)
|
||||
|
||||
messages = input<Message[] | NetworkMessage[]>([])
|
||||
|
||||
/**
|
||||
* Helps code readability by specifying what type of messages are being processed.
|
||||
* Example: messageAsDm(message).author == userid -- We are specifying that we are handling dm messages
|
||||
* @param message
|
||||
*/
|
||||
messageAsDm(message: Message | NetworkMessage) {
|
||||
return message == undefined ? <Message>{author: "no"} : message as Message
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps code readability by specifying what type of messages are being processed
|
||||
Example: messageAsNetwork(message).author.userid == userid -- We are specifying that we are handling network messages
|
||||
* @param message
|
||||
*/
|
||||
messageAsNetwork(message: Message | NetworkMessage) {
|
||||
return message == undefined ? <NetworkMessage>{author: {userid: "no"}} : message as NetworkMessage
|
||||
}
|
||||
|
||||
isMessageStartOfChain(i: number) {
|
||||
const message = this.messages()[i];
|
||||
if (!message) return false;
|
||||
|
||||
const prev = this.messages()[i - 1];
|
||||
|
||||
// is author == last msg's author
|
||||
if (prev) {
|
||||
if (
|
||||
(typeof message.author === 'string' && this.messageAsDm(prev).author !== this.messageAsDm(message).author) &&
|
||||
this.messageAsNetwork(prev).author.userid !== this.messageAsNetwork(message).author.userid
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// same author but time gap between this and prev msg
|
||||
if (
|
||||
Math.abs(message.sent_at.T - prev.sent_at.T) >= 60
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return i === 0;
|
||||
}
|
||||
|
||||
isMessageMiddleInChain(i: number) {
|
||||
const message = this.messages()[i];
|
||||
if (!message || i == 0) return false;
|
||||
|
||||
const prev = this.messages()[i - 1];
|
||||
|
||||
// message must be from the same author and have little time gap between
|
||||
return (
|
||||
(
|
||||
(typeof message.author === 'string' && this.messageAsDm(prev).author == this.messageAsDm(message).author) ||
|
||||
this.messageAsNetwork(prev).author.userid == this.messageAsNetwork(message).author.userid
|
||||
) &&
|
||||
Math.abs(message.sent_at.T - prev.sent_at.T) <= 60
|
||||
);
|
||||
}
|
||||
|
||||
isMessageEndOfChain(i: number) {
|
||||
// prevent false positive
|
||||
if (this.isMessageStartOfChain(i)) return false;
|
||||
|
||||
const message = this.messages()[i];
|
||||
if (!message) return false;
|
||||
|
||||
const next = this.messages()[i + 1];
|
||||
|
||||
// First condition: next author is different
|
||||
if (next) {
|
||||
if (
|
||||
(typeof message.author === 'string' && this.messageAsDm(next).author != this.messageAsDm(message).author) ||
|
||||
this.messageAsNetwork(next).author.userid != this.messageAsNetwork(message).author.userid
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// same author but time gap between this and next msg
|
||||
if (
|
||||
Math.abs(message.sent_at.T - next.sent_at.T) >= 60
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// last message is always end
|
||||
return i + 1 === this.messages().length;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {inject, Injectable, signal} from '@angular/core';
|
||||
import {inject, Injectable, Signal, signal, WritableSignal} from '@angular/core';
|
||||
import {IndexedDB} from './storage/indexed-db';
|
||||
import {Keyring} from './storage/keyring';
|
||||
import {KeyValue} from './storage/key-value';
|
||||
@@ -7,6 +7,8 @@ import {Session} from '@chatenium/chatenium-sdk/domain/sessionManager.schema';
|
||||
import {ChatService} from '@chatenium/chatenium-sdk/services/chatService';
|
||||
import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema';
|
||||
import {DMService} from '@chatenium/chatenium-sdk/services/dmService';
|
||||
import {Message} from '@chatenium/chatenium-sdk/domain/dmService.schema';
|
||||
import {MessageBoxViewModel} from './chat/elements/message-box/message-box-viewmodel';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -35,5 +37,7 @@ export enum LoadStatus {
|
||||
|
||||
export interface DmStorage {
|
||||
service: DMService
|
||||
chatData: Chat
|
||||
messages: WritableSignal<Message[]>
|
||||
chatData: WritableSignal<Chat>
|
||||
messageBox: MessageBoxViewModel
|
||||
}
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
.drag-active * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--tui-background-neutral-2-pressed);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
Reference in New Issue
Block a user