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.
This commit is contained in:
SondreB
2024-11-27 19:32:37 +01:00
parent 1f05b52fd7
commit 938a60b994
16 changed files with 119 additions and 95 deletions

View File

@@ -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;

View File

@@ -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: [],
};

View File

@@ -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;

View File

@@ -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"
/>

View File

@@ -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]
})

View File

@@ -20,12 +20,12 @@
<!-- Contact info -->
<div class="ml-2 mr-2 flex cursor-pointer items-center lg:ml-0" (click)="openContactInfo()">
<div class="relative flex h-10 w-10 flex-0 items-center justify-center">
@if (chat.contact?.picture) {
<img class="h-full w-full rounded-full object-cover" [src]="chat.contact?.picture"
@if (chat.contact?.avatar) {
<img class="h-full w-full rounded-full object-cover" [src]="chat.contact?.avatar"
onerror="this.onerror=null; this.src='/images/avatars/avatar-placeholder.png';"
alt="Contact picture" />
}
@if (!chat.contact?.picture) {
@if (!chat.contact?.avatar) {
<div
class="flex h-full w-full items-center justify-center rounded-full bg-gray-200 text-lg uppercase text-gray-600 dark:bg-gray-700 dark:text-gray-200">
{{ chat.contact?.name.charAt(0) }}

View File

@@ -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 ||

View File

@@ -73,6 +73,9 @@ export class PostEventComponent implements OnInit, OnDestroy {
private _unsubscribeAll: Subject<void> = new Subject<void>();
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[] = [];

View File

@@ -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<any>();
user: any;

View File

@@ -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<string[]>;
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<void> {
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<void> {
private async subscribeToUserProfileAndContacts(
pubKey: string
): Promise<void> {
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<void> {
private async processProfileMetadata(
event: NostrEvent,
pubKey: string
): Promise<void> {
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<boolean> {
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<void> {
async openZapDialog(eventId: string = ''): Promise<void> {
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<void> {
const isBookmarked = await this._bookmarkService.isBookmarked(projectNpub);
const isBookmarked =
await this._bookmarkService.isBookmarked(projectNpub);
if (isBookmarked) {
await this._bookmarkService.removeBookmark(projectNpub);
} else {

View File

@@ -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;

View File

@@ -122,7 +122,7 @@
<button
class="h-6 min-h-6 w-6 sm:opacity-0 sm:group-hover:opacity-100"
mat-icon-button
(click)="delete(notification)"
(click)="deleteNotification(notification)"
[matTooltip]="'Remove'"
>
<mat-icon

View File

@@ -38,7 +38,7 @@ import { Subject, takeUntil } from 'rxjs';
NgTemplateOutlet,
RouterLink,
DatePipe,
]
],
})
export class NotificationsComponent implements OnInit, OnDestroy {
@ViewChild('notificationsOrigin') private _notificationsOrigin: MatButton;
@@ -80,6 +80,10 @@ export class NotificationsComponent implements OnInit, OnDestroy {
});
}
deleteNotification(notification: NostrNotification) {
throw new Error('Method not implemented.');
}
ngOnDestroy(): void {
this._unsubscribeAll.next(null);
this._unsubscribeAll.complete();

View File

@@ -80,9 +80,9 @@ export class PostComponent implements OnDestroy {
private subscription: Subscription = new Subscription();
profileUser: any;
tokens = signal<(string | ParsedToken)[]>([]);
tokens = signal<(string | ParsedToken | any)[]>([]);
private isLiked = false;
isLiked = false;

View File

@@ -35,7 +35,7 @@ export class ParseContentService {
constructor(private utilities: Utilities) {}
parseContent(text: string): (string | ParsedToken)[] {
parseContent(text: string): (string | ParsedToken | any)[] {
const sanitizedText = this.sanitizeText(text);
const tokens = this.tokenizeText(sanitizedText);
return this.combinePlainText(tokens.map(token => this.processToken(token)));

View File

@@ -26,6 +26,6 @@
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": false,
"strictInputAccessModifiers": false,
"strictTemplates": false
"strictTemplates": true
}
}