Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57447e5bf4 | |||
| 8e411daaef | |||
| 183aebf841 | |||
| e3e500ccc2 | |||
| e7a2535ece | |||
| 761e369313 | |||
| 5248275d73 | |||
| cb033279dd | |||
| 41d50d8c28 | |||
| a52c2b4c3c | |||
| b5917cb184 | |||
| 57348472f8 | |||
| 4b6223dc00 |
@@ -1,5 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
# 1.5.3
|
||||
|
||||
* Add space edit form
|
||||
* Improve room syncing
|
||||
* Return better blossom errors
|
||||
* Fix access restricted bugs
|
||||
* Add room detail dialog
|
||||
* Fix broken link to self hosting
|
||||
* Tweak shadows
|
||||
* Always join spaces when visiting them
|
||||
|
||||
# 1.5.2
|
||||
|
||||
* Fix negentropy room syncing
|
||||
|
||||
# 1.5.1
|
||||
|
||||
* Fix chat path link
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "social.flotilla"
|
||||
minSdk rootProject.ext.minSdkVersion
|
||||
targetSdk rootProject.ext.targetSdkVersion
|
||||
versionCode 33
|
||||
versionName "1.5.2"
|
||||
versionCode 34
|
||||
versionName "1.5.3"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -358,14 +358,14 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.5.2;
|
||||
MARKETING_VERSION = 1.5.3;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -384,14 +384,14 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.5.2;
|
||||
MARKETING_VERSION = 1.5.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "flotilla",
|
||||
"version": "1.5.2",
|
||||
"version": "1.5.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -60,16 +60,16 @@
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@vite-pwa/assets-generator": "^0.2.6",
|
||||
"@vite-pwa/sveltekit": "^0.6.8",
|
||||
"@welshman/app": "^0.6.5",
|
||||
"@welshman/content": "^0.6.5",
|
||||
"@welshman/editor": "^0.6.5",
|
||||
"@welshman/feeds": "^0.6.5",
|
||||
"@welshman/lib": "^0.6.5",
|
||||
"@welshman/net": "^0.6.5",
|
||||
"@welshman/router": "^0.6.5",
|
||||
"@welshman/signer": "^0.6.5",
|
||||
"@welshman/store": "^0.6.5",
|
||||
"@welshman/util": "^0.6.5",
|
||||
"@welshman/app": "^0.6.8",
|
||||
"@welshman/content": "^0.6.8",
|
||||
"@welshman/editor": "^0.6.8",
|
||||
"@welshman/feeds": "^0.6.8",
|
||||
"@welshman/lib": "^0.6.8",
|
||||
"@welshman/net": "^0.6.8",
|
||||
"@welshman/router": "^0.6.8",
|
||||
"@welshman/signer": "^0.6.8",
|
||||
"@welshman/store": "^0.6.8",
|
||||
"@welshman/util": "^0.6.8",
|
||||
"compressorjs": "^1.2.1",
|
||||
"daisyui": "^4.12.24",
|
||||
"date-picker-svelte": "^2.16.0",
|
||||
|
||||
Generated
+76
-76
@@ -72,35 +72,35 @@ importers:
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(@sveltejs/kit@2.46.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.12)(vite@5.4.20(@types/node@24.7.2)(terser@5.44.0)))(svelte@5.39.12)(vite@5.4.20(@types/node@24.7.2)(terser@5.44.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.20(@types/node@24.7.2)(terser@5.44.0))(workbox-build@7.3.0)(workbox-window@7.3.0))
|
||||
'@welshman/app':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/content':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(typescript@5.9.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(typescript@5.9.3)
|
||||
'@welshman/editor':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(@tiptap/extension-image@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))(@tiptap/pm@2.26.3))(linkifyjs@4.3.2)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.3)(tiptap-markdown@0.8.10(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(typescript@5.9.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(@tiptap/extension-image@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))(@tiptap/pm@2.26.3))(linkifyjs@4.3.2)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.3)(tiptap-markdown@0.8.10(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(typescript@5.9.3)
|
||||
'@welshman/feeds':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/lib':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8
|
||||
'@welshman/net':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/router':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/signer':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/store':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util':
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(typescript@5.9.3)
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(typescript@5.9.3)
|
||||
compressorjs:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
@@ -1692,38 +1692,38 @@ packages:
|
||||
'@vite-pwa/assets-generator':
|
||||
optional: true
|
||||
|
||||
'@welshman/app@0.6.5':
|
||||
resolution: {integrity: sha512-hk39kKzptldZxtFbYzgrEK8Y151o75GwV6P2sK5LlkyafWlhx3SwteAcuNelcJZitoAPXi7w06W34bbwRYPx+Q==}
|
||||
'@welshman/app@0.6.8':
|
||||
resolution: {integrity: sha512-bhl18VWA9tzHLY7D+b2xlkc/RbJr03XiA7+otcjzf8X48S4pih/F4TDw1yJbAWOMOx9G3NI6sWLffpZQeSUPiQ==}
|
||||
|
||||
'@welshman/content@0.6.5':
|
||||
resolution: {integrity: sha512-QSlkuko+2r72q3VFlOXpnnoJ6GioCgan1ysHMlKqKarKNFTL4kfqdq1yxYrFRJdQou7WuB+f9iULO0AFWkXmXg==}
|
||||
'@welshman/content@0.6.8':
|
||||
resolution: {integrity: sha512-VLek8oOoMMTrEtpIfqFqM9BsbifWYwPC7UiuVuWYqaTSmiAbU3DM2J+tYFcrgnQF8xMnUi/JoVXJ+b2AtpjFrw==}
|
||||
|
||||
'@welshman/editor@0.6.5':
|
||||
resolution: {integrity: sha512-3sUnUFBeaVJbJgnkZlIqFqXv/NtnxXt3Pr6BkMYi2ocDMxHsMOIsOrCcyoXg8G5IYz7FzkWHtUtM3mhaDU7YVg==}
|
||||
'@welshman/editor@0.6.8':
|
||||
resolution: {integrity: sha512-QzNX7/Nobkh+bpjFnuW2REVpX7Sa+lj70LDdGmEJpjtXlTKlLuNZzpFLee5F9fSObcKCl1G2xBN3tYbZD3vHUA==}
|
||||
|
||||
'@welshman/feeds@0.6.5':
|
||||
resolution: {integrity: sha512-IT1kSN+Xf/MaoHAOHJftORDwJZxl3UCLizc+mvJ4ktvOT/oVu9YX5zcb0YMwiJN2N4C4FpK/BIBjxivS6QIaRQ==}
|
||||
'@welshman/feeds@0.6.8':
|
||||
resolution: {integrity: sha512-95VRR2QmGrBUyzYgdsMxhntVoOnaEMsMHRsci1/GX1oOFZPJFTiV7e/m/dD/aWVLUQV1hlRxxXosFFtEDkpjIw==}
|
||||
|
||||
'@welshman/lib@0.6.5':
|
||||
resolution: {integrity: sha512-L6NQm1QNBOTQ+ymiSFPfL+TDiW1AP64AEp633Fh1ciopaU73JFbV0P6xpLxt3qJkQFZfJRxk7gWDMaVDo3Y24w==}
|
||||
'@welshman/lib@0.6.8':
|
||||
resolution: {integrity: sha512-1Wybkk8+vBdqv9nRhnNwIW9YVbhu3di07A2fUYWAQvldto49X26U8u7EV2CkUsz4iNC/799EBYuelcc6W9oZYw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
'@welshman/net@0.6.5':
|
||||
resolution: {integrity: sha512-JcmPdWzT0aUaOWytw4EJpl9EooOstm4LcJczc9pJYk1hQE4gDix1AfW6bqBiDlTnJ5fplOW/KLgXuV1rhsKEWA==}
|
||||
'@welshman/net@0.6.8':
|
||||
resolution: {integrity: sha512-Lc1nIckdxW2ILiknowcbaKo+192QWQOBn6FLhFCEUZNyRNEOJYkAgDu4jKn7GXu91xpfJUFnq5KDvvq7hUeHqg==}
|
||||
|
||||
'@welshman/router@0.6.5':
|
||||
resolution: {integrity: sha512-7ZxAkCg09ZIeYh49LUlL7nFRvU4880DsMstEu2KRQQIO/wg6VZuMJh8+uKGQq5arul09rtNl1bhg0/YUFiAc/A==}
|
||||
'@welshman/router@0.6.8':
|
||||
resolution: {integrity: sha512-+OJoD2Jm+yFiLc5FYb4/za66639CeIMYk7j4UAzR7n1z/gFQYMDviXqFYbcWHln3fgy4G7UF1HWBoU0sQD8EEw==}
|
||||
|
||||
'@welshman/signer@0.6.5':
|
||||
resolution: {integrity: sha512-SgQCtb0du3vpyaRVGXM43CM5S0fTh+1WWLnZEWUBkEpyRzRtI9DegTowL6+vhbNxsWB6oHn/FqY7O3HLS5rIEw==}
|
||||
'@welshman/signer@0.6.8':
|
||||
resolution: {integrity: sha512-lt9Qq89TWyx/zSWgHkeVUX7MBCx86iBCkvzTdUIS7Ad6KfjjcYtsL9wAtfCc+TlvE87okOg97hAOvw18yIwfbw==}
|
||||
peerDependencies:
|
||||
nostr-signer-capacitor-plugin: ~0.0.4
|
||||
|
||||
'@welshman/store@0.6.5':
|
||||
resolution: {integrity: sha512-Fdl8ygK2/pZRxbLGSWtJJGtf2wTm46RDrCF2zURDJL6e80NGmTXl7LhqpSeRKmR1sTQiwEhsRvD1lqnKAWB3xQ==}
|
||||
'@welshman/store@0.6.8':
|
||||
resolution: {integrity: sha512-s5s5+tdPyXB1m2vLn2wfo7nx+uNKWBdwCyomk+soWKWEY3LWvg4DAKgQ1gF5hyOcja+UIHOJY9hS3BBEo0DtDA==}
|
||||
|
||||
'@welshman/util@0.6.5':
|
||||
resolution: {integrity: sha512-BmKgDtgSk0RSnw3YyExN8Mm25TJuJnjvE/7foTENpf2bMo2+PTXwVERNmgDEHRq9MCbmTkPv4h7kJTbpywLMVA==}
|
||||
'@welshman/util@0.6.8':
|
||||
resolution: {integrity: sha512-Q4x3Jm3yIk4zORYOscMuxyC7fJGyZFetE5U4PVYNrvgtSLCtULYKs1y6WkAra4FD7zfAa7lqzTlQq4uIZWzdkA==}
|
||||
|
||||
'@xml-tools/parser@1.0.11':
|
||||
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
|
||||
@@ -6651,16 +6651,16 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@vite-pwa/assets-generator': 0.2.6
|
||||
|
||||
'@welshman/app@0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)':
|
||||
'@welshman/app@0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@types/throttle-debounce': 5.0.2
|
||||
'@welshman/feeds': 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/net': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/router': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/signer': 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/store': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
||||
'@welshman/feeds': 0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/lib': 0.6.8
|
||||
'@welshman/net': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/router': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/signer': 0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/store': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||
fuse.js: 7.1.0
|
||||
svelte: 4.2.20
|
||||
throttle-debounce: 5.0.2
|
||||
@@ -6669,14 +6669,14 @@ snapshots:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/content@0.6.5(typescript@5.9.3)':
|
||||
'@welshman/content@0.6.8(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@braintree/sanitize-url': 7.1.1
|
||||
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@welshman/editor@0.6.5(@tiptap/extension-image@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))(@tiptap/pm@2.26.3))(linkifyjs@4.3.2)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.3)(tiptap-markdown@0.8.10(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(typescript@5.9.3)':
|
||||
'@welshman/editor@0.6.8(@tiptap/extension-image@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))(@tiptap/pm@2.26.3))(linkifyjs@4.3.2)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.3)(tiptap-markdown@0.8.10(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.26.3(@tiptap/pm@2.26.3)
|
||||
'@tiptap/extension-code': 2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))
|
||||
@@ -6691,8 +6691,8 @@ snapshots:
|
||||
'@tiptap/extension-text': 2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))
|
||||
'@tiptap/pm': 2.26.3
|
||||
'@tiptap/suggestion': 2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))(@tiptap/pm@2.26.3)
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
||||
'@welshman/lib': 0.6.8
|
||||
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||
nostr-editor: 1.0.2(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))(@tiptap/extension-image@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.26.3(@tiptap/pm@2.26.3))(@tiptap/pm@2.26.3))(@tiptap/pm@2.26.3)(linkifyjs@4.3.2)(nostr-tools@2.17.0(typescript@5.9.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.3)(tiptap-markdown@0.8.10(@tiptap/core@2.26.3(@tiptap/pm@2.26.3)))
|
||||
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||
tippy.js: 6.3.7
|
||||
@@ -6707,71 +6707,71 @@ snapshots:
|
||||
- tiptap-markdown
|
||||
- typescript
|
||||
|
||||
'@welshman/feeds@0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)':
|
||||
'@welshman/feeds@0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/net': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/router': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/signer': 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
||||
'@welshman/lib': 0.6.8
|
||||
'@welshman/net': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/router': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/signer': 0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||
trava: 1.2.1
|
||||
transitivePeerDependencies:
|
||||
- nostr-signer-capacitor-plugin
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/lib@0.6.5':
|
||||
'@welshman/lib@0.6.8':
|
||||
dependencies:
|
||||
'@scure/base': 1.2.6
|
||||
'@types/events': 3.0.3
|
||||
events: 3.3.0
|
||||
|
||||
'@welshman/net@0.6.5(typescript@5.9.3)(ws@8.18.3)':
|
||||
'@welshman/net@0.6.8(typescript@5.9.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
||||
'@welshman/lib': 0.6.8
|
||||
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||
events: 3.3.0
|
||||
isomorphic-ws: 5.0.0(ws@8.18.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/router@0.6.5(typescript@5.9.3)(ws@8.18.3)':
|
||||
'@welshman/router@0.6.8(typescript@5.9.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/net': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
||||
'@welshman/lib': 0.6.8
|
||||
'@welshman/net': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/signer@0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)':
|
||||
'@welshman/signer@0.6.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@noble/curves': 1.9.7
|
||||
'@noble/hashes': 1.8.0
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/net': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
||||
'@welshman/lib': 0.6.8
|
||||
'@welshman/net': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.4.3)
|
||||
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/store@0.6.5(typescript@5.9.3)(ws@8.18.3)':
|
||||
'@welshman/store@0.6.8(typescript@5.9.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/net': 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
||||
'@welshman/lib': 0.6.8
|
||||
'@welshman/net': 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||
svelte: 4.2.20
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/util@0.6.5(typescript@5.9.3)':
|
||||
'@welshman/util@0.6.8(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@types/ws': 8.18.1
|
||||
'@welshman/lib': 0.6.5
|
||||
'@welshman/lib': 0.6.8
|
||||
js-base64: 3.7.8
|
||||
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||
nostr-wasm: 0.1.0
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</script>
|
||||
|
||||
<div class="col-4">
|
||||
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
|
||||
<div class="card2 bg-alt flex flex-col gap-6 shadow-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<strong class="flex items-center gap-3">
|
||||
<Icon icon={Inbox} />
|
||||
@@ -108,7 +108,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card2 bg-alt flex flex-col gap-4 shadow-xl">
|
||||
<div class="card2 bg-alt flex flex-col gap-4 shadow-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<strong class="flex items-center gap-3">
|
||||
<Icon icon={Bell} />
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</script>
|
||||
|
||||
<Link
|
||||
class="col-3 card2 bg-alt w-full cursor-pointer shadow-xl"
|
||||
class="col-3 card2 bg-alt w-full cursor-pointer shadow-md"
|
||||
href={makeCalendarPath(url, event.id)}>
|
||||
<CalendarEventHeader {event} />
|
||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
||||
|
||||
@@ -2,21 +2,14 @@
|
||||
import {type Instance} from "tippy.js"
|
||||
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {
|
||||
thunks,
|
||||
mergeThunks,
|
||||
pubkey,
|
||||
deriveProfile,
|
||||
deriveProfileDisplay,
|
||||
sendWrapped,
|
||||
} from "@welshman/app"
|
||||
import {thunks, mergeThunks, pubkey, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
||||
import {isMobile} from "@lib/html"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||
@@ -37,7 +30,6 @@
|
||||
const {event, replyTo, pubkeys, showPubkey = false}: Props = $props()
|
||||
|
||||
const isOwn = event.pubkey === $pubkey
|
||||
const profile = deriveProfile(event.pubkey)
|
||||
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
||||
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
||||
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
||||
@@ -107,8 +99,8 @@
|
||||
<div class="flex items-center gap-2">
|
||||
{#if !isOwn}
|
||||
<Button onclick={openProfile} class="flex items-center gap-1">
|
||||
<Avatar
|
||||
src={$profile?.picture}
|
||||
<ProfileCircle
|
||||
pubkey={event.pubkey}
|
||||
class="border border-solid border-base-content"
|
||||
size={4} />
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl" bind:this={ul}>
|
||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-md" bind:this={ul}>
|
||||
<li>
|
||||
<Button onclick={createGoal}>
|
||||
<Icon size={4} icon={StarFallMinimalistic} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import {removeUndefined} from "@welshman/lib"
|
||||
import type {ProfilePointer} from "@welshman/content"
|
||||
import {deriveProfileDisplay} from "@welshman/app"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
const {value, url}: Props = $props()
|
||||
|
||||
const display = deriveProfileDisplay(value.pubkey, removeNil([url]))
|
||||
const display = deriveProfileDisplay(value.pubkey, removeUndefined([url]))
|
||||
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
|
||||
</script>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl" bind:this={ul}>
|
||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-md" bind:this={ul}>
|
||||
{#if isRoot}
|
||||
<li>
|
||||
<Button onclick={share}>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
const h = getTagValue("h", event.tags)
|
||||
</script>
|
||||
|
||||
<Link class="col-2 card2 bg-alt w-full cursor-pointer shadow-xl" href={makeGoalPath(url, event.id)}>
|
||||
<Link class="col-2 card2 bg-alt w-full cursor-pointer shadow-md" href={makeGoalPath(url, event.id)}>
|
||||
<p class="text-2xl">{event.content}</p>
|
||||
<Content
|
||||
event={{content: summary, tags: event.tags}}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-96 rounded-box bg-base-100 p-4 shadow-lg">
|
||||
<div class="w-96 rounded-box bg-base-100 p-4 shadow-2xl">
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<Icon icon={Magnifier} />
|
||||
<input bind:value={searchTerm} class="grow" type="text" placeholder="Search icons..." />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||
import SquareTopDown from "@assets/icons/square-top-down.svg?dataurl"
|
||||
import Exit from "@assets/icons/logout-3.svg?dataurl"
|
||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||
import Login from "@assets/icons/login-3.svg?dataurl"
|
||||
@@ -133,7 +134,7 @@
|
||||
<Popover hideOnClick onClose={toggleMenu}>
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-xl">
|
||||
class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-md">
|
||||
<li>
|
||||
<Button onclick={createInvite}>
|
||||
<Icon icon={LinkRound} />
|
||||
@@ -157,6 +158,7 @@
|
||||
<Link external href="https://landlubber.coracle.social">
|
||||
<Icon icon={Tuning2} />
|
||||
Manage Space
|
||||
<Icon icon={SquareTopDown} size={4} class="opacity-50" />
|
||||
</Link>
|
||||
</li>
|
||||
{:else if $relay?.pubkey}
|
||||
@@ -184,7 +186,7 @@
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex max-h-[calc(100vh-150px)] min-h-0 flex-col gap-1 overflow-auto">
|
||||
<div class="flex max-h-[calc(100vh-250px)] min-h-0 flex-col gap-1 overflow-auto">
|
||||
{#if hasNip29($relay)}
|
||||
<SecondaryNavItem {replaceState} href={makeSpacePath(url, "recent")}>
|
||||
<Icon icon={History} /> Recent Activity
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import CardButton from "@lib/components/CardButton.svelte"
|
||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
||||
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
@@ -13,9 +13,9 @@
|
||||
</script>
|
||||
|
||||
<Link replaceState href={path}>
|
||||
<CardButton class="btn-neutral">
|
||||
<CardButton class="btn-neutral shadow-md">
|
||||
{#snippet icon()}
|
||||
<div><SpaceAvatar {url} /></div>
|
||||
<RelayIcon {url} size={12} />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<div class="flex gap-1">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
|
||||
</script>
|
||||
|
||||
<div class="card2 bg-alt flex flex-col gap-4 shadow-xl">
|
||||
<div class="card2 bg-alt flex flex-col gap-4 shadow-md">
|
||||
<div class="flex justify-between">
|
||||
<Profile {pubkey} {url} />
|
||||
<Button onclick={openProfile} class="btn btn-primary hidden sm:flex">
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
import {goto} from "$app/navigation"
|
||||
import {splitAt} from "@welshman/lib"
|
||||
import {userProfile, shouldUnwrap} from "@welshman/app"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import Widget from "@assets/icons/widget.svg?dataurl"
|
||||
import Compass from "@assets/icons/compass.svg?dataurl"
|
||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
|
||||
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||
import Settings from "@assets/icons/settings.svg?dataurl"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
||||
import ChatEnable from "@app/components/ChatEnable.svelte"
|
||||
@@ -15,13 +23,6 @@
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
import {notifications} from "@app/util/notifications"
|
||||
import Widget from "@assets/icons/widget.svg?dataurl"
|
||||
import Compass from "@assets/icons/compass.svg?dataurl"
|
||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
|
||||
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||
import Settings from "@assets/icons/settings.svg?dataurl"
|
||||
|
||||
type Props = {
|
||||
children?: Snippet
|
||||
@@ -61,7 +62,7 @@
|
||||
<PrimaryNavItemSpace {url} />
|
||||
{:else}
|
||||
<PrimaryNavItem title="Home" href="/home" class="tooltip-right">
|
||||
<Avatar src={PLATFORM_LOGO} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Home" src={PLATFORM_LOGO} class="rounded-full" />
|
||||
</PrimaryNavItem>
|
||||
<Divider />
|
||||
{#each primarySpaceUrls as url (url)}
|
||||
@@ -73,11 +74,11 @@
|
||||
class="tooltip-right"
|
||||
onclick={showOtherSpacesMenu}
|
||||
notification={otherSpaceNotifications}>
|
||||
<Avatar icon={Widget} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Other Spaces" src={Widget} />
|
||||
</PrimaryNavItem>
|
||||
{/if}
|
||||
<PrimaryNavItem title="Add a Space" href="/discover" class="tooltip-right">
|
||||
<Avatar icon={Compass} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Add a Space" src={Compass} size={7} />
|
||||
</PrimaryNavItem>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -90,17 +91,17 @@
|
||||
href="/settings/profile"
|
||||
prefix="/settings"
|
||||
class="tooltip-right">
|
||||
<Avatar src={$userProfile?.picture} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Settings" src={$userProfile?.picture || UserRounded} class="rounded-full" />
|
||||
</PrimaryNavItem>
|
||||
<PrimaryNavItem
|
||||
title="Messages"
|
||||
onclick={openChat}
|
||||
class="tooltip-right"
|
||||
notification={$notifications.has("/chat")}>
|
||||
<Avatar icon={Letter} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Messages" src={Letter} size={7} />
|
||||
</PrimaryNavItem>
|
||||
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
|
||||
<Avatar icon={Magnifier} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Search" src={Magnifier} size={7} />
|
||||
</PrimaryNavItem>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,22 +116,26 @@
|
||||
<div class="content-padding-x content-sizing flex justify-between px-2">
|
||||
<div class="flex gap-2 sm:gap-6">
|
||||
<PrimaryNavItem title="Home" href="/home">
|
||||
<Avatar icon={HomeSmile} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Home" src={HomeSmile} size={7} />
|
||||
</PrimaryNavItem>
|
||||
<PrimaryNavItem
|
||||
title="Messages"
|
||||
onclick={openChat}
|
||||
notification={$notifications.has("/chat")}>
|
||||
<Avatar icon={Letter} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Messages" src={Letter} size={7} />
|
||||
</PrimaryNavItem>
|
||||
{#if PLATFORM_RELAYS.length !== 1}
|
||||
<PrimaryNavItem title="Spaces" href="/spaces" notification={anySpaceNotifications}>
|
||||
<Avatar icon={SettingsMinimalistic} class="!h-10 !w-10" />
|
||||
<ImageIcon alt="Spaces" src={SettingsMinimalistic} size={7} />
|
||||
</PrimaryNavItem>
|
||||
{/if}
|
||||
</div>
|
||||
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
||||
<Avatar icon={Settings} src={$userProfile?.picture} class="!h-10 !w-10" />
|
||||
<ImageIcon
|
||||
alt="Settings"
|
||||
src={$userProfile?.picture || Settings}
|
||||
size={7}
|
||||
class="rounded-full" />
|
||||
</PrimaryNavItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
||||
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||
import {makeSpacePath, goToSpace} from "@app/util/routes"
|
||||
import {notifications} from "@app/util/notifications"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
title={displayRelayUrl(url)}
|
||||
class="tooltip-right"
|
||||
notification={$notifications.has(makeSpacePath(url))}>
|
||||
<SpaceAvatar {url} />
|
||||
<RelayIcon {url} size={10} class="rounded-full" />
|
||||
</PrimaryNavItem>
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
<script lang="ts">
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import {removeUndefined} from "@welshman/lib"
|
||||
import {displayPubkey} from "@welshman/util"
|
||||
import {
|
||||
deriveHandleForPubkey,
|
||||
displayHandle,
|
||||
deriveProfile,
|
||||
deriveProfileDisplay,
|
||||
} from "@welshman/app"
|
||||
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import WotScore from "@app/components/WotScore.svelte"
|
||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
@@ -26,8 +21,7 @@
|
||||
|
||||
const {pubkey, url, showPubkey, avatarSize = 10}: Props = $props()
|
||||
|
||||
const relays = removeNil([url])
|
||||
const profile = deriveProfile(pubkey, relays)
|
||||
const relays = removeUndefined([url])
|
||||
const profileDisplay = deriveProfileDisplay(pubkey, relays)
|
||||
const handle = deriveHandleForPubkey(pubkey)
|
||||
|
||||
@@ -38,7 +32,7 @@
|
||||
|
||||
<div class="flex max-w-full items-start gap-3">
|
||||
<Button onclick={openProfile} class="py-1">
|
||||
<Avatar src={$profile?.picture} size={avatarSize} />
|
||||
<ProfileCircle {pubkey} size={avatarSize} />
|
||||
</Button>
|
||||
<div class="flex min-w-0 flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
<script lang="ts">
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import cx from "classnames"
|
||||
import {removeUndefined} from "@welshman/lib"
|
||||
import {deriveProfile} from "@welshman/app"
|
||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
|
||||
type Props = {
|
||||
pubkey: string
|
||||
class?: string
|
||||
size?: number
|
||||
url?: string
|
||||
} & Record<string, any>
|
||||
}
|
||||
|
||||
const {pubkey, url, ...props}: Props = $props()
|
||||
const {pubkey, url, size = 7, ...props}: Props = $props()
|
||||
|
||||
const profile = deriveProfile(pubkey, removeNil([url]))
|
||||
const profile = deriveProfile(pubkey, removeUndefined([url]))
|
||||
</script>
|
||||
|
||||
<Avatar src={$profile?.picture} icon={UserCircle} {...props} />
|
||||
<ImageIcon
|
||||
{size}
|
||||
class={cx(props.class, "rounded-full")}
|
||||
src={$profile?.picture || UserRounded}
|
||||
alt="Profile picture" />
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<script lang="ts">
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
|
||||
const {...props} = $props()
|
||||
type Props = {
|
||||
pubkeys: string[]
|
||||
size?: number
|
||||
}
|
||||
|
||||
const {pubkeys, size = 7}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex pr-3">
|
||||
{#each props.pubkeys.toSorted().slice(0, 15) as pubkey (pubkey)}
|
||||
{#each pubkeys.toSorted().slice(0, 15) as pubkey (pubkey)}
|
||||
<div class="z-feature -mr-3 inline-block">
|
||||
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {...props} />
|
||||
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {size} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
@@ -41,7 +41,7 @@
|
||||
</Button>
|
||||
<div class="flex gap-2">
|
||||
<Link external href={pubkeyLink(pubkey)} class="btn btn-neutral">
|
||||
<Avatar src="/coracle.png" />
|
||||
<ImageIcon alt="Open in Coracle" src="/coracle.png" />
|
||||
Open in Coracle
|
||||
</Link>
|
||||
<Button onclick={openChat} class="btn btn-primary">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import {removeUndefined} from "@welshman/lib"
|
||||
import {deriveProfile} from "@welshman/app"
|
||||
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
const {pubkey, url}: Props = $props()
|
||||
|
||||
const profile = deriveProfile(pubkey, removeNil([url]))
|
||||
const profile = deriveProfile(pubkey, removeUndefined([url]))
|
||||
</script>
|
||||
|
||||
{#if $profile}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import {removeUndefined} from "@welshman/lib"
|
||||
import {deriveProfileDisplay} from "@welshman/app"
|
||||
|
||||
type Props = {
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
const {pubkey, url}: Props = $props()
|
||||
|
||||
const profileDisplay = deriveProfileDisplay(pubkey, removeNil([url]))
|
||||
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url]))
|
||||
</script>
|
||||
|
||||
{$profileDisplay}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
||||
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
import {deriveGroupSelections, getSpaceUrlsFromGroupSelections} from "@app/core/state"
|
||||
@@ -26,7 +26,7 @@
|
||||
{#each spaceUrls as url (url)}
|
||||
<div class="card2 bg-alt flex flex-row items-center gap-2">
|
||||
<div class="flex-shrink-0">
|
||||
<SpaceAvatar {url} />
|
||||
<RelayIcon {url} size={12} />
|
||||
</div>
|
||||
<div class="flex flex-grow flex-col">
|
||||
<RelayName {url} />
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import {deriveRelay} from "@welshman/app"
|
||||
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
size?: number
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {url, size = 7, ...props}: Props = $props()
|
||||
|
||||
const relay = deriveRelay(url)
|
||||
</script>
|
||||
|
||||
<ImageIcon
|
||||
{size}
|
||||
src={$relay?.icon || RemoteControllerMinimalistic}
|
||||
alt="Relay image"
|
||||
class={props.class} />
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {deriveRelayDisplay} from "@welshman/app"
|
||||
|
||||
const {url} = $props()
|
||||
type Props = {
|
||||
url: string
|
||||
}
|
||||
|
||||
const {url}: Props = $props()
|
||||
|
||||
const display = $derived(deriveRelayDisplay(url))
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import EyeClosed from "@assets/icons/eye-closed.svg?dataurl"
|
||||
import Lock from "@assets/icons/lock.svg?dataurl"
|
||||
import Microphone from "@assets/icons/microphone.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import {deriveRoom} from "@app/core/state"
|
||||
|
||||
interface Props {
|
||||
h: any
|
||||
url: any
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
|
||||
const room = deriveRoom(url, h)
|
||||
</script>
|
||||
|
||||
{#if $room.isHidden}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="This room is not visible to non-members.">
|
||||
<Icon size={4} icon={EyeClosed} />
|
||||
</Button>
|
||||
{:else if $room.isPrivate}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Only members can view messages.">
|
||||
<Icon size={4} icon={Lock} />
|
||||
</Button>
|
||||
{:else if $room.isRestricted}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Only members can send messages.">
|
||||
<Icon size={4} icon={Microphone} />
|
||||
</Button>
|
||||
{/if}
|
||||
@@ -0,0 +1,190 @@
|
||||
<script lang="ts">
|
||||
import {goto} from "$app/navigation"
|
||||
import type {RoomMeta} from "@welshman/util"
|
||||
import {displayRelayUrl, makeRoomMeta} from "@welshman/util"
|
||||
import type {Thunk} from "@welshman/app"
|
||||
import {deleteRoom, waitForThunkError, repository, joinRoom, leaveRoom} from "@welshman/app"
|
||||
import Pen from "@assets/icons/pen.svg?dataurl"
|
||||
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||
import Login3 from "@assets/icons/login-3.svg?dataurl"
|
||||
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
import EyeClosed from "@assets/icons/eye-closed.svg?dataurl"
|
||||
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
|
||||
import Lock from "@assets/icons/lock.svg?dataurl"
|
||||
import Microphone from "@assets/icons/microphone.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Confirm from "@lib/components/Confirm.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
||||
import ProfileList from "@app/components/ProfileList.svelte"
|
||||
import RoomEdit from "@app/components/RoomEdit.svelte"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import RoomImage from "@app/components/RoomImage.svelte"
|
||||
import {
|
||||
deriveRoom,
|
||||
deriveRoomMembers,
|
||||
deriveUserIsRoomAdmin,
|
||||
deriveUserRoomMembershipStatus,
|
||||
MembershipStatus,
|
||||
} from "@app/core/state"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
h: string
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
|
||||
const room = deriveRoom(url, h)
|
||||
const members = deriveRoomMembers(url, h)
|
||||
const userIsAdmin = deriveUserIsRoomAdmin(url, h)
|
||||
const membershipStatus = deriveUserRoomMembershipStatus(url, h)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const startEdit = () => pushModal(RoomEdit, {url, h})
|
||||
|
||||
const handleLoading = async (f: (url: string, room: RoomMeta) => Thunk) => {
|
||||
loading = true
|
||||
|
||||
try {
|
||||
const message = await waitForThunkError(f(url, makeRoomMeta({h})))
|
||||
|
||||
if (message && !message.startsWith("duplicate:")) {
|
||||
pushToast({theme: "error", message})
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const join = () => handleLoading(joinRoom)
|
||||
|
||||
const leave = () => handleLoading(leaveRoom)
|
||||
|
||||
const showMembers = () =>
|
||||
pushModal(ProfileList, {
|
||||
title: "Members",
|
||||
subtitle: `of ${$room?.name || h}`,
|
||||
pubkeys: $members,
|
||||
})
|
||||
|
||||
const startDelete = () =>
|
||||
pushModal(Confirm, {
|
||||
title: "Are you sure you want to delete this room?",
|
||||
message:
|
||||
"This room will no longer be accessible to space members, and all messages posted to it will be deleted.",
|
||||
confirm: async () => {
|
||||
const thunk = deleteRoom(url, $room)
|
||||
const message = await waitForThunkError(thunk)
|
||||
|
||||
if (message) {
|
||||
repository.removeEvent(thunk.event.id)
|
||||
pushToast({theme: "error", message})
|
||||
} else {
|
||||
goto(makeSpacePath(url))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
let loading = $state(false)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-3">
|
||||
<RoomImage {url} {h} size={8} />
|
||||
<div class="flex min-w-0 flex-col">
|
||||
<RoomName {url} {h} class="text-2xl" />
|
||||
<span class="text-primary">{displayRelayUrl(url)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
{#if $room?.isRestricted}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Only members can send messages.">
|
||||
<Icon size={4} icon={Microphone} />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if $room?.isPrivate}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Only members can view messages.">
|
||||
<Icon size={4} icon={Lock} />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if $room?.isHidden}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="This room is not visible to non-members.">
|
||||
<Icon size={4} icon={EyeClosed} />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if $room?.isClosed}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Requests to join this room will be ignored.">
|
||||
<Icon size={4} icon={MinusCircle} />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if $room?.about}
|
||||
<p>{$room.about}</p>
|
||||
{/if}
|
||||
{#if $members.length > 0}
|
||||
<div class="card2 card2-sm bg-alt flex gap-4">
|
||||
<span>Members:</span>
|
||||
<Button onclick={showMembers}>
|
||||
<ProfileCircles pubkeys={$members} />
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<div class="flex gap-2">
|
||||
{#if $userIsAdmin}
|
||||
<Button class="btn btn-outline btn-error" onclick={startDelete}>
|
||||
<Icon icon={TrashBin2} />
|
||||
<span class="hidden md:inline">Delete Room</span>
|
||||
</Button>
|
||||
<Button class="btn btn-primary" onclick={startEdit}>
|
||||
<Icon icon={Pen} />
|
||||
Edit Room
|
||||
</Button>
|
||||
{:else if $membershipStatus === MembershipStatus.Initial}
|
||||
<Button class="btn btn-neutral" disabled={loading} onclick={join}>
|
||||
{#if loading}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<Icon icon={Login3} />
|
||||
{/if}
|
||||
Join member list
|
||||
</Button>
|
||||
{:else if $membershipStatus === MembershipStatus.Pending}
|
||||
<Button class="btn btn-neutral">
|
||||
<Icon icon={ClockCircle} />
|
||||
Membership pending
|
||||
</Button>
|
||||
{:else}
|
||||
<Button class="btn btn-neutral" disabled={loading} onclick={leave}>
|
||||
{#if loading}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<Icon icon={Login3} />
|
||||
{/if}
|
||||
Leave member list
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</div>
|
||||
@@ -2,20 +2,15 @@
|
||||
import {goto} from "$app/navigation"
|
||||
import type {RoomMeta} from "@welshman/util"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {deleteRoom, waitForThunkError, repository} from "@welshman/app"
|
||||
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Confirm from "@lib/components/Confirm.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import RoomForm from "@app/components/RoomForm.svelte"
|
||||
import {deriveRoom} from "@app/core/state"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
@@ -29,24 +24,6 @@
|
||||
const back = () => history.back()
|
||||
|
||||
const onsubmit = (room: RoomMeta) => goto(makeSpacePath(url, h))
|
||||
|
||||
const startDelete = () =>
|
||||
pushModal(Confirm, {
|
||||
title: "Are you sure you want to delete this room?",
|
||||
message:
|
||||
"This room will no longer be accessible to space members, and all messages posted to it will be deleted.",
|
||||
confirm: async () => {
|
||||
const thunk = deleteRoom(url, $room)
|
||||
const message = await waitForThunkError(thunk)
|
||||
|
||||
if (message) {
|
||||
repository.removeEvent(thunk.event.id)
|
||||
pushToast({theme: "error", message})
|
||||
} else {
|
||||
goto(makeSpacePath(url))
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<RoomForm {url} {onsubmit} initialValues={$room}>
|
||||
@@ -68,15 +45,9 @@
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<div class="flex gap-2">
|
||||
<Button class="btn btn-outline btn-error" onclick={startDelete}>
|
||||
<Icon icon={TrashBin2} />
|
||||
<span class="hidden md:inline">Delete Room</span>
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
<Spinner {loading}>Save Changes</Spinner>
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
<Spinner {loading}>Save Changes</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
{/snippet}
|
||||
</RoomForm>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
import {deriveRoom} from "@app/core/state"
|
||||
|
||||
interface Props {
|
||||
h: string
|
||||
url: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
const {url, h, size = 5}: Props = $props()
|
||||
|
||||
const room = deriveRoom(url, h)
|
||||
</script>
|
||||
|
||||
{#if $room.picture}
|
||||
<ImageIcon src={$room.picture} {size} alt="Room icon" />
|
||||
{:else}
|
||||
<Icon icon={Hashtag} {size} />
|
||||
{/if}
|
||||
@@ -7,7 +7,6 @@
|
||||
thunks,
|
||||
pubkey,
|
||||
mergeThunks,
|
||||
deriveProfile,
|
||||
deriveProfileDisplay,
|
||||
displayProfileByPubkey,
|
||||
} from "@welshman/app"
|
||||
@@ -16,12 +15,12 @@
|
||||
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||
import ReplyAlt from "@assets/icons/reply.svg?dataurl"
|
||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||
import RoomItemZapButton from "@app/components/RoomItemZapButton.svelte"
|
||||
import RoomItemEmojiButton from "@app/components/RoomItemEmojiButton.svelte"
|
||||
@@ -56,7 +55,6 @@
|
||||
const path = getRoomItemPath(url, event)
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const today = formatTimestampAsDate(now())
|
||||
const profile = deriveProfile(event.pubkey, [url])
|
||||
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
||||
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
||||
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
||||
@@ -83,7 +81,10 @@
|
||||
<div class="flex w-full gap-3 overflow-auto">
|
||||
{#if showPubkey}
|
||||
<Button onclick={openProfile} class="flex items-start">
|
||||
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={8} />
|
||||
<ProfileCircle
|
||||
pubkey={event.pubkey}
|
||||
class="border border-solid border-base-content"
|
||||
size={8} />
|
||||
</Button>
|
||||
{:else}
|
||||
<div class="w-8 min-w-8 max-w-8"></div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl">
|
||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-md">
|
||||
<li>
|
||||
<Button onclick={showInfo}>
|
||||
<Icon size={4} icon={Code2} />
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
<script lang="ts">
|
||||
import {roomsById, makeRoomId} from "@app/core/state"
|
||||
import {deriveRoom} from "@app/core/state"
|
||||
|
||||
const {url, h} = $props()
|
||||
type Props = {
|
||||
url: string
|
||||
h: string
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {url, h, ...props}: Props = $props()
|
||||
|
||||
const room = deriveRoom(url, h)
|
||||
</script>
|
||||
|
||||
{$roomsById.get(makeRoomId(url, h))?.name || h}
|
||||
<span class="ellipsize {props.class}">
|
||||
{$room?.name || h}
|
||||
</span>
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
<script lang="ts">
|
||||
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import {deriveRoom} from "@app/core/state"
|
||||
import RoomImage from "@app/components/RoomImage.svelte"
|
||||
|
||||
interface Props {
|
||||
url: any
|
||||
h: any
|
||||
h: string
|
||||
url: string
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
|
||||
const room = deriveRoom(url, h)
|
||||
const {url, h, ...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
{#if $room.picture}
|
||||
{@const src = $room.picture}
|
||||
<ImageIcon {src} alt="Room icon" />
|
||||
{:else}
|
||||
<Icon icon={Hashtag} />
|
||||
{/if}
|
||||
<div class="min-w-0 overflow-hidden text-ellipsis">
|
||||
<RoomName {url} {h} />
|
||||
<div class="flex flex-grow items-center justify-between gap-4 {props.class}">
|
||||
<div class="flex items-center gap-3">
|
||||
<RoomImage {url} {h} />
|
||||
<div class="min-w-0 overflow-hidden text-ellipsis">
|
||||
<RoomName {url} {h} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,12 +12,29 @@
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import SpaceAccessRequest from "@app/components/SpaceAccessRequest.svelte"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {removeSpaceMembership, publishLeaveRequest, removeTrustedRelay} from "@app/core/commands"
|
||||
|
||||
const {url, error} = $props()
|
||||
|
||||
const back = () => goto("/home")
|
||||
|
||||
const requestAccess = () => pushModal(SpaceAccessRequest, {url})
|
||||
|
||||
const leaveSpace = async () => {
|
||||
loading = true
|
||||
|
||||
try {
|
||||
await removeSpaceMembership(url)
|
||||
await publishLeaveRequest({url})
|
||||
await removeTrustedRelay(url)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
goto("/home")
|
||||
}
|
||||
|
||||
let loading = $state(false)
|
||||
</script>
|
||||
|
||||
<form class="column gap-4" onsubmit={preventDefault(requestAccess)}>
|
||||
@@ -40,9 +57,14 @@
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go Home
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary">
|
||||
Request Access
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Button>
|
||||
<div class="flex gap-2">
|
||||
<Button class="btn btn-outline btn-error" onclick={leaveSpace} disabled={loading}>
|
||||
Leave Space
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
Request Access
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import {deriveRelay} from "@welshman/app"
|
||||
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
||||
|
||||
interface Props {
|
||||
url?: string
|
||||
}
|
||||
|
||||
const {url = ""}: Props = $props()
|
||||
|
||||
const relay = deriveRelay(url)
|
||||
</script>
|
||||
|
||||
<Avatar
|
||||
icon={RemoteControllerMinimalistic}
|
||||
class="!h-10 !w-10"
|
||||
alt={displayRelayUrl(url)}
|
||||
src={$relay?.icon} />
|
||||
@@ -10,19 +10,24 @@
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import SpaceVisitConfirm, {confirmSpaceVisit} from "@app/components/SpaceVisitConfirm.svelte"
|
||||
import SpaceJoinConfirm, {confirmSpaceJoin} from "@app/components/SpaceJoinConfirm.svelte"
|
||||
import {attemptRelayAccess} from "@app/core/commands"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
const {url} = $props()
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const next = () => {
|
||||
if (!error && Pool.get().get(url).auth.status === AuthStatus.None) {
|
||||
pushModal(SpaceVisitConfirm, {url}, {replaceState: true})
|
||||
const next = async () => {
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error, timeout: 30_000})
|
||||
}
|
||||
|
||||
if (Pool.get().get(url).auth.status === AuthStatus.None) {
|
||||
pushModal(SpaceJoinConfirm, {url}, {replaceState: true})
|
||||
} else {
|
||||
confirmSpaceVisit(url)
|
||||
await confirmSpaceJoin(url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +77,7 @@
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
Go to Space
|
||||
Join Space
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {deriveRelay} from "@welshman/app"
|
||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
import Pen from "@assets/icons/pen.svg?dataurl"
|
||||
import ShieldUser from "@assets/icons/shield-user.svg?dataurl"
|
||||
import BillList from "@assets/icons/bill-list.svg?dataurl"
|
||||
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import SpaceEdit from "@app/components/SpaceEdit.svelte"
|
||||
import SpaceRelayStatus from "@app/components/SpaceRelayStatus.svelte"
|
||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||
import ProfileLatest from "@app/components/ProfileLatest.svelte"
|
||||
import {deriveUserIsSpaceAdmin} from "@app/core/state"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
@@ -20,8 +26,11 @@
|
||||
const {url}: Props = $props()
|
||||
const relay = deriveRelay(url)
|
||||
const owner = $derived($relay?.pubkey)
|
||||
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const startEdit = () => pushModal(SpaceEdit, {url, initialValues: $relay})
|
||||
</script>
|
||||
|
||||
<div class="column gap-4">
|
||||
@@ -78,5 +87,18 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Button class="btn btn-primary" onclick={back}>Got it</Button>
|
||||
{#if $userIsAdmin}
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button class="btn btn-primary" onclick={startEdit}>
|
||||
<Icon icon={Pen} />
|
||||
Edit Space
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
{:else}
|
||||
<Button class="btn btn-primary" onclick={back}>Got it</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<script lang="ts">
|
||||
import {uniqBy, prop, append, ifLet} from "@welshman/lib"
|
||||
import type {RelayProfile} from "@welshman/util"
|
||||
import {displayRelayUrl, ManagementMethod} from "@welshman/util"
|
||||
import {manageRelay, relays, fetchRelayProfileDirectly} from "@welshman/app"
|
||||
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
|
||||
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
import UploadMinimalistic from "@assets/icons/upload-minimalistic.svg?dataurl"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
import IconPickerButton from "@lib/components/IconPickerButton.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {clearModals} from "@app/util/modal"
|
||||
import {uploadFile} from "@app/core/commands"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
initialValues: RelayProfile
|
||||
}
|
||||
|
||||
const {url, initialValues}: Props = $props()
|
||||
|
||||
const values = $state(initialValues)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const submit = async () => {
|
||||
if (values.name != initialValues.name) {
|
||||
const res = await manageRelay(url, {
|
||||
method: ManagementMethod.ChangeRelayName,
|
||||
params: [values.name || ""],
|
||||
})
|
||||
|
||||
if (res.error) {
|
||||
return pushToast({theme: "error", message: res.error})
|
||||
}
|
||||
}
|
||||
|
||||
if (values.description != initialValues.description) {
|
||||
const res = await manageRelay(url, {
|
||||
method: ManagementMethod.ChangeRelayDescription,
|
||||
params: [values.description || ""],
|
||||
})
|
||||
|
||||
if (res.error) {
|
||||
return pushToast({theme: "error", message: res.error})
|
||||
}
|
||||
}
|
||||
|
||||
if (imageFile) {
|
||||
const {error, result} = await uploadFile(imageFile, {maxWidth: 128, maxHeight: 128})
|
||||
|
||||
console.log(imageFile, result)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error})
|
||||
}
|
||||
|
||||
const res = await manageRelay(url, {
|
||||
method: ManagementMethod.ChangeRelayIcon,
|
||||
params: [result.url],
|
||||
})
|
||||
|
||||
if (res.error) {
|
||||
return pushToast({theme: "error", message: res.error})
|
||||
}
|
||||
}
|
||||
|
||||
// Force-reload the relay
|
||||
ifLet(await fetchRelayProfileDirectly(url), relay => {
|
||||
relays.update($relays => uniqBy(prop("url"), append(relay, $relays)))
|
||||
})
|
||||
|
||||
pushToast({message: "Your changes have been saved!"})
|
||||
clearModals()
|
||||
}
|
||||
|
||||
const trySubmit = async () => {
|
||||
loading = true
|
||||
|
||||
try {
|
||||
await submit()
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
let loading = $state(false)
|
||||
let imageFile = $state<File | undefined>()
|
||||
let imagePreview = $state(initialValues.icon)
|
||||
|
||||
const handleImageUpload = async (event: Event) => {
|
||||
const file = (event.target as HTMLInputElement).files?.[0]
|
||||
|
||||
if (file && file.type.startsWith("image/")) {
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = e => {
|
||||
imageFile = file
|
||||
imagePreview = e.target?.result as string
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
|
||||
const handleIconSelect = (iconUrl: string) => {
|
||||
imagePreview = iconUrl
|
||||
|
||||
const parts = iconUrl.split(",")
|
||||
const imageData = atob(parts[1])
|
||||
const result = new Uint8Array(imageData.length)
|
||||
|
||||
for (let n = 0; n < imageData.length; n++) {
|
||||
result[n] = imageData.charCodeAt(n)
|
||||
}
|
||||
|
||||
imageFile = new File([result], `icon.svg`, {type: "image/svg+xml"})
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="column gap-4" onsubmit={preventDefault(trySubmit)}>
|
||||
<ModalHeader>
|
||||
{#snippet title()}
|
||||
<div>Edit a Space</div>
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
<span class="text-primary">{displayRelayUrl(url)}</span>
|
||||
{/snippet}
|
||||
</ModalHeader>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Icon</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
{#if imagePreview}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm opacity-75">Selected:</span>
|
||||
<ImageIcon src={imagePreview} alt="Room icon preview" />
|
||||
</div>
|
||||
{:else}
|
||||
<span class="text-sm opacity-75">No icon selected</span>
|
||||
{/if}
|
||||
<div class="flex gap-2">
|
||||
<IconPickerButton onSelect={handleIconSelect} class="btn btn-primary btn-sm">
|
||||
<Icon icon={StickerSmileSquare} size={4} />
|
||||
Select
|
||||
</IconPickerButton>
|
||||
<label class="btn btn-neutral btn-sm cursor-pointer">
|
||||
<Icon icon={UploadMinimalistic} size={4} />
|
||||
Upload
|
||||
<input type="file" accept="image/*" class="hidden" onchange={handleImageUpload} />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Name</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
{#if imagePreview}
|
||||
<ImageIcon src={imagePreview} alt="Room icon preview" />
|
||||
{:else}
|
||||
<Icon icon={SettingsMinimalistic} />
|
||||
{/if}
|
||||
<input bind:value={values.name} class="grow" type="text" />
|
||||
</label>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Description</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<input bind:value={values.description} class="grow" type="text" />
|
||||
</label>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
<Spinner {loading}>Save Changes</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
@@ -10,8 +10,8 @@
|
||||
await addSpaceMembership(url)
|
||||
|
||||
broadcastUserData([url])
|
||||
goto(makeSpacePath(url), {replaceState: true})
|
||||
relaysMostlyRestricted.update(dissoc(url))
|
||||
goto(makeSpacePath(url), {replaceState: true})
|
||||
pushToast({message: "Welcome to the space!"})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<script module lang="ts">
|
||||
import {goto} from "$app/navigation"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
|
||||
export const confirmSpaceVisit = (url: string) => {
|
||||
goto(makeSpacePath(url), {replaceState: true})
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Confirm from "@lib/components/Confirm.svelte"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
}
|
||||
|
||||
const {url}: Props = $props()
|
||||
|
||||
const confirm = () => confirmSpaceVisit(url)
|
||||
</script>
|
||||
|
||||
<Confirm
|
||||
{confirm}
|
||||
message="This space does not appear to limit who can post to it. This can result in a large amount of spam or other objectionable content. Continue?" />
|
||||
@@ -23,7 +23,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="card2 bg-alt col-2 shadow-2xl">
|
||||
<div class="card2 bg-alt col-2 shadow-lg">
|
||||
<p>
|
||||
Failed to publish to {displayRelayUrl(url)}: {message}.
|
||||
</p>
|
||||
|
||||
@@ -612,7 +612,7 @@ export const payInvoice = async (invoice: string) => {
|
||||
|
||||
export const normalizeBlossomUrl = (url: string) => normalizeUrl(url.replace(/^ws/, "http"))
|
||||
|
||||
export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
|
||||
export const fetchHasBlossomSupport = async (url: string) => {
|
||||
const server = normalizeBlossomUrl(url)
|
||||
const $signer = signer.get() || Nip01Signer.ephemeral()
|
||||
const headers: Record<string, string> = {
|
||||
@@ -633,7 +633,9 @@ export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
export const hasBlossomSupport = simpleCache(([url]: [string]) => fetchHasBlossomSupport(url))
|
||||
|
||||
export type GetBlossomServerOptions = {
|
||||
url?: string
|
||||
@@ -658,6 +660,8 @@ export const getBlossomServer = async (options: GetBlossomServerOptions = {}) =>
|
||||
export type UploadFileOptions = {
|
||||
url?: string
|
||||
encrypt?: boolean
|
||||
maxWidth?: number
|
||||
maxHeight?: number
|
||||
}
|
||||
|
||||
export type UploadFileResult = {
|
||||
@@ -669,8 +673,8 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
||||
try {
|
||||
const {name, type} = file
|
||||
|
||||
if (!type.match("image/(webp|gif)")) {
|
||||
file = await compressFile(file)
|
||||
if (!type.match("image/(webp|gif|svg)")) {
|
||||
file = await compressFile(file, options)
|
||||
}
|
||||
|
||||
const tags: string[][] = []
|
||||
@@ -701,7 +705,7 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
||||
let {uploaded, url, ...task} = parseJson(text) || {}
|
||||
|
||||
if (!uploaded) {
|
||||
return {error: text}
|
||||
return {error: text || `Failed to upload file (HTTP ${res.status})`}
|
||||
}
|
||||
|
||||
// Always append correct file extension if we encrypted the file, or if it's missing
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
sortBy,
|
||||
now,
|
||||
on,
|
||||
isNotNil,
|
||||
isDefined,
|
||||
filterVals,
|
||||
fromPairs,
|
||||
} from "@welshman/lib"
|
||||
@@ -279,6 +279,6 @@ export const requestRelayClaim = async (url: string) => {
|
||||
|
||||
export const requestRelayClaims = async (urls: string[]) =>
|
||||
filterVals(
|
||||
isNotNil,
|
||||
isDefined,
|
||||
fromPairs(await Promise.all(urls.map(async url => [url, await requestRelayClaim(url)]))),
|
||||
)
|
||||
|
||||
+26
-21
@@ -730,7 +730,7 @@ export const deriveSpaceMembers = (url: string) =>
|
||||
return getTagValues("member", membersEvent.tags)
|
||||
}
|
||||
|
||||
const members = new Set()
|
||||
const members = new Set<string>()
|
||||
|
||||
for (const event of sortBy(e => e.created_at, $events)) {
|
||||
const pubkeys = getPubkeyTagValues(event.tags)
|
||||
@@ -765,7 +765,7 @@ export const deriveRoomMembers = (url: string, h: string) =>
|
||||
return getPubkeyTagValues(membersEvent.tags)
|
||||
}
|
||||
|
||||
const members = new Set()
|
||||
const members = new Set<string>()
|
||||
|
||||
for (const event of sortBy(e => -e.created_at, $events)) {
|
||||
const pubkeys = getPubkeyTagValues(event.tags)
|
||||
@@ -806,18 +806,29 @@ export enum MembershipStatus {
|
||||
Granted,
|
||||
}
|
||||
|
||||
export const deriveUserIsSpaceAdmin = (url: string) => {
|
||||
const store = writable(false)
|
||||
|
||||
manageRelay(url, {method: ManagementMethod.SupportedMethods, params: []}).then(res =>
|
||||
store.set(Boolean(res.result?.length)),
|
||||
)
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
export const deriveUserSpaceMembershipStatus = (url: string) =>
|
||||
derived(
|
||||
[
|
||||
pubkey,
|
||||
deriveSpaceMembers(url),
|
||||
deriveEventsForUrl(url, [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]),
|
||||
deriveUserIsSpaceAdmin(url),
|
||||
],
|
||||
([$pubkey, $members, $events]) => {
|
||||
const isMember = $members.includes($pubkey)
|
||||
([$pubkey, $members, $events, $isAdmin]) => {
|
||||
const isMember = $members.includes($pubkey!) || $isAdmin
|
||||
|
||||
for (const event of $events) {
|
||||
if (!getPubkeyTagValues(event.tags).includes($pubkey!)) {
|
||||
if (event.pubkey !== $pubkey) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -834,18 +845,25 @@ export const deriveUserSpaceMembershipStatus = (url: string) =>
|
||||
},
|
||||
)
|
||||
|
||||
export const deriveUserIsRoomAdmin = (url: string, h: string) =>
|
||||
derived(
|
||||
[pubkey, deriveRoomAdmins(url, h), deriveUserIsSpaceAdmin(url)],
|
||||
([$pubkey, $admins, $isSpaceAdmin]) => $isSpaceAdmin || $admins.includes($pubkey!),
|
||||
)
|
||||
|
||||
export const deriveUserRoomMembershipStatus = (url: string, h: string) =>
|
||||
derived(
|
||||
[
|
||||
pubkey,
|
||||
deriveRoomMembers(url, h),
|
||||
deriveEventsForUrl(url, [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]),
|
||||
deriveUserIsRoomAdmin(url, h),
|
||||
],
|
||||
([$pubkey, $members, $events]) => {
|
||||
const isMember = $members.includes($pubkey)
|
||||
([$pubkey, $members, $events, $isAdmin]) => {
|
||||
const isMember = $members.includes($pubkey!) || $isAdmin
|
||||
|
||||
for (const event of $events) {
|
||||
if (!getPubkeyTagValues(event.tags).includes($pubkey!)) {
|
||||
if (event.pubkey !== $pubkey) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -872,19 +890,6 @@ export const deriveUserCanCreateRoom = (url: string) =>
|
||||
},
|
||||
)
|
||||
|
||||
export const deriveUserIsRoomAdmin = (url: string, h: string) =>
|
||||
derived([pubkey, deriveRoomAdmins(url, h)], ([$pubkey, $admins]) => $admins.includes($pubkey!))
|
||||
|
||||
export const deriveUserIsSpaceAdmin = (url: string) => {
|
||||
const store = writable(false)
|
||||
|
||||
manageRelay(url, {method: ManagementMethod.SupportedMethods, params: []}).then(res =>
|
||||
store.set(Boolean(res.result?.length)),
|
||||
)
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
// Other utils
|
||||
|
||||
export const encodeRelay = (url: string) =>
|
||||
|
||||
+4
-66
@@ -6,7 +6,6 @@ import {
|
||||
getListTags,
|
||||
getRelayTagValues,
|
||||
WRAP,
|
||||
MESSAGE,
|
||||
ROOM_META,
|
||||
ROOM_DELETE,
|
||||
ROOM_ADMINS,
|
||||
@@ -37,9 +36,9 @@ import {
|
||||
repository,
|
||||
shouldUnwrap,
|
||||
hasNegentropy,
|
||||
relaysByUrl,
|
||||
} from "@welshman/app"
|
||||
import {
|
||||
REACTION_KINDS,
|
||||
MESSAGE_KINDS,
|
||||
CONTENT_KINDS,
|
||||
INDEXER_RELAYS,
|
||||
@@ -50,7 +49,6 @@ import {
|
||||
bootstrapPubkeys,
|
||||
decodeRelay,
|
||||
getUrlsForEvent,
|
||||
hasNip29,
|
||||
getSpaceUrlsFromGroupSelections,
|
||||
getSpaceRoomsFromGroupSelections,
|
||||
makeCommentFilter,
|
||||
@@ -267,9 +265,11 @@ const syncSpace = (url: string) => {
|
||||
{kinds: [RELAY_MEMBERS]},
|
||||
{kinds: [ROOM_META, ROOM_DELETE]},
|
||||
{kinds: [ROOM_ADMINS, ROOM_MEMBERS]},
|
||||
{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER]},
|
||||
{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER]},
|
||||
...MESSAGE_KINDS.map(kind => ({kinds: [kind]})),
|
||||
makeCommentFilter(CONTENT_KINDS),
|
||||
{kinds: REACTION_KINDS, limit: 0},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -328,68 +328,6 @@ const syncSpaces = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Chat
|
||||
|
||||
const syncRoom = (url: string, h: string) => {
|
||||
const controller = new AbortController()
|
||||
|
||||
pullAndListen({
|
||||
relays: [url],
|
||||
signal: controller.signal,
|
||||
filters: [
|
||||
{kinds: [ROOM_ADMINS, ROOM_MEMBERS], "#d": [h]},
|
||||
{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER], "#h": [h]},
|
||||
{kinds: [MESSAGE], "#h": [h]},
|
||||
],
|
||||
})
|
||||
|
||||
return () => controller.abort()
|
||||
}
|
||||
|
||||
const syncRooms = () => {
|
||||
const unsubscribersByKey = new Map<string, Unsubscriber>()
|
||||
|
||||
const unsubscribeSpaceUrls = derived([userGroupSelections, relaysByUrl], identity).subscribe(
|
||||
([$l, $relaysByUrl]) => {
|
||||
const keys = new Set<string>()
|
||||
const newUnsubscribersByKey = new Map<string, Unsubscriber>()
|
||||
|
||||
// Add new subscriptions, depending on whether nip 29 is supported
|
||||
for (const url of getRelayTagValues(getListTags($l))) {
|
||||
if (hasNip29($relaysByUrl.get(url))) {
|
||||
for (const h of getSpaceRoomsFromGroupSelections(url, $l)) {
|
||||
const key = `${url}'${h}`
|
||||
|
||||
if (!unsubscribersByKey.has(key)) {
|
||||
newUnsubscribersByKey.set(key, syncRoom(url, h))
|
||||
}
|
||||
|
||||
keys.add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop syncing removed selections
|
||||
for (const [key, unsubscribe] of unsubscribersByKey.entries()) {
|
||||
if (!keys.has(key)) {
|
||||
unsubscribersByKey.delete(key)
|
||||
unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
// Start syncing newly added spaces
|
||||
for (const [key, unsubscriber] of newUnsubscribersByKey.entries()) {
|
||||
unsubscribersByKey.set(key, unsubscriber)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return () => {
|
||||
Array.from(unsubscribersByKey.values()).forEach(call)
|
||||
unsubscribeSpaceUrls()
|
||||
}
|
||||
}
|
||||
|
||||
// DMs
|
||||
|
||||
const syncDMRelay = (url: string, pubkey: string) => {
|
||||
@@ -479,7 +417,7 @@ const syncDMs = () => {
|
||||
// Merge all synchronization functions
|
||||
|
||||
export const syncApplicationData = () => {
|
||||
const unsubscribers = [syncRelays(), syncUserData(), syncSpaces(), syncRooms(), syncDMs()]
|
||||
const unsubscribers = [syncRelays(), syncUserData(), syncSpaces(), syncDMs()]
|
||||
|
||||
return () => unsubscribers.forEach(call)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type {NodeViewProps} from "@tiptap/core"
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import {removeUndefined} from "@welshman/lib"
|
||||
import {deriveProfileDisplay} from "@welshman/app"
|
||||
|
||||
export const makeMentionNodeView =
|
||||
(url?: string) =>
|
||||
({node}: NodeViewProps) => {
|
||||
const dom = document.createElement("span")
|
||||
const display = deriveProfileDisplay(node.attrs.pubkey, removeNil([url]))
|
||||
const display = deriveProfileDisplay(node.attrs.pubkey, removeUndefined([url]))
|
||||
|
||||
dom.classList.add("tiptap-object")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import {removeUndefined} from "@welshman/lib"
|
||||
import {displayPubkey} from "@welshman/util"
|
||||
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
||||
import WotScore from "@app/components/WotScore.svelte"
|
||||
@@ -13,7 +13,7 @@
|
||||
const {value, url}: Props = $props()
|
||||
|
||||
const pubkey = value
|
||||
const profileDisplay = deriveProfileDisplay(pubkey, removeNil([url]))
|
||||
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url]))
|
||||
const handle = deriveHandleForPubkey(pubkey)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import {writable} from "svelte/store"
|
||||
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||
import {
|
||||
NIP46_PERMS,
|
||||
PLATFORM_URL,
|
||||
PLATFORM_NAME,
|
||||
PLATFORM_LOGO,
|
||||
SIGNER_RELAYS,
|
||||
} from "@app/core/state"
|
||||
import {PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO, SIGNER_RELAYS} from "@app/core/state"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
export class Nip46Controller {
|
||||
@@ -25,7 +19,6 @@ export class Nip46Controller {
|
||||
|
||||
async start() {
|
||||
const url = await this.broker.makeNostrconnectUrl({
|
||||
perms: NIP46_PERMS,
|
||||
url: PLATFORM_URL,
|
||||
name: PLATFORM_NAME,
|
||||
image: PLATFORM_LOGO,
|
||||
|
||||
@@ -58,14 +58,18 @@ export const trustPolicy = (socket: Socket) => {
|
||||
export const mostlyRestrictedPolicy = (socket: Socket) => {
|
||||
let total = 0
|
||||
let restricted = 0
|
||||
let error = ""
|
||||
|
||||
const pending = new Set<string>()
|
||||
|
||||
const updateStatus = () =>
|
||||
relaysMostlyRestricted.update(
|
||||
restricted > total / 2 ? assoc(socket.url, error) : dissoc(socket.url),
|
||||
)
|
||||
const updateStatus = (error?: string) => {
|
||||
if (restricted > total / 2) {
|
||||
if (error) {
|
||||
return relaysMostlyRestricted.update(assoc(socket.url, error))
|
||||
}
|
||||
} else {
|
||||
relaysMostlyRestricted.update(dissoc(socket.url))
|
||||
}
|
||||
}
|
||||
|
||||
const unsubscribers = [
|
||||
on(socket, SocketEvent.Receive, (message: RelayMessage) => {
|
||||
@@ -83,8 +87,7 @@ export const mostlyRestrictedPolicy = (socket: Socket) => {
|
||||
|
||||
if (details.startsWith("restricted: ")) {
|
||||
restricted++
|
||||
error = details
|
||||
updateStatus()
|
||||
updateStatus(details)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,8 +106,7 @@ export const mostlyRestrictedPolicy = (socket: Socket) => {
|
||||
|
||||
if (details.startsWith("restricted: ")) {
|
||||
restricted++
|
||||
error = details
|
||||
updateStatus()
|
||||
updateStatus(details)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||
|
||||
const {src = "", size = 7, icon = UserRounded, style = "", ...restProps} = $props()
|
||||
|
||||
let element: HTMLElement
|
||||
|
||||
const rem = $derived(size * 4)
|
||||
|
||||
onMount(() => {
|
||||
if (src) {
|
||||
const image = new Image()
|
||||
|
||||
image.addEventListener("error", () => {
|
||||
element?.querySelector(".hidden")?.classList.remove("hidden")
|
||||
})
|
||||
|
||||
image.src = src
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={element}
|
||||
class="{restProps.class} relative !flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-cover bg-center"
|
||||
style="width: {rem}px; height: {rem}px; min-width: {rem}px; background-image: url({src}); {style}">
|
||||
<Icon {icon} class={src ? "hidden" : ""} size={Math.round(size * 0.8)} />
|
||||
</div>
|
||||
@@ -12,8 +12,8 @@
|
||||
</script>
|
||||
|
||||
<div class="btn flex h-[unset] w-full flex-nowrap py-4 text-left {props.class}">
|
||||
<div class="flex flex-grow flex-row items-start gap-1 sm:pl-2">
|
||||
<div class="flex h-14 w-12 flex-shrink-0 items-center">
|
||||
<div class="flex flex-grow flex-row items-start gap-4">
|
||||
<div class="flex h-14 w-12 flex-shrink-0 items-center justify-center">
|
||||
{@render props.icon?.()}
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
const extraClass = $derived(
|
||||
!fullscreen &&
|
||||
cx(
|
||||
"bg-alt text-base-content overflow-auto text-base-content shadow-xl",
|
||||
"bg-alt text-base-content overflow-auto text-base-content shadow-md",
|
||||
"px-4 py-6 bottom-0 left-0 right-0 top-20 rounded-t-box absolute",
|
||||
"sm:p-6 sm:max-h-[90vh] sm:w-[520px] sm:rounded-box sm:relative sm:top-0",
|
||||
),
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
type Props = {
|
||||
src: string
|
||||
alt: string
|
||||
size?: number
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {src, alt}: Props = $props()
|
||||
const {src, alt, size = 5, ...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
{#if src.includes("image/svg") || src.endsWith(".svg")}
|
||||
<Icon icon={src} />
|
||||
<Icon icon={src} {size} class={props.class} />
|
||||
{:else}
|
||||
<img {src} {alt} class="h-5 w-5 rounded-lg object-cover" />
|
||||
<img {src} {alt} class="h-{size} w-{size} aspect-square object-cover {props.class}" />
|
||||
{/if}
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
const {...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div data-component="PageBar" class="cw top-sai fixed z-feature p-2 {props.class}">
|
||||
<div data-component="PageBar" class="cw top-sai fixed z-nav p-2 {props.class}">
|
||||
<div
|
||||
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
|
||||
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-md">
|
||||
<div class="ellipsize flex items-center gap-4 whitespace-nowrap">
|
||||
{@render props.icon?.()}
|
||||
{@render props.title?.()}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{#if href}
|
||||
<a {href} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
||||
<div
|
||||
class="avatar cursor-pointer rounded-full p-1 {restProps.class} transition-colors hover:bg-base-300"
|
||||
class="avatar cursor-pointer rounded-full p-2 {restProps.class} transition-colors hover:bg-base-300"
|
||||
class:bg-base-300={active}
|
||||
class:tooltip={title}
|
||||
data-tip={title}>
|
||||
@@ -31,7 +31,7 @@
|
||||
{:else}
|
||||
<Button {onclick} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
||||
<div
|
||||
class="avatar cursor-pointer rounded-full p-1 {restProps.class} transition-colors hover:bg-base-300"
|
||||
class="avatar cursor-pointer rounded-full p-2 {restProps.class} transition-colors hover:bg-base-300"
|
||||
class:bg-base-300={active}
|
||||
class:tooltip={title}
|
||||
data-tip={title}>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<style>
|
||||
:global(.tippy-box[data-theme~="tooltip"]) {
|
||||
background-color: var(--neutral);
|
||||
color: var(--neutral-content);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgb(0 0 0 / 0.1),
|
||||
0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
:global(.tippy-box[data-theme~="tooltip"][data-placement^="top"] > .tippy-arrow::before) {
|
||||
border-top-color: var(--neutral);
|
||||
}
|
||||
|
||||
:global(.tippy-box[data-theme~="tooltip"][data-placement^="bottom"] > .tippy-arrow::before) {
|
||||
border-bottom-color: var(--neutral);
|
||||
}
|
||||
|
||||
:global(.tippy-box[data-theme~="tooltip"][data-placement^="left"] > .tippy-arrow::before) {
|
||||
border-left-color: var(--neutral);
|
||||
}
|
||||
|
||||
:global(.tippy-box[data-theme~="tooltip"][data-placement^="right"] > .tippy-arrow::before) {
|
||||
border-right-color: var(--neutral);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import "tippy.js/animations/shift-away.css"
|
||||
|
||||
import tippy from "tippy.js"
|
||||
import {onMount} from "svelte"
|
||||
import {isMobile} from "@lib/html"
|
||||
|
||||
let {
|
||||
content,
|
||||
arrow = true,
|
||||
interactive = false,
|
||||
children = undefined,
|
||||
instance = $bindable(),
|
||||
...restProps
|
||||
} = $props()
|
||||
|
||||
let element: Element
|
||||
|
||||
onMount(() => {
|
||||
instance = tippy(element, {
|
||||
content,
|
||||
arrow,
|
||||
interactive,
|
||||
animation: "shift-away",
|
||||
theme: "tooltip",
|
||||
appendTo: document.querySelector(".tippy-target")!,
|
||||
trigger: isMobile ? "click" : "mouseenter focus",
|
||||
})
|
||||
|
||||
return () => {
|
||||
instance?.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={element} class={restProps.class}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@
|
||||
{#if inviteData}
|
||||
{#key inviteData.url}
|
||||
<Button
|
||||
class="card2 bg-alt shadow-xl transition-all hover:shadow-2xl hover:dark:brightness-[1.1]"
|
||||
class="card2 bg-alt shadow-md transition-all hover:shadow-lg hover:dark:brightness-[1.1]"
|
||||
onclick={() => openSpace(inviteData.url, inviteData.claim)}>
|
||||
<RelaySummary url={inviteData.url} />
|
||||
</Button>
|
||||
@@ -153,7 +153,7 @@
|
||||
{/if}
|
||||
{#each relaySearch.searchOptions(term).slice(0, limit) as relay (relay.url)}
|
||||
<Button
|
||||
class="card2 bg-alt shadow-xl transition-all hover:shadow-2xl hover:dark:brightness-[1.1]"
|
||||
class="card2 bg-alt shadow-md transition-all hover:shadow-lg hover:dark:brightness-[1.1]"
|
||||
onclick={() => openSpace(relay.url)}>
|
||||
<RelaySummary url={relay.url} />
|
||||
</Button>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<Button onclick={addSpace}>
|
||||
<CardButton class="btn-neutral">
|
||||
{#snippet icon()}
|
||||
<div><Icon icon={AddCircle} size={7} /></div>
|
||||
<Icon icon={AddCircle} size={7} />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<div>Add a space</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
<Link href="/discover">
|
||||
<CardButton class="btn-neutral">
|
||||
{#snippet icon()}
|
||||
<div><Icon icon={Compass} size={7} /></div>
|
||||
<Icon icon={Compass} size={7} />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<div>Browse the network</div>
|
||||
@@ -61,7 +61,7 @@
|
||||
<Button onclick={openChat}>
|
||||
<CardButton class="btn-neutral">
|
||||
{#snippet icon()}
|
||||
<div><Icon icon={ChatRound} size={7} /></div>
|
||||
<Icon icon={ChatRound} size={7} />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<div>Start a conversation</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<h1 class="mb-4 text-center text-5xl font-bold uppercase">{PLATFORM_NAME}</h1>
|
||||
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
{#if Capacitor.getPlatform() !== "ios"}
|
||||
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-2xl">
|
||||
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-lg">
|
||||
<h3 class="text-2xl sm:h-12">Donate</h3>
|
||||
<p class="sm:h-16">Funds will be used to support development.</p>
|
||||
<Link external href="https://geyser.fund/project/flotilla" class="btn btn-primary">
|
||||
@@ -33,7 +33,7 @@
|
||||
</Link>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-2xl">
|
||||
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-lg">
|
||||
<h3 class="text-2xl sm:h-12">Get in touch</h3>
|
||||
<p class="sm:h-16">Having problems? Let us know.</p>
|
||||
<Link
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</script>
|
||||
|
||||
<form class="content column gap-4" {onsubmit}>
|
||||
<div class="card2 bg-alt col-4 shadow-xl">
|
||||
<div class="card2 bg-alt col-4 shadow-md">
|
||||
<strong class="text-lg">Content Settings</strong>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
||||
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
||||
@@ -44,11 +44,11 @@
|
||||
</script>
|
||||
|
||||
<div class="content column gap-4">
|
||||
<div class="card2 bg-alt shadow-xl">
|
||||
<div class="card2 bg-alt shadow-md">
|
||||
<div class="flex justify-between gap-2">
|
||||
<div class="flex max-w-full gap-3">
|
||||
<div class="py-1">
|
||||
<Avatar src={$profile?.picture} size={10} />
|
||||
<ProfileCircle pubkey={$pubkey!} size={10} />
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -70,7 +70,7 @@
|
||||
{/key}
|
||||
</div>
|
||||
{#if $session?.email}
|
||||
<div class="card2 bg-alt col-4 shadow-xl">
|
||||
<div class="card2 bg-alt col-4 shadow-md">
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Email Address</p>
|
||||
@@ -90,7 +90,7 @@
|
||||
</FieldInline>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="card2 bg-alt col-4 shadow-xl">
|
||||
<div class="card2 bg-alt col-4 shadow-md">
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p class="flex items-center gap-3">
|
||||
@@ -140,7 +140,7 @@
|
||||
{/if}
|
||||
<SignerStatus />
|
||||
</div>
|
||||
<div class="card2 bg-alt shadow-xl">
|
||||
<div class="card2 bg-alt shadow-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<strong class="flex items-center gap-3">
|
||||
<Icon icon={Settings} />
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</script>
|
||||
|
||||
<div class="content column gap-4">
|
||||
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
|
||||
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
||||
{#snippet title()}
|
||||
<h2 class="flex items-center gap-3 text-xl">
|
||||
<Icon icon={Globus} />
|
||||
@@ -83,7 +83,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
</Collapse>
|
||||
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
|
||||
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
||||
{#snippet title()}
|
||||
<h2 class="flex items-center gap-3 text-xl">
|
||||
<Icon icon={Inbox} />
|
||||
@@ -115,7 +115,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
</Collapse>
|
||||
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
|
||||
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
||||
{#snippet title()}
|
||||
<h2 class="flex items-center gap-3 text-xl">
|
||||
<Icon icon={Mailbox} />
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</script>
|
||||
|
||||
<div class="content column gap-4">
|
||||
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
|
||||
<div class="card2 bg-alt flex flex-col gap-6 shadow-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<strong class="flex items-center gap-3">
|
||||
<Icon icon={Wallet2} />
|
||||
@@ -100,7 +100,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="card2 bg-alt flex flex-col shadow-xl"
|
||||
class="card2 bg-alt flex flex-col shadow-md"
|
||||
class:gap-6={profileLightningAddress && walletLud16 && profile?.lud16 !== walletLud16}>
|
||||
<div class="flex items-center justify-between">
|
||||
<strong>Lightning Address</strong>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import MenuSpacesItem from "@app/components/MenuSpacesItem.svelte"
|
||||
import SpaceAdd from "@app/components/SpaceAdd.svelte"
|
||||
import {userSpaceUrls, PLATFORM_RELAYS} from "@app/core/state"
|
||||
import {userSpaceUrls, loadUserGroupSelections, PLATFORM_RELAYS} from "@app/core/state"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
|
||||
const addSpace = () => pushModal(SpaceAdd)
|
||||
@@ -37,17 +37,24 @@
|
||||
{#each PLATFORM_RELAYS as url (url)}
|
||||
<MenuSpacesItem {url} />
|
||||
{:else}
|
||||
{#each $userSpaceUrls as url (url)}
|
||||
<MenuSpacesItem {url} />
|
||||
{:else}
|
||||
<div class="flex flex-col gap-8 items-center py-20">
|
||||
<p>You haven't added any spaces yet!</p>
|
||||
<Button class="btn btn-primary" onclick={addSpace}>
|
||||
<Icon icon={AddCircle} />
|
||||
Add a Space
|
||||
</Button>
|
||||
{#await loadUserGroupSelections()}
|
||||
<div class="flex justify-center items-center py-20">
|
||||
<span class="loading loading-spinner mr-3"></span>
|
||||
Loading your spaces...
|
||||
</div>
|
||||
{/each}
|
||||
{:then}
|
||||
{#each $userSpaceUrls as url (url)}
|
||||
<MenuSpacesItem {url} />
|
||||
{:else}
|
||||
<div class="flex flex-col gap-8 items-center py-20">
|
||||
<p>You haven't added any spaces yet!</p>
|
||||
<Button class="btn btn-primary" onclick={addSpace}>
|
||||
<Icon icon={AddCircle} />
|
||||
Add a Space
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
{/await}
|
||||
{/each}
|
||||
</PageContent>
|
||||
</Page>
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
} from "@welshman/util"
|
||||
import {pubkey, publishThunk, waitForThunkError, joinRoom, leaveRoom} from "@welshman/app"
|
||||
import {slide, fade, fly} from "@lib/transition"
|
||||
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
||||
import Pen from "@assets/icons/pen.svg?dataurl"
|
||||
import InfoCircle from "@assets/icons/info-circle.svg?dataurl"
|
||||
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
|
||||
import Login2 from "@assets/icons/login-3.svg?dataurl"
|
||||
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
|
||||
import Logout2 from "@assets/icons/logout-3.svg?dataurl"
|
||||
import Bookmark from "@assets/icons/bookmark.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -31,8 +29,9 @@
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import RoomEdit from "@app/components/RoomEdit.svelte"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import RoomImage from "@app/components/RoomImage.svelte"
|
||||
import RoomDetail from "@app/components/RoomDetail.svelte"
|
||||
import RoomItem from "@app/components/RoomItem.svelte"
|
||||
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
||||
import RoomItemRemoveMember from "@src/app/components/RoomItemRemoveMember.svelte"
|
||||
@@ -48,7 +47,6 @@
|
||||
MembershipStatus,
|
||||
PROTECTED,
|
||||
MESSAGE_KINDS,
|
||||
deriveUserIsRoomAdmin,
|
||||
} from "@app/core/state"
|
||||
import {setChecked, checked} from "@app/util/notifications"
|
||||
import {
|
||||
@@ -70,10 +68,11 @@
|
||||
const room = deriveRoom(url, h)
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const userRooms = deriveUserRooms(url)
|
||||
const userIsAdmin = deriveUserIsRoomAdmin(url, h)
|
||||
const isFavorite = $derived($userRooms.includes(h))
|
||||
const membershipStatus = deriveUserRoomMembershipStatus(url, h)
|
||||
|
||||
const showRoomDetail = () => pushModal(RoomDetail, {url, h})
|
||||
|
||||
const addFavorite = () => addRoomMembership(url, h)
|
||||
|
||||
const removeFavorite = () => removeRoomMembership(url, h)
|
||||
@@ -86,10 +85,10 @@
|
||||
|
||||
if (message && !message.startsWith("duplicate:")) {
|
||||
return pushToast({theme: "error", message})
|
||||
} else {
|
||||
// Restart the feed now that we're a member
|
||||
start()
|
||||
}
|
||||
|
||||
// Restart the feed now that we're a member
|
||||
start()
|
||||
} finally {
|
||||
joining = false
|
||||
}
|
||||
@@ -303,8 +302,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const startEdit = () => pushModal(RoomEdit, {url, h})
|
||||
|
||||
onMount(() => {
|
||||
const observer = new ResizeObserver(() => {
|
||||
if (dynamicPadding && chatCompose) {
|
||||
@@ -334,57 +331,25 @@
|
||||
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon={Hashtag} />
|
||||
</div>
|
||||
<RoomImage {url} {h} />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong class="ellipsize">
|
||||
<RoomName {url} {h} />
|
||||
</strong>
|
||||
<RoomName {url} {h} />
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
{#if $userIsAdmin}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Edit room information"
|
||||
onclick={startEdit}>
|
||||
<Icon size={4} icon={Pen} />
|
||||
</Button>
|
||||
{:else if $membershipStatus === MembershipStatus.Initial}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Request to be added to the member list"
|
||||
disabled={joining}
|
||||
onclick={join}>
|
||||
{#if joining}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<Icon size={4} icon={Login2} />
|
||||
{/if}
|
||||
</Button>
|
||||
{:else if $membershipStatus === MembershipStatus.Pending}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Membership is pending">
|
||||
<Icon size={4} icon={ClockCircle} />
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Request to be removed from member list"
|
||||
disabled={leaving}
|
||||
onclick={leave}>
|
||||
<Icon size={4} icon={Logout2} />
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip={isFavorite ? "Remove Favorite" : "Add Favorite"}
|
||||
onclick={isFavorite ? removeFavorite : addFavorite}>
|
||||
<Icon size={4} icon={Bookmark} class={cx({"text-primary": isFavorite})} />
|
||||
</Button>
|
||||
<Button
|
||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||
data-tip="Room information"
|
||||
onclick={showRoomDetail}>
|
||||
<Icon size={4} icon={InfoCircle} />
|
||||
</Button>
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<li>Requires some technical skills</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Link class="btn btn-primary" href="https://github.com/coracle-social/zooid">
|
||||
<Link external class="btn btn-primary" href="https://github.com/coracle-social/zooid">
|
||||
Get Started
|
||||
<Icon icon={ArrowRight} />
|
||||
</Link>
|
||||
@@ -101,7 +101,7 @@
|
||||
<li>Support available</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Link class="btn btn-neutral" href="https://relay.tools/signup">
|
||||
<Link external class="btn btn-neutral" href="https://relay.tools/signup">
|
||||
Get Started
|
||||
<Icon icon={ArrowRight} />
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user