Compare commits
630 Commits
ca2c9130aa
...
dda38ab824
Author | SHA1 | Date | |
---|---|---|---|
dda38ab824 | |||
e9f70cd040 | |||
7443012995 | |||
a3f9aa2fbf | |||
07db21d4d0 | |||
d4b343a18c | |||
4ce15ca1c5 | |||
69d47621ac | |||
91a10024e0 | |||
96e49dac41 | |||
ce93ee1c41 | |||
a4f691d260 | |||
468dc3bae6 | |||
e8abd5ac09 | |||
ad3ec993a6 | |||
302aa2ec06 | |||
67af84e8bb | |||
7c63cf1ab5 | |||
fa701308c0 | |||
1febb82c22 | |||
2bd58a526c | |||
26e146251c | |||
b37d2b0fe8 | |||
902e2dd960 | |||
d704ded3d8 | |||
1f82d38acb | |||
bbcedbd99c | |||
d84843a46d | |||
51e68b1288 | |||
3acb2d4d34 | |||
d2b95375b6 | |||
61e93bd194 | |||
3a07e2abee | |||
c3a6304573 | |||
5be04c86b3 | |||
8b1745ae08 | |||
933ab4a71f | |||
511f401367 | |||
96e954d5e7 | |||
ca472a5756 | |||
b6197f533f | |||
11e3136008 | |||
8dc6f2884f | |||
d27045794a | |||
11824269f2 | |||
ea5e324793 | |||
|
9b5721faa4 | ||
6df3459dba | |||
de1ad63924 | |||
37cc990f18 | |||
1ffc2c6bf2 | |||
83299f2feb | |||
9299722ac0 | |||
acbe59b7bb | |||
79095b234b | |||
700decbe06 | |||
71fcf4f4ac | |||
65686ae9f3 | |||
d240e82a95 | |||
ad163625c3 | |||
1330db056a | |||
6d1d1bfc44 | |||
bc6bced1b8 | |||
aecb4408a0 | |||
ef4667c879 | |||
c4273b9bdf | |||
04fea3e43b | |||
9393e3342d | |||
9749883cc1 | |||
4d57cde8f0 | |||
95aa6f78bb | |||
07e2c15f54 | |||
a06b7ccada | |||
509228d532 | |||
ad0f691577 | |||
79e303f097 | |||
398ab46e42 | |||
3541f7fea3 | |||
d28f287173 | |||
d41e7e5ce0 | |||
9d0f7f4596 | |||
31129fab2c | |||
6190f3cd9d | |||
836af8fbd2 | |||
625c8d4d43 | |||
7dfb7ec363 | |||
8deb27b3e2 | |||
e184a96dc9 | |||
09dd97111a | |||
9db117e270 | |||
6b3102f30a | |||
360ca20eba | |||
dcd7faa3aa | |||
77ab39470f | |||
ae73e2b383 | |||
ecd5ea111d | |||
9043a1851d | |||
edf4c0db99 | |||
095564d16b | |||
42a95d8de0 | |||
61b4dba008 | |||
1080c4aa0e | |||
11cdd51eed | |||
0434ae43d8 | |||
8bc323489e | |||
2e54104618 | |||
9fd1685fa1 | |||
c2bd6ae856 | |||
a612db65c7 | |||
a592974b93 | |||
920f8ae20d | |||
a92fc267c3 | |||
561ee94ab0 | |||
9a0b7c08d9 | |||
8d44eb6a09 | |||
e6be61e12a | |||
602592c424 | |||
b62519db31 | |||
4813e4e4f1 | |||
b7545295d4 | |||
e0b85b90f9 | |||
13b76e7709 | |||
47aec5437d | |||
0f9f8ecb95 | |||
2c14a8f404 | |||
e21e549969 | |||
edfe9c3697 | |||
365031516d | |||
b5606028e0 | |||
e18500c80b | |||
68ebe5e7b1 | |||
0a3e15df82 | |||
9cae8ec6eb | |||
2c414c4a56 | |||
3e8fe2ca95 | |||
0f07e75905 | |||
29bba9aef3 | |||
6bef3ccf58 | |||
a0151745bf | |||
6a6accfed6 | |||
4eb0408c27 | |||
ef77606427 | |||
7e69e9a42a | |||
bfc42bcd36 | |||
f3eb414c6a | |||
20c4ecaa0c | |||
62e99a4ed4 | |||
5d9ca5ee39 | |||
6fd2741cc0 | |||
2884a35b5c | |||
90c00c5fa4 | |||
b52b84e466 | |||
de524b50fc | |||
1af8316453 | |||
55fae3aa41 | |||
a67263e5e1 | |||
131e564bd0 | |||
c5bc1cdbe7 | |||
e788e01c6d | |||
28bf0ea581 | |||
ced63ab6d1 | |||
e97b9bdba4 | |||
f9e7bc8558 | |||
f9d0318f36 | |||
a80c330e5b | |||
92c26ca609 | |||
f5bb524628 | |||
77984f9124 | |||
cb5fd78382 | |||
9aafc329cb | |||
59ffb84458 | |||
c5d22b84be | |||
8e5acc3ea5 | |||
667bce637f | |||
4dfa610c87 | |||
dd941ae70e | |||
60af57059b | |||
f69e05d8fc | |||
d8dfc962c1 | |||
56db2f652d | |||
756e50f866 | |||
fbd479b4fb | |||
0f5324ccad | |||
2030cb2061 | |||
731d312d92 | |||
b64c9fa767 | |||
aaa56738b5 | |||
7b4e6db306 | |||
c44877f780 | |||
dcbc10686c | |||
8dadb77555 | |||
377061e9d6 | |||
7c8fff97ce | |||
e8bcba129e | |||
c9baf487ae | |||
81d4382aba | |||
5c0ad1c375 | |||
5b3680d0df | |||
69c97033fb | |||
ef85b47b58 | |||
6d706717c8 | |||
52553adabf | |||
5942d92923 | |||
38277c3252 | |||
8deec37d15 | |||
1687137572 | |||
f2ff2c65be | |||
e3cb397d1d | |||
9721aa506a | |||
eba47f085d | |||
839f448231 | |||
3c2bfbb4c6 | |||
588c3756fd | |||
55c938961f | |||
aef27bf879 | |||
541f6164a7 | |||
9bec963da2 | |||
5589e748b5 | |||
e1f4aab214 | |||
8666467efc | |||
963ce098c6 | |||
3e50d34786 | |||
5a63a21fec | |||
981ab5790a | |||
95b7cca4cb | |||
ee0865d9af | |||
3611af9dce | |||
83ffe746b1 | |||
d0bcc59372 | |||
985827ba22 | |||
e65845af6d | |||
e398bf8e82 | |||
a2d084ab70 | |||
7174ed2502 | |||
c794a9e393 | |||
5fea9f3dfb | |||
eb47da0417 | |||
b1e7cf6bf9 | |||
55ec27cf4a | |||
c530e4ba72 | |||
f2a41cb474 | |||
de1b982e93 | |||
81d6f41050 | |||
24978f4e62 | |||
540f29dd69 | |||
815fc344f1 | |||
6db13c05c6 | |||
e9b7c7c6e3 | |||
6f396c825c | |||
b5df7dbb6e | |||
04a49755e6 | |||
b765cb29b7 | |||
e087df2f63 | |||
5b2d8741ba | |||
e72190b49f | |||
36c1d9502c | |||
70fd872848 | |||
98f6a686b0 | |||
9082630cc2 | |||
aa31047c45 | |||
8014d84b3d | |||
ff2da6c5fd | |||
ffda4b1a43 | |||
9016d21eaa | |||
|
34fa2bb524 | ||
7e590a4ef8 | |||
7088bee14d | |||
732a895a58 | |||
2d4d9117bd | |||
0e202b12d9 | |||
d119a5f626 | |||
a2bcb936ef | |||
b4697d1e04 | |||
c9935a275e | |||
3326aedc52 | |||
8dbbb24729 | |||
b234762f62 | |||
cf6aa6b134 | |||
e627cddd24 | |||
fcd2c8a3a0 | |||
fc3d196f48 | |||
b166427f28 | |||
d60862da11 | |||
758107fd50 | |||
e248889170 | |||
c689bd39dc | |||
f2456e060e | |||
de0e58b657 | |||
3d69762359 | |||
9d91cc6ec9 | |||
6ca8f8f339 | |||
5fe8a5e3b6 | |||
6e349051a2 | |||
e0b68ae817 | |||
b3c8ee982d | |||
d59c3ebdcb | |||
58e6be94fa | |||
4ccb052edb | |||
f482c004b3 | |||
0ebd2f167a | |||
930b493a12 | |||
9eb029e1dc | |||
0cef163eb9 | |||
71a05dd13c | |||
2f1d48792a | |||
ed3b6c84cf | |||
f252087f6b | |||
d4bf929e60 | |||
32c80ed1c5 | |||
83a085a343 | |||
f994f8722d | |||
6f15580682 | |||
c65bb7a992 | |||
8f90daa840 | |||
46d7c000ac | |||
454f957653 | |||
c2991b8e26 | |||
63950f1e6b | |||
0e3661afc6 | |||
c1ea68b296 | |||
22224cb4f2 | |||
548247f39c | |||
083f8a9edb | |||
6f1c36d53e | |||
b379550827 | |||
7b29290420 | |||
d06a914e01 | |||
76ec08a251 | |||
938bc84fb3 | |||
74480af85b | |||
2dd84bd280 | |||
fd746440b6 | |||
a5ae474c8b | |||
c68565c484 | |||
9f763fccba | |||
12c678ca7a | |||
bee0cc1188 | |||
15795d442f | |||
824b6fdce4 | |||
c96ea94bb3 | |||
234d354062 | |||
3f28c94a56 | |||
1a984a8075 | |||
748fe22101 | |||
b5e9203742 | |||
c823cd314d | |||
8ea5be1504 | |||
ccd98bef1b | |||
3b3a920124 | |||
4d4106a3ff | |||
3714867b98 | |||
4be93c8f51 | |||
ce09b92518 | |||
c0bfe376ed | |||
98c3d901ae | |||
0ba1ba05ac | |||
09cdd501c3 | |||
d954b90bfd | |||
c565dbc993 | |||
51f0d2ed15 | |||
63d3645dda | |||
190dd92f9a | |||
a331e43b4e | |||
5535614455 | |||
8e9e75c5f0 | |||
e3d17254f8 | |||
cf60dcb654 | |||
c4bbafb9d7 | |||
ab50afe917 | |||
2ff072f442 | |||
0e0d768eec | |||
a081f9655e | |||
81df18ea4e | |||
2e663dcb4c | |||
52f70056f8 | |||
7a1df4a178 | |||
d50979b8ae | |||
9b3cc94d18 | |||
700db8f62c | |||
2c7878ac7f | |||
d9bd198e8d | |||
ca18cf25e3 | |||
089c40d816 | |||
3f82b31b6b | |||
0548e1a9e1 | |||
2b09da6959 | |||
a0d14b158b | |||
faaeb6af4a | |||
6479a18cb2 | |||
9f6a030a11 | |||
88c146d729 | |||
281785952d | |||
1a507679f3 | |||
f34ccf72cb | |||
5e42c5e70c | |||
3b427338f4 | |||
725d6d11f0 | |||
f24d9982f4 | |||
dec2b9ce2e | |||
497ef7bf9a | |||
0f40b9a426 | |||
a168465bdb | |||
28fa0b4bc8 | |||
2ce5bd153b | |||
7129ffa1c7 | |||
227b3b8dd7 | |||
f0110e9009 | |||
65552e126b | |||
5162887807 | |||
e378a53b21 | |||
d9fc4f37b0 | |||
6448996529 | |||
a29d82bd56 | |||
3a95689792 | |||
10e83a5f55 | |||
4bf868c05a | |||
cf7d9b8883 | |||
70925e6f08 | |||
94058efb60 | |||
55d089072d | |||
ce550eb206 | |||
93e8e0bbae | |||
87bb9dafeb | |||
9251d5b90a | |||
a647fd09b3 | |||
ddb8e623f4 | |||
9b66b7b1da | |||
3b363d988e | |||
430763478f | |||
85faf528c5 | |||
2e38ac0d4f | |||
a080f0bb0c | |||
f5aa898631 | |||
01af3a3a58 | |||
733fb6da30 | |||
81642b906e | |||
8f401c07bc | |||
6e73e51501 | |||
6650c48c98 | |||
95a715839d | |||
7513d4cdd3 | |||
a0207e8874 | |||
a8964a2248 | |||
102134d47f | |||
5a67edaf0b | |||
2155d00a07 | |||
5360c5ad3b | |||
a0c8012f8e | |||
88ac4063cd | |||
022296fa18 | |||
0e4a040750 | |||
c239fba3df | |||
ece4219180 | |||
6eca5a632d | |||
3b505f6c3e | |||
bcaaba3fd4 | |||
09a9364163 | |||
|
5d137f281f | ||
|
b0d84779c8 | ||
2c31a37b6a | |||
50bfd9eaa0 | |||
672255187f | |||
9d33abbf1e | |||
c023a89271 | |||
b59c5685f9 | |||
8d882a0844 | |||
b62b877f5a | |||
01234d7be7 | |||
b5499f516c | |||
8f153b0428 | |||
b27bb47007 | |||
15fb4cabdf | |||
dbaad8bbb3 | |||
79ef147023 | |||
237ce498b7 | |||
226b0632a3 | |||
12723cf54b | |||
d2f786c98e | |||
|
9c4871d3c1 | ||
224960a11f | |||
091169ae7d | |||
5ed096509a | |||
3f7ac9e2d4 | |||
9f5d467745 | |||
e949708cec | |||
585f031ce1 | |||
0ff3aad6cb | |||
bc8e08ba47 | |||
92c90b40ee | |||
2a6433a6b4 | |||
fb28f5d7df | |||
cb8fec85c3 | |||
aa1ab7023f | |||
74c61ca9ba | |||
e583770518 | |||
71f5af649c | |||
873ba0a1d2 | |||
90ac55d561 | |||
efa303b68f | |||
4e2b7e6bb9 | |||
40e05480e6 | |||
ef7fc458f6 | |||
0b5dc2d290 | |||
bf0af2d14e | |||
58ba714d36 | |||
234167b749 | |||
d4a3e11d03 | |||
3f9805c48a | |||
b48ea3167a | |||
7f2149b8f1 | |||
bef8b40c8b | |||
23b45961aa | |||
11c32c2745 | |||
7587db9c8b | |||
38b647938e | |||
89982793ed | |||
88d5ff73e0 | |||
77016cfbc7 | |||
4d12de302d | |||
7c839ae3a8 | |||
15ee38c24f | |||
4342669896 | |||
|
8e34a7a078 | ||
c162ac6428 | |||
c3ab453022 | |||
a47e44d492 | |||
0a0c67dc89 | |||
1eb2fa4b90 | |||
9d19d02636 | |||
2efef99c95 | |||
e191528c4c | |||
317b58b298 | |||
f33961232b | |||
ecb0f0e78a | |||
fc38049b87 | |||
1d1e8889dc | |||
|
a0824646eb | ||
60326ea17f | |||
c4f40b5c8a | |||
e165ce232a | |||
b239bc65d8 | |||
b993c3ff3c | |||
8b9f55493e | |||
3b4f17dddf | |||
1d3db500a6 | |||
a780b9554d | |||
5c35d7af79 | |||
ea6b853057 | |||
03e61a7d4a | |||
cca17284ef | |||
03d6e13226 | |||
d1085410ec | |||
a06dc977bc | |||
b3ede123b5 | |||
3c812cb51a | |||
1f6e1886b3 | |||
5d698e550e | |||
820844eeb5 | |||
bfdcfa580a | |||
2e1efc3318 | |||
3541d1ab57 | |||
d7c79e8fde | |||
c611de2096 | |||
07e62368f9 | |||
cd8fce15a9 | |||
87f279bd77 | |||
566f086191 | |||
2f20b90dd9 | |||
1eb7216177 | |||
5b9f4b0b8d | |||
875348cc03 | |||
8e414a10c5 | |||
2833813ef3 | |||
80736cc5dd | |||
6bcb5f3f80 | |||
6e7a28a42b | |||
9f731da5be | |||
0e9ca7e2e3 | |||
4d629f5087 | |||
5692423e3c | |||
94da60ebfa | |||
3efb5321f6 | |||
58d5983316 | |||
ef6714f856 | |||
60c5dd7a2c | |||
c12703791a | |||
d3c0b866f2 | |||
200727635c | |||
9ec2e8b2e6 | |||
8ab3c16eed | |||
8c7fbed191 | |||
e8519daa47 | |||
4a2aa2aced | |||
5182b65591 | |||
dadf5159bd | |||
554bfa45f0 | |||
2b1226ce75 | |||
eef4270f84 | |||
01b3fd559b | |||
96d4e4bcc5 | |||
8244441929 | |||
3e0c4e5064 | |||
30f7f28f23 | |||
71f7f728fd | |||
4b57d57f94 | |||
0a22d67702 | |||
a3896cf55a | |||
02d9bbf70d | |||
f5617d1b01 | |||
dde730238d | |||
1c52db933f | |||
4b2161369d | |||
04239123bb | |||
6a9314d513 | |||
803c8ee05e | |||
9fb6f0dfee | |||
a1cd56292a | |||
bbbcbc0a92 | |||
8f9cbcedc8 | |||
6236d906b6 | |||
29410ca276 | |||
2f9e1eb5c8 | |||
0b9c43cfed | |||
950b0dbf4d | |||
a44a4ab69b | |||
e5ecc2aacf | |||
959ec58ec2 | |||
4220ae5bdc | |||
1a6df18e8b | |||
|
c223c89045 |
19
.drone.yml
@ -7,9 +7,15 @@ concurrency:
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
metadata:
|
||||
namespace: git
|
||||
steps:
|
||||
- name: Fetch tags
|
||||
image: alpine/git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- name: Build site
|
||||
image: node:current-bullseye
|
||||
volumes:
|
||||
@ -72,6 +78,8 @@ concurrency:
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
metadata:
|
||||
namespace: git
|
||||
steps:
|
||||
@ -95,8 +103,11 @@ steps:
|
||||
- npx @crowdin/cli pull -b main -T $CTOKEN
|
||||
- yarn prettier --write .
|
||||
- git add .
|
||||
- 'git commit -a -m "chore: Update translations"'
|
||||
- git push -u origin main
|
||||
- >
|
||||
if output=$(git status --porcelain) && [ -n "$output" ]; then
|
||||
git commit -a -m "chore: Update translations"
|
||||
git push -u origin main
|
||||
fi
|
||||
volumes:
|
||||
- name: cache
|
||||
claim:
|
||||
@ -113,6 +124,10 @@ trigger:
|
||||
metadata:
|
||||
namespace: git
|
||||
steps:
|
||||
- name: Fetch tags
|
||||
image: alpine/git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- name: Build site
|
||||
image: node:current-bullseye
|
||||
volumes:
|
||||
|
78
.github/workflows/release.yml
vendored
@ -44,7 +44,77 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tagName: ${{ github.ref_name }}
|
||||
releaseName: "Snort v__VERSION__"
|
||||
releaseBody: "See the assets to download and install this version."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
app:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
|
||||
- name: Cache gradle
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build Site
|
||||
run: yarn build
|
||||
|
||||
- name: Copy files
|
||||
run: |-
|
||||
git clone --depth 1 --branch ${{ github.ref_name }} https://git.v0l.io/Kieran/snort_android.git
|
||||
mkdir -p snort_android/app/src/main/assets/
|
||||
cp -r packages/app/build/* snort_android/app/src/main/assets/
|
||||
|
||||
- name: Build AAB
|
||||
working-directory: snort_android
|
||||
run: ./gradlew clean bundleRelease --stacktrace
|
||||
- name: Build APK
|
||||
working-directory: snort_android
|
||||
run: ./gradlew assembleRelease --stacktrace
|
||||
|
||||
- name: Sign AAB
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: snort_android/app/build/outputs/bundle/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.KEY_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Sign APK
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: snort_android/app/build/outputs/apk/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.KEY_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
- name: Rename files
|
||||
run: |-
|
||||
mkdir -p snort_android/app/release
|
||||
mv snort_android/app/build/outputs/bundle/release/app-release.aab snort_android/app/release/snort-${{ github.ref_name }}.aab
|
||||
mv snort_android/app/build/outputs/apk/release/app-universal-release-unsigned-signed.apk snort_android/app/release/snort-universal-${{ github.ref_name }}.apk
|
||||
mv snort_android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned-signed.apk snort_android/app/release/snort-arm64-v8a-${{ github.ref_name }}.apk
|
||||
mv snort_android/app/build/outputs/apk/release/app-x86_64-release-unsigned-signed.apk snort_android/app/release/snort-x86_64-${{ github.ref_name }}.apk
|
||||
mv snort_android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned-signed.apk snort_android/app/release/snort-armeabi-v7a-${{ github.ref_name }}.apk
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
snort_android/app/release/snort-${{ github.ref_name }}.aab
|
||||
snort_android/app/release/snort-universal-${{ github.ref_name }}.apk
|
||||
snort_android/app/release/snort-arm64-v8a-${{ github.ref_name }}.apk
|
||||
snort_android/app/release/snort-x86_64-${{ github.ref_name }}.apk
|
||||
snort_android/app/release/snort-armeabi-v7a-${{ github.ref_name }}.apk
|
@ -4,4 +4,5 @@ build/
|
||||
.github/
|
||||
transifex.yml
|
||||
dist/
|
||||
src-tauri/
|
||||
src-tauri/
|
||||
target/
|
3
.vscode/extensions.json
vendored
@ -1,6 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs",
|
||||
"dbaeumer.vscode-eslint"
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
|
2
.vscode/settings.json
vendored
@ -15,5 +15,5 @@
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"eslint.nodePath": ".yarn/sdks",
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.js"
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
|
||||
}
|
||||
|
20
.yarn/sdks/eslint/lib/unsupported-api.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require eslint/use-at-your-own-risk
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real eslint/use-at-your-own-risk your application uses
|
||||
module.exports = absRequire(`eslint/use-at-your-own-risk`);
|
12
.yarn/sdks/eslint/package.json
vendored
@ -1,6 +1,14 @@
|
||||
{
|
||||
"name": "eslint",
|
||||
"version": "8.44.0-sdk",
|
||||
"version": "8.53.0-sdk",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
"type": "commonjs",
|
||||
"bin": {
|
||||
"eslint": "./bin/eslint.js"
|
||||
},
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": "./lib/api.js",
|
||||
"./use-at-your-own-risk": "./lib/unsupported-api.js"
|
||||
}
|
||||
}
|
||||
|
20
.yarn/sdks/prettier/bin/prettier.cjs
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real prettier/bin/prettier.cjs your application uses
|
||||
module.exports = absRequire(`prettier/bin/prettier.cjs`);
|
20
.yarn/sdks/prettier/index.cjs
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real prettier your application uses
|
||||
module.exports = absRequire(`prettier`);
|
7
.yarn/sdks/prettier/package.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.1.0-sdk",
|
||||
"main": "./index.cjs",
|
||||
"type": "commonjs",
|
||||
"bin": "./bin/prettier.cjs"
|
||||
}
|
6
.yarn/sdks/typescript/lib/typescript.js
vendored
@ -11,10 +11,10 @@ const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/typescript.js
|
||||
// Setup the environment to be able to require typescript
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/typescript.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/typescript.js`);
|
||||
// Defer to the real typescript your application uses
|
||||
module.exports = absRequire(`typescript`);
|
||||
|
8
.yarn/sdks/typescript/package.json
vendored
@ -1,6 +1,10 @@
|
||||
{
|
||||
"name": "typescript",
|
||||
"version": "5.1.6-sdk",
|
||||
"version": "5.2.2-sdk",
|
||||
"main": "./lib/typescript.js",
|
||||
"type": "commonjs"
|
||||
"type": "commonjs",
|
||||
"bin": {
|
||||
"tsc": "./bin/tsc",
|
||||
"tsserver": "./bin/tsserver"
|
||||
}
|
||||
}
|
||||
|
14
Dockerfile
@ -1,14 +1,12 @@
|
||||
FROM node:16 as build
|
||||
FROM node:19 as build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json yarn.lock .
|
||||
COPY packages/app/package.json packages/app/
|
||||
COPY packages/nostr/package.json packages/nostr/
|
||||
RUN yarn install --network-timeout 1000000
|
||||
|
||||
COPY . .
|
||||
COPY package.json yarn.lock .yarnrc.yml .
|
||||
COPY .yarn .yarn
|
||||
COPY packages packages
|
||||
RUN yarn --network-timeout 1000000
|
||||
RUN yarn build
|
||||
|
||||
FROM nginx:mainline-alpine
|
||||
FROM nginxinc/nginx-unprivileged:mainline-alpine
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/packages/app/build /usr/share/nginx/html
|
||||
|
@ -1,3 +1,3 @@
|
||||
FROM nginx:mainline-alpine
|
||||
FROM nginxinc/nginx-unprivileged:mainline-alpine
|
||||
COPY packages/app/build /usr/share/nginx/html
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
21
README.md
@ -15,21 +15,21 @@ Snort supports the following NIP's:
|
||||
- [x] NIP-09: Event Deletion
|
||||
- [x] NIP-10: Conventions for clients' use of `e` and `p` tags in text events
|
||||
- [x] NIP-11: Relay Information Document
|
||||
- [x] NIP-12: Generic Tag Queries
|
||||
- [x] NIP-13: Proof of Work
|
||||
- [ ] NIP-14: Subject tag in text events
|
||||
- [x] NIP-15: End of Stored Events Notice
|
||||
- [x] NIP-18: Reposts
|
||||
- [x] NIP-19: bech32-encoded entities
|
||||
- [x] NIP-20: Command Results
|
||||
- [x] NIP-21: `nostr:` Protocol handler (`web+nostr`)
|
||||
- [x] NIP-23: Long form content
|
||||
- [x] NIP-25: Reactions
|
||||
- [x] NIP-26: Delegated Event Signing (Display delegated signings only)
|
||||
- [x] NIP-27: Text note references
|
||||
- [ ] NIP-28: Public Chat
|
||||
- [x] NIP-28: Public Chat
|
||||
- [x] NIP-30: Custom Emoji
|
||||
- [x] NIP-31: Alt tag for unknown events
|
||||
- [x] NIP-36: Sensitive Content
|
||||
- [x] NIP-38: User Statuses
|
||||
- [ ] NIP-39: External Identities
|
||||
- [ ] NIP-40: Expiration Timestamp
|
||||
- [x] NIP-42: Authentication of clients to relays
|
||||
- [x] NIP-44: Versioned encryption
|
||||
@ -38,11 +38,15 @@ Snort supports the following NIP's:
|
||||
- [x] NIP-50: Search
|
||||
- [x] NIP-51: Lists
|
||||
- [x] NIP-53: Live Events
|
||||
- [x] NIP-57: Zaps
|
||||
- [x] NIP-58: Badges
|
||||
- [x] NIP-59: Gift Wrap
|
||||
- [x] NIP-65: Relay List Metadata
|
||||
- [ ] NIP-78: App specific data
|
||||
- [x] NIP-94: File header
|
||||
- [x] NIP-75: Zap Goals
|
||||
- [x] NIP-78: App specific data
|
||||
- [ ] NIP-89: App handlers
|
||||
- [x] NIP-94: File Metadata
|
||||
- [x] NIP-96: HTTP File Storage Integration (Draft)
|
||||
- [x] NIP-98: HTTP Auth
|
||||
|
||||
### Running
|
||||
@ -55,7 +59,7 @@ To run the application, use
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
To build the application and nostr package, use
|
||||
To build the application and system packages, use
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
@ -70,8 +74,7 @@ Translations are managed on [Crowdin](https://crowdin.com/project/snort)
|
||||
To extract translations run:
|
||||
|
||||
```bash
|
||||
yarn workspace @snort/app intl-extract
|
||||
yarn workspace @snort/app intl-compile
|
||||
yarn pre:commit
|
||||
```
|
||||
|
||||
This will create the source file `packages/app/src/translations/en.json`
|
||||
|
@ -1,5 +1,5 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
19
package.json
@ -4,23 +4,24 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build",
|
||||
"start": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app start",
|
||||
"test": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20230307.0",
|
||||
"@tauri-apps/cli": "^1.2.3",
|
||||
"prettier": "^3.0.0"
|
||||
"build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-web build && yarn workspace @snort/system-react build && yarn workspace @snort/app build",
|
||||
"start": "yarn build && yarn workspace @snort/app start",
|
||||
"test": "yarn build && yarn workspace @snort/app test && yarn workspace @snort/system test",
|
||||
"pre:commit": "yarn workspace @snort/app intl-extract && yarn workspace @snort/app intl-compile && yarn prettier --write .",
|
||||
"push-prod": "git checkout snort-prod && git merge --ff-only main && git push && git checkout main"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 120,
|
||||
"bracketSameLine": true,
|
||||
"arrowParens": "avoid"
|
||||
"arrowParens": "avoid",
|
||||
"trailingComma": "all"
|
||||
},
|
||||
"packageManager": "yarn@3.6.3",
|
||||
"dependencies": {
|
||||
"@cloudflare/workers-types": "^4.20230307.0",
|
||||
"@tauri-apps/cli": "^1.2.3",
|
||||
"eslint": "^8.48.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,15 @@
|
||||
module.exports = {
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
plugins: ["@typescript-eslint", "formatjs"],
|
||||
rules: {
|
||||
"formatjs/enforce-id": [
|
||||
"error",
|
||||
{
|
||||
idInterpolationPattern: "[sha512:contenthash:base64:6]",
|
||||
},
|
||||
],
|
||||
},
|
||||
root: true,
|
||||
ignorePatterns: ["build/", "*.test.ts", "*.js"],
|
||||
env: {
|
||||
|
1
packages/app/.gitignore
vendored
@ -25,3 +25,4 @@ yarn-error.log*
|
||||
.idea
|
||||
|
||||
dist/
|
||||
dev-dist/
|
47
packages/app/.yarn/.cache/webpack-dev-server/server.pem
Normal file
@ -0,0 +1,47 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEApyUVkYJVwV7XgluUnllgCtrsdq1ctRICm5gQy8nd+aEdDQjA
|
||||
CKPOWh5miLl/fAQVZGZy/JxavzXulwXo8238E6n6bmNB1Us2nuw7a0aW4iUSQ1Pt
|
||||
P4ZhPpcrqeqMf+hp7iBW0nAHFy/aa2UR84d7tBmSk5J3NNrfBsZdUex/7FqF1EVv
|
||||
mEzlc8kepU9lRXWFQDtZCllEZ1kY3SBJPm10h0g9saI8YIVRxUuNII5GHDYAE3hb
|
||||
EmoY6fuSEoiXA8u0Yt9soBQxgxIhQVKSRPPoIPjGFOxsGHY6h8R9nx1kxhHKFRuV
|
||||
nwsn0uWl/7yjhwyHanogJu73/WgelPcgP/hMDQIDAQABAoIBAAru+xU0oGVwzcoi
|
||||
MXuWPxkWrwcoWfsiPXduIBMklleg+WSD4QPvqyzr9isVb0huf/O8W+M4WxtM7NmG
|
||||
MnHSDP5ATThxV7obHGyS6WQgDvimEibDU66nHK9adim8RQqM6nkANo23dE9I+xGx
|
||||
X9Y9U5M5ZQQwPYoAkzw/N5WHUerk+cSEYWYV8jDtO7wJhYOMu5qliPeuNOaWZ1W6
|
||||
1uwr8A4ih69WwzugPuBSgBrPAW1c84zWIFN+njAugqPF5x8xp2uM3tUO9s5UlHJC
|
||||
FWEuU40KcDT2utSUY+2HXSHbycF4KLKT5jAKSa4sPziLfo+YifrlN0Y3rhofUlZT
|
||||
jCaeZ8ECgYEA5/xpk8aVhCEvv5iCghv0p/IHcjdXjx5+PCWh3Adx0fF91UvU5oqn
|
||||
okdyYZDShZMuLDfJ0lG+OMKZd01JapnbTtiVNceVRMnraIdoWEM2/4bTXTSZGtdA
|
||||
8gh/Kc/PMbPf5ppVWwqTCbUkPOSyGHyGc7+DQquq1w6yZu04A3x9vHECgYEAuHJk
|
||||
uz8YKY5ZUR7CZ3y7YFuwq5Lcpl43AfiiCasjRch0P8yLrITc/6fORsXyy64XW9fC
|
||||
h3YmXvEPaM03W2dxw2aQDvXEvXiEITzmILs7SE3UmZR9m7OMy7Jeqr3+JOc0ckZe
|
||||
Rz5FfuMt1IvNB6lrpfHVtoVrpCOXpzHgC/k/x10CgYA6lU18GfwL/+107uiWPsUL
|
||||
3FzxBPTBmau7OK2lSOP/ZoKmaJ39Eiq/GlfSN6ZSQRa55+S5jhcBcnMa45OUrgHp
|
||||
6VvU1u/lDTC7luZM07yBzuR1dyDq3Ez0Uhz6zBXAsXHrZDIF6ae0HeBm2EH5WQkD
|
||||
Fevp3DwqTvXSdDle+AMwoQKBgQCBSlaH1rNmNc0wCsK07f8ejUcrDZgz2mjurc1P
|
||||
v7HK8bdjHUtvE/ciEguLGqiV06O2EmjesZg2Bv4JNYivPrTFBrjGc8qEEd10uw6J
|
||||
NRVaGoyDV04w/UwdYRvwzZs/XP4reF4PzHvEdRSkH5cJ3t2BhiKLfby1YumkHlbx
|
||||
rbbiVQKBgB02jyZUiB6pPTCP8vXZCJbZELgqNyS04ALhBBpdfGMcU1+0hRLJFBaE
|
||||
tClJPGARFXl+MPkY032vmJZOuH3LrcTCm8DmMLzM/hT1EWawQ8BJkkwiIokE4lqc
|
||||
Bi8CrkvuQs2cuCStK6C3Nkyr1lTkDge46trsb7KTcfHdtLsS7EPj
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkOgAwIBAgIJDji8iiceMvQlMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
BAMTCWxvY2FsaG9zdDAeFw0yMzEwMTYwOTI0MThaFw0yMzExMTUxMDI0MThaMBQx
|
||||
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||
ggEBAKclFZGCVcFe14JblJ5ZYAra7HatXLUSApuYEMvJ3fmhHQ0IwAijzloeZoi5
|
||||
f3wEFWRmcvycWr817pcF6PNt/BOp+m5jQdVLNp7sO2tGluIlEkNT7T+GYT6XK6nq
|
||||
jH/oae4gVtJwBxcv2mtlEfOHe7QZkpOSdzTa3wbGXVHsf+xahdRFb5hM5XPJHqVP
|
||||
ZUV1hUA7WQpZRGdZGN0gST5tdIdIPbGiPGCFUcVLjSCORhw2ABN4WxJqGOn7khKI
|
||||
lwPLtGLfbKAUMYMSIUFSkkTz6CD4xhTsbBh2OofEfZ8dZMYRyhUblZ8LJ9Llpf+8
|
||||
o4cMh2p6ICbu9/1oHpT3ID/4TA0CAwEAAaOBrzCBrDAMBgNVHRMEBTADAQH/MAsG
|
||||
A1UdDwQEAwIC9DAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUF
|
||||
BwMDBggrBgEFBQcDCDBcBgNVHREEVTBTgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5s
|
||||
b2NhbGRvbWFpboIGbHZoLm1lgggqLmx2aC5tZYIFWzo6MV2HBH8AAAGHEP6AAAAA
|
||||
AAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBABY0rgWuzLYvVtvoVvWKS9cg
|
||||
8rVhBRIFvpYO814ocN1iaxYQ9t9uLRsJXj0K+z1BHWf0zBiw4mB3dD9VpiKpuliL
|
||||
4tRT+vATA96OYCd9G5k7DFQascAau40H3jxckh9rimIWa45FUSd7FIcddo1jeciv
|
||||
gdAdiNUuHBen82O8KHJb+1PCBdA8RYeO5EGKfJM2yrOovu7dAFilf1ZPkXWgXnfG
|
||||
nN6YfDDo9rAVDbvNXImrkwmGqEcN3Pq909IHiM/VETlU5lP4AbTNgrDa/aaZ+I+b
|
||||
1MC1p87MvnibyXs+rTlK5+j8E6noNcD7tsHNd4ufkVCqr+pvSpuA3OvnXjbbm54=
|
||||
-----END CERTIFICATE-----
|
748
packages/app/CHANGELOG.md
Normal file
@ -0,0 +1,748 @@
|
||||
# v0.1.23
|
||||
|
||||
## Added
|
||||
|
||||
- DeepL translate api (Automatic for PRO subscribers)
|
||||
- Add nostr:nprofile1qqsydl97xpj74udw0qg5vkfyujyjxd3l706jd0t0w0turp93d0vvungfgfewr to contributors
|
||||
- Proxy LN address type enabled on Nostr Address settings pages
|
||||
- Infinite scrol on notifications page
|
||||
- Default 0.5% ZapPool rate for Snort donation address
|
||||
- Collect relay metrics in `@snort/system` for better relay selection algo in Outbox Model (NIP-65)
|
||||
- New sign up / login flow!
|
||||
- Topics / Mute words on sign up for easier onboarding
|
||||
- Drag & Drop for uploads on note creator - nostr:nprofile1qqs8tchhwf5smv3r2g0vkswz58c837uu456x59m3dh380gtrhqzydeqz4wlka
|
||||
- Mixin topics (hashtags) into timeline feed
|
||||
- Language specific trending posts
|
||||
- Show following info for hashtags
|
||||
- Sync preferences to network (`NIP-78` support)
|
||||
- Trending hashtags page
|
||||
- Note creator hashtag input
|
||||
- Top trending hashtags on note creator
|
||||
- Social Graph - nostr:nprofile1qqsy2ga7trfetvd3j65m3jptqw9k39wtq2mg85xz2w542p5dhg06e5qpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uh8am0r
|
||||
- New users relay list based off "close" relays
|
||||
- `NIP-96` support for nostr native image/file uploaders
|
||||
- Write replies/reactions to `p` tagged users read relays (Outbox model)
|
||||
- Sync joined public chats (`NIP-28`) using `PublicChatList` kind `10_005`
|
||||
|
||||
## Changed
|
||||
|
||||
- Read/Write relays only on kind `10_002` (NIP-65)
|
||||
- Removed `nostr.watch` code for adding new users to random relays
|
||||
- Render kind `10_002` on profile relays tab
|
||||
- `@snort/system` using eventemitter3 for triggering events
|
||||
- Use latest `NIP-51` spec (Bookmarks/Interests/`NIP-28` PublicChatList)
|
||||
- `nreq` support (Demo)
|
||||
- Write profile/relays to blasters
|
||||
- `@snort/system` automated outbox model (automatic fetching of relay metadata)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Upgrade ephermal connection to non-ephemeral
|
||||
- Remove relay tag from zaps (Some zap services dont support it)
|
||||
- Fix zap parsing for goals
|
||||
- Remove extra chars from quoted events to fix loading (`'s` etc)
|
||||
- CSS Fixes for profile card on light theme
|
||||
- Zap counting on replacable events
|
||||
- `NIP-28` chats loading
|
||||
- Overflowing modal UI
|
||||
- Live stream widget layout with long titles
|
||||
- Notifications marker has returned from its long slumber
|
||||
|
||||
---
|
||||
|
||||
# v0.1.22
|
||||
|
||||
## Fixes
|
||||
|
||||
- Note creator too wide on mobile
|
||||
- Sending notes dialog duplicated when replying
|
||||
|
||||
---
|
||||
|
||||
# v0.1.21
|
||||
|
||||
## Added
|
||||
|
||||
- Add gradients to iris.to domain
|
||||
- Render referenced kind-3 (ContactList) as pubkey list
|
||||
- List feed page renders the posts of a given list `/list-feed/{naddr-of-nip51-list-or-nevent-of-kind3}`
|
||||
- Respond to AUTH when expected (Requesting DM's/GiftWrap)
|
||||
- Show reply counts on threads
|
||||
- Quote Repost
|
||||
- Signature checks can be enabled in preferences
|
||||
- NIP-98 auth for void.cat / nostr.build file uploaders
|
||||
- Add `E` tag for direct replies
|
||||
- File upload progress bar (void.cat only)
|
||||
- Long form modal for deck layout (WIP still)
|
||||
- Video thumbnails using ImgProxy
|
||||
- Renew subscriptions for X months
|
||||
- Tailwind CSS migration @mmalmi
|
||||
- Seasonal features
|
||||
- Profile cards on hover for mentions
|
||||
- Dropdown search results on search bar @mmalmi
|
||||
- Renew subscription task on task list
|
||||
|
||||
## Changed
|
||||
|
||||
- Disable highligher.js code blocks (for now)
|
||||
- Removed "Popular Accounts" from new user flow, replaced with "Snort Devs" only
|
||||
- Moved "Show Preview" on note creator to preview toggle switch
|
||||
- Premium subscription renamed to PRO
|
||||
- Limit images in posts to 800px high
|
||||
- Nostrplebs colors removed
|
||||
|
||||
## Fixed
|
||||
|
||||
- Use correct hostname when submitting analytics
|
||||
- Disable WASM when not supported on device
|
||||
- Typo on "Nostr Address" in account settings
|
||||
- Hide expired user status on profiles
|
||||
- Hide muted dm chats
|
||||
- Hide blocked replies
|
||||
|
||||
---
|
||||
|
||||
# v0.1.20
|
||||
|
||||
## Added
|
||||
|
||||
- Highlight text in search results - @fernandoporazzi
|
||||
- Iris/Snort build configuration - @mmalmi
|
||||
- Iris free NIP-05 on Profile page - @mmalmi
|
||||
- Image galleries on posts - @fernandoporazzi
|
||||
- Close modal with ESC - @mmalmi
|
||||
- Navigate image spotlight with LR direction keys - @mmalmi
|
||||
- Spotlight preview profile/banner on click - @mmalmi
|
||||
- Fetch profiles from HTTP cache (Iris) - @mmalmi
|
||||
- Animal names for empty profile accounts (Iris) - @mmalmi
|
||||
- Redirect to NIP-05 short links for iris/snort accounts - @mmalmi
|
||||
- Code block highlighting - @fernandoporazzi
|
||||
- Notification summary graph - @Kieran
|
||||
- Profile hover cards - @Kieran
|
||||
- Keyboard shortcuts for new post/focus/search - @fernandoporazzi
|
||||
- Markdown rendering for long form content - @Kieran
|
||||
- Show relay response when publishing - @Kieran
|
||||
|
||||
## Fixed
|
||||
|
||||
- Copy buttons on insecure context - @Kieran
|
||||
|
||||
---
|
||||
|
||||
# v0.1.19
|
||||
|
||||
## Added
|
||||
|
||||
- Highlight search results
|
||||
|
||||
## Fixes
|
||||
|
||||
- Copy to clipboard on insecure context (Umbrel)
|
||||
|
||||
---
|
||||
|
||||
# v0.1.16
|
||||
|
||||
## Fixes
|
||||
|
||||
- Login bugs
|
||||
|
||||
---
|
||||
|
||||
# v0.1.15
|
||||
|
||||
## Added
|
||||
|
||||
- User status on profile pages (Music only [NIP-38])
|
||||
- Following mark on avatars, if you follow the pubkey you will see a green tick on their avatar
|
||||
- Pin encryption, encrypted private key storage for nsec login
|
||||
- Pubkey (readonly) logins hide buttons which cannot be used (reactions, reply, save profiles, dms etc)
|
||||
- Muted words feature (phase 1)
|
||||
- NIP-28 public chats
|
||||
|
||||
## Changed
|
||||
|
||||
- Styles changes for Content warnings
|
||||
- Live stream embed styles
|
||||
- Cashu token embed styles
|
||||
- Snort Deck thread navigation in modal from timeline
|
||||
- PoW miner moved to WASM module for faster hashing
|
||||
|
||||
## Fixed
|
||||
|
||||
- Profile link to dms
|
||||
- Long form content loading and replies
|
||||
- Search function restored
|
||||
|
||||
---
|
||||
|
||||
# v0.1.14
|
||||
|
||||
## Added
|
||||
|
||||
- Timeline cache: faster page loads and much lower data usage
|
||||
- WASM module: Some code moved to Rust WASM module for faster execution
|
||||
- Zap Splits: NIP-57.G
|
||||
- New Languages:
|
||||
- Finnish
|
||||
- Dutch
|
||||
- Portuguese Brazilian
|
||||
|
||||
## Changed
|
||||
|
||||
- Count polls by pubkey
|
||||
|
||||
---
|
||||
|
||||
# v0.1.13
|
||||
|
||||
# Added
|
||||
|
||||
- Snort V2 Design
|
||||
- NIP-24 Encrypted secret chats (nsec login only)
|
||||
- NIP-13 Proof of Work (POW)
|
||||
- NIP-31 Alt tag spec for unknown event kinds
|
||||
- Render mentioned zap goals (Kind 9041)
|
||||
- Embed fonts in src (No more google fonts requests)
|
||||
- Native key storage for Android app (`Nip7os` interface)
|
||||
- Swahili translations
|
||||
- Thai translations
|
||||
|
||||
# Changed
|
||||
|
||||
- PWA pre-cache setup (Faster PWA loading)
|
||||
- Show note creator button on profile pages
|
||||
|
||||
# Fixed
|
||||
|
||||
- Umlauts in urls
|
||||
- Reject events which don't match request filter
|
||||
|
||||
---
|
||||
|
||||
# v0.1.12
|
||||
|
||||
# Added
|
||||
|
||||
- nsecBunker support (connection string `bunker://<pubkey>?relay=wss://realy.com[#token]`)
|
||||
|
||||
# Changed
|
||||
|
||||
- New snort logo by Bitko
|
||||
- Infinite scroll changed to manual action (temperarily to fix performance issues)
|
||||
|
||||
# Fixed
|
||||
|
||||
- Note to self containing all DMS
|
||||
- Media spotlight disabled for poll options containing images
|
||||
- Badge image sizes oversize when bypassing imgproxy due to loading error
|
||||
|
||||
---
|
||||
|
||||
# v0.1.11
|
||||
|
||||
## Added
|
||||
|
||||
- `@snort/system` package
|
||||
- `@snort/system-react` package
|
||||
- Live streaming page (NIP-102)
|
||||
- Chat system refactor (adding new chat systems much easier now, NIP-29 first candidate)
|
||||
- NIP-29 simple group chat support
|
||||
|
||||
## Fixed
|
||||
|
||||
- Profile links with incorrect hrp fixed in some places
|
||||
- `naddr` event loading fixed
|
||||
- Relay specific requests fixed (Global tab / Search page)
|
||||
- NWC connection responds to AUTH requests now
|
||||
|
||||
https://git.v0l.io/Kieran/snort/compare/v0.1.10...v0.1.11
|
||||
|
||||
---
|
||||
|
||||
# v0.1.10
|
||||
|
||||
## Added
|
||||
|
||||
- Gossip model, query follows write relays for events
|
||||
- @snort/system NPM package containing Snort core nostr code
|
||||
- NIP-44 Encryption scheme support
|
||||
- NIP-59 Gift Wrap support
|
||||
|
||||
## Fixed
|
||||
|
||||
- Unmarked thread events replies out of order
|
||||
|
||||
https://git.v0l.io/Kieran/snort/compare/v0.1.9...v0.1.10
|
||||
|
||||
---
|
||||
|
||||
# v0.1.9
|
||||
|
||||
## Added
|
||||
|
||||
- Discover tab, shows trending users/posts from nostr.build
|
||||
- New DM styles
|
||||
- Mentioned Zapstr tracks are previewed on Snort with player
|
||||
- Custom emoji rendering in posts (NIP-30)
|
||||
- Lanaguage selector on new user flow
|
||||
- ZapPool, support nostr ecosystem by donating a percentage of your zaps
|
||||
- Alby NWC link added to NWC connect page
|
||||
- SemisolDev follow recommendations on Discover tab
|
||||
- Pubkey lists (NIP-51) render inline when mentioned in notes
|
||||
- Persian language
|
||||
- OpenGraph Image/Video media rendered inside link preview box
|
||||
- Option to zap everybody on mentioned pubkey list
|
||||
- L402 support for inline media (paywall content)
|
||||
|
||||
## Changed
|
||||
|
||||
- Error page shows actual error message now, also a button to reset app cache
|
||||
- Massivly improved profile loading
|
||||
- Improved JS bundle size by ejecting CRA and using dynamic modules
|
||||
- Switched to `@void-cat/api` package for void.cat uploads
|
||||
|
||||
---
|
||||
|
||||
# v0.1.8
|
||||
|
||||
## Added
|
||||
|
||||
- Tamil Language support
|
||||
- Quoted notes are rendered embedded
|
||||
- Multi-account support for subscribers
|
||||
- Zapper key loading processing in background to speed up profile loading
|
||||
- Export keys page added to settings
|
||||
- NIP-94 support for rendering quoted file metadata events
|
||||
- Interactions cache (zaps/likes/reports) for better UX
|
||||
- Full screen image/video previews in modal
|
||||
- Re-broadcast own events dialog
|
||||
- Nostr wallet connect support
|
||||
- Cashu token parsing preview with redeem link
|
||||
- Trending notes/people tabs added to search page
|
||||
|
||||
## Changed
|
||||
|
||||
- Profile page loads only 200 latest notes, improving profile load times for accounts with less activity
|
||||
- New user flow has been tweaked to be shorter with NIP5 & Twitter import steps removed
|
||||
|
||||
## Fixed
|
||||
|
||||
- Thread navigation without page reload
|
||||
- NIP-42 functionality restored
|
||||
- `a` tagged kind 1 replies render properly under root event
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.7...v0.1.8
|
||||
|
||||
---
|
||||
|
||||
# v0.1.7
|
||||
|
||||
## Added
|
||||
|
||||
- Per event zap targets by @v0l
|
||||
- Content warning (NIP-36) support by @v0l
|
||||
- Polls (NIP-69) by @v0l
|
||||
- Snort subscriptions by @v0l
|
||||
- NIP-94 File header support by @v0l
|
||||
- Link previews by @ghobs91 & @v0l
|
||||
- Cmd+Enter to post note by @v0l
|
||||
- `nostr:` links (NIP-27) by @v0l
|
||||
- Tending users on Search page by @ghobs91 & @v0l
|
||||
|
||||
## Changed
|
||||
|
||||
- Paste image upload by @vivganes
|
||||
- Note creator note preview by @v0l
|
||||
- Login private key input masking by @vivganes
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fix note creator closing on thread when new replies load by @SamSamskies
|
||||
- Follow hashtag tab highlighting by @SamSamskies
|
||||
- Language dropdown defaults to Arabic by @vivganes
|
||||
- Bookmarks showing reactions by @vivganes
|
||||
- Single zapper on note only shows name by @vivganes
|
||||
- Broken link previews show empty box by @vivganes
|
||||
- Render jfif images by @v0l
|
||||
|
||||
## PR List
|
||||
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/476
|
||||
- `nostr` package: implement NIP-05 by @sistemd in https://github.com/v0l/snort/pull/474
|
||||
- `nostr` package: NIP-09 event deletion by @sistemd in https://github.com/v0l/snort/pull/478
|
||||
- fix #484 by @vivganes in https://github.com/v0l/snort/pull/486
|
||||
- fix #485 by @vivganes in https://github.com/v0l/snort/pull/487
|
||||
- Per event zap targets by @v0l in https://github.com/v0l/snort/pull/466
|
||||
- feat: nip-36 by @v0l in https://github.com/v0l/snort/pull/497
|
||||
- fix #496 by @vivganes in https://github.com/v0l/snort/pull/498
|
||||
- use redux for NoteCreator state management by @SamSamskies in https://github.com/v0l/snort/pull/494
|
||||
- fix #495 by @vivganes in https://github.com/v0l/snort/pull/499
|
||||
- Polls (NIP-69) by @v0l in https://github.com/v0l/snort/pull/489
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/483
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/508
|
||||
- add ability to paste image from clipboard by @vivganes in https://github.com/v0l/snort/pull/510
|
||||
- Subscriptions by @v0l in https://github.com/v0l/snort/pull/506
|
||||
- feat: multi-account system by @v0l in https://github.com/v0l/snort/pull/514
|
||||
- fix followed tag active tab highlighting by @SamSamskies in https://github.com/v0l/snort/pull/516
|
||||
- NIP-94 file headers by @v0l in https://github.com/v0l/snort/pull/488
|
||||
- fix #517 by @vivganes in https://github.com/v0l/snort/pull/518
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/511
|
||||
- `nostr` package: get tests passing in the browser by @sistemd in https://github.com/v0l/snort/pull/490
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/519
|
||||
- Subscription handle by @v0l in https://github.com/v0l/snort/pull/522
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @vivganes made their first contribution in https://github.com/v0l/snort/pull/486
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.6...v0.1.7
|
||||
|
||||
---
|
||||
|
||||
# v0.1.6
|
||||
|
||||
## 🏷️ Summary
|
||||
|
||||
- Snort NIP5 management page (for transfers to new pubkeys)
|
||||
- Short links for Snort NIP5 owners (ie. https://snort.social/kieran)
|
||||
|
||||
## Other Changes
|
||||
|
||||
- Update Wavlake embed to support .com links by @blastshielddown in https://github.com/v0l/snort/pull/469
|
||||
- Bug fixes for save profile & relay connection on clean browser
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.5...v0.1.6
|
||||
|
||||
---
|
||||
|
||||
# v0.1.5
|
||||
|
||||
## 🏷️ Short Summary
|
||||
|
||||
- Completely rebuilt "core" subscription management system
|
||||
- Option to rewrite Twitter links to Nitter links
|
||||
- Tarui app setup, Mac/Windows/Linux desktop apps (coming soon)
|
||||
- OpenGraph tagging for profiles and events (Only for https://snort.social)
|
||||
- NIP-27 `nostr:` link parsing
|
||||
- Global tab full relay names
|
||||
|
||||
## What's Changed
|
||||
|
||||
- `nostr` package: add direct messages by @sistemd in https://github.com/v0l/snort/pull/399
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/445
|
||||
- Display search property alongside host in relay name by @h3y6e in https://github.com/v0l/snort/pull/452
|
||||
- Shorten long relay name by @h3y6e in https://github.com/v0l/snort/pull/455
|
||||
- Nostr links by @v0l in https://github.com/v0l/snort/pull/461
|
||||
- `nostr` package: vastly simplify the API by @sistemd in https://github.com/v0l/snort/pull/412
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/453
|
||||
- Fix: invisible <option> text in dark theme by @jiftechnify in https://github.com/v0l/snort/pull/454
|
||||
- add setting for rewriting twitter links to nitter by @w3irdrobot in https://github.com/v0l/snort/pull/459
|
||||
- RequestBuilder / Core Refactor by @v0l in https://github.com/v0l/snort/pull/326
|
||||
- Tauri setup by @v0l in https://github.com/v0l/snort/pull/462
|
||||
- Prevents adding ws relay when over https by @ivanacostarubio in https://github.com/v0l/snort/pull/463
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/465
|
||||
- OpenGraph tag injection by @v0l in https://github.com/v0l/snort/pull/470
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @jiftechnify made their first contribution in https://github.com/v0l/snort/pull/454
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.4...v0.1.5
|
||||
|
||||
---
|
||||
|
||||
# v0.1.4
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.3...v0.1.4
|
||||
|
||||
---
|
||||
|
||||
# v0.1.3
|
||||
|
||||
## What's Changed
|
||||
|
||||
- only replace note ID when note ID starts with `@` character by @SamSamskies in https://github.com/v0l/snort/pull/441
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.2...v0.1.3
|
||||
|
||||
---
|
||||
|
||||
# v0.1.2
|
||||
|
||||
## What's Changed
|
||||
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/309
|
||||
- UI bugs by @verbiricha in https://github.com/v0l/snort/pull/301
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/311
|
||||
- Add build command to readme by @joshr4 in https://github.com/v0l/snort/pull/300
|
||||
- fix(fotter-actions): add highlighting and min-width by @fernandolguevara in https://github.com/v0l/snort/pull/312
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/313
|
||||
- `nostr` package part 1 by @fcked in https://github.com/v0l/snort/pull/315
|
||||
- Reduce space between the texts for selecting relays by @h3y6e in https://github.com/v0l/snort/pull/316
|
||||
- fix(profile): convert page id to npub bech32 by @fernandolguevara in https://github.com/v0l/snort/pull/322
|
||||
- Improve overflow menu button by @joshr4 in https://github.com/v0l/snort/pull/304
|
||||
- German translations for snort by @gandlafbtc in https://github.com/v0l/snort/pull/323
|
||||
- fix: send all relays when zapping by @verbiricha in https://github.com/v0l/snort/pull/324
|
||||
- Add default page selector by @jacany in https://github.com/v0l/snort/pull/321
|
||||
- UI fixes by @verbiricha in https://github.com/v0l/snort/pull/318
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/319
|
||||
- feat: render kind 1 reposts by @kphrx in https://github.com/v0l/snort/pull/314
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/330
|
||||
- Use inner note content as comment by @Semisol in https://github.com/v0l/snort/pull/333
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/331
|
||||
- Fix stale relays by @SamSamskies in https://github.com/v0l/snort/pull/337
|
||||
- Feed cache rework by @v0l in https://github.com/v0l/snort/pull/339
|
||||
- fix long zap comment text overflow by @SamSamskies in https://github.com/v0l/snort/pull/344
|
||||
- fix links in parentheses by @SamSamskies in https://github.com/v0l/snort/pull/347
|
||||
- Revert "Merge pull request #347 from v0l/fix-links-in-parentheses" by @SamSamskies in https://github.com/v0l/snort/pull/350
|
||||
- Update thread detection to not include mentions by @w3irdrobot in https://github.com/v0l/snort/pull/351
|
||||
- Small settings page stuff by @w3irdrobot in https://github.com/v0l/snort/pull/353
|
||||
- Change message unread color to purple by @w3irdrobot in https://github.com/v0l/snort/pull/354
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/356
|
||||
- Remove unread message dot when messages all read by @w3irdrobot in https://github.com/v0l/snort/pull/355
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/359
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/362
|
||||
- `nostr` package part 2 by @fcked in https://github.com/v0l/snort/pull/346
|
||||
- feat: add search page field autofocus by @lujakob in https://github.com/v0l/snort/pull/363
|
||||
- fix URL parsing edge cases by @SamSamskies in https://github.com/v0l/snort/pull/360
|
||||
- Fast Zaps ⚡ by @v0l in https://github.com/v0l/snort/pull/370
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/364
|
||||
- Feat/add spinner to button by @lujakob in https://github.com/v0l/snort/pull/368
|
||||
- Update mark all read dm button to be disabled when no unreads by @w3irdrobot in https://github.com/v0l/snort/pull/373
|
||||
- `nostr` package part 3 by @fcked in https://github.com/v0l/snort/pull/365
|
||||
- LNDHub/LNC wallet by @v0l in https://github.com/v0l/snort/pull/219
|
||||
- Proposal: Remove SVGs from JSX by @enjikaka in https://github.com/v0l/snort/pull/382
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/374
|
||||
- add Nostr Nests embed by @SamSamskies in https://github.com/v0l/snort/pull/377
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/387
|
||||
- fix icons by @h3y6e in https://github.com/v0l/snort/pull/392
|
||||
- Fix broken note links by @SamSamskies in https://github.com/v0l/snort/pull/380
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/391
|
||||
- fix(BackButton): vertical align styles by @lujakob in https://github.com/v0l/snort/pull/397
|
||||
- feat(note): open note in new tab on cmd press by @lujakob in https://github.com/v0l/snort/pull/395
|
||||
- fix(skeleton): dark theme styles by @lujakob in https://github.com/v0l/snort/pull/393
|
||||
- fix HyperText matching by @mattn in https://github.com/v0l/snort/pull/405
|
||||
- Makes entire note clickable by @d-r-w in https://github.com/v0l/snort/pull/371
|
||||
- render webm links as inline videos by @SamSamskies in https://github.com/v0l/snort/pull/410
|
||||
- render embed for youtube live links by @SamSamskies in https://github.com/v0l/snort/pull/407
|
||||
- do not render reposts of badge award events in timelines by @SamSamskies in https://github.com/v0l/snort/pull/406
|
||||
- `nostr` package: use `EventEmitter` by @fcked in https://github.com/v0l/snort/pull/384
|
||||
- `nostr` pacakge: implement basic NIP-20 `OK` functionality by @fcked in https://github.com/v0l/snort/pull/385
|
||||
- feat: read nip-58 badges by @verbiricha in https://github.com/v0l/snort/pull/394
|
||||
- Add Wavlake embed by @blastshielddown in https://github.com/v0l/snort/pull/416
|
||||
- display search results on page load if query in url by @SamSamskies in https://github.com/v0l/snort/pull/415
|
||||
- Fix event mention bug by @SamSamskies in https://github.com/v0l/snort/pull/421
|
||||
- fix NaN when parsing empty string by @SamSamskies in https://github.com/v0l/snort/pull/422
|
||||
- NIP06 support by @w3irdrobot in https://github.com/v0l/snort/pull/425
|
||||
- Added key attr to Tabs to remove React warning by @w3irdrobot in https://github.com/v0l/snort/pull/424
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/426
|
||||
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/436
|
||||
- Update Wavlake embed URL, add support for album & artist links by @blastshielddown in https://github.com/v0l/snort/pull/439
|
||||
- build(deps): bump webpack from 5.75.0 to 5.76.1 by @dependabot in https://github.com/v0l/snort/pull/442
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @joshr4 made their first contribution in https://github.com/v0l/snort/pull/300
|
||||
- @gandlafbtc made their first contribution in https://github.com/v0l/snort/pull/323
|
||||
- @jacany made their first contribution in https://github.com/v0l/snort/pull/321
|
||||
- @kphrx made their first contribution in https://github.com/v0l/snort/pull/314
|
||||
- @lujakob made their first contribution in https://github.com/v0l/snort/pull/363
|
||||
- @mattn made their first contribution in https://github.com/v0l/snort/pull/405
|
||||
- @d-r-w made their first contribution in https://github.com/v0l/snort/pull/371
|
||||
- @blastshielddown made their first contribution in https://github.com/v0l/snort/pull/416
|
||||
- @dependabot made their first contribution in https://github.com/v0l/snort/pull/442
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.1...v0.1.2
|
||||
|
||||
---
|
||||
|
||||
# v0.1.1
|
||||
|
||||
## What's Changed
|
||||
|
||||
- React Map Optimization, [missing map keys ] by @ahmedrowaihi in https://github.com/v0l/snort/pull/283
|
||||
- Translate '/src/lang.json' in 'ar' by @transifex-integration in https://github.com/v0l/snort/pull/287
|
||||
- HTML auto direction for specific textual content by @verbiricha in https://github.com/v0l/snort/pull/286
|
||||
- Translate '/src/lang.json' in 'hu' by @transifex-integration in https://github.com/v0l/snort/pull/288
|
||||
- feat: twitch embed by @v0l in https://github.com/v0l/snort/pull/289
|
||||
- fix: don't show 0 if there is no description by @verbiricha in https://github.com/v0l/snort/pull/290
|
||||
- feat: pinned notes and bookmarks by @verbiricha in https://github.com/v0l/snort/pull/255
|
||||
- Translate '/src/lang.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/293
|
||||
- fix: set auto to whole text content instead of individual paragraphs by @verbiricha in https://github.com/v0l/snort/pull/292
|
||||
- protocol handler by @v0l in https://github.com/v0l/snort/pull/164
|
||||
- feat: read global from specific (paid) relays by @v0l in https://github.com/v0l/snort/pull/249
|
||||
- SUPPORT RTL/LTR ON LOGIN PAGE by @ahmedrowaihi in https://github.com/v0l/snort/pull/291
|
||||
- Add Apple Music embed by @SamSamskies in https://github.com/v0l/snort/pull/294
|
||||
- Workspace with decoupled `nostr` package by @ennmichael in https://github.com/v0l/snort/pull/274
|
||||
- Translate '/src/lang.json' in 'ar' by @transifex-integration in https://github.com/v0l/snort/pull/296
|
||||
- UI fixes + counts on profile page tabs by @verbiricha in https://github.com/v0l/snort/pull/282
|
||||
- Fix blackout when selecting global tab by @h3y6e in https://github.com/v0l/snort/pull/297
|
||||
- Prevent profile text from overflowing flex container when it is too long by @h3y6e in https://github.com/v0l/snort/pull/298
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @ahmedrowaihi made their first contribution in https://github.com/v0l/snort/pull/283
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.0...v0.1.1
|
||||
|
||||
---
|
||||
|
||||
# v0.1.0
|
||||
|
||||
## What's Changed
|
||||
|
||||
- Add global tab to Root by @p2pseed in https://github.com/v0l/snort/pull/3
|
||||
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/4
|
||||
- fix: dedupe thread pubkeys by @verbiricha in https://github.com/v0l/snort/pull/7
|
||||
- Note creator improvement by @verbiricha in https://github.com/v0l/snort/pull/6
|
||||
- fix: correctly follow user mention links by @verbiricha in https://github.com/v0l/snort/pull/5
|
||||
- fix: force timeline rerender on tab change by @verbiricha in https://github.com/v0l/snort/pull/8
|
||||
- feat: add mov to video files by @verbiricha in https://github.com/v0l/snort/pull/9
|
||||
- feat: copy npub on profile by @verbiricha in https://github.com/v0l/snort/pull/10
|
||||
- fix: display full lightning address, is trimmed if too long by @verbiricha in https://github.com/v0l/snort/pull/12
|
||||
- feat: embed youtube videos by @verbiricha in https://github.com/v0l/snort/pull/13
|
||||
- feat: add support for positive and negative reactions by @verbiricha in https://github.com/v0l/snort/pull/11
|
||||
- feat: nip05 on profile page by @verbiricha in https://github.com/v0l/snort/pull/21
|
||||
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/24
|
||||
- Home tabs by @v0l in https://github.com/v0l/snort/pull/25
|
||||
- fix: support m.youtube.com subdomain links by @verbiricha in https://github.com/v0l/snort/pull/27
|
||||
- fix: use all available width for note creator text area by @verbiricha in https://github.com/v0l/snort/pull/28
|
||||
- highlight hashtags by @verbiricha in https://github.com/v0l/snort/pull/29
|
||||
- UI tweaks by @verbiricha in https://github.com/v0l/snort/pull/30
|
||||
- Improve regexes by @verbiricha in https://github.com/v0l/snort/pull/32
|
||||
- Shows QR code first by @ivanacostarubio in https://github.com/v0l/snort/pull/23
|
||||
- feat: embed tweets by @verbiricha in https://github.com/v0l/snort/pull/33
|
||||
- Nip5 shop by @v0l in https://github.com/v0l/snort/pull/50
|
||||
- Activate snort.social NIP-5 service by @v0l in https://github.com/v0l/snort/pull/51
|
||||
- feat: add avatar borders with color gradients to partner nip05 providers by @verbiricha in https://github.com/v0l/snort/pull/52
|
||||
- DM's by @v0l in https://github.com/v0l/snort/pull/54
|
||||
- feat: display banner in profile by @verbiricha in https://github.com/v0l/snort/pull/53
|
||||
- add max width to details by @verbiricha in https://github.com/v0l/snort/pull/59
|
||||
- Markdown by @verbiricha in https://github.com/v0l/snort/pull/55
|
||||
- feat: mentions by @verbiricha in https://github.com/v0l/snort/pull/56
|
||||
- Minor UI fixes by @verbiricha in https://github.com/v0l/snort/pull/63
|
||||
- add user DB and cache nip-05 verifications by @verbiricha in https://github.com/v0l/snort/pull/65
|
||||
- fix: adjust nip05 size by @verbiricha in https://github.com/v0l/snort/pull/66
|
||||
- fix: dont display display_name as nip user when username is default by @verbiricha in https://github.com/v0l/snort/pull/67
|
||||
- fix: don't retry errored verifications by @verbiricha in https://github.com/v0l/snort/pull/71
|
||||
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/70
|
||||
- refactor: TS by @v0l in https://github.com/v0l/snort/pull/69
|
||||
- More TSX by @v0l in https://github.com/v0l/snort/pull/74
|
||||
- Moar UI fixes by @verbiricha in https://github.com/v0l/snort/pull/73
|
||||
- fix: autocomplete colors by @verbiricha in https://github.com/v0l/snort/pull/75
|
||||
- feat: query for autocompletion using local db by @verbiricha in https://github.com/v0l/snort/pull/76
|
||||
- fix: rerender user timeline on pubkey change by @verbiricha in https://github.com/v0l/snort/pull/77
|
||||
- feat: follows you on profile page by @ivanacostarubio in https://github.com/v0l/snort/pull/64
|
||||
- autocomplete improvements by @verbiricha in https://github.com/v0l/snort/pull/83
|
||||
- filter for self dms by @LiranCohen in https://github.com/v0l/snort/pull/86
|
||||
- Notifications by @v0l in https://github.com/v0l/snort/pull/88
|
||||
- Theme by @verbiricha in https://github.com/v0l/snort/pull/87
|
||||
- Modified self-dm to be a "Note to Self" by @LiranCohen in https://github.com/v0l/snort/pull/89
|
||||
- note footer ordering by @verbiricha in https://github.com/v0l/snort/pull/91
|
||||
- Hashtags by @v0l in https://github.com/v0l/snort/pull/92
|
||||
- Make logo cursor a pointer by @w3irdrobot in https://github.com/v0l/snort/pull/99
|
||||
- fix: active note colors by @verbiricha in https://github.com/v0l/snort/pull/102
|
||||
- Tidal embeds by @v0l in https://github.com/v0l/snort/pull/95
|
||||
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/103
|
||||
- User preferences by @v0l in https://github.com/v0l/snort/pull/104
|
||||
- Add note context menu by @v0l in https://github.com/v0l/snort/pull/105
|
||||
- feat: soundcloud embed by @ivanacostarubio in https://github.com/v0l/snort/pull/112
|
||||
- feat: Show latest by @v0l in https://github.com/v0l/snort/pull/113
|
||||
- light theme fixes by @verbiricha in https://github.com/v0l/snort/pull/116
|
||||
- add Karnage to contributors by @verbiricha in https://github.com/v0l/snort/pull/117
|
||||
- feat: note mentions by @verbiricha in https://github.com/v0l/snort/pull/125
|
||||
- Preferences & Profile changes by @FlannelDipole in https://github.com/v0l/snort/pull/126
|
||||
- sort bug in the event that your pubkey is the 2nd item in the list by @LiranCohen in https://github.com/v0l/snort/pull/137
|
||||
- UI updates by @verbiricha in https://github.com/v0l/snort/pull/135
|
||||
- fix: hide note creator on send by @verbiricha in https://github.com/v0l/snort/pull/139
|
||||
- fix: add bottom margin to thread by @verbiricha in https://github.com/v0l/snort/pull/140
|
||||
- adds mixcloud by @ivanacostarubio in https://github.com/v0l/snort/pull/136
|
||||
- feat: audio player by @verbiricha in https://github.com/v0l/snort/pull/146
|
||||
- feat: in-memory fallback for storing user profiles by @verbiricha in https://github.com/v0l/snort/pull/110
|
||||
- Make Markdown more interoperable by @fiatjaf in https://github.com/v0l/snort/pull/153
|
||||
- fix: default to in-memory db only on db read fail by @verbiricha in https://github.com/v0l/snort/pull/155
|
||||
- bug: logout reply by @ivanacostarubio in https://github.com/v0l/snort/pull/154
|
||||
- Search by @v0l in https://github.com/v0l/snort/pull/143
|
||||
- Nip42 (AUTH) by @v0l in https://github.com/v0l/snort/pull/144
|
||||
- Muted list via NIP-51 by @verbiricha in https://github.com/v0l/snort/pull/151
|
||||
- Add more relays (high performance) by @Semisol in https://github.com/v0l/snort/pull/149
|
||||
- Show absolute time on hover by @wanacode in https://github.com/v0l/snort/pull/166
|
||||
- nostr.build file uploads by @v0l in https://github.com/v0l/snort/pull/162
|
||||
- New UI by @v0l in https://github.com/v0l/snort/pull/161
|
||||
- fix: send d tags as list by @verbiricha in https://github.com/v0l/snort/pull/169
|
||||
- Image proxy service by @v0l in https://github.com/v0l/snort/pull/174
|
||||
- Translate notes by @v0l in https://github.com/v0l/snort/pull/179
|
||||
- Use standard imgproxy by @v0l in https://github.com/v0l/snort/pull/180
|
||||
- Fix races where Socket is closed before Websocket is created by @brugeman in https://github.com/v0l/snort/pull/186
|
||||
- feat: nostrimg.com by @v0l in https://github.com/v0l/snort/pull/181
|
||||
- Add Spotify embed by @SamSamskies in https://github.com/v0l/snort/pull/188
|
||||
- Ln invoice styling by @verbiricha in https://github.com/v0l/snort/pull/187
|
||||
- feed cache by @v0l in https://github.com/v0l/snort/pull/184
|
||||
- bug: prepends https when missing from website by @ivanacostarubio in https://github.com/v0l/snort/pull/194
|
||||
- Use the current embed player via TIDALs OEmbed API. by @enjikaka in https://github.com/v0l/snort/pull/191
|
||||
- feat: zaps by @verbiricha in https://github.com/v0l/snort/pull/78
|
||||
- display note zaps succintly by @verbiricha in https://github.com/v0l/snort/pull/196
|
||||
- nostr-pub.semisol.dev is now atlas.nostr.land by @Semisol in https://github.com/v0l/snort/pull/198
|
||||
- Zaps fixes by @verbiricha in https://github.com/v0l/snort/pull/199
|
||||
- Note creator improvements by @verbiricha in https://github.com/v0l/snort/pull/193
|
||||
- Settings page and UI tweaks by @verbiricha in https://github.com/v0l/snort/pull/200
|
||||
- fix avatars by @verbiricha in https://github.com/v0l/snort/pull/203
|
||||
- Skeleton component on timeline loading for better user experience by @leotuna in https://github.com/v0l/snort/pull/190
|
||||
- Threads by @verbiricha in https://github.com/v0l/snort/pull/170
|
||||
- fix: don't stream global feed in notifications tab by @verbiricha in https://github.com/v0l/snort/pull/207
|
||||
- Zap modal by @verbiricha in https://github.com/v0l/snort/pull/209
|
||||
- Add prettier formatting by @ennmichael in https://github.com/v0l/snort/pull/214
|
||||
- react-intl spike by @SamSamskies in https://github.com/v0l/snort/pull/216
|
||||
- Add support for zh and ja locales by @SamSamskies in https://github.com/v0l/snort/pull/218
|
||||
- feat: reactions modal by @verbiricha in https://github.com/v0l/snort/pull/215
|
||||
- Translate '/src/translations/en.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/224
|
||||
- Translate '/src/translations/en.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/227
|
||||
- fix: allow zap comments by @verbiricha in https://github.com/v0l/snort/pull/229
|
||||
- Eslint by @v0l in https://github.com/v0l/snort/pull/223
|
||||
- Translate '/src/translations/en.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/230
|
||||
- add ability to use babel plugins without ejecting by @SamSamskies in https://github.com/v0l/snort/pull/225
|
||||
- add prettier pre-commit hook by @SamSamskies in https://github.com/v0l/snort/pull/234
|
||||
- oversight of intl by @h3y6e in https://github.com/v0l/snort/pull/231
|
||||
- feat: new login page by @v0l in https://github.com/v0l/snort/pull/235
|
||||
- feat: onboarding by @verbiricha in https://github.com/v0l/snort/pull/233
|
||||
- Translate '/src/translations/en.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/243
|
||||
- Translate '/src/translations/en.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/242
|
||||
- Translate '/src/translations/en.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/241
|
||||
- feat: break lang by @v0l in https://github.com/v0l/snort/pull/244
|
||||
- fix(missing-event): avoid redirect by @fernandolguevara in https://github.com/v0l/snort/pull/246
|
||||
- fix(content): render media content for current pubkey by @fernandolguevara in https://github.com/v0l/snort/pull/240
|
||||
- remove follow button from reactions modal by @SamSamskies in https://github.com/v0l/snort/pull/247
|
||||
- NIP-65: Relay list metada by @verbiricha in https://github.com/v0l/snort/pull/238
|
||||
- Fix DM page UI by @SamSamskies in https://github.com/v0l/snort/pull/250
|
||||
- Translate '/src/lang.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/252
|
||||
- Translate '/src/lang.json' in 'es' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/258
|
||||
- Translate '/src/lang.json' in 'fr' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/259
|
||||
- Translate '/src/lang.json' in 'hu' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/260
|
||||
- Translate '/src/lang.json' in 'ja' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/261
|
||||
- Translate '/src/lang.json' in 'zh' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/262
|
||||
- Translate '/src/lang.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/275
|
||||
- Translate '/src/lang.json' in 'id' by @transifex-integration in https://github.com/v0l/snort/pull/277
|
||||
- Translate '/src/lang.json' in 'zh' by @transifex-integration in https://github.com/v0l/snort/pull/278
|
||||
- Translate '/src/lang.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/279
|
||||
- Translate '/src/lang.json' in 'hu' by @transifex-integration in https://github.com/v0l/snort/pull/280
|
||||
- Translate '/src/lang.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/281
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @p2pseed made their first contribution in https://github.com/v0l/snort/pull/3
|
||||
- @v0l made their first contribution in https://github.com/v0l/snort/pull/25
|
||||
- @ivanacostarubio made their first contribution in https://github.com/v0l/snort/pull/23
|
||||
- @w3irdrobot made their first contribution in https://github.com/v0l/snort/pull/99
|
||||
- @FlannelDipole made their first contribution in https://github.com/v0l/snort/pull/126
|
||||
- @fiatjaf made their first contribution in https://github.com/v0l/snort/pull/153
|
||||
- @Semisol made their first contribution in https://github.com/v0l/snort/pull/149
|
||||
- @wanacode made their first contribution in https://github.com/v0l/snort/pull/166
|
||||
- @SamSamskies made their first contribution in https://github.com/v0l/snort/pull/188
|
||||
- @enjikaka made their first contribution in https://github.com/v0l/snort/pull/191
|
||||
- @leotuna made their first contribution in https://github.com/v0l/snort/pull/190
|
||||
- @transifex-integration made their first contribution in https://github.com/v0l/snort/pull/224
|
||||
- @h3y6e made their first contribution in https://github.com/v0l/snort/pull/231
|
||||
- @fernandolguevara made their first contribution in https://github.com/v0l/snort/pull/246
|
||||
|
||||
**Full Changelog**: https://github.com/v0l/snort/commits/v0.1.0
|
@ -1,2 +1,2 @@
|
||||
/*
|
||||
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://nostrnests.com https://embed.wavlake.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://analytics.v0l.io https://platform.twitter.com https://embed.tidal.com;
|
||||
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://nostrnests.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://analytics.v0l.io https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;
|
1
packages/app/config/README.md
Normal file
@ -0,0 +1 @@
|
||||
Choose config with NODE_CONFIG_ENV: `NODE_CONFIG_ENV=iris yarn start`
|
35
packages/app/config/default.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"appName": "Snort",
|
||||
"appNameCapitalized": "Snort",
|
||||
"appTitle": "Snort - Nostr",
|
||||
"hostname": "snort.social",
|
||||
"nip05Domain": "snort.social",
|
||||
"favicon": "public/favicon.ico",
|
||||
"appleTouchIconUrl": "/nostrich_512.png",
|
||||
"navLogo": null,
|
||||
"publicDir": "public/snort",
|
||||
"httpCache": "",
|
||||
"animalNamePlaceholders": false,
|
||||
"showNoteBroadcaster": true,
|
||||
"defaultZapPoolFee": 0.5,
|
||||
"bypassImgProxyError": false,
|
||||
"features": {
|
||||
"analytics": true,
|
||||
"subscriptions": true,
|
||||
"deck": true,
|
||||
"zapPool": true
|
||||
},
|
||||
"signUp": {
|
||||
"moderation": true
|
||||
},
|
||||
"hideFromNavbar": ["/graph"],
|
||||
"deckSubKind": 1,
|
||||
"eventLinkPrefix": "nevent",
|
||||
"profileLinkPrefix": "nprofile",
|
||||
"defaultRelays": {
|
||||
"wss://relay.snort.social/": { "read": true, "write": true },
|
||||
"wss://nostr.wine/": { "read": true, "write": false },
|
||||
"wss://eden.nostr.land/": { "read": true, "write": false }
|
||||
},
|
||||
"showNip05InNotes": true
|
||||
}
|
36
packages/app/config/iris.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"appName": "iris",
|
||||
"appNameCapitalized": "Iris",
|
||||
"appTitle": "iris",
|
||||
"hostname": "iris.to",
|
||||
"nip05Domain": "iris.to",
|
||||
"favicon": "public/iris/favicon.ico",
|
||||
"appleTouchIconUrl": "/img/apple-touch-icon.png",
|
||||
"navLogo": "/img/icon128.png",
|
||||
"publicDir": "public/iris",
|
||||
"httpCache": "https://api.iris.to",
|
||||
"animalNamePlaceholders": true,
|
||||
"showNoteBroadcaster": false,
|
||||
"defaultZapPoolFee": 0.5,
|
||||
"bypassImgProxyError": true,
|
||||
"features": {
|
||||
"analytics": true,
|
||||
"subscriptions": false,
|
||||
"deck": true,
|
||||
"zapPool": true
|
||||
},
|
||||
"signUp": {
|
||||
"moderation": false
|
||||
},
|
||||
"hideFromNavbar": [],
|
||||
"eventLinkPrefix": "note",
|
||||
"profileLinkPrefix": "npub",
|
||||
"defaultRelays": {
|
||||
"wss://relay.snort.social/": { "read": true, "write": true },
|
||||
"wss://nostr.wine/": { "read": true, "write": false },
|
||||
"wss://eden.nostr.land/": { "read": true, "write": false },
|
||||
"wss://relay.nostr.band/": { "read": true, "write": true },
|
||||
"wss://relay.damus.io/": { "read": true, "write": true }
|
||||
},
|
||||
"showNip05InNotes": false
|
||||
}
|
48
packages/app/custom.d.ts
vendored
@ -30,7 +30,55 @@ declare module "translations/*.json" {
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.md" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "emojilib" {
|
||||
const value: Record<string, string>;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare const CONFIG: {
|
||||
appName: string;
|
||||
appNameCapitalized: string;
|
||||
appTitle: string;
|
||||
hostname: string;
|
||||
nip05Domain: string;
|
||||
favicon: string;
|
||||
appleTouchIconUrl: string;
|
||||
navLogo: string | null;
|
||||
httpCache: string;
|
||||
animalNamePlaceholders: boolean;
|
||||
showNoteBroadcaster: boolean;
|
||||
defaultZapPoolFee: number;
|
||||
bypassImgProxyError: boolean;
|
||||
features: {
|
||||
analytics: boolean;
|
||||
subscriptions: boolean;
|
||||
deck: boolean;
|
||||
zapPool: boolean;
|
||||
};
|
||||
signUp: {
|
||||
moderation: boolean;
|
||||
};
|
||||
// Filter urls from nav sidebar
|
||||
hideFromNavbar: Array<string>;
|
||||
// Limit deck to certain subscriber tier
|
||||
deckSubKind?: number;
|
||||
eventLinkPrefix: NostrPrefix;
|
||||
profileLinkPrefix: NostrPrefix;
|
||||
defaultRelays: Record<string, RelaySettings>;
|
||||
showNip05InNotes: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Single relay (Debug)
|
||||
*/
|
||||
declare const SINGLE_RELAY: string | undefined;
|
||||
|
||||
/**
|
||||
* Build git hash
|
||||
*/
|
||||
declare const __SNORT_VERSION__: string;
|
||||
|
@ -9,11 +9,12 @@
|
||||
name="keywords"
|
||||
content="nostr snort fast decentralized social media censorship resistant open source software" />
|
||||
<link rel="preconnect" href="https://imgproxy.snort.social" />
|
||||
<link rel="apple-touch-icon" href="/nostrich_512.png" />
|
||||
<link rel="apple-touch-icon" href="" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>Snort - Nostr</title>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,9 +0,0 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
bail: true,
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
roots: ["src"],
|
||||
moduleDirectories: ["src", "node_modules"],
|
||||
setupFiles: ["./src/setupTests.ts"],
|
||||
};
|
@ -1,37 +1,45 @@
|
||||
{
|
||||
"name": "@snort/app",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.23",
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^0.6.1",
|
||||
"@lightninglabs/lnc-web": "^0.2.3-alpha",
|
||||
"@noble/curves": "^1.0.0",
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@scure/bip32": "^1.3.0",
|
||||
"@scure/bip39": "^1.1.1",
|
||||
"@snort/shared": "workspace:*",
|
||||
"@snort/system": "workspace:*",
|
||||
"@snort/system-query": "workspace:*",
|
||||
"@snort/system-react": "workspace:*",
|
||||
"@snort/system-wasm": "workspace:*",
|
||||
"@snort/system-web": "workspace:*",
|
||||
"@szhsin/react-menu": "^3.3.1",
|
||||
"@void-cat/api": "^1.0.4",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@void-cat/api": "^1.0.10",
|
||||
"classnames": "^2.3.2",
|
||||
"debug": "^4.3.4",
|
||||
"dexie": "^3.2.4",
|
||||
"dns-over-http-resolver": "^2.1.1",
|
||||
"emojilib": "^3.0.10",
|
||||
"fuse.js": "^7.0.0",
|
||||
"highlight.js": "^11.8.0",
|
||||
"light-bolt11-decoder": "^2.1.0",
|
||||
"marked": "^9.1.0",
|
||||
"marked-footnote": "^1.0.0",
|
||||
"match-sorter": "^6.3.1",
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-force-graph-3d": "^1.24.0",
|
||||
"react-intersection-observer": "^9.4.1",
|
||||
"react-intl": "^6.4.4",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.5.0",
|
||||
"react-tag-input-component": "^2.0.2",
|
||||
"react-textarea-autosize": "^8.4.0",
|
||||
"react-twitter-embed": "^4.0.4",
|
||||
"use-long-press": "^2.0.3",
|
||||
"recharts": "^2.8.0",
|
||||
"three": "^0.157.0",
|
||||
"use-long-press": "^3.2.0",
|
||||
"use-sync-external-store": "^1.2.0",
|
||||
"uuid": "^9.0.0",
|
||||
"workbox-core": "^6.4.2",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
@ -39,9 +47,11 @@
|
||||
"workbox-strategies": "^6.4.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve --node-env=development --mode=development",
|
||||
"build": "webpack --node-env=production --mode=production",
|
||||
"test": "jest --runInBand",
|
||||
"start": "vite",
|
||||
"build": "yarn eslint --fix && vite build",
|
||||
"serve": "vite preview",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch",
|
||||
"intl-extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true",
|
||||
"intl-compile": "formatjs compile src/lang.json --out-file src/translations/en.json",
|
||||
"eslint": "eslint ."
|
||||
@ -66,49 +76,37 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.9",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/runtime": "^7.22.6",
|
||||
"@formatjs/cli": "^6.1.3",
|
||||
"@formatjs/ts-transformer": "^3.13.3",
|
||||
"@jest/globals": "^29.6.1",
|
||||
"@types/config": "^3.3.3",
|
||||
"@types/debug": "^4.1.8",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/node": "^20.4.1",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/three": "^0.157.2",
|
||||
"@types/use-sync-external-store": "^0.0.4",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
|
||||
"@types/webtorrent": "^0.109.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"@webbtc/webln-types": "^1.0.10",
|
||||
"@webpack-cli/generators": "^3.0.4",
|
||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"config": "^3.3.9",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-webpack-plugin": "^4.0.1",
|
||||
"html-webpack-plugin": "^5.5.1",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"eslint-plugin-formatjs": "^4.11.3",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-preset-env": "^9.2.0",
|
||||
"prettier": "2.8.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"source-map-loader": "^4.0.1",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"tinybench": "^2.5.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.4",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tinybench": "^2.5.1",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"workbox-webpack-plugin": "^6.5.4"
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-pwa": "^0.17.0",
|
||||
"vite-plugin-version-mark": "^0.0.10",
|
||||
"vitest": "^0.34.6"
|
||||
}
|
||||
}
|
||||
|
3
packages/app/postcss.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: [require("tailwindcss"), require("autoprefixer")],
|
||||
};
|
Before Width: | Height: | Size: 3.2 KiB |
12
packages/app/public/iris/.well-known/assetlinks.json
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "to.iris.twa",
|
||||
"sha256_cert_fingerprints": [
|
||||
"63:B5:70:E8:F1:75:7E:D6:EF:81:11:66:F4:9D:47:AB:49:3C:2E:00:B9:67:92:40:89:A5:03:0B:96:B9:40:09"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
BIN
packages/app/public/iris/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packages/app/public/iris/img/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
packages/app/public/iris/img/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
packages/app/public/iris/img/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
packages/app/public/iris/img/icon128.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packages/app/public/iris/img/irisconnects.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
packages/app/public/iris/img/maskable_icon.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
packages/app/public/iris/img/maskable_icon_x192.png
Normal file
After Width: | Height: | Size: 12 KiB |
41
packages/app/public/iris/manifest.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"short_name": "Iris",
|
||||
"name": "Iris",
|
||||
"description": "Fast nostr web ui",
|
||||
"id": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/img/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/img/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/img/maskable_icon.png",
|
||||
"sizes": "640x640",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/img/maskable_icon_x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#000000",
|
||||
"protocol_handlers": [
|
||||
{
|
||||
"protocol": "web+nostr",
|
||||
"url": "/%s"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"applinks": {
|
||||
"details": [
|
||||
{
|
||||
"appIDs": [
|
||||
"snort.social.app"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"webcredentials": {
|
||||
"apps": [
|
||||
"snort.social.app"
|
||||
]
|
||||
}
|
||||
}
|
13
packages/app/public/snort/.well-known/assetlinks.json
Normal file
@ -0,0 +1,13 @@
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "social.snort.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"78:CE:8A:F7:C1:E2:30:12:77:55:BF:0E:86:E4:5C:BA:99:93:A0:D7:D7:42:F8:27:8B:C9:1B:AC:FC:8A:85:05",
|
||||
"FC:C1:CA:02:C0:81:81:0C:1F:EC:1E:38:CA:38:61:62:6B:6E:90:88:62:DE:4A:66:FC:EC:08:33:B6:94:EE:3C"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -24,5 +24,14 @@
|
||||
"protocol": "web+nostr",
|
||||
"url": "/%s"
|
||||
}
|
||||
],
|
||||
"screenshots": [],
|
||||
"display_override": ["fullscreen"],
|
||||
"related_applications": [
|
||||
{
|
||||
"platform": "play",
|
||||
"url": "https://play.google.com/store/apps/details?id=social.snort.app",
|
||||
"id": "social.snort.app"
|
||||
}
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 528 KiB After Width: | Height: | Size: 528 KiB |
Before Width: | Height: | Size: 771 KiB After Width: | Height: | Size: 771 KiB |
3
packages/app/public/snort/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,6 +1,6 @@
|
||||
import { NostrEvent } from "@snort/system";
|
||||
import { FeedCache } from "@snort/shared";
|
||||
import { db } from "Db";
|
||||
import { db } from "@/Db";
|
||||
|
||||
export class ChatCache extends FeedCache<NostrEvent> {
|
||||
constructor() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FeedCache } from "@snort/shared";
|
||||
import { db, EventInteraction } from "Db";
|
||||
import { LoginStore } from "Login";
|
||||
import { sha256 } from "SnortUtils";
|
||||
import { db, EventInteraction } from "@/Db";
|
||||
import { LoginStore } from "@/Login";
|
||||
import { sha256 } from "@/SnortUtils";
|
||||
|
||||
export class EventInteractionCache extends FeedCache<EventInteraction> {
|
||||
constructor() {
|
||||
|
47
packages/app/src/Cache/FollowListCache.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { db } from "@/Db";
|
||||
import { unixNowMs } from "@snort/shared";
|
||||
import { EventKind, RequestBuilder, socialGraphInstance, TaggedNostrEvent } from "@snort/system";
|
||||
import { RefreshFeedCache } from "./RefreshFeedCache";
|
||||
import { LoginSession } from "@/Login";
|
||||
|
||||
export class FollowListCache extends RefreshFeedCache<TaggedNostrEvent> {
|
||||
constructor() {
|
||||
super("FollowListCache", db.followLists);
|
||||
}
|
||||
|
||||
buildSub(session: LoginSession, rb: RequestBuilder): void {
|
||||
const since = this.newest();
|
||||
rb.withFilter()
|
||||
.kinds([EventKind.ContactList])
|
||||
.authors(session.follows.item)
|
||||
.since(since === 0 ? undefined : since);
|
||||
}
|
||||
|
||||
async onEvent(evs: readonly TaggedNostrEvent[]) {
|
||||
await Promise.all(
|
||||
evs.map(async e => {
|
||||
const update = await super.update({
|
||||
...e,
|
||||
created: e.created_at,
|
||||
loaded: unixNowMs(),
|
||||
});
|
||||
if (update !== "no_change") {
|
||||
socialGraphInstance.handleFollowEvent(e);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
key(of: TaggedNostrEvent): string {
|
||||
return of.pubkey;
|
||||
}
|
||||
|
||||
takeSnapshot() {
|
||||
return [...this.cache.values()];
|
||||
}
|
||||
|
||||
override async preload() {
|
||||
await super.preload();
|
||||
this.snapshot().forEach(e => socialGraphInstance.handleFollowEvent(e));
|
||||
}
|
||||
}
|
@ -2,10 +2,10 @@ import debug from "debug";
|
||||
import { EventKind, RequestBuilder, SystemInterface, TaggedNostrEvent } from "@snort/system";
|
||||
import { unixNow, unixNowMs } from "@snort/shared";
|
||||
|
||||
import { db } from "Db";
|
||||
import { db } from "@/Db";
|
||||
import { RefreshFeedCache, TWithCreated } from "./RefreshFeedCache";
|
||||
import { LoginSession } from "Login";
|
||||
import { Day, Hour } from "Const";
|
||||
import { LoginSession } from "@/Login";
|
||||
import { Day, Hour } from "@/Const";
|
||||
|
||||
const WindowSize = Hour * 6;
|
||||
const MaxCacheWindow = Day * 7;
|
||||
@ -27,10 +27,12 @@ export class FollowsFeedCache extends RefreshFeedCache<TaggedNostrEvent> {
|
||||
}
|
||||
|
||||
buildSub(session: LoginSession, rb: RequestBuilder): void {
|
||||
const authors = session.follows.item;
|
||||
authors.push(session.publicKey);
|
||||
const since = this.newest();
|
||||
rb.withFilter()
|
||||
.kinds(this.#kinds)
|
||||
.authors(session.follows.item)
|
||||
.authors(authors)
|
||||
.since(since === 0 ? unixNow() - WindowSize : since);
|
||||
}
|
||||
|
||||
@ -47,7 +49,7 @@ export class FollowsFeedCache extends RefreshFeedCache<TaggedNostrEvent> {
|
||||
const keys = (await this.table?.toCollection().primaryKeys()) ?? [];
|
||||
this.onTable = new Set<string>(keys.map(a => a as string));
|
||||
|
||||
// load only latest 10 posts, rest can be loaded on-demand
|
||||
// load only latest 50 posts, rest can be loaded on-demand
|
||||
const latest = await this.table?.orderBy("created_at").reverse().limit(50).toArray();
|
||||
latest?.forEach(v => this.cache.set(this.key(v), v));
|
||||
|
||||
@ -67,9 +69,11 @@ export class FollowsFeedCache extends RefreshFeedCache<TaggedNostrEvent> {
|
||||
async loadMore(system: SystemInterface, session: LoginSession, before: number) {
|
||||
if (this.#oldest && before <= this.#oldest) {
|
||||
const rb = new RequestBuilder(`${this.name}-loadmore`);
|
||||
const authors = session.follows.item;
|
||||
authors.push(session.publicKey);
|
||||
rb.withFilter()
|
||||
.kinds(this.#kinds)
|
||||
.authors(session.follows.item)
|
||||
.authors(authors)
|
||||
.until(before)
|
||||
.since(before - WindowSize);
|
||||
await system.Fetch(rb, async evs => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { EventKind, EventPublisher, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||
import { UnwrappedGift, db } from "Db";
|
||||
import { findTag, unwrap } from "SnortUtils";
|
||||
import { UnwrappedGift, db } from "@/Db";
|
||||
import { findTag, unwrap } from "@/SnortUtils";
|
||||
import { RefreshFeedCache } from "./RefreshFeedCache";
|
||||
import { LoginSession } from "Login";
|
||||
import { LoginSession, LoginSessionType } from "@/Login";
|
||||
|
||||
export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
|
||||
constructor() {
|
||||
@ -15,7 +15,7 @@ export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
|
||||
|
||||
buildSub(session: LoginSession, rb: RequestBuilder): void {
|
||||
const pubkey = session.publicKey;
|
||||
if (pubkey) {
|
||||
if (pubkey && session.type === LoginSessionType.PrivateKey) {
|
||||
rb.withFilter().kinds([EventKind.GiftWrap]).tag("p", [pubkey]).since(this.newest());
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,9 @@ export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
|
||||
return [...this.cache.values()];
|
||||
}
|
||||
|
||||
override async onEvent(evs: Array<TaggedNostrEvent>, pub: EventPublisher) {
|
||||
override async onEvent(evs: Array<TaggedNostrEvent>, _: string, pub?: EventPublisher) {
|
||||
if (!pub) return;
|
||||
|
||||
const unwrapped = (
|
||||
await Promise.all(
|
||||
evs.map(async v => {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { EventKind, NostrEvent, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||
import { RefreshFeedCache, TWithCreated } from "./RefreshFeedCache";
|
||||
import { LoginSession } from "Login";
|
||||
import { db } from "Db";
|
||||
import { Day } from "Const";
|
||||
import { LoginSession } from "@/Login";
|
||||
import { NostrEventForSession, db } from "@/Db";
|
||||
import { Day } from "@/Const";
|
||||
import { unixNow } from "@snort/shared";
|
||||
|
||||
export class NotificationsCache extends RefreshFeedCache<NostrEvent> {
|
||||
export class NotificationsCache extends RefreshFeedCache<NostrEventForSession> {
|
||||
#kinds = [EventKind.TextNote, EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt];
|
||||
|
||||
constructor() {
|
||||
@ -14,7 +14,7 @@ export class NotificationsCache extends RefreshFeedCache<NostrEvent> {
|
||||
|
||||
buildSub(session: LoginSession, rb: RequestBuilder) {
|
||||
if (session.publicKey) {
|
||||
const newest = this.newest();
|
||||
const newest = this.newest(v => v.tags.some(a => a[0] === "p" && a[1] === session.publicKey));
|
||||
rb.withFilter()
|
||||
.kinds(this.#kinds)
|
||||
.tag("p", [session.publicKey])
|
||||
@ -22,10 +22,15 @@ export class NotificationsCache extends RefreshFeedCache<NostrEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
async onEvent(evs: readonly TaggedNostrEvent[]) {
|
||||
async onEvent(evs: readonly TaggedNostrEvent[], pubKey: string) {
|
||||
const filtered = evs.filter(a => this.#kinds.includes(a.kind) && a.tags.some(b => b[0] === "p"));
|
||||
if (filtered.length > 0) {
|
||||
await this.bulkSet(filtered);
|
||||
await this.bulkSet(
|
||||
filtered.map(v => ({
|
||||
...v,
|
||||
forSession: pubKey,
|
||||
})),
|
||||
);
|
||||
this.notifyChange(filtered.map(v => this.key(v)));
|
||||
}
|
||||
}
|
||||
@ -34,7 +39,7 @@ export class NotificationsCache extends RefreshFeedCache<NostrEvent> {
|
||||
return of.id;
|
||||
}
|
||||
|
||||
takeSnapshot(): TWithCreated<NostrEvent>[] {
|
||||
takeSnapshot() {
|
||||
return [...this.cache.values()];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Payment, db } from "Db";
|
||||
import { Payment, db } from "@/Db";
|
||||
import { FeedCache } from "@snort/shared";
|
||||
|
||||
export class Payments extends FeedCache<Payment> {
|
||||
|
@ -1,19 +1,23 @@
|
||||
import { FeedCache } from "@snort/shared";
|
||||
import { EventPublisher, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||
import { LoginSession } from "Login";
|
||||
import { LoginSession } from "@/Login";
|
||||
|
||||
export type TWithCreated<T> = (T | Readonly<T>) & { created_at: number };
|
||||
|
||||
export abstract class RefreshFeedCache<T> extends FeedCache<TWithCreated<T>> {
|
||||
abstract buildSub(session: LoginSession, rb: RequestBuilder): void;
|
||||
abstract onEvent(evs: Readonly<Array<TaggedNostrEvent>>, pub: EventPublisher): void;
|
||||
abstract onEvent(evs: Readonly<Array<TaggedNostrEvent>>, pubKey: string, pub?: EventPublisher): void;
|
||||
|
||||
/**
|
||||
* Get latest event
|
||||
*/
|
||||
protected newest() {
|
||||
protected newest(filter?: (e: TWithCreated<T>) => boolean) {
|
||||
let ret = 0;
|
||||
this.cache.forEach(v => (ret = v.created_at > ret ? v.created_at : ret));
|
||||
this.cache.forEach(v => {
|
||||
if (!filter || filter(v)) {
|
||||
ret = v.created_at > ret ? v.created_at : ret;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,26 @@
|
||||
import { UserProfileCache, UserRelaysCache, RelayMetricCache } from "@snort/system";
|
||||
import { SnortSystemDb } from "@snort/system-web";
|
||||
|
||||
import { EventInteractionCache } from "./EventInteractionCache";
|
||||
import { ChatCache } from "./ChatCache";
|
||||
import { Payments } from "./PaymentsCache";
|
||||
import { GiftWrapCache } from "./GiftWrapCache";
|
||||
import { NotificationsCache } from "./Notifications";
|
||||
import { FollowsFeedCache } from "./FollowsFeed";
|
||||
import { FollowListCache } from "./FollowListCache";
|
||||
|
||||
export const SystemDb = new SnortSystemDb();
|
||||
export const UserCache = new UserProfileCache(SystemDb.users);
|
||||
export const UserRelays = new UserRelaysCache(SystemDb.userRelays);
|
||||
export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics);
|
||||
|
||||
export const UserCache = new UserProfileCache();
|
||||
export const UserRelays = new UserRelaysCache();
|
||||
export const RelayMetrics = new RelayMetricCache();
|
||||
export const Chats = new ChatCache();
|
||||
export const PaymentsCache = new Payments();
|
||||
export const InteractionCache = new EventInteractionCache();
|
||||
export const GiftsCache = new GiftWrapCache();
|
||||
export const Notifications = new NotificationsCache();
|
||||
export const FollowsFeed = new FollowsFeedCache();
|
||||
export const FollowLists = new FollowListCache();
|
||||
|
||||
export async function preload(follows?: Array<string>) {
|
||||
const preloads = [
|
||||
@ -26,6 +32,7 @@ export async function preload(follows?: Array<string>) {
|
||||
GiftsCache.preload(),
|
||||
Notifications.preload(),
|
||||
FollowsFeed.preload(),
|
||||
FollowLists.preload(),
|
||||
];
|
||||
await Promise.all(preloads);
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { RelaySettings } from "@snort/system";
|
||||
|
||||
/**
|
||||
* 1 Hour in seconds
|
||||
*/
|
||||
@ -10,21 +8,16 @@ export const Hour = 60 * 60;
|
||||
*/
|
||||
export const Day = Hour * 24;
|
||||
|
||||
/**
|
||||
* Day this project started
|
||||
*/
|
||||
export const Birthday = new Date(2022, 11, 17);
|
||||
|
||||
/**
|
||||
* Add-on api for snort features
|
||||
*/
|
||||
export const ApiHost = "https://api.snort.social";
|
||||
|
||||
/**
|
||||
* LibreTranslate endpoint
|
||||
*/
|
||||
export const TranslateHost = "https://translate.snort.social";
|
||||
|
||||
/**
|
||||
* Void.cat file upload service url
|
||||
*/
|
||||
export const VoidCatHost = "https://void.cat";
|
||||
|
||||
/**
|
||||
* Kierans pubkey
|
||||
*/
|
||||
@ -35,53 +28,21 @@ export const KieranPubKey = "npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7v
|
||||
*/
|
||||
export const SnortPubKey = "npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws";
|
||||
|
||||
/**
|
||||
* Websocket re-connect timeout
|
||||
*/
|
||||
export const DefaultConnectTimeout = 2000;
|
||||
|
||||
/**
|
||||
* How long profile cache should be considered valid for
|
||||
*/
|
||||
export const ProfileCacheExpire = 1_000 * 60 * 60 * 6;
|
||||
|
||||
/**
|
||||
* Default bootstrap relays
|
||||
*/
|
||||
export const DefaultRelays = new Map<string, RelaySettings>([
|
||||
["wss://relay.snort.social/", { read: true, write: true }],
|
||||
["wss://nostr.wine/", { read: true, write: false }],
|
||||
["wss://nos.lol/", { read: true, write: true }],
|
||||
]);
|
||||
export const DefaultRelays = new Map(Object.entries(CONFIG.defaultRelays));
|
||||
|
||||
/**
|
||||
* Default search relays
|
||||
*/
|
||||
export const SearchRelays = ["wss://relay.nostr.band"];
|
||||
|
||||
/**
|
||||
* List of recommended follows for new users
|
||||
*/
|
||||
export const RecommendedFollows = [
|
||||
"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", // jack
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", // fiatjaf
|
||||
"020f2d21ae09bf35fcdfb65decf1478b846f5f728ab30c5eaabcd6d081a81c3e", // adam3us
|
||||
"6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93", // gigi
|
||||
"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", // Kieran
|
||||
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", // jb55
|
||||
"e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42", // wiz
|
||||
"00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700", // cameri
|
||||
"A341F45FF9758F570A21B000C17D4E53A3A497C8397F26C0E6D61E5ACFFC7A98", // Saylor
|
||||
"E88A691E98D9987C964521DFF60025F60700378A4879180DCBBB4A5027850411", // NVK
|
||||
"C4EABAE1BE3CF657BC1855EE05E69DE9F059CB7A059227168B80B89761CBC4E0", // jackmallers
|
||||
"85080D3BAD70CCDCD7F74C29A44F55BB85CBCD3DD0CBB957DA1D215BDB931204", // preston
|
||||
"C49D52A573366792B9A6E4851587C28042FB24FA5625C6D67B8C95C8751ACA15", // holdonaut
|
||||
"83E818DFBECCEA56B0F551576B3FD39A7A50E1D8159343500368FA085CCD964B", // jeffbooth
|
||||
"3F770D65D3A764A9C5CB503AE123E62EC7598AD035D836E2A810F3877A745B24", // DerekRoss
|
||||
"472F440F29EF996E92A186B8D320FF180C855903882E59D50DE1B8BD5669301E", // MartyBent
|
||||
"1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b", // yegorpetrov
|
||||
"04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9", // ODELL
|
||||
export const DeveloperAccounts = [
|
||||
"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", // kieran
|
||||
"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0", // Martti
|
||||
"7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", // verbiricha
|
||||
"1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411", // Karnage
|
||||
];
|
||||
|
||||
/**
|
||||
@ -98,6 +59,11 @@ export const DefaultImgProxy = {
|
||||
*/
|
||||
export const DerivationPath = "m/44'/1237'/0'/0/0";
|
||||
|
||||
/**
|
||||
* Blaster relays
|
||||
*/
|
||||
export const Blasters = ["wss://nostr.mutinywallet.com"];
|
||||
|
||||
/**
|
||||
* Regex to match email address
|
||||
*/
|
||||
@ -132,11 +98,6 @@ export const InvoiceRegex = /(lnbc\w+)/i;
|
||||
export const YoutubeUrlRegex =
|
||||
/(?:https?:\/\/)?(?:www|m\.)?(?:youtu\.be\/|youtube\.com\/(?:live\/|shorts\/|embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})/;
|
||||
|
||||
/**
|
||||
* Tweet Regex
|
||||
*/
|
||||
export const TweetUrlRegex = /https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/;
|
||||
|
||||
/**
|
||||
* Hashtag regex
|
||||
*/
|
||||
|
@ -2,7 +2,7 @@ import Dexie, { Table } from "dexie";
|
||||
import { HexKey, NostrEvent, TaggedNostrEvent, u256 } from "@snort/system";
|
||||
|
||||
export const NAME = "snortDB";
|
||||
export const VERSION = 14;
|
||||
export const VERSION = 16;
|
||||
|
||||
export interface SubCache {
|
||||
id: string;
|
||||
@ -35,6 +35,10 @@ export interface UnwrappedGift {
|
||||
tags?: Array<Array<string>>; // some tags extracted
|
||||
}
|
||||
|
||||
export type NostrEventForSession = TaggedNostrEvent & {
|
||||
forSession: string;
|
||||
};
|
||||
|
||||
const STORES = {
|
||||
chats: "++id",
|
||||
eventInteraction: "++id",
|
||||
@ -42,6 +46,7 @@ const STORES = {
|
||||
gifts: "++id",
|
||||
notifications: "++id",
|
||||
followsFeed: "++id, created_at, kind",
|
||||
followLists: "++pubkey",
|
||||
};
|
||||
|
||||
export class SnortDB extends Dexie {
|
||||
@ -50,8 +55,9 @@ export class SnortDB extends Dexie {
|
||||
eventInteraction!: Table<EventInteraction>;
|
||||
payments!: Table<Payment>;
|
||||
gifts!: Table<UnwrappedGift>;
|
||||
notifications!: Table<NostrEvent>;
|
||||
notifications!: Table<NostrEventForSession>;
|
||||
followsFeed!: Table<TaggedNostrEvent>;
|
||||
followLists!: Table<TaggedNostrEvent>;
|
||||
|
||||
constructor() {
|
||||
super(NAME);
|
||||
|
@ -1,14 +0,0 @@
|
||||
button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.spinner-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import "./AsyncButton.css";
|
||||
import { useState } from "react";
|
||||
import Spinner from "../Icons/Spinner";
|
||||
|
||||
interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
disabled?: boolean;
|
||||
onClick(e: React.MouseEvent): Promise<void> | void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function AsyncButton(props: AsyncButtonProps) {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
async function handle(e: React.MouseEvent) {
|
||||
e.stopPropagation();
|
||||
if (loading || props.disabled) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
if (typeof props.onClick === "function") {
|
||||
const f = props.onClick(e);
|
||||
if (f instanceof Promise) {
|
||||
await f;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button className="spinner-button" type="button" disabled={loading || props.disabled} {...props} onClick={handle}>
|
||||
<span style={{ visibility: loading ? "hidden" : "visible" }}>{props.children}</span>
|
||||
{loading && (
|
||||
<span className="spinner-wrapper">
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import Icon from "Icons/Icon";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import { HTMLProps, useState } from "react";
|
||||
|
||||
export interface AsyncIconProps extends HTMLProps<HTMLDivElement> {
|
||||
iconName: string;
|
||||
iconSize?: number;
|
||||
loading?: boolean;
|
||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => Promise<void>;
|
||||
}
|
||||
|
||||
export function AsyncIcon(props: AsyncIconProps) {
|
||||
const [loading, setLoading] = useState(props.loading ?? false);
|
||||
|
||||
async function handleClick(e: React.MouseEvent<HTMLDivElement>) {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (props.onClick) {
|
||||
await props.onClick(e);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const mergedProps = { ...props } as Record<string, unknown>;
|
||||
delete mergedProps["iconName"];
|
||||
delete mergedProps["iconSize"];
|
||||
delete mergedProps["loading"];
|
||||
return (
|
||||
<div {...mergedProps} onClick={e => handleClick(e)}>
|
||||
{loading ? <Spinner /> : <Icon name={props.iconName} size={props.iconSize} />}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import "./Avatar.css";
|
||||
|
||||
import { CSSProperties, ReactNode, useEffect, useState } from "react";
|
||||
import type { UserMetadata } from "@snort/system";
|
||||
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
import { getDisplayName } from "Element/ProfileImage";
|
||||
import { defaultAvatar } from "SnortUtils";
|
||||
|
||||
interface AvatarProps {
|
||||
pubkey: string;
|
||||
user?: UserMetadata;
|
||||
onClick?: () => void;
|
||||
size?: number;
|
||||
image?: string;
|
||||
imageOverlay?: ReactNode;
|
||||
}
|
||||
const Avatar = ({ pubkey, user, size, onClick, image, imageOverlay }: AvatarProps) => {
|
||||
const [url, setUrl] = useState("");
|
||||
const { proxy } = useImgProxy();
|
||||
|
||||
useEffect(() => {
|
||||
const url = image ?? user?.picture;
|
||||
if (url) {
|
||||
const proxyUrl = proxy(url, size ?? 120);
|
||||
setUrl(proxyUrl);
|
||||
} else {
|
||||
setUrl(defaultAvatar(pubkey));
|
||||
}
|
||||
}, [user, image]);
|
||||
|
||||
const backgroundImage = `url(${url})`;
|
||||
const style = { "--img-url": backgroundImage } as CSSProperties;
|
||||
const domain = user?.nip05 && user.nip05.split("@")[1];
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
className={`avatar${imageOverlay ? " with-overlay" : ""}`}
|
||||
data-domain={domain?.toLowerCase()}
|
||||
title={getDisplayName(user, "")}>
|
||||
{imageOverlay && <div className="overlay">{imageOverlay}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Avatar;
|
28
packages/app/src/Element/Button/AsyncButton.css
Normal file
@ -0,0 +1,28 @@
|
||||
.spinner-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.spinner-button > span {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.light .spinner-button {
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--font-secondary);
|
||||
box-shadow: rgba(0, 0, 0, 0.08) 0 1px 1px;
|
||||
}
|
||||
|
||||
.light .spinner-button:hover {
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 3px;
|
||||
}
|
||||
|
||||
.light .spinner-button > span {
|
||||
color: black;
|
||||
}
|
32
packages/app/src/Element/Button/AsyncButton.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import "./AsyncButton.css";
|
||||
import React, { ForwardedRef } from "react";
|
||||
import Spinner from "../../Icons/Spinner";
|
||||
import useLoading from "@/Hooks/useLoading";
|
||||
import classNames from "classnames";
|
||||
|
||||
export interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
onClick?: (e: React.MouseEvent) => Promise<void> | void;
|
||||
}
|
||||
|
||||
const AsyncButton = React.forwardRef<HTMLButtonElement, AsyncButtonProps>((props, ref) => {
|
||||
const { handle, loading } = useLoading(props.onClick, props.disabled);
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref as ForwardedRef<HTMLButtonElement>}
|
||||
type="button"
|
||||
disabled={loading || props.disabled}
|
||||
{...props}
|
||||
className={classNames("spinner-button", props.className)}
|
||||
onClick={handle}>
|
||||
<span style={{ visibility: loading ? "hidden" : "visible" }}>{props.children}</span>
|
||||
{loading && (
|
||||
<span className="spinner-wrapper">
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
export default AsyncButton;
|
24
packages/app/src/Element/Button/AsyncIcon.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import Icon from "@/Icons/Icon";
|
||||
import useLoading from "@/Hooks/useLoading";
|
||||
import Spinner from "@/Icons/Spinner";
|
||||
|
||||
export type AsyncIconProps = React.HTMLProps<HTMLDivElement> & {
|
||||
iconName: string;
|
||||
iconSize?: number;
|
||||
onClick?: (e: React.MouseEvent) => Promise<void> | void;
|
||||
};
|
||||
|
||||
export function AsyncIcon(props: AsyncIconProps) {
|
||||
const { loading, handle } = useLoading(props.onClick, props.disabled);
|
||||
|
||||
const mergedProps = { ...props } as Record<string, unknown>;
|
||||
delete mergedProps["iconName"];
|
||||
delete mergedProps["iconSize"];
|
||||
delete mergedProps["loading"];
|
||||
return (
|
||||
<div {...mergedProps} onClick={handle} className={props.className}>
|
||||
{loading ? <Spinner /> : <Icon name={props.iconName} size={props.iconSize} />}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -6,18 +6,17 @@
|
||||
font-size: var(--font-size);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.back-button svg {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: none;
|
||||
color: var(--font-color);
|
||||
.back-button:hover:hover,
|
||||
.light .back-button:hover {
|
||||
text-decoration: underline;
|
||||
box-shadow: none !important;
|
||||
background: none !important;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import "./BackButton.css";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import Icon from "Icons/Icon";
|
||||
import Icon from "@/Icons/Icon";
|
||||
|
||||
import messages from "./messages";
|
||||
import messages from "../messages";
|
||||
|
||||
interface BackButtonProps {
|
||||
text?: string;
|
21
packages/app/src/Element/Button/IconButton.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import classNames from "classnames";
|
||||
import Icon, { IconProps } from "@/Icons/Icon";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface IconButtonProps {
|
||||
onClick?: () => void;
|
||||
icon: IconProps;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const IconButton = ({ onClick, icon, children, className }: IconButtonProps) => {
|
||||
return (
|
||||
<button className={classNames("icon", className)} type="button" onClick={onClick}>
|
||||
<Icon {...icon} />
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconButton;
|
@ -1,21 +1,21 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { logout } from "Login";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import messages from "./messages";
|
||||
import { logout } from "@/Login";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import messages from "../messages";
|
||||
|
||||
export default function LogoutButton() {
|
||||
const navigate = useNavigate();
|
||||
const publicKey = useLogin().publicKey;
|
||||
const login = useLogin(s => ({ publicKey: s.publicKey, id: s.id }));
|
||||
|
||||
if (!publicKey) return;
|
||||
if (!login.publicKey) return;
|
||||
return (
|
||||
<button
|
||||
className="secondary"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
logout(publicKey);
|
||||
logout(login.id);
|
||||
navigate("/");
|
||||
}}>
|
||||
<FormattedMessage {...messages.Logout} />
|
@ -1,81 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
|
||||
interface Token {
|
||||
token: Array<{
|
||||
mint: string;
|
||||
proofs: Array<{
|
||||
amount: number;
|
||||
}>;
|
||||
}>;
|
||||
memo?: string;
|
||||
}
|
||||
|
||||
export default function CashuNuts({ token }: { token: string }) {
|
||||
const login = useLogin();
|
||||
const profile = useUserProfile(login.publicKey);
|
||||
|
||||
async function copyToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
||||
e.stopPropagation();
|
||||
await navigator.clipboard.writeText(token);
|
||||
}
|
||||
async function redeemToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
||||
e.stopPropagation();
|
||||
const lnurl = profile?.lud16 ?? "";
|
||||
const url = `https://redeem.cashu.me?token=${encodeURIComponent(token)}&lightning=${encodeURIComponent(
|
||||
lnurl,
|
||||
)}&autopay=yes`;
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
|
||||
const [cashu, setCashu] = useState<Token>();
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (!token.startsWith("cashuA") || token.length < 10) {
|
||||
return;
|
||||
}
|
||||
import("@cashu/cashu-ts").then(({ getDecodedToken }) => {
|
||||
const tkn = getDecodedToken(token);
|
||||
setCashu(tkn);
|
||||
});
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
if (!cashu) return <>{token}</>;
|
||||
|
||||
return (
|
||||
<div className="note-invoice">
|
||||
<div className="flex f-between">
|
||||
<div>
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Cashu token" />
|
||||
</h4>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Amount: {amount} sats"
|
||||
values={{
|
||||
amount: cashu.token[0].proofs.reduce((acc, v) => acc + v.amount, 0),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<small className="xs">
|
||||
<FormattedMessage defaultMessage="Mint: {url}" values={{ url: cashu.token[0].mint }} />
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={e => copyToken(e, token)} className="mr5">
|
||||
<FormattedMessage defaultMessage="Copy" description="Button: Copy Cashu token" />
|
||||
</button>
|
||||
<button onClick={e => redeemToken(e, token)}>
|
||||
<FormattedMessage defaultMessage="Redeem" description="Button: Redeem Cashu token" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
21
packages/app/src/Element/Chat/ChatParticipant.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { MetadataCache } from "@snort/system";
|
||||
|
||||
import { ChatParticipant } from "@/chat";
|
||||
import NoteToSelf from "../User/NoteToSelf";
|
||||
import ProfileImage from "../User/ProfileImage";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
|
||||
export function ChatParticipantProfile({ participant }: { participant: ChatParticipant }) {
|
||||
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||
if (participant.id === publicKey) {
|
||||
return <NoteToSelf className="grow" />;
|
||||
}
|
||||
return (
|
||||
<ProfileImage
|
||||
showNip05={false}
|
||||
pubkey={participant.id}
|
||||
className="grow"
|
||||
profile={participant.profile as MetadataCache}
|
||||
/>
|
||||
);
|
||||
}
|
7
packages/app/src/Element/Chat/DM.css
Normal file
@ -0,0 +1,7 @@
|
||||
.dm-gradient {
|
||||
background: var(--dm-gradient);
|
||||
}
|
||||
|
||||
.other {
|
||||
background: var(--gray-superdark);
|
||||
}
|
@ -1,16 +1,17 @@
|
||||
import "./DM.css";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import NoteTime from "Element/NoteTime";
|
||||
import Text from "Element/Text";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { Chat, ChatMessage, ChatType, setLastReadIn } from "chat";
|
||||
import ProfileImage from "./ProfileImage";
|
||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import NoteTime from "@/Element/Event/NoteTime";
|
||||
import Text from "@/Element/Text";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import { Chat, ChatMessage, ChatType, setLastReadIn } from "@/chat";
|
||||
import ProfileImage from "../User/ProfileImage";
|
||||
|
||||
import messages from "./messages";
|
||||
import messages from "../messages";
|
||||
|
||||
export interface DMProps {
|
||||
chat: Chat;
|
||||
@ -18,14 +19,14 @@ export interface DMProps {
|
||||
}
|
||||
|
||||
export default function DM(props: DMProps) {
|
||||
const pubKey = useLogin().publicKey;
|
||||
const publisher = useEventPublisher();
|
||||
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||
const { publisher } = useEventPublisher();
|
||||
const msg = props.data;
|
||||
const [content, setContent] = useState<string>();
|
||||
const { ref, inView } = useInView({ triggerOnce: true });
|
||||
const { formatMessage } = useIntl();
|
||||
const isMe = msg.from === pubKey;
|
||||
const otherPubkey = isMe ? pubKey : msg.from;
|
||||
const isMe = msg.from === publicKey;
|
||||
const otherPubkey = isMe ? publicKey : msg.from;
|
||||
|
||||
async function decrypt() {
|
||||
if (publisher) {
|
||||
@ -55,16 +56,27 @@ export default function DM(props: DMProps) {
|
||||
}, [inView]);
|
||||
|
||||
return (
|
||||
<div className={isMe ? "dm me" : "dm other"} ref={ref}>
|
||||
<div>
|
||||
<div
|
||||
className={
|
||||
isMe
|
||||
? "self-end mt-4 min-w-[100px] max-w-[90%] whitespace-pre-wrap align-self-end"
|
||||
: "mt-4 min-w-[100px] max-w-[90%] whitespace-pre-wrap"
|
||||
}
|
||||
ref={ref}>
|
||||
<div
|
||||
className={
|
||||
isMe
|
||||
? "p-3 dm-gradient rounded-tl-lg rounded-tr-lg rounded-bl-lg rounded-br-none"
|
||||
: "p-3 bg-gray-300 rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-none other"
|
||||
}>
|
||||
{sender()}
|
||||
{content ? (
|
||||
<Text id={msg.id} content={content} tags={[]} creator={otherPubkey} />
|
||||
) : (
|
||||
<FormattedMessage defaultMessage="Loading..." />
|
||||
<FormattedMessage defaultMessage="Loading..." id="gjBiyj" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className={isMe ? "text-end text-gray-400 text-sm mt-1" : "text-gray-400 text-sm mt-1"}>
|
||||
<NoteTime from={msg.created_at * 1000} fallback={formatMessage(messages.JustNow)} />
|
||||
</div>
|
||||
</div>
|
89
packages/app/src/Element/Chat/DmWindow.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import ProfileImage from "@/Element/User/ProfileImage";
|
||||
import DM from "@/Element/Chat/DM";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import WriteMessage from "@/Element/Chat/WriteMessage";
|
||||
import { Chat, createEmptyChatObject, useChatSystem } from "@/chat";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { ChatParticipantProfile } from "./ChatParticipant";
|
||||
|
||||
export default function DmWindow({ id }: { id: string }) {
|
||||
const dms = useChatSystem();
|
||||
const chat = dms.find(a => a.id === id) ?? createEmptyChatObject(id);
|
||||
|
||||
function sender() {
|
||||
if (chat.participants.length === 1) {
|
||||
return <ChatParticipantProfile participant={chat.participants[0]} />;
|
||||
} else {
|
||||
return (
|
||||
<div className="flex -space-x-5 mb-2.5">
|
||||
{chat.participants.map(v => (
|
||||
<ProfileImage pubkey={v.id} showUsername={false} />
|
||||
))}
|
||||
{chat.title ?? <FormattedMessage defaultMessage="Secret Group Chat" id="+Vxixo" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col h-[calc(100vh-62px)] md:h-screen">
|
||||
<div className="p-3">{sender()}</div>
|
||||
<div className="overflow-y-auto hide-scrollbar p-2.5 flex-grow">
|
||||
<div className="flex flex-col">{chat && <DmChatSelected chat={chat} />}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5 p-2.5">
|
||||
<WriteMessage chat={chat} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DmChatSelected({ chat }: { chat: Chat }) {
|
||||
const { publicKey: myPubKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const sortedDms = useMemo(() => {
|
||||
const myDms = chat?.messages;
|
||||
if (myPubKey && myDms) {
|
||||
// filter dms in this chat, or dms to self
|
||||
return [...myDms].sort((a, b) => a.created_at - b.created_at);
|
||||
}
|
||||
return [];
|
||||
}, [chat, myPubKey]);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "instant" });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(() => {
|
||||
scrollToBottom();
|
||||
});
|
||||
|
||||
// Start observing the element that you want to keep in view
|
||||
if (messagesContainerRef.current) {
|
||||
observer.observe(messagesContainerRef.current);
|
||||
}
|
||||
|
||||
// Make sure to scroll to bottom on initial load
|
||||
scrollToBottom();
|
||||
|
||||
// Clean up the observer on component unmount
|
||||
return () => {
|
||||
if (messagesContainerRef.current) {
|
||||
observer.unobserve(messagesContainerRef.current);
|
||||
}
|
||||
};
|
||||
}, [sortedDms]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col" ref={messagesContainerRef}>
|
||||
{sortedDms.map(a => (
|
||||
<DM data={a} key={a.id} chat={chat} />
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
49
packages/app/src/Element/Chat/WriteMessage.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { useState } from "react";
|
||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import Textarea from "../Textarea";
|
||||
import { Chat } from "@/chat";
|
||||
import { AsyncIcon } from "@/Element/Button/AsyncIcon";
|
||||
|
||||
export default function WriteMessage({ chat }: { chat: Chat }) {
|
||||
const [msg, setMsg] = useState("");
|
||||
const { publisher, system } = useEventPublisher();
|
||||
|
||||
async function sendMessage() {
|
||||
if (msg && publisher && chat) {
|
||||
const ev = await chat.createMessage(msg, publisher);
|
||||
await chat.sendMessage(ev, system);
|
||||
setMsg("");
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
setMsg(e.target.value);
|
||||
}
|
||||
|
||||
async function onEnter(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
||||
const isEnter = e.code === "Enter";
|
||||
if (isEnter && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
await sendMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grow">
|
||||
<Textarea
|
||||
autoFocus={true}
|
||||
placeholder=""
|
||||
className=""
|
||||
value={msg}
|
||||
onChange={e => onChange(e)}
|
||||
onKeyDown={e => onEnter(e)}
|
||||
onFocus={() => {
|
||||
// ignored
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<AsyncIcon className="circle flex items-center button" iconName="arrow-right" onClick={() => sendMessage()} />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { useState, ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import Icon from "Icons/Icon";
|
||||
import ShowMore from "Element/ShowMore";
|
||||
import Icon from "@/Icons/Icon";
|
||||
import ShowMore from "@/Element/Event/ShowMore";
|
||||
|
||||
interface CollapsedProps {
|
||||
text?: string;
|
||||
@ -38,15 +39,13 @@ interface CollapsedSectionProps {
|
||||
export const CollapsedSection = ({ title, children, className }: CollapsedSectionProps) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const icon = (
|
||||
<div className={`collapse-icon ${collapsed ? "" : "flip"}`}>
|
||||
<div className={classNames("collapse-icon", { flip: !collapsed })}>
|
||||
<Icon name="arrowFront" />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`collapsable-section${className ? ` ${className}` : ""}`}
|
||||
onClick={() => setCollapsed(!collapsed)}>
|
||||
<div className={classNames("collapsable-section", className)} onClick={() => setCollapsed(!collapsed)}>
|
||||
{title}
|
||||
<CollapsedIcon icon={icon} collapsed={collapsed} />
|
||||
</div>
|
||||
|
@ -1,5 +1,4 @@
|
||||
.copy .copy-body {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--font-color);
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import "./Copy.css";
|
||||
import Icon from "Icons/Icon";
|
||||
import { useCopy } from "useCopy";
|
||||
import classNames from "classnames";
|
||||
import Icon from "@/Icons/Icon";
|
||||
import { useCopy } from "@/useCopy";
|
||||
|
||||
export interface CopyProps {
|
||||
text: string;
|
||||
@ -13,7 +14,7 @@ export default function Copy({ text, maxSize = 32, className }: CopyProps) {
|
||||
const trimmed = text.length > maxSize ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` : text;
|
||||
|
||||
return (
|
||||
<div className={`copy flex pointer g8${className ? ` ${className}` : ""}`} onClick={() => copy(text)}>
|
||||
<div className={classNames("copy flex pointer g8 items-center", className)} onClick={() => copy(text)}>
|
||||
<span className="copy-body">{trimmed}</span>
|
||||
<span className="icon" style={{ color: copied ? "var(--success)" : "var(--highlight)" }}>
|
||||
{copied ? <Icon name="check" size={14} /> : <Icon name="copy-solid" size={14} />}
|
||||
|
@ -1,37 +0,0 @@
|
||||
.dm {
|
||||
margin-top: 16px;
|
||||
min-width: 100px;
|
||||
max-width: 90%;
|
||||
white-space: pre-wrap;
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
.dm a {
|
||||
color: var(--font-color) !important;
|
||||
}
|
||||
|
||||
.dm > div:last-child {
|
||||
color: var(--gray-light);
|
||||
font-size: small;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.dm.other > div:first-child {
|
||||
padding: 12px 16px;
|
||||
background: var(--gray-secondary);
|
||||
border-radius: 16px 16px 16px 0px;
|
||||
}
|
||||
|
||||
.dm.me {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.dm.me > div:first-child {
|
||||
padding: 12px 16px;
|
||||
background: var(--dm-gradient);
|
||||
border-radius: 16px 16px 0px 16px;
|
||||
}
|
||||
|
||||
.dm.me > div:last-child {
|
||||
text-align: end;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { useArticles } from "Feed/ArticlesFeed";
|
||||
import { orderDescending } from "SnortUtils";
|
||||
import Note from "../Note";
|
||||
|
||||
export default function Articles() {
|
||||
const data = useArticles();
|
||||
return (
|
||||
<>
|
||||
{orderDescending(data.data ?? []).map(a => (
|
||||
<Note data={a} key={a.id} related={[]} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
nav.deck {
|
||||
width: 48px;
|
||||
height: calc(100vh - 20px);
|
||||
padding: 10px 8px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav.deck .avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
import Avatar from "Element/Avatar";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import "./Nav.css";
|
||||
import Icon from "Icons/Icon";
|
||||
import { Link } from "react-router-dom";
|
||||
import { profileLink } from "SnortUtils";
|
||||
|
||||
export function DeckNav() {
|
||||
const { publicKey } = useLogin();
|
||||
const profile = useUserProfile(publicKey);
|
||||
|
||||
const unreadDms = 0;
|
||||
const hasNotifications = false;
|
||||
|
||||
return (
|
||||
<nav className="deck flex-column f-space">
|
||||
<div className="flex-column f-center g24">
|
||||
<Link className="btn" to="/messages">
|
||||
<Icon name="mail" size={24} />
|
||||
{unreadDms > 0 && <span className="has-unread"></span>}
|
||||
</Link>
|
||||
<Link className="btn" to="/notifications">
|
||||
<Icon name="bell-02" size={24} />
|
||||
{hasNotifications && <span className="has-unread"></span>}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex-column f-center g16">
|
||||
<Link className="btn" to="/">
|
||||
<Icon name="grid-01" size={24} />
|
||||
</Link>
|
||||
<Link className="btn" to="/settings">
|
||||
<Icon name="settings-02" size={24} />
|
||||
</Link>
|
||||
<Link to={profileLink(publicKey ?? "")}>
|
||||
<Avatar pubkey={publicKey ?? ""} user={profile} />
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
.dm-window {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.dm-window > div:nth-child(1) {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.dm-window > div:nth-child(2) {
|
||||
overflow-y: auto;
|
||||
padding: 0 10px 10px 10px;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.dm-window > div:nth-child(3) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.pfp-overlap .pfp:not(:last-of-type) {
|
||||
margin-right: -20px;
|
||||
}
|
||||
|
||||
.pfp-overlap .avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import "./DmWindow.css";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import DM from "Element/DM";
|
||||
import NoteToSelf from "Element/NoteToSelf";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import WriteMessage from "Element/WriteMessage";
|
||||
import { Chat, ChatParticipant, createEmptyChatObject, useChatSystem } from "chat";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export default function DmWindow({ id }: { id: string }) {
|
||||
const pubKey = useLogin().publicKey;
|
||||
const dms = useChatSystem();
|
||||
const chat = dms.find(a => a.id === id) ?? createEmptyChatObject(id);
|
||||
|
||||
function participant(p: ChatParticipant) {
|
||||
if (p.id === pubKey) {
|
||||
return <NoteToSelf className="f-grow mb-10" pubkey={p.id} />;
|
||||
}
|
||||
if (p.type === "pubkey") {
|
||||
return <ProfileImage pubkey={p.id} className="f-grow mb10" />;
|
||||
}
|
||||
if (p?.profile) {
|
||||
return <ProfileImage pubkey={p.id} className="f-grow mb10" profile={p.profile} />;
|
||||
}
|
||||
return <ProfileImage pubkey={p.id} className="f-grow mb10" overrideUsername={p.id} />;
|
||||
}
|
||||
|
||||
function sender() {
|
||||
if (chat.participants.length === 1) {
|
||||
return participant(chat.participants[0]);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex pfp-overlap mb10">
|
||||
{chat.participants.map(v => (
|
||||
<ProfileImage pubkey={v.id} showUsername={false} />
|
||||
))}
|
||||
{chat.title ?? <FormattedMessage defaultMessage="Secret Group Chat" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dm-window">
|
||||
<div>{sender()}</div>
|
||||
<div>
|
||||
<div className="flex f-col">{chat && <DmChatSelected chat={chat} />}</div>
|
||||
</div>
|
||||
<div>
|
||||
<WriteMessage chat={chat} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DmChatSelected({ chat }: { chat: Chat }) {
|
||||
const { publicKey: myPubKey } = useLogin();
|
||||
const sortedDms = useMemo(() => {
|
||||
const myDms = chat?.messages;
|
||||
if (myPubKey && myDms) {
|
||||
// filter dms in this chat, or dms to self
|
||||
return [...myDms].sort((a, b) => a.created_at - b.created_at);
|
||||
}
|
||||
return [];
|
||||
}, [chat, myPubKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sortedDms.map(a => (
|
||||
<DM data={a} key={a.id} chat={chat} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
8
packages/app/src/Element/Embed/CashuNuts.css
Normal file
@ -0,0 +1,8 @@
|
||||
.cashu {
|
||||
background: var(--cashu-gradient);
|
||||
}
|
||||
|
||||
.cashu h1 {
|
||||
font-size: 44px;
|
||||
line-height: 1em;
|
||||
}
|
139
packages/app/src/Element/Embed/CashuNuts.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import "./CashuNuts.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import Icon from "@/Icons/Icon";
|
||||
|
||||
interface Token {
|
||||
token: Array<{
|
||||
mint: string;
|
||||
proofs: Array<{
|
||||
amount: number;
|
||||
}>;
|
||||
}>;
|
||||
memo?: string;
|
||||
}
|
||||
|
||||
export default function CashuNuts({ token }: { token: string }) {
|
||||
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||
const profile = useUserProfile(publicKey);
|
||||
|
||||
async function copyToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
||||
e.stopPropagation();
|
||||
await navigator.clipboard.writeText(token);
|
||||
}
|
||||
async function redeemToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
||||
e.stopPropagation();
|
||||
const lnurl = profile?.lud16 ?? "";
|
||||
const url = `https://redeem.cashu.me?token=${encodeURIComponent(token)}&lightning=${encodeURIComponent(
|
||||
lnurl,
|
||||
)}&autopay=yes`;
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
|
||||
const [cashu, setCashu] = useState<Token>();
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (!token.startsWith("cashuA") || token.length < 10) {
|
||||
return;
|
||||
}
|
||||
import("@cashu/cashu-ts").then(({ getDecodedToken }) => {
|
||||
const tkn = getDecodedToken(token);
|
||||
setCashu(tkn);
|
||||
});
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
if (!cashu) return <>{token}</>;
|
||||
|
||||
const amount = cashu.token[0].proofs.reduce((acc, v) => acc + v.amount, 0);
|
||||
return (
|
||||
<div className="cashu flex justify-between p24 br">
|
||||
<div className="flex flex-col g8 f-ellipsis">
|
||||
<div className="flex items-center g16">
|
||||
<svg width="30" height="39" viewBox="0 0 30 39" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group 47711">
|
||||
<path
|
||||
id="Rectangle 585"
|
||||
d="M29.3809 2.47055L29.3809 11.7277L26.7913 11.021C23.8493 10.2181 20.727 10.3835 17.8863 11.4929C15.5024 12.4238 12.9113 12.6933 10.3869 12.2728L7.11501 11.7277L7.11501 2.47054L10.3869 3.01557C12.9113 3.43607 15.5024 3.1666 17.8863 2.23566C20.727 1.12632 23.8493 0.960876 26.7913 1.7638L29.3809 2.47055Z"
|
||||
fill="url(#paint0_linear_1976_19241)"
|
||||
/>
|
||||
<path
|
||||
id="Rectangle 587"
|
||||
d="M29.3809 27.9803L29.3809 37.2375L26.7913 36.5308C23.8493 35.7278 20.727 35.8933 17.8863 37.0026C15.5024 37.9336 12.9113 38.203 10.3869 37.7825L7.11501 37.2375L7.11501 27.9803L10.3869 28.5253C12.9113 28.9458 15.5024 28.6764 17.8863 27.7454C20.727 26.6361 23.8493 26.4706 26.7913 27.2736L29.3809 27.9803Z"
|
||||
fill="url(#paint1_linear_1976_19241)"
|
||||
/>
|
||||
<path
|
||||
id="Rectangle 586"
|
||||
d="M8.494e-08 15.2069L4.89585e-07 24.4641L2.5896 23.7573C5.53159 22.9544 8.6539 23.1198 11.4946 24.2292C13.8784 25.1601 16.4695 25.4296 18.9939 25.0091L22.2658 24.4641L22.2658 15.2069L18.9939 15.7519C16.4695 16.1724 13.8784 15.9029 11.4946 14.972C8.6539 13.8627 5.53159 13.6972 2.5896 14.5001L8.494e-08 15.2069Z"
|
||||
fill="url(#paint2_linear_1976_19241)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_1976_19241"
|
||||
x1="29.3809"
|
||||
y1="6.7213"
|
||||
x2="7.11501"
|
||||
y2="6.7213"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_1976_19241"
|
||||
x1="29.3809"
|
||||
y1="32.2311"
|
||||
x2="7.11501"
|
||||
y2="32.2311"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_1976_19241"
|
||||
x1="2.70746e-07"
|
||||
y1="19.4576"
|
||||
x2="22.2658"
|
||||
y2="19.4576"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<FormattedMessage
|
||||
defaultMessage="<h1>{n}</h1> Cashu sats"
|
||||
id="6/SF6e"
|
||||
values={{
|
||||
h1: c => <h1>{c}</h1>,
|
||||
n: <FormattedNumber value={amount} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<small className="xs w-max">
|
||||
<FormattedMessage
|
||||
defaultMessage="<b>Mint:</b> {url}"
|
||||
id="zwb6LR"
|
||||
values={{
|
||||
b: c => <b>{c}</b>,
|
||||
url: new URL(cashu.token[0].mint).hostname,
|
||||
}}
|
||||
/>
|
||||
</small>
|
||||
</div>
|
||||
<div className="flex g8">
|
||||
<button onClick={e => copyToken(e, token)}>
|
||||
<Icon name="copy" />
|
||||
</button>
|
||||
<button onClick={e => redeemToken(e, token)}>
|
||||
<FormattedMessage defaultMessage="Redeem" id="XrSk2j" description="Button: Redeem Cashu token" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
3
packages/app/src/Element/Embed/Hashtag.css
Normal file
@ -0,0 +1,3 @@
|
||||
.hashtag {
|
||||
color: var(--highlight);
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
import { TwitterTweetEmbed } from "react-twitter-embed";
|
||||
|
||||
import {
|
||||
YoutubeUrlRegex,
|
||||
TweetUrlRegex,
|
||||
TidalRegex,
|
||||
SoundCloudRegex,
|
||||
MixCloudRegex,
|
||||
@ -11,31 +8,32 @@ import {
|
||||
AppleMusicRegex,
|
||||
NostrNestsRegex,
|
||||
WavlakeRegex,
|
||||
} from "Const";
|
||||
import { magnetURIDecode } from "SnortUtils";
|
||||
import SoundCloudEmbed from "Element/SoundCloudEmded";
|
||||
import MixCloudEmbed from "Element/MixCloudEmbed";
|
||||
import SpotifyEmbed from "Element/SpotifyEmbed";
|
||||
import TidalEmbed from "Element/TidalEmbed";
|
||||
import TwitchEmbed from "Element/TwitchEmbed";
|
||||
import AppleMusicEmbed from "Element/AppleMusicEmbed";
|
||||
import WavlakeEmbed from "Element/WavlakeEmbed";
|
||||
import LinkPreview from "Element/LinkPreview";
|
||||
import NostrLink from "Element/NostrLink";
|
||||
import MagnetLink from "Element/MagnetLink";
|
||||
} from "@/Const";
|
||||
import { magnetURIDecode } from "@/SnortUtils";
|
||||
import SoundCloudEmbed from "@/Element/Embed/SoundCloudEmded";
|
||||
import MixCloudEmbed from "@/Element/Embed/MixCloudEmbed";
|
||||
import SpotifyEmbed from "@/Element/Embed/SpotifyEmbed";
|
||||
import TidalEmbed from "@/Element/Embed/TidalEmbed";
|
||||
import TwitchEmbed from "@/Element/Embed/TwitchEmbed";
|
||||
import AppleMusicEmbed from "@/Element/Embed/AppleMusicEmbed";
|
||||
import WavlakeEmbed from "@/Element/Embed/WavlakeEmbed";
|
||||
import LinkPreview from "@/Element/Embed/LinkPreview";
|
||||
import NostrLink from "@/Element/Embed/NostrLink";
|
||||
import MagnetLink from "@/Element/Embed/MagnetLink";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface HypeTextProps {
|
||||
link: string;
|
||||
children?: ReactNode | Array<ReactNode> | null;
|
||||
depth?: number;
|
||||
showLinkPreview?: boolean;
|
||||
}
|
||||
|
||||
export default function HyperText({ link, depth, showLinkPreview }: HypeTextProps) {
|
||||
export default function HyperText({ link, depth, showLinkPreview, children }: HypeTextProps) {
|
||||
const a = link;
|
||||
try {
|
||||
const url = new URL(a);
|
||||
const youtubeId = YoutubeUrlRegex.test(a) && RegExp.$1;
|
||||
const tweetId = TweetUrlRegex.test(a) && RegExp.$2;
|
||||
const tidalId = TidalRegex.test(a) && RegExp.$1;
|
||||
const soundcloundId = SoundCloudRegex.test(a) && RegExp.$1;
|
||||
const mixcloudId = MixCloudRegex.test(a) && RegExp.$1;
|
||||
@ -44,13 +42,8 @@ export default function HyperText({ link, depth, showLinkPreview }: HypeTextProp
|
||||
const isAppleMusicLink = AppleMusicRegex.test(a);
|
||||
const isNostrNestsLink = NostrNestsRegex.test(a);
|
||||
const isWavlakeLink = WavlakeRegex.test(a);
|
||||
if (tweetId) {
|
||||
return (
|
||||
<div className="tweet" key={tweetId}>
|
||||
<TwitterTweetEmbed tweetId={tweetId} />
|
||||
</div>
|
||||
);
|
||||
} else if (youtubeId) {
|
||||
|
||||
if (youtubeId) {
|
||||
return (
|
||||
<iframe
|
||||
className="w-max"
|
||||
@ -78,7 +71,7 @@ export default function HyperText({ link, depth, showLinkPreview }: HypeTextProp
|
||||
return (
|
||||
<>
|
||||
<a href={a} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
|
||||
{a}
|
||||
{children ?? a}
|
||||
</a>
|
||||
{/*<NostrNestsEmbed link={a} />,*/}
|
||||
</>
|
||||
@ -100,7 +93,7 @@ export default function HyperText({ link, depth, showLinkPreview }: HypeTextProp
|
||||
}
|
||||
return (
|
||||
<a href={a} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
|
||||
{a}
|
||||
{children ?? a}
|
||||
</a>
|
||||
);
|
||||
}
|
@ -8,6 +8,12 @@
|
||||
background: var(--invoice-gradient);
|
||||
}
|
||||
|
||||
.note-invoice.error {
|
||||
padding: 8px 12px !important;
|
||||
color: #aaa;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.note-invoice.expired {
|
||||
background: var(--expired-invoice-gradient);
|
||||
color: var(--font-secondary-color);
|
@ -3,12 +3,13 @@ import { useState } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { useMemo } from "react";
|
||||
import { decodeInvoice } from "@snort/shared";
|
||||
import classNames from "classnames";
|
||||
|
||||
import SendSats from "Element/SendSats";
|
||||
import Icon from "Icons/Icon";
|
||||
import { useWallet } from "Wallet";
|
||||
import SendSats from "@/Element/SendSats";
|
||||
import Icon from "@/Icons/Icon";
|
||||
import { useWallet } from "@/Wallet";
|
||||
|
||||
import messages from "./messages";
|
||||
import messages from "../messages";
|
||||
|
||||
export interface InvoiceProps {
|
||||
invoice: string;
|
||||
@ -60,7 +61,7 @@ export default function Invoice(props: InvoiceProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`note-invoice flex ${isExpired ? "expired" : ""} ${isPaid ? "paid" : ""}`}>
|
||||
<div className={classNames("note-invoice flex", { expired: isExpired, paid: isPaid })}>
|
||||
<div className="invoice-header">{header()}</div>
|
||||
|
||||
<p className="invoice-amount">
|
||||
@ -75,7 +76,7 @@ export default function Invoice(props: InvoiceProps) {
|
||||
{description && <p>{description}</p>}
|
||||
{isPaid ? (
|
||||
<div className="paid">
|
||||
<FormattedMessage defaultMessage="Paid" />
|
||||
<FormattedMessage defaultMessage="Paid" id="u/vOPu" />
|
||||
</div>
|
||||
) : (
|
||||
<button disabled={isExpired} type="button" onClick={payInvoice}>
|
@ -21,6 +21,8 @@
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: initial;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.link-preview-container:hover .link-preview-title > h1 {
|
||||
@ -40,12 +42,23 @@
|
||||
margin: 0 0 15px 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background-image: var(--img-url);
|
||||
min-height: 250px;
|
||||
min-height: 220px;
|
||||
max-height: 500px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.light .link-preview-container {
|
||||
background: #ddd;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.light .link-preview-container:hover {
|
||||
box-shadow: rgba(0, 0, 0, 0.08) 0 1px 3px;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.link-preview-image {
|
||||
min-height: 342px;
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import "./LinkPreview.css";
|
||||
import { CSSProperties, useEffect, useState } from "react";
|
||||
|
||||
import Spinner from "Icons/Spinner";
|
||||
import SnortApi, { LinkPreviewData } from "SnortApi";
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
import { MediaElement } from "Element/MediaElement";
|
||||
import Spinner from "@/Icons/Spinner";
|
||||
import SnortApi, { LinkPreviewData } from "@/External/SnortApi";
|
||||
import useImgProxy from "@/Hooks/useImgProxy";
|
||||
import { MediaElement } from "@/Element/Embed/MediaElement";
|
||||
|
||||
async function fetchUrlPreviewInfo(url: string) {
|
||||
const api = new SnortApi();
|
||||
@ -81,7 +81,7 @@ const LinkPreview = ({ url }: { url: string }) => {
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
{!preview && <Spinner className="f-center" />}
|
||||
{!preview && <Spinner className="items-center" />}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { Magnet } from "SnortUtils";
|
||||
import { Magnet } from "@/SnortUtils";
|
||||
|
||||
interface MagnetLinkProps {
|
||||
magnet: Magnet;
|
||||
@ -10,7 +10,7 @@ const MagnetLink = ({ magnet }: MagnetLinkProps) => {
|
||||
return (
|
||||
<div className="note-invoice">
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Magnet Link" />
|
||||
<FormattedMessage defaultMessage="Magnet Link" id="Gcn9NQ" />
|
||||
</h4>
|
||||
<a href={magnet.raw} rel="noreferrer">
|
||||
{magnet.dn ?? magnet.infoHash}
|
@ -1,14 +1,7 @@
|
||||
import { ProxyImg } from "Element/ProxyImg";
|
||||
import { ProxyImg } from "@/Element/ProxyImg";
|
||||
import useImgProxy from "@/Hooks/useImgProxy";
|
||||
import React from "react";
|
||||
|
||||
/*
|
||||
[
|
||||
"imeta",
|
||||
"url https://nostr.build/i/148e3e8cbe29ae268b0d6aad0065a086319d3c3b1fdf8b89f1e2327d973d2d05.jpg",
|
||||
"blurhash e6A0%UE2t6D*R%?u?a9G?aM|~pM|%LR*RjR-%2NG%2t7_2R*%1IVWB",
|
||||
"dim 3024x4032"
|
||||
],
|
||||
*/
|
||||
interface MediaElementProps {
|
||||
mime: string;
|
||||
url: string;
|
||||
@ -19,12 +12,14 @@ interface MediaElementProps {
|
||||
}
|
||||
|
||||
export function MediaElement(props: MediaElementProps) {
|
||||
const { proxy } = useImgProxy();
|
||||
|
||||
if (props.mime.startsWith("image/")) {
|
||||
return <ProxyImg key={props.url} src={props.url} onClick={props.onMediaClick} />;
|
||||
} else if (props.mime.startsWith("audio/")) {
|
||||
return <audio key={props.url} src={props.url} controls />;
|
||||
} else if (props.mime.startsWith("video/")) {
|
||||
return <video key={props.url} src={props.url} controls />;
|
||||
return <video key={props.url} src={props.url} controls poster={proxy(props.url)} />;
|
||||
} else {
|
||||
return (
|
||||
<a
|
35
packages/app/src/Element/Embed/Mention.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { NostrLink, NostrPrefix } from "@snort/system";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
|
||||
import DisplayName from "@/Element/User/DisplayName";
|
||||
import { ProfileCard } from "@/Element/User/ProfileCard";
|
||||
import { ProfileLink } from "@/Element/User/ProfileLink";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
export default function Mention({ link }: { link: NostrLink }) {
|
||||
const profile = useUserProfile(link.id);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
hoverTimeoutRef.current && clearTimeout(hoverTimeoutRef.current);
|
||||
hoverTimeoutRef.current = setTimeout(() => setIsHovering(true), 100); // Adjust timeout as needed
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
hoverTimeoutRef.current && clearTimeout(hoverTimeoutRef.current);
|
||||
hoverTimeoutRef.current = setTimeout(() => setIsHovering(false), 300); // Adjust timeout as needed
|
||||
}, []);
|
||||
|
||||
if (link.type !== NostrPrefix.Profile && link.type !== NostrPrefix.PublicKey) return;
|
||||
|
||||
return (
|
||||
<span className="highlight" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
<ProfileLink pubkey={link.id} link={link} user={profile} onClick={e => e.stopPropagation()}>
|
||||
@<DisplayName user={profile} pubkey={link.id} />
|
||||
</ProfileLink>
|
||||
{isHovering && <ProfileCard pubkey={link.id} user={profile} show={true} />}
|
||||
</span>
|
||||
);
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import { MixCloudRegex } from "Const";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { MixCloudRegex } from "@/Const";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
|
||||
const MixCloudEmbed = ({ link }: { link: string }) => {
|
||||
const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2);
|
||||
|
||||
const lightTheme = useLogin().preferences.theme === "light";
|
||||
const lightParams = lightTheme ? "light=1" : "light=0";
|
||||
const theme = useLogin(s => s.appData.item.preferences.theme);
|
||||
const lightParams = theme === "light" ? "light=1" : "light=0";
|
||||
return (
|
||||
<>
|
||||
<br />
|