This commit is contained in:
miggymofongo 2024-12-05 21:52:42 -04:00
parent a59b6a5cfd
commit dcea70fc17
8 changed files with 1013 additions and 75 deletions

827
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,9 @@
"@lit/localize": "^0.12.2", "@lit/localize": "^0.12.2",
"@shoelace-style/shoelace": "^2.18.0", "@shoelace-style/shoelace": "^2.18.0",
"@thepassle/app-tools": "^0.9.12", "@thepassle/app-tools": "^0.9.12",
"axios": "^1.7.7",
"cors": "^2.8.5",
"express": "^4.21.1",
"lit": "^3.2.1", "lit": "^3.2.1",
"nostr-tools": "^2.10.1", "nostr-tools": "^2.10.1",
"urlpattern-polyfill": "^10.0.0", "urlpattern-polyfill": "^10.0.0",
@ -26,6 +29,7 @@
"workbox-precaching": "^7.3.0" "workbox-precaching": "^7.3.0"
}, },
"devDependencies": { "devDependencies": {
"concurrently": "^9.1.0",
"typescript": "^5.6.3", "typescript": "^5.6.3",
"vite": "^5.4.11", "vite": "^5.4.11",
"vite-plugin-pwa": "^0.20.5" "vite-plugin-pwa": "^0.20.5"

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -70,7 +70,7 @@ export class AppHeader extends LitElement {
//is there a stored pubkey in localStorage already? //is there a stored pubkey in localStorage already?
const storedPubkey = localStorage.getItem('pubkey'); const storedPubkey = localStorage.getItem('pubkey');
if (storedPubkey) { if (storedPubkey) {
this.isSignedIn = true; this.isSignedIn = false;
this.fetchProfileMetadata(storedPubkey); this.fetchProfileMetadata(storedPubkey);
} else { } else {
// Show guest view by default // Show guest view by default
@ -81,7 +81,7 @@ export class AppHeader extends LitElement {
// Set initial values for guest view // Set initial values for guest view
this.nostrAddy = ''; this.nostrAddy = '';
this.bio = 'Welcome, guest! Please sign in with a browser extension to view your profile.'; this.bio = 'Welcome, guest! Please sign in with a browser extension to view your profile.';
this.profilePic = ''; // Could be a placeholder image for guests this.profilePic = '/public/assets/img/default_pfp.png'; // Could be a placeholder image for guests
this.isSignedIn = false; this.isSignedIn = false;
} }
@ -98,20 +98,27 @@ export class AppHeader extends LitElement {
], { ], {
onevent: (event) => { onevent: (event) => {
const profileData = JSON.parse(event.content); const profileData = JSON.parse(event.content);
this.profilePic = profileData.picture || ''; this.profilePic = profileData.picture || '/assets/img/default_pfp.png';
this.nostrAddy = profileData.nip05 || ''; this.nostrAddy = profileData.nip05 || 'no addy available';
this.bio = profileData.about || ''; this.bio = profileData.about || 'bio not available';
this.dispatchEvent(new CustomEvent('profile-updated' , { this.dispatchEvent(new CustomEvent('profile-updated' , {
detail: { nostrAddy: this.nostrAddy, bio: this.bio, profilePic: this.profilePic }, detail: { nostrAddy: this.nostrAddy, bio: this.bio, profilePic: this.profilePic },
bubbles: true, bubbles: true,
composed: true composed: true
})); }));
console.log('Profile updated event dispatched:', { nostrAddy: this.nostrAddy, bio: this.bio, profilePic: this.profilePic });
}, },
oneose: () => { oneose: () => {
sub.close(); sub.close();
} }
}); });
} catch (error: any) {
console.error('Failed to fetch profile metadata:', error);
this.profilePic = '/assets/img/default_pfp.png';
this.nostrAddy = 'failed to fetch address';
this.bio = 'failed to fetch profile'
} }
async signInWithNostr() { async signInWithNostr() {
@ -126,6 +133,7 @@ export class AppHeader extends LitElement {
await this.fetchProfileMetadata(userPubkey); await this.fetchProfileMetadata(userPubkey);
} catch (error) { } catch (error) {
console.error('Failed to sign in:', error); console.error('Failed to sign in:', error);
this.isSignedIn = false;
} }
} else { } else {
alert('Nostr extension not detected. Please install a Nostr extension.'); alert('Nostr extension not detected. Please install a Nostr extension.');
@ -134,9 +142,12 @@ export class AppHeader extends LitElement {
signOut() { signOut() {
// clear pubkey from localStorage and reset to guest view // clear pubkey from localStorage and reset to guest view
localStorage.removeItem('pubKey'); localStorage.removeItem('pubkey');
this.displayGuestView(); this.displayGuestView();
console.log('signed out')
this.isSignedIn = false;
console.log('signed out')
this.dispatchEvent( new CustomEvent('user-signed-out', { this.dispatchEvent( new CustomEvent('user-signed-out', {
bubbles: true, bubbles: true,
@ -157,7 +168,7 @@ export class AppHeader extends LitElement {
</div> </div>
<sl-button variant="primary" @click="${this.isSignedIn ? this.signOut : this.signInWithNostr}"> <sl-button variant="primary" @click="${this.isSignedIn ? this.signOut : this.signInWithNostr}">
${this.isSignedIn ? 'Sign out' : 'Sign in with Nostr'} ${this.isSignedIn ? 'Sign out' : 'Sign in'}
</sl-button> </sl-button>
</header> </header>

View file

@ -3,7 +3,6 @@ import { property, customElement } from 'lit/decorators.js';
import { resolveRouterPath } from '../router'; import { resolveRouterPath } from '../router';
import { Relay } from 'nostr-tools'; import { Relay } from 'nostr-tools';
import { WindowNostr } from 'nostr-tools/nip07'; import { WindowNostr } from 'nostr-tools/nip07';
import '../img/default_pfp.png'
import '@shoelace-style/shoelace/dist/components/card/card.js'; import '@shoelace-style/shoelace/dist/components/card/card.js';
import '@shoelace-style/shoelace/dist/components/button/button.js'; import '@shoelace-style/shoelace/dist/components/button/button.js';
@ -27,6 +26,7 @@ export class AppHome extends LitElement {
@property({ type: String }) profilePic = ''; @property({ type: String }) profilePic = '';
@property({ type: Boolean }) isSignedIn = false; @property({ type: Boolean }) isSignedIn = false;
@property({ type: String }) publicKey = ''; @property({ type: String }) publicKey = '';
@property({ type: String }) updateProfileFromEvent = ''
static styles = [ static styles = [
@ -76,10 +76,11 @@ Profile Picture Container
*/ */
.profile-picture-container { .profile-picture-container {
display: grid; display: grid;
grid-template-columns: 200px 115px; grid-template-columns: 300px 150px;
grid-template-rows: 250px; grid-template-rows: 250px;
grid-column-gap: 15px; grid-column-gap: 15px;
margin-bottom: 12px; margin-bottom: 12px;
} }
.profile-picture-container p { .profile-picture-container p {
@ -90,11 +91,12 @@ Profile Picture Container
grid-area: 1/1; grid-area: 1/1;
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
border-radius: 50%;
} }
.personal-msg { .personal-msg {
grid-area: 1/2; grid-area: 1/2;
margin: 0; margin-top: 300px;
} }
@ -107,9 +109,6 @@ Profile Picture Container
`]; `];
async firstUpdated() { async firstUpdated() {
// this method is a lifecycle even in lit // this method is a lifecycle even in lit
// for more info check out the lit docs https://lit.dev/docs/components/lifecycle/ // for more info check out the lit docs https://lit.dev/docs/components/lifecycle/
@ -119,29 +118,43 @@ Profile Picture Container
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.addEventListener('user-signed-out', this.handlesSignOut.bind(this)) window.addEventListener('profile-updated', this.updateProfileFromEvent.bind(this));
const storedPubkey = localStorage.getItem('pubkey');
if (storedPubkey) {
// Manually fetch profile metadata on mount
this.nostrAddy = 'Fetching...';
this.bio = 'Fetching profile...';
this.profilePic = '/assets/img/default_pfp.png';
}
} }
handlesSignOut() {
this.profilePic = '';
this.bio = '';
this.nostrAddy = 'Guest'
}
disconnectedCallback(): void { disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
window.removeEventListener('profile-updated', this.updateProfileFromEvent.bind(this)); window.removeEventListener('profile-updated', this.updateProfileFromEvent.bind(this));
window.removeEventListener('user-signed-out', this.handlesSignOut.bind(this));
} }
updateProfileFromEvent(event: Event) { /* updateProfileFromEvent(event: Event) {
console.log('Profile updated event received:', (event as CustomEvent).detail);
const detail = (event as CustomEvent).detail; const detail = (event as CustomEvent).detail;
this.nostrAddy = detail.nostrAddy; this.nostrAddy = detail.nostrAddy;
this.bio = detail.bio; this.bio = detail.bio;
this.profilePic = detail.profilePic; this.profilePic = detail.profilePic;
this.isSignedIn = true; this.isSignedIn = true;
this.requestUpdate(); this.requestUpdate();
} */
handlesSignOut() {
this.profilePic = '';
this.bio = '';
this.nostrAddy = 'Guest';
this.isSignedIn = false;
this.requestUpdate();
} }
displayGuestView() { displayGuestView() {
// Set initial values for guest view // Set initial values for guest view
this.nostrAddy = ''; this.nostrAddy = '';
@ -151,8 +164,6 @@ handlesSignOut() {
} }
share() { share() {
if ((navigator as any).share) { if ((navigator as any).share) {
(navigator as any).share({ (navigator as any).share({
@ -166,9 +177,6 @@ handlesSignOut() {
render() { render() {
return html` return html`
<app-header></app-header> <app-header></app-header>
@ -188,9 +196,7 @@ handlesSignOut() {
</p> </p>
<div class="profile-picture-container"> <div class="profile-picture-container">
${this.profilePic <img class="profile-pic" src="${this.profilePic || '/assets/img/default_pfp.png'}" alt="Profile Picture" width="200px" height="200px">
? html`<img class="profile-pic" src="${this.profilePic}" alt="Profile Picture" width="200px" height="200px">`
: html`<img class="profile-pic" src="img/default_pfp.png" alt="Guest Profile Picture" width="200px" height="200px">`}
<p class="personal-msg"><b>${this.bio || 'Welcome, guest! Please sign in to view your profile.'}</b></p> <p class="personal-msg"><b>${this.bio || 'Welcome, guest! Please sign in to view your profile.'}</b></p>

View file

@ -2,6 +2,7 @@ import { LitElement, css, html } from 'lit';
import { property, customElement } from 'lit/decorators.js'; import { property, customElement } from 'lit/decorators.js';
import { resolveRouterPath } from '../router'; import { resolveRouterPath } from '../router';
import { Relay } from 'nostr-tools'; import { Relay } from 'nostr-tools';
import axios from 'axios';
import '@shoelace-style/shoelace/dist/components/card/card.js'; import '@shoelace-style/shoelace/dist/components/card/card.js';
import '@shoelace-style/shoelace/dist/components/button/button.js'; import '@shoelace-style/shoelace/dist/components/button/button.js';
@ -16,7 +17,6 @@ export class NoteWall extends LitElement {
static styles = [ static styles = [
styles, styles,
css` css`
@ -144,6 +144,10 @@ export class NoteWall extends LitElement {
note = ''; // store notes note = ''; // store notes
getUserLang() {
return navigator.language || 'en';
}
async firstUpdated() { async firstUpdated() {
// this method is a lifecycle even in lit // this method is a lifecycle even in lit
// for more info check out the lit docs https://lit.dev/docs/components/lifecycle/ // for more info check out the lit docs https://lit.dev/docs/components/lifecycle/
@ -158,38 +162,59 @@ export class NoteWall extends LitElement {
]); ]);
} }
async fetchNotes() {
const ifcaRelay = await Relay.connect('wss://hi.myvoiceourstory.org');
const migsRelay = await Relay.connect('wss://notes.miguelalmodo.com');
console.log(`connected to ${ifcaRelay.url}`);
console.log(`connected to ${migsRelay.url}`);
const eric = 'f8d17812ce41a9b145aede4f0720050ab63607f51688832b5d78c65d620ec7d9'
const migs = 'ec965405e11a6a6186b27fa451a2ffc1396ede7883d2ea11c32fbd2c63996966'
const sub = ifcaRelay.subscribe([ async translateText(text: string, targetLang: string) {
{ try {
kinds: [1], // short form notes const response = await axios.post('https://api-free.deepl.com/v2/translate', {
limit: 20, text, target_lang: targetLang });
authors: [eric]
}
], {
onevent: (event) => {
this.notes.push({ return response.data.translations[0].text; // Assuming DeepL's API structure
content: event.content, } catch (error) {
date: new Date(event.created_at * 1000).toLocaleDateString() console.error('Error during translation:', error.response?.data || error.message);
}); return '';
this.requestUpdate();
console.log(event)
},
oneose: () => {
sub.close();
this.requestUpdate();
}
});
} }
}
async fetchNotes() {
const ifcaRelay = await Relay.connect('wss://hi.myvoiceourstory.org');
const migsRelay = await Relay.connect('wss://notes.miguelalmodo.com');
console.log(`connected to ${ifcaRelay.url}`);
console.log(`connected to ${migsRelay.url}`);
const sub = ifcaRelay.subscribe([
{
kinds: [1], // short form notes
limit: 20,
}
], {
onevent: async (event) => {
// Translate the note content to the user's language
const userLang = this.getUserLang();
const translatedContent = await this.translateText(event.content, userLang);
this.notes.push({
content: translatedContent,
date: new Date(event.created_at * 1000).toLocaleDateString(),
});
this.requestUpdate();
console.log(event);
},
oneose: () => {
sub.close();
this.requestUpdate();
},
});
}
share() { share() {
@ -216,24 +241,41 @@ export class NoteWall extends LitElement {
<table> <table>
${this.notes.map(note => { ${this.notes.map(note => {
// Check if there's a URL in the content // Extract URL from note content
const urlMatch = note.content.match(/https?:\/\/[^\s]+/); const urlMatch = note.content.match(/https?:\/\/[^\s]+/);
const imageUrl = urlMatch ? urlMatch[0] : null; const textContent = note.content.replace(urlMatch?.[0] || '', '').trim();
const textContent = note.content.replace(urlMatch?.[0] || '', '').trim();
// Check for YouTube links and extract video ID
const youtubeRegex = /(?:https?:\/\/(?:www\.)?youtube\.com\/watch\?v=|https?:\/\/youtu\.be\/)([a-zA-Z0-9_-]{11})/;
const youtubeMatch = urlMatch?.[0].match(youtubeRegex);
const youtubeVideoId = youtubeMatch ? youtubeMatch[1] : null;
// Construct YouTube thumbnail URL if applicable
const thumbnailUrl = youtubeVideoId
? `https://img.youtube.com/vi/${youtubeVideoId}/maxresdefault.jpg`
: null;
return html`
<tr>
<td><h3>${note.date}</h3></td>
<td>
<p>${textContent}</p>
${thumbnailUrl
? html`<img src="${thumbnailUrl}" alt="YouTube thumbnail" style="max-width: 30%; height: 30%;">`
: urlMatch
? html`<img src="${urlMatch[0]}" alt="Note image" style="max-width: 30%; height: 30%;">`
: ''}
</td>
</tr>
`;
})}
</table>
return html`
<tr>
<td><h3>${note.date}</h3></td>
<td>
<p>${textContent}</p>
${imageUrl ? html`<img src="${imageUrl}" alt="Note image" style="max-width: 30%; height: 30%;">` : ''}
</td>
</tr>
`;
})}
</table>
</section></main> </section></main>

45
src/server.js Normal file
View file

@ -0,0 +1,45 @@
const express = require('express');
const axios = require('axios');
const bodyParser = require('body-parser');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:5176'); // Allow your frontend
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); // Allow required HTTP methods
res.header('Access-Control-Allow-Headers', 'Content-Type'); // Allow required headers
next();
});
app.post('/translate', async (req, res) => {
const { text, target_lang } = req.body;
console.log('Received request:', { text, target_lang });
if (!text || !target_lang) {
console.error('Invalid request payload:', req.body);
return res.status(400).send('Missing text or target_lang');
}
try {
const apiKey = '5e8b82a7-3595-466d-bb60-b11d5c128697:fx'; // Ensure this is set
const response = await axios.post('https://api.deepl.com/v2/translate', {
auth_key: apiKey,
text,
target_lang,
});
console.log('Translation API response:', response.data);
res.status(200).json(response.data);
} catch (error) {
console.error('Error during translation:', error.response?.data || error.message);
res.status(500).send('Error during translation.');
}
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});

View file

@ -4,6 +4,9 @@ import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: "/", base: "/",
server: {
port: 5176,
},
build: { build: {
sourcemap: true, sourcemap: true,
assetsDir: "code", assetsDir: "code",