Note style changes

This commit is contained in:
Kieran 2023-01-09 16:18:34 +00:00
parent fd7e00c8d4
commit ed4a0678e1
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
10 changed files with 79 additions and 33 deletions

View File

@ -10,7 +10,6 @@
"@reduxjs/toolkit": "^1.9.1",
"bech32": "^2.0.0",
"light-bolt11-decoder": "^2.1.0",
"moment": "^2.29.4",
"qr-code-styling": "^1.6.0-rc.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@ -2,14 +2,13 @@ import { Link } from "react-router-dom";
import Invoice from "./element/Invoice";
import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex, YoutubeUrlRegex } from "./Const";
import { eventLink, profileLink } from "./Util";
import { eventLink, hexToBech32, profileLink } from "./Util";
function transformHttpLink(a) {
try {
const url = new URL(a);
const vParam = url.searchParams.get('v')
const youtubeId = YoutubeUrlRegex.test(a) && RegExp.$1
const extension = FileExtensionRegex.test(url.pathname.toLowerCase()) && RegExp.$1
const youtubeId = YoutubeUrlRegex.test(a) && RegExp.$1;
const extension = FileExtensionRegex.test(url.pathname.toLowerCase()) && RegExp.$1;
if (extension) {
switch (extension) {
case "gif":
@ -28,7 +27,7 @@ function transformHttpLink(a) {
return <video key={url} src={url} controls />
}
default:
return <a key={url} href={url}>{url.toString()}</a>
return <a key={url} href={url} onClick={(e) => e.stopPropagation()}>{url.toString()}</a>
}
} else if (youtubeId) {
return (
@ -37,7 +36,7 @@ function transformHttpLink(a) {
<iframe
src={`https://www.youtube.com/embed/${youtubeId}`}
title="YouTube video player"
frameborder="0"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen=""
/>
@ -45,7 +44,7 @@ function transformHttpLink(a) {
</>
)
} else {
return <a key={url} href={url}>{url.toString()}</a>
return <a key={url} href={url} onClick={(e) => e.stopPropagation()}>{url.toString()}</a>
}
} catch (e) {
console.warn(`Not a valid url: ${a}`);
@ -77,12 +76,12 @@ export function extractMentions(fragments, tags, users) {
if (ref) {
switch (ref.Key) {
case "p": {
let pUser = users[ref.PubKey]?.name ?? ref.PubKey.substring(0, 8);
return <Link key={ref.PubKey} to={profileLink(ref.PubKey)} onClick={(ev) => ev.stopPropagation()}>@{pUser}</Link>;
let pUser = users[ref.PubKey]?.name ?? hexToBech32("npub", ref.PubKey).substring(0, 12);
return <Link key={ref.PubKey} to={profileLink(ref.PubKey)} onClick={(e) => e.stopPropagation()}>@{pUser}</Link>;
}
case "e": {
let eText = ref.Event.substring(0, 8);
return <Link key={ref.Event} to={eventLink(ref.Event)}>#{eText}</Link>;
let eText = hexToBech32("note", ref.Event).substring(0, 12);
return <Link key={ref.Event} to={eventLink(ref.Event)} onClick={(e) => e.stopPropagation()}>#{eText}</Link>;
}
}
}

View File

@ -50,8 +50,16 @@ export function bech32ToText(str) {
* @returns
*/
export function eventLink(hex) {
return `/e/${hexToBech32("note", hex)}`;
}
/**
* Convert hex to bech32
* @param {string} hex
*/
export function hexToBech32(hrp, hex) {
let buf = secp.utils.hexToBytes(hex);
return `/e/${bech32.encode("note", bech32.toWords(buf))}`;
return bech32.encode(hrp, bech32.toWords(buf));
}
/**
@ -60,8 +68,7 @@ export function eventLink(hex) {
* @returns
*/
export function profileLink(hex) {
let buf = secp.utils.hexToBytes(hex);
return `/p/${bech32.encode("npub", bech32.toWords(buf))}`;
return `/p/${hexToBech32("npub", hex)}`;
}
/**

View File

@ -1,7 +1,7 @@
import "./Invoice.css";
import { decode as invoiceDecode } from "light-bolt11-decoder";
import { useMemo } from "react";
import moment from "moment";
import NoteTime from "./NoteTime";
export default function Invoice(props) {
const invoice = props.invoice;
@ -48,7 +48,7 @@ export default function Invoice(props) {
<div className="note-invoice flex">
<div className="f-grow flex f-col">
{header()}
{info?.expire ? <small>{info?.expired ? "Expired" : "Expires"} {moment(info.expire * 1000).fromNow()}</small> : null}
{info?.expire ? <small>{info?.expired ? "Expired" : "Expires"} <NoteTime from={info.expire * 1000} /></small> : null}
</div>
{info?.expired ? <div className="btn">Expired</div> :

View File

@ -13,10 +13,13 @@
.note > .header .reply {
font-size: small;
color: #999;
}
.note > .header > .info {
font-size: small;
white-space: nowrap;
color: #999;
}
.note > .body {

View File

@ -1,14 +1,14 @@
import "./Note.css";
import { useCallback } from "react";
import { useSelector } from "react-redux";
import moment from "moment";
import { useNavigate } from "react-router-dom";
import Event from "../nostr/Event";
import ProfileImage from "./ProfileImage";
import { extractLinks, extractMentions, extractInvoices } from "../Text";
import { eventLink } from "../Util";
import { eventLink, hexToBech32 } from "../Util";
import NoteFooter from "./NoteFooter";
import NoteTime from "./NoteTime";
export default function Note(props) {
const navigate = useNavigate();
@ -57,11 +57,14 @@ export default function Note(props) {
return null;
}
const maxMentions = 2;
let replyId = ev.Thread?.ReplyTo?.Event;
let mentions = ev.Thread?.PubKeys?.map(a => [a, users[a]])?.map(a => a[1]?.name ?? a[0].substring(0, 8));
let mentions = ev.Thread?.PubKeys?.map(a => [a, users[a]])?.map(a => a[1]?.name ?? hexToBech32("npub", a[0]).substring(0, 12))
.sort((a, b) => a.startsWith("npub") ? 1 : -1);
let pubMentions = mentions.length > maxMentions ? `${mentions?.slice(0, maxMentions).join(", ")} & ${mentions.length - maxMentions} others` : mentions?.join(", ");
return (
<div className="reply" onClick={(e) => goToEvent(e, replyId)}>
{mentions?.join(", ") ?? replyId?.substring(0, 8)}
<div className="reply">
{pubMentions ?? hexToBech32("note", replyId).substring(0, 12)}
</div>
)
}
@ -84,7 +87,7 @@ export default function Note(props) {
<ProfileImage pubkey={ev.RootPubKey} subHeader={replyTag()} />
{options.showTime ?
<div className="info">
{moment(ev.CreatedAt * 1000).fromNow()}
<NoteTime from={ev.CreatedAt * 1000} />
</div> : null}
</div> : null}
<div className="body" onClick={(e) => goToEvent(e, ev.Id)}>

View File

@ -1,12 +1,12 @@
import "./NoteReaction.css";
import moment from "moment";
import EventKind from "../nostr/EventKind";
import Note from "./Note";
import ProfileImage from "./ProfileImage";
import Event from "../nostr/Event";
import { eventLink } from "../Util";
import { eventLink, hexToBech32 } from "../Util";
import { Link } from "react-router-dom";
import { useMemo } from "react";
import NoteTime from "./NoteTime";
export default function NoteReaction(props) {
const ev = props["data-ev"] || Event.FromObject(props.data);
@ -68,12 +68,12 @@ export default function NoteReaction(props) {
<div className="header flex">
<ProfileImage pubkey={ev.RootPubKey} subHeader={tagLine()} />
<div className="info">
{moment(ev.CreatedAt * 1000).fromNow()}
<NoteTime from={ev.CreatedAt * 1000} />
</div>
</div>
{root ? <Note data={root} options={opt} /> : null}
{!root && refEvent ? <p><Link to={eventLink(refEvent)}>#{refEvent.substring(0, 8)}</Link></p> : null}
{!root && refEvent ? <p><Link to={eventLink(refEvent)}>#{hexToBech32("note", refEvent).substring(0, 12)}</Link></p> : null}
</div>
);
}

40
src/element/NoteTime.js Normal file
View File

@ -0,0 +1,40 @@
import { useEffect, useState } from "react";
const MinuteInMs = 1_000 * 60;
const HourInMs = MinuteInMs * 60;
const DayInMs = HourInMs * 24;
export default function NoteTime(props) {
const from = props.from;
const [time, setTime] = useState("");
function calcTime() {
let fromDate = new Date(from);
let ago = (new Date().getTime()) - from;
let absAgo = Math.abs(ago);
if (absAgo > DayInMs) {
return fromDate.toLocaleDateString(undefined, { year: "2-digit", month: "short", day: "2-digit", weekday: "short" });
} else if (absAgo > HourInMs) {
return `${fromDate.getHours().toString().padStart(2, '0')}:${fromDate.getMinutes().toString().padStart(2, '0')}`;
} else {
let mins = parseInt(absAgo / MinuteInMs);
return `${mins} mins ago`;
}
}
useEffect(() => {
setTime(calcTime());
let t = setInterval(() => {
setTime(s => {
let newTime = calcTime();
if (newTime !== s) {
return newTime;
}
return s;
})
}, MinuteInMs);
return () => clearInterval(t);
}, [from]);
return <>{time}</>
}

View File

@ -6,7 +6,7 @@
.pfp > img {
width: 40px;
height: 40px;
margin-right: 20px;
margin-right: 10px;
border-radius: 10px;
cursor: pointer;
}

View File

@ -6037,11 +6037,6 @@ mkdirp@~0.5.1:
dependencies:
minimist "^1.2.6"
moment@^2.29.4:
version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"