📚 Enhance project setup & maintenance workflows

- 📝 Add comprehensive README.md, CONTRIBUTING.md, and 🔒 SECURITY.md documentation
- 🔧 Integrate code formatting tool and refactor existing codebase to meet style guidelines
- ⬆️ Update project dependencies to latest stable versions
- 🤖 Implement pre-commit hook for automated code formatting

#documentation #tooling #maintenance
This commit is contained in:
Danny Morabito 2025-03-10 17:14:09 +01:00
parent 25b3972f8b
commit 18e4ad7629
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
47 changed files with 1027 additions and 568 deletions

View file

@ -1,9 +1,9 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import "@components/AppIcon";
import '@components/AppIcon';
@customElement("arx-app-grid")
@customElement('arx-app-grid')
export class AppGrid extends LitElement {
@property()
apps: {
@ -59,7 +59,7 @@ export class AppGrid extends LitElement {
.href=${app.href}
.name=${app.name}
></arx-app-icon>
`
`,
)}
</div>
`;

View file

@ -1,21 +1,21 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-app-icon")
@customElement('arx-app-icon')
export class AppIcon extends LitElement {
@property()
icon: string | undefined;
@property()
color = "#ff9900";
color = '#ff9900';
@property()
href = "#";
href = '#';
@property()
name = "App";
name = 'App';
static override styles = [
css`
@ -83,15 +83,17 @@ export class AppIcon extends LitElement {
return html`
<arx-eve-link href="${this.href}" class="app-icon">
<div class="icon" style="background-color: ${this.color}">
${this.icon
? html`<iconify-icon
${
this.icon
? html`<iconify-icon
icon="${this.icon}"
class="app-icon"
width="48"
height="48"
color="white"
></iconify-icon>`
: ""}
: ''
}
</div>
<span class="app-name">${this.name}</span>
</arx-eve-link>

View file

@ -1,5 +1,5 @@
import { html, css, LitElement } from 'lit';
import { property, customElement } from 'lit/decorators.js';
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import './BreadcrumbsItem';
@customElement('arx-breadcrumbs')

View file

@ -1,11 +1,11 @@
import { html, css, LitElement } from "lit";
import { property, customElement } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-breadcrumbs-item")
@customElement('arx-breadcrumbs-item')
export class BreadcrumbsItem extends LitElement {
@property() text = "";
@property() text = '';
@property() href?: string;
@property() index = 0;
@ -37,14 +37,14 @@ export class BreadcrumbsItem extends LitElement {
override render() {
return html`
<li>
${this.index > 0
? html`<span class="separator" aria-hidden="true">/</span>`
: ""}
${this.href
? html`<arx-eve-link class="link" href=${this.href}
${this.index > 0 ? html`<span class="separator" aria-hidden="true">/</span>` : ''}
${
this.href
? html`<arx-eve-link class="link" href=${this.href}
>${this.text}</arx-eve-link
>`
: html`<span class="secondary">${this.text}</span>`}
: html`<span class="secondary">${this.text}</span>`
}
</li>
`;
}

View file

@ -1,4 +1,4 @@
import { html, LitElement, css } from 'lit';
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('arx-error-view')

View file

@ -1,18 +1,18 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement("arx-eve-link")
@customElement('arx-eve-link')
export class EveLink extends LitElement {
@property({ type: String }) href = "#";
@property({ type: String }) target = "";
@property({ type: String }) rel = "";
@property({ type: String }) href = '#';
@property({ type: String }) target = '';
@property({ type: String }) rel = '';
get hrefValue() {
let href = this.href;
if (href.startsWith("javascript:")) return href;
if (href.startsWith("eve://")) href = href.replace("eve://", "#");
if (href.startsWith("/")) href = href.replace("/", "#");
if (!href.startsWith("#")) href = `#${href}`;
if (href.startsWith('javascript:')) return href;
if (href.startsWith('eve://')) href = href.replace('eve://', '#');
if (href.startsWith('/')) href = href.replace('/', '#');
if (!href.startsWith('#')) href = `#${href}`;
return href;
}

View file

@ -1,16 +1,16 @@
import { html, css, LitElement } from "lit";
import { property, customElement } from "lit/decorators.js";
import formatDateTime from "@utils/formatDateTime";
import formatDateTime from '@utils/formatDateTime';
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import "@components/MarkdownContent";
import '@components/MarkdownContent';
@customElement("arx-forum-post")
@customElement('arx-forum-post')
export class ForumPost extends LitElement {
@property({ type: String }) override id = "";
@property({ type: String }) topicId = "";
@property({ type: String }) npub = "";
@property({ type: String }) override id = '';
@property({ type: String }) topicId = '';
@property({ type: String }) npub = '';
@property({ type: Date }) date = new Date();
@property({ type: String }) content = "";
@property({ type: String }) content = '';
static override styles = [
css`
@ -82,13 +82,13 @@ export class ForumPost extends LitElement {
</div>
<div>
<arx-markdown-content
.content=${this.content || "no content"}
.content=${this.content || 'no content'}
></arx-markdown-content>
</div>
<div>
<arx-phora-button
href="#"
@click=${() => alert("TODO")}
@click=${() => alert('TODO')}
class="disabled"
>
<iconify-icon size="32" icon="mdi:reply"></iconify-icon>
@ -100,7 +100,7 @@ export class ForumPost extends LitElement {
</arx-phora-button>
<arx-phora-button
href="#"
@click=${() => alert("TODO")}
@click=${() => alert('TODO')}
class="disabled"
>
<iconify-icon size="32" icon="bxs:zap"></iconify-icon>
@ -108,7 +108,7 @@ export class ForumPost extends LitElement {
</arx-phora-button>
<arx-phora-button
href="#"
@click=${() => alert("TODO")}
@click=${() => alert('TODO')}
class="disabled"
>
<iconify-icon

View file

@ -1,15 +1,15 @@
import { html, css, LitElement } from "lit";
import { property, customElement } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement("arx-header")
@customElement('arx-header')
export class Header extends LitElement {
@property({ type: String }) override title = "Eve";
@property({ type: String }) url = "eve://home";
@property({ type: String }) override title = 'Eve';
@property({ type: String }) url = 'eve://home';
@property({ type: Boolean }) canGoBack = false;
@property({ type: Boolean }) canGoForward = false;
private searchQuery = "";
private searchQuery = '';
private searchInput: HTMLInputElement | null = null;
static override styles = [
@ -95,15 +95,15 @@ export class Header extends LitElement {
<header>
<div class="nav-buttons">
<button
class=${this.canGoBack ? "" : "disabled"}
@click=${() => this.dispatchEvent(new CustomEvent("go-back"))}
class=${this.canGoBack ? '' : 'disabled'}
@click=${() => this.dispatchEvent(new CustomEvent('go-back'))}
aria-label="Go back"
>
<iconify-icon icon="material-symbols:arrow-back"></iconify-icon>
</button>
<button
class=${this.canGoForward ? "" : "disabled"}
@click=${() => this.dispatchEvent(new CustomEvent("go-forward"))}
class=${this.canGoForward ? '' : 'disabled'}
@click=${() => this.dispatchEvent(new CustomEvent('go-forward'))}
aria-label="Go forward"
>
<iconify-icon icon="material-symbols:arrow-forward"></iconify-icon>
@ -126,8 +126,8 @@ export class Header extends LitElement {
}
private handleSearch(e: KeyboardEvent) {
if (e.key !== "Enter") return;
const hash = (e.target as HTMLInputElement).value.replace("eve://", "#");
if (e.key !== 'Enter') return;
const hash = (e.target as HTMLInputElement).value.replace('eve://', '#');
window.location.hash = hash;
}
}

View file

@ -1,29 +1,29 @@
import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import { animate } from "@lit-labs/motion";
import * as nostrTools from "@nostr/tools/pure";
import * as nip06 from "@nostr/tools/nip06";
import * as nip19 from "@nostr/tools/nip19";
import * as nip49 from "@nostr/tools/nip49";
import { ndk, setSigner } from "@/ndk";
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { encodeBase64 } from "@std/encoding/base64";
import { randomBytes } from "@noble/ciphers/webcrypto";
import { ndk, setSigner } from '@/ndk';
import { animate } from '@lit-labs/motion';
import { randomBytes } from '@noble/ciphers/webcrypto';
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import * as nip06 from '@nostr/tools/nip06';
import * as nip19 from '@nostr/tools/nip19';
import * as nip49 from '@nostr/tools/nip49';
import * as nostrTools from '@nostr/tools/pure';
import { encodeBase64 } from '@std/encoding/base64';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
@customElement("arx-initial-setup")
@customElement('arx-initial-setup')
export class InitialSetup extends LitElement {
@state() private currentPage = 1;
@state() private isAnimating = false;
@state() private seedPhrase = "";
@state() private userName = "";
@state() private profileImage = "";
@state() private lightningAddress = "";
@state() private seedPhrase = '';
@state() private userName = '';
@state() private profileImage = '';
@state() private lightningAddress = '';
get encryptionPassphrase() {
let encryptionPassphrase = localStorage.getItem("encryption_key");
let encryptionPassphrase = localStorage.getItem('encryption_key');
if (!encryptionPassphrase) {
encryptionPassphrase = encodeBase64(randomBytes(32));
localStorage.setItem("encryption_key", encryptionPassphrase);
localStorage.setItem('encryption_key', encryptionPassphrase);
}
return encryptionPassphrase;
}
@ -285,7 +285,9 @@ export class InitialSetup extends LitElement {
if (this.isAnimating) return;
this.isAnimating = true;
this.currentPage = page;
setTimeout(() => (this.isAnimating = false), 300);
setTimeout(() => {
this.isAnimating = false;
}, 300);
}
private onSeedPhraseInput(event: Event) {
@ -297,9 +299,9 @@ export class InitialSetup extends LitElement {
}
private isValidSeedPhrase() {
const words = this.seedPhrase.split(" ");
const words = this.seedPhrase.split(' ');
if (words.length !== 12) return false;
if (!nip06.validateWords(words.join(" "))) return false;
if (!nip06.validateWords(words.join(' '))) return false;
return true;
}
@ -574,15 +576,12 @@ export class InitialSetup extends LitElement {
private async goToFinalStep() {
const randomPrivateKey = nostrTools.generateSecretKey();
const encryptedNsec = nip49.encrypt(
randomPrivateKey,
this.encryptionPassphrase
);
const encryptedNsec = nip49.encrypt(randomPrivateKey, this.encryptionPassphrase);
const npub = nip19.npubEncode(nostrTools.getPublicKey(randomPrivateKey));
if (!this.lightningAddress) this.lightningAddress = `${npub}@npub.cash`;
localStorage.setItem("ncryptsec", encryptedNsec);
localStorage.setItem('ncryptsec', encryptedNsec);
setSigner(new NDKPrivateKeySigner(randomPrivateKey));
await ndk.connect(5000);
@ -615,11 +614,13 @@ export class InitialSetup extends LitElement {
required to invite new members to the community.
</p>
<p>Your username is: <b>${this.userName}</b></p>
${this.profileImage
? html` <p>
${
this.profileImage
? html` <p>
Your profile image is: <img .src=${this.profileImage} />
</p>`
: ""}
: ''
}
<p>Your lightning address is: <b>${this.lightningAddress}</b></p>
</section>
@ -638,7 +639,7 @@ export class InitialSetup extends LitElement {
finish() {
this.dispatchEvent(
new CustomEvent("finish", {
new CustomEvent('finish', {
detail: {
userName: this.userName,
profileImage: this.profileImage,
@ -646,7 +647,7 @@ export class InitialSetup extends LitElement {
},
bubbles: true,
composed: true,
})
}),
);
}

View file

@ -1,4 +1,4 @@
import { html, LitElement } from 'lit';
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
@customElement('arx-loading-view')

View file

@ -1,18 +1,18 @@
import { css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import MarkdownIt, { type StateCore, type Token } from "markdown-it";
import { LitElement, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import MarkdownIt, { type StateCore, type Token } from 'markdown-it';
function nostrPlugin(md: MarkdownIt): void {
const npubRegex = /(npub[0-9a-zA-Z]{59})/g;
md.core.ruler.after("inline", "nostr_npub", (state: StateCore): boolean => {
md.core.ruler.after('inline', 'nostr_npub', (state: StateCore): boolean => {
for (const token of state.tokens) {
if (token.type === "inline" && token.children) {
if (token.type === 'inline' && token.children) {
for (let i = 0; i < token.children.length; i++) {
const child = token.children[i];
if (child.type === "text") {
if (child.type === 'text') {
const matches = child.content.match(npubRegex);
if (!matches) continue;
@ -24,26 +24,26 @@ function nostrPlugin(md: MarkdownIt): void {
// @ts-ignore this is an issue with the types
(match: string, npub: string, offset: number) => {
if (offset > lastIndex) {
const textToken = new state.Token("text", "", 0);
const textToken = new state.Token('text', '', 0);
textToken.content = child.content.slice(lastIndex, offset);
newTokens.push(textToken);
}
const linkOpen = new state.Token("link_open", "a", 1);
linkOpen.attrs = [["href", `nostr:${npub}`]];
const linkOpen = new state.Token('link_open', 'a', 1);
linkOpen.attrs = [['href', `nostr:${npub}`]];
const text = new state.Token("text", "", 0);
const text = new state.Token('text', '', 0);
text.content = npub;
const linkClose = new state.Token("link_close", "a", -1);
const linkClose = new state.Token('link_close', 'a', -1);
newTokens.push(linkOpen, text, linkClose);
lastIndex = offset + match.length;
}
},
);
if (lastIndex < child.content.length) {
const textToken = new state.Token("text", "", 0);
const textToken = new state.Token('text', '', 0);
textToken.content = child.content.slice(lastIndex);
newTokens.push(textToken);
}
@ -59,7 +59,7 @@ function nostrPlugin(md: MarkdownIt): void {
});
}
@customElement("arx-markdown-content")
@customElement('arx-markdown-content')
export class MarkdownContent extends LitElement {
private md = new MarkdownIt({
html: false,
@ -69,7 +69,7 @@ export class MarkdownContent extends LitElement {
});
@property({ type: String })
content = "";
content = '';
static override styles = [
css`

View file

@ -1,5 +1,5 @@
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { css, html, LitElement } from 'lit-element';
import { LitElement, css, html } from 'lit-element';
import { customElement, property } from 'lit/decorators.js';
type AvatarSize = 'short' | 'medium' | 'large' | 'huge';

View file

@ -1,6 +1,6 @@
import { html, css, LitElement } from 'lit';
import { property, customElement, state } from 'lit/decorators.js';
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { getUserProfile } from '../ndk';
import '@components/profiles/ShortProfile';
import '@components/profiles/MediumProfile';
@ -11,11 +11,7 @@ import '@components/profiles/CardProfile';
export class NostrProfile extends LitElement {
@property() npub = '';
@property({ reflect: true }) renderType:
| 'short'
| 'medium'
| 'large'
| 'card' = 'short';
@property({ reflect: true }) renderType: 'short' | 'medium' | 'large' | 'card' = 'short';
@state()
private profile: NDKUserProfile | undefined = undefined;

View file

@ -1,17 +1,17 @@
import { html, css, LitElement } from "lit";
import { property, customElement } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-phora-button")
@customElement('arx-phora-button')
export class PhoraButton extends LitElement {
@property({ type: String }) href = "";
@property({ type: String }) target = "";
@property({ type: String }) rel = "";
@property({ type: String }) href = '';
@property({ type: String }) target = '';
@property({ type: String }) rel = '';
@property({ type: Boolean }) disabled = false;
get hrefValue() {
if (!this.href || this.disabled) return "javascript:void(0)";
if (!this.href || this.disabled) return 'javascript:void(0)';
return this.href;
}
@ -66,7 +66,7 @@ export class PhoraButton extends LitElement {
.href=${this.hrefValue}
.target=${this.target}
.rel=${this.rel}
class="${this.disabled ? "disabled" : ""}"
class="${this.disabled ? 'disabled' : ''}"
>
<slot></slot>
</arx-eve-link>

View file

@ -1,10 +1,10 @@
import { html, css, LitElement } from "lit";
import { property, customElement } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement("arx-phora-forum-category")
@customElement('arx-phora-forum-category')
export class PhoraForumCategory extends LitElement {
@property({ type: String }) override title = "";
@property({ type: String }) override id = "";
@property({ type: String }) override title = '';
@property({ type: String }) override id = '';
static override styles = [
css`

View file

@ -1,9 +1,9 @@
import { html, css, LitElement } from "lit";
import { property, customElement } from "lit/decorators.js";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-phora-forum-topic")
@customElement('arx-phora-forum-topic')
export class PhoraForumTopic extends LitElement {
static override styles = [
css`
@ -165,19 +165,19 @@ export class PhoraForumTopic extends LitElement {
`,
];
@property({ type: String }) override id = "";
@property({ type: String }) override title = "";
@property({ type: String }) description = "";
@property({ type: String }) override id = '';
@property({ type: String }) override title = '';
@property({ type: String }) description = '';
@property({ type: Number }) posts = 0;
@property({ type: String }) lastPostBy = "";
@property({ type: String }) lastPostTime = "";
@property({ type: String }) lastPostBy = '';
@property({ type: String }) lastPostTime = '';
@property({ type: Boolean }) isNew = false;
@property({ type: Boolean }) isHot = false;
get status() {
if (this.isNew) return "new-status";
if (this.isHot) return "hot-status";
return "";
if (this.isNew) return 'new-status';
if (this.isHot) return 'hot-status';
return '';
}
override render() {

View file

@ -1,8 +1,8 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getLastBlockHeight } from "@utils/lastBlockHeight";
import { getLastBlockHeight } from '@utils/lastBlockHeight';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
@customElement("arx-bitcoin-block-widget")
@customElement('arx-bitcoin-block-widget')
export class BitcoinBlockWidget extends LitElement {
@state()
private lastBlock: number | null = null;
@ -21,10 +21,7 @@ export class BitcoinBlockWidget extends LitElement {
constructor() {
super();
this.loadBlockHeight();
this.timer = window.setInterval(
this.loadBlockHeight,
this.REFRESH_INTERVAL
);
this.timer = window.setInterval(this.loadBlockHeight, this.REFRESH_INTERVAL);
}
override disconnectedCallback() {
@ -38,7 +35,7 @@ export class BitcoinBlockWidget extends LitElement {
this.lastBlock = response.height;
this.error = null;
} catch (error) {
this.error = "Failed to load block height";
this.error = 'Failed to load block height';
console.error(error);
} finally {
this.isLoading = false;

View file

@ -1,7 +1,7 @@
import { html, css, LitElement } from 'lit';
import { property, customElement, state } from 'lit/decorators.js';
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { getProfile } from '@utils/profileUtils';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement('arx-nostr-card-profile')
export class CardProfile extends LitElement {
@ -86,7 +86,7 @@ export class CardProfile extends LitElement {
.join('\n')
.trim()
.split(/-+\n/, 2)
.filter(section => section.trim() !== '')
.filter((section) => section.trim() !== '')
.join('\n')
.trim();

View file

@ -1,8 +1,8 @@
import { html, css, LitElement } from 'lit';
import { property, customElement, state } from 'lit/decorators.js';
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { getProfile } from '@utils/profileUtils';
import firstLine from '@utils/firstLine';
import { getProfile } from '@utils/profileUtils';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement('arx-nostr-large-profile')
export class LargeProfile extends LitElement {

View file

@ -1,24 +1,24 @@
import { html, css, LitElement } from "lit";
import { property, customElement, state } from "lit/decorators.js";
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import { getProfile } from "@utils/profileUtils";
import firstLine from "@utils/firstLine";
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import firstLine from '@utils/firstLine';
import { getProfile } from '@utils/profileUtils';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-nostr-medium-profile")
@customElement('arx-nostr-medium-profile')
export class MediumProfile extends LitElement {
@property() profile!: NDKUserProfile;
@property() npub = "";
@property() npub = '';
@state()
private displayName = "";
private displayName = '';
@state()
private profileUrl = "";
private profileUrl = '';
@state()
private truncatedAbout = "";
private truncatedAbout = '';
static override styles = [
css`

View file

@ -1,20 +1,20 @@
import { html, css, LitElement } from "lit";
import { property, customElement, state } from "lit/decorators.js";
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import { getProfile } from "@utils/profileUtils";
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { getProfile } from '@utils/profileUtils';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-nostr-short-profile")
@customElement('arx-nostr-short-profile')
export class ShortProfile extends LitElement {
@property() profile!: NDKUserProfile;
@property() npub = "";
@property() npub = '';
@state()
private displayName = "";
private displayName = '';
@state()
private profileUrl = "";
private profileUrl = '';
static override styles = [
css`

View file

@ -1,38 +1,38 @@
import { app, shell, BrowserWindow, ipcMain } from "electron";
import { optimizer, is } from "@electron-toolkit/utils";
import { RelayManager } from "./relayManager";
import path from "node:path";
import fs from "node:fs";
import os from "node:os";
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { is, optimizer } from '@electron-toolkit/utils';
import { BrowserWindow, app, ipcMain, shell } from 'electron';
import { RelayManager } from './relayManager';
const relay = new RelayManager();
ipcMain.handle("relay:writeSeed", async (_, ...args: any) => {
if (!args[0]) throw new Error("No seed provided");
ipcMain.handle('relay:writeSeed', async (_, ...args: string[]) => {
if (!args[0]) throw new Error('No seed provided');
const seed = args[0] as string;
let configPath: string;
if (process.platform === "darwin") {
configPath = path.join(app.getPath("userData"), "arx", "Eve");
if (process.platform === 'darwin') {
configPath = path.join(app.getPath('userData'), 'arx', 'Eve');
} else {
configPath = path.join(os.homedir(), ".config", "arx", "Eve");
configPath = path.join(os.homedir(), '.config', 'arx', 'Eve');
}
const seedPath = path.join(configPath, "ccn.seed");
const seedPath = path.join(configPath, 'ccn.seed');
fs.mkdirSync(configPath, { recursive: true });
fs.writeFileSync(seedPath, seed);
});
ipcMain.handle("relay:start", (_, ...args: any) => {
if (!args[0]) throw new Error("No encryption key provided");
const encryptionKey = args[0] as string;
ipcMain.handle('relay:start', (_, ...args: string[]) => {
if (!args[0]) throw new Error('No encryption key provided');
const encryptionKey = args[0];
return relay.start(encryptionKey);
});
ipcMain.handle("relay:stop", () => {
ipcMain.handle('relay:stop', () => {
return relay.stop();
});
ipcMain.handle("relay:status", () => {
ipcMain.handle('relay:status', () => {
return {
running: relay.isRunning,
pid: relay.pid,
@ -40,7 +40,7 @@ ipcMain.handle("relay:status", () => {
};
});
ipcMain.handle("relay:getLogs", () => {
ipcMain.handle('relay:getLogs', () => {
return relay.getLogs();
});
@ -51,33 +51,32 @@ function createWindow(): void {
show: false,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "../preload/preload.mjs"),
preload: path.join(__dirname, '../preload/preload.mjs'),
sandbox: false,
},
});
mainWindow.on("ready-to-show", () => {
mainWindow.on('ready-to-show', () => {
mainWindow.show();
});
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
return { action: "deny" };
return { action: 'deny' };
});
if (is.dev && process.env["ELECTRON_RENDERER_URL"])
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
else mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
if (is.dev && process.env['ELECTRON_RENDERER_URL']) mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']);
else mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
app.whenReady().then(() => {
app.on("browser-window-created", (_, window) => {
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window);
});
createWindow();
});
app.on("window-all-closed", () => {
app.on('window-all-closed', () => {
app.quit();
});

View file

@ -1,16 +1,15 @@
import { contextBridge, ipcRenderer } from "electron";
import { electronAPI } from "@electron-toolkit/preload";
import { electronAPI } from '@electron-toolkit/preload';
import { contextBridge, ipcRenderer } from 'electron';
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld("electron", electronAPI);
contextBridge.exposeInMainWorld("relay", {
writeSeed: (seed: string) => ipcRenderer.invoke("relay:writeSeed", seed),
start: (encryptionKey: string) =>
ipcRenderer.invoke("relay:start", encryptionKey),
stop: () => ipcRenderer.invoke("relay:stop"),
getStatus: () => ipcRenderer.invoke("relay:status"),
getLogs: () => ipcRenderer.invoke("relay:logs"),
contextBridge.exposeInMainWorld('electron', electronAPI);
contextBridge.exposeInMainWorld('relay', {
writeSeed: (seed: string) => ipcRenderer.invoke('relay:writeSeed', seed),
start: (encryptionKey: string) => ipcRenderer.invoke('relay:start', encryptionKey),
stop: () => ipcRenderer.invoke('relay:stop'),
getStatus: () => ipcRenderer.invoke('relay:status'),
getLogs: () => ipcRenderer.invoke('relay:logs'),
});
} catch (error) {
console.error(error);

View file

@ -1,8 +1,8 @@
import { spawn, ChildProcess } from "child_process";
import { join } from "path";
import { is } from "@electron-toolkit/utils";
import { type ChildProcess, spawn } from 'node:child_process';
import { join } from 'node:path';
import { is } from '@electron-toolkit/utils';
type PackageEnvironment = "flatpak" | "appimage" | "system" | "mac" | "dev";
type PackageEnvironment = 'flatpak' | 'appimage' | 'system' | 'mac' | 'dev';
export class RelayManager {
private process: ChildProcess | null;
@ -49,27 +49,27 @@ export class RelayManager {
}
private detectEnvironment(): PackageEnvironment {
if (is.dev) return "dev";
if (process.platform === "darwin") return "mac";
if (process.env.FLATPAK_ID) return "flatpak";
if (process.env.APPIMAGE) return "appimage";
return "system";
if (is.dev) return 'dev';
if (process.platform === 'darwin') return 'mac';
if (process.env.FLATPAK_ID) return 'flatpak';
if (process.env.APPIMAGE) return 'appimage';
return 'system';
}
private getRelayPath(): string {
const environment = this.detectEnvironment();
switch (environment) {
case "dev":
return join(__dirname, "../../extras/linux/relay");
case "mac":
return join(process.resourcesPath, "macos", "eve-relay");
case "flatpak":
return "/app/lib/com.arx_ccn.eve/usr/bin/eve-relay";
case "appimage":
return join(process.env.APPDIR || "", "usr/bin/eve-relay");
case "system":
return "/usr/bin/eve-relay";
case 'dev':
return join(__dirname, '../../extras/linux/relay');
case 'mac':
return join(process.resourcesPath, 'macos', 'eve-relay');
case 'flatpak':
return '/app/lib/com.arx_ccn.eve/usr/bin/eve-relay';
case 'appimage':
return join(process.env.APPDIR || '', 'usr/bin/eve-relay');
case 'system':
return '/usr/bin/eve-relay';
}
}
@ -82,16 +82,12 @@ export class RelayManager {
private restartProcess(): void {
if (this.restartAttempts >= this.maxRestartAttempts) {
console.error(
`Failed to restart relay after ${this.maxRestartAttempts} attempts`
);
console.error(`Failed to restart relay after ${this.maxRestartAttempts} attempts`);
return;
}
this.restartAttempts++;
console.log(
`Attempting restart #${this.restartAttempts} in ${this.restartDelay}ms...`
);
console.log(`Attempting restart #${this.restartAttempts} in ${this.restartDelay}ms...`);
if (this.restartTimeout) clearTimeout(this.restartTimeout);
@ -137,7 +133,7 @@ export class RelayManager {
});
if (this.process.stdout) {
this.process.stdout.on("data", (data: Buffer) => {
this.process.stdout.on('data', (data: Buffer) => {
const logLine = data.toString().trim();
this.addLog(logLine);
console.log(logLine);
@ -145,30 +141,26 @@ export class RelayManager {
}
if (this.process.stderr) {
this.process.stderr.on("data", (data: Buffer) => {
this.process.stderr.on('data', (data: Buffer) => {
const logLine = data.toString().trim();
this.addLog(logLine);
console.error(logLine);
});
}
this.process.on("error", (err: Error) => {
this.process.on('error', (err: Error) => {
console.error(`Failed to start Relay: ${err.message}`);
this.process = null;
this.restartProcess();
});
this.process.on("exit", this.handleProcessExit.bind(this));
this.process.on('exit', this.handleProcessExit.bind(this));
if (this.process.pid) {
this.restartAttempts = 0;
}
} catch (error) {
console.error(
`Error starting Relay: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
console.error(`Error starting Relay: ${error instanceof Error ? error.message : 'Unknown error'}`);
this.restartProcess();
}
}

View file

@ -1,28 +1,28 @@
import "./style.scss";
import './style.scss';
import "@components/ErrorView";
import "@components/NostrAvatar";
import "@components/LoadingView";
import "@components/NostrProfile";
import "@components/Breadcrumbs";
import "@components/Header";
import "@routes/router";
import "@components/LoadingView";
import type EveRouter from "@routes/router";
import { sleep } from "./utils/sleep";
import '@components/ErrorView';
import '@components/NostrAvatar';
import '@components/LoadingView';
import '@components/NostrProfile';
import '@components/Breadcrumbs';
import '@components/Header';
import '@routes/router';
import '@components/LoadingView';
import type EveRouter from '@routes/router';
import { sleep } from './utils/sleep';
async function startRelay() {
if (localStorage.getItem("ncryptsec")) {
const loadingIndicator = document.createElement("arx-loading-view");
if (localStorage.getItem('ncryptsec')) {
const loadingIndicator = document.createElement('arx-loading-view');
document.body.appendChild(loadingIndicator);
await window.relay.start(localStorage.getItem("encryption_key")!);
await window.relay.start(localStorage.getItem('encryption_key')!);
await sleep(5000);
loadingIndicator.remove();
}
}
startRelay().then(() => {
const router = document.createElement("arx-eve-router") as EveRouter;
router.ccnSetup = !!localStorage.getItem("ncryptsec");
const router = document.createElement('arx-eve-router') as EveRouter;
router.ccnSetup = !!localStorage.getItem('ncryptsec');
document.body.appendChild(router);
});

View file

@ -1,22 +1,20 @@
import NDK, { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import * as nip49 from "@nostr/tools/nip49";
import NDK, { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import * as nip49 from '@nostr/tools/nip49';
export const ndk = new NDK({
explicitRelayUrls: ["ws://localhost:6942"],
explicitRelayUrls: ['ws://localhost:6942'],
enableOutboxModel: false,
autoConnectUserRelays: false,
clientName: "Arx",
clientNip89: "arx",
clientName: 'Arx',
clientNip89: 'arx',
});
export async function getSigner() {
await ndk.connect();
if (ndk.signer) return;
const encryptionPassphrase = localStorage.getItem("encryption_key");
if (!encryptionPassphrase) throw new Error("Encryption passphrase not found");
const signer = new NDKPrivateKeySigner(
nip49.decrypt(localStorage.getItem("ncryptsec")!, encryptionPassphrase)
);
const encryptionPassphrase = localStorage.getItem('encryption_key');
if (!encryptionPassphrase) throw new Error('Encryption passphrase not found');
const signer = new NDKPrivateKeySigner(nip49.decrypt(localStorage.getItem('ncryptsec')!, encryptionPassphrase));
setSigner(signer);
}
@ -24,7 +22,7 @@ export async function getNpub() {
await getSigner();
const user = await ndk.signer?.user();
if (user) return user.npub;
throw new Error("Could not get npub");
throw new Error('Could not get npub');
}
export function setSigner(signer: NDKPrivateKeySigner) {
@ -33,7 +31,7 @@ export function setSigner(signer: NDKPrivateKeySigner) {
export async function getUserProfile(npub: string) {
await ndk.connect();
const query = npub.startsWith("npub") ? { npub } : { pubkey: npub };
const query = npub.startsWith('npub') ? { npub } : { pubkey: npub };
const user = ndk.getUser(query);
await user.fetchProfile();
return user.profile;

View file

@ -1,16 +1,16 @@
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import type { RouteParams } from "./router";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import type { RouteParams } from './router';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-404-page")
@customElement('arx-404-page')
export class FourOhFourPage extends LitElement {
@property({ type: Object })
params: RouteParams = {};
@property({ type: String })
path = "";
path = '';
@property({ type: Boolean })
canGoBack = false;
@ -177,10 +177,10 @@ export class FourOhFourPage extends LitElement {
href="javascript:void(0)"
@click="${() =>
this.dispatchEvent(
new CustomEvent("go-back", {
new CustomEvent('go-back', {
bubbles: true,
composed: true,
})
}),
)}"
class="primary-button"
>

View file

@ -1,12 +1,12 @@
import { getNpub, getUserProfile } from "@/ndk";
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import { css, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { html, literal } from "lit/static-html.js";
import "@widgets/BitcoinBlockWidget";
import "@components/AppGrid";
import { getNpub, getUserProfile } from '@/ndk';
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { LitElement, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { html, literal } from 'lit/static-html.js';
import '@widgets/BitcoinBlockWidget';
import '@components/AppGrid';
@customElement("arx-eve-home")
@customElement('arx-eve-home')
export class Home extends LitElement {
@state()
private npub: string | undefined;
@ -18,72 +18,72 @@ export class Home extends LitElement {
apps = [
{
id: 0,
href: "letters",
name: "Letters",
color: "#FF33BB",
icon: "bxs:envelope",
href: 'letters',
name: 'Letters',
color: '#FF33BB',
icon: 'bxs:envelope',
},
{
id: 1,
href: "messages",
name: "Messages",
color: "#34C759",
icon: "bxs:chat",
href: 'messages',
name: 'Messages',
color: '#34C759',
icon: 'bxs:chat',
},
{
id: 2,
href: "calendar",
name: "Calendar",
color: "#FF9500",
icon: "bxs:calendar",
href: 'calendar',
name: 'Calendar',
color: '#FF9500',
icon: 'bxs:calendar',
},
{
id: 3,
href: "phora",
name: "Phora",
color: "#FF3B30",
icon: "bxs:conversation",
href: 'phora',
name: 'Phora',
color: '#FF3B30',
icon: 'bxs:conversation',
},
{
id: 5,
href: "agora",
name: "Agora",
color: "#5856D6",
icon: "bxs:store",
href: 'agora',
name: 'Agora',
color: '#5856D6',
icon: 'bxs:store',
},
{
id: 6,
href: "wallet",
name: "Wallet",
color: "#007AFF",
icon: "bxs:wallet",
href: 'wallet',
name: 'Wallet',
color: '#007AFF',
icon: 'bxs:wallet',
},
{
id: 7,
href: "consortium",
name: "Consortium",
color: "#FFCC00",
icon: "bxs:landmark",
href: 'consortium',
name: 'Consortium',
color: '#FFCC00',
icon: 'bxs:landmark',
},
{
id: 8,
href: "settings",
name: "Settings",
color: "#deadbeef",
icon: "bxs:wrench",
href: 'settings',
name: 'Settings',
color: '#deadbeef',
icon: 'bxs:wrench',
},
];
widgets = [
{
title: "Bitcoin Block",
title: 'Bitcoin Block',
content: literal`arx-bitcoin-block-widget`,
},
];
async loadProperties() {
const npub = await getNpub();
if (!npub) return alert("No npub?");
if (!npub) return alert('No npub?');
this.npub = npub;
this.profile = (await getUserProfile(this.npub)) as NDKUserProfile;
this.username = this.profile?.name || this.npub.substring(0, 8);
@ -242,7 +242,7 @@ export class Home extends LitElement {
<h3>${widget.title}</h3>
<${widget.content}></${widget.content}>
</div>
`
`,
)}
</div>
</div>

View file

@ -1,13 +1,13 @@
import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import formatDateTime from "@utils/formatDateTime";
import type { NDKSubscription } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import type { NDKSubscription } from '@nostr-dev-kit/ndk';
import formatDateTime from '@utils/formatDateTime';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import "@components/Breadcrumbs";
import "@components/PhoraForumCategory";
import "@components/PhoraForumTopic";
import "@components/PhoraButton";
import '@components/Breadcrumbs';
import '@components/PhoraForumCategory';
import '@components/PhoraForumTopic';
import '@components/PhoraButton';
interface ForumTopic {
id: string;
@ -24,7 +24,7 @@ interface ForumCategory {
topics: ForumTopic[];
}
@customElement("arx-phora-home")
@customElement('arx-phora-home')
export class PhoraForum extends LitElement {
@state()
private categories: ForumCategory[] = [];
@ -53,18 +53,14 @@ export class PhoraForum extends LitElement {
.subscribe({
kinds: [11],
})
.on("event", (event) => {
const subject = event.tags.find(
(tag: string[]) => tag[0] === "subject"
);
const parent = event.tags.find((tag: string[]) => tag[0] === "e");
.on('event', (event) => {
const subject = event.tags.find((tag: string[]) => tag[0] === 'subject');
const parent = event.tags.find((tag: string[]) => tag[0] === 'e');
if (!subject) return;
if (parent) {
const categoryIndex = this.categories.findIndex(
(category) => category.id === parent[1]
);
const categoryIndex = this.categories.findIndex((category) => category.id === parent[1]);
if (categoryIndex === -1) return;
const updatedCategories = [...this.categories];
@ -95,7 +91,7 @@ export class PhoraForum extends LitElement {
override render() {
return html`
<arx-breadcrumbs
.items=${[{ text: "Home", href: "/" }, { text: "Phora" }]}
.items=${[{ text: 'Home', href: '/' }, { text: 'Phora' }]}
>
</arx-breadcrumbs>
@ -116,10 +112,10 @@ export class PhoraForum extends LitElement {
lastPostTime=${topic.created_at}
>
</arx-phora-forum-topic>
`
`,
)}
</arx-phora-forum-category>
`
`,
)}
`;
}

View file

@ -1,15 +1,15 @@
import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
@customElement("arx-phora-category-creator")
@customElement('arx-phora-category-creator')
export class PhoraCategoryCreator extends LitElement {
@state()
private newCategory = "";
private newCategory = '';
@state()
private categoryDescription = "";
private categoryDescription = '';
static override styles = css`
:host {
@ -33,12 +33,12 @@ export class PhoraCategoryCreator extends LitElement {
private async doCreateCategory() {
if (this.newCategory.length < 3) {
alert("Category name must be at least 3 characters long");
alert('Category name must be at least 3 characters long');
return;
}
if (this.categoryDescription.length < 10) {
alert("Category description must be at least 10 characters long");
alert('Category description must be at least 10 characters long');
return;
}
@ -46,23 +46,23 @@ export class PhoraCategoryCreator extends LitElement {
await getSigner();
const event = new NDKEvent(ndk);
event.kind = 11;
event.tags = [["subject", this.newCategory]];
event.tags = [['subject', this.newCategory]];
event.content = this.categoryDescription;
await event.sign();
await event.publish();
this.dispatchEvent(
new CustomEvent("category-created", {
new CustomEvent('category-created', {
bubbles: true,
composed: true,
})
}),
);
this.newCategory = "";
this.categoryDescription = "";
this.newCategory = '';
this.categoryDescription = '';
} catch (error) {
console.error("Failed to create category:", error);
alert("Failed to create category");
console.error('Failed to create category:', error);
alert('Failed to create category');
}
}

View file

@ -1,15 +1,15 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement("arx-phora-post-creator")
@customElement('arx-phora-post-creator')
export class PhoraPostCreator extends LitElement {
@property({ type: String })
topicId = "";
topicId = '';
@state()
private postContent = "";
private postContent = '';
@state()
private isCreating = false;
@ -71,7 +71,7 @@ export class PhoraPostCreator extends LitElement {
if (this.isCreating) return;
if (this.postContent.length < 10) {
this.error = "Post content must be at least 10 characters long";
this.error = 'Post content must be at least 10 characters long';
return;
}
@ -82,27 +82,27 @@ export class PhoraPostCreator extends LitElement {
await getSigner();
const event = new NDKEvent(ndk);
event.kind = 1111;
event.tags = [["e", this.topicId]];
event.tags = [['e', this.topicId]];
event.content = this.postContent;
await event.sign();
await event.publish();
this.dispatchEvent(
new CustomEvent("post-created", {
new CustomEvent('post-created', {
bubbles: true,
composed: true,
detail: {
postId: event.id,
topicId: this.topicId,
},
})
}),
);
// Reset form
this.postContent = "";
this.postContent = '';
} catch (error) {
console.error("Failed to create post:", error);
this.error = "Failed to create post. Please try again.";
console.error('Failed to create post:', error);
this.error = 'Failed to create post. Please try again.';
} finally {
this.isCreating = false;
}
@ -131,7 +131,7 @@ export class PhoraPostCreator extends LitElement {
<div class="actions">
<arx-phora-button
@click=${() => this.dispatchEvent(new CustomEvent("cancel"))}
@click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
?disabled=${this.isCreating}
>
Cancel
@ -141,7 +141,7 @@ export class PhoraPostCreator extends LitElement {
@click=${this.doCreatePost}
?disabled=${this.isCreating}
>
${this.isCreating ? "Creating..." : "Create"}
${this.isCreating ? 'Creating...' : 'Create'}
</arx-phora-button>
</div>
</div>

View file

@ -1,18 +1,18 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement("arx-phora-topic-creator")
@customElement('arx-phora-topic-creator')
export class PhoraTopicCreator extends LitElement {
@property({ type: String })
categoryId = "";
categoryId = '';
@state()
private newTopic = "";
private newTopic = '';
@state()
private topicContent = "";
private topicContent = '';
@state()
private isCreating = false;
@ -52,12 +52,12 @@ export class PhoraTopicCreator extends LitElement {
if (this.isCreating) return;
if (this.newTopic.length < 3) {
alert("Topic title must be at least 3 characters long");
alert('Topic title must be at least 3 characters long');
return;
}
if (this.topicContent.length < 10) {
alert("Topic content must be at least 10 characters long");
alert('Topic content must be at least 10 characters long');
return;
}
@ -68,29 +68,29 @@ export class PhoraTopicCreator extends LitElement {
const event = new NDKEvent(ndk);
event.kind = 11;
event.tags = [
["subject", this.newTopic],
["e", this.categoryId],
['subject', this.newTopic],
['e', this.categoryId],
];
event.content = this.topicContent;
await event.sign();
await event.publish();
this.dispatchEvent(
new CustomEvent("topic-created", {
new CustomEvent('topic-created', {
bubbles: true,
composed: true,
detail: {
topicId: event.id,
categoryId: this.categoryId,
},
})
}),
);
this.newTopic = "";
this.topicContent = "";
this.newTopic = '';
this.topicContent = '';
} catch (error) {
console.error("Failed to create topic:", error);
alert("Failed to create topic");
console.error('Failed to create topic:', error);
alert('Failed to create topic');
} finally {
this.isCreating = false;
}
@ -125,7 +125,7 @@ export class PhoraTopicCreator extends LitElement {
<div class="button-group">
<arx-phora-button
@click=${() => this.dispatchEvent(new CustomEvent("cancel"))}
@click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
?disabled=${this.isCreating}
>
Cancel
@ -135,7 +135,7 @@ export class PhoraTopicCreator extends LitElement {
@click=${this.doCreateTopic}
?disabled=${this.isCreating}
>
${this.isCreating ? "Creating..." : "Create"}
${this.isCreating ? 'Creating...' : 'Create'}
</arx-phora-button>
</div>
`;

View file

@ -1,11 +1,11 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import type { NDKSubscription } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import type { NDKSubscription } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import "@components/Breadcrumbs";
import "@components/ForumPost";
import "@components/PhoraButton";
import '@components/Breadcrumbs';
import '@components/ForumPost';
import '@components/PhoraButton';
interface ForumPost {
id: string;
@ -14,13 +14,13 @@ interface ForumPost {
content: string;
}
@customElement("arx-phora-topic-view")
@customElement('arx-phora-topic-view')
export class PhoraTopicView extends LitElement {
@property({ type: String })
topicId = "";
topicId = '';
@state()
override title = "";
override title = '';
@state()
private posts: ForumPost[] = [];
@ -82,10 +82,10 @@ export class PhoraTopicView extends LitElement {
const event = await ndk.fetchEvent(this.topicId);
if (!event) {
throw new Error("Could not load topic");
throw new Error('Could not load topic');
}
this.title = event.tags.find((tag) => tag[0] === "subject")?.[1] || "";
this.title = event.tags.find((tag) => tag[0] === 'subject')?.[1] || '';
this.posts = [
{
@ -100,9 +100,9 @@ export class PhoraTopicView extends LitElement {
this.subscription = ndk
.subscribe({
kinds: [1111],
"#e": [this.topicId],
'#e': [this.topicId],
})
.on("event", (event) => {
.on('event', (event) => {
this.posts = [
...this.posts,
{
@ -114,17 +114,13 @@ export class PhoraTopicView extends LitElement {
];
});
} catch (error) {
console.error("Failed to load topic:", error);
alert("Could not load topic");
console.error('Failed to load topic:', error);
alert('Could not load topic');
}
}
override render() {
const breadcrumbItems = [
{ text: "Home", href: "/" },
{ text: "Phora", href: "/phora" },
{ text: this.title },
];
const breadcrumbItems = [{ text: 'Home', href: '/' }, { text: 'Phora', href: '/phora' }, { text: this.title }];
return html`
<arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs>
@ -144,7 +140,7 @@ export class PhoraTopicView extends LitElement {
.date=${post.date}
.content=${post.content}
></arx-forum-post>
`
`,
)}
</div>

View file

@ -1,13 +1,13 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { when } from "lit/directives/when.js";
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import { getUserProfile } from "../ndk";
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import { getUserProfile } from '../ndk';
@customElement("arx-profile-route")
@customElement('arx-profile-route')
export class NostrProfile extends LitElement {
@property({ type: String })
npub = "";
npub = '';
@state()
private profile: NDKUserProfile | undefined;
@ -282,16 +282,14 @@ export class NostrProfile extends LitElement {
try {
this.profile = await getUserProfile(this.npub);
} catch (err) {
this.error = "Failed to load profile";
this.error = 'Failed to load profile';
console.error(err);
}
}
private get displayName() {
if (!this.profile) return this.npub;
return (
this.profile.displayName || this.profile.name || this.npub.substring(0, 8)
);
return this.profile.displayName || this.profile.name || this.npub.substring(0, 8);
}
override render() {
@ -313,13 +311,11 @@ export class NostrProfile extends LitElement {
</div>
<div class="banner-overlay"></div>
</div>
`
`,
)}
<div
class=${this.profile.banner
? "profile-container with-banner"
: "profile-container no-banner"}
class=${this.profile.banner ? 'profile-container with-banner' : 'profile-container no-banner'}
>
<div class="profile-card">
<div class="profile-content">
@ -339,7 +335,7 @@ export class NostrProfile extends LitElement {
class="placeholder-icon"
></svg-icon>
</div>
`
`,
)}
</div>
@ -354,7 +350,7 @@ export class NostrProfile extends LitElement {
<span class="verified-icon">
<svg-icon icon="mdi:check-decagram"></svg-icon>
</span>
`
`,
)}
</h1>
${when(
@ -364,15 +360,12 @@ export class NostrProfile extends LitElement {
<svg-icon icon="mdi:at"></svg-icon>
${this.profile!.nip05}
</p>
`
`,
)}
</div>
</div>
${when(
this.profile.about,
() => html` <p class="bio">${this.profile!.about}</p> `
)}
${when(this.profile.about, () => html` <p class="bio">${this.profile!.about}</p> `)}
</div>
</div>
@ -388,7 +381,7 @@ export class NostrProfile extends LitElement {
<svg-icon icon="mdi:web" class="link-icon website"></svg-icon>
<span>${this.profile!.website}</span>
</a>
`
`,
)}
${when(
this.profile.lud16,
@ -400,7 +393,7 @@ export class NostrProfile extends LitElement {
></svg-icon>
<span>${this.profile!.lud16}</span>
</a>
`
`,
)}
</div>
</div>

View file

@ -1,17 +1,17 @@
import "@routes/404Page";
import "@routes/Home";
import "@routes/Profile";
import "@routes/Phora/Home";
import "@routes/Phora/NewCategory";
import "@routes/Phora/NewTopic";
import "@routes/Phora/TopicView";
import "@routes/Phora/NewPost";
import "@components/InitialSetup";
import '@routes/404Page';
import '@routes/Home';
import '@routes/Profile';
import '@routes/Phora/Home';
import '@routes/Phora/NewCategory';
import '@routes/Phora/NewTopic';
import '@routes/Phora/TopicView';
import '@routes/Phora/NewPost';
import '@components/InitialSetup';
import { css, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { html, literal, type StaticValue } from "lit/static-html.js";
import { spread } from "@open-wc/lit-helpers";
import { spread } from '@open-wc/lit-helpers';
import { LitElement, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { type StaticValue, html, literal } from 'lit/static-html.js';
export interface RouteParams {
[key: string]: string;
@ -26,46 +26,46 @@ interface Route {
meta?: Record<string, string>;
}
@customElement("arx-eve-router")
@customElement('arx-eve-router')
export default class EveRouter extends LitElement {
private static routes: Route[] = [
{
pattern: "home",
pattern: 'home',
params: {},
component: literal`arx-eve-home`,
},
{
pattern: "profile/:npub",
pattern: 'profile/:npub',
params: {},
component: literal`arx-profile-route`,
},
{
pattern: "phora",
pattern: 'phora',
params: {},
component: literal`arx-phora-home`,
},
{
pattern: "phora/new-category",
pattern: 'phora/new-category',
params: {},
component: literal`arx-phora-category-creator`,
},
{
pattern: "phora/new-topic/:categoryId",
pattern: 'phora/new-topic/:categoryId',
params: {},
component: literal`arx-phora-topic-creator`,
},
{
pattern: "phora/topics/:topicId",
pattern: 'phora/topics/:topicId',
params: {},
component: literal`arx-phora-topic-view`,
},
{
pattern: "phora/new-post/:topicId",
pattern: 'phora/new-post/:topicId',
params: {},
component: literal`arx-phora-post-creator`,
},
{
pattern: "404",
pattern: '404',
params: {},
component: literal`arx-404-page`,
},
@ -116,8 +116,7 @@ export default class EveRouter extends LitElement {
constructor() {
super();
this.initializeRouter();
if (this.ccnSetup)
window.relay.start(localStorage.getItem("encryption_key")!);
if (this.ccnSetup) window.relay.start(localStorage.getItem('encryption_key')!);
}
override connectedCallback(): void {
@ -127,8 +126,8 @@ export default class EveRouter extends LitElement {
override disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("hashchange", this.handleHashChange);
window.removeEventListener("popstate", this.handlePopState);
window.removeEventListener('hashchange', this.handleHashChange);
window.removeEventListener('popstate', this.handlePopState);
}
private initializeRouter(): void {
@ -139,8 +138,8 @@ export default class EveRouter extends LitElement {
}
private setupEventListeners(): void {
window.addEventListener("hashchange", this.handleHashChange.bind(this));
window.addEventListener("popstate", this.handlePopState.bind(this));
window.addEventListener('hashchange', this.handleHashChange.bind(this));
window.addEventListener('popstate', this.handlePopState.bind(this));
}
private handleHashChange(): void {
@ -161,12 +160,9 @@ export default class EveRouter extends LitElement {
this.currentIndex = this.history.length - 1;
}
private matchRoute(
pattern: string,
path: string
): { isMatch: boolean; params: RouteParams } {
const patternParts = pattern.split("/").filter(Boolean);
const pathParts = path.split("/").filter(Boolean);
private matchRoute(pattern: string, path: string): { isMatch: boolean; params: RouteParams } {
const patternParts = pattern.split('/').filter(Boolean);
const pathParts = path.split('/').filter(Boolean);
const params: RouteParams = {};
if (patternParts.length !== pathParts.length) {
@ -175,7 +171,7 @@ export default class EveRouter extends LitElement {
const isMatch = patternParts.every((patternPart, index) => {
const pathPart = pathParts[index];
if (patternPart.startsWith(":")) {
if (patternPart.startsWith(':')) {
const paramName = patternPart.slice(1);
params[paramName] = decodeURIComponent(pathPart);
return true;
@ -188,15 +184,12 @@ export default class EveRouter extends LitElement {
get currentPath(): string {
const hash = window.location.hash?.slice(1);
return hash === "" ? "home" : hash;
return hash === '' ? 'home' : hash;
}
get currentRoute(): Route {
const route = EveRouter.routes.find((route) => {
const { isMatch, params } = this.matchRoute(
route.pattern,
this.currentPath
);
const { isMatch, params } = this.matchRoute(route.pattern, this.currentPath);
if (isMatch) {
route.params = params;
return true;
@ -208,7 +201,7 @@ export default class EveRouter extends LitElement {
private getNotFoundRoute(): Route {
return {
pattern: "404",
pattern: '404',
params: {},
component: literal`arx-404-page`,
};

View file

@ -1,4 +1,4 @@
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
interface ProfileProps {
profile: NDKUserProfile;

View file

@ -14,7 +14,7 @@ function formatSatsGroup(sats: number): string {
.join('')
.match(/.{1,3}/g)
?.reverse()
.map(group => group.split('').reverse().join(''))
.map((group) => group.split('').reverse().join(''))
.join(' ') || '0'
);
}
@ -29,10 +29,7 @@ function formatBtcGroup(sats: number): string {
?.join(' ')} BTC`.trim();
}
export default function satsComma(
sats: number,
format: SatcommaFormat = SatcommaFormat.SATS,
): string {
export default function satsComma(sats: number, format: SatcommaFormat = SatcommaFormat.SATS): string {
if (!Number.isFinite(sats)) {
throw new Error('Invalid input: sats must be a finite number');
}
@ -41,7 +38,5 @@ export default function satsComma(
return `-${satsComma(Math.abs(sats), format)}`;
}
return format === SatcommaFormat.SATS
? formatSatsGroup(sats)
: formatBtcGroup(sats);
return format === SatcommaFormat.SATS ? formatSatsGroup(sats) : formatBtcGroup(sats);
}