From 938a60b9943789809d821034b4b06f02f04fbdbd Mon Sep 17 00:00:00 2001 From: SondreB Date: Wed, 27 Nov 2024 19:32:37 +0100 Subject: [PATCH] Implement strict template mode - Fixes all issues with strict mode. - Might introduce some bugs as there was a few properties and types that was not correct. --- .../components/auth/login/login.component.ts | 7 +- src/app/components/chat/chat.service.ts | 6 +- src/app/components/chat/chat.types.ts | 3 +- .../chat/chats/chats.component.html | 4 - .../contact-info/contact-info.component.ts | 1 + .../conversation/conversation.component.html | 6 +- .../conversation/conversation.component.ts | 5 +- .../post-event/post-event.component.ts | 3 + .../post-profile/post-profile.component.ts | 2 +- .../components/profile/profile.component.ts | 159 ++++++++++-------- src/app/interface/project.interface.ts | 2 + .../notifications.component.html | 2 +- .../notifications/notifications.component.ts | 6 +- src/app/layout/common/post/post.component.ts | 4 +- src/app/services/parse-content.service.ts | 2 +- tsconfig.json | 2 +- 16 files changed, 119 insertions(+), 95 deletions(-) diff --git a/src/app/components/auth/login/login.component.ts b/src/app/components/auth/login/login.component.ts index e962fbb..da3e547 100644 --- a/src/app/components/auth/login/login.component.ts +++ b/src/app/components/auth/login/login.component.ts @@ -1,4 +1,4 @@ -import { AngorAlertComponent } from '@angor/components/alert'; +import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; import { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { @@ -22,6 +22,7 @@ import { Subscription } from 'rxjs'; @Component({ selector: 'auth-sign-in', templateUrl: './login.component.html', + standalone: true, imports: [ RouterLink, AngorAlertComponent, @@ -39,10 +40,10 @@ import { Subscription } from 'rxjs'; export class LoginComponent implements OnInit { SecretKeyLoginForm: FormGroup; MenemonicLoginForm: FormGroup; - secAlert = { type: 'error', message: '' }; + secAlert = { type: 'error' as AngorAlertType, message: '' }; showSecAlert = false; - menemonicAlert = { type: 'error', message: '' }; + menemonicAlert = { type: 'error' as AngorAlertType, message: '' }; showMenemonicAlert = false; loading = false; diff --git a/src/app/components/chat/chat.service.ts b/src/app/components/chat/chat.service.ts index 8de5d09..380187b 100644 --- a/src/app/components/chat/chat.service.ts +++ b/src/app/components/chat/chat.service.ts @@ -310,7 +310,7 @@ export class ChatService implements OnDestroy { if (Number(existingChat.lastMessageAt) < createdAt) { existingChat.lastMessage = message; - existingChat.lastMessageAt = createdAt.toString(); + existingChat.lastMessageAt = createdAt; } } } else { @@ -329,7 +329,7 @@ export class ChatService implements OnDestroy { displayName: contactInfo.displayName || contactInfo.name || 'Unknown', }, lastMessage: message, - lastMessageAt: createdAt.toString(), + lastMessageAt: createdAt, messages: [newMessage], }; this.chatList.push(newChat); @@ -544,7 +544,7 @@ export class ChatService implements OnDestroy { picture: '/images/avatars/avatar-placeholder.png', }, lastMessage: 'new chat...', - lastMessageAt: Math.floor(Date.now() / 1000).toString() || '0', + lastMessageAt: Math.floor(Date.now() / 1000) || 0, messages: [], }; diff --git a/src/app/components/chat/chat.types.ts b/src/app/components/chat/chat.types.ts index c3cc0be..ccf299d 100644 --- a/src/app/components/chat/chat.types.ts +++ b/src/app/components/chat/chat.types.ts @@ -16,6 +16,7 @@ export interface Contact { pubKey?: string; name?: string; username?: string; + avatar?: string; // TODO: I introduced this to fix strict mode, must be verified. picture?: string; about?: string; displayName?: string; @@ -33,7 +34,7 @@ export interface Chat { unreadCount?: number; muted?: boolean; lastMessage?: string; - lastMessageAt?: string; + lastMessageAt?: number; messages?: { id?: string; chatId?: string; diff --git a/src/app/components/chat/chats/chats.component.html b/src/app/components/chat/chats/chats.component.html index 7a7e3bc..f9137b2 100644 --- a/src/app/components/chat/chats/chats.component.html +++ b/src/app/components/chat/chats/chats.component.html @@ -187,10 +187,6 @@ *ngIf="chat.contact?.picture" class="h-full w-full rounded-full object-cover" [src]="chat.contact?.picture" - (error)=" - this.src = - '/images/avatars/avatar-placeholder.png' - " alt="Contact picture" /> diff --git a/src/app/components/chat/contact-info/contact-info.component.ts b/src/app/components/chat/contact-info/contact-info.component.ts index 1abcd22..b9cce24 100644 --- a/src/app/components/chat/contact-info/contact-info.component.ts +++ b/src/app/components/chat/contact-info/contact-info.component.ts @@ -14,6 +14,7 @@ import { Chat } from '../chat.types'; selector: 'chat-contact-info', templateUrl: './contact-info.component.html', encapsulation: ViewEncapsulation.None, + standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatButtonModule, MatIconModule, RouterModule] }) diff --git a/src/app/components/chat/conversation/conversation.component.html b/src/app/components/chat/conversation/conversation.component.html index 5593d4c..c9f9d8c 100644 --- a/src/app/components/chat/conversation/conversation.component.html +++ b/src/app/components/chat/conversation/conversation.component.html @@ -20,12 +20,12 @@
- @if (chat.contact?.picture) { - Contact picture } - @if (!chat.contact?.picture) { + @if (!chat.contact?.avatar) {
{{ chat.contact?.name.charAt(0) }} diff --git a/src/app/components/chat/conversation/conversation.component.ts b/src/app/components/chat/conversation/conversation.component.ts index b87e67f..dd169f0 100644 --- a/src/app/components/chat/conversation/conversation.component.ts +++ b/src/app/components/chat/conversation/conversation.component.ts @@ -30,18 +30,19 @@ import { AngorConfigService } from '@angor/services/config'; import { MatDialog } from '@angular/material/dialog'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { PickerComponent } from '@ctrl/ngx-emoji-mart'; -import { Chat } from 'app/layout/common/quick-chat/quick-chat.types'; import { GifDialogComponent } from 'app/shared/gif-dialog/gif-dialog.component'; import { Subject, takeUntil } from 'rxjs'; import { ChatService } from '../chat.service'; import { ContactInfoComponent } from '../contact-info/contact-info.component'; import { ParseContentService } from 'app/services/parse-content.service'; +import { Chat } from '../chat.types'; @Component({ selector: 'chat-conversation', templateUrl: './conversation.component.html', styleUrls: ['./conversation.component.css'], encapsulation: ViewEncapsulation.None, + standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MatSidenavModule, @@ -81,7 +82,7 @@ export class ConversationComponent implements OnInit, OnDestroy { private _angorConfigService: AngorConfigService, public dialog: MatDialog, private sanitizer: DomSanitizer, - private parseContent: ParseContentService + public parseContent: ParseContentService ) { const SpeechRecognition = (window as any).SpeechRecognition || diff --git a/src/app/components/post-event/post-event.component.ts b/src/app/components/post-event/post-event.component.ts index 7d5b9d1..c8d0549 100644 --- a/src/app/components/post-event/post-event.component.ts +++ b/src/app/components/post-event/post-event.component.ts @@ -73,6 +73,9 @@ export class PostEventComponent implements OnInit, OnDestroy { private _unsubscribeAll: Subject = new Subject(); private subscription: Subscription = new Subscription(); + // TODO: This should obviously not be on the component but come from some service. Added to fix strict mode. + darkMode: boolean = false; + likes: PostReaction[] = []; reposts: PostReaction[] = []; zaps: PostReaction[] = []; diff --git a/src/app/components/post-event/post-profile/post-profile.component.ts b/src/app/components/post-event/post-profile/post-profile.component.ts index c2fc6f4..dd64abb 100644 --- a/src/app/components/post-event/post-profile/post-profile.component.ts +++ b/src/app/components/post-event/post-profile/post-profile.component.ts @@ -15,7 +15,7 @@ import { MetadataService } from 'app/services/metadata.service'; export class PostProfileComponent implements OnInit, OnDestroy { @Input() pubkey!: string; @Input() avatarUrl?: string; - @Input() created_at?: string; + @Input() created_at?: number; @Output() userChange = new EventEmitter(); user: any; diff --git a/src/app/components/profile/profile.component.ts b/src/app/components/profile/profile.component.ts index dd31aba..336619d 100644 --- a/src/app/components/profile/profile.component.ts +++ b/src/app/components/profile/profile.component.ts @@ -1,8 +1,9 @@ import { AngorCardComponent } from '@angor/components/card'; import { AngorConfigService } from '@angor/services/config'; import { AngorConfirmationService } from '@angor/services/confirmation'; +import { Clipboard } from '@angular/cdk/clipboard'; import { TextFieldModule } from '@angular/cdk/text-field'; -import { CommonModule, DatePipe, NgClass, NgTemplateOutlet } from '@angular/common'; +import { CommonModule, NgClass } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -17,34 +18,31 @@ import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSlideToggle } from '@angular/material/slide-toggle'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { PickerComponent } from '@ctrl/ngx-emoji-mart'; -import { EventService } from 'app/services/event.service'; -import { SignerService } from 'app/services/signer.service'; -import { ContactEvent, StorageService } from 'app/services/storage.service'; -import { InfiniteScrollModule } from 'ngx-infinite-scroll'; -import { Filter, NostrEvent } from 'nostr-tools'; -import { Observable, Subject, takeUntil } from 'rxjs'; -import { SubscriptionService } from 'app/services/subscription.service'; -import { Clipboard } from '@angular/cdk/clipboard'; -import { MatExpansionModule } from '@angular/material/expansion'; -import { ParseContentService } from 'app/services/parse-content.service'; -import { MatSidenavModule } from '@angular/material/sidenav'; -import { AgoPipe } from 'app/shared/pipes/ago.pipe'; -import { ZapDialogComponent } from 'app/shared/zap-dialog/zap-dialog.component'; -import { ZapDialogData } from 'app/services/interfaces'; -import { Contacts } from 'nostr-tools/kinds'; import { PostComponent } from 'app/layout/common/post/post.component'; import { BookmarkService } from 'app/services/bookmark.service'; +import { EventService } from 'app/services/event.service'; +import { ZapDialogData } from 'app/services/interfaces'; +import { ParseContentService } from 'app/services/parse-content.service'; +import { SignerService } from 'app/services/signer.service'; +import { ContactEvent, StorageService } from 'app/services/storage.service'; +import { SubscriptionService } from 'app/services/subscription.service'; +import { ZapDialogComponent } from 'app/shared/zap-dialog/zap-dialog.component'; +import { InfiniteScrollModule } from 'ngx-infinite-scroll'; +import { Filter, NostrEvent } from 'nostr-tools'; +import { Observable, Subject } from 'rxjs'; interface Chip { color?: string; selected?: string; @@ -79,11 +77,10 @@ interface Chip { MatIconModule, MatExpansionModule, MatSidenavModule, - PostComponent - ] + PostComponent, + ], }) export class ProfileComponent implements OnInit, OnDestroy { - @ViewChild('eventInput', { static: false }) eventInput: ElementRef; @ViewChild('commentInput') commentInput: ElementRef; @@ -130,8 +127,12 @@ export class ProfileComponent implements OnInit, OnDestroy { followingList: ContactEvent[] = []; aboutExpanded: boolean = true; - stats$!: Observable<{ pubKey: string, totalContacts: number, followersCount: number, followingCount: number }>; - + stats$!: Observable<{ + pubKey: string; + totalContacts: number; + followersCount: number; + followingCount: number; + }>; bookmarks$: Observable; bookmarkedProjectNpubs: string[] = []; @@ -149,9 +150,8 @@ export class ProfileComponent implements OnInit, OnDestroy { private _eventService: EventService, private _subscriptionService: SubscriptionService, private _clipboard: Clipboard, - private parseContent: ParseContentService, - private _bookmarkService: BookmarkService, - + public parseContent: ParseContentService, + private _bookmarkService: BookmarkService ) { this.bookmarks$ = this._bookmarkService.bookmarks$; } @@ -173,6 +173,9 @@ export class ProfileComponent implements OnInit, OnDestroy { }); } + trackByFn(index: number, item: any): number { + return index; + } private checkIfRoutePubKeyIsFollowing(): void { if (!this.routePubKey || !this.followersList) { @@ -180,10 +183,11 @@ export class ProfileComponent implements OnInit, OnDestroy { return; } - this.isFollowing = this.followersList.some(follower => follower.pubkey === this.routePubKey); + this.isFollowing = this.followersList.some( + (follower) => follower.pubkey === this.routePubKey + ); } - private processRouteParams(): void { this._route.paramMap.subscribe((params) => { const routeKey = params.get('pubkey') || ''; @@ -194,7 +198,8 @@ export class ProfileComponent implements OnInit, OnDestroy { this.routePubKey = hexPubKey; this.isCurrentUserProfile = false; } else { - this.errorMessage = 'Public key is invalid. Please check your input.'; + this.errorMessage = + 'Public key is invalid. Please check your input.'; this.setCurrentUserProfile(); } } else { @@ -212,7 +217,7 @@ export class ProfileComponent implements OnInit, OnDestroy { private loadUserProfileData(pubKey: string): void { this.loadUserProfile(pubKey); - } + } private isValidHexPubkey(pubkey: string): boolean { const hexPattern = /^[a-fA-F0-9]{64}$/; @@ -227,11 +232,12 @@ export class ProfileComponent implements OnInit, OnDestroy { try { while (attemptCount < maxAttempts) { - const additionalPosts = await this._storageService.getPostsByPubKeysWithPagination( - [this.routePubKey], - this.currentPage, - 10 - ); + const additionalPosts = + await this._storageService.getPostsByPubKeysWithPagination( + [this.routePubKey], + this.currentPage, + 10 + ); if (additionalPosts.length > 0) { this.posts = [...this.posts, ...additionalPosts]; @@ -258,24 +264,25 @@ export class ProfileComponent implements OnInit, OnDestroy { this.refreshUI(); } - private delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } - private subscribeToNewPosts(): void { if (!this.isCurrentUserProfile) { const filters: Filter[] = [ { authors: [this.routePubKey], kinds: [1] }, ]; - this.postsSubscriptionId = this._subscriptionService.addSubscriptions(filters, async (event: NostrEvent) => { - if (!this.isReply(event)) { - this._storageService.savePost(event); - } - }); - } - else { + this.postsSubscriptionId = + this._subscriptionService.addSubscriptions( + filters, + async (event: NostrEvent) => { + if (!this.isReply(event)) { + this._storageService.savePost(event); + } + } + ); + } else { this._storageService.posts$.subscribe((newPost) => { if (newPost) { if (newPost.pubkey === this.routePubKey) { @@ -285,7 +292,6 @@ export class ProfileComponent implements OnInit, OnDestroy { } } }); - } } @@ -296,24 +302,26 @@ export class ProfileComponent implements OnInit, OnDestroy { return replyTags.length > 0; } - loadNextPage(): void { if (this.loading) return; this.currentPage++; this.loadInitialPosts(); } - toggleAbout(): void { this.aboutExpanded = !this.aboutExpanded; } ngOnDestroy(): void { if (this.subscriptionId) { - this._subscriptionService.removeSubscriptionById(this.subscriptionId); + this._subscriptionService.removeSubscriptionById( + this.subscriptionId + ); } if (this.postsSubscriptionId) { - this._subscriptionService.removeSubscriptionById(this.postsSubscriptionId); + this._subscriptionService.removeSubscriptionById( + this.postsSubscriptionId + ); } this._unsubscribeAll.next(null); @@ -334,8 +342,8 @@ export class ProfileComponent implements OnInit, OnDestroy { return; } try { - - const cachedMetadata = await this._storageService.getProfile(publicKey); + const cachedMetadata = + await this._storageService.getProfile(publicKey); if (cachedMetadata) { this.profileUser = cachedMetadata; this.refreshUI(); @@ -345,26 +353,29 @@ export class ProfileComponent implements OnInit, OnDestroy { } catch (error) { console.error('Error loading user profile:', error); } - } - - - private async subscribeToUserProfileAndContacts(pubKey: string): Promise { + private async subscribeToUserProfileAndContacts( + pubKey: string + ): Promise { const combinedFilters: Filter[] = [ // Profile filter (kind 0) { authors: [pubKey], kinds: [0], limit: 1 }, - - ]; - this.subscriptionId = this._subscriptionService.addSubscriptions(combinedFilters, async (event: NostrEvent) => { - // Handle profile metadata - await this.processProfileMetadata(event, pubKey); - }); + this.subscriptionId = this._subscriptionService.addSubscriptions( + combinedFilters, + async (event: NostrEvent) => { + // Handle profile metadata + await this.processProfileMetadata(event, pubKey); + } + ); } - private async processProfileMetadata(event: NostrEvent, pubKey: string): Promise { + private async processProfileMetadata( + event: NostrEvent, + pubKey: string + ): Promise { try { const newMetadata = JSON.parse(event.content); this.profileUser = newMetadata; @@ -378,13 +389,10 @@ export class ProfileComponent implements OnInit, OnDestroy { } } - - getSafeUrl(url: string): SafeUrl { return this._sanitizer.bypassSecurityTrustUrl(url); } - private refreshUI(): void { this._changeDetectorRef.detectChanges(); } @@ -394,23 +402,27 @@ export class ProfileComponent implements OnInit, OnDestroy { } async canUseZap(): Promise { - const canReceiveZap = this.profileUser && (this.profileUser.lud06 || this.profileUser.lud16); + const canReceiveZap = + this.profileUser && + (this.profileUser.lud06 || this.profileUser.lud16); if (canReceiveZap) { return true; } else { - this.openSnackBar("Using Zap is not possible. Please complete your profile to include lud06 or lud16."); + this.openSnackBar( + 'Using Zap is not possible. Please complete your profile to include lud06 or lud16.' + ); return false; } } - async openZapDialog(eventId: string = ""): Promise { + async openZapDialog(eventId: string = ''): Promise { const canZap = await this.canUseZap(); if (canZap) { const zapData: ZapDialogData = { lud16: this.profileUser.lud16, lud06: this.profileUser.lud06, pubkey: this.profileUser.pubkey, - eventId: eventId + eventId: eventId, }; // Open dialog with mapped data @@ -468,7 +480,7 @@ export class ProfileComponent implements OnInit, OnDestroy { this._eventService .sendTextEvent(this.eventInput.nativeElement.value) .then(() => { - this.eventInput.nativeElement.value = ""; + this.eventInput.nativeElement.value = ''; this._changeDetectorRef.markForCheck(); }) .catch((error) => { @@ -483,7 +495,7 @@ export class ProfileComponent implements OnInit, OnDestroy { } copyNpub() { - var npub = this._signerService.getNpubFromPubkey(this.routePubKey) + var npub = this._signerService.getNpubFromPubkey(this.routePubKey); this._clipboard.copy(npub); this.openSnackBar('npub public key copied', 'dismiss'); } @@ -493,7 +505,9 @@ export class ProfileComponent implements OnInit, OnDestroy { this._clipboard.copy(this.routePubKey); this.openSnackBar('hex public key copied', 'dismiss'); } else if (keyType === 'npub') { - const npub = this._signerService.getNpubFromPubkey(this.routePubKey); + const npub = this._signerService.getNpubFromPubkey( + this.routePubKey + ); this._clipboard.copy(npub); this.openSnackBar('npub public key copied', 'dismiss'); } @@ -513,7 +527,8 @@ export class ProfileComponent implements OnInit, OnDestroy { } async toggleBookmark(projectNpub: string): Promise { - const isBookmarked = await this._bookmarkService.isBookmarked(projectNpub); + const isBookmarked = + await this._bookmarkService.isBookmarked(projectNpub); if (isBookmarked) { await this._bookmarkService.removeBookmark(projectNpub); } else { diff --git a/src/app/interface/project.interface.ts b/src/app/interface/project.interface.ts index a648085..755a9ad 100644 --- a/src/app/interface/project.interface.ts +++ b/src/app/interface/project.interface.ts @@ -2,6 +2,8 @@ export interface Project { projectIdentifier: string; nostrPubKey: string; displayName?: string; + name?: string // TODO: Verify if this is actually a possible value? + totalInvestmentsCount?: number; // TODO: Verify if this is wrong type or not? There is a different project interface. about?: string; picture?: string; banner?: string; diff --git a/src/app/layout/common/notifications/notifications.component.html b/src/app/layout/common/notifications/notifications.component.html index 5dc9658..9ce3349 100644 --- a/src/app/layout/common/notifications/notifications.component.html +++ b/src/app/layout/common/notifications/notifications.component.html @@ -122,7 +122,7 @@