refactor: stricter domain: chat
This commit is contained in:
@@ -7,9 +7,12 @@ Text messaging, reactions, GIF search, typing indicators, and the user list. All
|
|||||||
```
|
```
|
||||||
chat/
|
chat/
|
||||||
├── application/
|
├── application/
|
||||||
│ └── klipy.service.ts GIF search via the KLIPY API (proxied through the server)
|
│ └── services/
|
||||||
|
│ ├── klipy.service.ts GIF search via the KLIPY API (proxied through the server)
|
||||||
|
│ └── link-metadata.service.ts Link preview metadata fetching
|
||||||
│
|
│
|
||||||
├── domain/
|
├── domain/
|
||||||
|
│ └── rules/
|
||||||
│ ├── message.rules.ts canEditMessage, normaliseDeletedMessage, getMessageTimestamp
|
│ ├── message.rules.ts canEditMessage, normaliseDeletedMessage, getMessageTimestamp
|
||||||
│ └── message-sync.rules.ts Inventory-based sync: chunkArray, findMissingIds, limits
|
│ └── message-sync.rules.ts Inventory-based sync: chunkArray, findMissingIds, limits
|
||||||
│
|
│
|
||||||
@@ -25,6 +28,7 @@ chat/
|
|||||||
│ │ └── services/
|
│ │ └── services/
|
||||||
│ │ └── chat-markdown.service.ts Markdown-to-HTML rendering
|
│ │ └── chat-markdown.service.ts Markdown-to-HTML rendering
|
||||||
│ │
|
│ │
|
||||||
|
│ ├── chat-image-proxy-fallback.directive.ts Image proxy fallback for broken URLs
|
||||||
│ ├── klipy-gif-picker/ GIF search/browse picker panel
|
│ ├── klipy-gif-picker/ GIF search/browse picker panel
|
||||||
│ ├── typing-indicator/ "X is typing..." display (3 s TTL, max 4 names)
|
│ ├── typing-indicator/ "X is typing..." display (3 s TTL, max 4 names)
|
||||||
│ └── user-list/ Online user sidebar
|
│ └── user-list/ Online user sidebar
|
||||||
@@ -129,7 +133,7 @@ graph LR
|
|||||||
Klipy --> API
|
Klipy --> API
|
||||||
|
|
||||||
click Picker "feature/klipy-gif-picker/" "GIF search panel" _blank
|
click Picker "feature/klipy-gif-picker/" "GIF search panel" _blank
|
||||||
click Klipy "application/klipy.service.ts" "GIF search via KLIPY API" _blank
|
click Klipy "application/services/klipy.service.ts" "GIF search via KLIPY API" _blank
|
||||||
click SD "../server-directory/application/server-directory.facade.ts" "Resolves API base URL" _blank
|
click SD "../server-directory/application/server-directory.facade.ts" "Resolves API base URL" _blank
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
throwError
|
throwError
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { catchError, map } from 'rxjs/operators';
|
import { catchError, map } from 'rxjs/operators';
|
||||||
import { ServerDirectoryFacade } from '../../server-directory';
|
import { ServerDirectoryFacade } from '../../../server-directory';
|
||||||
|
|
||||||
export interface KlipyGif {
|
export interface KlipyGif {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { ServerDirectoryFacade } from '../../server-directory';
|
import { ServerDirectoryFacade } from '../../../server-directory';
|
||||||
import { LinkMetadata } from '../../../shared-kernel';
|
import { LinkMetadata } from '../../../../shared-kernel';
|
||||||
|
|
||||||
const URL_PATTERN = /https?:\/\/[^\s<>)"']+/g;
|
const URL_PATTERN = /https?:\/\/[^\s<>)"']+/g;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DELETED_MESSAGE_CONTENT, type Message } from '../../../shared-kernel';
|
import { DELETED_MESSAGE_CONTENT, type Message } from '../../../../shared-kernel';
|
||||||
|
|
||||||
/** Extracts the effective timestamp from a message (editedAt takes priority). */
|
/** Extracts the effective timestamp from a message (editedAt takes priority). */
|
||||||
export function getMessageTimestamp(msg: Message): number {
|
export function getMessageTimestamp(msg: Message): number {
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
input,
|
input,
|
||||||
signal
|
signal
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { KlipyService } from '../application/klipy.service';
|
import { KlipyService } from '../application/services/klipy.service';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: 'img[appChatImageProxyFallback]',
|
selector: 'img[appChatImageProxyFallback]',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Store } from '@ngrx/store';
|
|||||||
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
|
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
|
||||||
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||||
import { Attachment, AttachmentFacade } from '../../../attachment';
|
import { Attachment, AttachmentFacade } from '../../../attachment';
|
||||||
import { KlipyGif } from '../../application/klipy.service';
|
import { KlipyGif } from '../../application/services/klipy.service';
|
||||||
import { MessagesActions } from '../../../../store/messages/messages.actions';
|
import { MessagesActions } from '../../../../store/messages/messages.actions';
|
||||||
import {
|
import {
|
||||||
selectAllMessages,
|
selectAllMessages,
|
||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
ChatMessageImageContextMenuEvent,
|
ChatMessageImageContextMenuEvent,
|
||||||
ChatMessageReactionEvent,
|
ChatMessageReactionEvent,
|
||||||
ChatMessageReplyEvent
|
ChatMessageReplyEvent
|
||||||
} from './models/chat-messages.models';
|
} from './models/chat-messages.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-chat-messages',
|
selector: 'app-chat-messages',
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import {
|
|||||||
} from '@ng-icons/lucide';
|
} from '@ng-icons/lucide';
|
||||||
import type { ClipboardFilePayload } from '../../../../../../core/platform/electron/electron-api.models';
|
import type { ClipboardFilePayload } from '../../../../../../core/platform/electron/electron-api.models';
|
||||||
import { ElectronBridgeService } from '../../../../../../core/platform/electron/electron-bridge.service';
|
import { ElectronBridgeService } from '../../../../../../core/platform/electron/electron-bridge.service';
|
||||||
import { KlipyGif, KlipyService } from '../../../../application/klipy.service';
|
import { KlipyGif, KlipyService } from '../../../../application/services/klipy.service';
|
||||||
import { Message } from '../../../../../../shared-kernel';
|
import { Message } from '../../../../../../shared-kernel';
|
||||||
import { ChatImageProxyFallbackDirective } from '../../../chat-image-proxy-fallback.directive';
|
import { ChatImageProxyFallbackDirective } from '../../../chat-image-proxy-fallback.directive';
|
||||||
import { TypingIndicatorComponent } from '../../../typing-indicator/typing-indicator.component';
|
import { TypingIndicatorComponent } from '../../../typing-indicator/typing-indicator.component';
|
||||||
import { ChatMarkdownService } from '../../services/chat-markdown.service';
|
import { ChatMarkdownService } from '../../services/chat-markdown.service';
|
||||||
import { ChatMessageComposerSubmitEvent } from '../../models/chat-messages.models';
|
import { ChatMessageComposerSubmitEvent } from '../../models/chat-messages.model';
|
||||||
|
|
||||||
type LocalFileWithPath = File & {
|
type LocalFileWithPath = File & {
|
||||||
path?: string;
|
path?: string;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
AttachmentFacade,
|
AttachmentFacade,
|
||||||
MAX_AUTO_SAVE_SIZE_BYTES
|
MAX_AUTO_SAVE_SIZE_BYTES
|
||||||
} from '../../../../../attachment';
|
} from '../../../../../attachment';
|
||||||
import { KlipyService } from '../../../../application/klipy.service';
|
import { KlipyService } from '../../../../application/services/klipy.service';
|
||||||
import { DELETED_MESSAGE_CONTENT, Message } from '../../../../../../shared-kernel';
|
import { DELETED_MESSAGE_CONTENT, Message } from '../../../../../../shared-kernel';
|
||||||
import {
|
import {
|
||||||
ChatAudioPlayerComponent,
|
ChatAudioPlayerComponent,
|
||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
ChatMessageImageContextMenuEvent,
|
ChatMessageImageContextMenuEvent,
|
||||||
ChatMessageReactionEvent,
|
ChatMessageReactionEvent,
|
||||||
ChatMessageReplyEvent
|
ChatMessageReplyEvent
|
||||||
} from '../../models/chat-messages.models';
|
} from '../../models/chat-messages.model';
|
||||||
|
|
||||||
const COMMON_EMOJIS = [
|
const COMMON_EMOJIS = [
|
||||||
'👍',
|
'👍',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
signal
|
signal
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Attachment } from '../../../../../attachment';
|
import { Attachment } from '../../../../../attachment';
|
||||||
import { getMessageTimestamp } from '../../../../domain/message.rules';
|
import { getMessageTimestamp } from '../../../../domain/rules/message.rules';
|
||||||
import { Message } from '../../../../../../shared-kernel';
|
import { Message } from '../../../../../../shared-kernel';
|
||||||
import {
|
import {
|
||||||
ChatMessageDeleteEvent,
|
ChatMessageDeleteEvent,
|
||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
ChatMessageImageContextMenuEvent,
|
ChatMessageImageContextMenuEvent,
|
||||||
ChatMessageReactionEvent,
|
ChatMessageReactionEvent,
|
||||||
ChatMessageReplyEvent
|
ChatMessageReplyEvent
|
||||||
} from '../../models/chat-messages.models';
|
} from '../../models/chat-messages.model';
|
||||||
import { ChatMessageItemComponent } from '../message-item/chat-message-item.component';
|
import { ChatMessageItemComponent } from '../message-item/chat-message-item.component';
|
||||||
|
|
||||||
interface PrismGlobal {
|
interface PrismGlobal {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from '@ng-icons/lucide';
|
} from '@ng-icons/lucide';
|
||||||
import { Attachment } from '../../../../../attachment';
|
import { Attachment } from '../../../../../attachment';
|
||||||
import { ContextMenuComponent } from '../../../../../../shared';
|
import { ContextMenuComponent } from '../../../../../../shared';
|
||||||
import { ChatMessageImageContextMenuEvent } from '../../models/chat-messages.models';
|
import { ChatMessageImageContextMenuEvent } from '../../models/chat-messages.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-chat-message-overlays',
|
selector: 'app-chat-message-overlays',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
lucideSearch,
|
lucideSearch,
|
||||||
lucideX
|
lucideX
|
||||||
} from '@ng-icons/lucide';
|
} from '@ng-icons/lucide';
|
||||||
import { KlipyGif, KlipyService } from '../../application/klipy.service';
|
import { KlipyGif, KlipyService } from '../../application/services/klipy.service';
|
||||||
import { ChatImageProxyFallbackDirective } from '../chat-image-proxy-fallback.directive';
|
import { ChatImageProxyFallbackDirective } from '../chat-image-proxy-fallback.directive';
|
||||||
|
|
||||||
const KLIPY_CARD_MIN_WIDTH = 140;
|
const KLIPY_CARD_MIN_WIDTH = 140;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './application/klipy.service';
|
export * from './application/services/klipy.service';
|
||||||
export * from './domain/message.rules';
|
export * from './application/services/link-metadata.service';
|
||||||
export * from './domain/message-sync.rules';
|
export * from './domain/rules/message.rules';
|
||||||
|
export * from './domain/rules/message-sync.rules';
|
||||||
export { ChatMessagesComponent } from './feature/chat-messages/chat-messages.component';
|
export { ChatMessagesComponent } from './feature/chat-messages/chat-messages.component';
|
||||||
export { TypingIndicatorComponent } from './feature/typing-indicator/typing-indicator.component';
|
export { TypingIndicatorComponent } from './feature/typing-indicator/typing-indicator.component';
|
||||||
export { KlipyGifPickerComponent } from './feature/klipy-gif-picker/klipy-gif-picker.component';
|
export { KlipyGifPickerComponent } from './feature/klipy-gif-picker/klipy-gif-picker.component';
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import { DatabaseService } from '../../infrastructure/persistence';
|
|||||||
import { reportDebuggingError, trackDebuggingTaskFailure } from '../../core/helpers/debugging-helpers';
|
import { reportDebuggingError, trackDebuggingTaskFailure } from '../../core/helpers/debugging-helpers';
|
||||||
import { DebuggingService } from '../../core/services';
|
import { DebuggingService } from '../../core/services';
|
||||||
import { AttachmentFacade } from '../../domains/attachment';
|
import { AttachmentFacade } from '../../domains/attachment';
|
||||||
import { LinkMetadataService } from '../../domains/chat/application/link-metadata.service';
|
import { LinkMetadataService } from '../../domains/chat/application/services/link-metadata.service';
|
||||||
import { TimeSyncService } from '../../core/services/time-sync.service';
|
import { TimeSyncService } from '../../core/services/time-sync.service';
|
||||||
import {
|
import {
|
||||||
DELETED_MESSAGE_CONTENT,
|
DELETED_MESSAGE_CONTENT,
|
||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
Reaction
|
Reaction
|
||||||
} from '../../shared-kernel';
|
} from '../../shared-kernel';
|
||||||
import { hydrateMessages } from './messages.helpers';
|
import { hydrateMessages } from './messages.helpers';
|
||||||
import { canEditMessage } from '../../domains/chat/domain/message.rules';
|
import { canEditMessage } from '../../domains/chat/domain/rules/message.rules';
|
||||||
import { resolveRoomPermission } from '../../domains/access-control';
|
import { resolveRoomPermission } from '../../domains/access-control';
|
||||||
import { dispatchIncomingMessage, IncomingMessageContext } from './messages-incoming.handlers';
|
import { dispatchIncomingMessage, IncomingMessageContext } from './messages-incoming.handlers';
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
import { Message } from '../../shared-kernel';
|
import { Message } from '../../shared-kernel';
|
||||||
import { DatabaseService } from '../../infrastructure/persistence';
|
import { DatabaseService } from '../../infrastructure/persistence';
|
||||||
import { getMessageTimestamp, normaliseDeletedMessage } from '../../domains/chat/domain/message.rules';
|
import { getMessageTimestamp, normaliseDeletedMessage } from '../../domains/chat/domain/rules/message.rules';
|
||||||
import type { InventoryItem } from '../../domains/chat/domain/message-sync.rules';
|
import type { InventoryItem } from '../../domains/chat/domain/rules/message-sync.rules';
|
||||||
|
|
||||||
// Re-export domain logic so existing callers keep working
|
// Re-export domain logic so existing callers keep working
|
||||||
export {
|
export {
|
||||||
@@ -13,7 +13,7 @@ export {
|
|||||||
getLatestTimestamp,
|
getLatestTimestamp,
|
||||||
normaliseDeletedMessage,
|
normaliseDeletedMessage,
|
||||||
canEditMessage
|
canEditMessage
|
||||||
} from '../../domains/chat/domain/message.rules';
|
} from '../../domains/chat/domain/rules/message.rules';
|
||||||
export {
|
export {
|
||||||
INVENTORY_LIMIT,
|
INVENTORY_LIMIT,
|
||||||
CHUNK_SIZE,
|
CHUNK_SIZE,
|
||||||
@@ -23,8 +23,8 @@ export {
|
|||||||
FULL_SYNC_LIMIT,
|
FULL_SYNC_LIMIT,
|
||||||
chunkArray,
|
chunkArray,
|
||||||
findMissingIds
|
findMissingIds
|
||||||
} from '../../domains/chat/domain/message-sync.rules';
|
} from '../../domains/chat/domain/rules/message-sync.rules';
|
||||||
export type { InventoryItem } from '../../domains/chat/domain/message-sync.rules';
|
export type { InventoryItem } from '../../domains/chat/domain/rules/message-sync.rules';
|
||||||
|
|
||||||
/** Hydrates a single message with its reactions from the database. */
|
/** Hydrates a single message with its reactions from the database. */
|
||||||
export async function hydrateMessage(
|
export async function hydrateMessage(
|
||||||
|
|||||||
Reference in New Issue
Block a user