fix: build
continuous-integration/drone/push Build encountered an error Details

This commit is contained in:
kieran 2024-04-22 15:24:25 +01:00
parent 80a4b5d8e6
commit bfdcbca08b
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
20 changed files with 135 additions and 128 deletions

37
packages/app/.eslintrc.js Normal file
View File

@ -0,0 +1,37 @@
/* eslint-disable import/no-anonymous-default-export */
module.exports = {
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react/recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "formatjs", "react-refresh", "simple-import-sort"],
rules: {
"formatjs/enforce-id": [
"error",
{
idInterpolationPattern: "[sha512:contenthash:base64:6]",
},
],
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "off",
"react-refresh/only-export-components": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"@typescript-eslint/no-unused-vars": "error",
"max-lines": ["warn", { max: 300, skipBlankLines: true, skipComments: true }],
},
overrides: [
{
files: ["*.tsx"],
rules: {
"max-lines": ["warn", { max: 200, skipBlankLines: true, skipComments: true }],
},
},
],
root: true,
ignorePatterns: ["build/", "*.test.ts", "*.js"],
env: {
browser: true,
worker: true,
commonjs: true,
node: false,
},
};

View File

@ -1,44 +0,0 @@
/* eslint-disable import/no-anonymous-default-export */
export default [
{
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "formatjs", "react-refresh", "simple-import-sort"],
rules: {
"formatjs/enforce-id": [
"error",
{
idInterpolationPattern: "[sha512:contenthash:base64:6]",
},
],
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "off",
"react-refresh/only-export-components": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"@typescript-eslint/no-unused-vars": "error",
"max-lines": ["warn", { max: 300, skipBlankLines: true, skipComments: true }],
},
overrides: [
{
files: ["*.tsx"],
rules: {
"max-lines": ["warn", { max: 200, skipBlankLines: true, skipComments: true }],
},
},
],
root: true,
ignores: ["build/", "*.test.ts", "*.js"],
env: {
browser: true,
worker: true,
commonjs: true,
node: false,
},
},
];

View File

@ -1,4 +1,4 @@
import { EventKind, HexKey, NostrLink, NostrPrefix } from "@snort/system";
import { EventKind, NostrEvent, NostrLink } from "@snort/system";
import { Menu, MenuItem } from "@szhsin/react-menu";
import { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
@ -90,16 +90,12 @@ export function NoteContextMenu({ ev, ...props }: NoteContextMenuProps) {
await navigator.clipboard.writeText(link);
}
async function pin(id: HexKey) {
if (publisher) {
//todo: PIN note
}
async function pin(ev: NostrEvent) {
await login.state.addToList(EventKind.PinList, NostrLink.fromEvent(ev), true);
}
async function bookmark(id: string) {
if (publisher) {
//todo: bookmark note
}
async function bookmark(ev: NostrEvent) {
await login.state.addToList(EventKind.BookmarksList, NostrLink.fromEvent(ev), true);
}
async function copyEvent() {
@ -129,13 +125,13 @@ export function NoteContextMenu({ ev, ...props }: NoteContextMenuProps) {
<FormattedMessage {...messages.Share} />
</MenuItem>
{!login.state.isOnList(EventKind.PinList, link) && !login.readonly && (
<MenuItem onClick={() => pin(ev.id)}>
<MenuItem onClick={() => pin(ev)}>
<Icon name="pin" />
<FormattedMessage {...messages.Pin} />
</MenuItem>
)}
{!login.state.isOnList(EventKind.BookmarksList, link) && !login.readonly && (
<MenuItem onClick={() => bookmark(ev.id)}>
<MenuItem onClick={() => bookmark(ev)}>
<Icon name="bookmark" />
<FormattedMessage {...messages.Bookmark} />
</MenuItem>

View File

@ -1,5 +1,7 @@
import "./RootTabs.css";
import { unwrap } from "@snort/shared";
import { EventKind } from "@snort/system";
import { Menu, MenuItem } from "@szhsin/react-menu";
import { useEffect, useMemo, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
@ -9,8 +11,6 @@ import Icon from "@/Components/Icons/Icon";
import useLogin from "@/Hooks/useLogin";
import usePreferences from "@/Hooks/usePreferences";
import { RootTabRoutePath } from "@/Pages/Root/RootTabRoutes";
import { EventKind } from "@snort/system";
import { unwrap } from "@snort/shared";
export function RootTabs({ base = "/" }: { base: string }) {
const navigate = useNavigate();

View File

@ -255,7 +255,7 @@ class IrisAccount extends Component<Props> {
}
async declineReserved() {
if (!confirm(`Are you sure you want to decline iris.to/${name}?`)) {
if (!window.confirm(`Are you sure you want to decline iris.to/${this.state.newUserName}?`)) {
return;
}
const login = LoginStore.snapshot();

View File

@ -1,36 +1,36 @@
import { useEffect, useRef, useState } from "react";
function useHistoryState(initialValue, key) {
const currentHistoryState = history.state ? history.state[key] : undefined;
function useHistoryState<T>(initialValue: T, key: string) {
const currentHistoryState = globalThis.history.state ? globalThis.history.state[key] : undefined;
const myInitialValue = currentHistoryState === undefined ? initialValue : currentHistoryState;
const [state, setState] = useState(myInitialValue);
const latestValue = useRef(state);
const setHistoryState = value => {
const newHistoryState = { ...history.state, [key]: value };
history.replaceState(newHistoryState, "");
const setHistoryState = (value: T) => {
const newHistoryState = { ...globalThis.history.state, [key]: value };
globalThis.history.replaceState(newHistoryState, "");
latestValue.current = value;
};
useEffect(() => {
if (state !== latestValue.current) {
setHistoryState(state);
const newHistoryState = { ...history.state, [key]: state };
history.replaceState(newHistoryState, "");
const newHistoryState = { ...globalThis.history.state, [key]: state };
globalThis.history.replaceState(newHistoryState, "");
latestValue.current = state;
}
// Cleanup logic
return () => {
if (state !== latestValue.current) {
const newHistoryState = { ...history.state, [key]: state };
history.replaceState(newHistoryState, ""); // Save the final state
const newHistoryState = { ...globalThis.history.state, [key]: state };
globalThis.history.replaceState(newHistoryState, ""); // Save the final state
}
};
}, [state, key]);
const popStateListener = event => {
const popStateListener = (event: PopStateEvent) => {
if (event.state && key in event.state) {
setState(event.state[key]);
}

View File

@ -1,7 +1,15 @@
import { EventKind, NostrEvent, NostrLink, TaggedNostrEvent } from "@snort/system";
import { dedupe } from "@snort/shared";
import { EventKind, NostrEvent, NostrLink, TaggedNostrEvent, ToNostrEventTag } from "@snort/system";
import useLogin from "@/Hooks/useLogin";
import { dedupe } from "@snort/shared";
export class MutedWordTag implements ToNostrEventTag {
constructor(readonly word: string) {}
toEventTag(): string[] | undefined {
return ["word", this.word.toLowerCase()];
}
}
export default function useModeration() {
const state = useLogin(s => s.state);
@ -30,13 +38,33 @@ export default function useModeration() {
}
function isMutedWord(word: string) {
return false;
const words = getMutedWords();
return words.includes(word);
}
async function addMutedWord(word: string) {
await state.addToList(EventKind.MuteList, new MutedWordTag(word.toLowerCase()));
}
async function removeMutedWord(word: string) {
await state.removeFromList(EventKind.MuteList, new MutedWordTag(word.toLowerCase()));
}
function isEventMuted(ev: TaggedNostrEvent | NostrEvent) {
return isMuted(ev.pubkey) || false;
}
function getMutedWords() {
return state
.getList(EventKind.MuteList, o => {
if (o[0] === "word") {
return new MutedWordTag(o[1]);
}
})
.filter(a => a instanceof MutedWordTag)
.map(a => (a as MutedWordTag).word);
}
return {
muteList: state.muted,
mute,
@ -45,5 +73,8 @@ export default function useModeration() {
isMuted,
isMutedWord,
isEventMuted,
addMutedWord,
removeMutedWord,
getMutedWords,
};
}

View File

@ -11,7 +11,6 @@ export function ZapPoolDonateSection() {
if (!CONFIG.features.zapPool) {
return;
}
// eslint-disable-next-line react-hooks/rules-of-hooks
const zapPool = useSyncExternalStore(
c => unwrap(ZapPoolController).hook(c),
() => unwrap(ZapPoolController).snapshot(),

View File

@ -9,9 +9,9 @@ import { Link, useParams } from "react-router-dom";
import AsyncButton from "@/Components/Button/AsyncButton";
import Timeline from "@/Components/Feed/Timeline";
import ProfileImage from "@/Components/User/ProfileImage";
import { TimelineSubject } from "@/Feed/TimelineFeed";
import useLogin from "@/Hooks/useLogin";
import { formatShort } from "@/Utils/Number";
import { TimelineSubject } from "@/Feed/TimelineFeed";
const HashTagsPage = () => {
const params = useParams();

View File

@ -1,3 +1,4 @@
import { unwrap } from "@snort/shared";
import { EventKind, NostrLink, NostrPrefix, parseNostrLink } from "@snort/system";
import { useEventFeed } from "@snort/system-react";
import classNames from "classnames";
@ -13,7 +14,6 @@ import useLogin from "@/Hooks/useLogin";
import { LogoHeader } from "@/Pages/Layout/LogoHeader";
import NotificationsHeader from "@/Pages/Layout/NotificationsHeader";
import { bech32ToHex } from "@/Utils";
import { unwrap } from "@snort/shared";
export function Header() {
const navigate = useNavigate();

View File

@ -9,7 +9,7 @@ import useLogin from "@/Hooks/useLogin";
export default function RightColumn() {
const { pubkey } = useLogin(s => ({ pubkey: s.publicKey }));
const hideRightColumnPaths = ["/login", "/new", "/messages"];
const show = !hideRightColumnPaths.some(path => location.pathname.startsWith(path));
const show = !hideRightColumnPaths.some(path => globalThis.location.pathname.startsWith(path));
const getTitleMessage = () => {
return pubkey ? (

View File

@ -1,4 +1,4 @@
import { EventKind, NostrLink, TaggedNostrEvent, Nip10 } from "@snort/system";
import { EventKind, Nip10, NostrLink, TaggedNostrEvent } from "@snort/system";
export function getNotificationContext(ev: TaggedNostrEvent) {
switch (ev.kind) {

View File

@ -1,10 +1,10 @@
import { unwrap } from "@snort/shared";
import { EventKind } from "@snort/system";
import { useMemo } from "react";
import Timeline from "@/Components/Feed/Timeline";
import useLogin from "@/Hooks/useLogin";
import { EventKind } from "@snort/system";
import { unwrap } from "@snort/shared";
import { TimelineSubject } from "@/Feed/TimelineFeed";
import useLogin from "@/Hooks/useLogin";
export function TopicsPage() {
const { tags, pubKey } = useLogin(s => ({

View File

@ -1,39 +1,15 @@
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import useEventPublisher from "@/Hooks/useEventPublisher";
import useLogin from "@/Hooks/useLogin";
import { appendDedupe } from "@/Utils";
import { SnortAppData, updateAppData } from "@/Utils/Login";
import AsyncButton from "@/Components/Button/AsyncButton";
import useModeration from "@/Hooks/useModeration";
import { useAllPreferences } from "@/Hooks/usePreferences";
export default function ModerationSettingsPage() {
const state = useAllPreferences();
const { addMutedWord, removeMutedWord, getMutedWords } = useModeration();
const preferences = useAllPreferences();
const [muteWord, setMuteWord] = useState("");
function addMutedWord() {
updateAppData(login.id, ad => ({
...ad,
mutedWords: appendDedupe(appData.mutedWords, [muteWord]),
}));
setMuteWord("");
}
const handleToggle = (setting: keyof SnortAppData) => {
updateAppData(login.id, ad => ({
...ad,
[setting]: !appData[setting],
}));
};
function removeMutedWord(word: string) {
updateAppData(login.id, ad => ({
...ad,
mutedWords: appData.mutedWords.filter(a => a !== word),
}));
setMuteWord("");
}
return (
<>
<h2>
@ -44,8 +20,13 @@ export default function ModerationSettingsPage() {
<div className="flex items-center mb-2">
<input
type="checkbox"
checked={appData.showContentWarningPosts}
onChange={() => handleToggle("showContentWarningPosts")}
checked={preferences.preferences.showContentWarningPosts}
onChange={() =>
preferences.update({
...preferences.preferences,
showContentWarningPosts: !preferences.preferences.showContentWarningPosts,
})
}
className="mr-2"
id="showContentWarningPosts"
/>
@ -67,11 +48,11 @@ export default function ModerationSettingsPage() {
value={muteWord}
onChange={e => setMuteWord(e.target.value.toLowerCase())}
/>
<button type="button" onClick={addMutedWord}>
<AsyncButton type="button" onClick={() => addMutedWord(muteWord)}>
<FormattedMessage defaultMessage="Add" id="2/2yg+" />
</button>
</AsyncButton>
</div>
{appData.mutedWords.map(v => (
{getMutedWords().map(v => (
<div key={v} className="p br b flex items-center justify-between">
<div>{v}</div>
<button type="button" onClick={() => removeMutedWord(v)}>

View File

@ -100,7 +100,7 @@ export async function subscribeToNotifications(publisher: EventPublisher) {
endpoint: sub.endpoint,
p256dh: base64.encode(new Uint8Array(unwrap(sub.getKey("p256dh")))),
auth: base64.encode(new Uint8Array(unwrap(sub.getKey("auth")))),
scope: `${location.protocol}//${location.hostname}`,
scope: `${globalThis.location.protocol}//${globalThis.location.hostname}`,
});
}
}

View File

@ -134,12 +134,12 @@ class ZapPool extends ExternalStore<Array<ZapPoolRecipient>> {
}
#save() {
self.localStorage.setItem("zap-pool", JSON.stringify(this.takeSnapshot()));
self.localStorage.setItem("zap-pool-last-payout", this.#lastPayout.toString());
globalThis.localStorage.setItem("zap-pool", JSON.stringify(this.takeSnapshot()));
globalThis.localStorage.setItem("zap-pool-last-payout", this.#lastPayout.toString());
}
#load() {
const existing = self.localStorage.getItem("zap-pool");
const existing = globalThis.localStorage.getItem("zap-pool");
if (existing) {
const arr = JSON.parse(existing) as Array<ZapPoolRecipient>;
this.#store = new Map(arr.map(a => [`${a.pubkey}-${a.type}`, a]));
@ -157,7 +157,7 @@ class ZapPool extends ExternalStore<Array<ZapPoolRecipient>> {
]);
}
const lastPayout = self.localStorage.getItem("zap-pool-last-payout");
const lastPayout = globalThis.localStorage.getItem("zap-pool-last-payout");
if (lastPayout) {
this.#lastPayout = Number(lastPayout);
}

View File

@ -41,6 +41,7 @@ import { storeRefCode, unwrap } from "@/Utils";
import { hasWasm, wasmInit, WasmPath } from "@/Utils/wasm";
import { Wallets } from "@/Wallet";
import { setupWebLNWalletConfig } from "@/Wallet";
import { LoginStore } from "./Utils/Login";
async function initSite() {

View File

@ -1,7 +1,6 @@
/// <reference lib="webworker" />
import { CacheableResponsePlugin } from "workbox-cacheable-response";
import { encodeTLVEntries, NostrLink, NostrPrefix, TLVEntryType, tryParseNostrLink } from "@snort/system";
import { CacheableResponsePlugin } from "workbox-cacheable-response";
import { clientsClaim } from "workbox-core";
import { ExpirationPlugin } from "workbox-expiration";
import { precacheAndRoute, PrecacheEntry } from "workbox-precaching";
@ -181,7 +180,7 @@ self.addEventListener("notificationclick", event => {
if (mention.event) {
return `/${new NostrLink(NostrPrefix.Note, mention.event).encode()}`;
}
} else if (ev.type == PushType.DirectMessage) {
} else if (ev.type === PushType.DirectMessage) {
const reaction = ev.data as CompactReaction;
return `/messages/${encodeTLVEntries("chat4" as NostrPrefix, {
type: TLVEntryType.Author,

View File

@ -24,6 +24,13 @@ export class NostrHashtagLink implements ToNostrEventTag {
}
}
export class UnknownTag implements ToNostrEventTag {
constructor(readonly value: Array<string>) {}
toEventTag(): string[] | undefined {
return this.value;
}
}
export class NostrLink implements ToNostrEventTag {
constructor(
readonly type: NostrPrefix,
@ -188,7 +195,7 @@ export class NostrLink implements ToNostrEventTag {
tag: Array<string>,
author?: string,
kind?: number,
fnOther?: (tag: Array<string>) => T,
fnOther?: (tag: Array<string>) => T | undefined,
) {
const relays = tag.length > 2 ? [tag[2]] : undefined;
switch (tag[0]) {
@ -203,15 +210,12 @@ export class NostrLink implements ToNostrEventTag {
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, tag[3]);
}
default: {
if (fnOther) {
return fnOther(tag);
}
return fnOther?.(tag) ?? new UnknownTag(tag);
}
}
throw new Error(`Unknown tag kind ${tag[0]}`);
}
static fromTags<T = NostrLink>(tags: ReadonlyArray<Array<string>>, fnOther?: (tag: Array<string>) => T) {
static fromTags<T = NostrLink>(tags: ReadonlyArray<Array<string>>, fnOther?: (tag: Array<string>) => T | undefined) {
return removeUndefined(
tags.map(a => {
try {

View File

@ -396,9 +396,12 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
return false;
}
getList(kind: EventKind): Array<ToNostrEventTag> {
getList<T extends ToNostrEventTag>(
kind: EventKind,
fnOther?: (tag: Array<string>) => T | undefined,
): Array<ToNostrEventTag> {
const list = this.#standardLists.get(kind);
return NostrLink.fromTags(list?.tags ?? []);
return NostrLink.fromTags<T>(list?.tags ?? [], fnOther);
}
serialize(): UserStateObject<TAppData> {