mirror of
https://github.com/block-core/blockcore-notes.git
synced 2024-09-29 14:30:43 +00:00
Lots of general UX improvements
This commit is contained in:
parent
15de678496
commit
1ab0f41061
@ -11,6 +11,7 @@ import { SettingsComponent } from './settings/settings.component';
|
||||
import { UserComponent } from './user/user.component';
|
||||
import { CirclesComponent } from './circles/circles.component';
|
||||
import { PeopleComponent } from './people/people.component';
|
||||
import { NoteComponent } from './note/note.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -47,6 +48,11 @@ const routes: Routes = [
|
||||
component: UserComponent,
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'note/:id',
|
||||
component: NoteComponent,
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
|
@ -61,6 +61,7 @@ import { FeedPrivateComponent } from './feed-private/feed-private.component';
|
||||
import { FeedPublicComponent } from './feed-public/feed-public.component';
|
||||
import { NotesComponent } from './notes/notes.component';
|
||||
import { NgxColorsModule } from 'ngx-colors';
|
||||
import { NoteComponent } from './note/note.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -87,6 +88,7 @@ import { NgxColorsModule } from 'ngx-colors';
|
||||
FeedPrivateComponent,
|
||||
FeedPublicComponent,
|
||||
NotesComponent,
|
||||
NoteComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -1,4 +1,5 @@
|
||||
<span *ngIf="feedService.threadedEvents$ | async as events">
|
||||
<span *ngIf="feedService.filteredEvents$ | async as events">
|
||||
|
||||
<mat-progress-bar *ngIf="events == null" mode="indeterminate"></mat-progress-bar>
|
||||
|
||||
<div class="page loading-container" *ngIf="!profileService.initialized">
|
||||
@ -37,24 +38,6 @@
|
||||
</mat-grid-list>
|
||||
|
||||
<button class="follow-button" mat-raised-button color="primary" (click)="follow()">Follow</button>
|
||||
|
||||
<!-- <div *ngFor="let profile of defaults">
|
||||
<mat-card class="default-card">
|
||||
<mat-card-header>
|
||||
<img mat-card-avatar [src]="profile.picture" />
|
||||
<mat-card-title>{{ profile.name }}</mat-card-title>
|
||||
<mat-card-subtitle>{{ profile.about }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p class="wrap">
|
||||
{{ profile.pubkey }}
|
||||
</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary">Follow</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="feed-page" *ngIf="events.length > 0">
|
||||
|
@ -95,8 +95,9 @@ export class FeedPrivateComponent {
|
||||
// this.events = this.validator.filterEvents(this.events);
|
||||
}
|
||||
|
||||
// TODO: FIX THIS IMMEDIATELY FOR PERFORMANCE!
|
||||
hashtags(tags: any[]) {
|
||||
const hashtags = tags.map((row) => {
|
||||
const hashtags = tags.filter((row) => {
|
||||
if (row[0] === 't') {
|
||||
return row[1];
|
||||
}
|
||||
|
4
src/app/note/note.component.css
Normal file
4
src/app/note/note.component.css
Normal file
@ -0,0 +1,4 @@
|
||||
.original-post {
|
||||
margin-bottom: 2em;
|
||||
background-color: #303030;
|
||||
}
|
47
src/app/note/note.component.html
Normal file
47
src/app/note/note.component.html
Normal file
@ -0,0 +1,47 @@
|
||||
<!-- <mat-progress-bar *ngIf="!events || events.length == 0" mode="indeterminate"></mat-progress-bar> -->
|
||||
|
||||
<div class="feed-page">
|
||||
<div class="original-post events noclick" *ngIf="event">
|
||||
<app-profile-actions [event]="event" [pubkey]="event.pubkey"></app-profile-actions>
|
||||
<app-profile-header [pubkey]="event.pubkey"
|
||||
><span class="event-date">{{ event.created_at | ago }}</span> <app-directory-icon [pubkey]="event.pubkey"></app-directory-icon
|
||||
></app-profile-header>
|
||||
<div class="content">
|
||||
{{ event.content }}<span *ngIf="event.contentCut">... (message was truncated)</span>
|
||||
<div class="dimmed">{{ rootLikes() }} likes {{ rootReplies() }} replies</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="page" *ngIf="feedService.thread.length === 0">
|
||||
<h3 class="marginless">Loading thread... this can take some seconds.</h3>
|
||||
</div>
|
||||
|
||||
<div class="feed-page" *ngIf="feedService.thread.length > 0">
|
||||
<!-- <mat-accordion class="options">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Options</mat-panel-title>
|
||||
<mat-panel-description>{{ activeOptions() }}</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<p>
|
||||
<mat-slide-toggle class="options-slider" [(ngModel)]="settings.options.paused">Pause feed</mat-slide-toggle>
|
||||
<mat-slide-toggle class="options-slider" [(ngModel)]="settings.options.hideSpam" (ngModelChange)="optionsUpdated()">Hide spam</mat-slide-toggle>
|
||||
<mat-slide-toggle class="options-slider" [(ngModel)]="settings.options.hideInvoice" (ngModelChange)="optionsUpdated()">Hide LN invoices</mat-slide-toggle>
|
||||
|
||||
</p>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion> -->
|
||||
|
||||
<div class="events" *ngFor="let event of filteredThread(); trackBy: trackByFn">
|
||||
<span *ngIf="event.kind != 7">
|
||||
<app-profile-actions [event]="event" [pubkey]="event.pubkey"></app-profile-actions>
|
||||
<app-profile-header [pubkey]="event.pubkey"
|
||||
><span class="event-date">{{ event.created_at | ago }}</span> <app-directory-icon [pubkey]="event.pubkey"></app-directory-icon
|
||||
></app-profile-header>
|
||||
|
||||
<div class="content">{{ event.content }}<span *ngIf="event.contentCut">... (message was truncated)</span></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
165
src/app/note/note.component.ts
Normal file
165
src/app/note/note.component.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationState } from '../services/applicationstate.service';
|
||||
import { Utilities } from '../services/utilities.service';
|
||||
import { relayInit, Relay } from 'nostr-tools';
|
||||
import * as moment from 'moment';
|
||||
import { DataValidation } from '../services/data-validation.service';
|
||||
import { NostrEvent, NostrEventDocument, NostrProfile, NostrProfileDocument } from '../services/interfaces';
|
||||
import { ProfileService } from '../services/profile.service';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
import { FeedService } from '../services/feed.service';
|
||||
import { map, Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-note',
|
||||
templateUrl: './note.component.html',
|
||||
styleUrls: ['./note.component.css'],
|
||||
})
|
||||
export class NoteComponent {
|
||||
id?: string | null;
|
||||
event?: NostrEventDocument;
|
||||
|
||||
// userEvents$ = this.feedService.replyEvents$.pipe(
|
||||
// map((data) => {
|
||||
// return data.filter((events) => {
|
||||
// events.tags
|
||||
|
||||
// const eTag = events.tags.find((t) => t[0] === 'e');
|
||||
|
||||
// if (eTag[1] == this.id) {
|
||||
// return ;
|
||||
// }
|
||||
|
||||
// console.log(eTag[1]);
|
||||
// });
|
||||
|
||||
// // data.filter((events) => events.tags.find((t) => t[0] === 'e')
|
||||
|
||||
// // return data.filter((events) => events.tags.find((t) => t[0] === 'e'));
|
||||
// // return data;
|
||||
// })
|
||||
// );
|
||||
|
||||
// replyEvents$ = this.feedService.events$.pipe(
|
||||
// map((data) => {
|
||||
// if (!this.pubkey) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// return data.filter((n) => n.pubkey == this.pubkey);
|
||||
// })
|
||||
// );
|
||||
|
||||
constructor(
|
||||
public appState: ApplicationState,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private cd: ChangeDetectorRef,
|
||||
public settings: SettingsService,
|
||||
public feedService: FeedService,
|
||||
public profiles: ProfileService,
|
||||
private validator: DataValidation,
|
||||
private utilities: Utilities,
|
||||
private router: Router
|
||||
) {
|
||||
// this.appState.title = 'Blockcore Notes';
|
||||
this.appState.showBackButton = true;
|
||||
|
||||
this.activatedRoute.paramMap.subscribe(async (params) => {
|
||||
const id: any = params.get('id');
|
||||
|
||||
if (!id) {
|
||||
this.router.navigateByUrl('/');
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
this.event = this.feedService.events.find((e) => e.id == this.id);
|
||||
|
||||
if (!this.event) {
|
||||
// this.router.navigateByUrl('/');
|
||||
return;
|
||||
}
|
||||
|
||||
this.appState.title = `Note: ${this.event.content.substring(0, 200)}`;
|
||||
|
||||
// Clear the initial thread:
|
||||
this.feedService.thread = [];
|
||||
|
||||
// First download all posts, if any, that is mentioned in the e tags.
|
||||
this.feedService.downloadThread(this.id!);
|
||||
});
|
||||
}
|
||||
|
||||
rootLikes() {
|
||||
const eventsWithSingleeTag = this.feedService.thread.filter((e) => e.kind == 7 && e.tags.filter((p) => p[0] === 'e').length == 1);
|
||||
return eventsWithSingleeTag.length;
|
||||
}
|
||||
|
||||
rootReplies() {
|
||||
const eventsWithSingleeTag = this.feedService.thread.filter((e) => e.kind != 7 && e.tags.filter((p) => p[0] === 'e').length == 1);
|
||||
return eventsWithSingleeTag.length;
|
||||
}
|
||||
|
||||
filteredThread() {
|
||||
return this.feedService.thread.filter((p) => p.kind != 7);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// if (this.pubkey) {
|
||||
// console.log('PIPING EVENTS...');
|
||||
// this.userEvents$ =
|
||||
// }
|
||||
}
|
||||
|
||||
openEvent($event: any, event: NostrEventDocument) {
|
||||
const paths = $event.composedPath();
|
||||
|
||||
if (!paths || paths.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (paths[0].className.indexOf('clickable') == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(['/note', event.id]);
|
||||
}
|
||||
|
||||
optionsUpdated() {
|
||||
// this.allComplete = this.task.subtasks != null && this.task.subtasks.every(t => t.completed);
|
||||
// Parse existing content.
|
||||
// this.events = this.validator.filterEvents(this.events);
|
||||
}
|
||||
|
||||
activeOptions() {
|
||||
let options = '';
|
||||
|
||||
if (this.settings.options.hideSpam) {
|
||||
options += ' Spam: Filtered';
|
||||
} else {
|
||||
options += ' Spam: Allowed';
|
||||
}
|
||||
|
||||
if (this.settings.options.hideInvoice) {
|
||||
options += ' Invoices: Hidden';
|
||||
} else {
|
||||
options += ' Invoices: Displayed';
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public trackByFn(index: number, item: NostrEvent) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
sub: any;
|
||||
initialLoad = true;
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.sub) {
|
||||
this.sub.unsub();
|
||||
}
|
||||
}
|
||||
}
|
@ -38,12 +38,12 @@
|
||||
</mat-list-item>
|
||||
</mat-list> -->
|
||||
|
||||
<div class="events" *ngFor="let profile of profiles; trackBy: trackByFn">
|
||||
<div class="events clickable" (click)="openProfile($event, profile)" *ngFor="let profile of profiles; trackBy: trackByFn">
|
||||
<app-profile-actions [profile]="profile"></app-profile-actions>
|
||||
<app-profile-header [profile]="profile"><span *ngIf="profile.created">Started following {{ profile.created | ago }}</span> <app-directory-icon [pubkey]="profile.pubkey"></app-directory-icon></app-profile-header>
|
||||
<div class="content">
|
||||
<span class="content">
|
||||
{{ profile.about }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -102,6 +102,20 @@ export class PeopleComponent {
|
||||
});
|
||||
}
|
||||
|
||||
openProfile($event: any, event: NostrProfileDocument) {
|
||||
const paths = $event.composedPath();
|
||||
|
||||
if (!paths || paths.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (paths[0].className.indexOf('clickable') == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(['/user', event.pubkey]);
|
||||
}
|
||||
|
||||
async search() {
|
||||
const text: string = this.searchTerm;
|
||||
|
||||
|
@ -276,6 +276,47 @@ export class FeedService {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Temporary container for thread events. The downloadThread should probably return an Observable that should
|
||||
// vanish when the user is finished watched it.
|
||||
thread: NostrEvent[] = [];
|
||||
|
||||
// threadQueue: string[];
|
||||
|
||||
downloadThread(id: string) {
|
||||
const relay = this.relays[0];
|
||||
|
||||
const backInTime = moment().subtract(120, 'minutes').unix();
|
||||
|
||||
const sub = relay.sub([{ ['#e']: [id] }], {}) as NostrSubscription;
|
||||
|
||||
sub.loading = true;
|
||||
|
||||
// Keep all subscriptions around so we can close them when needed.
|
||||
this.subs.push(sub);
|
||||
|
||||
sub.on('event', (originalEvent: any) => {
|
||||
const event = this.eventService.processEvent(originalEvent);
|
||||
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventIndex = this.thread.findIndex((e) => e.id == event.id);
|
||||
|
||||
if (eventIndex > -1) {
|
||||
this.thread[eventIndex] = event;
|
||||
} else {
|
||||
this.thread.unshift(event);
|
||||
}
|
||||
});
|
||||
|
||||
sub.on('eose', () => {
|
||||
console.log('Initial load of people feed completed.');
|
||||
sub.loading = false;
|
||||
sub.unsub();
|
||||
});
|
||||
}
|
||||
|
||||
async downloadProfile(pubkey: string) {
|
||||
console.log('ADD DOWNLOAD PROFILE:', pubkey);
|
||||
if (!pubkey) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
<!-- <app-profile-image [publicKey]="event.pubkey"></app-profile-image> -->
|
||||
</a>
|
||||
</div>
|
||||
<div class="name">
|
||||
<div class="name clickable">
|
||||
<a [routerLink]="['/user', pubkey]">
|
||||
<span [class.muted]="muted" [matTooltip]="tooltipName" matTooltipPosition="above">{{ profileName }}</span>
|
||||
<!-- <span *ngIf="!tooltip" [matTooltip]="tooltipName">{{ profileName }}</span> -->
|
||||
|
@ -33,7 +33,7 @@
|
||||
><span class="event-date">{{ event.created_at | ago }}</span> <app-directory-icon [pubkey]="event.pubkey"></app-directory-icon
|
||||
></app-profile-header>
|
||||
|
||||
<div class="content">{{ event.content }}<span *ngIf="event.contentCut">... (message was truncated)</span></div>
|
||||
<span class="content">{{ event.content }}<span *ngIf="event.contentCut">... (message was truncated)</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -198,11 +198,21 @@ mat-sidenav-content mat-toolbar {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.events:hover {
|
||||
// .events {
|
||||
// background-color: #535353;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
|
||||
.events.clickable:hover {
|
||||
background-color: #535353;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.noclick:hover {
|
||||
background-color: #424242;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.items {
|
||||
margin-top: 1em;
|
||||
padding: 1em;
|
||||
|
Loading…
Reference in New Issue
Block a user