diff --git a/package-lock.json b/package-lock.json index 795a2c4..359482b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,10 @@ "@angular/forms": "^21.2.0", "@angular/platform-browser": "^21.2.0", "@angular/router": "^21.2.0", - "@chatenium/chatenium-sdk": "^1.1.3", + "@chatenium/chatenium-sdk": "^1.1.5", + "@fortawesome/angular-fontawesome": "^4.0.0", + "@fortawesome/free-brands-svg-icons": "^7.1.0", + "@fortawesome/free-solid-svg-icons": "^7.1.0", "@ngx-translate/core": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0", "@taiga-ui/addon-charts": "^5.1.0", @@ -988,13 +991,12 @@ } }, "node_modules/@chatenium/chatenium-sdk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.1.3.tgz", - "integrity": "sha512-y1+ls4MnMu9/t0vWtQEjIw1QPwk0peQbdEx738xu8OgxD/0nEBn6SGhhnesPHcQmdnkhZe/xiRVrIlDmZxGUHg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.1.5.tgz", + "integrity": "sha512-seEsxlRk96WHsE4h6oPPa5nGZioePIZpB5qk0Xs3CaKNbVJKH02/Q5HW2PIi6DHw+djAENWu5zVXIic+9DrqZw==", "dependencies": { "@faker-js/faker": "^10.4.0", "axios": "^1.14.0", - "dotenv": "^17.4.0", "msw": "^2.12.14", "uuid": "^13.0.0" } @@ -1652,6 +1654,64 @@ "npm": ">=10" } }, + "node_modules/@fortawesome/angular-fontawesome": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-4.0.0.tgz", + "integrity": "sha512-TCqHqT5ovFY1A4RgMpoBUgS+RX3OVs39+CzHFgzDhbCPAopOa26J748TZJcuZwJAvGAk9tbWeVEmWuLByINAeg==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^7.1.0", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@angular/core": "^21.0.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz", + "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz", + "integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.2.0.tgz", + "integrity": "sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz", + "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@gar/promise-retry": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", @@ -5616,18 +5676,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/dotenv": { - "version": "17.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz", - "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index 60c1ecf..79c5e74 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,10 @@ "@angular/forms": "^21.2.0", "@angular/platform-browser": "^21.2.0", "@angular/router": "^21.2.0", - "@chatenium/chatenium-sdk": "^1.1.3", + "@chatenium/chatenium-sdk": "^1.1.5", + "@fortawesome/angular-fontawesome": "^4.0.0", + "@fortawesome/free-brands-svg-icons": "^7.1.0", + "@fortawesome/free-solid-svg-icons": "^7.1.0", "@ngx-translate/core": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0", "@taiga-ui/addon-charts": "^5.1.0", diff --git a/public/Kinn-Book.ttf b/public/Kinn-Book.ttf new file mode 100644 index 0000000..dc89fa1 Binary files /dev/null and b/public/Kinn-Book.ttf differ diff --git a/public/Kinn-Heavy.ttf b/public/Kinn-Heavy.ttf new file mode 100644 index 0000000..61ade82 Binary files /dev/null and b/public/Kinn-Heavy.ttf differ diff --git a/public/favicon.ico b/public/favicon.ico index 57614f9..978d2c3 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/i18n/en.json b/public/i18n/en.json index f24ebc6..802c6b1 100644 --- a/public/i18n/en.json +++ b/public/i18n/en.json @@ -1,4 +1,80 @@ { + "version": "3.0 Beta 2 (2026.04.10)", + "ok": "Ok", + "aChatProgram": "A messaging platform that you can trust.", + "fast": "Fast", + "secure": "Secure", + "independent": "Independent", + "scrollDownForMore": "Scroll down for more", + "whatIsChtn": "Chatenium is a chat platform aiming to provide a secure, well integrated fast chatting experience across any devices. You can create a new account for free and start chatting and broadcasting messages.", + "home": { + "chtn": "Chatenium ", + "chtnIs": "Chatenium is ", + "help": "Help", + "blog": "Blog", + "apiSpecs": "API specifications", + "adaptsToYou": "adapts to you", + "adaptsToYouDesc": "We actively work on bringing Chatenium to every device, but not the fast way, the good way by developing a native application for each platform.", + "chtnOnWeb": "Chatenium On Web", + "chtnOnWebDesc": "Built for maximum compatibility. The Web will work on every device you have.", + "chtnOnAndroid": "Chatenium On Android™", + "chtnOnAndroidDesc": "Built to work beautifully with your Android™ device. Blends in beautifully with your Material You theme.", + "chtnOnApple": "Chatenium On Apple®", + "chtnOnAppleDesc": "Built with the latest Liquid Glass design to blend in and work flawlessly across all your Apple® devices.", + "chtnOnWindows": "Chatenium On Windows®", + "chtnOnWindowsDesc": "We are also expanding our native apps to Microsoft Windows®, because using a web app is resource intensive and inferior to a native application. So we built a fully native Windows® application using WinUI3 + C#.", + "chtnOnLinux": "Chatenium On Linux", + "chtnOnLinuxDesc": "Built on the latest GNOME Adwaita design language to beautifully blend into the GNOME desktop environment on Linux.", + "chtnEcho": "Chatenium Echo", + "chtnEchoDesc": "Built on the GNOME GTK design language to beautifully blend into any GTK based desktop environment on Linux.", + "chtnReson": "Chatenium Reson", + "chtnResonDesc": "Built on the KDE Kirigami design language to beautifully blend into KDE or any other Qt based desktop environment on Linux.", + "tba": "To be announced", + "secure": "secure", + "secureDesc": "Chatenium uses the latest algorithms to protect your data from any third-parties.", + "secureCdn": "Encrypted CDN", + "secureCdnDesc": "All uploaded media is encrypted and never accessed by anyone outside your chat. Not even by us. All deleted media are permanently removed from our servers and databases.", + "encryptedText": "Encrypted messages", + "encryptedTextDesc": "Any message that is sent to any DM chat or channel is encrypted and stored securely in our databases. We do not share your messages with any third-parties.", + "zeroDataCollection": "Zero data collection", + "zeroDataCollectionDesc": "Our policies allow zero data collection. We do not collect any data from our clients.", + "secureCalling": "Secure calls", + "secureCallingDesc": "When using Chatenium Call, all audio and video tracks are sent to our servers for secure distribution. No audio or video is ever recorded by our servers under any circumstances.", + "zeroDigitalFootprint": "Zero digital footprint", + "zeroDigitalFootprintDesc": "We make it easy to delete your account from our services and we make sure to remove all data made by you.", + "underDevelopment": "Under development", + "devPreview": "Developer Preview", + "weAreExcitedFor": "We are excited for ", + "you": "you", + "weAreExcitedForYouDesc": "Our community awaits you with love. We are independent, so you can express your true feelings without getting censored.", + "enterChtnOnWeb": "Enter Chatenium On Web", + "enterChtnOnWebDesc": "Register your account here are access the latest experimental features we can offer. The web is mobile-friendly and offers offline mode.", + "downloadChtnOnAndroid": "Get Chatenium On Android", + "downloadChtnOnAndroidDesc": "We are preparing a Google Play release right now. Don't worry, you can get access to .apk releases in the official 'Chatenium' network.", + "downloadChtnOnApple": "Get Chatenium On Apple", + "downloadChtnOnAppleDesc": "We are preparing an App Store release right now. Don't worry, you can get access to .ipa (.dmg soon) releases in the official 'Chatenium' network.", + "storeReleaseSoon": "Store release coming soon...", + "closedAlpha": "Closed alpha", + "openBeta": "Beta", + "openBetaDesc": "Participate in public beta testing to help us create the best app experience!", + "closedAlphaDesc": "Great news! You are eligible to participate in the testing program. All we need is your e-mail address.", + "alphaRequestedSuccessfully": "Request sent successfully! We will get back to you later.", + "alphaRequestAlreadySent": "You already requested an alpha access! We will get back to you later.", + "alphaRequestError": "Something went wrong. Please try again later.", + "cookies": "Cookies", + "cookiesDesc": "Chatenium is using cookies to securely store your session token. No cookies are used for tracking purposes", + "understood": "Understood", + "privacyPolicy": "Privacy policy", + "tos": "Terms of services", + "requestAlphaAccess": "Start beta testing", + "joinGoogleGroup": "Our Google Group", + "joinGoogleGroupWhy": "All you need to do is join our Google Group and you can join the beta testing program via the link provided in the group.", + "testFlight": "Apple TestFlight", + "testFlightDesc": "Start testing the application on your Apple device via TestFlight!", + "downloadOnWindows": "Get Chatenium On Windows", + "microsoftStore": "Microsoft Store", + "microsoftStoreDesc": "Start testing the application on your Windows computer via the Microsoft Store!" + }, "signIn": { "formTitle": "Sign in", "mainInput": { @@ -18,6 +94,13 @@ } }, "chat": { + "changeLogDialog": { + "label": "Chatenium has been updated", + "changeLog": { + "1": "UI changes and bug fixes in the video player", + "2": "UI overflow fix in chat navigation bar" + } + }, "chatnav": { "dmList": { "newChat": "Start new chat", @@ -30,6 +113,8 @@ }, "elements": { "messageBox": { + "editMessageLabel": "Editing message: ", + "attachments": "Attachment(s)", "placeholder": "Type a message...", "message": "Message", "uplDrag": { @@ -39,6 +124,12 @@ "fileUploadDialog": { "label": "Upload files" } + }, + "messages": { + "contextMenu": { + "edit": "Edit", + "delete": "Delete" + } } } } diff --git a/public/images/homepage/new_customi_dark.png b/public/images/homepage/new_customi_dark.png new file mode 100644 index 0000000..6c78d31 Binary files /dev/null and b/public/images/homepage/new_customi_dark.png differ diff --git a/public/images/homepage/new_customi_light.png b/public/images/homepage/new_customi_light.png new file mode 100644 index 0000000..1f8cc69 Binary files /dev/null and b/public/images/homepage/new_customi_light.png differ diff --git a/public/images/homepage/new_dm_dark.png b/public/images/homepage/new_dm_dark.png new file mode 100644 index 0000000..32ae4c3 Binary files /dev/null and b/public/images/homepage/new_dm_dark.png differ diff --git a/public/images/homepage/new_dm_light.png b/public/images/homepage/new_dm_light.png new file mode 100644 index 0000000..627cd44 Binary files /dev/null and b/public/images/homepage/new_dm_light.png differ diff --git a/public/images/homepage/new_network_dark.png b/public/images/homepage/new_network_dark.png new file mode 100644 index 0000000..388b0ed Binary files /dev/null and b/public/images/homepage/new_network_dark.png differ diff --git a/public/images/homepage/new_network_discovery_dark.png b/public/images/homepage/new_network_discovery_dark.png new file mode 100644 index 0000000..2140bb1 Binary files /dev/null and b/public/images/homepage/new_network_discovery_dark.png differ diff --git a/public/images/homepage/new_network_discovery_light.png b/public/images/homepage/new_network_discovery_light.png new file mode 100644 index 0000000..c723faa Binary files /dev/null and b/public/images/homepage/new_network_discovery_light.png differ diff --git a/public/images/homepage/new_network_light.png b/public/images/homepage/new_network_light.png new file mode 100644 index 0000000..71c7e53 Binary files /dev/null and b/public/images/homepage/new_network_light.png differ diff --git a/public/images/homepage/new_picture_stats_dark.png b/public/images/homepage/new_picture_stats_dark.png new file mode 100644 index 0000000..01a7494 Binary files /dev/null and b/public/images/homepage/new_picture_stats_dark.png differ diff --git a/public/images/homepage/new_picture_stats_light.png b/public/images/homepage/new_picture_stats_light.png new file mode 100644 index 0000000..bddae0d Binary files /dev/null and b/public/images/homepage/new_picture_stats_light.png differ diff --git a/public/images/homepage/new_pictures_dark.png b/public/images/homepage/new_pictures_dark.png new file mode 100644 index 0000000..416294a Binary files /dev/null and b/public/images/homepage/new_pictures_dark.png differ diff --git a/public/images/homepage/new_pictures_light.png b/public/images/homepage/new_pictures_light.png new file mode 100644 index 0000000..b7001da Binary files /dev/null and b/public/images/homepage/new_pictures_light.png differ diff --git a/public/images/logo/logo.png b/public/images/logo/logo.png new file mode 100755 index 0000000..4783c4d Binary files /dev/null and b/public/images/logo/logo.png differ diff --git a/public/images/logo/logo_beta.png b/public/images/logo/logo_beta.png new file mode 100755 index 0000000..2210213 Binary files /dev/null and b/public/images/logo/logo_beta.png differ diff --git a/public/images/logo/logo_short.png b/public/images/logo/logo_short.png new file mode 100755 index 0000000..8e0a004 Binary files /dev/null and b/public/images/logo/logo_short.png differ diff --git a/public/images/logo/logo_short_dark.png b/public/images/logo/logo_short_dark.png new file mode 100755 index 0000000..203f9ee Binary files /dev/null and b/public/images/logo/logo_short_dark.png differ diff --git a/public/images/logo/logo_short_dark_1024_1024.png b/public/images/logo/logo_short_dark_1024_1024.png new file mode 100755 index 0000000..a6cfa08 Binary files /dev/null and b/public/images/logo/logo_short_dark_1024_1024.png differ diff --git a/public/images/logo/logo_short_dark_120_120.png b/public/images/logo/logo_short_dark_120_120.png new file mode 100755 index 0000000..4ff21d6 Binary files /dev/null and b/public/images/logo/logo_short_dark_120_120.png differ diff --git a/public/images/logo/logo_short_old.png b/public/images/logo/logo_short_old.png new file mode 100755 index 0000000..cd48857 Binary files /dev/null and b/public/images/logo/logo_short_old.png differ diff --git a/public/images/logo/logoold.png b/public/images/logo/logoold.png new file mode 100755 index 0000000..573c05b Binary files /dev/null and b/public/images/logo/logoold.png differ diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 29b9d27..f439905 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -2,11 +2,19 @@ import {Routes} from '@angular/router'; import {SignIn} from './signin/signin'; import {Chat} from './chat/chat'; import {Dm} from './chat/dm/dm'; +import {noAuthGuard} from './guards/no-auth-guard'; +import {authNeededGuard} from './guards/auth-needed-guard'; +import {Homepage} from './homepage/homepage'; +import {Privacy} from './privacy/privacy'; +import {TOS} from './tos/tos'; export const routes: Routes = [ - {path: 'signin', component: SignIn}, + {path: '', component: Homepage}, + {path: 'privacy', component: Privacy}, + {path: 'tos', component: TOS}, + {path: 'signin', component: SignIn, canActivate: [noAuthGuard]}, { - path: 'chat', component: Chat, children: [ + path: 'chat', component: Chat, canActivate: [authNeededGuard], children: [ {path: 'dm/:chatid', component: Dm}, ] }, diff --git a/src/app/chat/chat.html b/src/app/chat/chat.html index 083e3a7..b1c7552 100644 --- a/src/app/chat/chat.html +++ b/src/app/chat/chat.html @@ -1,3 +1,13 @@ + +

{{"version"|translate}}

+ + + +
+ @if (serviceManager.currentSession() == null) {
@@ -10,15 +20,15 @@ - - - diff --git a/src/app/chat/chat.scss b/src/app/chat/chat.scss index 5d51542..65c1ab7 100644 --- a/src/app/chat/chat.scss +++ b/src/app/chat/chat.scss @@ -7,6 +7,7 @@ #chatnav { display: grid; grid-template-columns: 70px minmax(0, 1fr); + height: 100svh; aside { padding: 15px; @@ -25,6 +26,12 @@ button { height: 50px; + + tui-icon { + &::before { + color: var(--tui-background-accent-1); + } + } } } @@ -41,11 +48,13 @@ border-radius: 20px 0 0 20px; margin: 10px 0 10px 10px; padding: 15px; + overflow-y: scroll; } } #content { width: 100%; + height: 100svh; padding: 10px 10px 10px 0; #content_tint { diff --git a/src/app/chat/chat.ts b/src/app/chat/chat.ts index e598344..fdd7994 100644 --- a/src/app/chat/chat.ts +++ b/src/app/chat/chat.ts @@ -1,13 +1,15 @@ -import {Component, inject, OnInit} from '@angular/core'; +import {Component, inject, OnInit, signal} from '@angular/core'; import {RouterOutlet} from '@angular/router'; import {TuiSegmented} from '@taiga-ui/kit'; -import {TuiAppearance, TuiButton, TuiGroup, TuiIcon, TuiLoader} from '@taiga-ui/core'; +import {TuiAppearance, TuiButton, TuiDialog, TuiGroup, TuiIcon, TuiLoader} from '@taiga-ui/core'; import {SessionManager} from '@chatenium/chatenium-sdk/services/sessionManager'; 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'; +import {TranslatePipe} from '@ngx-translate/core'; +import {environment} from '../../environments/environment'; @Component({ selector: 'app-chat', @@ -20,7 +22,9 @@ import {WebSocketHandler} from '@chatenium/chatenium-sdk/core/webSocketHandler'; DmList, JsonPipe, TuiAppearance, - TuiGroup + TuiGroup, + TuiDialog, + TranslatePipe ], templateUrl: './chat.html', styleUrl: './chat.scss', @@ -29,13 +33,24 @@ export class Chat implements OnInit { serviceManager = inject(ServiceManager) indexedDb = inject(IndexedDB) + changeLogOpen = signal(false) + async ngOnInit() { this.indexedDb.openDatabase().then(async () => { const session = await this.serviceManager.sessionManager.loadPreferredSession() this.serviceManager.currentSession.set(session) await WebSocketHandler.getInstance().connect(session.userData.userid, session.token) }) + + setTimeout(() => { + const latestRead = localStorage.getItem("changeLogLastRead") + if (latestRead != environment.version) { + this.changeLogOpen.set(true) + } + }, 50) } protected readonly LoadStatus = LoadStatus; + protected readonly localStorage = localStorage; + protected readonly environment = environment; } diff --git a/src/app/chat/dm/dm.html b/src/app/chat/dm/dm.html index 287d33d..6885b6f 100644 --- a/src/app/chat/dm/dm.html +++ b/src/app/chat/dm/dm.html @@ -13,13 +13,13 @@
-
- + } diff --git a/src/app/chat/dm/dm.ts b/src/app/chat/dm/dm.ts index 2c89400..305e228 100644 --- a/src/app/chat/dm/dm.ts +++ b/src/app/chat/dm/dm.ts @@ -40,8 +40,31 @@ export class Dm implements OnInit { } async sendMessage(message: string, files: FileDataWithPreview[] | null) { + if (!files && message.trim() == "") return + const session = this.serviceManager.currentSession(); if (session != null) { + const editedMessage = this.store.messageBox.editingMessage() + if (editedMessage) { + const storedMsg = this.store.messages().find(m => m.msgid == editedMessage.messageId) + const originalMessage: Message = JSON.parse(JSON.stringify(storedMsg)) + if (storedMsg) { + storedMsg.message = message + } + + try { + await this.store.service.editMessage(editedMessage.messageId, message) + } catch (e) { + if (storedMsg) { + storedMsg.message = originalMessage.message + } + } + + this.store.messageBox.editingMessage.set(null) + this.store.messageBox.message.set("") + return + } + let attachments: Attachment[] = [] files?.forEach(file => { const extraMetaData: Record = {} @@ -74,12 +97,40 @@ export class Dm implements OnInit { forwardedFromName: "" }]) + this.scrollToBottom("smooth") + await this.store.service.sendMessage("", message, null, null, files, { fileProgressUpdate: (tempMsgId, fileId, allChunks, chunksDone) => { this.uploadProgressUpdate(tempMsgId, fileId, allChunks, chunksDone) } }) } + + this.store.messageBox.message.set("") + } + + async deleteMessage(messageId: string) { + const i = this.store.messages().findIndex(m => m.msgid == messageId) + if (i != -1) { + const originalMessage: Message = JSON.parse(JSON.stringify(this.store.messages()[i])) + this.store.messages().splice(i, 1) + try { + await this.store.service.deleteMessages([messageId]) + } catch (e) { + this.store.messages().splice(i, 0, originalMessage) + } + } + } + + scrollToBottom(anim: 'instant' | 'smooth'): void { + setTimeout(() => { + const scrollContainer = document.querySelector("#scrollContainer") + scrollContainer.scroll({ + top: scrollContainer.scrollHeight, + left: 0, + behavior: anim + }); + }, 0) } uploadProgressUpdate(tempMsgId: string, fileId: string, allChunks: number, chunksDone: number) { @@ -95,6 +146,7 @@ export class Dm implements OnInit { switch (action) { case "newMessage": targetStore.messages.update(messages => [...messages, data]); + this.scrollToBottom("smooth") break; } } @@ -135,6 +187,7 @@ export class Dm implements OnInit { const currentStore = this.serviceManager.dmServices()[chatid]; const history = await currentStore.service.get(); currentStore.messages.set(history); + this.scrollToBottom("instant") await currentStore.service.joinWebSocketRoom(); } diff --git a/src/app/chat/elements/message-box/message-box-viewmodel.ts b/src/app/chat/elements/message-box/message-box-viewmodel.ts index d3341e6..0babf48 100644 --- a/src/app/chat/elements/message-box/message-box-viewmodel.ts +++ b/src/app/chat/elements/message-box/message-box-viewmodel.ts @@ -12,4 +12,5 @@ export class MessageBoxViewModel { message = signal("") files = signal([]) dialogOpen = signal(false) + editingMessage = signal<{messageId: string, message: string} | null >(null) } diff --git a/src/app/chat/elements/message-box/message-box.html b/src/app/chat/elements/message-box/message-box.html index 65e2981..e3b6661 100644 --- a/src/app/chat/elements/message-box/message-box.html +++ b/src/app/chat/elements/message-box/message-box.html @@ -5,7 +5,7 @@ {{ "chat.elements.messageBox.uplDrag.upload"|translate }} -
+
@@ -14,13 +14,25 @@
- + @if (filterPictureVideo(viewModel().files()).length != 0) { + + @for (file of viewModel().files(); track file) { + @if (file.type == "image") { + + } + } + + } + +
@for (file of viewModel().files(); track file) { - @if (file.type == "image") { - + @if (file.type != "image") { +
+ {{ file.name }} +
} } - +
@@ -37,25 +49,45 @@
-
-
- +
+
+
+ @if (viewModel().editingMessage()) { + + {{"chat.elements.messageBox.editMessageLabel"|translate}} + @if (viewModel().editingMessage()!.message == "") { + + {{"chat.elements.messageBox.attachments"|translate}} + } @else { + {{viewModel().editingMessage()!.message}} + } + } +
-
-
- - {{ "chat.elements.messageBox.placeholder"|translate }} +
+
+ + + +
+
+ + {{ "chat.elements.messageBox.placeholder"|translate }} +
+
+ +
-
- -
-
+
diff --git a/src/app/chat/elements/message-box/message-box.scss b/src/app/chat/elements/message-box/message-box.scss index 081800f..9fd0ada 100644 --- a/src/app/chat/elements/message-box/message-box.scss +++ b/src/app/chat/elements/message-box/message-box.scss @@ -48,72 +48,115 @@ } } - #message-box { - transition: all 0.2s ease-in-out; - width: 60%; - background: var(--tui-background-base-alt); - min-height: 75px; - max-height: 200px; - border: 2px solid var(--tui-border-normal); - display: grid; - grid-template-columns: 85px 1fr 100px; + main { + width: 100%; + display: flex; align-items: center; - padding: 0 10px; + flex-direction: column; + gap: 5px; - .items-left, .items-middle, .items-right { - display: flex; - align-items: center; - } + #message-box-extension { + transition: all 0.2s ease-in-out; + width: 50%; + border-radius: 20px; + background: var(--tui-background-base-alt); + border: 2px solid var(--tui-border-normal); + transform: translateY(50px); + display: grid; + grid-template-columns: 1fr 40px; + justify-content: center; + height: 0; + + #content { + padding-left: 10px; + display: flex; + gap: 5px; + align-items: center; + } + + tui-icon { + font-size: 13px; + } - .items-left, .items-right { button { - height: 30px; + height: 26px; width: 30px; + } - tui-icon { - font-size: 20px; - } + &.shown { + transform: translateY(0); + height: 30px; } } - .items-middle { - ::-webkit-scrollbar-thumb { - background: transparent; - border-radius: 10px; + #message-box { + z-index: 5; + transition: all 0.2s ease-in-out; + width: 60%; + background: var(--tui-background-base-alt); + min-height: 75px; + max-height: 200px; + border: 2px solid var(--tui-border-normal); + display: grid; + grid-template-columns: 85px 1fr 100px; + align-items: center; + padding: 0 10px; + + .items-left, .items-middle, .items-right { + display: flex; + align-items: center; } - position: relative; + .items-left, .items-right { + button { + height: 30px; + width: 30px; - textarea { - width: 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; + tui-icon { + font-size: 20px; + } } } - } - .items-right { - display: flex; - justify-content: end; + .items-middle { + ::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 10px; + } + + position: relative; + + textarea { + width: 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; + } } } } diff --git a/src/app/chat/elements/message-box/message-box.ts b/src/app/chat/elements/message-box/message-box.ts index 0f55039..d2f5f4d 100644 --- a/src/app/chat/elements/message-box/message-box.ts +++ b/src/app/chat/elements/message-box/message-box.ts @@ -103,6 +103,15 @@ export class MessageBox { this.viewModel().dialogOpen.set(false) } + handleEnterKeydown(e: any) { + e.preventDefault() + return false + } + + filterPictureVideo(files: FileDataWithPreview[]) { + return files.filter(f => f.type == "image") + } + async processFiles(fileList: FileList) { for (let i = 0; i < fileList.length; i++) { const file = fileList[i]; diff --git a/src/app/chat/elements/messages/messages.html b/src/app/chat/elements/messages/messages.html index 6b7b97c..c4c8d8e 100644 --- a/src/app/chat/elements/messages/messages.html +++ b/src/app/chat/elements/messages/messages.html @@ -8,25 +8,59 @@ [class.chained_end]="isMessageEndOfChain(i)" >
- {{message.sent_at.T * 1000 | date: 'HH:mm'}} + {{ message.sent_at.T * 1000 | date: 'HH:mm' }}
-
- {{message.message}} +
+ {{ message.message }} - @for (file of message.files; track file) { + @for (file of filterExpressedMedia(message.files); track file) { @if (file.type == "image") { - + } @else if (file.type == "video") { @if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) { - + } @else { - + } } } + +
+ @for (file of filterNonExpressedMedia(message.files); track file) { +
+ + {{ file.fileName }} +
+ } +
+ + + + + + + +
} } diff --git a/src/app/chat/elements/messages/messages.scss b/src/app/chat/elements/messages/messages.scss index 2e12ce9..65444d1 100644 --- a/src/app/chat/elements/messages/messages.scss +++ b/src/app/chat/elements/messages/messages.scss @@ -56,6 +56,7 @@ max-width: 50%; min-width: 250px; min-height: 40px; + max-height: 400px; display: flex; flex-direction: column; justify-content: center; diff --git a/src/app/chat/elements/messages/messages.ts b/src/app/chat/elements/messages/messages.ts index 9a2eba5..c93e55d 100644 --- a/src/app/chat/elements/messages/messages.ts +++ b/src/app/chat/elements/messages/messages.ts @@ -1,17 +1,38 @@ -import {Component, inject, input} from '@angular/core'; +import {Component, EventEmitter, inject, input, Output} 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'; import {Masonry} from '../masonry/masonry'; import {VideoPlayer} from '../video-player/video-player'; +import { + TuiButton, + TuiDataListComponent, + TuiDropdown, + TuiDropdownContext, + TuiGroup, + TuiIcon, + TuiOption +} from '@taiga-ui/core'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MessageBoxViewModel} from '../message-box/message-box-viewmodel'; +import {FileDataWithPreview} from '../message-box/message-box'; +import {Attachment} from '@chatenium/chatenium-sdk/domain/common.schema'; @Component({ selector: 'messages', imports: [ DatePipe, Masonry, - VideoPlayer + VideoPlayer, + TuiDropdownContext, + TuiDropdown, + TuiButton, + TuiOption, + TranslatePipe, + TuiDataListComponent, + TuiGroup, + TuiIcon ], templateUrl: './messages.html', styleUrl: './messages.scss', @@ -19,7 +40,10 @@ import {VideoPlayer} from '../video-player/video-player'; export class Messages { serviceManager = inject(ServiceManager) - messages = input([]) + messages = input.required() + messageBoxViewModel = input.required() + + @Output() onDelete = new EventEmitter() /** * Helps code readability by specifying what type of messages are being processed. @@ -111,5 +135,13 @@ export class Messages { return i + 1 === this.messages().length; } + filterExpressedMedia(files: Attachment[]) { + return files.filter(f => f.type == "image" || f.type == "video") + } + + filterNonExpressedMedia(files: Attachment[]) { + return files.filter(f => f.type != "image" && f.type != "video") + } + protected readonly Object = Object; } diff --git a/src/app/chat/elements/oimg/oimg.html b/src/app/chat/elements/oimg/oimg.html index 37bf80b..8dc79f6 100644 --- a/src/app/chat/elements/oimg/oimg.html +++ b/src/app/chat/elements/oimg/oimg.html @@ -1 +1 @@ - + diff --git a/src/app/chat/elements/oimg/oimg.ts b/src/app/chat/elements/oimg/oimg.ts index 91a4858..fd2fbb0 100644 --- a/src/app/chat/elements/oimg/oimg.ts +++ b/src/app/chat/elements/oimg/oimg.ts @@ -15,4 +15,5 @@ export class Oimg { width = input("") src = input("") radius = input(15) + objectFit = input("cover") } diff --git a/src/app/chat/elements/video-player/video-player.html b/src/app/chat/elements/video-player/video-player.html index 2fe6e28..4822447 100644 --- a/src/app/chat/elements/video-player/video-player.html +++ b/src/app/chat/elements/video-player/video-player.html @@ -1,7 +1,7 @@ @if (playerActive) {
-