oop
This commit is contained in:
parent
a59b6a5cfd
commit
dcea70fc17
8 changed files with 1013 additions and 75 deletions
827
package-lock.json
generated
827
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
|
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
45
src/server.js
Normal 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');
|
||||
});
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue