Fix lint, make design more consistent, add license texts,
All checks were successful
Queue Release Build / prepare (push) Successful in 11s
Deploy Web Apps / deploy (push) Successful in 14m0s
Queue Release Build / build-linux (push) Successful in 35m41s
Queue Release Build / build-windows (push) Successful in 28m53s
Queue Release Build / finalize (push) Successful in 2m6s

This commit is contained in:
2026-04-02 04:08:53 +02:00
parent 37cac95b38
commit ae0ee8fac7
45 changed files with 988 additions and 572 deletions

View File

@@ -11,7 +11,7 @@
</span>
<button
(click)="clearReply()"
class="rounded p-1 hover:bg-secondary"
class="grid h-6 w-6 place-items-center rounded transition-colors hover:bg-secondary"
>
<ng-icon
name="lucideX"

View File

@@ -55,7 +55,7 @@
<div class="flex flex-col gap-2">
<button
(click)="saveEdit()"
class="rounded p-1 text-primary hover:bg-primary/10"
class="grid h-6 w-6 place-items-center rounded text-primary transition-colors hover:bg-primary/10"
>
<ng-icon
name="lucideCheck"
@@ -64,7 +64,7 @@
</button>
<button
(click)="cancelEdit()"
class="rounded p-1 text-muted-foreground hover:bg-secondary"
class="grid h-6 w-6 place-items-center rounded text-muted-foreground transition-colors hover:bg-secondary"
>
<ng-icon
name="lucideX"
@@ -108,7 +108,7 @@
<div class="absolute right-2 top-2 flex gap-1 opacity-0 transition-opacity group-hover/img:opacity-100">
<button
(click)="openLightbox(att); $event.stopPropagation()"
class="rounded-md bg-black/60 p-1.5 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
class="grid h-7 w-7 place-items-center rounded-md bg-black/60 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
title="View full size"
>
<ng-icon
@@ -118,7 +118,7 @@
</button>
<button
(click)="downloadAttachment(att); $event.stopPropagation()"
class="rounded-md bg-black/60 p-1.5 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
class="grid h-7 w-7 place-items-center rounded-md bg-black/60 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
title="Download"
>
<ng-icon
@@ -338,7 +338,7 @@
<div class="relative">
<button
(click)="toggleEmojiPicker()"
class="rounded-l-lg p-1.5 transition-colors hover:bg-secondary"
class="grid h-8 w-8 place-items-center rounded-l-lg transition-colors hover:bg-secondary"
>
<ng-icon
name="lucideSmile"
@@ -362,7 +362,7 @@
<button
(click)="requestReply()"
class="p-1.5 transition-colors hover:bg-secondary"
class="grid h-8 w-8 place-items-center transition-colors hover:bg-secondary"
>
<ng-icon
name="lucideReply"
@@ -373,7 +373,7 @@
@if (isOwnMessage()) {
<button
(click)="startEdit()"
class="p-1.5 transition-colors hover:bg-secondary"
class="grid h-8 w-8 place-items-center transition-colors hover:bg-secondary"
>
<ng-icon
name="lucideEdit"
@@ -385,7 +385,7 @@
@if (isOwnMessage() || isAdmin()) {
<button
(click)="requestDelete()"
class="rounded-r-lg p-1.5 transition-colors hover:bg-destructive/10"
class="grid h-8 w-8 place-items-center rounded-r-lg transition-colors hover:bg-destructive/10"
>
<ng-icon
name="lucideTrash2"

View File

@@ -55,7 +55,15 @@ const COMMON_EMOJIS = [
'🔥',
'👀'
];
const RICH_MARKDOWN_PATTERN = /(^|\n)(#{1,6}\s|>\s|[-*+]\s|\d+\.\s|```|~~~)|!\[[^\]]*\]\([^)]+\)|\[[^\]]+\]\([^)]+\)|`[^`\n]+`|\*\*[^*\n]+\*\*|__[^_\n]+__|\*[^*\n]+\*|_[^_\n]+_|(?:^|\n)\|.+\|/m;
const RICH_MARKDOWN_PATTERNS = [
/(^|\n)(#{1,6}\s|>\s|[-*+]\s|\d+\.\s|```|~~~)/m,
/!\[[^\]]*\]\([^)]+\)/,
/\[[^\]]+\]\([^)]+\)/,
/`[^`\n]+`/,
/\*\*[^*\n]+\*\*|__[^_\n]+__/,
/\*[^*\n]+\*|_[^_\n]+_/,
/(?:^|\n)\|.+\|/m
];
interface ChatMessageAttachmentViewModel extends Attachment {
isAudio: boolean;
@@ -292,7 +300,7 @@ export class ChatMessageItemComponent {
}
requiresRichMarkdown(content: string): boolean {
return RICH_MARKDOWN_PATTERN.test(content);
return RICH_MARKDOWN_PATTERNS.some((pattern) => pattern.test(content));
}
formatBytes(bytes: number): string {

View File

@@ -73,4 +73,4 @@ export class ChatMessageMarkdownComponent {
return PRISM_LANGUAGE_ALIASES[normalized] ?? normalized;
}
}
}

View File

@@ -20,7 +20,7 @@
<div class="absolute right-3 top-3 flex gap-2">
<button
(click)="downloadAttachment(lightboxAttachment()!)"
class="rounded-lg bg-black/60 p-2 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
class="grid h-9 w-9 place-items-center rounded-lg bg-black/60 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
title="Download"
>
<ng-icon
@@ -30,7 +30,7 @@
</button>
<button
(click)="closeLightbox()"
class="rounded-lg bg-black/60 p-2 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
class="grid h-9 w-9 place-items-center rounded-lg bg-black/60 text-white backdrop-blur-sm transition-colors hover:bg-black/80"
title="Close"
>
<ng-icon

View File

@@ -80,21 +80,21 @@
</div>
</div>
} @else {
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 xl:grid-cols-4">
<div class="columns-[12rem] gap-4">
@for (gif of results(); track gif.id) {
<button
type="button"
(click)="selectGif(gif)"
class="group overflow-hidden rounded-2xl border border-border/80 bg-secondary/10 text-left shadow-sm transition-transform duration-200 hover:-translate-y-0.5 hover:border-primary/50 hover:bg-secondary/30"
class="group mx-auto mb-4 inline-block w-full max-w-[15.5rem] break-inside-avoid align-top overflow-hidden rounded-2xl border border-border/80 bg-secondary/10 text-left shadow-sm transition-transform duration-200 hover:-translate-y-0.5 hover:border-primary/50 hover:bg-secondary/30"
>
<div
class="relative overflow-hidden bg-secondary/30"
[style.aspect-ratio]="gifAspectRatio(gif)"
class="relative flex items-center justify-center overflow-hidden bg-secondary/30"
[style.height.px]="gifCardHeight(gif)"
>
<img
[appChatImageProxyFallback]="gif.previewUrl || gif.url"
[alt]="gif.title || 'KLIPY GIF'"
class="h-full w-full object-cover transition-transform duration-200 group-hover:scale-[1.03]"
class="h-full w-full object-contain p-1.5 transition-transform duration-200 group-hover:scale-[1.03]"
loading="lazy"
/>
<span

View File

@@ -23,6 +23,12 @@ import {
import { KlipyGif, KlipyService } from '../../application/klipy.service';
import { ChatImageProxyFallbackDirective } from '../chat-image-proxy-fallback.directive';
const KLIPY_CARD_MIN_WIDTH = 140;
const KLIPY_CARD_MAX_WIDTH = 248;
const KLIPY_CARD_MIN_HEIGHT = 104;
const KLIPY_CARD_MAX_HEIGHT = 220;
const KLIPY_CARD_FALLBACK_SIZE = 160;
@Component({
selector: 'app-klipy-gif-picker',
standalone: true,
@@ -106,12 +112,8 @@ export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy
this.closed.emit(undefined);
}
gifAspectRatio(gif: KlipyGif): string {
if (gif.width > 0 && gif.height > 0) {
return `${gif.width} / ${gif.height}`;
}
return '1 / 1';
gifCardHeight(gif: KlipyGif): number {
return this.getGifCardSize(gif).height;
}
private async loadResults(reset: boolean): Promise<void> {
@@ -182,4 +184,32 @@ export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy
this.searchTimer = null;
}
}
private getGifCardSize(gif: KlipyGif): { width: number; height: number } {
if (gif.width <= 0 || gif.height <= 0) {
return {
width: KLIPY_CARD_FALLBACK_SIZE,
height: KLIPY_CARD_FALLBACK_SIZE
};
}
const maxScale = Math.min(
KLIPY_CARD_MAX_WIDTH / gif.width,
KLIPY_CARD_MAX_HEIGHT / gif.height
);
const minScale = Math.max(
KLIPY_CARD_MIN_WIDTH / gif.width,
KLIPY_CARD_MIN_HEIGHT / gif.height
);
const scale = minScale <= maxScale
? Math.min(maxScale, Math.max(minScale, 1))
: maxScale;
const scaledWidth = Math.round(gif.width * scale);
const scaledHeight = Math.round(gif.height * scale);
return {
width: Math.min(KLIPY_CARD_MAX_WIDTH, Math.max(KLIPY_CARD_MIN_WIDTH, scaledWidth)),
height: Math.min(KLIPY_CARD_MAX_HEIGHT, Math.max(KLIPY_CARD_MIN_HEIGHT, scaledHeight))
};
}
}