Merge pull request #263 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-02-24 19:18:39 +01:00
committed by GitHub
15 changed files with 356 additions and 126 deletions

View File

@@ -1,19 +1,19 @@
# # send notifications to telegram group on commits, releases, issues
# name: notifications
# send notifications to telegram group on commits, releases, issues
name: notifications
# on:
# pull_request:
# branches:
# - master
# push:
# branches:
# - master
# release:
# types: [published]
# issues:
# types: [opened]
on:
pull_request:
branches:
- master
# push:
# branches:
# - master
release:
types: [published]
issues:
types: [opened]
# jobs:
jobs:
# push:
# if: ${{ github.event_name == 'push' }}
# runs-on: ubuntu-latest
@@ -44,50 +44,50 @@
# format: html
# message_file: message.txt
# release:
# if: ${{ github.event_name == 'release' }}
# runs-on: ubuntu-latest
# steps:
# # - name: debug
# # env:
# # DEBUG: ${{ toJSON(github) }}
# # run: echo "$DEBUG"
release:
if: ${{ github.event_name == 'release' }}
runs-on: ubuntu-latest
steps:
# - name: debug
# env:
# DEBUG: ${{ toJSON(github) }}
# run: echo "$DEBUG"
# # write message to file
# - run: echo "<b>${{ github.event.repository.name }} ${{ github.event.release.name }}</b>" >> message.txt
# - run: echo "" >> message.txt
# - run: echo "${{ github.event.release.body }}" >> message.txt
# - run: echo "${{ github.event.release.html_url }}" >> message.txt
# write message to file
- run: echo "<b>${{ github.event.repository.name }} ${{ github.event.release.name }}</b>" >> message.txt
- run: echo "" >> message.txt
- run: echo "${{ github.event.release.body }}" >> message.txt
- run: echo "${{ github.event.release.html_url }}" >> message.txt
# # send message, @plebbit telegram chat id is -1001665335693
# - name: "telegram notification"
# uses: appleboy/telegram-action@master
# with:
# to: -1001665335693
# token: ${{ secrets.TELEGRAM_TOKEN }}
# format: html
# message_file: message.txt
# send message, @plebbit telegram chat id is -1001665335693
- name: "telegram notification"
uses: appleboy/telegram-action@master
with:
to: -1001665335693
token: ${{ secrets.TELEGRAM_TOKEN }}
format: html
message_file: message.txt
# issue:
# if: ${{ github.event_name == 'issues' }}
# runs-on: ubuntu-latest
# steps:
# # - name: debug
# # env:
# # DEBUG: ${{ toJSON(github) }}
# # run: echo "$DEBUG"
issue:
if: ${{ github.event_name == 'issues' }}
runs-on: ubuntu-latest
steps:
# - name: debug
# env:
# DEBUG: ${{ toJSON(github) }}
# run: echo "$DEBUG"
# # write message to file
# - run: echo "<code>${{ github.event.issue.title }}</code>" >> message.txt
# - run: echo "" >> message.txt
# - run: echo "by <i>${{ github.event.issue.user.login }}</i>" >> message.txt
# - run: echo "${{ github.event.issue.html_url }}" >> message.txt
# write message to file
- run: echo "<code>${{ github.event.issue.title }}</code>" >> message.txt
- run: echo "" >> message.txt
- run: echo "by <i>${{ github.event.issue.user.login }}</i>" >> message.txt
- run: echo "${{ github.event.issue.html_url }}" >> message.txt
# # send message, @plebbit telegram chat id is -1001665335693
# - name: "telegram notification"
# uses: appleboy/telegram-action@master
# with:
# to: -1001665335693
# token: ${{ secrets.TELEGRAM_TOKEN }}
# format: html
# message_file: message.txt
# send message, @plebbit telegram chat id is -1001665335693
- name: "telegram notification"
uses: appleboy/telegram-action@master
with:
to: -1001665335693
token: ${{ secrets.TELEGRAM_TOKEN }}
format: html
message_file: message.txt

View File

@@ -42,8 +42,6 @@ const useEditStore = create<EditStoreState>((set) => ({
onChallengeVerification: alertChallengeVerificationFailed,
onError: (error: Error) => {
console.error(error);
let errorMessage = error.message;
alert(errorMessage);
},
};
return nextState;

View File

@@ -11,23 +11,23 @@ type ShareMenuProps = {
const ShareButton = ({ cid, subplebbitAddress }: ShareMenuProps) => {
const { t } = useTranslation();
const [hasShared, setHasShared] = useState(false);
const [hasCopied, setHasCopied] = useState(false);
useEffect(() => {
if (hasShared) {
setTimeout(() => setHasShared(false), 2000);
if (hasCopied) {
setTimeout(() => setHasCopied(false), 2000);
}
}, [hasShared]);
}, [hasCopied]);
return (
<div
className={`${!hasShared ? styles.menuItem : styles.text}`}
className={`${!hasCopied ? styles.menuItem : styles.text}`}
onClick={() => {
setHasShared(true);
setHasCopied(true);
copyShareLinkToClipboard(subplebbitAddress, cid);
}}
>
{hasShared ? t('link_copied') : t('copy_link')}
{hasCopied ? t('link_copied') : t('copy_link')}
</div>
);
};

View File

@@ -59,7 +59,6 @@
}
.xEmbed {
width: 400px !important;
height: 580px !important;
}

View File

@@ -1,6 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import styles from './reply-form.module.css';
import { isValidURL } from '../../lib/utils/url-utils';
import useReply from '../../hooks/use-reply';
@@ -10,6 +9,7 @@ type ReplyFormProps = {
cid: string;
isReplyingToReply?: boolean;
hideReplyForm?: () => void;
subplebbitAddress: string;
};
export const FormattingHelpTable = () => {
@@ -80,11 +80,10 @@ export const FormattingHelpTable = () => {
);
};
const ReplyForm = ({ cid, isReplyingToReply, hideReplyForm }: ReplyFormProps) => {
const ReplyForm = ({ cid, isReplyingToReply, hideReplyForm, subplebbitAddress }: ReplyFormProps) => {
const { t } = useTranslation();
const [showOptions, setShowOptions] = useState(false);
const [showFormattingHelp, setShowFormattingHelp] = useState(false);
const subplebbitAddress = useParams().subplebbitAddress as string;
const { setContent, resetContent, replyIndex, publishReply } = useReply({ cid, subplebbitAddress });
const mdContainerClass = isReplyingToReply ? `${styles.mdContainer} ${styles.mdContainerReplying}` : styles.mdContainer;

View File

@@ -414,7 +414,7 @@ const Reply = ({ cidOfReplyWithContext, depth = 0, isSingleComment, isSingleRepl
showReplyForm={showReplyForm}
spoiler={spoiler}
/>
{isReplying && <ReplyForm cid={cid} isReplyingToReply={true} hideReplyForm={hideReplyForm} />}
{isReplying && <ReplyForm cid={cid} isReplyingToReply={true} hideReplyForm={hideReplyForm} subplebbitAddress={subplebbitAddress} />}
{!isSingleReply &&
replies.map((reply, index) => {
return (

View File

@@ -61,7 +61,7 @@ const Post = ({ post }: { post: Comment }) => {
</div>
</div>
<div className={styles.spacer} />
{!isSingleComment && <ReplyForm cid={cid} />}
{!isSingleComment && <ReplyForm cid={cid} subplebbitAddress={subplebbitAddress} />}
{loadingString && loadingString}
</div>
{isSingleComment && (

View File

@@ -38,18 +38,23 @@
width: 80vw;
}
.infoButton {
padding: 0px 4px 0px 4px;
font-size: 11px;
position: relative;
bottom: 2px;
margin-bottom: -2px;
}
.copyAddressSetting {
padding-bottom: 10px;
}
.copyAddressSetting button {
margin-left: 0;
}
.cryptoAddressInfo {
padding: 5px 20px 5px 0;
word-break: break-word;
}
.cryptoAddressInfo a {
color: var(--text-primary);
}
.cryptoAddressInfo a:hover {
text-decoration: underline;
}

View File

@@ -65,12 +65,6 @@ const AddressSettings = () => {
}));
}, [resolvedAddress, account?.signer?.address, t]);
const cryptoAddressInfo = () => {
alert(
'Change your account address to an ENS name you own: in your ENS name page on ens.domains, click on "Records", "Edit Records", "Add record", add "plebbit-author-address" as record name, add your full address as value (you can copy it from your account data) and save.',
);
};
const saveCryptoAddress = async () => {
if (!cryptoState.cryptoAddress || !cryptoState.cryptoAddress.includes('.')) {
alert(t('enter_crypto_address'));
@@ -129,16 +123,11 @@ const AddressSettings = () => {
}
}, [savedCryptoAddress]);
const [showCryptoAddressInfo, setShowCryptoAddressInfo] = useState(false);
return (
<div className={styles.addressSettings}>
<div className={styles.copyAddressSetting}>
<button onClick={() => navigator.clipboard.writeText(account?.signer?.address)}>{t('copy')}</button> plebbit-author-address
</div>
<div className={styles.cryptoAddressSetting}>
<span className={styles.settingTitle}>{t('crypto_address')}</span>
<button className={styles.infoButton} onClick={cryptoAddressInfo}>
?
</button>
<div className={styles.usernameInput}>
<input
type='text'
@@ -146,9 +135,20 @@ const AddressSettings = () => {
value={cryptoState.cryptoAddress}
onChange={(e) => setCryptoState((prevState) => ({ ...prevState, cryptoAddress: e.target.value }))}
/>
<button className={styles.infoButton} onClick={() => setShowCryptoAddressInfo(!showCryptoAddressInfo)}>
{showCryptoAddressInfo ? 'x' : '?'}
</button>
<button className={styles.button} onClick={saveCryptoAddress}>
{t('save')}
</button>
{showCryptoAddressInfo && (
<div className={styles.cryptoAddressInfo}>
<a href='https://app.ens.domains/' target='_blank' rel='noopener noreferrer'>
app.ens.domains
</a>
{` > address.eth > records > edit records > add record > record name: "plebbit-author-address" > record value: ${account?.signer?.address} > save`}
</div>
)}
{savedCryptoAddress && <span className={styles.saved}>{t('saved')}</span>}
</div>
<div className={styles.checkCryptoAddress}>

View File

@@ -0,0 +1,63 @@
.avatar {
width: 70px;
height: 70px;
border: 1px solid var(--border-text);
}
.avatar img {
width: 70px;
height: 70px;
}
.emptyAvatar {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: 100%;
cursor: pointer;
}
.avatarSettingsForm {
padding-top: 10px;
}
.avatarSettingsForm input {
margin-bottom: 10px;
width: 200px;
padding: 2px;
box-shadow: var(--box-shadow-input);
}
.avatarSettingInput input {
display: block;
}
.settingTitle {
font-style: italic;
text-transform: lowercase;
}
.copyMessage {
padding-bottom: 10px;
}
.pasteSignature span {
display: block;
}
.pasteSignature button {
margin-left: 5px;
}
.state {
padding-top: 10px;
}
.copyMessage a {
color: var(--text-primary);
}
.copyMessage a:hover {
text-decoration: underline;
}

View File

@@ -0,0 +1,195 @@
import { useEffect, useMemo, useState } from 'react';
import { setAccount, useAccount, useAuthorAvatar } from '@plebbit/plebbit-react-hooks';
import { useTranslation } from 'react-i18next';
import styles from './avatar-settings.module.css';
interface AvatarSettingsProps {
areSettingsShown?: boolean;
avatar?: any;
showSettings?: () => void;
}
const AvatarPreview = ({ avatar, showSettings, areSettingsShown }: AvatarSettingsProps) => {
const { t } = useTranslation();
const account = useAccount();
let author = useMemo(() => ({ ...account?.author, avatar }), [account, avatar]);
const { imageUrl, state, error } = useAuthorAvatar({ author });
// if avatar already set, and user hasn't typed anything yet, preview already set author
if (account?.author?.avatar && !avatar?.chainTicker && !avatar?.address && !avatar?.id && !avatar?.signature) {
author = account.author;
}
// not enough data to preview yet
if (!author?.avatar?.address && !author?.avatar?.signature) {
return;
}
const stateText = state !== 'succeeded' ? `${state}...` : undefined;
return (
<>
<div className={styles.avatar}>
{imageUrl && state !== 'initializing' ? (
<img src={imageUrl} alt='avatar' />
) : (
<span className={styles.emptyAvatar} onClick={showSettings}>
{areSettingsShown ? '' + t('hide') : '+' + t('add')}
</span>
)}
</div>
<div className={styles.state}>
{stateText} {error?.message}
</div>
</>
);
};
const AvatarSettings = () => {
const [showSettings, setShowSettings] = useState(false);
const account = useAccount();
const authorAddress = account?.author?.address;
const [chainTicker, setChainTicker] = useState(account?.author?.avatar?.chainTicker);
const [tokenAddress, setTokenAddress] = useState(account?.author?.avatar?.address);
const [tokenId, setTokenId] = useState(account?.author?.avatar?.id);
const [timestamp, setTimestamp] = useState(account?.author?.avatar?.timestamp);
const [signature, setSignature] = useState(account?.author?.avatar?.signature?.signature);
const getNftMessageToSign = (authorAddress: string, timestamp: number, tokenAddress: string, tokenId: string) => {
let messageToSign: any = {};
// the property names must be in this order for the signature to match
// insert props one at a time otherwise babel/webpack will reorder
messageToSign.domainSeparator = 'plebbit-author-avatar';
messageToSign.authorAddress = authorAddress;
messageToSign.timestamp = timestamp;
messageToSign.tokenAddress = tokenAddress;
messageToSign.tokenId = String(tokenId); // must be a type string, not number
// use plain JSON so the user can read what he's signing
messageToSign = JSON.stringify(messageToSign);
return messageToSign;
};
const [hasCopied, setHasCopied] = useState(false);
useEffect(() => {
if (hasCopied) {
setTimeout(() => setHasCopied(false), 2000);
}
}, [hasCopied]);
const copyMessageToSign = () => {
if (!chainTicker) {
return alert('missing chain ticker');
}
if (!tokenAddress) {
return alert('missing token address');
}
if (!tokenId) {
return alert('missing token id');
}
const newTimestamp = Math.floor(Date.now() / 1000);
const messageToSign = getNftMessageToSign(authorAddress, newTimestamp, tokenAddress, tokenId);
// update timestamp every time the user gets a new message to sign
setTimestamp(newTimestamp);
navigator.clipboard.writeText(messageToSign);
setHasCopied(true);
};
// how to resolve and verify NFT signatures https://github.com/plebbit/plebbit-js/blob/master/docs/nft.md
const avatar = {
chainTicker: chainTicker?.toLowerCase() || account?.author?.avatar?.chainTicker,
timestamp,
address: tokenAddress || account?.author?.avatar?.address,
id: tokenId || account?.author?.avatar?.id,
signature: {
signature: signature || account?.author?.avatar?.signature?.signature,
type: 'eip191',
},
};
const save = () => {
if (!chainTicker) {
return alert('missing chain ticker');
}
if (!tokenAddress) {
return alert('missing token address');
}
if (!tokenId) {
return alert('missing token id');
}
if (!signature) {
return alert('missing signature');
}
setAccount({ ...account, author: { ...account?.author, avatar } });
alert(`saved`);
};
return (
<div className={styles.avatarSettings}>
<AvatarPreview avatar={avatar} showSettings={() => setShowSettings(!showSettings)} areSettingsShown={showSettings} />
{showSettings && (
<div className={styles.avatarSettingsForm}>
<div className={styles.avatarSettingInput}>
<span className={styles.settingTitle}>chain ticker</span>
<input
type='text'
placeholder='eth/sol/avax'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.chainTicker}
onChange={(e) => setChainTicker(e.target.value)}
/>
</div>
<div className={styles.avatarSettingInput}>
<span className={styles.settingTitle}>token address</span>
<input
type='text'
placeholder='0x...'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.address}
onChange={(e) => setTokenAddress(e.target.value)}
/>
</div>
<div className={styles.avatarSettingInput}>
<span className={styles.settingTitle}>token id</span>
<input
type='text'
placeholder='Token ID'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.id}
onChange={(e) => setTokenId(e.target.value)}
/>
</div>
<div className={styles.copyMessage}>
<button onClick={copyMessageToSign}>{hasCopied ? 'copied' : 'copy'}</button> message to sign on{' '}
<a href='https://etherscan.io/verifiedSignatures' target='_blank' rel='noopener noreferrer'>
etherscan
</a>
</div>
<div className={styles.pasteSignature}>
<span className={styles.settingTitle}>paste signature</span>
<input
type='text'
placeholder='0x...'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
defaultValue={account?.author?.avatar?.signature?.signature}
onChange={(e) => setSignature(e.target.value)}
/>
<button onClick={save}>save</button>
</div>
</div>
)}
</div>
);
};
export default AvatarSettings;

View File

@@ -0,0 +1 @@
export { default } from './avatar-settings';

View File

@@ -75,26 +75,6 @@
text-decoration: underline;
}
.avatar {
width: 70px;
height: 70px;
border: 1px solid var(--border-text);
}
.avatar img {
width: 70px;
height: 70px;
}
.emptyAvatar {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: 100%;
cursor: pointer;
}
.usernameInput input {
width: 200px;
padding: 2px;

View File

@@ -1,9 +1,10 @@
import { useEffect, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { setAccount, useAccount, useAuthorAvatar } from '@plebbit/plebbit-react-hooks';
import { setAccount, useAccount } from '@plebbit/plebbit-react-hooks';
import styles from './settings.module.css';
import AccountSettings from './account-settings';
import AddressSettings from './address-settings';
import AvatarSettings from './avatar-settings';
import useTheme from '../../hooks/use-theme';
import packageJson from '../../../package.json';
import _ from 'lodash';
@@ -106,18 +107,6 @@ const ThemeSettings = () => {
);
};
const AvatarSettings = () => {
const { t } = useTranslation();
const account = useAccount();
const { imageUrl } = useAuthorAvatar({ author: account?.author });
return (
<div className={styles.avatarSettings}>
<div className={styles.avatar}>{imageUrl ? <img src={imageUrl} alt='avatar' /> : <span className={styles.emptyAvatar}>+{t('add')}</span>}</div>
</div>
);
};
const DisplayNameSetting = () => {
const { t } = useTranslation();
const account = useAccount();
@@ -233,7 +222,7 @@ const Settings = () => {
</span>
</div>
<div className={styles.category}>
<span className={styles.categoryTitle}>{t('address')}</span>
<span className={styles.categoryTitle}>{t('crypto_address')}</span>
<span className={styles.categorySettings}>
<AddressSettings />
</span>

View File

@@ -62,6 +62,7 @@
.readOnlyDescription {
font-size: 14px;
font-family: verdana, arial, helvetica, sans-serif;
word-wrap: break-word;
white-space: pre-wrap;
}