mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-10-01 17:31:13 +00:00
Add lnbc to DMs
This commit is contained in:
parent
8d2124a56b
commit
f4b3cf831e
@ -150,6 +150,170 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.noBack {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lnbcAlter {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 158px;
|
||||||
|
background-color: none;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.paymentOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--background-site);
|
||||||
|
opacity: 0.6;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerActions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
.qrIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-mask: url(../../assets/icons/qr_code.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/qr_code.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
.copyIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-mask: url(../../assets/icons/copy_border.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/copy_border.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.qrIcon, .copyIcon {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyDone {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.checkIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: var(--success-bright);
|
||||||
|
-webkit-mask: url(../../assets/icons/check.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/check.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
height: 36px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.expiryDate {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
.expiredDate {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payAction {
|
||||||
|
height: 36px;
|
||||||
|
min-width: 120px;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--accent);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.noBack {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lnIcon {
|
.lnIcon {
|
||||||
|
@ -22,7 +22,7 @@ import { useIntl } from '@cookbook/solid-intl';
|
|||||||
import { lnInvoice } from '../../translations';
|
import { lnInvoice } from '../../translations';
|
||||||
|
|
||||||
|
|
||||||
const Lnbc: Component< { id?: string, lnbc: string } > = (props) => {
|
const Lnbc: Component< { id?: string, lnbc: string, alternative?: boolean, noBack?: boolean } > = (props) => {
|
||||||
|
|
||||||
const app = useAppContext();
|
const app = useAppContext();
|
||||||
const toast = useToastContext();
|
const toast = useToastContext();
|
||||||
@ -151,8 +151,17 @@ const Lnbc: Component< { id?: string, lnbc: string } > = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const klass = () => {
|
||||||
|
let k = props.alternative ? styles.lnbcAlter : styles.lnbc;
|
||||||
|
if (props.noBack) {
|
||||||
|
k += ` ${styles.noBack}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={props.id} class={styles.lnbc}>
|
<div id={props.id} class={klass()}>
|
||||||
<Show when={paymentInProgress()}>
|
<Show when={paymentInProgress()}>
|
||||||
<div class={styles.paymentOverlay}>
|
<div class={styles.paymentOverlay}>
|
||||||
<Loader />
|
<Loader />
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
max-width: calc(100% - 48px);
|
max-width: calc(100% - 48px);
|
||||||
|
|
||||||
.message {
|
.message, .messageLn {
|
||||||
@include messageContent();
|
@include messageContent();
|
||||||
|
|
||||||
@if $align-end {
|
@if $align-end {
|
||||||
@ -46,12 +46,18 @@
|
|||||||
background-color: var(--subtile-devider);
|
background-color: var(--subtile-devider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.messageLn {
|
||||||
|
display: flex;
|
||||||
|
width: calc(500px + 12px - 40px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.threadTime {
|
.threadTime {
|
||||||
color: var(--text-tertiary-2);
|
color: var(--text-tertiary-2);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.messagesContent {
|
.messagesContent {
|
||||||
@ -258,7 +264,7 @@
|
|||||||
.myThread {
|
.myThread {
|
||||||
@include thread(true);
|
@include thread(true);
|
||||||
.threadMessages {
|
.threadMessages {
|
||||||
.message {
|
.message, .messageLn {
|
||||||
color: var(--text-primary-button);
|
color: var(--text-primary-button);
|
||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
border-radius: 12px 0px 0px 12px;
|
border-radius: 12px 0px 0px 12px;
|
||||||
@ -279,7 +285,7 @@
|
|||||||
.theirThread {
|
.theirThread {
|
||||||
@include thread(false);
|
@include thread(false);
|
||||||
.threadMessages {
|
.threadMessages {
|
||||||
.message {
|
.message, .messageLn {
|
||||||
background-color: var(--background-input);
|
background-color: var(--background-input);
|
||||||
border-radius: 0px 12px 12px 0px;
|
border-radius: 0px 12px 12px 0px;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { useIntl } from '@cookbook/solid-intl';
|
import { useIntl } from '@cookbook/solid-intl';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { Component, createEffect, createSignal, For, onCleanup, onMount, Show } from 'solid-js';
|
import { Component, createEffect, createSignal, For, JSXElement, Match, onCleanup, onMount, Show, Switch } from 'solid-js';
|
||||||
import Avatar from '../components/Avatar/Avatar';
|
import Avatar from '../components/Avatar/Avatar';
|
||||||
import { useAccountContext } from '../contexts/AccountContext';
|
import { useAccountContext } from '../contexts/AccountContext';
|
||||||
import { useMessagesContext } from '../contexts/MessagesContext';
|
import { useMessagesContext } from '../contexts/MessagesContext';
|
||||||
import { nip05Verification, truncateNpub, userName } from '../stores/profile';
|
import { nip05Verification, truncateNpub, userName } from '../stores/profile';
|
||||||
import { PrimalNote, PrimalUser } from '../types/primal';
|
import { DirectMessage, DirectMessageThread, PrimalNote, PrimalUser } from '../types/primal';
|
||||||
import { date } from '../lib/dates';
|
import { date } from '../lib/dates';
|
||||||
|
|
||||||
import styles from './Messages.module.scss';
|
import styles from './Messages.module.scss';
|
||||||
@ -20,7 +20,7 @@ import SearchOption from '../components/Search/SearchOption';
|
|||||||
import { debounce, isVisibleInContainer, uuidv4 } from '../utils';
|
import { debounce, isVisibleInContainer, uuidv4 } from '../utils';
|
||||||
import { useSearchContext } from '../contexts/SearchContext';
|
import { useSearchContext } from '../contexts/SearchContext';
|
||||||
import { createStore } from 'solid-js/store';
|
import { createStore } from 'solid-js/store';
|
||||||
import { editMentionRegex, emojiSearchLimit } from '../constants';
|
import { editMentionRegex, emojiSearchLimit, linebreakRegex } from '../constants';
|
||||||
import Search from '../components/Search/Search';
|
import Search from '../components/Search/Search';
|
||||||
import { useProfileContext } from '../contexts/ProfileContext';
|
import { useProfileContext } from '../contexts/ProfileContext';
|
||||||
import Paginator from '../components/Paginator/Paginator';
|
import Paginator from '../components/Paginator/Paginator';
|
||||||
@ -35,6 +35,7 @@ import {
|
|||||||
import PageCaption from '../components/PageCaption/PageCaption';
|
import PageCaption from '../components/PageCaption/PageCaption';
|
||||||
import { useMediaContext } from '../contexts/MediaContext';
|
import { useMediaContext } from '../contexts/MediaContext';
|
||||||
import PageTitle from '../components/PageTitle/PageTitle';
|
import PageTitle from '../components/PageTitle/PageTitle';
|
||||||
|
import Lnbc from '../components/Lnbc/Lnbc';
|
||||||
|
|
||||||
type AutoSizedTextArea = HTMLTextAreaElement & { _baseScrollHeight: number };
|
type AutoSizedTextArea = HTMLTextAreaElement & { _baseScrollHeight: number };
|
||||||
|
|
||||||
@ -294,6 +295,7 @@ const Messages: Component = () => {
|
|||||||
if (!messages) {
|
if (!messages) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseNoteLinks(
|
return parseNoteLinks(
|
||||||
parseNpubLinks(
|
parseNpubLinks(
|
||||||
highlightHashtags(
|
highlightHashtags(
|
||||||
@ -831,6 +833,13 @@ const Messages: Component = () => {
|
|||||||
newMessageInput.dispatchEvent(e);
|
newMessageInput.dispatchEvent(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const msgHasInvoice = (msg: DirectMessage) => {
|
||||||
|
const r =/(\s+|\r\n|\r|\n|^)lnbc[a-zA-Z0-9]+/;
|
||||||
|
const test = r.test(msg.content);
|
||||||
|
|
||||||
|
return test
|
||||||
|
};
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (account?.hasPublicKey()) {
|
if (account?.hasPublicKey()) {
|
||||||
profile?.actions.setProfileKey(account.publicKey)
|
profile?.actions.setProfileKey(account.publicKey)
|
||||||
@ -884,6 +893,75 @@ const Messages: Component = () => {
|
|||||||
newMessageInput && setMessage(newMessageInput.value)
|
newMessageInput && setMessage(newMessageInput.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderMessage = (msg: DirectMessage, thread: DirectMessageThread) => {
|
||||||
|
if (!msgHasInvoice(msg)) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={styles.message}
|
||||||
|
data-event-id={msg.id}
|
||||||
|
title={date(msg.created_at || 0).date.toLocaleString()}
|
||||||
|
innerHTML={parseMessage(msg.content)}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let sections: string[] = [];
|
||||||
|
|
||||||
|
let content = msg.content.replace(linebreakRegex, ' __LB__ ').replace(/\s+/g, ' __SP__ ');
|
||||||
|
|
||||||
|
let tokens: string[] = content.split(/[\s]+/);
|
||||||
|
|
||||||
|
let sectionIndex = 0;
|
||||||
|
tokens.forEach((t) => {
|
||||||
|
if (t.startsWith('lnbc')) {
|
||||||
|
if (sections[sectionIndex]) sectionIndex++;
|
||||||
|
|
||||||
|
sections[sectionIndex] = t;
|
||||||
|
|
||||||
|
sectionIndex++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let c = t;
|
||||||
|
const prev = sections[sectionIndex] || '';
|
||||||
|
|
||||||
|
if (t === '__SP__') {
|
||||||
|
c = prev.length === 0 ? '' : ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t === '__LB__') {
|
||||||
|
c = prev.length === 0 ? '' : '\r';
|
||||||
|
}
|
||||||
|
|
||||||
|
sections[sectionIndex] = prev + c;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<For each={sections.reverse()}>
|
||||||
|
{section => (
|
||||||
|
<Switch fallback={
|
||||||
|
<div
|
||||||
|
class={styles.message}
|
||||||
|
data-event-id={msg.id}
|
||||||
|
title={date(msg.created_at || 0).date.toLocaleString()}
|
||||||
|
innerHTML={parseMessage(section)}
|
||||||
|
></div>
|
||||||
|
}>
|
||||||
|
<Match when={section.startsWith('lnbc')}>
|
||||||
|
<div
|
||||||
|
class={styles.messageLn}
|
||||||
|
data-event-id={msg.id}
|
||||||
|
title={date(msg.created_at || 0).date.toLocaleString()}
|
||||||
|
>
|
||||||
|
<Lnbc lnbc={section} noBack={true} alternative={!isSelectedSender(thread.author)} />
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageTitle title={intl.formatMessage(tMessages.title)} />
|
<PageTitle title={intl.formatMessage(tMessages.title)} />
|
||||||
@ -1064,14 +1142,7 @@ const Messages: Component = () => {
|
|||||||
</A>
|
</A>
|
||||||
<div class={styles.threadMessages}>
|
<div class={styles.threadMessages}>
|
||||||
<For each={thread.messages}>
|
<For each={thread.messages}>
|
||||||
{(msg) => (
|
{msg => renderMessage(msg, thread)}
|
||||||
<div
|
|
||||||
class={styles.message}
|
|
||||||
data-event-id={msg.id}
|
|
||||||
title={date(msg.created_at || 0).date.toLocaleString()}
|
|
||||||
innerHTML={parseMessage(msg.content)}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
<Show when={thread.messages[0]}>
|
<Show when={thread.messages[0]}>
|
||||||
@ -1091,14 +1162,7 @@ const Messages: Component = () => {
|
|||||||
</A>
|
</A>
|
||||||
<div class={styles.threadMessages}>
|
<div class={styles.threadMessages}>
|
||||||
<For each={thread.messages}>
|
<For each={thread.messages}>
|
||||||
{(msg) => (
|
{msg => renderMessage(msg, thread)}
|
||||||
<div
|
|
||||||
class={styles.message}
|
|
||||||
data-event-id={msg.id}
|
|
||||||
title={date(msg.created_at || 0).date.toLocaleString()}
|
|
||||||
innerHTML={parseMessage(msg.content)}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
<Show when={thread.messages[0]}>
|
<Show when={thread.messages[0]}>
|
||||||
|
Loading…
Reference in New Issue
Block a user