📚 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`