forked from coracle/flotilla
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
|
# 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
|
# 1.5.1
|
||||||
|
|
||||||
* Fix chat path link
|
* Fix chat path link
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "social.flotilla"
|
applicationId "social.flotilla"
|
||||||
minSdk rootProject.ext.minSdkVersion
|
minSdk rootProject.ext.minSdkVersion
|
||||||
targetSdk rootProject.ext.targetSdkVersion
|
targetSdk rootProject.ext.targetSdkVersion
|
||||||
versionCode 33
|
versionCode 34
|
||||||
versionName "1.5.2"
|
versionName "1.5.3"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// 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_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 24;
|
CURRENT_PROJECT_VERSION = 25;
|
||||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
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\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -384,14 +384,14 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 24;
|
CURRENT_PROJECT_VERSION = 25;
|
||||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 1.5.2;
|
MARKETING_VERSION = 1.5.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|||||||
+11
-11
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "1.5.2",
|
"version": "1.5.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -60,16 +60,16 @@
|
|||||||
"@types/throttle-debounce": "^5.0.2",
|
"@types/throttle-debounce": "^5.0.2",
|
||||||
"@vite-pwa/assets-generator": "^0.2.6",
|
"@vite-pwa/assets-generator": "^0.2.6",
|
||||||
"@vite-pwa/sveltekit": "^0.6.8",
|
"@vite-pwa/sveltekit": "^0.6.8",
|
||||||
"@welshman/app": "^0.6.5",
|
"@welshman/app": "^0.6.8",
|
||||||
"@welshman/content": "^0.6.5",
|
"@welshman/content": "^0.6.8",
|
||||||
"@welshman/editor": "^0.6.5",
|
"@welshman/editor": "^0.6.8",
|
||||||
"@welshman/feeds": "^0.6.5",
|
"@welshman/feeds": "^0.6.8",
|
||||||
"@welshman/lib": "^0.6.5",
|
"@welshman/lib": "^0.6.8",
|
||||||
"@welshman/net": "^0.6.5",
|
"@welshman/net": "^0.6.8",
|
||||||
"@welshman/router": "^0.6.5",
|
"@welshman/router": "^0.6.8",
|
||||||
"@welshman/signer": "^0.6.5",
|
"@welshman/signer": "^0.6.8",
|
||||||
"@welshman/store": "^0.6.5",
|
"@welshman/store": "^0.6.8",
|
||||||
"@welshman/util": "^0.6.5",
|
"@welshman/util": "^0.6.8",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
"daisyui": "^4.12.24",
|
"daisyui": "^4.12.24",
|
||||||
"date-picker-svelte": "^2.16.0",
|
"date-picker-svelte": "^2.16.0",
|
||||||
|
|||||||
Generated
+76
-76
@@ -72,35 +72,35 @@ importers:
|
|||||||
specifier: ^0.6.8
|
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))
|
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':
|
'@welshman/app':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
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':
|
'@welshman/content':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(typescript@5.9.3)
|
version: 0.6.8(typescript@5.9.3)
|
||||||
'@welshman/editor':
|
'@welshman/editor':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
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)
|
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':
|
'@welshman/feeds':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
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':
|
'@welshman/lib':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5
|
version: 0.6.8
|
||||||
'@welshman/net':
|
'@welshman/net':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
version: 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||||
'@welshman/router':
|
'@welshman/router':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
version: 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||||
'@welshman/signer':
|
'@welshman/signer':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.4.3))(typescript@5.9.3)(ws@8.18.3)
|
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':
|
'@welshman/store':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(typescript@5.9.3)(ws@8.18.3)
|
version: 0.6.8(typescript@5.9.3)(ws@8.18.3)
|
||||||
'@welshman/util':
|
'@welshman/util':
|
||||||
specifier: ^0.6.5
|
specifier: ^0.6.8
|
||||||
version: 0.6.5(typescript@5.9.3)
|
version: 0.6.8(typescript@5.9.3)
|
||||||
compressorjs:
|
compressorjs:
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
@@ -1692,38 +1692,38 @@ packages:
|
|||||||
'@vite-pwa/assets-generator':
|
'@vite-pwa/assets-generator':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@welshman/app@0.6.5':
|
'@welshman/app@0.6.8':
|
||||||
resolution: {integrity: sha512-hk39kKzptldZxtFbYzgrEK8Y151o75GwV6P2sK5LlkyafWlhx3SwteAcuNelcJZitoAPXi7w06W34bbwRYPx+Q==}
|
resolution: {integrity: sha512-bhl18VWA9tzHLY7D+b2xlkc/RbJr03XiA7+otcjzf8X48S4pih/F4TDw1yJbAWOMOx9G3NI6sWLffpZQeSUPiQ==}
|
||||||
|
|
||||||
'@welshman/content@0.6.5':
|
'@welshman/content@0.6.8':
|
||||||
resolution: {integrity: sha512-QSlkuko+2r72q3VFlOXpnnoJ6GioCgan1ysHMlKqKarKNFTL4kfqdq1yxYrFRJdQou7WuB+f9iULO0AFWkXmXg==}
|
resolution: {integrity: sha512-VLek8oOoMMTrEtpIfqFqM9BsbifWYwPC7UiuVuWYqaTSmiAbU3DM2J+tYFcrgnQF8xMnUi/JoVXJ+b2AtpjFrw==}
|
||||||
|
|
||||||
'@welshman/editor@0.6.5':
|
'@welshman/editor@0.6.8':
|
||||||
resolution: {integrity: sha512-3sUnUFBeaVJbJgnkZlIqFqXv/NtnxXt3Pr6BkMYi2ocDMxHsMOIsOrCcyoXg8G5IYz7FzkWHtUtM3mhaDU7YVg==}
|
resolution: {integrity: sha512-QzNX7/Nobkh+bpjFnuW2REVpX7Sa+lj70LDdGmEJpjtXlTKlLuNZzpFLee5F9fSObcKCl1G2xBN3tYbZD3vHUA==}
|
||||||
|
|
||||||
'@welshman/feeds@0.6.5':
|
'@welshman/feeds@0.6.8':
|
||||||
resolution: {integrity: sha512-IT1kSN+Xf/MaoHAOHJftORDwJZxl3UCLizc+mvJ4ktvOT/oVu9YX5zcb0YMwiJN2N4C4FpK/BIBjxivS6QIaRQ==}
|
resolution: {integrity: sha512-95VRR2QmGrBUyzYgdsMxhntVoOnaEMsMHRsci1/GX1oOFZPJFTiV7e/m/dD/aWVLUQV1hlRxxXosFFtEDkpjIw==}
|
||||||
|
|
||||||
'@welshman/lib@0.6.5':
|
'@welshman/lib@0.6.8':
|
||||||
resolution: {integrity: sha512-L6NQm1QNBOTQ+ymiSFPfL+TDiW1AP64AEp633Fh1ciopaU73JFbV0P6xpLxt3qJkQFZfJRxk7gWDMaVDo3Y24w==}
|
resolution: {integrity: sha512-1Wybkk8+vBdqv9nRhnNwIW9YVbhu3di07A2fUYWAQvldto49X26U8u7EV2CkUsz4iNC/799EBYuelcc6W9oZYw==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
'@welshman/net@0.6.5':
|
'@welshman/net@0.6.8':
|
||||||
resolution: {integrity: sha512-JcmPdWzT0aUaOWytw4EJpl9EooOstm4LcJczc9pJYk1hQE4gDix1AfW6bqBiDlTnJ5fplOW/KLgXuV1rhsKEWA==}
|
resolution: {integrity: sha512-Lc1nIckdxW2ILiknowcbaKo+192QWQOBn6FLhFCEUZNyRNEOJYkAgDu4jKn7GXu91xpfJUFnq5KDvvq7hUeHqg==}
|
||||||
|
|
||||||
'@welshman/router@0.6.5':
|
'@welshman/router@0.6.8':
|
||||||
resolution: {integrity: sha512-7ZxAkCg09ZIeYh49LUlL7nFRvU4880DsMstEu2KRQQIO/wg6VZuMJh8+uKGQq5arul09rtNl1bhg0/YUFiAc/A==}
|
resolution: {integrity: sha512-+OJoD2Jm+yFiLc5FYb4/za66639CeIMYk7j4UAzR7n1z/gFQYMDviXqFYbcWHln3fgy4G7UF1HWBoU0sQD8EEw==}
|
||||||
|
|
||||||
'@welshman/signer@0.6.5':
|
'@welshman/signer@0.6.8':
|
||||||
resolution: {integrity: sha512-SgQCtb0du3vpyaRVGXM43CM5S0fTh+1WWLnZEWUBkEpyRzRtI9DegTowL6+vhbNxsWB6oHn/FqY7O3HLS5rIEw==}
|
resolution: {integrity: sha512-lt9Qq89TWyx/zSWgHkeVUX7MBCx86iBCkvzTdUIS7Ad6KfjjcYtsL9wAtfCc+TlvE87okOg97hAOvw18yIwfbw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
nostr-signer-capacitor-plugin: ~0.0.4
|
nostr-signer-capacitor-plugin: ~0.0.4
|
||||||
|
|
||||||
'@welshman/store@0.6.5':
|
'@welshman/store@0.6.8':
|
||||||
resolution: {integrity: sha512-Fdl8ygK2/pZRxbLGSWtJJGtf2wTm46RDrCF2zURDJL6e80NGmTXl7LhqpSeRKmR1sTQiwEhsRvD1lqnKAWB3xQ==}
|
resolution: {integrity: sha512-s5s5+tdPyXB1m2vLn2wfo7nx+uNKWBdwCyomk+soWKWEY3LWvg4DAKgQ1gF5hyOcja+UIHOJY9hS3BBEo0DtDA==}
|
||||||
|
|
||||||
'@welshman/util@0.6.5':
|
'@welshman/util@0.6.8':
|
||||||
resolution: {integrity: sha512-BmKgDtgSk0RSnw3YyExN8Mm25TJuJnjvE/7foTENpf2bMo2+PTXwVERNmgDEHRq9MCbmTkPv4h7kJTbpywLMVA==}
|
resolution: {integrity: sha512-Q4x3Jm3yIk4zORYOscMuxyC7fJGyZFetE5U4PVYNrvgtSLCtULYKs1y6WkAra4FD7zfAa7lqzTlQq4uIZWzdkA==}
|
||||||
|
|
||||||
'@xml-tools/parser@1.0.11':
|
'@xml-tools/parser@1.0.11':
|
||||||
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
|
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
|
||||||
@@ -6651,16 +6651,16 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@vite-pwa/assets-generator': 0.2.6
|
'@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:
|
dependencies:
|
||||||
'@types/throttle-debounce': 5.0.2
|
'@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/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.5
|
'@welshman/lib': 0.6.8
|
||||||
'@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)
|
||||||
'@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)
|
||||||
'@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)
|
||||||
'@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)
|
||||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||||
fuse.js: 7.1.0
|
fuse.js: 7.1.0
|
||||||
svelte: 4.2.20
|
svelte: 4.2.20
|
||||||
throttle-debounce: 5.0.2
|
throttle-debounce: 5.0.2
|
||||||
@@ -6669,14 +6669,14 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/content@0.6.5(typescript@5.9.3)':
|
'@welshman/content@0.6.8(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@braintree/sanitize-url': 7.1.1
|
'@braintree/sanitize-url': 7.1.1
|
||||||
nostr-tools: 2.17.0(typescript@5.9.3)
|
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- 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:
|
dependencies:
|
||||||
'@tiptap/core': 2.26.3(@tiptap/pm@2.26.3)
|
'@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))
|
'@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/extension-text': 2.27.1(@tiptap/core@2.26.3(@tiptap/pm@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)
|
'@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/lib': 0.6.8
|
||||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
'@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-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)
|
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||||
tippy.js: 6.3.7
|
tippy.js: 6.3.7
|
||||||
@@ -6707,71 +6707,71 @@ snapshots:
|
|||||||
- tiptap-markdown
|
- tiptap-markdown
|
||||||
- typescript
|
- 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:
|
dependencies:
|
||||||
'@welshman/lib': 0.6.5
|
'@welshman/lib': 0.6.8
|
||||||
'@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)
|
||||||
'@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)
|
||||||
'@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)
|
||||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||||
trava: 1.2.1
|
trava: 1.2.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- nostr-signer-capacitor-plugin
|
- nostr-signer-capacitor-plugin
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/lib@0.6.5':
|
'@welshman/lib@0.6.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@scure/base': 1.2.6
|
'@scure/base': 1.2.6
|
||||||
'@types/events': 3.0.3
|
'@types/events': 3.0.3
|
||||||
events: 3.3.0
|
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:
|
dependencies:
|
||||||
'@welshman/lib': 0.6.5
|
'@welshman/lib': 0.6.8
|
||||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
isomorphic-ws: 5.0.0(ws@8.18.3)
|
isomorphic-ws: 5.0.0(ws@8.18.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- 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:
|
dependencies:
|
||||||
'@welshman/lib': 0.6.5
|
'@welshman/lib': 0.6.8
|
||||||
'@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)
|
||||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- 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:
|
dependencies:
|
||||||
'@noble/curves': 1.9.7
|
'@noble/curves': 1.9.7
|
||||||
'@noble/hashes': 1.8.0
|
'@noble/hashes': 1.8.0
|
||||||
'@welshman/lib': 0.6.5
|
'@welshman/lib': 0.6.8
|
||||||
'@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)
|
||||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||||
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.4.3)
|
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.4.3)
|
||||||
nostr-tools: 2.17.0(typescript@5.9.3)
|
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- 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:
|
dependencies:
|
||||||
'@welshman/lib': 0.6.5
|
'@welshman/lib': 0.6.8
|
||||||
'@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)
|
||||||
'@welshman/util': 0.6.5(typescript@5.9.3)
|
'@welshman/util': 0.6.8(typescript@5.9.3)
|
||||||
svelte: 4.2.20
|
svelte: 4.2.20
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/util@0.6.5(typescript@5.9.3)':
|
'@welshman/util@0.6.8(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ws': 8.18.1
|
'@types/ws': 8.18.1
|
||||||
'@welshman/lib': 0.6.5
|
'@welshman/lib': 0.6.8
|
||||||
js-base64: 3.7.8
|
js-base64: 3.7.8
|
||||||
nostr-tools: 2.17.0(typescript@5.9.3)
|
nostr-tools: 2.17.0(typescript@5.9.3)
|
||||||
nostr-wasm: 0.1.0
|
nostr-wasm: 0.1.0
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="col-4">
|
<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">
|
<div class="flex items-center justify-between">
|
||||||
<strong class="flex items-center gap-3">
|
<strong class="flex items-center gap-3">
|
||||||
<Icon icon={Inbox} />
|
<Icon icon={Inbox} />
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="flex items-center justify-between">
|
||||||
<strong class="flex items-center gap-3">
|
<strong class="flex items-center gap-3">
|
||||||
<Icon icon={Bell} />
|
<Icon icon={Bell} />
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link
|
<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)}>
|
href={makeCalendarPath(url, event.id)}>
|
||||||
<CalendarEventHeader {event} />
|
<CalendarEventHeader {event} />
|
||||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
<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 {type Instance} from "tippy.js"
|
||||||
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {
|
import {thunks, mergeThunks, pubkey, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
||||||
thunks,
|
|
||||||
mergeThunks,
|
|
||||||
pubkey,
|
|
||||||
deriveProfile,
|
|
||||||
deriveProfileDisplay,
|
|
||||||
sendWrapped,
|
|
||||||
} from "@welshman/app"
|
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
import TapTarget from "@lib/components/TapTarget.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 Content from "@app/components/Content.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
@@ -37,7 +30,6 @@
|
|||||||
const {event, replyTo, pubkeys, showPubkey = false}: Props = $props()
|
const {event, replyTo, pubkeys, showPubkey = false}: Props = $props()
|
||||||
|
|
||||||
const isOwn = event.pubkey === $pubkey
|
const isOwn = event.pubkey === $pubkey
|
||||||
const profile = deriveProfile(event.pubkey)
|
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
||||||
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
||||||
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
||||||
@@ -107,8 +99,8 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{#if !isOwn}
|
{#if !isOwn}
|
||||||
<Button onclick={openProfile} class="flex items-center gap-1">
|
<Button onclick={openProfile} class="flex items-center gap-1">
|
||||||
<Avatar
|
<ProfileCircle
|
||||||
src={$profile?.picture}
|
pubkey={event.pubkey}
|
||||||
class="border border-solid border-base-content"
|
class="border border-solid border-base-content"
|
||||||
size={4} />
|
size={4} />
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</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>
|
<li>
|
||||||
<Button onclick={createGoal}>
|
<Button onclick={createGoal}>
|
||||||
<Icon size={4} icon={StarFallMinimalistic} />
|
<Icon size={4} icon={StarFallMinimalistic} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import type {ProfilePointer} from "@welshman/content"
|
import type {ProfilePointer} from "@welshman/content"
|
||||||
import {deriveProfileDisplay} from "@welshman/app"
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
const {value, url}: Props = $props()
|
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})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</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}
|
{#if isRoot}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={share}>
|
<Button onclick={share}>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
const h = getTagValue("h", event.tags)
|
const h = getTagValue("h", event.tags)
|
||||||
</script>
|
</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>
|
<p class="text-2xl">{event.content}</p>
|
||||||
<Content
|
<Content
|
||||||
event={{content: summary, tags: event.tags}}
|
event={{content: summary, tags: event.tags}}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</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">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon={Magnifier} />
|
<Icon icon={Magnifier} />
|
||||||
<input bind:value={searchTerm} class="grow" type="text" placeholder="Search icons..." />
|
<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 RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
||||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
import LinkRound from "@assets/icons/link-round.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 Exit from "@assets/icons/logout-3.svg?dataurl"
|
||||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||||
import Login from "@assets/icons/login-3.svg?dataurl"
|
import Login from "@assets/icons/login-3.svg?dataurl"
|
||||||
@@ -133,7 +134,7 @@
|
|||||||
<Popover hideOnClick onClose={toggleMenu}>
|
<Popover hideOnClick onClose={toggleMenu}>
|
||||||
<ul
|
<ul
|
||||||
transition:fly
|
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>
|
<li>
|
||||||
<Button onclick={createInvite}>
|
<Button onclick={createInvite}>
|
||||||
<Icon icon={LinkRound} />
|
<Icon icon={LinkRound} />
|
||||||
@@ -157,6 +158,7 @@
|
|||||||
<Link external href="https://landlubber.coracle.social">
|
<Link external href="https://landlubber.coracle.social">
|
||||||
<Icon icon={Tuning2} />
|
<Icon icon={Tuning2} />
|
||||||
Manage Space
|
Manage Space
|
||||||
|
<Icon icon={SquareTopDown} size={4} class="opacity-50" />
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
{:else if $relay?.pubkey}
|
{:else if $relay?.pubkey}
|
||||||
@@ -184,7 +186,7 @@
|
|||||||
</Popover>
|
</Popover>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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)}
|
{#if hasNip29($relay)}
|
||||||
<SecondaryNavItem {replaceState} href={makeSpacePath(url, "recent")}>
|
<SecondaryNavItem {replaceState} href={makeSpacePath(url, "recent")}>
|
||||||
<Icon icon={History} /> Recent Activity
|
<Icon icon={History} /> Recent Activity
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import CardButton from "@lib/components/CardButton.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 RelayName from "@app/components/RelayName.svelte"
|
||||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link replaceState href={path}>
|
<Link replaceState href={path}>
|
||||||
<CardButton class="btn-neutral">
|
<CardButton class="btn-neutral shadow-md">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><SpaceAvatar {url} /></div>
|
<RelayIcon {url} size={12} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
|
||||||
</script>
|
</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">
|
<div class="flex justify-between">
|
||||||
<Profile {pubkey} {url} />
|
<Profile {pubkey} {url} />
|
||||||
<Button onclick={openProfile} class="btn btn-primary hidden sm:flex">
|
<Button onclick={openProfile} class="btn btn-primary hidden sm:flex">
|
||||||
|
|||||||
@@ -4,7 +4,15 @@
|
|||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {splitAt} from "@welshman/lib"
|
import {splitAt} from "@welshman/lib"
|
||||||
import {userProfile, shouldUnwrap} from "@welshman/app"
|
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 Divider from "@lib/components/Divider.svelte"
|
||||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
||||||
import ChatEnable from "@app/components/ChatEnable.svelte"
|
import ChatEnable from "@app/components/ChatEnable.svelte"
|
||||||
@@ -15,13 +23,6 @@
|
|||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {notifications} from "@app/util/notifications"
|
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 = {
|
type Props = {
|
||||||
children?: Snippet
|
children?: Snippet
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
<PrimaryNavItemSpace {url} />
|
<PrimaryNavItemSpace {url} />
|
||||||
{:else}
|
{:else}
|
||||||
<PrimaryNavItem title="Home" href="/home" class="tooltip-right">
|
<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>
|
</PrimaryNavItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
{#each primarySpaceUrls as url (url)}
|
{#each primarySpaceUrls as url (url)}
|
||||||
@@ -73,11 +74,11 @@
|
|||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
onclick={showOtherSpacesMenu}
|
onclick={showOtherSpacesMenu}
|
||||||
notification={otherSpaceNotifications}>
|
notification={otherSpaceNotifications}>
|
||||||
<Avatar icon={Widget} class="!h-10 !w-10" />
|
<ImageIcon alt="Other Spaces" src={Widget} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
<PrimaryNavItem title="Add a Space" href="/discover" class="tooltip-right">
|
<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>
|
</PrimaryNavItem>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -90,17 +91,17 @@
|
|||||||
href="/settings/profile"
|
href="/settings/profile"
|
||||||
prefix="/settings"
|
prefix="/settings"
|
||||||
class="tooltip-right">
|
class="tooltip-right">
|
||||||
<Avatar src={$userProfile?.picture} class="!h-10 !w-10" />
|
<ImageIcon alt="Settings" src={$userProfile?.picture || UserRounded} class="rounded-full" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Messages"
|
title="Messages"
|
||||||
onclick={openChat}
|
onclick={openChat}
|
||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
notification={$notifications.has("/chat")}>
|
notification={$notifications.has("/chat")}>
|
||||||
<Avatar icon={Letter} class="!h-10 !w-10" />
|
<ImageIcon alt="Messages" src={Letter} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
|
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
|
||||||
<Avatar icon={Magnifier} class="!h-10 !w-10" />
|
<ImageIcon alt="Search" src={Magnifier} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,22 +116,26 @@
|
|||||||
<div class="content-padding-x content-sizing flex justify-between px-2">
|
<div class="content-padding-x content-sizing flex justify-between px-2">
|
||||||
<div class="flex gap-2 sm:gap-6">
|
<div class="flex gap-2 sm:gap-6">
|
||||||
<PrimaryNavItem title="Home" href="/home">
|
<PrimaryNavItem title="Home" href="/home">
|
||||||
<Avatar icon={HomeSmile} class="!h-10 !w-10" />
|
<ImageIcon alt="Home" src={HomeSmile} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Messages"
|
title="Messages"
|
||||||
onclick={openChat}
|
onclick={openChat}
|
||||||
notification={$notifications.has("/chat")}>
|
notification={$notifications.has("/chat")}>
|
||||||
<Avatar icon={Letter} class="!h-10 !w-10" />
|
<ImageIcon alt="Messages" src={Letter} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{#if PLATFORM_RELAYS.length !== 1}
|
{#if PLATFORM_RELAYS.length !== 1}
|
||||||
<PrimaryNavItem title="Spaces" href="/spaces" notification={anySpaceNotifications}>
|
<PrimaryNavItem title="Spaces" href="/spaces" notification={anySpaceNotifications}>
|
||||||
<Avatar icon={SettingsMinimalistic} class="!h-10 !w-10" />
|
<ImageIcon alt="Spaces" src={SettingsMinimalistic} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
<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>
|
</PrimaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
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 {makeSpacePath, goToSpace} from "@app/util/routes"
|
||||||
import {notifications} from "@app/util/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
|
||||||
@@ -15,5 +15,5 @@
|
|||||||
title={displayRelayUrl(url)}
|
title={displayRelayUrl(url)}
|
||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
notification={$notifications.has(makeSpacePath(url))}>
|
notification={$notifications.has(makeSpacePath(url))}>
|
||||||
<SpaceAvatar {url} />
|
<RelayIcon {url} size={10} class="rounded-full" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {displayPubkey} from "@welshman/util"
|
import {displayPubkey} from "@welshman/util"
|
||||||
import {
|
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
||||||
deriveHandleForPubkey,
|
|
||||||
displayHandle,
|
|
||||||
deriveProfile,
|
|
||||||
deriveProfileDisplay,
|
|
||||||
} from "@welshman/app"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.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 WotScore from "@app/components/WotScore.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
@@ -26,8 +21,7 @@
|
|||||||
|
|
||||||
const {pubkey, url, showPubkey, avatarSize = 10}: Props = $props()
|
const {pubkey, url, showPubkey, avatarSize = 10}: Props = $props()
|
||||||
|
|
||||||
const relays = removeNil([url])
|
const relays = removeUndefined([url])
|
||||||
const profile = deriveProfile(pubkey, relays)
|
|
||||||
const profileDisplay = deriveProfileDisplay(pubkey, relays)
|
const profileDisplay = deriveProfileDisplay(pubkey, relays)
|
||||||
const handle = deriveHandleForPubkey(pubkey)
|
const handle = deriveHandleForPubkey(pubkey)
|
||||||
|
|
||||||
@@ -38,7 +32,7 @@
|
|||||||
|
|
||||||
<div class="flex max-w-full items-start gap-3">
|
<div class="flex max-w-full items-start gap-3">
|
||||||
<Button onclick={openProfile} class="py-1">
|
<Button onclick={openProfile} class="py-1">
|
||||||
<Avatar src={$profile?.picture} size={avatarSize} />
|
<ProfileCircle {pubkey} size={avatarSize} />
|
||||||
</Button>
|
</Button>
|
||||||
<div class="flex min-w-0 flex-col">
|
<div class="flex min-w-0 flex-col">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import cx from "classnames"
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfile} from "@welshman/app"
|
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 = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
class?: string
|
||||||
|
size?: number
|
||||||
url?: string
|
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>
|
</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">
|
<script lang="ts">
|
||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
|
|
||||||
const {...props} = $props()
|
type Props = {
|
||||||
|
pubkeys: string[]
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const {pubkeys, size = 7}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex pr-3">
|
<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">
|
<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>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
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 Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Link external href={pubkeyLink(pubkey)} class="btn btn-neutral">
|
<Link external href={pubkeyLink(pubkey)} class="btn btn-neutral">
|
||||||
<Avatar src="/coracle.png" />
|
<ImageIcon alt="Open in Coracle" src="/coracle.png" />
|
||||||
Open in Coracle
|
Open in Coracle
|
||||||
</Link>
|
</Link>
|
||||||
<Button onclick={openChat} class="btn btn-primary">
|
<Button onclick={openChat} class="btn btn-primary">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfile} from "@welshman/app"
|
import {deriveProfile} from "@welshman/app"
|
||||||
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
const {pubkey, url}: Props = $props()
|
const {pubkey, url}: Props = $props()
|
||||||
|
|
||||||
const profile = deriveProfile(pubkey, removeNil([url]))
|
const profile = deriveProfile(pubkey, removeUndefined([url]))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $profile}
|
{#if $profile}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfileDisplay} from "@welshman/app"
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
const {pubkey, url}: Props = $props()
|
const {pubkey, url}: Props = $props()
|
||||||
|
|
||||||
const profileDisplay = deriveProfileDisplay(pubkey, removeNil([url]))
|
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url]))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{$profileDisplay}
|
{$profileDisplay}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.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 RelayName from "@app/components/RelayName.svelte"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {deriveGroupSelections, getSpaceUrlsFromGroupSelections} from "@app/core/state"
|
import {deriveGroupSelections, getSpaceUrlsFromGroupSelections} from "@app/core/state"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{#each spaceUrls as url (url)}
|
{#each spaceUrls as url (url)}
|
||||||
<div class="card2 bg-alt flex flex-row items-center gap-2">
|
<div class="card2 bg-alt flex flex-row items-center gap-2">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<SpaceAvatar {url} />
|
<RelayIcon {url} size={12} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-grow flex-col">
|
<div class="flex flex-grow flex-col">
|
||||||
<RelayName {url} />
|
<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">
|
<script lang="ts">
|
||||||
import {deriveRelayDisplay} from "@welshman/app"
|
import {deriveRelayDisplay} from "@welshman/app"
|
||||||
|
|
||||||
const {url} = $props()
|
type Props = {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url}: Props = $props()
|
||||||
|
|
||||||
const display = $derived(deriveRelayDisplay(url))
|
const display = $derived(deriveRelayDisplay(url))
|
||||||
</script>
|
</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 {goto} from "$app/navigation"
|
||||||
import type {RoomMeta} from "@welshman/util"
|
import type {RoomMeta} from "@welshman/util"
|
||||||
import {displayRelayUrl} 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 AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Confirm from "@lib/components/Confirm.svelte"
|
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import RoomForm from "@app/components/RoomForm.svelte"
|
import RoomForm from "@app/components/RoomForm.svelte"
|
||||||
import {deriveRoom} from "@app/core/state"
|
import {deriveRoom} from "@app/core/state"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {pushModal} from "@app/util/modal"
|
|
||||||
import {pushToast} from "@app/util/toast"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -29,24 +24,6 @@
|
|||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const onsubmit = (room: RoomMeta) => goto(makeSpacePath(url, h))
|
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>
|
</script>
|
||||||
|
|
||||||
<RoomForm {url} {onsubmit} initialValues={$room}>
|
<RoomForm {url} {onsubmit} initialValues={$room}>
|
||||||
@@ -68,15 +45,9 @@
|
|||||||
<Icon icon={AltArrowLeft} />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<div class="flex gap-2">
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
<Button class="btn btn-outline btn-error" onclick={startDelete}>
|
<Spinner {loading}>Save Changes</Spinner>
|
||||||
<Icon icon={TrashBin2} />
|
</Button>
|
||||||
<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>
|
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</RoomForm>
|
</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,
|
thunks,
|
||||||
pubkey,
|
pubkey,
|
||||||
mergeThunks,
|
mergeThunks,
|
||||||
deriveProfile,
|
|
||||||
deriveProfileDisplay,
|
deriveProfileDisplay,
|
||||||
displayProfileByPubkey,
|
displayProfileByPubkey,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
@@ -16,12 +15,12 @@
|
|||||||
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
import ReplyAlt from "@assets/icons/reply.svg?dataurl"
|
import ReplyAlt from "@assets/icons/reply.svg?dataurl"
|
||||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import RoomItemZapButton from "@app/components/RoomItemZapButton.svelte"
|
import RoomItemZapButton from "@app/components/RoomItemZapButton.svelte"
|
||||||
import RoomItemEmojiButton from "@app/components/RoomItemEmojiButton.svelte"
|
import RoomItemEmojiButton from "@app/components/RoomItemEmojiButton.svelte"
|
||||||
@@ -56,7 +55,6 @@
|
|||||||
const path = getRoomItemPath(url, event)
|
const path = getRoomItemPath(url, event)
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
const today = formatTimestampAsDate(now())
|
const today = formatTimestampAsDate(now())
|
||||||
const profile = deriveProfile(event.pubkey, [url])
|
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
||||||
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
||||||
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
||||||
@@ -83,7 +81,10 @@
|
|||||||
<div class="flex w-full gap-3 overflow-auto">
|
<div class="flex w-full gap-3 overflow-auto">
|
||||||
{#if showPubkey}
|
{#if showPubkey}
|
||||||
<Button onclick={openProfile} class="flex items-start">
|
<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>
|
</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="w-8 min-w-8 max-w-8"></div>
|
<div class="w-8 min-w-8 max-w-8"></div>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</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>
|
<li>
|
||||||
<Button onclick={showInfo}>
|
<Button onclick={showInfo}>
|
||||||
<Icon size={4} icon={Code2} />
|
<Icon size={4} icon={Code2} />
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
<script lang="ts">
|
<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>
|
</script>
|
||||||
|
|
||||||
{$roomsById.get(makeRoomId(url, h))?.name || h}
|
<span class="ellipsize {props.class}">
|
||||||
|
{$room?.name || h}
|
||||||
|
</span>
|
||||||
|
|||||||
@@ -1,26 +1,21 @@
|
|||||||
<script lang="ts">
|
<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 RoomName from "@app/components/RoomName.svelte"
|
||||||
import {deriveRoom} from "@app/core/state"
|
import RoomImage from "@app/components/RoomImage.svelte"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: any
|
h: string
|
||||||
h: any
|
url: string
|
||||||
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, h}: Props = $props()
|
const {url, h, ...props}: Props = $props()
|
||||||
|
|
||||||
const room = deriveRoom(url, h)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $room.picture}
|
<div class="flex flex-grow items-center justify-between gap-4 {props.class}">
|
||||||
{@const src = $room.picture}
|
<div class="flex items-center gap-3">
|
||||||
<ImageIcon {src} alt="Room icon" />
|
<RoomImage {url} {h} />
|
||||||
{:else}
|
<div class="min-w-0 overflow-hidden text-ellipsis">
|
||||||
<Icon icon={Hashtag} />
|
<RoomName {url} {h} />
|
||||||
{/if}
|
</div>
|
||||||
<div class="min-w-0 overflow-hidden text-ellipsis">
|
</div>
|
||||||
<RoomName {url} {h} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,12 +12,29 @@
|
|||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import SpaceAccessRequest from "@app/components/SpaceAccessRequest.svelte"
|
import SpaceAccessRequest from "@app/components/SpaceAccessRequest.svelte"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import {removeSpaceMembership, publishLeaveRequest, removeTrustedRelay} from "@app/core/commands"
|
||||||
|
|
||||||
const {url, error} = $props()
|
const {url, error} = $props()
|
||||||
|
|
||||||
const back = () => goto("/home")
|
const back = () => goto("/home")
|
||||||
|
|
||||||
const requestAccess = () => pushModal(SpaceAccessRequest, {url})
|
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>
|
</script>
|
||||||
|
|
||||||
<form class="column gap-4" onsubmit={preventDefault(requestAccess)}>
|
<form class="column gap-4" onsubmit={preventDefault(requestAccess)}>
|
||||||
@@ -40,9 +57,14 @@
|
|||||||
<Icon icon={AltArrowLeft} />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go Home
|
Go Home
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary">
|
<div class="flex gap-2">
|
||||||
Request Access
|
<Button class="btn btn-outline btn-error" onclick={leaveSpace} disabled={loading}>
|
||||||
<Icon icon={AltArrowRight} />
|
Leave Space
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
|
Request Access
|
||||||
|
<Icon icon={AltArrowRight} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</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 Button from "@lib/components/Button.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.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 {attemptRelayAccess} from "@app/core/commands"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const next = () => {
|
const next = async () => {
|
||||||
if (!error && Pool.get().get(url).auth.status === AuthStatus.None) {
|
if (error) {
|
||||||
pushModal(SpaceVisitConfirm, {url}, {replaceState: true})
|
return pushToast({theme: "error", message: error, timeout: 30_000})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Pool.get().get(url).auth.status === AuthStatus.None) {
|
||||||
|
pushModal(SpaceJoinConfirm, {url}, {replaceState: true})
|
||||||
} else {
|
} else {
|
||||||
confirmSpaceVisit(url)
|
await confirmSpaceJoin(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +77,7 @@
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
Go to Space
|
Join Space
|
||||||
<Icon icon={AltArrowRight} />
|
<Icon icon={AltArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
|||||||
@@ -2,16 +2,22 @@
|
|||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import {deriveRelay} from "@welshman/app"
|
import {deriveRelay} from "@welshman/app"
|
||||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
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 ShieldUser from "@assets/icons/shield-user.svg?dataurl"
|
||||||
import BillList from "@assets/icons/bill-list.svg?dataurl"
|
import BillList from "@assets/icons/bill-list.svg?dataurl"
|
||||||
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
|
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
import RelayName from "@app/components/RelayName.svelte"
|
||||||
|
import SpaceEdit from "@app/components/SpaceEdit.svelte"
|
||||||
import SpaceRelayStatus from "@app/components/SpaceRelayStatus.svelte"
|
import SpaceRelayStatus from "@app/components/SpaceRelayStatus.svelte"
|
||||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||||
import ProfileLatest from "@app/components/ProfileLatest.svelte"
|
import ProfileLatest from "@app/components/ProfileLatest.svelte"
|
||||||
|
import {deriveUserIsSpaceAdmin} from "@app/core/state"
|
||||||
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -20,8 +26,11 @@
|
|||||||
const {url}: Props = $props()
|
const {url}: Props = $props()
|
||||||
const relay = deriveRelay(url)
|
const relay = deriveRelay(url)
|
||||||
const owner = $derived($relay?.pubkey)
|
const owner = $derived($relay?.pubkey)
|
||||||
|
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const startEdit = () => pushModal(SpaceEdit, {url, initialValues: $relay})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
@@ -78,5 +87,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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>
|
</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)
|
await addSpaceMembership(url)
|
||||||
|
|
||||||
broadcastUserData([url])
|
broadcastUserData([url])
|
||||||
goto(makeSpacePath(url), {replaceState: true})
|
|
||||||
relaysMostlyRestricted.update(dissoc(url))
|
relaysMostlyRestricted.update(dissoc(url))
|
||||||
|
goto(makeSpacePath(url), {replaceState: true})
|
||||||
pushToast({message: "Welcome to the space!"})
|
pushToast({message: "Welcome to the space!"})
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
</script>
|
||||||
|
|
||||||
<div class="card2 bg-alt col-2 shadow-2xl">
|
<div class="card2 bg-alt col-2 shadow-lg">
|
||||||
<p>
|
<p>
|
||||||
Failed to publish to {displayRelayUrl(url)}: {message}.
|
Failed to publish to {displayRelayUrl(url)}: {message}.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ export const payInvoice = async (invoice: string) => {
|
|||||||
|
|
||||||
export const normalizeBlossomUrl = (url: string) => normalizeUrl(url.replace(/^ws/, "http"))
|
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 server = normalizeBlossomUrl(url)
|
||||||
const $signer = signer.get() || Nip01Signer.ephemeral()
|
const $signer = signer.get() || Nip01Signer.ephemeral()
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
@@ -633,7 +633,9 @@ export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
})
|
}
|
||||||
|
|
||||||
|
export const hasBlossomSupport = simpleCache(([url]: [string]) => fetchHasBlossomSupport(url))
|
||||||
|
|
||||||
export type GetBlossomServerOptions = {
|
export type GetBlossomServerOptions = {
|
||||||
url?: string
|
url?: string
|
||||||
@@ -658,6 +660,8 @@ export const getBlossomServer = async (options: GetBlossomServerOptions = {}) =>
|
|||||||
export type UploadFileOptions = {
|
export type UploadFileOptions = {
|
||||||
url?: string
|
url?: string
|
||||||
encrypt?: boolean
|
encrypt?: boolean
|
||||||
|
maxWidth?: number
|
||||||
|
maxHeight?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UploadFileResult = {
|
export type UploadFileResult = {
|
||||||
@@ -669,8 +673,8 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
|||||||
try {
|
try {
|
||||||
const {name, type} = file
|
const {name, type} = file
|
||||||
|
|
||||||
if (!type.match("image/(webp|gif)")) {
|
if (!type.match("image/(webp|gif|svg)")) {
|
||||||
file = await compressFile(file)
|
file = await compressFile(file, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags: string[][] = []
|
const tags: string[][] = []
|
||||||
@@ -701,7 +705,7 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
|||||||
let {uploaded, url, ...task} = parseJson(text) || {}
|
let {uploaded, url, ...task} = parseJson(text) || {}
|
||||||
|
|
||||||
if (!uploaded) {
|
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
|
// Always append correct file extension if we encrypted the file, or if it's missing
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
sortBy,
|
sortBy,
|
||||||
now,
|
now,
|
||||||
on,
|
on,
|
||||||
isNotNil,
|
isDefined,
|
||||||
filterVals,
|
filterVals,
|
||||||
fromPairs,
|
fromPairs,
|
||||||
} from "@welshman/lib"
|
} from "@welshman/lib"
|
||||||
@@ -279,6 +279,6 @@ export const requestRelayClaim = async (url: string) => {
|
|||||||
|
|
||||||
export const requestRelayClaims = async (urls: string[]) =>
|
export const requestRelayClaims = async (urls: string[]) =>
|
||||||
filterVals(
|
filterVals(
|
||||||
isNotNil,
|
isDefined,
|
||||||
fromPairs(await Promise.all(urls.map(async url => [url, await requestRelayClaim(url)]))),
|
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)
|
return getTagValues("member", membersEvent.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = new Set()
|
const members = new Set<string>()
|
||||||
|
|
||||||
for (const event of sortBy(e => e.created_at, $events)) {
|
for (const event of sortBy(e => e.created_at, $events)) {
|
||||||
const pubkeys = getPubkeyTagValues(event.tags)
|
const pubkeys = getPubkeyTagValues(event.tags)
|
||||||
@@ -765,7 +765,7 @@ export const deriveRoomMembers = (url: string, h: string) =>
|
|||||||
return getPubkeyTagValues(membersEvent.tags)
|
return getPubkeyTagValues(membersEvent.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = new Set()
|
const members = new Set<string>()
|
||||||
|
|
||||||
for (const event of sortBy(e => -e.created_at, $events)) {
|
for (const event of sortBy(e => -e.created_at, $events)) {
|
||||||
const pubkeys = getPubkeyTagValues(event.tags)
|
const pubkeys = getPubkeyTagValues(event.tags)
|
||||||
@@ -806,18 +806,29 @@ export enum MembershipStatus {
|
|||||||
Granted,
|
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) =>
|
export const deriveUserSpaceMembershipStatus = (url: string) =>
|
||||||
derived(
|
derived(
|
||||||
[
|
[
|
||||||
pubkey,
|
pubkey,
|
||||||
deriveSpaceMembers(url),
|
deriveSpaceMembers(url),
|
||||||
deriveEventsForUrl(url, [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]),
|
deriveEventsForUrl(url, [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]),
|
||||||
|
deriveUserIsSpaceAdmin(url),
|
||||||
],
|
],
|
||||||
([$pubkey, $members, $events]) => {
|
([$pubkey, $members, $events, $isAdmin]) => {
|
||||||
const isMember = $members.includes($pubkey)
|
const isMember = $members.includes($pubkey!) || $isAdmin
|
||||||
|
|
||||||
for (const event of $events) {
|
for (const event of $events) {
|
||||||
if (!getPubkeyTagValues(event.tags).includes($pubkey!)) {
|
if (event.pubkey !== $pubkey) {
|
||||||
continue
|
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) =>
|
export const deriveUserRoomMembershipStatus = (url: string, h: string) =>
|
||||||
derived(
|
derived(
|
||||||
[
|
[
|
||||||
pubkey,
|
pubkey,
|
||||||
deriveRoomMembers(url, h),
|
deriveRoomMembers(url, h),
|
||||||
deriveEventsForUrl(url, [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]),
|
deriveEventsForUrl(url, [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]),
|
||||||
|
deriveUserIsRoomAdmin(url, h),
|
||||||
],
|
],
|
||||||
([$pubkey, $members, $events]) => {
|
([$pubkey, $members, $events, $isAdmin]) => {
|
||||||
const isMember = $members.includes($pubkey)
|
const isMember = $members.includes($pubkey!) || $isAdmin
|
||||||
|
|
||||||
for (const event of $events) {
|
for (const event of $events) {
|
||||||
if (!getPubkeyTagValues(event.tags).includes($pubkey!)) {
|
if (event.pubkey !== $pubkey) {
|
||||||
continue
|
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
|
// Other utils
|
||||||
|
|
||||||
export const encodeRelay = (url: string) =>
|
export const encodeRelay = (url: string) =>
|
||||||
|
|||||||
+4
-66
@@ -6,7 +6,6 @@ import {
|
|||||||
getListTags,
|
getListTags,
|
||||||
getRelayTagValues,
|
getRelayTagValues,
|
||||||
WRAP,
|
WRAP,
|
||||||
MESSAGE,
|
|
||||||
ROOM_META,
|
ROOM_META,
|
||||||
ROOM_DELETE,
|
ROOM_DELETE,
|
||||||
ROOM_ADMINS,
|
ROOM_ADMINS,
|
||||||
@@ -37,9 +36,9 @@ import {
|
|||||||
repository,
|
repository,
|
||||||
shouldUnwrap,
|
shouldUnwrap,
|
||||||
hasNegentropy,
|
hasNegentropy,
|
||||||
relaysByUrl,
|
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import {
|
import {
|
||||||
|
REACTION_KINDS,
|
||||||
MESSAGE_KINDS,
|
MESSAGE_KINDS,
|
||||||
CONTENT_KINDS,
|
CONTENT_KINDS,
|
||||||
INDEXER_RELAYS,
|
INDEXER_RELAYS,
|
||||||
@@ -50,7 +49,6 @@ import {
|
|||||||
bootstrapPubkeys,
|
bootstrapPubkeys,
|
||||||
decodeRelay,
|
decodeRelay,
|
||||||
getUrlsForEvent,
|
getUrlsForEvent,
|
||||||
hasNip29,
|
|
||||||
getSpaceUrlsFromGroupSelections,
|
getSpaceUrlsFromGroupSelections,
|
||||||
getSpaceRoomsFromGroupSelections,
|
getSpaceRoomsFromGroupSelections,
|
||||||
makeCommentFilter,
|
makeCommentFilter,
|
||||||
@@ -267,9 +265,11 @@ const syncSpace = (url: string) => {
|
|||||||
{kinds: [RELAY_MEMBERS]},
|
{kinds: [RELAY_MEMBERS]},
|
||||||
{kinds: [ROOM_META, ROOM_DELETE]},
|
{kinds: [ROOM_META, ROOM_DELETE]},
|
||||||
{kinds: [ROOM_ADMINS, ROOM_MEMBERS]},
|
{kinds: [ROOM_ADMINS, ROOM_MEMBERS]},
|
||||||
|
{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER]},
|
||||||
{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER]},
|
{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER]},
|
||||||
...MESSAGE_KINDS.map(kind => ({kinds: [kind]})),
|
...MESSAGE_KINDS.map(kind => ({kinds: [kind]})),
|
||||||
makeCommentFilter(CONTENT_KINDS),
|
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
|
// DMs
|
||||||
|
|
||||||
const syncDMRelay = (url: string, pubkey: string) => {
|
const syncDMRelay = (url: string, pubkey: string) => {
|
||||||
@@ -479,7 +417,7 @@ const syncDMs = () => {
|
|||||||
// Merge all synchronization functions
|
// Merge all synchronization functions
|
||||||
|
|
||||||
export const syncApplicationData = () => {
|
export const syncApplicationData = () => {
|
||||||
const unsubscribers = [syncRelays(), syncUserData(), syncSpaces(), syncRooms(), syncDMs()]
|
const unsubscribers = [syncRelays(), syncUserData(), syncSpaces(), syncDMs()]
|
||||||
|
|
||||||
return () => unsubscribers.forEach(call)
|
return () => unsubscribers.forEach(call)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type {NodeViewProps} from "@tiptap/core"
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfileDisplay} from "@welshman/app"
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
|
|
||||||
export const makeMentionNodeView =
|
export const makeMentionNodeView =
|
||||||
(url?: string) =>
|
(url?: string) =>
|
||||||
({node}: NodeViewProps) => {
|
({node}: NodeViewProps) => {
|
||||||
const dom = document.createElement("span")
|
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")
|
dom.classList.add("tiptap-object")
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {displayPubkey} from "@welshman/util"
|
import {displayPubkey} from "@welshman/util"
|
||||||
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
||||||
import WotScore from "@app/components/WotScore.svelte"
|
import WotScore from "@app/components/WotScore.svelte"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
const {value, url}: Props = $props()
|
const {value, url}: Props = $props()
|
||||||
|
|
||||||
const pubkey = value
|
const pubkey = value
|
||||||
const profileDisplay = deriveProfileDisplay(pubkey, removeNil([url]))
|
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url]))
|
||||||
const handle = deriveHandleForPubkey(pubkey)
|
const handle = deriveHandleForPubkey(pubkey)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
||||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||||
import {
|
import {PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO, SIGNER_RELAYS} from "@app/core/state"
|
||||||
NIP46_PERMS,
|
|
||||||
PLATFORM_URL,
|
|
||||||
PLATFORM_NAME,
|
|
||||||
PLATFORM_LOGO,
|
|
||||||
SIGNER_RELAYS,
|
|
||||||
} from "@app/core/state"
|
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
export class Nip46Controller {
|
export class Nip46Controller {
|
||||||
@@ -25,7 +19,6 @@ export class Nip46Controller {
|
|||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
const url = await this.broker.makeNostrconnectUrl({
|
const url = await this.broker.makeNostrconnectUrl({
|
||||||
perms: NIP46_PERMS,
|
|
||||||
url: PLATFORM_URL,
|
url: PLATFORM_URL,
|
||||||
name: PLATFORM_NAME,
|
name: PLATFORM_NAME,
|
||||||
image: PLATFORM_LOGO,
|
image: PLATFORM_LOGO,
|
||||||
|
|||||||
@@ -58,14 +58,18 @@ export const trustPolicy = (socket: Socket) => {
|
|||||||
export const mostlyRestrictedPolicy = (socket: Socket) => {
|
export const mostlyRestrictedPolicy = (socket: Socket) => {
|
||||||
let total = 0
|
let total = 0
|
||||||
let restricted = 0
|
let restricted = 0
|
||||||
let error = ""
|
|
||||||
|
|
||||||
const pending = new Set<string>()
|
const pending = new Set<string>()
|
||||||
|
|
||||||
const updateStatus = () =>
|
const updateStatus = (error?: string) => {
|
||||||
relaysMostlyRestricted.update(
|
if (restricted > total / 2) {
|
||||||
restricted > total / 2 ? assoc(socket.url, error) : dissoc(socket.url),
|
if (error) {
|
||||||
)
|
return relaysMostlyRestricted.update(assoc(socket.url, error))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
relaysMostlyRestricted.update(dissoc(socket.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const unsubscribers = [
|
const unsubscribers = [
|
||||||
on(socket, SocketEvent.Receive, (message: RelayMessage) => {
|
on(socket, SocketEvent.Receive, (message: RelayMessage) => {
|
||||||
@@ -83,8 +87,7 @@ export const mostlyRestrictedPolicy = (socket: Socket) => {
|
|||||||
|
|
||||||
if (details.startsWith("restricted: ")) {
|
if (details.startsWith("restricted: ")) {
|
||||||
restricted++
|
restricted++
|
||||||
error = details
|
updateStatus(details)
|
||||||
updateStatus()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +106,7 @@ export const mostlyRestrictedPolicy = (socket: Socket) => {
|
|||||||
|
|
||||||
if (details.startsWith("restricted: ")) {
|
if (details.startsWith("restricted: ")) {
|
||||||
restricted++
|
restricted++
|
||||||
error = details
|
updateStatus(details)
|
||||||
updateStatus()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
</script>
|
||||||
|
|
||||||
<div class="btn flex h-[unset] w-full flex-nowrap py-4 text-left {props.class}">
|
<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 flex-grow flex-row items-start gap-4">
|
||||||
<div class="flex h-14 w-12 flex-shrink-0 items-center">
|
<div class="flex h-14 w-12 flex-shrink-0 items-center justify-center">
|
||||||
{@render props.icon?.()}
|
{@render props.icon?.()}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
const extraClass = $derived(
|
const extraClass = $derived(
|
||||||
!fullscreen &&
|
!fullscreen &&
|
||||||
cx(
|
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",
|
"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",
|
"sm:p-6 sm:max-h-[90vh] sm:w-[520px] sm:rounded-box sm:relative sm:top-0",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,13 +4,15 @@
|
|||||||
type Props = {
|
type Props = {
|
||||||
src: string
|
src: string
|
||||||
alt: string
|
alt: string
|
||||||
|
size?: number
|
||||||
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const {src, alt}: Props = $props()
|
const {src, alt, size = 5, ...props}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if src.includes("image/svg") || src.endsWith(".svg")}
|
{#if src.includes("image/svg") || src.endsWith(".svg")}
|
||||||
<Icon icon={src} />
|
<Icon icon={src} {size} class={props.class} />
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
const {...props}: Props = $props()
|
const {...props}: Props = $props()
|
||||||
</script>
|
</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
|
<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">
|
<div class="ellipsize flex items-center gap-4 whitespace-nowrap">
|
||||||
{@render props.icon?.()}
|
{@render props.icon?.()}
|
||||||
{@render props.title?.()}
|
{@render props.title?.()}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{#if href}
|
{#if href}
|
||||||
<a {href} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
<a {href} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
||||||
<div
|
<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:bg-base-300={active}
|
||||||
class:tooltip={title}
|
class:tooltip={title}
|
||||||
data-tip={title}>
|
data-tip={title}>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<Button {onclick} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
<Button {onclick} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
||||||
<div
|
<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:bg-base-300={active}
|
||||||
class:tooltip={title}
|
class:tooltip={title}
|
||||||
data-tip={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}
|
{#if inviteData}
|
||||||
{#key inviteData.url}
|
{#key inviteData.url}
|
||||||
<Button
|
<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)}>
|
onclick={() => openSpace(inviteData.url, inviteData.claim)}>
|
||||||
<RelaySummary url={inviteData.url} />
|
<RelaySummary url={inviteData.url} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each relaySearch.searchOptions(term).slice(0, limit) as relay (relay.url)}
|
{#each relaySearch.searchOptions(term).slice(0, limit) as relay (relay.url)}
|
||||||
<Button
|
<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)}>
|
onclick={() => openSpace(relay.url)}>
|
||||||
<RelaySummary url={relay.url} />
|
<RelaySummary url={relay.url} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<Button onclick={addSpace}>
|
<Button onclick={addSpace}>
|
||||||
<CardButton class="btn-neutral">
|
<CardButton class="btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon={AddCircle} size={7} /></div>
|
<Icon icon={AddCircle} size={7} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Add a space</div>
|
<div>Add a space</div>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<Link href="/discover">
|
<Link href="/discover">
|
||||||
<CardButton class="btn-neutral">
|
<CardButton class="btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon={Compass} size={7} /></div>
|
<Icon icon={Compass} size={7} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Browse the network</div>
|
<div>Browse the network</div>
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<Button onclick={openChat}>
|
<Button onclick={openChat}>
|
||||||
<CardButton class="btn-neutral">
|
<CardButton class="btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon={ChatRound} size={7} /></div>
|
<Icon icon={ChatRound} size={7} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Start a conversation</div>
|
<div>Start a conversation</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<h1 class="mb-4 text-center text-5xl font-bold uppercase">{PLATFORM_NAME}</h1>
|
<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">
|
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||||
{#if Capacitor.getPlatform() !== "ios"}
|
{#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>
|
<h3 class="text-2xl sm:h-12">Donate</h3>
|
||||||
<p class="sm:h-16">Funds will be used to support development.</p>
|
<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">
|
<Link external href="https://geyser.fund/project/flotilla" class="btn btn-primary">
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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>
|
<h3 class="text-2xl sm:h-12">Get in touch</h3>
|
||||||
<p class="sm:h-16">Having problems? Let us know.</p>
|
<p class="sm:h-16">Having problems? Let us know.</p>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="content column gap-4" {onsubmit}>
|
<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>
|
<strong class="text-lg">Content Settings</strong>
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
import Button from "@lib/components/Button.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 ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||||
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
||||||
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
||||||
@@ -44,11 +44,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="content column gap-4">
|
<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 justify-between gap-2">
|
||||||
<div class="flex max-w-full gap-3">
|
<div class="flex max-w-full gap-3">
|
||||||
<div class="py-1">
|
<div class="py-1">
|
||||||
<Avatar src={$profile?.picture} size={10} />
|
<ProfileCircle pubkey={$pubkey!} size={10} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-col">
|
<div class="flex min-w-0 flex-col">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
{#if $session?.email}
|
{#if $session?.email}
|
||||||
<div class="card2 bg-alt col-4 shadow-xl">
|
<div class="card2 bg-alt col-4 shadow-md">
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
<p>Email Address</p>
|
<p>Email Address</p>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
</FieldInline>
|
</FieldInline>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="card2 bg-alt col-4 shadow-xl">
|
<div class="card2 bg-alt col-4 shadow-md">
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
<p class="flex items-center gap-3">
|
<p class="flex items-center gap-3">
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<SignerStatus />
|
<SignerStatus />
|
||||||
</div>
|
</div>
|
||||||
<div class="card2 bg-alt shadow-xl">
|
<div class="card2 bg-alt shadow-md">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<strong class="flex items-center gap-3">
|
<strong class="flex items-center gap-3">
|
||||||
<Icon icon={Settings} />
|
<Icon icon={Settings} />
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="content column gap-4">
|
<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()}
|
{#snippet title()}
|
||||||
<h2 class="flex items-center gap-3 text-xl">
|
<h2 class="flex items-center gap-3 text-xl">
|
||||||
<Icon icon={Globus} />
|
<Icon icon={Globus} />
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
|
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<h2 class="flex items-center gap-3 text-xl">
|
<h2 class="flex items-center gap-3 text-xl">
|
||||||
<Icon icon={Inbox} />
|
<Icon icon={Inbox} />
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
|
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<h2 class="flex items-center gap-3 text-xl">
|
<h2 class="flex items-center gap-3 text-xl">
|
||||||
<Icon icon={Mailbox} />
|
<Icon icon={Mailbox} />
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="content column gap-4">
|
<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">
|
<div class="flex items-center justify-between">
|
||||||
<strong class="flex items-center gap-3">
|
<strong class="flex items-center gap-3">
|
||||||
<Icon icon={Wallet2} />
|
<Icon icon={Wallet2} />
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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}>
|
class:gap-6={profileLightningAddress && walletLud16 && profile?.lud16 !== walletLud16}>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<strong>Lightning Address</strong>
|
<strong>Lightning Address</strong>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import PageContent from "@lib/components/PageContent.svelte"
|
import PageContent from "@lib/components/PageContent.svelte"
|
||||||
import MenuSpacesItem from "@app/components/MenuSpacesItem.svelte"
|
import MenuSpacesItem from "@app/components/MenuSpacesItem.svelte"
|
||||||
import SpaceAdd from "@app/components/SpaceAdd.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"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const addSpace = () => pushModal(SpaceAdd)
|
const addSpace = () => pushModal(SpaceAdd)
|
||||||
@@ -37,17 +37,24 @@
|
|||||||
{#each PLATFORM_RELAYS as url (url)}
|
{#each PLATFORM_RELAYS as url (url)}
|
||||||
<MenuSpacesItem {url} />
|
<MenuSpacesItem {url} />
|
||||||
{:else}
|
{:else}
|
||||||
{#each $userSpaceUrls as url (url)}
|
{#await loadUserGroupSelections()}
|
||||||
<MenuSpacesItem {url} />
|
<div class="flex justify-center items-center py-20">
|
||||||
{:else}
|
<span class="loading loading-spinner mr-3"></span>
|
||||||
<div class="flex flex-col gap-8 items-center py-20">
|
Loading your spaces...
|
||||||
<p>You haven't added any spaces yet!</p>
|
|
||||||
<Button class="btn btn-primary" onclick={addSpace}>
|
|
||||||
<Icon icon={AddCircle} />
|
|
||||||
Add a Space
|
|
||||||
</Button>
|
|
||||||
</div>
|
</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}
|
{/each}
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -16,12 +16,10 @@
|
|||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {pubkey, publishThunk, waitForThunkError, joinRoom, leaveRoom} from "@welshman/app"
|
import {pubkey, publishThunk, waitForThunkError, joinRoom, leaveRoom} from "@welshman/app"
|
||||||
import {slide, fade, fly} from "@lib/transition"
|
import {slide, fade, fly} from "@lib/transition"
|
||||||
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
import InfoCircle from "@assets/icons/info-circle.svg?dataurl"
|
||||||
import Pen from "@assets/icons/pen.svg?dataurl"
|
|
||||||
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
|
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
|
||||||
import Login2 from "@assets/icons/login-3.svg?dataurl"
|
import Login2 from "@assets/icons/login-3.svg?dataurl"
|
||||||
import AltArrowDown from "@assets/icons/alt-arrow-down.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 Bookmark from "@assets/icons/bookmark.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -31,8 +29,9 @@
|
|||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||||
import RoomEdit from "@app/components/RoomEdit.svelte"
|
|
||||||
import RoomName from "@app/components/RoomName.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 RoomItem from "@app/components/RoomItem.svelte"
|
||||||
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
||||||
import RoomItemRemoveMember from "@src/app/components/RoomItemRemoveMember.svelte"
|
import RoomItemRemoveMember from "@src/app/components/RoomItemRemoveMember.svelte"
|
||||||
@@ -48,7 +47,6 @@
|
|||||||
MembershipStatus,
|
MembershipStatus,
|
||||||
PROTECTED,
|
PROTECTED,
|
||||||
MESSAGE_KINDS,
|
MESSAGE_KINDS,
|
||||||
deriveUserIsRoomAdmin,
|
|
||||||
} from "@app/core/state"
|
} from "@app/core/state"
|
||||||
import {setChecked, checked} from "@app/util/notifications"
|
import {setChecked, checked} from "@app/util/notifications"
|
||||||
import {
|
import {
|
||||||
@@ -70,10 +68,11 @@
|
|||||||
const room = deriveRoom(url, h)
|
const room = deriveRoom(url, h)
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
const userRooms = deriveUserRooms(url)
|
const userRooms = deriveUserRooms(url)
|
||||||
const userIsAdmin = deriveUserIsRoomAdmin(url, h)
|
|
||||||
const isFavorite = $derived($userRooms.includes(h))
|
const isFavorite = $derived($userRooms.includes(h))
|
||||||
const membershipStatus = deriveUserRoomMembershipStatus(url, h)
|
const membershipStatus = deriveUserRoomMembershipStatus(url, h)
|
||||||
|
|
||||||
|
const showRoomDetail = () => pushModal(RoomDetail, {url, h})
|
||||||
|
|
||||||
const addFavorite = () => addRoomMembership(url, h)
|
const addFavorite = () => addRoomMembership(url, h)
|
||||||
|
|
||||||
const removeFavorite = () => removeRoomMembership(url, h)
|
const removeFavorite = () => removeRoomMembership(url, h)
|
||||||
@@ -86,10 +85,10 @@
|
|||||||
|
|
||||||
if (message && !message.startsWith("duplicate:")) {
|
if (message && !message.startsWith("duplicate:")) {
|
||||||
return pushToast({theme: "error", message})
|
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 {
|
} finally {
|
||||||
joining = false
|
joining = false
|
||||||
}
|
}
|
||||||
@@ -303,8 +302,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startEdit = () => pushModal(RoomEdit, {url, h})
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
if (dynamicPadding && chatCompose) {
|
if (dynamicPadding && chatCompose) {
|
||||||
@@ -334,57 +331,25 @@
|
|||||||
|
|
||||||
<PageBar>
|
<PageBar>
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div class="center">
|
<RoomImage {url} {h} />
|
||||||
<Icon icon={Hashtag} />
|
|
||||||
</div>
|
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<strong class="ellipsize">
|
<RoomName {url} {h} />
|
||||||
<RoomName {url} {h} />
|
|
||||||
</strong>
|
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet action()}
|
{#snippet action()}
|
||||||
<div class="row-2">
|
<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
|
<Button
|
||||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||||
data-tip={isFavorite ? "Remove Favorite" : "Add Favorite"}
|
data-tip={isFavorite ? "Remove Favorite" : "Add Favorite"}
|
||||||
onclick={isFavorite ? removeFavorite : addFavorite}>
|
onclick={isFavorite ? removeFavorite : addFavorite}>
|
||||||
<Icon size={4} icon={Bookmark} class={cx({"text-primary": isFavorite})} />
|
<Icon size={4} icon={Bookmark} class={cx({"text-primary": isFavorite})} />
|
||||||
</Button>
|
</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} />
|
<MenuSpaceButton {url} />
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<li>Requires some technical skills</li>
|
<li>Requires some technical skills</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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
|
Get Started
|
||||||
<Icon icon={ArrowRight} />
|
<Icon icon={ArrowRight} />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
<li>Support available</li>
|
<li>Support available</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<Link class="btn btn-neutral" href="https://relay.tools/signup">
|
<Link external class="btn btn-neutral" href="https://relay.tools/signup">
|
||||||
Get Started
|
Get Started
|
||||||
<Icon icon={ArrowRight} />
|
<Icon icon={ArrowRight} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
Reference in New Issue
Block a user