mirror of
https://github.com/block-core/blockcore-notes.git
synced 2024-09-29 06:20:42 +00:00
Add ability to publish articles
- Ensures that the relay supports NIP-33.
This commit is contained in:
parent
0d969586c0
commit
e16e29b28b
@ -164,6 +164,14 @@ const routes: Routes = [
|
||||
data: LoadingResolverService,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'a/:id',
|
||||
component: NoteComponent,
|
||||
canActivate: [AuthGuard],
|
||||
resolve: {
|
||||
data: LoadingResolverService,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'followers/:id',
|
||||
component: FollowersComponent,
|
||||
|
@ -121,6 +121,7 @@ import { PasswordDialog } from './shared/password-dialog/password-dialog';
|
||||
import { UsernamePipe } from './shared/username';
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
import { EditorComponent } from './editor/editor';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -195,7 +196,7 @@ import { EditorComponent } from './editor/editor';
|
||||
ConnectKeyComponent,
|
||||
PasswordDialog,
|
||||
UsernamePipe,
|
||||
EditorComponent
|
||||
EditorComponent,
|
||||
],
|
||||
imports: [
|
||||
AboutModule,
|
||||
@ -239,6 +240,7 @@ import { EditorComponent } from './editor/editor';
|
||||
MatProgressBarModule,
|
||||
MatDialogModule,
|
||||
MatDatepickerModule,
|
||||
MatButtonToggleModule,
|
||||
ScrollingModule,
|
||||
PhotoGalleryModule,
|
||||
NgxMatDatetimePickerModule,
|
||||
|
@ -20,19 +20,6 @@ h1 {
|
||||
color: #9c27b0;
|
||||
}
|
||||
|
||||
.maximize-button {
|
||||
cursor: pointer;
|
||||
margin-right: auto;
|
||||
color: #d87fe7;
|
||||
}
|
||||
|
||||
.maximize-button:hover {
|
||||
color: #9c27b0;
|
||||
}
|
||||
|
||||
.note-input {
|
||||
}
|
||||
|
||||
.margin-right {
|
||||
margin-right: 5px;
|
||||
}
|
||||
@ -42,3 +29,8 @@ h1 {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.note-type {
|
||||
text-align: right;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
@ -1,17 +1,67 @@
|
||||
<div class="page">
|
||||
<!-- <mat-card class="home-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Create Note</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content> -->
|
||||
<h1>Write your thoughts</h1>
|
||||
|
||||
<form [formGroup]="formGroup">
|
||||
<div mat-dialog-content class="mat-dialog-content">
|
||||
<!-- <div class="toolbar">
|
||||
<mat-icon class="toolbar-icon margin-right" (click)="isEmojiPickerVisible = !isEmojiPickerVisible;" matTooltip="Insert emoji">sentiment_satisfied</mat-icon>
|
||||
</div> -->
|
||||
<div class="note-type">
|
||||
<mat-button-toggle-group name="fontStyle" [(ngModel)]="eventType" aria-label="Font Style" #group="matButtonToggleGroup">
|
||||
<mat-button-toggle value="text">Text</mat-button-toggle>
|
||||
<mat-button-toggle value="blog">Blog</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="blogForm" *ngIf="group.value == 'blog'" (ngSubmit)="onSubmitBlog()">
|
||||
<div mat-dialog-content class="mat-dialog-content">
|
||||
<mat-form-field appearance="outline" class="input-full-width">
|
||||
<mat-label>Title</mat-label>
|
||||
<input matInput #message formControlName="title" placeholder="Ex. My favorite food..." />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="input-full-width">
|
||||
<mat-label>URL (slug)</mat-label>
|
||||
<input matInput #message placeholder="Can only contain - and lower case text" formControlName="slug" (blur)="formatSlug()" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="input-full-width">
|
||||
<mat-label>Long form text that supports markdown formatting</mat-label>
|
||||
<textarea class="note-input" matInput type="text" [(ngModel)]="note" formControlName="content" rows="7"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="input-full-width">
|
||||
<mat-label>Summary (optional)</mat-label>
|
||||
<textarea class="note-input" matInput type="text" [(ngModel)]="summary" formControlName="summary" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="input-full-width">
|
||||
<mat-label>Banner image (optional)</mat-label>
|
||||
<input matInput #message placeholder="Blog post banner image" formControlName="image" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="input-full-width">
|
||||
<mat-label>Tags (optional, comma separated)</mat-label>
|
||||
<input matInput #message placeholder="Tech, News, Social" formControlName="tags" />
|
||||
</mat-form-field>
|
||||
|
||||
<emoji-mart class="picker" *ngIf="isEmojiPickerVisible" emoji="point_up" [isNative]="true" [showPreview]="false" (emojiSelect)="addEmoji($event)" title="Choose your emoji"></emoji-mart>
|
||||
<mat-icon class="toolbar-icon margin-right" (click)="isEmojiPickerVisible = !isEmojiPickerVisible;" matTooltip="Insert emoji">sentiment_satisfied</mat-icon>
|
||||
<!-- <mat-icon class="toolbar-icon margin-right" matTooltip="Upload file">attach_file</mat-icon> -->
|
||||
|
||||
<!-- <mat-form-field>
|
||||
<input matInput [ngxMatDatetimePicker]="picker" placeholder="Choose a timeout" [formControl]="dateControl" [min]="minDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="$any(picker)"></mat-datepicker-toggle>
|
||||
<ngx-mat-datetime-picker #picker [showSpinners]="true" [showSeconds]="false" [stepHour]="1" [stepMinute]="1" [stepSecond]="1" [touchUi]="false" [enableMeridian]="false" [disableMinute]="false" [hideTime]="false">
|
||||
<ng-template>
|
||||
<span>Set timeout</span>
|
||||
</ng-template>
|
||||
</ngx-mat-datetime-picker>
|
||||
</mat-form-field> -->
|
||||
</div>
|
||||
<div mat-dialog-actions class="mat-dialog-actions" align="end">
|
||||
<button mat-stroked-button type="button" (click)="onCancel()">Cancel</button> <button type="button" mat-stroked-button disabled="disabled">Save Draft</button>
|
||||
<button mat-flat-button [disabled]="!blogForm.valid" type="submit" color="primary">Publish</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form [formGroup]="noteForm" *ngIf="group.value == 'text'" (ngSubmit)="onSubmitNote()">
|
||||
<div mat-dialog-content class="mat-dialog-content">
|
||||
<mat-form-field appearance="outline" class="input-full-width">
|
||||
<mat-label>What's on your mind?</mat-label>
|
||||
<textarea class="note-input" matInput type="text" [(ngModel)]="note" formControlName="note" rows="7"></textarea>
|
||||
@ -32,8 +82,8 @@
|
||||
</mat-form-field> -->
|
||||
</div>
|
||||
<div mat-dialog-actions class="mat-dialog-actions" align="end">
|
||||
<button mat-stroked-button (click)="onCancel()">Cancel</button> <button mat-stroked-button disabled="disabled">Save Draft</button>
|
||||
<button mat-flat-button [disabled]="note == ''" (click)="postNote()" color="primary" cdkFocusInitial>Publish</button>
|
||||
<button mat-stroked-button type="button" (click)="onCancel()">Cancel</button> <button mat-stroked-button type="button" disabled="disabled">Save Draft</button>
|
||||
<button mat-flat-button [disabled]="!noteForm.valid" type="submit" color="primary">Publish</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- </mat-card-content>
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { FormControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { FormBuilder, FormControl, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { NavigationService } from '../services/navigation';
|
||||
import { Location } from '@angular/common';
|
||||
import { ApplicationState } from '../services/applicationstate';
|
||||
import { BlogEvent } from '../services/interfaces';
|
||||
import { Event } from 'nostr-tools';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Utilities } from '../services/utilities';
|
||||
|
||||
export interface NoteDialogData {
|
||||
note: string;
|
||||
@ -18,15 +22,38 @@ export class EditorComponent {
|
||||
|
||||
isEmojiPickerVisible: boolean | undefined;
|
||||
|
||||
formGroup!: UntypedFormGroup;
|
||||
noteForm = this.fb.group({
|
||||
note: ['', Validators.required],
|
||||
expiration: [''],
|
||||
dateControl: [],
|
||||
});
|
||||
|
||||
blogForm = this.fb.group({
|
||||
content: ['', Validators.required],
|
||||
title: ['', Validators.required],
|
||||
summary: [''],
|
||||
image: [''],
|
||||
slug: [''],
|
||||
tags: [''],
|
||||
});
|
||||
|
||||
note: string = '';
|
||||
|
||||
blog?: BlogEvent = { title: '', content: '', tags: '' };
|
||||
|
||||
title = '';
|
||||
|
||||
summary = '';
|
||||
|
||||
minDate?: number;
|
||||
|
||||
eventType: string = 'text';
|
||||
|
||||
public dateControl = new FormControl(null);
|
||||
|
||||
constructor(private appState: ApplicationState, private location: Location, private formBuilder: UntypedFormBuilder, public navigation: NavigationService) {}
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
constructor(private utilities: Utilities, private appState: ApplicationState, private location: Location, private fb: FormBuilder, public navigation: NavigationService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.appState.updateTitle(`Write a note`);
|
||||
@ -36,11 +63,28 @@ export class EditorComponent {
|
||||
|
||||
this.minDate = Date.now();
|
||||
|
||||
this.formGroup = this.formBuilder.group({
|
||||
note: ['', Validators.required],
|
||||
expiration: [''],
|
||||
dateControl: [],
|
||||
});
|
||||
this.subscriptions.push(
|
||||
this.blogForm.controls.title.valueChanges.subscribe((text) => {
|
||||
if (text) {
|
||||
this.blogForm.controls.slug.setValue(this.createSlug(text));
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.utilities.unsubscribe(this.subscriptions);
|
||||
}
|
||||
|
||||
createSlug(input: string) {
|
||||
// convert input to lowercase
|
||||
input = input.toLowerCase();
|
||||
// replace spaces and punctuation with hyphens
|
||||
input = input.replace(/[\s\W]+/g, '-');
|
||||
// remove duplicate or trailing hyphens
|
||||
input = input.replace(/^-+|-+$/g, '');
|
||||
// return the slug
|
||||
return input;
|
||||
}
|
||||
|
||||
public addEmoji(event: { emoji: { native: any } }) {
|
||||
@ -49,6 +93,49 @@ export class EditorComponent {
|
||||
this.isEmojiPickerVisible = false;
|
||||
}
|
||||
|
||||
postBlog() {
|
||||
// this.formGroupBlog.controls.
|
||||
|
||||
// this.profileForm.value
|
||||
|
||||
console.log('BLOG:', this.blog);
|
||||
// this.navigation.saveNote(this.note);
|
||||
}
|
||||
|
||||
formatSlug() {
|
||||
this.blogForm.controls.slug.setValue(this.createSlug(this.blogForm.controls.slug.value!));
|
||||
}
|
||||
|
||||
onSubmitBlog() {
|
||||
const controls = this.blogForm.controls;
|
||||
|
||||
const blog: BlogEvent = {
|
||||
content: controls.content.value!,
|
||||
title: controls.title.value!,
|
||||
summary: controls.summary.value!,
|
||||
image: controls.image.value!,
|
||||
slug: controls.slug.value!,
|
||||
tags: controls.tags.value!,
|
||||
};
|
||||
|
||||
this.navigation.saveBlog(blog);
|
||||
|
||||
// const entry: Event = {
|
||||
// kind: 30023,
|
||||
// id: '',
|
||||
// sig: '',
|
||||
// content: '',
|
||||
// tags: [],
|
||||
// created_at:
|
||||
// };
|
||||
|
||||
console.log('SUBMIT BLOG!!');
|
||||
}
|
||||
|
||||
onSubmitNote() {
|
||||
console.log('SUBMIT NOTE!');
|
||||
}
|
||||
|
||||
postNote() {
|
||||
this.navigation.saveNote(this.note);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { NostrEvent, NostrEventDocument, NostrProfileDocument, NostrRelay, Nostr
|
||||
import { ProfileService } from './profile';
|
||||
import { EventService } from './event';
|
||||
import { RelayService } from './relay';
|
||||
import { Filter, Relay, Event, getEventHash, validateEvent, verifySignature, Kind } from 'nostr-tools';
|
||||
import { Filter, Relay, Event, getEventHash, validateEvent, verifySignature, Kind, UnsignedEvent } from 'nostr-tools';
|
||||
import { DataValidation } from './data-validation';
|
||||
import { ApplicationState } from './applicationstate';
|
||||
import { timeout, map, merge, Observable, delay, Observer, race, take, switchMap, mergeMap, tap, finalize, concatMap, mergeAll, exhaustMap, catchError, of, combineAll, combineLatestAll, filter, from } from 'rxjs';
|
||||
@ -64,9 +64,7 @@ export class DataService {
|
||||
async publishRelays() {
|
||||
const mappedRelays = this.getArrayFomattedRelayList();
|
||||
|
||||
let originalEvent: Event = {
|
||||
id: '',
|
||||
sig: '',
|
||||
let originalEvent: UnsignedEvent = {
|
||||
kind: 10002, // NIP-65: https://github.com/nostr-protocol/nips/blob/master/65.md
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: '',
|
||||
@ -115,24 +113,29 @@ export class DataService {
|
||||
return mappedRelays;
|
||||
}
|
||||
|
||||
private async createAndSignEvent(originalEvent: Event) {
|
||||
originalEvent.id = getEventHash(originalEvent);
|
||||
private async createAndSignEvent(originalEvent: UnsignedEvent) {
|
||||
let signedEvent = originalEvent as Event;
|
||||
|
||||
signedEvent.id = getEventHash(originalEvent);
|
||||
|
||||
// Use nostr directly on global, similar to how most Nostr app will interact with the provider.
|
||||
const signedEvent = await this.nostr.sign(originalEvent);
|
||||
originalEvent = signedEvent;
|
||||
signedEvent = await this.nostr.sign(originalEvent);
|
||||
|
||||
// We force validation upon user so we make sure they don't create content that we won't be able to parse back later.
|
||||
// We must do this before we run nostr-tools validate and signature validation.
|
||||
const event = this.eventService.processEvent(originalEvent as NostrEventDocument);
|
||||
const event = this.eventService.processEvent(signedEvent as NostrEventDocument);
|
||||
|
||||
let ok = validateEvent(originalEvent);
|
||||
if (!event) {
|
||||
throw new Error('The event is not valid. Cannot publish.');
|
||||
}
|
||||
|
||||
let ok = validateEvent(signedEvent);
|
||||
|
||||
if (!ok) {
|
||||
throw new Error('The event is not valid. Cannot publish.');
|
||||
}
|
||||
|
||||
let veryOk = await verifySignature(originalEvent as any); // Required .id and .sig, which we know has been added at this stage.
|
||||
let veryOk = await verifySignature(event as any); // Required .id and .sig, which we know has been added at this stage.
|
||||
|
||||
if (!veryOk) {
|
||||
throw new Error('The event signature not valid. Maybe you choose a different account than the one specified?');
|
||||
@ -182,9 +185,7 @@ export class DataService {
|
||||
|
||||
const mappedRelays = this.getJsonFormattedRelayList();
|
||||
|
||||
let originalEvent: Event = {
|
||||
id: '',
|
||||
sig: '',
|
||||
let originalEvent: UnsignedEvent = {
|
||||
kind: Kind.Contacts,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: JSON.stringify(mappedRelays),
|
||||
@ -724,10 +725,8 @@ export class DataService {
|
||||
}
|
||||
|
||||
/** Creates an event ready for modification, signing and publish. */
|
||||
createEvent(kind: Kind | number, content: any): Event {
|
||||
let event: Event = {
|
||||
id: '',
|
||||
sig: '',
|
||||
createEvent(kind: Kind | number, content: any): UnsignedEvent {
|
||||
let event: UnsignedEvent = {
|
||||
kind: kind,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: content,
|
||||
@ -738,19 +737,21 @@ export class DataService {
|
||||
return event;
|
||||
}
|
||||
|
||||
/** Request an event to be signed. This method will calculate the content id automatically. */
|
||||
async signEvent(event: Event) {
|
||||
if (!event.id) {
|
||||
event.id = getEventHash(event);
|
||||
}
|
||||
/** Request an article to be signed. This method does not add id. */
|
||||
async signArticle(event: UnsignedEvent) {
|
||||
let signedEvent = event as Event;
|
||||
|
||||
// Use nostr directly on global, similar to how most Nostr app will interact with the provider.
|
||||
const signedEvent = await this.nostr.sign(event);
|
||||
signedEvent = await this.nostr.sign(event);
|
||||
|
||||
// We force validation upon user so we make sure they don't create content that we won't be able to parse back later.
|
||||
// We must do this before we run nostr-tools validate and signature validation.
|
||||
const verifiedEvent = this.eventService.processEvent(signedEvent as NostrEventDocument);
|
||||
|
||||
if (!verifiedEvent) {
|
||||
throw new Error('The event is not valid. Cannot publish.');
|
||||
}
|
||||
|
||||
let ok = validateEvent(signedEvent);
|
||||
|
||||
if (!ok) {
|
||||
@ -766,6 +767,38 @@ export class DataService {
|
||||
return signedEvent;
|
||||
}
|
||||
|
||||
/** Request an event to be signed. This method will calculate the content id automatically. */
|
||||
async signEvent(event: UnsignedEvent) {
|
||||
let signedEvent = event as Event;
|
||||
|
||||
if (!signedEvent.id) {
|
||||
signedEvent.id = getEventHash(event);
|
||||
}
|
||||
|
||||
return this.signArticle(signedEvent);
|
||||
|
||||
// // Use nostr directly on global, similar to how most Nostr app will interact with the provider.
|
||||
// signedEvent = await this.nostr.sign(event);
|
||||
|
||||
// // We force validation upon user so we make sure they don't create content that we won't be able to parse back later.
|
||||
// // We must do this before we run nostr-tools validate and signature validation.
|
||||
// const verifiedEvent = this.eventService.processEvent(signedEvent as NostrEventDocument);
|
||||
|
||||
// let ok = validateEvent(signedEvent);
|
||||
|
||||
// if (!ok) {
|
||||
// throw new Error('The event is not valid. Cannot publish.');
|
||||
// }
|
||||
|
||||
// let veryOk = await verifySignature(signedEvent as any); // Required .id and .sig, which we know has been added at this stage.
|
||||
|
||||
// if (!veryOk) {
|
||||
// throw new Error('The event signature not valid. Maybe you choose a different account than the one specified?');
|
||||
// }
|
||||
|
||||
// return signedEvent;
|
||||
}
|
||||
|
||||
async publishEvent(event: Event) {
|
||||
this.relayService.publish(event);
|
||||
}
|
||||
@ -775,9 +808,7 @@ export class DataService {
|
||||
return ['p', c];
|
||||
});
|
||||
|
||||
let originalEvent: Event = {
|
||||
id: '',
|
||||
sig: '',
|
||||
let originalEvent: UnsignedEvent = {
|
||||
kind: 3,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: '',
|
||||
@ -785,23 +816,23 @@ export class DataService {
|
||||
tags: mappedContacts,
|
||||
};
|
||||
|
||||
originalEvent.id = getEventHash(originalEvent);
|
||||
let signedEvent = originalEvent as Event;
|
||||
signedEvent.id = getEventHash(originalEvent);
|
||||
|
||||
// Use nostr directly on global, similar to how most Nostr app will interact with the provider.
|
||||
const signedEvent = await this.nostr.sign(originalEvent);
|
||||
originalEvent = signedEvent;
|
||||
signedEvent = await this.nostr.sign(originalEvent);
|
||||
|
||||
// We force validation upon user so we make sure they don't create content that we won't be able to parse back later.
|
||||
// We must do this before we run nostr-tools validate and signature validation.
|
||||
const event = this.eventService.processEvent(originalEvent as NostrEventDocument);
|
||||
const event = this.eventService.processEvent(signedEvent as NostrEventDocument);
|
||||
|
||||
let ok = validateEvent(originalEvent);
|
||||
let ok = validateEvent(signedEvent);
|
||||
|
||||
if (!ok) {
|
||||
throw new Error('The event is not valid. Cannot publish.');
|
||||
}
|
||||
|
||||
let veryOk = await verifySignature(originalEvent as any); // Required .id and .sig, which we know has been added at this stage.
|
||||
let veryOk = await verifySignature(signedEvent as any); // Required .id and .sig, which we know has been added at this stage.
|
||||
|
||||
if (!veryOk) {
|
||||
throw new Error('The event signature not valid. Maybe you choose a different account than the one specified?');
|
||||
@ -811,7 +842,7 @@ export class DataService {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('PUBLISH EVENT:', originalEvent);
|
||||
console.log('PUBLISH EVENT:', signedEvent);
|
||||
|
||||
// First we persist our own event like would normally happen if we receive this event.
|
||||
// await this.#persist(event);
|
||||
|
@ -61,9 +61,9 @@ export interface NostrDocument<T> {
|
||||
}
|
||||
|
||||
export interface NostrRelay extends Relay {
|
||||
// nip11: any;
|
||||
nip11: any;
|
||||
// error: string;
|
||||
metadata: NostrRelayDocument;
|
||||
// metadata: NostrRelayDocument;
|
||||
// subscriptions: Sub[];
|
||||
}
|
||||
|
||||
@ -289,3 +289,19 @@ export interface NotificationModel {
|
||||
|
||||
kind: number;
|
||||
}
|
||||
|
||||
export interface BlogEvent {
|
||||
title: string;
|
||||
|
||||
content: string;
|
||||
|
||||
summary?: string;
|
||||
|
||||
image?: string;
|
||||
|
||||
slug?: string;
|
||||
|
||||
tags: string;
|
||||
|
||||
published_at?: number;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Circle, NostrEvent, NostrEventDocument, NostrProfileDocument, NostrThreadEventDocument } from './interfaces';
|
||||
import { BlogEvent, Circle, NostrEvent, NostrEventDocument, NostrProfileDocument, NostrThreadEventDocument } from './interfaces';
|
||||
import { tap, delay, timer, takeUntil, timeout, Observable, of, BehaviorSubject, map, combineLatest, single, Subject, Observer, concat, concatMap, switchMap, catchError, race } from 'rxjs';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { NoteDialog } from '../shared/create-note-dialog/create-note-dialog';
|
||||
@ -102,6 +102,51 @@ export class NavigationService {
|
||||
this.router.navigate(['/e', signedEvent.id]);
|
||||
}
|
||||
|
||||
/** Saves a new note and navigates to it. */
|
||||
async saveBlog(blog: BlogEvent) {
|
||||
console.log('save blog data:', blog);
|
||||
let note = blog.content;
|
||||
|
||||
if (typeof note !== 'string') {
|
||||
note = JSON.stringify(note);
|
||||
}
|
||||
|
||||
let event = this.dataService.createEvent(Kind.Article, note);
|
||||
|
||||
if (blog.slug) {
|
||||
event.tags.push(['d', blog.slug]);
|
||||
}
|
||||
|
||||
if (blog.summary) {
|
||||
event.tags.push(['summary', blog.summary]);
|
||||
}
|
||||
|
||||
if (blog.title) {
|
||||
event.tags.push(['title', blog.title]);
|
||||
}
|
||||
|
||||
if (blog.image) {
|
||||
event.tags.push(['image', blog.image]);
|
||||
}
|
||||
|
||||
event.tags.push(['published_at', event.created_at.toString()]);
|
||||
|
||||
const tags = blog.tags.split(',').filter((t) => t);
|
||||
|
||||
for (let index = 0; index < tags.length; index++) {
|
||||
const tag = tags[index];
|
||||
event.tags.push(['t', tag]);
|
||||
}
|
||||
|
||||
// TODO: We should likely save this event locally to ensure user don't loose their posts
|
||||
// if all of the network is down.
|
||||
const signedEvent = await this.dataService.signArticle(event);
|
||||
|
||||
await this.dataService.publishEvent(signedEvent);
|
||||
|
||||
this.router.navigate(['/a', signedEvent.id]);
|
||||
}
|
||||
|
||||
createNote(): void {
|
||||
this.router.navigateByUrl('/editor');
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Event, Filter, relayInit } from 'nostr-tools';
|
||||
import { Event, Filter, Kind, relayInit } from 'nostr-tools';
|
||||
import { NostrRelay, NostrRelaySubscription, NostrSub, QueryJob } from '../services/interfaces';
|
||||
import { RelayResponse } from '../services/messages';
|
||||
import { Queue } from '../services/queue';
|
||||
@ -18,6 +18,19 @@ export class RelayWorker {
|
||||
}
|
||||
|
||||
async publish(event: Event) {
|
||||
if (event.kind == Kind.Article) {
|
||||
// If we don't have metadata from the relay, don't publish articles.
|
||||
if (!this.relay.nip11) {
|
||||
console.log(`${this.relay.url}: This relay does not return NIP-11 metadata. Article will not be published here.`);
|
||||
return;
|
||||
} else if (!this.relay.nip11.supported_nips.includes(33)) {
|
||||
console.log(`${this.relay.url}: This relay does not NIP-23. Article will not be published here.`);
|
||||
return;
|
||||
} else {
|
||||
console.log(`${this.relay.url}: This relay supports NIP-23. Publishing article on this relay.`);
|
||||
}
|
||||
}
|
||||
|
||||
let pub = this.relay.publish(event);
|
||||
pub.on('ok', () => {
|
||||
console.log(`${this.relay.url} has accepted our event`);
|
||||
@ -493,6 +506,8 @@ export class RelayWorker {
|
||||
});
|
||||
if (rawResponse.status === 200) {
|
||||
const content = await rawResponse.json();
|
||||
// Keep a local reference to the NIP11 info on the relay instance.
|
||||
this.relay.nip11 = content;
|
||||
postMessage({ type: 'nip11', data: content, url: this.url } as RelayResponse);
|
||||
} else {
|
||||
postMessage({ type: 'nip11', data: { error: `Unable to get NIP-11 data. Status: ${rawResponse.statusText}` }, url: this.url } as RelayResponse);
|
||||
|
Loading…
Reference in New Issue
Block a user