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",
"@shoelace-style/shoelace": "^2.18.0",
"@thepassle/app-tools": "^0.9.12",
"axios": "^1.7.7",
"cors": "^2.8.5",
"express": "^4.21.1",
"lit": "^3.2.1",
"nostr-tools": "^2.10.1",
"urlpattern-polyfill": "^10.0.0",
@ -26,6 +29,7 @@
"workbox-precaching": "^7.3.0"
},
"devDependencies": {
"concurrently": "^9.1.0",
"typescript": "^5.6.3",
"vite": "^5.4.11",
"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?
const storedPubkey = localStorage.getItem('pubkey');
if (storedPubkey) {
this.isSignedIn = true;
this.isSignedIn = false;
this.fetchProfileMetadata(storedPubkey);
} else {
// Show guest view by default
@ -81,7 +81,7 @@ export class AppHeader extends LitElement {
// Set initial values for guest view
this.nostrAddy = '';
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;
}
@ -98,20 +98,27 @@ export class AppHeader extends LitElement {
], {
onevent: (event) => {
const profileData = JSON.parse(event.content);
this.profilePic = profileData.picture || '';
this.nostrAddy = profileData.nip05 || '';
this.bio = profileData.about || '';
this.profilePic = profileData.picture || '/assets/img/default_pfp.png';
this.nostrAddy = profileData.nip05 || 'no addy available';
this.bio = profileData.about || 'bio not available';
this.dispatchEvent(new CustomEvent('profile-updated' , {
detail: { nostrAddy: this.nostrAddy, bio: this.bio, profilePic: this.profilePic },
bubbles: true,
composed: true
}));
console.log('Profile updated event dispatched:', { nostrAddy: this.nostrAddy, bio: this.bio, profilePic: this.profilePic });
},
oneose: () => {
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() {
@ -126,6 +133,7 @@ export class AppHeader extends LitElement {
await this.fetchProfileMetadata(userPubkey);
} catch (error) {
console.error('Failed to sign in:', error);
this.isSignedIn = false;
}
} else {
alert('Nostr extension not detected. Please install a Nostr extension.');
@ -134,9 +142,12 @@ export class AppHeader extends LitElement {
signOut() {
// clear pubkey from localStorage and reset to guest view
localStorage.removeItem('pubKey');
localStorage.removeItem('pubkey');
this.displayGuestView();
console.log('signed out')
this.isSignedIn = false;
console.log('signed out')
this.dispatchEvent( new CustomEvent('user-signed-out', {
bubbles: true,
@ -157,7 +168,7 @@ export class AppHeader extends LitElement {
</div>
<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>
</header>

View file

@ -3,7 +3,6 @@ import { property, customElement } from 'lit/decorators.js';
import { resolveRouterPath } from '../router';
import { Relay } from 'nostr-tools';
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/button/button.js';
@ -27,6 +26,7 @@ export class AppHome extends LitElement {
@property({ type: String }) profilePic = '';
@property({ type: Boolean }) isSignedIn = false;
@property({ type: String }) publicKey = '';
@property({ type: String }) updateProfileFromEvent = ''
static styles = [
@ -76,10 +76,11 @@ Profile Picture Container
*/
.profile-picture-container {
display: grid;
grid-template-columns: 200px 115px;
grid-template-columns: 300px 150px;
grid-template-rows: 250px;
grid-column-gap: 15px;
margin-bottom: 12px;
}
.profile-picture-container p {
@ -90,11 +91,12 @@ Profile Picture Container
grid-area: 1/1;
margin-top: 10px;
margin-bottom: 10px;
border-radius: 50%;
}
.personal-msg {
grid-area: 1/2;
margin: 0;
margin-top: 300px;
}
@ -107,9 +109,6 @@ Profile Picture Container
`];
async firstUpdated() {
// this method is a lifecycle even in lit
// for more info check out the lit docs https://lit.dev/docs/components/lifecycle/
@ -119,29 +118,43 @@ Profile Picture Container
connectedCallback(): void {
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 {
super.disconnectedCallback();
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;
this.nostrAddy = detail.nostrAddy;
this.bio = detail.bio;
this.profilePic = detail.profilePic;
this.isSignedIn = true;
this.requestUpdate();
} */
handlesSignOut() {
this.profilePic = '';
this.bio = '';
this.nostrAddy = 'Guest';
this.isSignedIn = false;
this.requestUpdate();
}
displayGuestView() {
// Set initial values for guest view
this.nostrAddy = '';
@ -151,8 +164,6 @@ handlesSignOut() {
}
share() {
if ((navigator as any).share) {
(navigator as any).share({
@ -166,9 +177,6 @@ handlesSignOut() {
render() {
return html`
<app-header></app-header>
@ -188,9 +196,7 @@ handlesSignOut() {
</p>
<div class="profile-picture-container">
${this.profilePic
? 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">`}
<img class="profile-pic" src="${this.profilePic || '/assets/img/default_pfp.png'}" alt="Profile Picture" width="200px" height="200px">
<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 { resolveRouterPath } from '../router';
import { Relay } from 'nostr-tools';
import axios from 'axios';
import '@shoelace-style/shoelace/dist/components/card/card.js';
import '@shoelace-style/shoelace/dist/components/button/button.js';
@ -16,7 +17,6 @@ export class NoteWall extends LitElement {
static styles = [
styles,
css`
@ -144,6 +144,10 @@ export class NoteWall extends LitElement {
note = ''; // store notes
getUserLang() {
return navigator.language || 'en';
}
async firstUpdated() {
// this method is a lifecycle even in lit
// 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([
{
kinds: [1], // short form notes
limit: 20,
authors: [eric]
}
], {
onevent: (event) => {
async translateText(text: string, targetLang: string) {
try {
const response = await axios.post('https://api-free.deepl.com/v2/translate', {
text, target_lang: targetLang });
this.notes.push({
content: event.content,
date: new Date(event.created_at * 1000).toLocaleDateString()
});
this.requestUpdate();
console.log(event)
},
oneose: () => {
sub.close();
this.requestUpdate();
}
});
return response.data.translations[0].text; // Assuming DeepL's API structure
} catch (error) {
console.error('Error during translation:', error.response?.data || error.message);
return '';
}
}
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() {
@ -216,24 +241,41 @@ export class NoteWall extends LitElement {
<table>
${this.notes.map(note => {
// Check if there's a URL in the content
const urlMatch = note.content.match(/https?:\/\/[^\s]+/);
const imageUrl = urlMatch ? urlMatch[0] : null;
const textContent = note.content.replace(urlMatch?.[0] || '', '').trim();
<table>
${this.notes.map(note => {
// Extract URL from note content
const urlMatch = note.content.match(/https?:\/\/[^\s]+/);
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>

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/
export default defineConfig({
base: "/",
server: {
port: 5176,
},
build: {
sourcemap: true,
assetsDir: "code",