Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v5
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/directory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v5
uses: actions/checkout@v4

- name: Run Directory Tree Generator
uses: DenizAltunkapan/directory-tree-generator@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/generate-javadoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v5
Expand Down Expand Up @@ -48,4 +48,4 @@ jobs:
git diff --cached --quiet || git commit -m "chore: update Javadoc"
git push --force https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7 changes: 7 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --ssl",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
Expand All @@ -25,6 +25,7 @@
"@stomp/stompjs": "^7.1.1",
"@tailwindcss/postcss": "^4.1.10",
"dompurify": "^3.4.11",
"emoji-picker-element": "^1.29.1",
"marked": "^18.0.6",
"ngx-toastr": "^19.1.0",
"postcss": "^8.5.6",
Expand Down
19 changes: 0 additions & 19 deletions frontend/src/app/pages/private-chat-dialog/chat-reactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,6 @@ export interface ChatSticker {
src: string;
}

export const CHAT_EMOJIS = [
'😀',
'😂',
'😊',
'😍',
'😎',
'🥳',
'🤔',
'👍',
'🙏',
'🔥',
'✨',
'💙',
'🎉',
'✅',
'👀',
'💡',
];

export const CHAT_STICKERS: ChatSticker[] = [
{
id: 'spark',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,21 +202,13 @@
*ngIf="isEmojiPickerOpen || isStickerPickerOpen"
class="reaction-popover"
>
<div
<emoji-picker
*ngIf="isEmojiPickerOpen"
class="emoji-grid"
class="light"
aria-label="Emoji picker"
>
<button
*ngFor="let emoji of emojis"
type="button"
class="emoji-option"
(click)="insertEmoji(emoji)"
[attr.aria-label]="'Insert ' + emoji"
>
{{ emoji }}
</button>
</div>
(emoji-click)="insertEmoji($event)"
(keydown)="onEmojiPickerKeydown($event)"
Comment thread
Sayan-pixel marked this conversation as resolved.
></emoji-picker>

<div
*ngIf="isStickerPickerOpen"
Expand Down Expand Up @@ -264,7 +256,7 @@
class="chat-input flex-1 px-3 py-2 outline-none border bg-[var(--surface-b)] text-[var(--text-color)]"
placeholder="Type a message..."
autocomplete="off"
(focus)="closeReactionPickers()"
(focus)="onInputFocus()"
/>
<button
type="submit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,28 +282,18 @@ button {
z-index: 2;
}

.emoji-grid {
display: grid;
grid-template-columns: repeat(8, minmax(2rem, 1fr));
gap: 0.35rem;
}

.emoji-option {
aspect-ratio: 1;
border-radius: 0.5rem;
border: 1px solid transparent;
background: transparent;
font-size: 1.35rem;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;

&:hover,
&:focus-visible {
border-color: var(--color-border-hover);
background: var(--color-surface-hover);
}
emoji-picker {
width: 100%;
height: 320px;
--background: transparent;
--border-color: var(--color-border);
--input-border-color: var(--color-input-border);
--input-font-color: var(--color-text-primary);
--category-font-color: var(--color-text-secondary);
--indicator-color: var(--color-primary);
--button-hover-background: var(--color-surface-hover);
--num-columns: 8;
--emoji-size: 1.5rem;
}

.sticker-grid {
Expand Down Expand Up @@ -364,8 +354,8 @@ button {
right: 0.75rem;
}

.emoji-grid {
grid-template-columns: repeat(4, minmax(2.4rem, 1fr));
emoji-picker {
--num-columns: 6;
}

.sticker-grid {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
ViewChildren,
AfterViewChecked,
OnDestroy,
CUSTOM_ELEMENTS_SCHEMA,
HostListener,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ChatMessageDto } from '../../models/dtos/ChatMessageDto';
Expand All @@ -26,14 +28,10 @@ import { GroupService } from '../../services/group.service';
import { UserService } from '../../services/user.service';
import { GroupDto } from '../../models/dtos/GroupDto';
import { UserDto } from '../../models/dtos/UserDto';
import {
CHAT_EMOJIS,
CHAT_STICKERS,
ChatSticker,
findChatSticker,
} from './chat-reactions';
import { CHAT_STICKERS, ChatSticker, findChatSticker } from './chat-reactions';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import DOMPurify from 'dompurify';
import 'emoji-picker-element';

interface ChatMessageView {
kind: 'text' | 'sticker';
Expand Down Expand Up @@ -82,6 +80,7 @@ type ParsedDecryptedMessageBody = {
imports: [CommonModule, FormsModule],
templateUrl: './private-chat-dialog.component.html',
styleUrls: ['./private-chat-dialog.component.scss'],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class PrivateChatDialogComponent
implements OnInit, OnDestroy, AfterViewChecked
Expand Down Expand Up @@ -118,10 +117,10 @@ export class PrivateChatDialogComponent
matchedMessageIndexes: number[] = [];
private matchedMessageIndexSet = new Set<number>();
activeMatchPosition = -1;
readonly emojis = CHAT_EMOJIS;
readonly stickers = CHAT_STICKERS;
isEmojiPickerOpen = false;
isStickerPickerOpen = false;
private preventFocusClose = false;
private isTyping = false;
private typingIdleTimer: ReturnType<typeof setTimeout> | null = null;
private typingStaleSweepTimer: ReturnType<typeof setInterval> | null = null;
Expand Down Expand Up @@ -418,7 +417,16 @@ export class PrivateChatDialogComponent
};
}

insertEmoji(emoji: string): void {
insertEmoji(eventOrEmoji: any): void {
const emoji =
eventOrEmoji && eventOrEmoji.detail && eventOrEmoji.detail.unicode
? eventOrEmoji.detail.unicode
: eventOrEmoji;
Comment thread
Sayan-pixel marked this conversation as resolved.

if (typeof emoji !== 'string') {
return;
}

const input = this.messageInput?.nativeElement;
if (!input) {
this.newMessage += emoji;
Expand All @@ -429,15 +437,35 @@ export class PrivateChatDialogComponent
const end = input.selectionEnd ?? start;
this.newMessage =
this.newMessage.slice(0, start) + emoji + this.newMessage.slice(end);
this.closeReactionPickers();

this.preventFocusClose = true;
setTimeout(() => {
input.focus();
const cursorPosition = start + emoji.length;
input.setSelectionRange(cursorPosition, cursorPosition);
this.preventFocusClose = false;
}, 0);
}

onEmojiPickerKeydown(event: Event): void {
const kbEvent = event as KeyboardEvent;
if (kbEvent.key === 'ArrowLeft' || kbEvent.key === 'ArrowRight') {
const picker = event.currentTarget as HTMLElement;
// Allow the web component's own keydown handler to move focus first
setTimeout(() => {
const activeEl = picker.shadowRoot?.activeElement as HTMLElement | null;
if (activeEl?.getAttribute('role') === 'tab') {
activeEl.click();
}
}, 0);
}
}

onInputFocus(): void {
if (this.preventFocusClose) return;
this.closeReactionPickers();
}

sendSticker(sticker: ChatSticker): void {
this.closeReactionPickers();
void this.sendEncryptedStickerMessage(sticker);
Expand All @@ -453,6 +481,20 @@ export class PrivateChatDialogComponent
this.isEmojiPickerOpen = false;
}

@HostListener('document:click', ['$event'])
onDocumentClick(event: MouseEvent): void {
if (!this.isEmojiPickerOpen && !this.isStickerPickerOpen) {
return;
}
const target = event.target as HTMLElement;
if (
!target.closest('.reaction-popover') &&
!target.closest('.composer-tool-btn')
) {
this.closeReactionPickers();
}
}

closeReactionPickers(): void {
this.isEmojiPickerOpen = false;
this.isStickerPickerOpen = false;
Expand Down
Loading